@malloydata/malloy 0.0.262 → 0.0.263-dev250414184158
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/lang/ast/expressions/expr-filter-expr.d.ts +0 -3
- package/dist/lang/ast/expressions/expr-filter-expr.js +7 -59
- package/dist/lang/ast/expressions/expr-func.js +3 -0
- package/dist/lang/ast/expressions/pick-when.js +6 -0
- package/dist/lang/ast/field-space/dynamic-space.js +1 -1
- package/dist/lang/ast/field-space/join-space-field.js +1 -1
- package/dist/lang/ast/index.d.ts +1 -0
- package/dist/lang/ast/index.js +1 -0
- package/dist/lang/ast/parameters/has-parameter.d.ts +6 -3
- package/dist/lang/ast/parameters/has-parameter.js +28 -9
- package/dist/lang/ast/query-items/field-declaration.d.ts +1 -2
- package/dist/lang/ast/query-items/field-declaration.js +0 -4
- package/dist/lang/ast/source-elements/named-source.js +6 -0
- package/dist/lang/ast/source-properties/join.d.ts +3 -3
- package/dist/lang/ast/source-properties/join.js +2 -2
- package/dist/lang/ast/types/expression-def.d.ts +2 -1
- package/dist/lang/ast/types/expression-def.js +79 -17
- package/dist/lang/ast/types/space-param.d.ts +1 -3
- package/dist/lang/ast/types/space-param.js +8 -8
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +114 -113
- package/dist/lang/lib/Malloy/MalloyLexer.js +1272 -1266
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +253 -239
- package/dist/lang/lib/Malloy/MalloyParser.js +2244 -2142
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +11 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +7 -0
- package/dist/lang/malloy-to-ast.d.ts +1 -1
- package/dist/lang/malloy-to-ast.js +38 -12
- package/dist/lang/test/expr-to-str.js +3 -20
- package/dist/model/filter_compilers.d.ts +27 -0
- package/dist/model/filter_compilers.js +335 -3
- package/dist/model/malloy_query.d.ts +2 -1
- package/dist/model/malloy_query.js +45 -10
- package/dist/model/malloy_types.d.ts +18 -6
- package/dist/model/malloy_types.js +6 -2
- package/dist/to_stable.js +19 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/model/filter_temporal_compiler.d.ts +0 -29
- package/dist/model/filter_temporal_compiler.js +0 -343
|
@@ -113,6 +113,7 @@ import { SourcePropertyListContext } from "./MalloyParser";
|
|
|
113
113
|
import { SourceDefinitionContext } from "./MalloyParser";
|
|
114
114
|
import { SqExploreContext } from "./MalloyParser";
|
|
115
115
|
import { SourceParametersContext } from "./MalloyParser";
|
|
116
|
+
import { LegalParamTypeContext } from "./MalloyParser";
|
|
116
117
|
import { SourceParameterContext } from "./MalloyParser";
|
|
117
118
|
import { ParameterNameDefContext } from "./MalloyParser";
|
|
118
119
|
import { SourceNameDefContext } from "./MalloyParser";
|
|
@@ -1551,6 +1552,16 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
1551
1552
|
* @param ctx the parse tree
|
|
1552
1553
|
*/
|
|
1553
1554
|
exitSourceParameters?: (ctx: SourceParametersContext) => void;
|
|
1555
|
+
/**
|
|
1556
|
+
* Enter a parse tree produced by `MalloyParser.legalParamType`.
|
|
1557
|
+
* @param ctx the parse tree
|
|
1558
|
+
*/
|
|
1559
|
+
enterLegalParamType?: (ctx: LegalParamTypeContext) => void;
|
|
1560
|
+
/**
|
|
1561
|
+
* Exit a parse tree produced by `MalloyParser.legalParamType`.
|
|
1562
|
+
* @param ctx the parse tree
|
|
1563
|
+
*/
|
|
1564
|
+
exitLegalParamType?: (ctx: LegalParamTypeContext) => void;
|
|
1554
1565
|
/**
|
|
1555
1566
|
* Enter a parse tree produced by `MalloyParser.sourceParameter`.
|
|
1556
1567
|
* @param ctx the parse tree
|
|
@@ -113,6 +113,7 @@ import { SourcePropertyListContext } from "./MalloyParser";
|
|
|
113
113
|
import { SourceDefinitionContext } from "./MalloyParser";
|
|
114
114
|
import { SqExploreContext } from "./MalloyParser";
|
|
115
115
|
import { SourceParametersContext } from "./MalloyParser";
|
|
116
|
+
import { LegalParamTypeContext } from "./MalloyParser";
|
|
116
117
|
import { SourceParameterContext } from "./MalloyParser";
|
|
117
118
|
import { ParameterNameDefContext } from "./MalloyParser";
|
|
118
119
|
import { SourceNameDefContext } from "./MalloyParser";
|
|
@@ -1014,6 +1015,12 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
1014
1015
|
* @return the visitor result
|
|
1015
1016
|
*/
|
|
1016
1017
|
visitSourceParameters?: (ctx: SourceParametersContext) => Result;
|
|
1018
|
+
/**
|
|
1019
|
+
* Visit a parse tree produced by `MalloyParser.legalParamType`.
|
|
1020
|
+
* @param ctx the parse tree
|
|
1021
|
+
* @return the visitor result
|
|
1022
|
+
*/
|
|
1023
|
+
visitLegalParamType?: (ctx: LegalParamTypeContext) => Result;
|
|
1017
1024
|
/**
|
|
1018
1025
|
* Visit a parse tree produced by `MalloyParser.sourceParameter`.
|
|
1019
1026
|
* @param ctx the parse tree
|
|
@@ -79,7 +79,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
79
79
|
protected getIsNotes(cx: parse.IsDefineContext): Note[];
|
|
80
80
|
visitMalloyDocument(pcx: parse.MalloyDocumentContext): ast.Document;
|
|
81
81
|
visitDefineSourceStatement(pcx: parse.DefineSourceStatementContext): ast.DefineSourceList;
|
|
82
|
-
getSourceParameter(pcx: parse.SourceParameterContext): ast.HasParameter;
|
|
82
|
+
getSourceParameter(pcx: parse.SourceParameterContext): ast.HasParameter | null;
|
|
83
83
|
getSourceParameters(pcx: parse.SourceParametersContext | undefined): ast.HasParameter[];
|
|
84
84
|
visitSourceDefinition(pcx: parse.SourceDefinitionContext): ast.DefineSource;
|
|
85
85
|
protected getSourceExtensions(extensions: parse.ExplorePropertiesContext): ast.SourceDesc;
|
|
@@ -56,8 +56,8 @@ const ast_1 = require("./ast");
|
|
|
56
56
|
const parse_utils_1 = require("./parse-utils");
|
|
57
57
|
const malloy_types_1 = require("../model/malloy_types");
|
|
58
58
|
const malloy_tag_1 = require("@malloydata/malloy-tag");
|
|
59
|
-
const constant_expression_1 = require("./ast/expressions/constant-expression");
|
|
60
59
|
const utils_1 = require("./utils");
|
|
60
|
+
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
61
61
|
class ErrorNode extends ast.SourceQueryElement {
|
|
62
62
|
constructor() {
|
|
63
63
|
super(...arguments);
|
|
@@ -262,23 +262,49 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
262
262
|
return defList;
|
|
263
263
|
}
|
|
264
264
|
getSourceParameter(pcx) {
|
|
265
|
+
const name = (0, parse_utils_1.getId)(pcx.parameterNameDef());
|
|
266
|
+
let pType;
|
|
267
|
+
let filterType;
|
|
268
|
+
const typeCx = pcx.legalParamType();
|
|
269
|
+
if (typeCx) {
|
|
270
|
+
const fTypeCx = typeCx.malloyType();
|
|
271
|
+
if (fTypeCx) {
|
|
272
|
+
const t = this.getMalloyType(fTypeCx);
|
|
273
|
+
if ((0, malloy_filter_1.isFilterable)(t)) {
|
|
274
|
+
filterType = t;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
this.contextError(typeCx, 'parameter-illegal-default-type', `Unknown filter type ${t}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const parseType = typeCx.FILTER()
|
|
281
|
+
? 'filter expression'
|
|
282
|
+
: typeCx.text.toLowerCase();
|
|
283
|
+
if (!(0, malloy_types_1.isParameterType)(parseType)) {
|
|
284
|
+
this.contextError(typeCx, 'parameter-illegal-default-type', `Unknown parameter type ${parseType}`);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
pType = parseType;
|
|
288
|
+
}
|
|
265
289
|
const defaultCx = pcx.fieldExpr();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return this.astAt(new ast.HasParameter({
|
|
272
|
-
name: (0, parse_utils_1.getId)(pcx.parameterNameDef()),
|
|
273
|
-
type,
|
|
274
|
-
default: defaultValue,
|
|
275
|
-
}), pcx);
|
|
290
|
+
let defVal;
|
|
291
|
+
if (defaultCx) {
|
|
292
|
+
const defaultExpr = new ast.ConstantExpression(this.getFieldExpr(defaultCx));
|
|
293
|
+
defVal = this.astAt(defaultExpr, defaultCx);
|
|
294
|
+
}
|
|
295
|
+
return this.astAt(new ast.HasParameter({ name, type: pType, default: defVal, filterType }), pcx);
|
|
276
296
|
}
|
|
277
297
|
getSourceParameters(pcx) {
|
|
278
298
|
if (pcx === undefined)
|
|
279
299
|
return [];
|
|
280
300
|
this.inExperiment('parameters', pcx);
|
|
281
|
-
|
|
301
|
+
function notNullParam(p) {
|
|
302
|
+
return p !== null;
|
|
303
|
+
}
|
|
304
|
+
return pcx
|
|
305
|
+
.sourceParameter()
|
|
306
|
+
.map(param => this.getSourceParameter(param))
|
|
307
|
+
.filter(notNullParam);
|
|
282
308
|
}
|
|
283
309
|
visitSourceDefinition(pcx) {
|
|
284
310
|
const exploreExpr = this.visit(pcx.sqExplore());
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.exprToStr = void 0;
|
|
10
10
|
const malloy_types_1 = require("../../model/malloy_types");
|
|
11
|
-
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
12
11
|
function exprToStr(e, symbols) {
|
|
13
12
|
function subExpr(e) {
|
|
14
13
|
return exprToStr(e, symbols);
|
|
@@ -87,27 +86,11 @@ function exprToStr(e, symbols) {
|
|
|
87
86
|
return sql;
|
|
88
87
|
}
|
|
89
88
|
case 'filterMatch': {
|
|
90
|
-
let filterText = '';
|
|
91
|
-
switch (e.dataType) {
|
|
92
|
-
case 'string':
|
|
93
|
-
filterText = malloy_filter_1.StringFilterExpression.unparse(e.filter);
|
|
94
|
-
break;
|
|
95
|
-
case 'number':
|
|
96
|
-
filterText = malloy_filter_1.NumberFilterExpression.unparse(e.filter);
|
|
97
|
-
break;
|
|
98
|
-
case 'date':
|
|
99
|
-
case 'timestamp':
|
|
100
|
-
filterText = malloy_filter_1.TemporalFilterExpression.unparse(e.filter);
|
|
101
|
-
break;
|
|
102
|
-
case 'boolean':
|
|
103
|
-
filterText = malloy_filter_1.BooleanFilterExpression.unparse(e.filter);
|
|
104
|
-
break;
|
|
105
|
-
default:
|
|
106
|
-
filterText = 'UNKOWN-FILTER';
|
|
107
|
-
}
|
|
108
89
|
const fType = `${e.dataType[0].toUpperCase()}${e.dataType.slice(1)}`;
|
|
109
|
-
return `{filter${fType} ${subExpr(e.
|
|
90
|
+
return `{filter${fType} ${subExpr(e.kids.expr)} | ${subExpr(e.kids.filterExpr)}}`;
|
|
110
91
|
}
|
|
92
|
+
case 'filterLiteral':
|
|
93
|
+
return `${e.filterSrc}`;
|
|
111
94
|
}
|
|
112
95
|
if ((0, malloy_types_1.exprHasKids)(e) && e.kids['left'] && e.kids['right']) {
|
|
113
96
|
return `{${subExpr(e.kids['left'])} ${e.node} ${subExpr(e.kids['right'])}}`;
|
|
@@ -7,3 +7,30 @@ export declare const FilterCompilers: {
|
|
|
7
7
|
stringCompile(sc: StringFilter, x: string, d: Dialect): string;
|
|
8
8
|
temporalCompile(tc: TemporalFilter, x: string, d: Dialect, t: 'date' | 'timestamp'): string;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* I felt like there was enough "helpful functions needed to make everything
|
|
12
|
+
* work, all of which need to know the dialect", to justify making a class
|
|
13
|
+
* for this. Maybe this should just be a set of functions which take
|
|
14
|
+
* a dialect as an argument?
|
|
15
|
+
*/
|
|
16
|
+
export declare class TemporalFilterCompiler {
|
|
17
|
+
readonly expr: string;
|
|
18
|
+
readonly timetype: 'timestamp' | 'date';
|
|
19
|
+
readonly d: Dialect;
|
|
20
|
+
constructor(expr: string, dialect: Dialect, timetype?: 'timestamp' | 'date');
|
|
21
|
+
time(timeSQL: string): string;
|
|
22
|
+
compile(tc: TemporalFilter): string;
|
|
23
|
+
private expandLiteral;
|
|
24
|
+
private literalNode;
|
|
25
|
+
private nowExpr;
|
|
26
|
+
private n;
|
|
27
|
+
private delta;
|
|
28
|
+
private dayofWeek;
|
|
29
|
+
private nowDot;
|
|
30
|
+
private thisUnit;
|
|
31
|
+
private lastUnit;
|
|
32
|
+
private nextUnit;
|
|
33
|
+
mod7(n: string): string;
|
|
34
|
+
private moment;
|
|
35
|
+
private isIn;
|
|
36
|
+
}
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.FilterCompilers = void 0;
|
|
9
|
+
exports.TemporalFilterCompiler = exports.FilterCompilers = void 0;
|
|
10
10
|
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
11
|
-
const
|
|
11
|
+
const malloy_types_1 = require("./malloy_types");
|
|
12
|
+
const luxon_1 = require("luxon");
|
|
12
13
|
function escapeForLike(v) {
|
|
13
14
|
return v.replace(/([%_\\])/g, '\\$1');
|
|
14
15
|
}
|
|
@@ -247,8 +248,339 @@ exports.FilterCompilers = {
|
|
|
247
248
|
},
|
|
248
249
|
// mtoy todo figure out what to do about dates
|
|
249
250
|
temporalCompile(tc, x, d, t) {
|
|
250
|
-
const c = new
|
|
251
|
+
const c = new TemporalFilterCompiler(x, d, t);
|
|
251
252
|
return c.compile(tc);
|
|
252
253
|
},
|
|
253
254
|
};
|
|
255
|
+
const fYear = 'yyyy';
|
|
256
|
+
const fMonth = `${fYear}-LL`;
|
|
257
|
+
const fDay = `${fMonth}-dd`;
|
|
258
|
+
const fHour = `${fDay} HH`;
|
|
259
|
+
const fMinute = `${fHour}:mm`;
|
|
260
|
+
const fTimestamp = `${fMinute}:ss`;
|
|
261
|
+
/**
|
|
262
|
+
* I felt like there was enough "helpful functions needed to make everything
|
|
263
|
+
* work, all of which need to know the dialect", to justify making a class
|
|
264
|
+
* for this. Maybe this should just be a set of functions which take
|
|
265
|
+
* a dialect as an argument?
|
|
266
|
+
*/
|
|
267
|
+
class TemporalFilterCompiler {
|
|
268
|
+
constructor(expr, dialect, timetype = 'timestamp') {
|
|
269
|
+
this.expr = expr;
|
|
270
|
+
this.timetype = timetype;
|
|
271
|
+
this.d = dialect;
|
|
272
|
+
}
|
|
273
|
+
time(timeSQL) {
|
|
274
|
+
if (this.timetype === 'timestamp') {
|
|
275
|
+
return timeSQL;
|
|
276
|
+
}
|
|
277
|
+
return this.d.sqlCast({}, {
|
|
278
|
+
node: 'cast',
|
|
279
|
+
e: {
|
|
280
|
+
node: 'genericSQLExpr',
|
|
281
|
+
src: ['', timeSQL],
|
|
282
|
+
kids: { args: [] },
|
|
283
|
+
sql: timeSQL,
|
|
284
|
+
},
|
|
285
|
+
srcType: { type: 'timestamp' },
|
|
286
|
+
dstType: { type: 'date' },
|
|
287
|
+
safe: false,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
compile(tc) {
|
|
291
|
+
const x = this.expr;
|
|
292
|
+
switch (tc.operator) {
|
|
293
|
+
case 'after':
|
|
294
|
+
return `${x} ${tc.not ? '<' : '>='} ${this.time(this.moment(tc.after).end)}`;
|
|
295
|
+
case 'before':
|
|
296
|
+
return `${x} ${tc.not ? '>=' : '<'} ${this.time(this.moment(tc.before).begin.sql)}`;
|
|
297
|
+
case 'in': {
|
|
298
|
+
const m = this.moment(tc.in);
|
|
299
|
+
if (m.begin.sql === m.end) {
|
|
300
|
+
return tc.not
|
|
301
|
+
? `${x} != ${this.time(m.end)} OR ${x} IS NULL`
|
|
302
|
+
: `${x} = ${this.time(m.end)}`;
|
|
303
|
+
}
|
|
304
|
+
return this.isIn(tc.not, m.begin.sql, m.end);
|
|
305
|
+
}
|
|
306
|
+
case 'for': {
|
|
307
|
+
const start = this.moment(tc.begin);
|
|
308
|
+
const end = this.delta(start.begin, '+', tc.n, tc.units);
|
|
309
|
+
return this.isIn(tc.not, start.begin.sql, end.sql);
|
|
310
|
+
}
|
|
311
|
+
case 'in_last': {
|
|
312
|
+
// last N units means "N - 1 UNITS AGO FOR N UNITS"
|
|
313
|
+
const back = Number(tc.n) - 1;
|
|
314
|
+
const thisUnit = this.nowDot(tc.units);
|
|
315
|
+
const start = back > 0
|
|
316
|
+
? this.delta(thisUnit, '-', back.toString(), tc.units)
|
|
317
|
+
: thisUnit;
|
|
318
|
+
const end = this.delta(thisUnit, '+', '1', tc.units);
|
|
319
|
+
return this.isIn(tc.not, start.sql, end.sql);
|
|
320
|
+
}
|
|
321
|
+
case 'to': {
|
|
322
|
+
const firstMoment = this.moment(tc.fromMoment);
|
|
323
|
+
const lastMoment = this.moment(tc.toMoment);
|
|
324
|
+
return this.isIn(tc.not, firstMoment.begin.sql, lastMoment.begin.sql);
|
|
325
|
+
}
|
|
326
|
+
case 'last': {
|
|
327
|
+
const thisUnit = this.nowDot(tc.units);
|
|
328
|
+
const start = this.delta(thisUnit, '-', tc.n, tc.units);
|
|
329
|
+
return this.isIn(tc.not, start.sql, thisUnit.sql);
|
|
330
|
+
}
|
|
331
|
+
case 'next': {
|
|
332
|
+
const thisUnit = this.nowDot(tc.units);
|
|
333
|
+
const start = this.delta(thisUnit, '+', '1', tc.units);
|
|
334
|
+
const end = this.delta(thisUnit, '+', (Number(tc.n) + 1).toString(), tc.units);
|
|
335
|
+
return this.isIn(tc.not, start.sql, end.sql);
|
|
336
|
+
}
|
|
337
|
+
case 'null':
|
|
338
|
+
return tc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
|
|
339
|
+
case '()': {
|
|
340
|
+
const wrapped = '(' + this.compile(tc.expr) + ')';
|
|
341
|
+
return tc.not ? `NOT ${wrapped}` : wrapped;
|
|
342
|
+
}
|
|
343
|
+
case 'and':
|
|
344
|
+
case 'or':
|
|
345
|
+
return tc.members
|
|
346
|
+
.map(m => this.compile(m))
|
|
347
|
+
.join(` ${tc.operator.toUpperCase()} `);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
expandLiteral(tl) {
|
|
351
|
+
let literal = tl.literal;
|
|
352
|
+
switch (tl.units) {
|
|
353
|
+
case 'year': {
|
|
354
|
+
const y = luxon_1.DateTime.fromFormat(literal, fYear);
|
|
355
|
+
const begin = this.literalNode(y.toFormat(fTimestamp));
|
|
356
|
+
const next = y.plus({ year: 1 });
|
|
357
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
358
|
+
}
|
|
359
|
+
case 'month': {
|
|
360
|
+
const yyyymm = luxon_1.DateTime.fromFormat(literal, fMonth);
|
|
361
|
+
const begin = this.literalNode(yyyymm.toFormat(fTimestamp));
|
|
362
|
+
const next = yyyymm.plus({ month: 1 });
|
|
363
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
364
|
+
}
|
|
365
|
+
case 'day': {
|
|
366
|
+
const yyyymmdd = luxon_1.DateTime.fromFormat(literal, fDay);
|
|
367
|
+
const begin = this.literalNode(yyyymmdd.toFormat(fTimestamp));
|
|
368
|
+
const next = yyyymmdd.plus({ day: 1 });
|
|
369
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
370
|
+
}
|
|
371
|
+
case 'hour': {
|
|
372
|
+
const ymdh = luxon_1.DateTime.fromFormat(literal, fHour);
|
|
373
|
+
const begin = this.literalNode(ymdh.toFormat(fTimestamp));
|
|
374
|
+
const next = ymdh.plus({ hour: 1 });
|
|
375
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
376
|
+
}
|
|
377
|
+
case 'minute': {
|
|
378
|
+
const ymdhm = luxon_1.DateTime.fromFormat(literal, fMinute);
|
|
379
|
+
const begin = this.literalNode(ymdhm.toFormat(fTimestamp));
|
|
380
|
+
const next = ymdhm.plus({ minute: 1 });
|
|
381
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
382
|
+
}
|
|
383
|
+
case 'week': {
|
|
384
|
+
const a = luxon_1.DateTime.fromFormat(literal.slice(0, 10), fDay);
|
|
385
|
+
// Luxon uses monday weeks, so look for the Monday week which contains
|
|
386
|
+
// the day after, which for all days except Sunday is the same as
|
|
387
|
+
// the sunday week, and on Sunday it is this monday week instead of
|
|
388
|
+
// last monday week.
|
|
389
|
+
const mondayWeek = a.plus({ day: 1 }).startOf('week');
|
|
390
|
+
// Now back that up by one day and we have the Sunday week
|
|
391
|
+
const ymd_wk = mondayWeek.minus({ day: 1 });
|
|
392
|
+
const begin = this.literalNode(ymd_wk.toFormat(fTimestamp));
|
|
393
|
+
const next = ymd_wk.plus({ days: 7 });
|
|
394
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
395
|
+
}
|
|
396
|
+
case 'quarter': {
|
|
397
|
+
const yyyy = literal.slice(0, 4);
|
|
398
|
+
const q = literal.slice(6);
|
|
399
|
+
if (q === '1') {
|
|
400
|
+
literal = `${yyyy}-01-01 00:00:00`;
|
|
401
|
+
}
|
|
402
|
+
else if (q === '2') {
|
|
403
|
+
literal = `${yyyy}-03-01 00:00:00`;
|
|
404
|
+
}
|
|
405
|
+
else if (q === '3') {
|
|
406
|
+
literal = `${yyyy}-06-01 00:00:00`;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
literal = `${yyyy}-09-01 00:00:00`;
|
|
410
|
+
}
|
|
411
|
+
const begin = this.literalNode(literal);
|
|
412
|
+
const ymd_q = luxon_1.DateTime.fromFormat(literal, fTimestamp);
|
|
413
|
+
const next = ymd_q.plus({ months: 3 });
|
|
414
|
+
return { begin, end: this.literalNode(next.toFormat(fTimestamp)).sql };
|
|
415
|
+
}
|
|
416
|
+
case undefined:
|
|
417
|
+
case 'second': {
|
|
418
|
+
const begin = this.literalNode(literal);
|
|
419
|
+
return { begin, end: begin.sql };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
literalNode(literal) {
|
|
424
|
+
const literalNode = {
|
|
425
|
+
node: 'timeLiteral',
|
|
426
|
+
typeDef: { type: 'timestamp' },
|
|
427
|
+
literal,
|
|
428
|
+
};
|
|
429
|
+
return { ...literalNode, sql: this.d.sqlLiteralTime({}, literalNode) };
|
|
430
|
+
}
|
|
431
|
+
nowExpr() {
|
|
432
|
+
return {
|
|
433
|
+
node: 'now',
|
|
434
|
+
typeDef: { type: 'timestamp' },
|
|
435
|
+
sql: this.d.sqlNowExpr(),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
n(literal) {
|
|
439
|
+
return { node: 'numberLiteral', literal, sql: literal };
|
|
440
|
+
}
|
|
441
|
+
delta(from, op, n, units) {
|
|
442
|
+
const ret = {
|
|
443
|
+
node: 'delta',
|
|
444
|
+
op,
|
|
445
|
+
units,
|
|
446
|
+
kids: {
|
|
447
|
+
base: (0, malloy_types_1.mkTemporal)(from, 'timestamp'),
|
|
448
|
+
delta: this.n(n),
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
return { ...ret, sql: this.d.sqlAlterTimeExpr(ret) };
|
|
452
|
+
}
|
|
453
|
+
dayofWeek(e) {
|
|
454
|
+
const t = {
|
|
455
|
+
node: 'extract',
|
|
456
|
+
e: (0, malloy_types_1.mkTemporal)(e, 'timestamp'),
|
|
457
|
+
units: 'day_of_week',
|
|
458
|
+
};
|
|
459
|
+
return { ...t, sql: this.d.sqlTimeExtractExpr({}, t) };
|
|
460
|
+
}
|
|
461
|
+
nowDot(units) {
|
|
462
|
+
const nowTruncExpr = {
|
|
463
|
+
node: 'trunc',
|
|
464
|
+
e: this.nowExpr(),
|
|
465
|
+
units,
|
|
466
|
+
};
|
|
467
|
+
return { ...nowTruncExpr, sql: this.d.sqlTruncExpr({}, nowTruncExpr) };
|
|
468
|
+
}
|
|
469
|
+
thisUnit(units) {
|
|
470
|
+
const thisUnit = this.nowDot(units);
|
|
471
|
+
const nextUnit = this.delta(thisUnit, '+', '1', units);
|
|
472
|
+
return { begin: thisUnit, end: nextUnit.sql };
|
|
473
|
+
}
|
|
474
|
+
lastUnit(units) {
|
|
475
|
+
const thisUnit = this.nowDot(units);
|
|
476
|
+
const lastUnit = this.delta(thisUnit, '-', '1', units);
|
|
477
|
+
return { begin: lastUnit, end: thisUnit.sql };
|
|
478
|
+
}
|
|
479
|
+
nextUnit(units) {
|
|
480
|
+
const thisUnit = this.nowDot(units);
|
|
481
|
+
const nextUnit = this.delta(thisUnit, '+', '1', units);
|
|
482
|
+
const next2Unit = this.delta(thisUnit, '+', '2', units);
|
|
483
|
+
return { begin: nextUnit, end: next2Unit.sql };
|
|
484
|
+
}
|
|
485
|
+
mod7(n) {
|
|
486
|
+
return this.d.hasModOperator ? `(${n})%7` : `MOD(${n},7)`;
|
|
487
|
+
}
|
|
488
|
+
moment(m) {
|
|
489
|
+
switch (m.moment) {
|
|
490
|
+
case 'now': {
|
|
491
|
+
const now = this.nowExpr();
|
|
492
|
+
return { begin: now, end: now.sql };
|
|
493
|
+
}
|
|
494
|
+
case 'literal':
|
|
495
|
+
return this.expandLiteral(m);
|
|
496
|
+
case 'ago':
|
|
497
|
+
case 'from_now': {
|
|
498
|
+
const nowTruncExpr = this.nowDot(m.units);
|
|
499
|
+
const nowTrunc = (0, malloy_types_1.mkTemporal)(nowTruncExpr, 'timestamp');
|
|
500
|
+
const beginExpr = this.delta(nowTrunc, m.moment === 'ago' ? '-' : '+', m.n, m.units);
|
|
501
|
+
// Now the end is one unit after that .. either n-1 units ago or n+1 units from now
|
|
502
|
+
if (m.moment === 'ago' && m.n === '1') {
|
|
503
|
+
return { begin: beginExpr, end: nowTruncExpr.sql };
|
|
504
|
+
}
|
|
505
|
+
const oneDifferent = Number(m.n) + (m.moment === 'ago' ? -1 : 1);
|
|
506
|
+
const endExpr = {
|
|
507
|
+
...beginExpr,
|
|
508
|
+
kids: { base: nowTrunc, delta: this.n(oneDifferent.toString()) },
|
|
509
|
+
};
|
|
510
|
+
return { begin: beginExpr, end: this.d.sqlAlterTimeExpr(endExpr) };
|
|
511
|
+
}
|
|
512
|
+
case 'today':
|
|
513
|
+
return this.thisUnit('day');
|
|
514
|
+
case 'yesterday':
|
|
515
|
+
return this.lastUnit('day');
|
|
516
|
+
case 'tomorrow':
|
|
517
|
+
return this.nextUnit('day');
|
|
518
|
+
case 'this':
|
|
519
|
+
return this.thisUnit(m.units);
|
|
520
|
+
case 'last':
|
|
521
|
+
return this.lastUnit(m.units);
|
|
522
|
+
case 'next':
|
|
523
|
+
return this.nextUnit(m.units);
|
|
524
|
+
case 'monday':
|
|
525
|
+
case 'tuesday':
|
|
526
|
+
case 'wednesday':
|
|
527
|
+
case 'thursday':
|
|
528
|
+
case 'friday':
|
|
529
|
+
case 'saturday':
|
|
530
|
+
case 'sunday': {
|
|
531
|
+
const destDay = [
|
|
532
|
+
'sunday',
|
|
533
|
+
'monday',
|
|
534
|
+
'tuesday',
|
|
535
|
+
'wednesday',
|
|
536
|
+
'thursday',
|
|
537
|
+
'friday',
|
|
538
|
+
'saturday',
|
|
539
|
+
].indexOf(m.moment);
|
|
540
|
+
const dow = this.dayofWeek(this.nowExpr()).sql;
|
|
541
|
+
if (m.which === 'next') {
|
|
542
|
+
const nForwards = `${this.mod7(`${destDay}-(${dow}-1)+6`)}+1`;
|
|
543
|
+
const begin = this.delta(this.thisUnit('day').begin, '+', nForwards, 'day');
|
|
544
|
+
const end = this.delta(this.thisUnit('day').begin, '+', `${nForwards}+1`, 'day');
|
|
545
|
+
// console.log(
|
|
546
|
+
// `SELECT ${
|
|
547
|
+
// this.nowExpr().sql
|
|
548
|
+
// } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nForwards} as nForwards,\n ${
|
|
549
|
+
// begin.sql
|
|
550
|
+
// } as begin,\n ${end.sql} as end`
|
|
551
|
+
// );
|
|
552
|
+
return { begin, end: end.sql };
|
|
553
|
+
}
|
|
554
|
+
// dacks back = mod((daw0 - dst) + 6, 7) + 1;
|
|
555
|
+
// dacks back = mod(((daw - 1) - dst) + 6, 7) + 1;
|
|
556
|
+
// dacks back = mod(((daw) - dst) + 7, 7) + 1;
|
|
557
|
+
const nBack = `${this.mod7(`(${dow}-1)-${destDay}+6`)}+1`;
|
|
558
|
+
const begin = this.delta(this.thisUnit('day').begin, '-', nBack, 'day');
|
|
559
|
+
const end = this.delta(this.thisUnit('day').begin, '-', `(${nBack})-1`, 'day');
|
|
560
|
+
// console.log(
|
|
561
|
+
// `SELECT ${
|
|
562
|
+
// this.nowExpr().sql
|
|
563
|
+
// } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nBack} as nBack,\n ${
|
|
564
|
+
// begin.sql
|
|
565
|
+
// } as begin,\n ${end.sql} as end`
|
|
566
|
+
// );
|
|
567
|
+
return { begin, end: end.sql };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
isIn(notIn, begin, end) {
|
|
572
|
+
let begOp = '>=';
|
|
573
|
+
let endOp = '<';
|
|
574
|
+
let joinOp = 'AND';
|
|
575
|
+
if (notIn) {
|
|
576
|
+
joinOp = 'OR';
|
|
577
|
+
begOp = '<';
|
|
578
|
+
endOp = '>=';
|
|
579
|
+
}
|
|
580
|
+
begin = this.time(begin);
|
|
581
|
+
end = this.time(end);
|
|
582
|
+
return `${this.expr} ${begOp} ${begin} ${joinOp} ${this.expr} ${endOp} ${end}`;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
exports.TemporalFilterCompiler = TemporalFilterCompiler;
|
|
254
586
|
//# sourceMappingURL=filter_compilers.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { QueryInfo, Dialect, DialectFieldList } from '../dialect';
|
|
2
|
-
import type { AggregateFunctionType, Annotation, CompiledQuery, Expr, FieldDef, Filtered, FunctionOverloadDef, FunctionParameterDef, JoinRelationship, ModelDef, OrderBy, OutputFieldNode, ParameterNode, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SegmentFieldDef, StructDef, StructRef, TurtleDef, FunctionOrderBy, Argument, AggregateExpr, FilterCondition, GenericSQLExpr, FieldnameNode, FunctionCallNode, UngroupNode, SourceReferenceNode, SpreadExpr, FilteredExpr, SourceDef, BooleanFieldDef, QueryResultDef, QueryToMaterialize, PrepareResultOptions, CaseExpr, BasicAtomicDef, AtomicFieldDef } from './malloy_types';
|
|
2
|
+
import type { AggregateFunctionType, Annotation, CompiledQuery, Expr, FieldDef, Filtered, FunctionOverloadDef, FunctionParameterDef, JoinRelationship, ModelDef, OrderBy, OutputFieldNode, ParameterNode, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SegmentFieldDef, StructDef, StructRef, TurtleDef, FunctionOrderBy, Argument, AggregateExpr, FilterCondition, GenericSQLExpr, FieldnameNode, FunctionCallNode, UngroupNode, SourceReferenceNode, SpreadExpr, FilteredExpr, SourceDef, BooleanFieldDef, QueryResultDef, QueryToMaterialize, PrepareResultOptions, CaseExpr, BasicAtomicDef, AtomicFieldDef, FilterMatchExpr } from './malloy_types';
|
|
3
3
|
import type { Connection } from '../connection/types';
|
|
4
4
|
import { AndChain } from './utils';
|
|
5
5
|
import type { EventStream } from '../runtime_types';
|
|
@@ -94,6 +94,7 @@ declare class QueryField extends QueryNode {
|
|
|
94
94
|
generateAnalyticFragment(dialect: string, resultStruct: FieldInstanceResult, context: QueryStruct, expr: Expr, overload: FunctionOverloadDef, state: GenerateState, args: Expr[], partitionByFields?: string[], funcOrdering?: string): string;
|
|
95
95
|
generateCaseSQL(pf: CaseExpr): string;
|
|
96
96
|
exprToSQL(resultSet: FieldInstanceResult, context: QueryStruct, exprToTranslate: Expr, state?: GenerateState): string;
|
|
97
|
+
generateAppliedFilter(context: QueryStruct, filterMatchExpr: FilterMatchExpr): string;
|
|
97
98
|
isNestedInParent(parentDef: FieldDef): boolean;
|
|
98
99
|
isArrayElement(parentDef: FieldDef): boolean;
|
|
99
100
|
generateExpression(resultSet: FieldInstanceResult): string;
|
|
@@ -890,20 +890,55 @@ class QueryField extends QueryNode {
|
|
|
890
890
|
case 'compositeField':
|
|
891
891
|
return '{COMPOSITE_FIELD}';
|
|
892
892
|
case 'filterMatch':
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
expr.dataType === 'timestamp' ||
|
|
897
|
-
expr.dataType === 'boolean') {
|
|
898
|
-
if (expr.filter === null || (0, malloy_filter_1.isFilterExpression)(expr.filter)) {
|
|
899
|
-
return filter_compilers_1.FilterCompilers.compile(expr.dataType, expr.filter, expr.e.sql || '', this.parent.dialect);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
throw new Error(`Internal Error: Filter Compiler Undefined Type '${expr.dataType}'`);
|
|
893
|
+
return this.generateAppliedFilter(context, expr);
|
|
894
|
+
case 'filterLiteral':
|
|
895
|
+
return 'INTERNAL ERROR FILTER EXPRESSION VALUE SHOULD NOT BE USED';
|
|
903
896
|
default:
|
|
904
897
|
throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
|
|
905
898
|
}
|
|
906
899
|
}
|
|
900
|
+
generateAppliedFilter(context, filterMatchExpr) {
|
|
901
|
+
var _a;
|
|
902
|
+
let filterExpr = filterMatchExpr.kids.filterExpr;
|
|
903
|
+
while (filterExpr.node === '()') {
|
|
904
|
+
filterExpr = filterExpr.e;
|
|
905
|
+
}
|
|
906
|
+
if (filterExpr.node === 'parameter') {
|
|
907
|
+
const name = filterExpr.path[0];
|
|
908
|
+
(_a = context.eventStream) === null || _a === void 0 ? void 0 : _a.emit('source-argument-compiled', { name });
|
|
909
|
+
const argument = context.arguments()[name];
|
|
910
|
+
if (argument.value) {
|
|
911
|
+
filterExpr = argument.value;
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
throw new Error(`Parameter ${name} was expected to be a filter expression`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (filterExpr.node !== 'filterLiteral') {
|
|
918
|
+
throw new Error('Can only use filter expression literals or parameters as filter expressions');
|
|
919
|
+
}
|
|
920
|
+
const filterSrc = filterExpr.filterSrc;
|
|
921
|
+
let fParse;
|
|
922
|
+
switch (filterMatchExpr.dataType) {
|
|
923
|
+
case 'string':
|
|
924
|
+
fParse = malloy_filter_1.StringFilterExpression.parse(filterSrc);
|
|
925
|
+
break;
|
|
926
|
+
case 'number':
|
|
927
|
+
fParse = malloy_filter_1.NumberFilterExpression.parse(filterSrc);
|
|
928
|
+
break;
|
|
929
|
+
case 'boolean':
|
|
930
|
+
fParse = malloy_filter_1.BooleanFilterExpression.parse(filterSrc);
|
|
931
|
+
break;
|
|
932
|
+
case 'date':
|
|
933
|
+
case 'timestamp':
|
|
934
|
+
fParse = malloy_filter_1.TemporalFilterExpression.parse(filterSrc);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
if (fParse.log.length > 0) {
|
|
938
|
+
throw new Error(`Filter expression parse error: ${fParse.log[0]}`);
|
|
939
|
+
}
|
|
940
|
+
return filter_compilers_1.FilterCompilers.compile(filterMatchExpr.dataType, fParse.parsed, filterMatchExpr.kids.expr.sql || '', context.dialect);
|
|
941
|
+
}
|
|
907
942
|
isNestedInParent(parentDef) {
|
|
908
943
|
switch (parentDef.type) {
|
|
909
944
|
case 'record':
|