@malloydata/malloy 0.0.225-dev250113200903 → 0.0.225-dev250113203344
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 +1 -1
- package/dist/dialect/dialect.js +2 -1
- package/dist/dialect/mysql/mysql.d.ts +1 -1
- package/dist/dialect/mysql/mysql.js +2 -2
- package/dist/lang/ast/expressions/expr-not.js +6 -0
- package/dist/lang/ast/expressions/expr-null.d.ts +19 -1
- package/dist/lang/ast/expressions/expr-null.js +49 -1
- package/dist/lang/ast/types/expression-def.js +11 -35
- package/dist/lang/lib/Malloy/MalloyParser.d.ts +53 -29
- package/dist/lang/lib/Malloy/MalloyParser.js +1881 -1728
- package/dist/lang/lib/Malloy/MalloyParserListener.d.ts +27 -5
- package/dist/lang/lib/Malloy/MalloyParserVisitor.d.ts +17 -3
- package/dist/lang/malloy-to-ast.d.ts +3 -1
- package/dist/lang/malloy-to-ast.js +33 -21
- package/dist/lang/test/expressions.spec.js +21 -23
- package/dist/lang/test/parameters.spec.js +1 -1
- package/package.json +1 -1
|
@@ -40,7 +40,7 @@ import { ExprAndTreeContext } from "./MalloyParser";
|
|
|
40
40
|
import { ExprOrTreeContext } from "./MalloyParser";
|
|
41
41
|
import { ExprCompareContext } from "./MalloyParser";
|
|
42
42
|
import { ExprWarnLikeContext } from "./MalloyParser";
|
|
43
|
-
import {
|
|
43
|
+
import { ExprNullCheckContext } from "./MalloyParser";
|
|
44
44
|
import { ExprWarnInContext } from "./MalloyParser";
|
|
45
45
|
import { ExprApplyContext } from "./MalloyParser";
|
|
46
46
|
import { ExprNotContext } from "./MalloyParser";
|
|
@@ -209,6 +209,8 @@ import { TimeframeContext } from "./MalloyParser";
|
|
|
209
209
|
import { UngroupContext } from "./MalloyParser";
|
|
210
210
|
import { MalloyOrSQLTypeContext } from "./MalloyParser";
|
|
211
211
|
import { FieldExprContext } from "./MalloyParser";
|
|
212
|
+
import { PartialCompareContext } from "./MalloyParser";
|
|
213
|
+
import { PartialTestContext } from "./MalloyParser";
|
|
212
214
|
import { PartialAllowedFieldExprContext } from "./MalloyParser";
|
|
213
215
|
import { FieldExprListContext } from "./MalloyParser";
|
|
214
216
|
import { PickStatementContext } from "./MalloyParser";
|
|
@@ -733,17 +735,17 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
733
735
|
*/
|
|
734
736
|
exitExprWarnLike?: (ctx: ExprWarnLikeContext) => void;
|
|
735
737
|
/**
|
|
736
|
-
* Enter a parse tree produced by the `
|
|
738
|
+
* Enter a parse tree produced by the `exprNullCheck`
|
|
737
739
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
738
740
|
* @param ctx the parse tree
|
|
739
741
|
*/
|
|
740
|
-
|
|
742
|
+
enterExprNullCheck?: (ctx: ExprNullCheckContext) => void;
|
|
741
743
|
/**
|
|
742
|
-
* Exit a parse tree produced by the `
|
|
744
|
+
* Exit a parse tree produced by the `exprNullCheck`
|
|
743
745
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
744
746
|
* @param ctx the parse tree
|
|
745
747
|
*/
|
|
746
|
-
|
|
748
|
+
exitExprNullCheck?: (ctx: ExprNullCheckContext) => void;
|
|
747
749
|
/**
|
|
748
750
|
* Enter a parse tree produced by the `exprWarnIn`
|
|
749
751
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
@@ -2512,6 +2514,26 @@ export interface MalloyParserListener extends ParseTreeListener {
|
|
|
2512
2514
|
* @param ctx the parse tree
|
|
2513
2515
|
*/
|
|
2514
2516
|
exitFieldExpr?: (ctx: FieldExprContext) => void;
|
|
2517
|
+
/**
|
|
2518
|
+
* Enter a parse tree produced by `MalloyParser.partialCompare`.
|
|
2519
|
+
* @param ctx the parse tree
|
|
2520
|
+
*/
|
|
2521
|
+
enterPartialCompare?: (ctx: PartialCompareContext) => void;
|
|
2522
|
+
/**
|
|
2523
|
+
* Exit a parse tree produced by `MalloyParser.partialCompare`.
|
|
2524
|
+
* @param ctx the parse tree
|
|
2525
|
+
*/
|
|
2526
|
+
exitPartialCompare?: (ctx: PartialCompareContext) => void;
|
|
2527
|
+
/**
|
|
2528
|
+
* Enter a parse tree produced by `MalloyParser.partialTest`.
|
|
2529
|
+
* @param ctx the parse tree
|
|
2530
|
+
*/
|
|
2531
|
+
enterPartialTest?: (ctx: PartialTestContext) => void;
|
|
2532
|
+
/**
|
|
2533
|
+
* Exit a parse tree produced by `MalloyParser.partialTest`.
|
|
2534
|
+
* @param ctx the parse tree
|
|
2535
|
+
*/
|
|
2536
|
+
exitPartialTest?: (ctx: PartialTestContext) => void;
|
|
2515
2537
|
/**
|
|
2516
2538
|
* Enter a parse tree produced by `MalloyParser.partialAllowedFieldExpr`.
|
|
2517
2539
|
* @param ctx the parse tree
|
|
@@ -40,7 +40,7 @@ import { ExprAndTreeContext } from "./MalloyParser";
|
|
|
40
40
|
import { ExprOrTreeContext } from "./MalloyParser";
|
|
41
41
|
import { ExprCompareContext } from "./MalloyParser";
|
|
42
42
|
import { ExprWarnLikeContext } from "./MalloyParser";
|
|
43
|
-
import {
|
|
43
|
+
import { ExprNullCheckContext } from "./MalloyParser";
|
|
44
44
|
import { ExprWarnInContext } from "./MalloyParser";
|
|
45
45
|
import { ExprApplyContext } from "./MalloyParser";
|
|
46
46
|
import { ExprNotContext } from "./MalloyParser";
|
|
@@ -209,6 +209,8 @@ import { TimeframeContext } from "./MalloyParser";
|
|
|
209
209
|
import { UngroupContext } from "./MalloyParser";
|
|
210
210
|
import { MalloyOrSQLTypeContext } from "./MalloyParser";
|
|
211
211
|
import { FieldExprContext } from "./MalloyParser";
|
|
212
|
+
import { PartialCompareContext } from "./MalloyParser";
|
|
213
|
+
import { PartialTestContext } from "./MalloyParser";
|
|
212
214
|
import { PartialAllowedFieldExprContext } from "./MalloyParser";
|
|
213
215
|
import { FieldExprListContext } from "./MalloyParser";
|
|
214
216
|
import { PickStatementContext } from "./MalloyParser";
|
|
@@ -531,12 +533,12 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
531
533
|
*/
|
|
532
534
|
visitExprWarnLike?: (ctx: ExprWarnLikeContext) => Result;
|
|
533
535
|
/**
|
|
534
|
-
* Visit a parse tree produced by the `
|
|
536
|
+
* Visit a parse tree produced by the `exprNullCheck`
|
|
535
537
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
536
538
|
* @param ctx the parse tree
|
|
537
539
|
* @return the visitor result
|
|
538
540
|
*/
|
|
539
|
-
|
|
541
|
+
visitExprNullCheck?: (ctx: ExprNullCheckContext) => Result;
|
|
540
542
|
/**
|
|
541
543
|
* Visit a parse tree produced by the `exprWarnIn`
|
|
542
544
|
* labeled alternative in `MalloyParser.fieldExpr`.
|
|
@@ -1589,6 +1591,18 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
|
|
|
1589
1591
|
* @return the visitor result
|
|
1590
1592
|
*/
|
|
1591
1593
|
visitFieldExpr?: (ctx: FieldExprContext) => Result;
|
|
1594
|
+
/**
|
|
1595
|
+
* Visit a parse tree produced by `MalloyParser.partialCompare`.
|
|
1596
|
+
* @param ctx the parse tree
|
|
1597
|
+
* @return the visitor result
|
|
1598
|
+
*/
|
|
1599
|
+
visitPartialCompare?: (ctx: PartialCompareContext) => Result;
|
|
1600
|
+
/**
|
|
1601
|
+
* Visit a parse tree produced by `MalloyParser.partialTest`.
|
|
1602
|
+
* @param ctx the parse tree
|
|
1603
|
+
* @return the visitor result
|
|
1604
|
+
*/
|
|
1605
|
+
visitPartialTest?: (ctx: PartialTestContext) => Result;
|
|
1592
1606
|
/**
|
|
1593
1607
|
* Visit a parse tree produced by `MalloyParser.partialAllowedFieldExpr`.
|
|
1594
1608
|
* @param ctx the parse tree
|
|
@@ -158,6 +158,8 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
158
158
|
visitExprOrTree(pcx: parse.ExprOrTreeContext): ast.ExprAlternationTree;
|
|
159
159
|
visitExprAndTree(pcx: parse.ExprAndTreeContext): ast.ExprAlternationTree;
|
|
160
160
|
visitExprCoalesce(pcx: parse.ExprCoalesceContext): ast.ExprCoalesce;
|
|
161
|
+
visitPartialCompare(pcx: parse.PartialCompareContext): ast.PartialCompare;
|
|
162
|
+
visitPartialTest(pcx: parse.PartialTestContext): ast.ExpressionDef;
|
|
161
163
|
visitPartialAllowedFieldExpr(pcx: parse.PartialAllowedFieldExprContext): ast.ExpressionDef;
|
|
162
164
|
visitExprString(pcx: parse.ExprStringContext): ast.ExprString;
|
|
163
165
|
visitExprRegex(pcx: parse.ExprRegexContext): ast.ExprRegEx;
|
|
@@ -236,7 +238,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
|
|
|
236
238
|
visitExprLiteralRecord(pcx: parse.ExprLiteralRecordContext): ast.RecordLiteral;
|
|
237
239
|
visitExprArrayLiteral(pcx: parse.ExprArrayLiteralContext): ast.ArrayLiteral;
|
|
238
240
|
visitExprWarnLike(pcx: parse.ExprWarnLikeContext): ast.ExprCompare;
|
|
239
|
-
|
|
241
|
+
visitExprNullCheck(pcx: parse.ExprNullCheckContext): ast.ExprIsNull;
|
|
240
242
|
visitExprWarnIn(pcx: parse.ExprWarnInContext): ast.ExprLegacyIn;
|
|
241
243
|
}
|
|
242
244
|
export {};
|
|
@@ -881,17 +881,30 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
881
881
|
const right = this.getFieldExpr(pcx.fieldExpr()[1]);
|
|
882
882
|
return this.astAt(new ast.ExprCoalesce(left, right), pcx);
|
|
883
883
|
}
|
|
884
|
+
visitPartialCompare(pcx) {
|
|
885
|
+
const partialOp = pcx.compareOp().text;
|
|
886
|
+
if (ast.isComparison(partialOp)) {
|
|
887
|
+
return this.astAt(new ast.PartialCompare(partialOp, this.getFieldExpr(pcx.fieldExpr())), pcx);
|
|
888
|
+
}
|
|
889
|
+
throw this.internalError(pcx, `partial comparison '${partialOp}' not recognized`);
|
|
890
|
+
}
|
|
891
|
+
visitPartialTest(pcx) {
|
|
892
|
+
const cmp = pcx.partialCompare();
|
|
893
|
+
if (cmp) {
|
|
894
|
+
return this.visitPartialCompare(cmp);
|
|
895
|
+
}
|
|
896
|
+
return this.astAt(new ast.PartialIsNull(pcx.NOT() ? '!=' : '='), pcx);
|
|
897
|
+
}
|
|
884
898
|
visitPartialAllowedFieldExpr(pcx) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
if (partialOp) {
|
|
889
|
-
if (ast.isComparison(partialOp)) {
|
|
890
|
-
return this.astAt(new ast.PartialCompare(partialOp, fieldExpr), pcx);
|
|
891
|
-
}
|
|
892
|
-
throw this.internalError(pcx, `partial comparison '${partialOp}' not recognized`);
|
|
899
|
+
const exprCx = pcx.fieldExpr();
|
|
900
|
+
if (exprCx) {
|
|
901
|
+
return this.getFieldExpr(exprCx);
|
|
893
902
|
}
|
|
894
|
-
|
|
903
|
+
const partialCx = pcx.partialTest();
|
|
904
|
+
if (partialCx) {
|
|
905
|
+
return this.visitPartialTest(partialCx);
|
|
906
|
+
}
|
|
907
|
+
throw this.internalError(pcx, 'impossible partial');
|
|
895
908
|
}
|
|
896
909
|
visitExprString(pcx) {
|
|
897
910
|
const str = this.getPlainStringFrom(pcx);
|
|
@@ -935,6 +948,15 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
935
948
|
const left = this.getFieldExpr(pcx.fieldExpr(0));
|
|
936
949
|
const right = this.getFieldExpr(pcx.fieldExpr(1));
|
|
937
950
|
if (ast.isEquality(op)) {
|
|
951
|
+
const wholeRange = this.parseInfo.rangeFromContext(pcx);
|
|
952
|
+
if (right instanceof ast.ExprNULL) {
|
|
953
|
+
if (op === '=') {
|
|
954
|
+
this.warnWithReplacement('sql-is-null', "Use 'is null' to check for NULL instead of '= null'", wholeRange, `${this.getSourceCode(pcx.fieldExpr(0))} is null`);
|
|
955
|
+
}
|
|
956
|
+
else if (op === '!=') {
|
|
957
|
+
this.warnWithReplacement('sql-is-not-null', "Use 'is not null' to check for NULL instead of '!= null'", wholeRange, `${this.getSourceCode(pcx.fieldExpr(0))} is not null`);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
938
960
|
return this.astAt(new ast.ExprEquality(left, op, right), pcx);
|
|
939
961
|
}
|
|
940
962
|
else if (ast.isComparison(op)) {
|
|
@@ -1465,19 +1487,9 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
|
|
|
1465
1487
|
}
|
|
1466
1488
|
return this.astAt(new ast.ExprCompare(this.getFieldExpr(left), op, this.getFieldExpr(right)), pcx);
|
|
1467
1489
|
}
|
|
1468
|
-
|
|
1469
|
-
let op = '=';
|
|
1490
|
+
visitExprNullCheck(pcx) {
|
|
1470
1491
|
const expr = pcx.fieldExpr();
|
|
1471
|
-
|
|
1472
|
-
if (pcx.NOT()) {
|
|
1473
|
-
op = '!=';
|
|
1474
|
-
this.warnWithReplacement('sql-is-not-null', "Use '!= NULL' to check for NULL instead of 'IS NOT NULL'", wholeRange, `${this.getSourceCode(expr)} != null`);
|
|
1475
|
-
}
|
|
1476
|
-
else {
|
|
1477
|
-
this.warnWithReplacement('sql-is-null', "Use '= NULL' to check for NULL instead of 'IS NULL'", wholeRange, `${this.getSourceCode(expr)} = null`);
|
|
1478
|
-
}
|
|
1479
|
-
const nullExpr = new ast.ExprNULL();
|
|
1480
|
-
return this.astAt(new ast.ExprCompare(this.getFieldExpr(expr), op, nullExpr), pcx);
|
|
1492
|
+
return this.astAt(new ast.ExprIsNull(this.getFieldExpr(expr), pcx.NOT() ? '!=' : '='), pcx);
|
|
1481
1493
|
}
|
|
1482
1494
|
visitExprWarnIn(pcx) {
|
|
1483
1495
|
const expr = this.getFieldExpr(pcx.fieldExpr());
|
|
@@ -136,6 +136,18 @@ describe('expressions', () => {
|
|
|
136
136
|
test('null-check (??)', () => {
|
|
137
137
|
expect('ai ?? 7').compilesTo('{ai coalesce 7}');
|
|
138
138
|
});
|
|
139
|
+
test('normal is-null', () => {
|
|
140
|
+
expect('ai is null').compilesTo('{is-null ai}');
|
|
141
|
+
});
|
|
142
|
+
test('normal is-not-null', () => {
|
|
143
|
+
expect('ai is not null').compilesTo('{is-not-null ai}');
|
|
144
|
+
});
|
|
145
|
+
test('apply is-null', () => {
|
|
146
|
+
expect('ai ? is null').compilesTo('{is-null ai}');
|
|
147
|
+
});
|
|
148
|
+
test('apply is-not-null', () => {
|
|
149
|
+
expect('ai ? is not null').compilesTo('{is-not-null ai}');
|
|
150
|
+
});
|
|
139
151
|
test('coalesce type mismatch', () => {
|
|
140
152
|
expect(new test_translator_1.BetaExpression('ai ?? @2003')).toLog((0, test_translator_1.errorMessage)('Mismatched types for coalesce (number, date)'));
|
|
141
153
|
});
|
|
@@ -177,19 +189,19 @@ describe('expressions', () => {
|
|
|
177
189
|
expect((0, test_translator_1.expr) `ai ? (> 1 & < 100)`).toTranslate();
|
|
178
190
|
});
|
|
179
191
|
describe('sql friendly warnings', () => {
|
|
180
|
-
test('
|
|
181
|
-
const warnSrc = (0, test_translator_1.expr)
|
|
182
|
-
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use '
|
|
192
|
+
test('= null with warning', () => {
|
|
193
|
+
const warnSrc = (0, test_translator_1.expr) `${'ai = null'}`;
|
|
194
|
+
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use 'is null' to check for NULL instead of '= null'"));
|
|
183
195
|
expect(warnSrc).compilesTo('{is-null ai}');
|
|
184
196
|
const warning = warnSrc.translator.problems()[0];
|
|
185
|
-
expect(warning.replacement).toEqual('ai
|
|
197
|
+
expect(warning.replacement).toEqual('ai is null');
|
|
186
198
|
});
|
|
187
199
|
test('is not null with warning', () => {
|
|
188
|
-
const warnSrc = (0, test_translator_1.expr)
|
|
189
|
-
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use '
|
|
200
|
+
const warnSrc = (0, test_translator_1.expr) `${'ai != null'}`;
|
|
201
|
+
expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use 'is not null' to check for NULL instead of '!= null'"));
|
|
190
202
|
expect(warnSrc).compilesTo('{is-not-null ai}');
|
|
191
203
|
const warning = warnSrc.translator.problems()[0];
|
|
192
|
-
expect(warning.replacement).toEqual('ai
|
|
204
|
+
expect(warning.replacement).toEqual('ai is not null');
|
|
193
205
|
});
|
|
194
206
|
test('like with warning', () => {
|
|
195
207
|
const warnSrc = (0, test_translator_1.expr) `astr like 'a'`;
|
|
@@ -205,20 +217,6 @@ describe('expressions', () => {
|
|
|
205
217
|
const warning = warnSrc.translator.problems()[0];
|
|
206
218
|
expect(warning.replacement).toEqual("astr !~ 'a'");
|
|
207
219
|
});
|
|
208
|
-
test('is is-null in a model', () => {
|
|
209
|
-
const isNullSrc = (0, test_translator_1.model) `source: xa is a extend { dimension: x1 is astr is null }`;
|
|
210
|
-
expect(isNullSrc).toLog((0, test_translator_1.warningMessage)("Use '= NULL' to check for NULL instead of 'IS NULL'"));
|
|
211
|
-
});
|
|
212
|
-
test('is not-null in a model', () => {
|
|
213
|
-
const isNullSrc = (0, test_translator_1.model) `source: xa is a extend { dimension: x1 is not null }`;
|
|
214
|
-
expect(isNullSrc).toTranslate();
|
|
215
|
-
});
|
|
216
|
-
test('is not-null is in a model', () => {
|
|
217
|
-
const isNullSrc = (0, test_translator_1.model) `source: xa is a extend { dimension: x1 is not null is null }`;
|
|
218
|
-
expect(isNullSrc).toLog((0, test_translator_1.warningMessage)("Use '= NULL' to check for NULL instead of 'IS NULL'"));
|
|
219
|
-
const warning = isNullSrc.translator.problems()[0];
|
|
220
|
-
expect(warning.replacement).toEqual('null = null');
|
|
221
|
-
});
|
|
222
220
|
test('x is expr y is not null', () => {
|
|
223
221
|
const isNullSrc = (0, test_translator_1.model) `source: xa is a extend { dimension: x is 1 y is not null }`;
|
|
224
222
|
expect(isNullSrc).toTranslate();
|
|
@@ -1042,7 +1040,7 @@ describe('expressions', () => {
|
|
|
1042
1040
|
['astr', 'string'],
|
|
1043
1041
|
['abool', 'boolean'],
|
|
1044
1042
|
])('Can compare field %s (type %s) to NULL', (name, _datatype) => {
|
|
1045
|
-
expect((0, test_translator_1.expr) `${name}
|
|
1043
|
+
expect((0, test_translator_1.expr) `${name} IS NULL`).toTranslate();
|
|
1046
1044
|
});
|
|
1047
1045
|
});
|
|
1048
1046
|
describe('alternations as in', () => {
|
|
@@ -1098,7 +1096,7 @@ describe('sql native fields in schema', () => {
|
|
|
1098
1096
|
expect(uModel).toTranslate();
|
|
1099
1097
|
});
|
|
1100
1098
|
test('sql native reference can be compared to NULL', () => {
|
|
1101
|
-
const uModel = new test_translator_1.TestTranslator('run: a->{ where: aun
|
|
1099
|
+
const uModel = new test_translator_1.TestTranslator('run: a->{ where: aun is not null; select: * }');
|
|
1102
1100
|
expect(uModel).toTranslate();
|
|
1103
1101
|
});
|
|
1104
1102
|
test('flag unsupported equality', () => {
|
|
@@ -61,7 +61,7 @@ describe('parameters', () => {
|
|
|
61
61
|
expect(`
|
|
62
62
|
##! experimental.parameters
|
|
63
63
|
source: ab_new(param is null::string) is ab extend {
|
|
64
|
-
where: param
|
|
64
|
+
where: param is null
|
|
65
65
|
}
|
|
66
66
|
run: ab_new(param is "foo") -> { select: * } -> { select: * }
|
|
67
67
|
`).toTranslate();
|