@malloydata/malloy 0.0.242-dev250313002313 → 0.0.242-dev250313045635
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 +7 -3
- package/dist/dialect/dialect.js +25 -12
- package/dist/lang/ast/expressions/expr-filter-expr.d.ts +11 -0
- package/dist/lang/ast/expressions/expr-filter-expr.js +61 -0
- package/dist/lang/ast/index.d.ts +1 -0
- package/dist/lang/ast/index.js +1 -0
- package/dist/lang/lib/Malloy/MalloyLexer.d.ts +64 -58
- package/dist/lang/lib/Malloy/MalloyLexer.js +1216 -1158
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +109 -61
- package/dist/lang/lib/Malloy/MalloyParser.js +2059 -1734
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +46 -0
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +29 -0
- package/dist/lang/malloy-to-ast.d.ts +2 -0
- package/dist/lang/malloy-to-ast.js +19 -1
- package/dist/lang/parse-log.d.ts +2 -0
- package/dist/lang/test/expr-to-str.d.ts +9 -0
- package/dist/lang/test/expr-to-str.js +124 -0
- package/dist/lang/test/parse-expects.js +2 -91
- package/dist/model/filter_compilers.d.ts +5 -0
- package/dist/model/filter_compilers.js +160 -0
- package/dist/model/malloy_query.js +12 -4
- package/dist/model/malloy_types.d.ts +12 -2
- package/dist/model/malloy_types.js +6 -2
- package/package.json +4 -3
|
@@ -19,6 +19,7 @@ import { ExprTimeContext } from "./MalloyParser";
|
|
|
19
19
|
import { ExprNULLContext } from "./MalloyParser";
|
|
20
20
|
import { ExprBoolContext } from "./MalloyParser";
|
|
21
21
|
import { ExprRegexContext } from "./MalloyParser";
|
|
22
|
+
import { FilterString_stubContext } from "./MalloyParser";
|
|
22
23
|
import { ExprNowContext } from "./MalloyParser";
|
|
23
24
|
import { TableFunctionContext } from "./MalloyParser";
|
|
24
25
|
import { TableMethodContext } from "./MalloyParser";
|
|
@@ -235,6 +236,9 @@ import { FieldNameContext } from "./MalloyParser";
|
|
|
235
236
|
import { SqlExploreNameRefContext } from "./MalloyParser";
|
|
236
237
|
import { NameSQLBlockContext } from "./MalloyParser";
|
|
237
238
|
import { ConnectionNameContext } from "./MalloyParser";
|
|
239
|
+
import { TripFilterStringContext } from "./MalloyParser";
|
|
240
|
+
import { TickFilterStringContext } from "./MalloyParser";
|
|
241
|
+
import { FilterStringContext } from "./MalloyParser";
|
|
238
242
|
import { DebugExprContext } from "./MalloyParser";
|
|
239
243
|
import { DebugPartialContext } from "./MalloyParser";
|
|
240
244
|
import { ExperimentalStatementForTestingContext } from "./MalloyParser";
|
|
@@ -483,6 +487,18 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
483
487
|
* @param ctx the parse tree
|
|
484
488
|
*/
|
|
485
489
|
exitExprRegex?: (ctx: ExprRegexContext) => void;
|
|
490
|
+
/**
|
|
491
|
+
* Enter a parse tree produced by the `filterString_stub`
|
|
492
|
+
* labeled alternative in `MalloyParser.literal`.
|
|
493
|
+
* @param ctx the parse tree
|
|
494
|
+
*/
|
|
495
|
+
enterFilterString_stub?: (ctx: FilterString_stubContext) => void;
|
|
496
|
+
/**
|
|
497
|
+
* Exit a parse tree produced by the `filterString_stub`
|
|
498
|
+
* labeled alternative in `MalloyParser.literal`.
|
|
499
|
+
* @param ctx the parse tree
|
|
500
|
+
*/
|
|
501
|
+
exitFilterString_stub?: (ctx: FilterString_stubContext) => void;
|
|
486
502
|
/**
|
|
487
503
|
* Enter a parse tree produced by the `exprNow`
|
|
488
504
|
* labeled alternative in `MalloyParser.literal`.
|
|
@@ -2777,6 +2793,36 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
2777
2793
|
* @param ctx the parse tree
|
|
2778
2794
|
*/
|
|
2779
2795
|
exitConnectionName?: (ctx: ConnectionNameContext) => void;
|
|
2796
|
+
/**
|
|
2797
|
+
* Enter a parse tree produced by `MalloyParser.tripFilterString`.
|
|
2798
|
+
* @param ctx the parse tree
|
|
2799
|
+
*/
|
|
2800
|
+
enterTripFilterString?: (ctx: TripFilterStringContext) => void;
|
|
2801
|
+
/**
|
|
2802
|
+
* Exit a parse tree produced by `MalloyParser.tripFilterString`.
|
|
2803
|
+
* @param ctx the parse tree
|
|
2804
|
+
*/
|
|
2805
|
+
exitTripFilterString?: (ctx: TripFilterStringContext) => void;
|
|
2806
|
+
/**
|
|
2807
|
+
* Enter a parse tree produced by `MalloyParser.tickFilterString`.
|
|
2808
|
+
* @param ctx the parse tree
|
|
2809
|
+
*/
|
|
2810
|
+
enterTickFilterString?: (ctx: TickFilterStringContext) => void;
|
|
2811
|
+
/**
|
|
2812
|
+
* Exit a parse tree produced by `MalloyParser.tickFilterString`.
|
|
2813
|
+
* @param ctx the parse tree
|
|
2814
|
+
*/
|
|
2815
|
+
exitTickFilterString?: (ctx: TickFilterStringContext) => void;
|
|
2816
|
+
/**
|
|
2817
|
+
* Enter a parse tree produced by `MalloyParser.filterString`.
|
|
2818
|
+
* @param ctx the parse tree
|
|
2819
|
+
*/
|
|
2820
|
+
enterFilterString?: (ctx: FilterStringContext) => void;
|
|
2821
|
+
/**
|
|
2822
|
+
* Exit a parse tree produced by `MalloyParser.filterString`.
|
|
2823
|
+
* @param ctx the parse tree
|
|
2824
|
+
*/
|
|
2825
|
+
exitFilterString?: (ctx: FilterStringContext) => void;
|
|
2780
2826
|
/**
|
|
2781
2827
|
* Enter a parse tree produced by `MalloyParser.debugExpr`.
|
|
2782
2828
|
* @param ctx the parse tree
|
|
@@ -19,6 +19,7 @@ import { ExprTimeContext } from "./MalloyParser";
|
|
|
19
19
|
import { ExprNULLContext } from "./MalloyParser";
|
|
20
20
|
import { ExprBoolContext } from "./MalloyParser";
|
|
21
21
|
import { ExprRegexContext } from "./MalloyParser";
|
|
22
|
+
import { FilterString_stubContext } from "./MalloyParser";
|
|
22
23
|
import { ExprNowContext } from "./MalloyParser";
|
|
23
24
|
import { TableFunctionContext } from "./MalloyParser";
|
|
24
25
|
import { TableMethodContext } from "./MalloyParser";
|
|
@@ -235,6 +236,9 @@ import { FieldNameContext } from "./MalloyParser";
|
|
|
235
236
|
import { SqlExploreNameRefContext } from "./MalloyParser";
|
|
236
237
|
import { NameSQLBlockContext } from "./MalloyParser";
|
|
237
238
|
import { ConnectionNameContext } from "./MalloyParser";
|
|
239
|
+
import { TripFilterStringContext } from "./MalloyParser";
|
|
240
|
+
import { TickFilterStringContext } from "./MalloyParser";
|
|
241
|
+
import { FilterStringContext } from "./MalloyParser";
|
|
238
242
|
import { DebugExprContext } from "./MalloyParser";
|
|
239
243
|
import { DebugPartialContext } from "./MalloyParser";
|
|
240
244
|
import { ExperimentalStatementForTestingContext } from "./MalloyParser";
|
|
@@ -386,6 +390,13 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
386
390
|
* @return the visitor result
|
|
387
391
|
*/
|
|
388
392
|
visitExprRegex?: (ctx: ExprRegexContext) => Result;
|
|
393
|
+
/**
|
|
394
|
+
* Visit a parse tree produced by the `filterString_stub`
|
|
395
|
+
* labeled alternative in `MalloyParser.literal`.
|
|
396
|
+
* @param ctx the parse tree
|
|
397
|
+
* @return the visitor result
|
|
398
|
+
*/
|
|
399
|
+
visitFilterString_stub?: (ctx: FilterString_stubContext) => Result;
|
|
389
400
|
/**
|
|
390
401
|
* Visit a parse tree produced by the `exprNow`
|
|
391
402
|
* labeled alternative in `MalloyParser.literal`.
|
|
@@ -1749,6 +1760,24 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
1749
1760
|
* @return the visitor result
|
|
1750
1761
|
*/
|
|
1751
1762
|
visitConnectionName?: (ctx: ConnectionNameContext) => Result;
|
|
1763
|
+
/**
|
|
1764
|
+
* Visit a parse tree produced by `MalloyParser.tripFilterString`.
|
|
1765
|
+
* @param ctx the parse tree
|
|
1766
|
+
* @return the visitor result
|
|
1767
|
+
*/
|
|
1768
|
+
visitTripFilterString?: (ctx: TripFilterStringContext) => Result;
|
|
1769
|
+
/**
|
|
1770
|
+
* Visit a parse tree produced by `MalloyParser.tickFilterString`.
|
|
1771
|
+
* @param ctx the parse tree
|
|
1772
|
+
* @return the visitor result
|
|
1773
|
+
*/
|
|
1774
|
+
visitTickFilterString?: (ctx: TickFilterStringContext) => Result;
|
|
1775
|
+
/**
|
|
1776
|
+
* Visit a parse tree produced by `MalloyParser.filterString`.
|
|
1777
|
+
* @param ctx the parse tree
|
|
1778
|
+
* @return the visitor result
|
|
1779
|
+
*/
|
|
1780
|
+
visitFilterString?: (ctx: FilterStringContext) => Result;
|
|
1752
1781
|
/**
|
|
1753
1782
|
* Visit a parse tree produced by `MalloyParser.debugExpr`.
|
|
1754
1783
|
* @param ctx the parse tree
|
|
@@ -242,5 +242,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
242
242
|
visitExprWarnLike(pcx: parse.ExprWarnLikeContext): ast.ExprCompare;
|
|
243
243
|
visitExprNullCheck(pcx: parse.ExprNullCheckContext): ast.ExprIsNull;
|
|
244
244
|
visitExprWarnIn(pcx: parse.ExprWarnInContext): ast.ExprLegacyIn;
|
|
245
|
+
visitTickFilterString(pcx: parse.TickFilterStringContext): ast.ExprFilterExpression;
|
|
246
|
+
visitTripFilterString(pcx: parse.TripFilterStringContext): ast.ExprFilterExpression;
|
|
245
247
|
}
|
|
246
248
|
export {};
|
|
@@ -917,7 +917,8 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
917
917
|
const str = pcx.text.slice(1).trimStart();
|
|
918
918
|
const lastChar = str[str.length - 1];
|
|
919
919
|
if (lastChar === '\n') {
|
|
920
|
-
|
|
920
|
+
const t = str[0] === "'" ? '"' : "'";
|
|
921
|
+
this.contextError(pcx, 'literal-string-newline', `Missing ${t}${str[0]}${t} before end-of-line`);
|
|
921
922
|
}
|
|
922
923
|
const astStr = new ast.ExprString(str.slice(1, -1));
|
|
923
924
|
return this.astAt(astStr, pcx);
|
|
@@ -1513,6 +1514,23 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1513
1514
|
.join(' | ')})`);
|
|
1514
1515
|
return inStmt;
|
|
1515
1516
|
}
|
|
1517
|
+
visitTickFilterString(pcx) {
|
|
1518
|
+
const fString = pcx.text.slice(1).trimStart(); // remove fSPACE
|
|
1519
|
+
const lastChar = fString[fString.length - 1];
|
|
1520
|
+
if (lastChar === '\n') {
|
|
1521
|
+
const t = fString[0] === "'" ? '"' : "'";
|
|
1522
|
+
this.contextError(pcx, 'literal-string-newline', `Missing $${t}${fString[0]}${t} before end-of-line`);
|
|
1523
|
+
}
|
|
1524
|
+
const filterText = fString.slice(1, -1);
|
|
1525
|
+
const mfe = new ast.ExprFilterExpression(filterText);
|
|
1526
|
+
return this.astAt(mfe, pcx);
|
|
1527
|
+
}
|
|
1528
|
+
visitTripFilterString(pcx) {
|
|
1529
|
+
const fString = pcx.text.slice(1).trimStart();
|
|
1530
|
+
const filterText = fString.slice(3, -3);
|
|
1531
|
+
const mfe = new ast.ExprFilterExpression(filterText);
|
|
1532
|
+
return this.astAt(mfe, pcx);
|
|
1533
|
+
}
|
|
1516
1534
|
}
|
|
1517
1535
|
exports.MalloyToAST = MalloyToAST;
|
|
1518
1536
|
//# sourceMappingURL=malloy-to-ast.js.map
|
package/dist/lang/parse-log.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ type MessageParameterTypes = {
|
|
|
54
54
|
'pick-when-must-be-boolean': {
|
|
55
55
|
whenType: ExpressionValueType;
|
|
56
56
|
};
|
|
57
|
+
'pick-non-atomic-type': string;
|
|
57
58
|
'experiment-not-enabled': {
|
|
58
59
|
experimentId: string;
|
|
59
60
|
};
|
|
@@ -362,6 +363,7 @@ type MessageParameterTypes = {
|
|
|
362
363
|
'unsupported-path-in-include': string;
|
|
363
364
|
'wildcard-include-rename': string;
|
|
364
365
|
'literal-string-newline': string;
|
|
366
|
+
'filter-expression-type': string;
|
|
365
367
|
'invalid-malloy-query-document': string;
|
|
366
368
|
};
|
|
367
369
|
export declare const MESSAGE_FORMATTERS: PartialErrorCodeMessageMap;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Expr } from '../../model/malloy_types';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a readable shorthand for the an Expr for use in debugging.
|
|
4
|
+
* If not passed any symbols, the first field reference will be A,
|
|
5
|
+
* the second B and so on in the output.
|
|
6
|
+
*/
|
|
7
|
+
type ESymbols = Record<string, string> | undefined;
|
|
8
|
+
export declare function exprToStr(e: Expr, symbols: ESymbols): string;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
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.exprToStr = void 0;
|
|
10
|
+
const malloy_types_1 = require("../../model/malloy_types");
|
|
11
|
+
const malloy_filter_1 = require("@malloydata/malloy-filter");
|
|
12
|
+
function exprToStr(e, symbols) {
|
|
13
|
+
function subExpr(e) {
|
|
14
|
+
return exprToStr(e, symbols);
|
|
15
|
+
}
|
|
16
|
+
switch (e.node) {
|
|
17
|
+
case 'field': {
|
|
18
|
+
const ref = e.path.join('.');
|
|
19
|
+
if (symbols) {
|
|
20
|
+
if (symbols[ref] === undefined) {
|
|
21
|
+
const nSyms = Object.keys(symbols).length;
|
|
22
|
+
symbols[ref] = String.fromCharCode('A'.charCodeAt(0) + nSyms);
|
|
23
|
+
}
|
|
24
|
+
return symbols[ref];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return ref;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
case '()':
|
|
31
|
+
return `(${subExpr(e.e)})`;
|
|
32
|
+
case 'numberLiteral':
|
|
33
|
+
return `${e.literal}`;
|
|
34
|
+
case 'stringLiteral':
|
|
35
|
+
return `{"${e.literal}"}`;
|
|
36
|
+
case 'timeLiteral':
|
|
37
|
+
return `@${e.literal}`;
|
|
38
|
+
case 'recordLiteral': {
|
|
39
|
+
const parts = [];
|
|
40
|
+
for (const [name, val] of Object.entries(e.kids)) {
|
|
41
|
+
parts.push(`${name}:${subExpr(val)}`);
|
|
42
|
+
}
|
|
43
|
+
return `{${parts.join(', ')}}`;
|
|
44
|
+
}
|
|
45
|
+
case 'arrayLiteral': {
|
|
46
|
+
const parts = e.kids.values.map(k => subExpr(k));
|
|
47
|
+
return `[${parts.join(', ')}]`;
|
|
48
|
+
}
|
|
49
|
+
case 'regexpLiteral':
|
|
50
|
+
return `/${e.literal}/`;
|
|
51
|
+
case 'trunc':
|
|
52
|
+
return `{timeTrunc-${e.units} ${subExpr(e.e)}}`;
|
|
53
|
+
case 'delta':
|
|
54
|
+
return `{${e.op}${e.units} ${subExpr(e.kids.base)} ${subExpr(e.kids.delta)}}`;
|
|
55
|
+
case 'true':
|
|
56
|
+
case 'false':
|
|
57
|
+
return e.node;
|
|
58
|
+
case 'case': {
|
|
59
|
+
const caseStmt = ['case'];
|
|
60
|
+
if (e.kids.caseValue !== undefined) {
|
|
61
|
+
caseStmt.push(`${subExpr(e.kids.caseValue)}`);
|
|
62
|
+
}
|
|
63
|
+
for (let i = 0; i < e.kids.caseWhen.length; i += 1) {
|
|
64
|
+
caseStmt.push(`when ${subExpr(e.kids.caseWhen[i])} then ${subExpr(e.kids.caseThen[i])}`);
|
|
65
|
+
}
|
|
66
|
+
if (e.kids.caseElse !== undefined) {
|
|
67
|
+
caseStmt.push(`else ${subExpr(e.kids.caseElse)}`);
|
|
68
|
+
}
|
|
69
|
+
return `{${caseStmt.join(' ')}}`;
|
|
70
|
+
}
|
|
71
|
+
case 'regexpMatch':
|
|
72
|
+
return `{${subExpr(e.kids.expr)} regex-match ${subExpr(e.kids.regex)}}`;
|
|
73
|
+
case 'in': {
|
|
74
|
+
return `{${subExpr(e.kids.e)} ${e.not ? 'not in' : 'in'} {${e.kids.oneOf
|
|
75
|
+
.map(o => `${subExpr(o)}`)
|
|
76
|
+
.join(',')}}}`;
|
|
77
|
+
}
|
|
78
|
+
case 'genericSQLExpr': {
|
|
79
|
+
let sql = '';
|
|
80
|
+
let i = 0;
|
|
81
|
+
for (; i < e.kids.args.length; i++) {
|
|
82
|
+
sql += `${e.src[i]}{${subExpr(e.kids.args[i])}}`;
|
|
83
|
+
}
|
|
84
|
+
if (i < e.src.length) {
|
|
85
|
+
sql += e.src[i];
|
|
86
|
+
}
|
|
87
|
+
return sql;
|
|
88
|
+
}
|
|
89
|
+
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
|
+
const fType = `${e.dataType[0].toUpperCase()}${e.dataType.slice(1)}`;
|
|
109
|
+
return `{filter${fType} ${subExpr(e.e)} | ${filterText}}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if ((0, malloy_types_1.exprHasKids)(e) && e.kids['left'] && e.kids['right']) {
|
|
113
|
+
return `{${subExpr(e.kids['left'])} ${e.node} ${subExpr(e.kids['right'])}}`;
|
|
114
|
+
}
|
|
115
|
+
else if ((0, malloy_types_1.exprHasE)(e)) {
|
|
116
|
+
return `{${e.node} ${subExpr(e.e)}}`;
|
|
117
|
+
}
|
|
118
|
+
else if ((0, malloy_types_1.exprIsLeaf)(e)) {
|
|
119
|
+
return `{${e.node}}`;
|
|
120
|
+
}
|
|
121
|
+
return `{?${e.node}}`;
|
|
122
|
+
}
|
|
123
|
+
exports.exprToStr = exprToStr;
|
|
124
|
+
//# sourceMappingURL=expr-to-str.js.map
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
const
|
|
26
|
+
const expr_to_str_1 = require("./expr-to-str");
|
|
27
27
|
const test_translator_1 = require("./test-translator");
|
|
28
28
|
function rangeToStr(loc) {
|
|
29
29
|
if (loc) {
|
|
@@ -122,95 +122,6 @@ function xlated(tt, warningsOkay = false) {
|
|
|
122
122
|
tt.translate();
|
|
123
123
|
return checkForNeededs(tt);
|
|
124
124
|
}
|
|
125
|
-
function eToStr(e, symbols) {
|
|
126
|
-
function subExpr(e) {
|
|
127
|
-
return eToStr(e, symbols);
|
|
128
|
-
}
|
|
129
|
-
switch (e.node) {
|
|
130
|
-
case 'field': {
|
|
131
|
-
const ref = e.path.join('.');
|
|
132
|
-
if (symbols) {
|
|
133
|
-
if (symbols[ref] === undefined) {
|
|
134
|
-
const nSyms = Object.keys(symbols).length;
|
|
135
|
-
symbols[ref] = String.fromCharCode('A'.charCodeAt(0) + nSyms);
|
|
136
|
-
}
|
|
137
|
-
return symbols[ref];
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
return ref;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
case '()':
|
|
144
|
-
return `(${subExpr(e.e)})`;
|
|
145
|
-
case 'numberLiteral':
|
|
146
|
-
return `${e.literal}`;
|
|
147
|
-
case 'stringLiteral':
|
|
148
|
-
return `{"${e.literal}"}`;
|
|
149
|
-
case 'timeLiteral':
|
|
150
|
-
return `@${e.literal}`;
|
|
151
|
-
case 'recordLiteral': {
|
|
152
|
-
const parts = [];
|
|
153
|
-
for (const [name, val] of Object.entries(e.kids)) {
|
|
154
|
-
parts.push(`${name}:${subExpr(val)}`);
|
|
155
|
-
}
|
|
156
|
-
return `{${parts.join(', ')}}`;
|
|
157
|
-
}
|
|
158
|
-
case 'arrayLiteral': {
|
|
159
|
-
const parts = e.kids.values.map(k => subExpr(k));
|
|
160
|
-
return `[${parts.join(', ')}]`;
|
|
161
|
-
}
|
|
162
|
-
case 'regexpLiteral':
|
|
163
|
-
return `/${e.literal}/`;
|
|
164
|
-
case 'trunc':
|
|
165
|
-
return `{timeTrunc-${e.units} ${subExpr(e.e)}}`;
|
|
166
|
-
case 'delta':
|
|
167
|
-
return `{${e.op}${e.units} ${subExpr(e.kids.base)} ${subExpr(e.kids.delta)}}`;
|
|
168
|
-
case 'true':
|
|
169
|
-
case 'false':
|
|
170
|
-
return e.node;
|
|
171
|
-
case 'case': {
|
|
172
|
-
const caseStmt = ['case'];
|
|
173
|
-
if (e.kids.caseValue !== undefined) {
|
|
174
|
-
caseStmt.push(`${subExpr(e.kids.caseValue)}`);
|
|
175
|
-
}
|
|
176
|
-
for (let i = 0; i < e.kids.caseWhen.length; i += 1) {
|
|
177
|
-
caseStmt.push(`when ${subExpr(e.kids.caseWhen[i])} then ${subExpr(e.kids.caseThen[i])}`);
|
|
178
|
-
}
|
|
179
|
-
if (e.kids.caseElse !== undefined) {
|
|
180
|
-
caseStmt.push(`else ${subExpr(e.kids.caseElse)}`);
|
|
181
|
-
}
|
|
182
|
-
return `{${caseStmt.join(' ')}}`;
|
|
183
|
-
}
|
|
184
|
-
case 'regexpMatch':
|
|
185
|
-
return `{${subExpr(e.kids.expr)} regex-match ${subExpr(e.kids.regex)}}`;
|
|
186
|
-
case 'in': {
|
|
187
|
-
return `{${subExpr(e.kids.e)} ${e.not ? 'not in' : 'in'} {${e.kids.oneOf
|
|
188
|
-
.map(o => `${subExpr(o)}`)
|
|
189
|
-
.join(',')}}}`;
|
|
190
|
-
}
|
|
191
|
-
case 'genericSQLExpr': {
|
|
192
|
-
let sql = '';
|
|
193
|
-
let i = 0;
|
|
194
|
-
for (; i < e.kids.args.length; i++) {
|
|
195
|
-
sql += `${e.src[i]}{${subExpr(e.kids.args[i])}}`;
|
|
196
|
-
}
|
|
197
|
-
if (i < e.src.length) {
|
|
198
|
-
sql += e.src[i];
|
|
199
|
-
}
|
|
200
|
-
return sql;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if ((0, model_1.exprHasKids)(e) && e.kids['left'] && e.kids['right']) {
|
|
204
|
-
return `{${subExpr(e.kids['left'])} ${e.node} ${subExpr(e.kids['right'])}}`;
|
|
205
|
-
}
|
|
206
|
-
else if ((0, model_1.exprHasE)(e)) {
|
|
207
|
-
return `{${e.node} ${subExpr(e.e)}}`;
|
|
208
|
-
}
|
|
209
|
-
else if ((0, model_1.exprIsLeaf)(e)) {
|
|
210
|
-
return `{${e.node}}`;
|
|
211
|
-
}
|
|
212
|
-
return `{?${e.node}}`;
|
|
213
|
-
}
|
|
214
125
|
expect.extend({
|
|
215
126
|
toParse: function (tx) {
|
|
216
127
|
const x = xlator(tx);
|
|
@@ -286,7 +197,7 @@ expect.extend({
|
|
|
286
197
|
if (!badRefs.pass) {
|
|
287
198
|
return badRefs;
|
|
288
199
|
}
|
|
289
|
-
const rcvExpr =
|
|
200
|
+
const rcvExpr = (0, expr_to_str_1.exprToStr)(bx.generated().value, undefined);
|
|
290
201
|
const pass = this.equals(rcvExpr, expr);
|
|
291
202
|
const msg = pass ? `Matched: ${rcvExpr}` : this.utils.diff(expr, rcvExpr);
|
|
292
203
|
return { pass, message: () => `${msg}` };
|
|
@@ -0,0 +1,160 @@
|
|
|
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.FilterCompilers = void 0;
|
|
10
|
+
function likeSafe(v) {
|
|
11
|
+
return v.replace(/([_%])g/, '\\$1');
|
|
12
|
+
}
|
|
13
|
+
function unlike(disLiked, x) {
|
|
14
|
+
const orNull = `OR ${x} IS NULL`;
|
|
15
|
+
if (disLiked.length === 1) {
|
|
16
|
+
return `${disLiked[0]} ${orNull}`;
|
|
17
|
+
}
|
|
18
|
+
return `(${disLiked.join(' and ')}) $orNULL`;
|
|
19
|
+
}
|
|
20
|
+
/*
|
|
21
|
+
* These compilers from filter expression to SQL actually belong in malloy-filters but
|
|
22
|
+
* that will require moving Dialect out to malloy-dialect to avoid a circular dependency
|
|
23
|
+
* between the filter compilers and packages/malloy. That's why these are
|
|
24
|
+
* defined in this slightly weird way. At some point the code here for
|
|
25
|
+
* XXXXFilterCompiler.compile() will move to XXXFilterExpression.compile()
|
|
26
|
+
*/
|
|
27
|
+
exports.FilterCompilers = {
|
|
28
|
+
stringCompile(sc, x, d) {
|
|
29
|
+
switch (sc.operator) {
|
|
30
|
+
case 'null':
|
|
31
|
+
return sc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
|
|
32
|
+
case 'empty':
|
|
33
|
+
return sc.not ? `COALESCE(${x},'') != ''` : `COALESCE(${x},'') = ''`;
|
|
34
|
+
case '=': {
|
|
35
|
+
if (sc.values.length === 1) {
|
|
36
|
+
const eq = sc.not ? '!=' : '=';
|
|
37
|
+
const compare = `${x} ${eq} ${d.sqlLiteralString(sc.values[0])}`;
|
|
38
|
+
return sc.not ? `(${compare} OR ${x} IS NULL)` : compare;
|
|
39
|
+
}
|
|
40
|
+
const eqList = '(' + sc.values.map(v => d.sqlLiteralString(v)).join(', ') + ')';
|
|
41
|
+
return sc.not
|
|
42
|
+
? `(${x} NOT IN ${eqList} OR ${x} IS NULL)`
|
|
43
|
+
: `${x} IN ${eqList}`;
|
|
44
|
+
}
|
|
45
|
+
case '()': {
|
|
46
|
+
const wrapped = '(' + exports.FilterCompilers.stringCompile(sc.expr, x, d) + ')';
|
|
47
|
+
return sc.not ? `not ${wrapped}` : wrapped;
|
|
48
|
+
}
|
|
49
|
+
case 'contains': {
|
|
50
|
+
const matches = sc.values.map(v => '%' + likeSafe(v) + '%');
|
|
51
|
+
if (sc.not) {
|
|
52
|
+
return unlike(matches.map(m => d.sqlLike('NOT LIKE', x, m)), x);
|
|
53
|
+
}
|
|
54
|
+
return matches.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
|
|
55
|
+
}
|
|
56
|
+
case 'starts': {
|
|
57
|
+
const matches = sc.values.map(v => likeSafe(v) + '%');
|
|
58
|
+
if (sc.not) {
|
|
59
|
+
return unlike(matches.map(m => d.sqlLike('NOT LIKE', x, m)), x);
|
|
60
|
+
}
|
|
61
|
+
return matches.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
|
|
62
|
+
}
|
|
63
|
+
case 'ends': {
|
|
64
|
+
const matches = sc.values.map(v => '%' + likeSafe(v));
|
|
65
|
+
if (sc.not) {
|
|
66
|
+
return unlike(matches.map(m => d.sqlLike('NOT LIKE', x, m)), x);
|
|
67
|
+
}
|
|
68
|
+
return matches.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
|
|
69
|
+
}
|
|
70
|
+
case '~':
|
|
71
|
+
if (sc.not) {
|
|
72
|
+
return unlike(sc.escaped_values.map(m => d.sqlLike('NOT LIKE', x, m)), x);
|
|
73
|
+
}
|
|
74
|
+
return sc.escaped_values.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
|
|
75
|
+
case 'and': {
|
|
76
|
+
const clauses = sc.members.map(c => exports.FilterCompilers.stringCompile(c, x, d));
|
|
77
|
+
return clauses.join(' and ');
|
|
78
|
+
}
|
|
79
|
+
case 'or': {
|
|
80
|
+
const clauses = sc.members.map(c => exports.FilterCompilers.stringCompile(c, x, d));
|
|
81
|
+
return clauses.join(' or ');
|
|
82
|
+
}
|
|
83
|
+
case ',': {
|
|
84
|
+
/*
|
|
85
|
+
* Basic formula over all members
|
|
86
|
+
* ALL INCLUDED THINGS OR TOGETHER AND ALL EXCLUDED THINGS ANDED TOGETHER
|
|
87
|
+
*/
|
|
88
|
+
const includes = [];
|
|
89
|
+
const excludes = [];
|
|
90
|
+
let includeNull = false;
|
|
91
|
+
let excludeNull = false;
|
|
92
|
+
let includeEmpty = false;
|
|
93
|
+
let excludeEmpty = false;
|
|
94
|
+
for (const c of sc.members) {
|
|
95
|
+
switch (c.operator) {
|
|
96
|
+
case 'or':
|
|
97
|
+
case 'and':
|
|
98
|
+
case ',':
|
|
99
|
+
includes.push(c);
|
|
100
|
+
break;
|
|
101
|
+
case 'null':
|
|
102
|
+
if (c.not) {
|
|
103
|
+
excludeNull = true;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
includeNull = true;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case 'empty':
|
|
110
|
+
if (c.not) {
|
|
111
|
+
excludeEmpty = true;
|
|
112
|
+
excludeNull = true;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
includeEmpty = true;
|
|
116
|
+
includeNull = true;
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
(c.not ? excludes : includes).push(c);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if ((includeEmpty && excludeEmpty) || (includeNull && excludeNull)) {
|
|
124
|
+
return 'false';
|
|
125
|
+
}
|
|
126
|
+
let includeSQL = '';
|
|
127
|
+
if (includes.length > 0 || includeNull || includeEmpty) {
|
|
128
|
+
excludeEmpty = false;
|
|
129
|
+
excludeNull = false;
|
|
130
|
+
const includeExprs = includes.map(inc => exports.FilterCompilers.stringCompile(inc, x, d));
|
|
131
|
+
if (includeEmpty) {
|
|
132
|
+
includeExprs.push(`${x} = ''`);
|
|
133
|
+
}
|
|
134
|
+
if (includeNull) {
|
|
135
|
+
includeExprs.push(`${x} IS NULL`);
|
|
136
|
+
}
|
|
137
|
+
includeSQL = includeExprs.join(' or ');
|
|
138
|
+
}
|
|
139
|
+
let excludeSQL = '';
|
|
140
|
+
if (excludes.length > 0 || excludeEmpty || excludeNull) {
|
|
141
|
+
const excludeExprs = excludes.map(inc => exports.FilterCompilers.stringCompile(inc, x, d));
|
|
142
|
+
if (excludeEmpty) {
|
|
143
|
+
excludeExprs.push(`${x} != ''`);
|
|
144
|
+
}
|
|
145
|
+
if (excludeNull) {
|
|
146
|
+
excludeExprs.push(`${x} IS NOT NULL`);
|
|
147
|
+
}
|
|
148
|
+
excludeSQL = excludeExprs.join(' and ');
|
|
149
|
+
}
|
|
150
|
+
if (includeSQL) {
|
|
151
|
+
return excludeSQL !== ''
|
|
152
|
+
? includeSQL + ' and ' + excludeSQL
|
|
153
|
+
: includeSQL;
|
|
154
|
+
}
|
|
155
|
+
return excludeSQL !== '' ? excludeSQL : 'true';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=filter_compilers.js.map
|
|
@@ -30,6 +30,7 @@ const malloy_types_1 = require("./malloy_types");
|
|
|
30
30
|
const utils_1 = require("./utils");
|
|
31
31
|
const utils_2 = require("./materialization/utils");
|
|
32
32
|
const annotation_1 = require("../annotation");
|
|
33
|
+
const filter_compilers_1 = require("./filter_compilers");
|
|
33
34
|
function pathToCol(path) {
|
|
34
35
|
return path.map(el => encodeURIComponent(el)).join('/');
|
|
35
36
|
}
|
|
@@ -722,6 +723,7 @@ class QueryField extends QueryNode {
|
|
|
722
723
|
return caseStmt.join(' ');
|
|
723
724
|
}
|
|
724
725
|
exprToSQL(resultSet, context, exprToTranslate, state = new GenerateState()) {
|
|
726
|
+
var _a;
|
|
725
727
|
// Wrap non leaf sub expressions in parenthesis
|
|
726
728
|
const subExpr = function (qf, e) {
|
|
727
729
|
const sql = qf.exprToSQL(resultSet, context, e, state);
|
|
@@ -841,11 +843,12 @@ class QueryField extends QueryNode {
|
|
|
841
843
|
return `${expr.kids.e.sql} ${expr.not ? 'NOT IN' : 'IN'} (${oneOf})`;
|
|
842
844
|
}
|
|
843
845
|
case 'like':
|
|
844
|
-
return this.parent.dialect.likeExpr(expr);
|
|
845
|
-
// Malloy inequality comparisons always return a boolean
|
|
846
846
|
case '!like': {
|
|
847
|
-
const
|
|
848
|
-
|
|
847
|
+
const likeIt = expr.node === 'like' ? 'LIKE' : 'NOT LIKE';
|
|
848
|
+
const compare = expr.kids.right.node === 'stringLiteral'
|
|
849
|
+
? this.parent.dialect.sqlLike(likeIt, (_a = expr.kids.left.sql) !== null && _a !== void 0 ? _a : '', expr.kids.right.literal)
|
|
850
|
+
: `${expr.kids.left.sql} ${likeIt} ${expr.kids.right.sql}`;
|
|
851
|
+
return expr.node === 'like' ? compare : `COALESCE(${compare},true)`;
|
|
849
852
|
}
|
|
850
853
|
case '()':
|
|
851
854
|
return `(${expr.e.sql})`;
|
|
@@ -879,6 +882,11 @@ class QueryField extends QueryNode {
|
|
|
879
882
|
return '';
|
|
880
883
|
case 'compositeField':
|
|
881
884
|
return '{COMPOSITE_FIELD}';
|
|
885
|
+
case 'filterMatch':
|
|
886
|
+
if (expr.dataType === 'string') {
|
|
887
|
+
return filter_compilers_1.FilterCompilers.stringCompile(expr.filter, expr.e.sql || '', this.parent.dialect);
|
|
888
|
+
}
|
|
889
|
+
throw new Error(`Internal Error: FCU ${expr.dataType}`);
|
|
882
890
|
default:
|
|
883
891
|
throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
|
|
884
892
|
}
|