@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.
@@ -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
- this.contextError(pcx, 'literal-string-newline', 'String cannot contain a new-line character');
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
@@ -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 model_1 = require("../../model");
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 = eToStr(bx.generated().value, undefined);
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,5 @@
1
+ import { StringClause } from '@malloydata/malloy-filter';
2
+ import { Dialect } from '../dialect';
3
+ export declare const FilterCompilers: {
4
+ stringCompile(sc: StringClause, x: string, d: Dialect): string;
5
+ };
@@ -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 notLike = this.parent.dialect.likeExpr(expr);
848
- return `COALESCE(${notLike},true)`;
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
  }