@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.
Files changed (40) hide show
  1. package/dist/lang/ast/expressions/expr-filter-expr.d.ts +0 -3
  2. package/dist/lang/ast/expressions/expr-filter-expr.js +7 -59
  3. package/dist/lang/ast/expressions/expr-func.js +3 -0
  4. package/dist/lang/ast/expressions/pick-when.js +6 -0
  5. package/dist/lang/ast/field-space/dynamic-space.js +1 -1
  6. package/dist/lang/ast/field-space/join-space-field.js +1 -1
  7. package/dist/lang/ast/index.d.ts +1 -0
  8. package/dist/lang/ast/index.js +1 -0
  9. package/dist/lang/ast/parameters/has-parameter.d.ts +6 -3
  10. package/dist/lang/ast/parameters/has-parameter.js +28 -9
  11. package/dist/lang/ast/query-items/field-declaration.d.ts +1 -2
  12. package/dist/lang/ast/query-items/field-declaration.js +0 -4
  13. package/dist/lang/ast/source-elements/named-source.js +6 -0
  14. package/dist/lang/ast/source-properties/join.d.ts +3 -3
  15. package/dist/lang/ast/source-properties/join.js +2 -2
  16. package/dist/lang/ast/types/expression-def.d.ts +2 -1
  17. package/dist/lang/ast/types/expression-def.js +79 -17
  18. package/dist/lang/ast/types/space-param.d.ts +1 -3
  19. package/dist/lang/ast/types/space-param.js +8 -8
  20. package/dist/lang/lib/Malloy/MalloyLexer.d.ts +114 -113
  21. package/dist/lang/lib/Malloy/MalloyLexer.js +1272 -1266
  22. package/dist/lang/lib/Malloy/MalloyParser.d.ts +253 -239
  23. package/dist/lang/lib/Malloy/MalloyParser.js +2244 -2142
  24. package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +11 -0
  25. package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +7 -0
  26. package/dist/lang/malloy-to-ast.d.ts +1 -1
  27. package/dist/lang/malloy-to-ast.js +38 -12
  28. package/dist/lang/test/expr-to-str.js +3 -20
  29. package/dist/model/filter_compilers.d.ts +27 -0
  30. package/dist/model/filter_compilers.js +335 -3
  31. package/dist/model/malloy_query.d.ts +2 -1
  32. package/dist/model/malloy_query.js +45 -10
  33. package/dist/model/malloy_types.d.ts +18 -6
  34. package/dist/model/malloy_types.js +6 -2
  35. package/dist/to_stable.js +19 -5
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +4 -4
  39. package/dist/model/filter_temporal_compiler.d.ts +0 -29
  40. 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
- const defaultValue = defaultCx
267
- ? this.astAt(new constant_expression_1.ConstantExpression(this.getFieldExpr(defaultCx)), defaultCx)
268
- : undefined;
269
- const typeCx = pcx.malloyType();
270
- const type = typeCx ? this.getMalloyType(typeCx) : undefined;
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
- return pcx.sourceParameter().map(param => this.getSourceParameter(param));
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.e)} | ${filterText}}`;
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 filter_temporal_compiler_1 = require("./filter_temporal_compiler");
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 filter_temporal_compiler_1.TemporalFilterCompiler(x, d, t);
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
- if (expr.dataType === 'string' ||
894
- expr.dataType === 'number' ||
895
- expr.dataType === 'date' ||
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':