@malloydata/malloy 0.0.321 → 0.0.323
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/dialect.d.ts +109 -3
- package/dist/dialect/dialect.js +98 -3
- package/dist/dialect/duckdb/duckdb.d.ts +5 -2
- package/dist/dialect/duckdb/duckdb.js +22 -11
- package/dist/dialect/mysql/mysql.d.ts +5 -4
- package/dist/dialect/mysql/mysql.js +31 -47
- package/dist/dialect/pg_impl.d.ts +1 -2
- package/dist/dialect/pg_impl.js +0 -30
- package/dist/dialect/postgres/postgres.d.ts +5 -2
- package/dist/dialect/postgres/postgres.js +28 -11
- package/dist/dialect/snowflake/snowflake.d.ts +5 -3
- package/dist/dialect/snowflake/snowflake.js +23 -22
- package/dist/dialect/standardsql/standardsql.d.ts +9 -3
- package/dist/dialect/standardsql/standardsql.js +42 -33
- package/dist/dialect/trino/trino.d.ts +7 -3
- package/dist/dialect/trino/trino.js +41 -43
- package/dist/lang/ast/error-factory.js +1 -1
- package/dist/lang/ast/field-space/static-space.js +5 -1
- package/dist/lang/ast/types/malloy-element.js +10 -2
- package/dist/model/expression_compiler.js +172 -145
- package/dist/model/filter_compilers.d.ts +0 -2
- package/dist/model/filter_compilers.js +132 -57
- package/dist/model/malloy_types.d.ts +19 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -46,43 +46,53 @@ function sqlSumDistinct(dialect, sqlExp, sqlDistintKey) {
|
|
|
46
46
|
return ret;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
* This function was extracted from QueryField.exprToSQL to break circular dependencies.
|
|
49
|
+
* Deep copies an expression tree, preserving structure and types.
|
|
51
50
|
*/
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
function deepCopyExpr(expr) {
|
|
52
|
+
if ((0, malloy_types_1.exprHasE)(expr)) {
|
|
53
|
+
return { ...expr, e: deepCopyExpr(expr.e) };
|
|
54
|
+
}
|
|
55
|
+
else if ((0, malloy_types_1.exprHasKids)(expr)) {
|
|
56
|
+
const copiedKids = {};
|
|
57
|
+
for (const [name, kidExpr] of Object.entries(expr.kids)) {
|
|
58
|
+
if (kidExpr === null) {
|
|
59
|
+
copiedKids[name] = null;
|
|
60
|
+
}
|
|
61
|
+
else if (Array.isArray(kidExpr)) {
|
|
62
|
+
copiedKids[name] = kidExpr.map(e => deepCopyExpr(e));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
copiedKids[name] = deepCopyExpr(kidExpr);
|
|
66
|
+
}
|
|
59
67
|
}
|
|
60
|
-
return
|
|
61
|
-
}
|
|
68
|
+
return { ...expr, kids: copiedKids };
|
|
69
|
+
}
|
|
70
|
+
return { ...expr };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Compiles an expression tree by mutating it in-place to set all .sql fields.
|
|
74
|
+
* Assumes the expression tree is already a copy that can be mutated.
|
|
75
|
+
*/
|
|
76
|
+
function compileExpr(resultSet, context, expr, state = new utils_1.GenerateState(), wrap = true) {
|
|
62
77
|
/*
|
|
63
78
|
* Translate the children first, and stash the translation
|
|
64
79
|
* in the nodes themselves, so that if we call into the dialect
|
|
65
80
|
* it will have access to the translated children.
|
|
66
81
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
else if ((0, malloy_types_1.exprHasKids)(exprToTranslate)) {
|
|
74
|
-
expr = { ...exprToTranslate };
|
|
75
|
-
const oldKids = exprToTranslate.kids;
|
|
76
|
-
for (const [name, kidExpr] of Object.entries(oldKids)) {
|
|
82
|
+
if ((0, malloy_types_1.exprHasE)(expr)) {
|
|
83
|
+
compileExpr(resultSet, context, expr.e, state);
|
|
84
|
+
}
|
|
85
|
+
else if ((0, malloy_types_1.exprHasKids)(expr)) {
|
|
86
|
+
for (const kidExpr of Object.values(expr.kids)) {
|
|
77
87
|
if (kidExpr === null)
|
|
78
88
|
continue;
|
|
79
89
|
if (Array.isArray(kidExpr)) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
90
|
+
for (const e of kidExpr) {
|
|
91
|
+
compileExpr(resultSet, context, e, state);
|
|
92
|
+
}
|
|
83
93
|
}
|
|
84
94
|
else {
|
|
85
|
-
|
|
95
|
+
compileExpr(resultSet, context, kidExpr, state);
|
|
86
96
|
}
|
|
87
97
|
}
|
|
88
98
|
}
|
|
@@ -92,132 +102,149 @@ function exprToSQL(resultSet, context, exprToTranslate, state = new utils_1.Gene
|
|
|
92
102
|
const qi = resultSet.getQueryInfo();
|
|
93
103
|
const dialectSQL = context.dialect.exprToSQL(qi, expr);
|
|
94
104
|
if (dialectSQL) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
expr.sql = wrap && (0, malloy_types_1.exprHasKids)(expr) ? `(${dialectSQL})` : dialectSQL;
|
|
106
|
+
return expr;
|
|
107
|
+
}
|
|
108
|
+
const sql = (() => {
|
|
109
|
+
var _a;
|
|
110
|
+
switch (expr.node) {
|
|
111
|
+
case 'field':
|
|
112
|
+
return generateFieldFragment(resultSet, context, expr, state);
|
|
113
|
+
case 'parameter':
|
|
114
|
+
return generateParameterFragment(resultSet, context, expr, state);
|
|
115
|
+
case 'filteredExpr':
|
|
116
|
+
return generateFilterFragment(resultSet, context, expr, state);
|
|
117
|
+
case 'all':
|
|
118
|
+
case 'exclude':
|
|
119
|
+
return generateUngroupedFragment(resultSet, context, expr, state);
|
|
120
|
+
case 'genericSQLExpr':
|
|
121
|
+
return Array.from(stringsFromSQLExpression(resultSet, context, expr, state)).join('');
|
|
122
|
+
case 'aggregate': {
|
|
123
|
+
let agg = '';
|
|
124
|
+
if (expr.function === 'sum') {
|
|
125
|
+
agg = generateSumFragment(resultSet, context, expr, state);
|
|
126
|
+
}
|
|
127
|
+
else if (expr.function === 'avg') {
|
|
128
|
+
agg = generateAvgFragment(resultSet, context, expr, state);
|
|
129
|
+
}
|
|
130
|
+
else if (expr.function === 'count') {
|
|
131
|
+
agg = generateCountFragment(resultSet, context, expr, state);
|
|
132
|
+
}
|
|
133
|
+
else if (expr.function === 'min' ||
|
|
134
|
+
expr.function === 'max' ||
|
|
135
|
+
expr.function === 'distinct') {
|
|
136
|
+
agg = generateSymmetricFragment(resultSet, context, expr, state);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
throw new Error(`Internal Error: Unknown aggregate function ${expr.function}`);
|
|
140
|
+
}
|
|
141
|
+
if (resultSet.root().isComplexQuery) {
|
|
142
|
+
let groupSet = resultSet.groupSet;
|
|
143
|
+
if (state.totalGroupSet !== -1) {
|
|
144
|
+
groupSet = state.totalGroupSet;
|
|
145
|
+
}
|
|
146
|
+
return (0, utils_1.caseGroup)([groupSet], agg);
|
|
147
|
+
}
|
|
148
|
+
return agg;
|
|
116
149
|
}
|
|
117
|
-
|
|
118
|
-
|
|
150
|
+
case 'function_parameter':
|
|
151
|
+
throw new Error('Internal Error: Function parameter fragment remaining during SQL generation');
|
|
152
|
+
case 'outputField':
|
|
153
|
+
return generateOutputFieldFragment(resultSet, context, expr, state);
|
|
154
|
+
case 'function_call':
|
|
155
|
+
return generateFunctionCallExpression(resultSet, context, expr, state);
|
|
156
|
+
case 'spread':
|
|
157
|
+
throw new Error("Internal Error: expandFunctionCall() failed to process node: 'spread'");
|
|
158
|
+
case 'source-reference':
|
|
159
|
+
return generateSourceReference(resultSet, context, expr);
|
|
160
|
+
case '+':
|
|
161
|
+
case '-':
|
|
162
|
+
case '*':
|
|
163
|
+
case '%':
|
|
164
|
+
case '/':
|
|
165
|
+
case '>':
|
|
166
|
+
case '<':
|
|
167
|
+
case '>=':
|
|
168
|
+
case '<=':
|
|
169
|
+
case '=':
|
|
170
|
+
return `${expr.kids.left.sql}${expr.node}${expr.kids.right.sql}`;
|
|
171
|
+
// Malloy inequality comparisons always return a boolean
|
|
172
|
+
case '!=': {
|
|
173
|
+
const notEqual = `${expr.kids.left.sql}!=${expr.kids.right.sql}`;
|
|
174
|
+
return `COALESCE(${notEqual},true)`;
|
|
119
175
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expr.
|
|
123
|
-
|
|
176
|
+
case 'and':
|
|
177
|
+
case 'or':
|
|
178
|
+
return `${expr.kids.left.sql} ${expr.node} ${expr.kids.right.sql}`;
|
|
179
|
+
case 'coalesce':
|
|
180
|
+
return `COALESCE(${expr.kids.left.sql},${expr.kids.right.sql})`;
|
|
181
|
+
case 'in': {
|
|
182
|
+
const oneOf = expr.kids.oneOf.map(o => o.sql).join(',');
|
|
183
|
+
return `${expr.kids.e.sql} ${expr.not ? 'NOT IN' : 'IN'} (${oneOf})`;
|
|
124
184
|
}
|
|
125
|
-
|
|
126
|
-
|
|
185
|
+
case 'like':
|
|
186
|
+
case '!like': {
|
|
187
|
+
const likeIt = expr.node === 'like' ? 'LIKE' : 'NOT LIKE';
|
|
188
|
+
const compare = expr.kids.right.node === 'stringLiteral'
|
|
189
|
+
? context.dialect.sqlLike(likeIt, (_a = expr.kids.left.sql) !== null && _a !== void 0 ? _a : '', expr.kids.right.literal)
|
|
190
|
+
: `${expr.kids.left.sql} ${likeIt} ${expr.kids.right.sql}`;
|
|
191
|
+
return expr.node === 'like' ? compare : `COALESCE(${compare},true)`;
|
|
127
192
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
193
|
+
case '()':
|
|
194
|
+
return `(${expr.e.sql})`;
|
|
195
|
+
case 'not':
|
|
196
|
+
// Malloy not operator always returns a boolean
|
|
197
|
+
return `COALESCE(NOT ${expr.e.sql},TRUE)`;
|
|
198
|
+
case 'unary-':
|
|
199
|
+
return `-${expr.e.sql}`;
|
|
200
|
+
case 'is-null':
|
|
201
|
+
return `${expr.e.sql} IS NULL`;
|
|
202
|
+
case 'is-not-null':
|
|
203
|
+
return `${expr.e.sql} IS NOT NULL`;
|
|
204
|
+
case 'true':
|
|
205
|
+
case 'false':
|
|
206
|
+
return expr.node;
|
|
207
|
+
case 'null':
|
|
208
|
+
return 'NULL';
|
|
209
|
+
case 'case':
|
|
210
|
+
return generateCaseSQL(expr);
|
|
211
|
+
case '':
|
|
212
|
+
return '';
|
|
213
|
+
case 'filterCondition':
|
|
214
|
+
// our child will be translated at the top of this function
|
|
215
|
+
if (expr.e.sql) {
|
|
216
|
+
expr.sql = expr.e.sql;
|
|
217
|
+
return expr.sql;
|
|
132
218
|
}
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
case '+':
|
|
148
|
-
case '-':
|
|
149
|
-
case '*':
|
|
150
|
-
case '%':
|
|
151
|
-
case '/':
|
|
152
|
-
case '>':
|
|
153
|
-
case '<':
|
|
154
|
-
case '>=':
|
|
155
|
-
case '<=':
|
|
156
|
-
case '=':
|
|
157
|
-
return `${expr.kids.left.sql}${expr.node}${expr.kids.right.sql}`;
|
|
158
|
-
// Malloy inequality comparisons always return a boolean
|
|
159
|
-
case '!=': {
|
|
160
|
-
const notEqual = `${expr.kids.left.sql}!=${expr.kids.right.sql}`;
|
|
161
|
-
return `COALESCE(${notEqual},true)`;
|
|
162
|
-
}
|
|
163
|
-
case 'and':
|
|
164
|
-
case 'or':
|
|
165
|
-
return `${expr.kids.left.sql} ${expr.node} ${expr.kids.right.sql}`;
|
|
166
|
-
case 'coalesce':
|
|
167
|
-
return `COALESCE(${expr.kids.left.sql},${expr.kids.right.sql})`;
|
|
168
|
-
case 'in': {
|
|
169
|
-
const oneOf = expr.kids.oneOf.map(o => o.sql).join(',');
|
|
170
|
-
return `${expr.kids.e.sql} ${expr.not ? 'NOT IN' : 'IN'} (${oneOf})`;
|
|
171
|
-
}
|
|
172
|
-
case 'like':
|
|
173
|
-
case '!like': {
|
|
174
|
-
const likeIt = expr.node === 'like' ? 'LIKE' : 'NOT LIKE';
|
|
175
|
-
const compare = expr.kids.right.node === 'stringLiteral'
|
|
176
|
-
? context.dialect.sqlLike(likeIt, (_a = expr.kids.left.sql) !== null && _a !== void 0 ? _a : '', expr.kids.right.literal)
|
|
177
|
-
: `${expr.kids.left.sql} ${likeIt} ${expr.kids.right.sql}`;
|
|
178
|
-
return expr.node === 'like' ? compare : `COALESCE(${compare},true)`;
|
|
219
|
+
return '';
|
|
220
|
+
case 'functionDefaultOrderBy':
|
|
221
|
+
case 'functionOrderBy':
|
|
222
|
+
return '';
|
|
223
|
+
// TODO: throw an error here; not simple because we call into this
|
|
224
|
+
// code currently before the composite source is resolved in some cases
|
|
225
|
+
case 'compositeField':
|
|
226
|
+
return '{COMPOSITE_FIELD}';
|
|
227
|
+
case 'filterMatch':
|
|
228
|
+
return generateAppliedFilter(context, expr, qi);
|
|
229
|
+
case 'filterLiteral':
|
|
230
|
+
return 'INTERNAL ERROR FILTER EXPRESSION VALUE SHOULD NOT BE USED';
|
|
231
|
+
default:
|
|
232
|
+
throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
|
|
179
233
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
case 'null':
|
|
195
|
-
return 'NULL';
|
|
196
|
-
case 'case':
|
|
197
|
-
return generateCaseSQL(expr);
|
|
198
|
-
case '':
|
|
199
|
-
return '';
|
|
200
|
-
case 'filterCondition':
|
|
201
|
-
// our child will be translated at the top of this function
|
|
202
|
-
if (expr.e.sql) {
|
|
203
|
-
expr.sql = expr.e.sql;
|
|
204
|
-
return expr.sql;
|
|
205
|
-
}
|
|
206
|
-
return '';
|
|
207
|
-
case 'functionDefaultOrderBy':
|
|
208
|
-
case 'functionOrderBy':
|
|
209
|
-
return '';
|
|
210
|
-
// TODO: throw an error here; not simple because we call into this
|
|
211
|
-
// code currently before the composite source is resolved in some cases
|
|
212
|
-
case 'compositeField':
|
|
213
|
-
return '{COMPOSITE_FIELD}';
|
|
214
|
-
case 'filterMatch':
|
|
215
|
-
return generateAppliedFilter(context, expr, qi);
|
|
216
|
-
case 'filterLiteral':
|
|
217
|
-
return 'INTERNAL ERROR FILTER EXPRESSION VALUE SHOULD NOT BE USED';
|
|
218
|
-
default:
|
|
219
|
-
throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
|
|
220
|
-
}
|
|
234
|
+
})();
|
|
235
|
+
expr.sql = wrap && (0, malloy_types_1.exprHasKids)(expr) ? `(${sql})` : sql;
|
|
236
|
+
return expr;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Converts an expression to SQL.
|
|
240
|
+
* This function was extracted from QueryField.exprToSQL to break circular dependencies.
|
|
241
|
+
*/
|
|
242
|
+
function exprToSQL(resultSet, context, exprToTranslate, state = new utils_1.GenerateState()) {
|
|
243
|
+
// Make a deep copy that we can mutate during compilation
|
|
244
|
+
const exprCopy = deepCopyExpr(exprToTranslate);
|
|
245
|
+
// Compile the copy, setting .sql on all nodes
|
|
246
|
+
const compiled = compileExpr(resultSet, context, exprCopy, state, false);
|
|
247
|
+
return compiled.sql;
|
|
221
248
|
}
|
|
222
249
|
function generateAppliedFilter(context, filterMatchExpr, qi) {
|
|
223
250
|
var _a;
|
|
@@ -340,18 +340,32 @@ class TemporalFilterCompiler {
|
|
|
340
340
|
}
|
|
341
341
|
case 'for': {
|
|
342
342
|
const start = this.moment(tc.begin);
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
// start.begin could be any moment (literal, "last monday", etc.)
|
|
344
|
+
// so we can't optimize through its already-generated SQL
|
|
345
|
+
const endSql = this.d.sqlTruncAndOffset((0, malloy_types_1.mkTemporal)(start.begin, 'timestamp'), this.qi, undefined, {
|
|
346
|
+
op: '+',
|
|
347
|
+
magnitude: tc.n,
|
|
348
|
+
unit: tc.units,
|
|
349
|
+
});
|
|
350
|
+
return this.isIn(tc.not, start.begin.sql, endSql);
|
|
345
351
|
}
|
|
346
352
|
case 'in_last': {
|
|
347
353
|
// last N units means "N - 1 UNITS AGO FOR N UNITS"
|
|
348
354
|
const back = Number(tc.n) - 1;
|
|
349
|
-
const
|
|
355
|
+
const now = this.nowExpr();
|
|
350
356
|
const start = back > 0
|
|
351
|
-
? this.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
357
|
+
? this.d.sqlTruncAndOffset(now, this.qi, tc.units, {
|
|
358
|
+
op: '-',
|
|
359
|
+
magnitude: back.toString(),
|
|
360
|
+
unit: tc.units,
|
|
361
|
+
})
|
|
362
|
+
: this.d.sqlTruncAndOffset(now, this.qi, tc.units);
|
|
363
|
+
const end = this.d.sqlTruncAndOffset(now, this.qi, tc.units, {
|
|
364
|
+
op: '+',
|
|
365
|
+
magnitude: '1',
|
|
366
|
+
unit: tc.units,
|
|
367
|
+
});
|
|
368
|
+
return this.isIn(tc.not, start, end);
|
|
355
369
|
}
|
|
356
370
|
case 'to': {
|
|
357
371
|
const firstMoment = this.moment(tc.fromMoment);
|
|
@@ -359,15 +373,28 @@ class TemporalFilterCompiler {
|
|
|
359
373
|
return this.isIn(tc.not, firstMoment.begin.sql, lastMoment.begin.sql);
|
|
360
374
|
}
|
|
361
375
|
case 'last': {
|
|
362
|
-
const
|
|
363
|
-
const start = this.
|
|
364
|
-
|
|
376
|
+
const now = this.nowExpr();
|
|
377
|
+
const start = this.d.sqlTruncAndOffset(now, this.qi, tc.units, {
|
|
378
|
+
op: '-',
|
|
379
|
+
magnitude: tc.n,
|
|
380
|
+
unit: tc.units,
|
|
381
|
+
});
|
|
382
|
+
const end = this.d.sqlTruncAndOffset(now, this.qi, tc.units);
|
|
383
|
+
return this.isIn(tc.not, start, end);
|
|
365
384
|
}
|
|
366
385
|
case 'next': {
|
|
367
|
-
const
|
|
368
|
-
const start = this.
|
|
369
|
-
|
|
370
|
-
|
|
386
|
+
const now = this.nowExpr();
|
|
387
|
+
const start = this.d.sqlTruncAndOffset(now, this.qi, tc.units, {
|
|
388
|
+
op: '+',
|
|
389
|
+
magnitude: '1',
|
|
390
|
+
unit: tc.units,
|
|
391
|
+
});
|
|
392
|
+
const end = this.d.sqlTruncAndOffset(now, this.qi, tc.units, {
|
|
393
|
+
op: '+',
|
|
394
|
+
magnitude: (Number(tc.n) + 1).toString(),
|
|
395
|
+
unit: tc.units,
|
|
396
|
+
});
|
|
397
|
+
return this.isIn(tc.not, start, end);
|
|
371
398
|
}
|
|
372
399
|
case 'null':
|
|
373
400
|
return tc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
|
|
@@ -476,18 +503,6 @@ class TemporalFilterCompiler {
|
|
|
476
503
|
n(literal) {
|
|
477
504
|
return { node: 'numberLiteral', literal, sql: literal };
|
|
478
505
|
}
|
|
479
|
-
delta(from, op, n, units) {
|
|
480
|
-
const ret = {
|
|
481
|
-
node: 'delta',
|
|
482
|
-
op,
|
|
483
|
-
units,
|
|
484
|
-
kids: {
|
|
485
|
-
base: (0, malloy_types_1.mkTemporal)(from, 'timestamp'),
|
|
486
|
-
delta: this.n(n),
|
|
487
|
-
},
|
|
488
|
-
};
|
|
489
|
-
return { ...ret, sql: this.d.sqlAlterTimeExpr(ret) };
|
|
490
|
-
}
|
|
491
506
|
dayofWeek(e) {
|
|
492
507
|
const t = {
|
|
493
508
|
node: 'extract',
|
|
@@ -496,29 +511,52 @@ class TemporalFilterCompiler {
|
|
|
496
511
|
};
|
|
497
512
|
return { ...t, sql: this.d.sqlTimeExtractExpr(this.qi, t) };
|
|
498
513
|
}
|
|
499
|
-
nowDot(units) {
|
|
500
|
-
const nowTruncExpr = {
|
|
501
|
-
node: 'trunc',
|
|
502
|
-
e: this.nowExpr(),
|
|
503
|
-
units,
|
|
504
|
-
};
|
|
505
|
-
return { ...nowTruncExpr, sql: this.d.sqlTruncExpr(this.qi, nowTruncExpr) };
|
|
506
|
-
}
|
|
507
514
|
thisUnit(units) {
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
|
|
515
|
+
const now = this.nowExpr();
|
|
516
|
+
const beginSql = this.d.sqlTruncAndOffset(now, this.qi, units);
|
|
517
|
+
const endSql = this.d.sqlTruncAndOffset(now, this.qi, units, {
|
|
518
|
+
op: '+',
|
|
519
|
+
magnitude: '1',
|
|
520
|
+
unit: units,
|
|
521
|
+
});
|
|
522
|
+
const beginNode = { node: 'trunc', e: now, units };
|
|
523
|
+
return { begin: { ...beginNode, sql: beginSql }, end: endSql };
|
|
511
524
|
}
|
|
512
525
|
lastUnit(units) {
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
|
|
526
|
+
const now = this.nowExpr();
|
|
527
|
+
const beginSql = this.d.sqlTruncAndOffset(now, this.qi, units, {
|
|
528
|
+
op: '-',
|
|
529
|
+
magnitude: '1',
|
|
530
|
+
unit: units,
|
|
531
|
+
});
|
|
532
|
+
const endSql = this.d.sqlTruncAndOffset(now, this.qi, units);
|
|
533
|
+
const beginNode = {
|
|
534
|
+
node: 'delta',
|
|
535
|
+
op: '-',
|
|
536
|
+
units,
|
|
537
|
+
kids: { base: now, delta: this.n('1') },
|
|
538
|
+
};
|
|
539
|
+
return { begin: { ...beginNode, sql: beginSql }, end: endSql };
|
|
516
540
|
}
|
|
517
541
|
nextUnit(units) {
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
542
|
+
const now = this.nowExpr();
|
|
543
|
+
const beginSql = this.d.sqlTruncAndOffset(now, this.qi, units, {
|
|
544
|
+
op: '+',
|
|
545
|
+
magnitude: '1',
|
|
546
|
+
unit: units,
|
|
547
|
+
});
|
|
548
|
+
const endSql = this.d.sqlTruncAndOffset(now, this.qi, units, {
|
|
549
|
+
op: '+',
|
|
550
|
+
magnitude: '2',
|
|
551
|
+
unit: units,
|
|
552
|
+
});
|
|
553
|
+
const beginNode = {
|
|
554
|
+
node: 'delta',
|
|
555
|
+
op: '+',
|
|
556
|
+
units,
|
|
557
|
+
kids: { base: now, delta: this.n('1') },
|
|
558
|
+
};
|
|
559
|
+
return { begin: { ...beginNode, sql: beginSql }, end: endSql };
|
|
522
560
|
}
|
|
523
561
|
mod7(n) {
|
|
524
562
|
return this.d.hasModOperator ? `(${n})%7` : `MOD(${n},7)`;
|
|
@@ -533,19 +571,36 @@ class TemporalFilterCompiler {
|
|
|
533
571
|
return this.expandLiteral(m);
|
|
534
572
|
case 'ago':
|
|
535
573
|
case 'from_now': {
|
|
536
|
-
const
|
|
537
|
-
const
|
|
538
|
-
const
|
|
574
|
+
const now = this.nowExpr();
|
|
575
|
+
const op = m.moment === 'ago' ? '-' : '+';
|
|
576
|
+
const beginSql = this.d.sqlTruncAndOffset(now, this.qi, m.units, {
|
|
577
|
+
op,
|
|
578
|
+
magnitude: m.n,
|
|
579
|
+
unit: m.units,
|
|
580
|
+
});
|
|
581
|
+
const beginNode = {
|
|
582
|
+
node: 'delta',
|
|
583
|
+
op,
|
|
584
|
+
units: m.units,
|
|
585
|
+
kids: { base: now, delta: this.n(m.n) },
|
|
586
|
+
};
|
|
539
587
|
// Now the end is one unit after that .. either n-1 units ago or n+1 units from now
|
|
588
|
+
let endSql;
|
|
540
589
|
if (m.moment === 'ago' && m.n === '1') {
|
|
541
|
-
|
|
590
|
+
endSql = this.d.sqlTruncAndOffset(now, this.qi, m.units);
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
const oneDifferent = Number(m.n) + (m.moment === 'ago' ? -1 : 1);
|
|
594
|
+
endSql = this.d.sqlTruncAndOffset(now, this.qi, m.units, {
|
|
595
|
+
op,
|
|
596
|
+
magnitude: oneDifferent.toString(),
|
|
597
|
+
unit: m.units,
|
|
598
|
+
});
|
|
542
599
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
kids: { base: nowTrunc, delta: this.n(oneDifferent.toString()) },
|
|
600
|
+
return {
|
|
601
|
+
begin: { ...beginNode, sql: beginSql },
|
|
602
|
+
end: endSql,
|
|
547
603
|
};
|
|
548
|
-
return { begin: beginExpr, end: this.d.sqlAlterTimeExpr(endExpr) };
|
|
549
604
|
}
|
|
550
605
|
case 'today':
|
|
551
606
|
return this.thisUnit('day');
|
|
@@ -591,7 +646,7 @@ class TemporalFilterCompiler {
|
|
|
591
646
|
weekdayMoment(destDay, which) {
|
|
592
647
|
const direction = which || 'last';
|
|
593
648
|
const dow = this.dayofWeek(this.nowExpr());
|
|
594
|
-
const
|
|
649
|
+
const now = this.nowExpr();
|
|
595
650
|
// destDay comes in as 1-7 (Malloy format), convert to 0-6
|
|
596
651
|
const destDayZeroBased = destDay - 1;
|
|
597
652
|
// dow is 1-7, convert to 0-6 for the arithmetic
|
|
@@ -612,9 +667,29 @@ class TemporalFilterCompiler {
|
|
|
612
667
|
// End offset is one day less (closer to today)
|
|
613
668
|
endOffset = `${this.mod7(`${dowZeroBased}-${destDayZeroBased}+6`)}`;
|
|
614
669
|
}
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
|
|
670
|
+
const op = direction === 'next' ? '+' : '-';
|
|
671
|
+
const beginSql = this.d.sqlTruncAndOffset(now, this.qi, 'day', {
|
|
672
|
+
op,
|
|
673
|
+
magnitude: beginOffset,
|
|
674
|
+
unit: 'day',
|
|
675
|
+
});
|
|
676
|
+
const endSql = this.d.sqlTruncAndOffset(now, this.qi, 'day', {
|
|
677
|
+
op,
|
|
678
|
+
magnitude: endOffset,
|
|
679
|
+
unit: 'day',
|
|
680
|
+
});
|
|
681
|
+
// Build an Expr node for begin (truncate now to day, then add offset)
|
|
682
|
+
const truncatedNow = { node: 'trunc', e: now, units: 'day' };
|
|
683
|
+
const beginNode = {
|
|
684
|
+
node: 'delta',
|
|
685
|
+
op,
|
|
686
|
+
units: 'day',
|
|
687
|
+
kids: {
|
|
688
|
+
base: (0, malloy_types_1.mkTemporal)(truncatedNow, 'timestamp'),
|
|
689
|
+
delta: this.n(beginOffset),
|
|
690
|
+
},
|
|
691
|
+
};
|
|
692
|
+
return { begin: { ...beginNode, sql: beginSql }, end: endSql };
|
|
618
693
|
}
|
|
619
694
|
}
|
|
620
695
|
exports.TemporalFilterCompiler = TemporalFilterCompiler;
|