@malloydata/malloy 0.0.245 → 0.0.246-dev250320220920
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dialect/pg_impl.js +1 -1
- package/dist/dialect/postgres/postgres.js +1 -1
- package/dist/dialect/snowflake/snowflake.js +4 -2
- package/dist/lang/ast/expressions/expr-filter-expr.js +4 -0
- package/dist/lang/ast/expressions/expr-now.js +1 -1
- package/dist/lang/test/parse-expects.js +2 -1
- package/dist/model/filter_compilers.d.ts +6 -6
- package/dist/model/filter_compilers.js +13 -9
- package/dist/model/filter_temporal_compiler.d.ts +28 -0
- package/dist/model/filter_temporal_compiler.js +325 -0
- package/dist/model/malloy_query.js +7 -1
- package/dist/model/malloy_types.d.ts +4 -1
- package/dist/model/malloy_types.js +5 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
package/dist/dialect/pg_impl.js
CHANGED
|
@@ -52,7 +52,7 @@ class PostgresBase extends dialect_1.Dialect {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
const extracted = `EXTRACT(${units} FROM ${extractFrom})`;
|
|
55
|
-
return from.units === 'day_of_week' ? `
|
|
55
|
+
return from.units === 'day_of_week' ? `(${extracted}+1)` : extracted;
|
|
56
56
|
}
|
|
57
57
|
sqlCast(qi, cast) {
|
|
58
58
|
const expr = cast.e.sql || '';
|
|
@@ -257,7 +257,7 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
|
|
|
257
257
|
timeframe = 'day';
|
|
258
258
|
n = `${n}*7`;
|
|
259
259
|
}
|
|
260
|
-
const interval = `make_interval(${pgMakeIntervalMap[timeframe]}
|
|
260
|
+
const interval = `make_interval(${pgMakeIntervalMap[timeframe]}=>(${n})::integer)`;
|
|
261
261
|
return `(${df.kids.base.sql})${df.op}${interval}`;
|
|
262
262
|
}
|
|
263
263
|
sqlCast(qi, cast) {
|
|
@@ -270,8 +270,10 @@ ${(0, utils_1.indent)(sql)}
|
|
|
270
270
|
return `EXTRACT(${extractUnits} FROM ${extractFrom})`;
|
|
271
271
|
}
|
|
272
272
|
sqlAlterTimeExpr(df) {
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
var _a;
|
|
274
|
+
const add = ((_a = df.typeDef) === null || _a === void 0 ? void 0 : _a.type) === 'date' ? 'DATEADD' : 'TIMESTAMPADD';
|
|
275
|
+
const n = df.op === '+' ? df.kids.delta.sql : `-(${df.kids.delta.sql})`;
|
|
276
|
+
return `${add}(${df.units},${n},${df.kids.base.sql})`;
|
|
275
277
|
}
|
|
276
278
|
atTz(sqlExpr, tz) {
|
|
277
279
|
if (tz !== undefined) {
|
|
@@ -21,6 +21,7 @@ class ExprFilterExpression extends expression_def_1.ExpressionDef {
|
|
|
21
21
|
return this.loggedErrorExpr('filter-expression-type', 'Filter expression illegal here');
|
|
22
22
|
}
|
|
23
23
|
apply(fs, op, left, _warnOnComplexTree = false) {
|
|
24
|
+
var _a;
|
|
24
25
|
if (op === '~' || op === '!~') {
|
|
25
26
|
const matchExpr = left.getExpression(fs);
|
|
26
27
|
if (matchExpr.type === 'error') {
|
|
@@ -35,6 +36,9 @@ class ExprFilterExpression extends expression_def_1.ExpressionDef {
|
|
|
35
36
|
fParse = malloy_filter_1.NumberFilterExpression.parse(this.filterText);
|
|
36
37
|
break;
|
|
37
38
|
case 'boolean':
|
|
39
|
+
if ((_a = fs.dialectObj()) === null || _a === void 0 ? void 0 : _a.booleanAsNumbers) {
|
|
40
|
+
return this.loggedErrorExpr('filter-expression-type', 'Boolean filters not supported on this connection type');
|
|
41
|
+
}
|
|
38
42
|
fParse = malloy_filter_1.BooleanFilterExpression.parse(this.filterText);
|
|
39
43
|
break;
|
|
40
44
|
case 'date':
|
|
@@ -36,7 +36,7 @@ class ExprNow extends expression_def_1.ExpressionDef {
|
|
|
36
36
|
expressionType: 'scalar',
|
|
37
37
|
// `now` is considered to be a constant, at least in the dialects we support today
|
|
38
38
|
evalSpace: 'constant',
|
|
39
|
-
value: { node: 'now' },
|
|
39
|
+
value: { node: 'now', typeDef: { type: 'timestamp' } },
|
|
40
40
|
compositeFieldUsage: (0, composite_source_utils_1.emptyCompositeFieldUsage)(),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
@@ -197,7 +197,8 @@ expect.extend({
|
|
|
197
197
|
if (!badRefs.pass) {
|
|
198
198
|
return badRefs;
|
|
199
199
|
}
|
|
200
|
-
const
|
|
200
|
+
const toExpr = bx.generated().value;
|
|
201
|
+
const rcvExpr = (0, expr_to_str_1.exprToStr)(toExpr, undefined);
|
|
201
202
|
const pass = this.equals(rcvExpr, expr);
|
|
202
203
|
const msg = pass ? `Matched: ${rcvExpr}` : this.utils.diff(expr, rcvExpr);
|
|
203
204
|
return { pass, message: () => `${msg}` };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BooleanFilter, FilterExpression, NumberFilter, StringFilter, TemporalFilter } from '@malloydata/malloy-filter';
|
|
2
2
|
import type { Dialect } from '../dialect';
|
|
3
3
|
export declare const FilterCompilers: {
|
|
4
|
-
compile(t: string, c:
|
|
5
|
-
numberCompile(nc:
|
|
6
|
-
booleanCompile(bc:
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
compile(t: string, c: FilterExpression, x: string, d: Dialect): string;
|
|
5
|
+
numberCompile(nc: NumberFilter, x: string, d: Dialect): string;
|
|
6
|
+
booleanCompile(bc: BooleanFilter, x: string, _d: Dialect): string;
|
|
7
|
+
stringCompile(sc: StringFilter, x: string, d: Dialect): string;
|
|
8
|
+
temporalCompile(tc: TemporalFilter, x: string, d: Dialect): string;
|
|
9
9
|
};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.FilterCompilers = void 0;
|
|
10
10
|
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
11
|
+
const filter_temporal_compiler_1 = require("./filter_temporal_compiler");
|
|
11
12
|
function escapeForLike(v) {
|
|
12
13
|
return v.replace(/([%_\\])/g, '\\$1');
|
|
13
14
|
}
|
|
@@ -36,16 +37,16 @@ function unlike(disLiked, x) {
|
|
|
36
37
|
*/
|
|
37
38
|
exports.FilterCompilers = {
|
|
38
39
|
compile(t, c, x, d) {
|
|
39
|
-
if (t === 'string' && (0, malloy_filter_1.
|
|
40
|
+
if (t === 'string' && (0, malloy_filter_1.isStringFilter)(c)) {
|
|
40
41
|
return exports.FilterCompilers.stringCompile(c, x, d);
|
|
41
42
|
}
|
|
42
|
-
else if (t === 'number' && (0, malloy_filter_1.
|
|
43
|
+
else if (t === 'number' && (0, malloy_filter_1.isNumberFilter)(c)) {
|
|
43
44
|
return exports.FilterCompilers.numberCompile(c, x, d);
|
|
44
45
|
}
|
|
45
|
-
else if (t === 'boolean' && (0, malloy_filter_1.
|
|
46
|
+
else if (t === 'boolean' && (0, malloy_filter_1.isBooleanFilter)(c)) {
|
|
46
47
|
return exports.FilterCompilers.booleanCompile(c, x, d);
|
|
47
48
|
}
|
|
48
|
-
else if ((t === 'date' || t === 'timestamp') && (0, malloy_filter_1.
|
|
49
|
+
else if ((t === 'date' || t === 'timestamp') && (0, malloy_filter_1.isTemporalFilter)(c)) {
|
|
49
50
|
return exports.FilterCompilers.temporalCompile(c, x, d);
|
|
50
51
|
}
|
|
51
52
|
throw new Error('INTERNAL ERROR: No filter compiler for ' + t);
|
|
@@ -93,7 +94,7 @@ exports.FilterCompilers = {
|
|
|
93
94
|
case 'or':
|
|
94
95
|
return nc.members
|
|
95
96
|
.map(m => exports.FilterCompilers.numberCompile(m, x, d))
|
|
96
|
-
.join(` ${nc.operator.toUpperCase()}`);
|
|
97
|
+
.join(` ${nc.operator.toUpperCase()} `);
|
|
97
98
|
}
|
|
98
99
|
},
|
|
99
100
|
booleanCompile(bc, x, _d) {
|
|
@@ -108,10 +109,6 @@ exports.FilterCompilers = {
|
|
|
108
109
|
return x;
|
|
109
110
|
}
|
|
110
111
|
},
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
112
|
-
temporalCompile(tc, x, d) {
|
|
113
|
-
return 'false';
|
|
114
|
-
},
|
|
115
112
|
stringCompile(sc, x, d) {
|
|
116
113
|
switch (sc.operator) {
|
|
117
114
|
case 'null':
|
|
@@ -171,6 +168,8 @@ exports.FilterCompilers = {
|
|
|
171
168
|
/*
|
|
172
169
|
* Basic formula over all members
|
|
173
170
|
* ALL INCLUDED THINGS OR TOGETHER AND ALL EXCLUDED THINGS ANDED TOGETHER
|
|
171
|
+
*
|
|
172
|
+
* mtoy todo write some tests to see if AND clauses are includes or excludes
|
|
174
173
|
*/
|
|
175
174
|
const includes = [];
|
|
176
175
|
const excludes = [];
|
|
@@ -243,5 +242,10 @@ exports.FilterCompilers = {
|
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
244
|
},
|
|
245
|
+
// mtoy todo figure out what to do about dates
|
|
246
|
+
temporalCompile(tc, x, d) {
|
|
247
|
+
const c = new filter_temporal_compiler_1.TemporalFilterCompiler(x, d);
|
|
248
|
+
return c.compile(tc);
|
|
249
|
+
},
|
|
246
250
|
};
|
|
247
251
|
//# sourceMappingURL=filter_compilers.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TemporalFilter } from '@malloydata/malloy-filter';
|
|
2
|
+
import type { Dialect } from '../dialect';
|
|
3
|
+
/**
|
|
4
|
+
* I felt like there was enough "helpful functions needed to make everything
|
|
5
|
+
* work, all of which need to know the dialect", to justify making a class
|
|
6
|
+
* for this. Maybe this should just be a set of functions which take
|
|
7
|
+
* a dialect as an argument?
|
|
8
|
+
*/
|
|
9
|
+
export declare class TemporalFilterCompiler {
|
|
10
|
+
readonly expr: string;
|
|
11
|
+
readonly timetype: 'timestamp' | 'date';
|
|
12
|
+
readonly d: Dialect;
|
|
13
|
+
constructor(expr: string, dialect: Dialect, timetype?: 'timestamp' | 'date');
|
|
14
|
+
compile(tc: TemporalFilter): string;
|
|
15
|
+
private expandLiteral;
|
|
16
|
+
private literalNode;
|
|
17
|
+
private nowExpr;
|
|
18
|
+
private n;
|
|
19
|
+
private delta;
|
|
20
|
+
private dayofWeek;
|
|
21
|
+
private nowDot;
|
|
22
|
+
private thisUnit;
|
|
23
|
+
private lastUnit;
|
|
24
|
+
private nextUnit;
|
|
25
|
+
mod7(n: string): string;
|
|
26
|
+
private moment;
|
|
27
|
+
private isIn;
|
|
28
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.TemporalFilterCompiler = void 0;
|
|
10
|
+
const malloy_types_1 = require("./malloy_types");
|
|
11
|
+
const luxon_1 = require("luxon");
|
|
12
|
+
const fYear = 'yyyy';
|
|
13
|
+
const fMonth = `${fYear}-LL`;
|
|
14
|
+
const fDay = `${fMonth}-dd`;
|
|
15
|
+
const fHour = `${fDay} HH`;
|
|
16
|
+
const fMinute = `${fHour}:mm`;
|
|
17
|
+
const fTimestamp = `${fMinute}:ss`;
|
|
18
|
+
/**
|
|
19
|
+
* I felt like there was enough "helpful functions needed to make everything
|
|
20
|
+
* work, all of which need to know the dialect", to justify making a class
|
|
21
|
+
* for this. Maybe this should just be a set of functions which take
|
|
22
|
+
* a dialect as an argument?
|
|
23
|
+
*/
|
|
24
|
+
class TemporalFilterCompiler {
|
|
25
|
+
constructor(expr, dialect, timetype = 'timestamp') {
|
|
26
|
+
this.expr = expr;
|
|
27
|
+
this.timetype = timetype;
|
|
28
|
+
this.d = dialect;
|
|
29
|
+
}
|
|
30
|
+
compile(tc) {
|
|
31
|
+
const x = this.expr;
|
|
32
|
+
switch (tc.operator) {
|
|
33
|
+
case 'after':
|
|
34
|
+
return `${x} ${tc.not ? '<' : '>='} ${this.moment(tc.after).end}`;
|
|
35
|
+
case 'before':
|
|
36
|
+
return `${x} ${tc.not ? '>=' : '<'} ${this.moment(tc.before).begin}`;
|
|
37
|
+
case 'in': {
|
|
38
|
+
// mtoy todo in now
|
|
39
|
+
const m = this.moment(tc.in);
|
|
40
|
+
return this.isIn(tc.not, m.begin.sql, m.end);
|
|
41
|
+
}
|
|
42
|
+
case 'for': {
|
|
43
|
+
const start = this.moment(tc.begin);
|
|
44
|
+
const end = this.delta(start.begin, '+', tc.n, tc.units);
|
|
45
|
+
return this.isIn(tc.not, start.begin.sql, end.sql);
|
|
46
|
+
}
|
|
47
|
+
case 'in_last': {
|
|
48
|
+
// last N units means "N - 1 UNITS AGO FOR N UNITS"
|
|
49
|
+
const back = Number(tc.n) - 1;
|
|
50
|
+
const thisUnit = this.nowDot(tc.units);
|
|
51
|
+
const start = back > 0
|
|
52
|
+
? this.delta(thisUnit, '-', back.toString(), tc.units)
|
|
53
|
+
: thisUnit;
|
|
54
|
+
const end = this.delta(thisUnit, '+', '1', tc.units);
|
|
55
|
+
return this.isIn(tc.not, start.sql, end.sql);
|
|
56
|
+
}
|
|
57
|
+
case 'to': {
|
|
58
|
+
const firstMoment = this.moment(tc.fromMoment);
|
|
59
|
+
const lastMoment = this.moment(tc.toMoment);
|
|
60
|
+
return this.isIn(tc.not, firstMoment.begin.sql, lastMoment.end);
|
|
61
|
+
}
|
|
62
|
+
case 'last': {
|
|
63
|
+
const thisUnit = this.nowDot(tc.units);
|
|
64
|
+
const start = this.delta(thisUnit, '-', tc.n, tc.units);
|
|
65
|
+
return this.isIn(tc.not, start.sql, thisUnit.sql);
|
|
66
|
+
}
|
|
67
|
+
case 'next': {
|
|
68
|
+
const thisUnit = this.nowDot(tc.units);
|
|
69
|
+
const start = this.delta(thisUnit, '+', '1', tc.units);
|
|
70
|
+
const end = this.delta(thisUnit, '+', (Number(tc.n) + 1).toString(), tc.units);
|
|
71
|
+
return this.isIn(tc.not, start.sql, end.sql);
|
|
72
|
+
}
|
|
73
|
+
case 'null':
|
|
74
|
+
return tc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
|
|
75
|
+
case '()': {
|
|
76
|
+
const wrapped = '(' + this.compile(tc.expr) + ')';
|
|
77
|
+
return tc.not ? `NOT ${wrapped}` : wrapped;
|
|
78
|
+
}
|
|
79
|
+
case 'and':
|
|
80
|
+
case 'or':
|
|
81
|
+
return tc.members
|
|
82
|
+
.map(m => this.compile(m))
|
|
83
|
+
.join(` ${tc.operator.toUpperCase()} `);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
expandLiteral(tl) {
|
|
87
|
+
let literal = tl.literal;
|
|
88
|
+
switch (tl.units) {
|
|
89
|
+
case 'year': {
|
|
90
|
+
const y = luxon_1.DateTime.fromFormat(literal, fYear);
|
|
91
|
+
const begin = this.literalNode(y.toFormat(fTimestamp));
|
|
92
|
+
const next = y.plus({ year: 1 });
|
|
93
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
94
|
+
}
|
|
95
|
+
case 'month': {
|
|
96
|
+
const yyyymm = luxon_1.DateTime.fromFormat(literal, fMonth);
|
|
97
|
+
const begin = this.literalNode(yyyymm.toFormat(fTimestamp));
|
|
98
|
+
const next = yyyymm.plus({ month: 1 });
|
|
99
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
100
|
+
}
|
|
101
|
+
case 'day': {
|
|
102
|
+
const yyyymmdd = luxon_1.DateTime.fromFormat(literal, fDay);
|
|
103
|
+
const begin = this.literalNode(yyyymmdd.toFormat(fTimestamp));
|
|
104
|
+
const next = yyyymmdd.plus({ day: 1 });
|
|
105
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
106
|
+
}
|
|
107
|
+
case 'hour': {
|
|
108
|
+
const ymdh = luxon_1.DateTime.fromFormat(literal, fHour);
|
|
109
|
+
const begin = this.literalNode(ymdh.toFormat(fTimestamp));
|
|
110
|
+
const next = ymdh.plus({ hour: 1 });
|
|
111
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
112
|
+
}
|
|
113
|
+
case 'minute': {
|
|
114
|
+
const ymdhm = luxon_1.DateTime.fromFormat(literal, fMinute);
|
|
115
|
+
const begin = this.literalNode(ymdhm.toFormat(fTimestamp));
|
|
116
|
+
const next = ymdhm.plus({ minute: 1 });
|
|
117
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
118
|
+
}
|
|
119
|
+
case 'week': {
|
|
120
|
+
const a = luxon_1.DateTime.fromFormat(literal.slice(0, 10), fDay);
|
|
121
|
+
// Luxon uses monday weeks, so look for the Monday week which contains
|
|
122
|
+
// the day after, which for all days except Sunday is the same as
|
|
123
|
+
// the sunday week, and on Sunday it is this monday week instead of
|
|
124
|
+
// last monday week.
|
|
125
|
+
const mondayWeek = a.plus({ day: 1 }).startOf('week');
|
|
126
|
+
// Now back that up by one day and we have the Sunday week
|
|
127
|
+
const ymd_wk = mondayWeek.minus({ day: 1 });
|
|
128
|
+
const begin = this.literalNode(ymd_wk.toFormat(fTimestamp));
|
|
129
|
+
const next = ymd_wk.plus({ days: 7 });
|
|
130
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
131
|
+
}
|
|
132
|
+
case 'quarter': {
|
|
133
|
+
const yyyy = literal.slice(0, 4);
|
|
134
|
+
const q = literal.slice(6);
|
|
135
|
+
if (q === '1') {
|
|
136
|
+
literal = `${yyyy}-01-01 00:00:00`;
|
|
137
|
+
}
|
|
138
|
+
else if (q === '2') {
|
|
139
|
+
literal = `${yyyy}-03-01 00:00:00`;
|
|
140
|
+
}
|
|
141
|
+
else if (q === '3') {
|
|
142
|
+
literal = `${yyyy}-06-01 00:00:00`;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
literal = `${yyyy}-09-01 00:00:00`;
|
|
146
|
+
}
|
|
147
|
+
const begin = this.literalNode(literal);
|
|
148
|
+
const ymd_q = luxon_1.DateTime.fromFormat(literal, fTimestamp);
|
|
149
|
+
const next = ymd_q.plus({ months: 3 });
|
|
150
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
151
|
+
}
|
|
152
|
+
case undefined:
|
|
153
|
+
case 'second':
|
|
154
|
+
return { begin: this.literalNode(literal), end: literal };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
literalNode(literal) {
|
|
158
|
+
const literalNode = {
|
|
159
|
+
node: 'timeLiteral',
|
|
160
|
+
typeDef: { type: 'timestamp' },
|
|
161
|
+
literal,
|
|
162
|
+
};
|
|
163
|
+
return { ...literalNode, sql: this.d.sqlLiteralTime({}, literalNode) };
|
|
164
|
+
}
|
|
165
|
+
nowExpr() {
|
|
166
|
+
return {
|
|
167
|
+
node: 'now',
|
|
168
|
+
typeDef: { type: 'timestamp' },
|
|
169
|
+
sql: this.d.sqlNowExpr(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
n(literal) {
|
|
173
|
+
return { node: 'numberLiteral', literal, sql: literal };
|
|
174
|
+
}
|
|
175
|
+
delta(from, op, n, units) {
|
|
176
|
+
const ret = {
|
|
177
|
+
node: 'delta',
|
|
178
|
+
op,
|
|
179
|
+
units,
|
|
180
|
+
kids: {
|
|
181
|
+
base: (0, malloy_types_1.mkTemporal)(from, 'timestamp'),
|
|
182
|
+
delta: this.n(n),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
return { ...ret, sql: this.d.sqlAlterTimeExpr(ret) };
|
|
186
|
+
}
|
|
187
|
+
dayofWeek(e) {
|
|
188
|
+
const t = {
|
|
189
|
+
node: 'extract',
|
|
190
|
+
e: (0, malloy_types_1.mkTemporal)(e, 'timestamp'),
|
|
191
|
+
units: 'day_of_week',
|
|
192
|
+
};
|
|
193
|
+
return { ...t, sql: this.d.sqlTimeExtractExpr({}, t) };
|
|
194
|
+
}
|
|
195
|
+
nowDot(units) {
|
|
196
|
+
const nowTruncExpr = {
|
|
197
|
+
node: 'trunc',
|
|
198
|
+
e: this.nowExpr(),
|
|
199
|
+
units,
|
|
200
|
+
};
|
|
201
|
+
return { ...nowTruncExpr, sql: this.d.sqlTruncExpr({}, nowTruncExpr) };
|
|
202
|
+
}
|
|
203
|
+
thisUnit(units) {
|
|
204
|
+
const thisUnit = this.nowDot(units);
|
|
205
|
+
const nextUnit = this.delta(thisUnit, '+', '1', units);
|
|
206
|
+
return { begin: thisUnit, end: nextUnit.sql };
|
|
207
|
+
}
|
|
208
|
+
lastUnit(units) {
|
|
209
|
+
const thisUnit = this.nowDot(units);
|
|
210
|
+
const lastUnit = this.delta(thisUnit, '-', '1', units);
|
|
211
|
+
return { begin: lastUnit, end: thisUnit.sql };
|
|
212
|
+
}
|
|
213
|
+
nextUnit(units) {
|
|
214
|
+
const thisUnit = this.nowDot(units);
|
|
215
|
+
const nextUnit = this.delta(thisUnit, '+', '1', units);
|
|
216
|
+
const next2Unit = this.delta(thisUnit, '+', '2', units);
|
|
217
|
+
return { begin: nextUnit, end: next2Unit.sql };
|
|
218
|
+
}
|
|
219
|
+
mod7(n) {
|
|
220
|
+
return this.d.hasModOperator ? `(${n})%7` : `MOD(${n},7)`;
|
|
221
|
+
}
|
|
222
|
+
moment(m) {
|
|
223
|
+
switch (m.moment) {
|
|
224
|
+
// mtoy todo moments which have no duration should have somethign in the interface?
|
|
225
|
+
case 'now': {
|
|
226
|
+
const now = this.nowExpr();
|
|
227
|
+
return { begin: now, end: now.sql };
|
|
228
|
+
}
|
|
229
|
+
case 'literal':
|
|
230
|
+
return this.expandLiteral(m);
|
|
231
|
+
case 'ago':
|
|
232
|
+
case 'from_now': {
|
|
233
|
+
// mtoy todo just pretending all units work, they don't
|
|
234
|
+
const nowTruncExpr = {
|
|
235
|
+
node: 'trunc',
|
|
236
|
+
e: this.nowExpr(),
|
|
237
|
+
units: m.units,
|
|
238
|
+
};
|
|
239
|
+
nowTruncExpr.sql = this.d.sqlTruncExpr({}, nowTruncExpr);
|
|
240
|
+
const nowTrunc = (0, malloy_types_1.mkTemporal)(nowTruncExpr, 'timestamp');
|
|
241
|
+
const beginExpr = this.delta(nowTrunc, m.moment === 'ago' ? '-' : '+', m.n, m.units);
|
|
242
|
+
// Now the end is one unit after that .. either n-1 units ago or n+1 units from now
|
|
243
|
+
if (m.moment === 'ago' && m.n === '1') {
|
|
244
|
+
return { begin: beginExpr, end: nowTruncExpr.sql };
|
|
245
|
+
}
|
|
246
|
+
const oneDifferent = Number(m.n) + m.moment === 'ago' ? -1 : 1;
|
|
247
|
+
const endExpr = {
|
|
248
|
+
...beginExpr,
|
|
249
|
+
kids: { base: nowTrunc, delta: this.n(oneDifferent.toString()) },
|
|
250
|
+
};
|
|
251
|
+
return { begin: beginExpr, end: this.d.sqlAlterTimeExpr(endExpr) };
|
|
252
|
+
}
|
|
253
|
+
case 'today':
|
|
254
|
+
return this.thisUnit('day');
|
|
255
|
+
case 'yesterday':
|
|
256
|
+
return this.lastUnit('day');
|
|
257
|
+
case 'tomorrow':
|
|
258
|
+
return this.nextUnit('day');
|
|
259
|
+
case 'this':
|
|
260
|
+
return this.thisUnit(m.units);
|
|
261
|
+
case 'last':
|
|
262
|
+
return this.lastUnit(m.units);
|
|
263
|
+
case 'next':
|
|
264
|
+
return this.nextUnit(m.units);
|
|
265
|
+
case 'monday':
|
|
266
|
+
case 'tuesday':
|
|
267
|
+
case 'wednesday':
|
|
268
|
+
case 'thursday':
|
|
269
|
+
case 'friday':
|
|
270
|
+
case 'saturday':
|
|
271
|
+
case 'sunday': {
|
|
272
|
+
const destDay = [
|
|
273
|
+
'sunday',
|
|
274
|
+
'monday',
|
|
275
|
+
'tuesday',
|
|
276
|
+
'wednesday',
|
|
277
|
+
'thursday',
|
|
278
|
+
'friday',
|
|
279
|
+
'saturday',
|
|
280
|
+
].indexOf(m.moment);
|
|
281
|
+
const dow = this.dayofWeek(this.nowExpr()).sql;
|
|
282
|
+
if (m.which === 'next') {
|
|
283
|
+
const nForwards = `${this.mod7(`${destDay}-(${dow}-1)+6`)}+1`;
|
|
284
|
+
const begin = this.delta(this.thisUnit('day').begin, '+', nForwards, 'day');
|
|
285
|
+
const end = this.delta(this.thisUnit('day').begin, '+', `${nForwards}+1`, 'day');
|
|
286
|
+
// console.log(
|
|
287
|
+
// `SELECT ${
|
|
288
|
+
// this.nowExpr().sql
|
|
289
|
+
// } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nForwards} as nForwards,\n ${
|
|
290
|
+
// begin.sql
|
|
291
|
+
// } as begin,\n ${end.sql} as end`
|
|
292
|
+
// );
|
|
293
|
+
return { begin, end: end.sql };
|
|
294
|
+
}
|
|
295
|
+
// dacks back = mod((daw0 - dst) + 6, 7) + 1;
|
|
296
|
+
// dacks back = mod(((daw - 1) - dst) + 6, 7) + 1;
|
|
297
|
+
// dacks back = mod(((daw) - dst) + 7, 7) + 1;
|
|
298
|
+
const nBack = `${this.mod7(`(${dow}-1)-${destDay}+6`)}+1`;
|
|
299
|
+
const begin = this.delta(this.thisUnit('day').begin, '-', nBack, 'day');
|
|
300
|
+
const end = this.delta(this.thisUnit('day').begin, '-', `(${nBack})-1`, 'day');
|
|
301
|
+
// console.log(
|
|
302
|
+
// `SELECT ${
|
|
303
|
+
// this.nowExpr().sql
|
|
304
|
+
// } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nBack} as nBack,\n ${
|
|
305
|
+
// begin.sql
|
|
306
|
+
// } as begin,\n ${end.sql} as end`
|
|
307
|
+
// );
|
|
308
|
+
return { begin, end: end.sql };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
isIn(notIn, begin, end) {
|
|
313
|
+
let begOp = '>=';
|
|
314
|
+
let endOp = '<';
|
|
315
|
+
let joinOp = 'AND';
|
|
316
|
+
if (notIn) {
|
|
317
|
+
joinOp = 'OR';
|
|
318
|
+
begOp = '<';
|
|
319
|
+
endOp = '>=';
|
|
320
|
+
}
|
|
321
|
+
return `${this.expr} ${begOp} ${begin} ${joinOp} ${this.expr} ${endOp} ${end}`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
exports.TemporalFilterCompiler = TemporalFilterCompiler;
|
|
325
|
+
//# sourceMappingURL=filter_temporal_compiler.js.map
|
|
@@ -31,6 +31,7 @@ const utils_1 = require("./utils");
|
|
|
31
31
|
const utils_2 = require("./materialization/utils");
|
|
32
32
|
const annotation_1 = require("../annotation");
|
|
33
33
|
const filter_compilers_1 = require("./filter_compilers");
|
|
34
|
+
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
34
35
|
function pathToCol(path) {
|
|
35
36
|
return path.map(el => encodeURIComponent(el)).join('/');
|
|
36
37
|
}
|
|
@@ -885,9 +886,14 @@ class QueryField extends QueryNode {
|
|
|
885
886
|
case 'filterMatch':
|
|
886
887
|
if (expr.dataType === 'string' ||
|
|
887
888
|
expr.dataType === 'number' ||
|
|
889
|
+
expr.dataType === 'date' ||
|
|
890
|
+
expr.dataType === 'timestamp' ||
|
|
888
891
|
expr.dataType === 'boolean') {
|
|
889
|
-
|
|
892
|
+
if ((0, malloy_filter_1.isFilterExpression)(expr.filter)) {
|
|
893
|
+
return filter_compilers_1.FilterCompilers.compile(expr.dataType, expr.filter, expr.e.sql || '', this.parent.dialect);
|
|
894
|
+
}
|
|
890
895
|
}
|
|
896
|
+
// mtoy todo no throw
|
|
891
897
|
throw new Error(`Internal Error: Filter Compiler Undefined (FCU) ${expr.dataType}`);
|
|
892
898
|
default:
|
|
893
899
|
throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
|
|
@@ -114,11 +114,14 @@ export interface ParameterNode extends ExprLeaf {
|
|
|
114
114
|
}
|
|
115
115
|
export interface NowNode extends ExprLeaf {
|
|
116
116
|
node: 'now';
|
|
117
|
+
typeDef: {
|
|
118
|
+
type: 'timestamp';
|
|
119
|
+
};
|
|
117
120
|
}
|
|
118
121
|
interface HasTimeValue {
|
|
119
122
|
typeDef: TemporalTypeDef;
|
|
120
123
|
}
|
|
121
|
-
type TimeExpr = Expr & HasTimeValue;
|
|
124
|
+
export type TimeExpr = Expr & HasTimeValue;
|
|
122
125
|
export declare function mkTemporal(e: Expr, timeType: TemporalTypeDef | TemporalFieldType): TimeExpr;
|
|
123
126
|
export interface MeasureTimeExpr extends ExprWithKids {
|
|
124
127
|
node: 'timeDiff';
|
|
@@ -58,9 +58,11 @@ function canMakeTemporal(e) {
|
|
|
58
58
|
return e.node !== 'arrayLiteral' && e.node !== 'recordLiteral';
|
|
59
59
|
}
|
|
60
60
|
function mkTemporal(e, timeType) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
if (!('typeDef' in e)) {
|
|
62
|
+
const ttd = typeof timeType === 'string' ? { type: timeType } : timeType;
|
|
63
|
+
if (canMakeTemporal(e)) {
|
|
64
|
+
return { ...e, typeDef: { ...ttd } };
|
|
65
|
+
}
|
|
64
66
|
}
|
|
65
67
|
return e;
|
|
66
68
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MALLOY_VERSION = "0.0.
|
|
1
|
+
export declare const MALLOY_VERSION = "0.0.246";
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MALLOY_VERSION = void 0;
|
|
4
4
|
// generated with 'generate-version-file' script; do not edit manually
|
|
5
|
-
exports.MALLOY_VERSION = '0.0.
|
|
5
|
+
exports.MALLOY_VERSION = '0.0.246';
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.246-dev250320220920",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@malloydata/malloy-filter": "^0.0.
|
|
45
|
-
"@malloydata/malloy-interfaces": "^0.0.
|
|
46
|
-
"@malloydata/malloy-tag": "^0.0.
|
|
44
|
+
"@malloydata/malloy-filter": "^0.0.246-dev250320220920",
|
|
45
|
+
"@malloydata/malloy-interfaces": "^0.0.246-dev250320220920",
|
|
46
|
+
"@malloydata/malloy-tag": "^0.0.246-dev250320220920",
|
|
47
47
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
48
48
|
"assert": "^2.0.0",
|
|
49
49
|
"jest-diff": "^29.6.2",
|