@malloydata/malloy 0.0.197 → 0.0.198-dev241010181909

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.
@@ -50,6 +50,7 @@ import { ExprExprContext } from "./MalloyParser";
50
50
  import { ExprAggFuncContext } from "./MalloyParser";
51
51
  import { ExprFuncContext } from "./MalloyParser";
52
52
  import { ExprPickContext } from "./MalloyParser";
53
+ import { ExprCaseContext } from "./MalloyParser";
53
54
  import { ExprUngroupContext } from "./MalloyParser";
54
55
  import { SegFieldContext } from "./MalloyParser";
55
56
  import { SegOpsContext } from "./MalloyParser";
@@ -197,6 +198,8 @@ import { FieldExprContext } from "./MalloyParser";
197
198
  import { PartialAllowedFieldExprContext } from "./MalloyParser";
198
199
  import { PickStatementContext } from "./MalloyParser";
199
200
  import { PickContext } from "./MalloyParser";
201
+ import { CaseStatementContext } from "./MalloyParser";
202
+ import { CaseWhenContext } from "./MalloyParser";
200
203
  import { RecordKeyContext } from "./MalloyParser";
201
204
  import { RecordElementContext } from "./MalloyParser";
202
205
  import { ArgumentListContext } from "./MalloyParser";
@@ -832,6 +835,18 @@ export interface MalloyParserListener extends ParseTreeListener {
832
835
  * @param ctx the parse tree
833
836
  */
834
837
  exitExprPick?: (ctx: ExprPickContext) => void;
838
+ /**
839
+ * Enter a parse tree produced by the `exprCase`
840
+ * labeled alternative in `MalloyParser.fieldExpr`.
841
+ * @param ctx the parse tree
842
+ */
843
+ enterExprCase?: (ctx: ExprCaseContext) => void;
844
+ /**
845
+ * Exit a parse tree produced by the `exprCase`
846
+ * labeled alternative in `MalloyParser.fieldExpr`.
847
+ * @param ctx the parse tree
848
+ */
849
+ exitExprCase?: (ctx: ExprCaseContext) => void;
835
850
  /**
836
851
  * Enter a parse tree produced by the `exprUngroup`
837
852
  * labeled alternative in `MalloyParser.fieldExpr`.
@@ -2360,6 +2375,26 @@ export interface MalloyParserListener extends ParseTreeListener {
2360
2375
  * @param ctx the parse tree
2361
2376
  */
2362
2377
  exitPick?: (ctx: PickContext) => void;
2378
+ /**
2379
+ * Enter a parse tree produced by `MalloyParser.caseStatement`.
2380
+ * @param ctx the parse tree
2381
+ */
2382
+ enterCaseStatement?: (ctx: CaseStatementContext) => void;
2383
+ /**
2384
+ * Exit a parse tree produced by `MalloyParser.caseStatement`.
2385
+ * @param ctx the parse tree
2386
+ */
2387
+ exitCaseStatement?: (ctx: CaseStatementContext) => void;
2388
+ /**
2389
+ * Enter a parse tree produced by `MalloyParser.caseWhen`.
2390
+ * @param ctx the parse tree
2391
+ */
2392
+ enterCaseWhen?: (ctx: CaseWhenContext) => void;
2393
+ /**
2394
+ * Exit a parse tree produced by `MalloyParser.caseWhen`.
2395
+ * @param ctx the parse tree
2396
+ */
2397
+ exitCaseWhen?: (ctx: CaseWhenContext) => void;
2363
2398
  /**
2364
2399
  * Enter a parse tree produced by `MalloyParser.recordKey`.
2365
2400
  * @param ctx the parse tree
@@ -50,6 +50,7 @@ import { ExprExprContext } from "./MalloyParser";
50
50
  import { ExprAggFuncContext } from "./MalloyParser";
51
51
  import { ExprFuncContext } from "./MalloyParser";
52
52
  import { ExprPickContext } from "./MalloyParser";
53
+ import { ExprCaseContext } from "./MalloyParser";
53
54
  import { ExprUngroupContext } from "./MalloyParser";
54
55
  import { SegFieldContext } from "./MalloyParser";
55
56
  import { SegOpsContext } from "./MalloyParser";
@@ -197,6 +198,8 @@ import { FieldExprContext } from "./MalloyParser";
197
198
  import { PartialAllowedFieldExprContext } from "./MalloyParser";
198
199
  import { PickStatementContext } from "./MalloyParser";
199
200
  import { PickContext } from "./MalloyParser";
201
+ import { CaseStatementContext } from "./MalloyParser";
202
+ import { CaseWhenContext } from "./MalloyParser";
200
203
  import { RecordKeyContext } from "./MalloyParser";
201
204
  import { RecordElementContext } from "./MalloyParser";
202
205
  import { ArgumentListContext } from "./MalloyParser";
@@ -580,6 +583,13 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
580
583
  * @return the visitor result
581
584
  */
582
585
  visitExprPick?: (ctx: ExprPickContext) => Result;
586
+ /**
587
+ * Visit a parse tree produced by the `exprCase`
588
+ * labeled alternative in `MalloyParser.fieldExpr`.
589
+ * @param ctx the parse tree
590
+ * @return the visitor result
591
+ */
592
+ visitExprCase?: (ctx: ExprCaseContext) => Result;
583
593
  /**
584
594
  * Visit a parse tree produced by the `exprUngroup`
585
595
  * labeled alternative in `MalloyParser.fieldExpr`.
@@ -1491,6 +1501,18 @@ export interface MalloyParserVisitor<Result> extends ParseTreeVisitor<Result> {
1491
1501
  * @return the visitor result
1492
1502
  */
1493
1503
  visitPick?: (ctx: PickContext) => Result;
1504
+ /**
1505
+ * Visit a parse tree produced by `MalloyParser.caseStatement`.
1506
+ * @param ctx the parse tree
1507
+ * @return the visitor result
1508
+ */
1509
+ visitCaseStatement?: (ctx: CaseStatementContext) => Result;
1510
+ /**
1511
+ * Visit a parse tree produced by `MalloyParser.caseWhen`.
1512
+ * @param ctx the parse tree
1513
+ * @return the visitor result
1514
+ */
1515
+ visitCaseWhen?: (ctx: CaseWhenContext) => Result;
1494
1516
  /**
1495
1517
  * Visit a parse tree produced by `MalloyParser.recordKey`.
1496
1518
  * @param ctx the parse tree
@@ -45,6 +45,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
45
45
  */
46
46
  protected astError<T extends MessageCode>(el: ast.MalloyElement, code: T, data: MessageParameterType<T>, options?: LogMessageOptions): void;
47
47
  protected getLocation(cx: ParserRuleContext): DocumentLocation;
48
+ protected getSourceString(cx: ParserRuleContext): string;
48
49
  /**
49
50
  * Log an error message relative to a parse node
50
51
  */
@@ -187,6 +188,7 @@ export declare class MalloyToAST extends AbstractParseTreeVisitor<ast.MalloyElem
187
188
  visitExprAggFunc(pcx: parse.ExprAggFuncContext): ast.ExpressionDef;
188
189
  visitExprFunc(pcx: parse.ExprFuncContext): ast.ExpressionDef;
189
190
  visitExprDuration(pcx: parse.ExprDurationContext): ast.ExprDuration;
191
+ visitCaseStatement(pcx: parse.CaseStatementContext): ast.Case;
190
192
  visitPickStatement(pcx: parse.PickStatementContext): ast.Pick;
191
193
  visitExprFieldProps(pcx: parse.ExprFieldPropsContext): ast.ExprProps;
192
194
  visitPartitionByStatement(pcx: parse.PartitionByStatementContext): ast.PartitionBy;
@@ -110,6 +110,9 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
110
110
  range: this.parseInfo.rangeFromContext(cx),
111
111
  };
112
112
  }
113
+ getSourceString(cx) {
114
+ return this.parseInfo.sourceStream.getText(new Interval_1.Interval(cx.start.startIndex, cx.stop ? cx.stop.stopIndex : cx.start.startIndex));
115
+ }
113
116
  /**
114
117
  * Log an error message relative to a parse node
115
118
  */
@@ -1116,6 +1119,22 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
1116
1119
  visitExprDuration(pcx) {
1117
1120
  return new ast.ExprDuration(this.getFieldExpr(pcx.fieldExpr()), this.visitTimeframe(pcx.timeframe()).text);
1118
1121
  }
1122
+ visitCaseStatement(pcx) {
1123
+ const valueCx = pcx._valueExpr;
1124
+ const value = valueCx ? this.getFieldExpr(valueCx) : undefined;
1125
+ const whenCxs = pcx.caseWhen();
1126
+ const whens = whenCxs.map(whenCx => {
1127
+ return new ast.CaseWhen(this.getFieldExpr(whenCx._condition), this.getFieldExpr(whenCx._result));
1128
+ });
1129
+ const elseCx = pcx._caseElse;
1130
+ const theElse = elseCx ? this.getFieldExpr(elseCx) : undefined;
1131
+ this.warnWithReplacement('sql-case', 'Use a `pick` statement instead of `case`', this.parseInfo.rangeFromContext(pcx), `${[
1132
+ ...(valueCx ? [`${this.getSourceCode(valueCx)} ?`] : []),
1133
+ ...whenCxs.map(whenCx => `pick ${this.getSourceCode(whenCx._result)} when ${this.getSourceCode(whenCx._condition)}`),
1134
+ elseCx ? `else ${elseCx.text}` : 'else null',
1135
+ ].join(' ')}`);
1136
+ return new ast.Case(value, whens, theElse);
1137
+ }
1119
1138
  visitPickStatement(pcx) {
1120
1139
  const picks = pcx.pick().map(pwCx => {
1121
1140
  let pickExpr;
@@ -1347,10 +1366,10 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
1347
1366
  const wholeRange = this.parseInfo.rangeFromContext(pcx);
1348
1367
  if (pcx.NOT()) {
1349
1368
  op = '!~';
1350
- this.warnWithReplacement('sql-not-like', "Use Malloy operator '!~' instead of 'NOT LIKE'", wholeRange, `${left.text} !~ ${right.text}`);
1369
+ this.warnWithReplacement('sql-not-like', "Use Malloy operator '!~' instead of 'NOT LIKE'", wholeRange, `${this.getSourceCode(left)} !~ ${this.getSourceCode(right)}`);
1351
1370
  }
1352
1371
  else {
1353
- this.warnWithReplacement('sql-like', "Use Malloy operator '~' instead of 'LIKE'", wholeRange, `${left.text} ~ ${right.text}`);
1372
+ this.warnWithReplacement('sql-like', "Use Malloy operator '~' instead of 'LIKE'", wholeRange, `${this.getSourceCode(left)} ~ ${this.getSourceCode(right)}`);
1354
1373
  }
1355
1374
  return this.astAt(new ast.ExprCompare(this.getFieldExpr(left), op, this.getFieldExpr(right)), pcx);
1356
1375
  }
@@ -1360,10 +1379,10 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
1360
1379
  const wholeRange = this.parseInfo.rangeFromContext(pcx);
1361
1380
  if (pcx.NOT()) {
1362
1381
  op = '!=';
1363
- this.warnWithReplacement('sql-is-not-null', "Use '!= NULL' to check for NULL instead of 'IS NOT NULL'", wholeRange, `${expr.text} != null`);
1382
+ this.warnWithReplacement('sql-is-not-null', "Use '!= NULL' to check for NULL instead of 'IS NOT NULL'", wholeRange, `${this.getSourceCode(expr)} != null`);
1364
1383
  }
1365
1384
  else {
1366
- this.warnWithReplacement('sql-is-null', "Use '= NULL' to check for NULL instead of 'IS NULL'", wholeRange, `${expr.text} = null`);
1385
+ this.warnWithReplacement('sql-is-null', "Use '= NULL' to check for NULL instead of 'IS NULL'", wholeRange, `${this.getSourceCode(expr)} = null`);
1367
1386
  }
1368
1387
  const nullExpr = new ast.ExprNULL();
1369
1388
  return this.astAt(new ast.ExprCompare(this.getFieldExpr(expr), op, nullExpr), pcx);
@@ -52,7 +52,7 @@ type MessageParameterTypes = {
52
52
  'pick-missing-value': {};
53
53
  'pick-illegal-partial': {};
54
54
  'pick-when-must-be-boolean': {
55
- whenType: string;
55
+ whenType: FieldValueType;
56
56
  };
57
57
  'experiment-not-enabled': {
58
58
  experimentId: string;
@@ -315,6 +315,22 @@ type MessageParameterTypes = {
315
315
  'sql-is-not-null': string;
316
316
  'sql-is-null': string;
317
317
  'illegal-record-property-type': string;
318
+ 'sql-case': string;
319
+ 'case-then-type-does-not-match': {
320
+ thenType: FieldValueType;
321
+ returnType: FieldValueType;
322
+ };
323
+ 'case-else-type-does-not-match': {
324
+ elseType: FieldValueType;
325
+ returnType: FieldValueType;
326
+ };
327
+ 'case-when-must-be-boolean': {
328
+ whenType: FieldValueType;
329
+ };
330
+ 'case-when-type-does-not-match': {
331
+ whenType: FieldValueType;
332
+ valueType: FieldValueType;
333
+ };
318
334
  'dialect-cast-unsafe-only': string;
319
335
  };
320
336
  export declare const MESSAGE_FORMATTERS: PartialErrorCodeMessageMap;
@@ -90,6 +90,10 @@ exports.MESSAGE_FORMATTERS = {
90
90
  'syntax-error': e => e.message,
91
91
  'internal-translator-error': e => `Internal Translator Error: ${e.message}`,
92
92
  'invalid-timezone': e => `Invalid timezone: ${e.timezone}`,
93
+ 'case-then-type-does-not-match': e => `Case then type ${e.thenType} does not match return type ${e.returnType}`,
94
+ 'case-else-type-does-not-match': e => `Case else type ${e.elseType} does not match return type ${e.returnType}`,
95
+ 'case-when-must-be-boolean': e => `Case when expression must be boolean, not ${e.whenType}`,
96
+ 'case-when-type-does-not-match': e => `Case when type ${e.whenType} does not match value type ${e.valueType}`,
93
97
  };
94
98
  function makeLogMessage(code, parameters, options) {
95
99
  var _a, _b, _c, _d, _e;
@@ -735,6 +735,172 @@ describe('expressions', () => {
735
735
  }`).toLog((0, test_translator_1.errorMessage)("'output' is not defined"));
736
736
  });
737
737
  });
738
+ describe('case statements', () => {
739
+ test('full', () => {
740
+ const e = (0, test_translator_1.expr) `
741
+ case
742
+ when ai = 42 then 'the answer'
743
+ when ai = 54 then 'the questionable answer'
744
+ else 'random'
745
+ end
746
+ `;
747
+ expect(e).toLog((0, test_translator_1.warning)('sql-case'));
748
+ expect(e).compilesTo('{case when {ai = 42} then "the answer" when {ai = 54} then "the questionable answer" else "random"}');
749
+ });
750
+ test('with value', () => {
751
+ const e = (0, test_translator_1.expr) `
752
+ case ai
753
+ when 42 then 'the answer'
754
+ when 54 then 'the questionable answer'
755
+ else 'random'
756
+ end
757
+ `;
758
+ expect(e).toLog((0, test_translator_1.warning)('sql-case'));
759
+ expect(e).compilesTo('{case ai when 42 then "the answer" when 54 then "the questionable answer" else "random"}');
760
+ });
761
+ test('no else', () => {
762
+ const e = (0, test_translator_1.expr) `
763
+ case
764
+ when ai = 42 then 'the answer'
765
+ when ai = 54 then 'the questionable answer'
766
+ end
767
+ `;
768
+ expect(e).toLog((0, test_translator_1.warning)('sql-case'));
769
+ expect(e).compilesTo('{case when {ai = 42} then "the answer" when {ai = 54} then "the questionable answer"}');
770
+ });
771
+ test('wrong then type', () => {
772
+ expect((0, test_translator_1.expr) `
773
+ case
774
+ when ai = 42 then 'the answer'
775
+ when ai = 54 then 7
776
+ end
777
+ `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-then-type-does-not-match', {
778
+ thenType: 'number',
779
+ returnType: 'string',
780
+ }));
781
+ });
782
+ test('wrong when type', () => {
783
+ expect((0, test_translator_1.expr) `
784
+ case ai
785
+ when 42 then 'the answer'
786
+ when 'forty-two' then 'the answer but string'
787
+ end
788
+ `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-when-type-does-not-match', {
789
+ whenType: 'string',
790
+ valueType: 'number',
791
+ }));
792
+ });
793
+ test('wrong else type', () => {
794
+ expect((0, test_translator_1.expr) `
795
+ case
796
+ when ai = 42 then 'the answer'
797
+ else @2020
798
+ end
799
+ `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-else-type-does-not-match', {
800
+ elseType: 'date',
801
+ returnType: 'string',
802
+ }));
803
+ });
804
+ test('null then type okay second', () => {
805
+ expect((0, test_translator_1.expr) `
806
+ case
807
+ when ai = 42 then 'the answer'
808
+ when ai = 54 then null
809
+ end
810
+ `).toLog((0, test_translator_1.warning)('sql-case'));
811
+ });
812
+ test('null then type okay first', () => {
813
+ expect((0, test_translator_1.expr) `
814
+ case
815
+ when ai = 54 then null
816
+ when ai = 42 then 'the answer'
817
+ end
818
+ `).toLog((0, test_translator_1.warning)('sql-case'));
819
+ });
820
+ test('null else type okay', () => {
821
+ expect((0, test_translator_1.expr) `
822
+ case
823
+ when ai = 42 then 'the answer'
824
+ else null
825
+ end
826
+ `).toLog((0, test_translator_1.warning)('sql-case'));
827
+ });
828
+ test('null then type before else okay', () => {
829
+ expect((0, test_translator_1.expr) `
830
+ case
831
+ when ai = 42 then null
832
+ else 'not the answer'
833
+ end
834
+ `).toLog((0, test_translator_1.warning)('sql-case'));
835
+ });
836
+ test('non boolean when', () => {
837
+ expect((0, test_translator_1.expr) `
838
+ case when ai then null end
839
+ `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-when-must-be-boolean'));
840
+ });
841
+ test('type of null then second', () => {
842
+ expect(`
843
+ case
844
+ when ai = 42 then 'the answer'
845
+ when ai = 54 then null
846
+ end
847
+ `).toReturnType('string');
848
+ });
849
+ test('type of null then first', () => {
850
+ expect(`
851
+ case
852
+ when ai = 54 then null
853
+ when ai = 42 then 'the answer'
854
+ end
855
+ `).toReturnType('string');
856
+ });
857
+ test('type of null else', () => {
858
+ expect(`
859
+ case
860
+ when ai = 42 then 'the answer'
861
+ else null
862
+ end
863
+ `).toReturnType('string');
864
+ });
865
+ test('type of null then type before else', () => {
866
+ expect(`
867
+ case
868
+ when ai = 42 then null
869
+ else 'not the answer'
870
+ end
871
+ `).toReturnType('string');
872
+ });
873
+ test('replacement for full case', () => {
874
+ const e = (0, test_translator_1.expr) `case
875
+ when ai = 42 then 'the answer'
876
+ when ai = 54 then 'the questionable answer'
877
+ else 'random'
878
+ end`;
879
+ e.translator.translate();
880
+ expect(e.translator.logger.getLog()[0].replacement).toBe("pick 'the answer' when ai = 42 pick 'the questionable answer' when ai = 54 else 'random'");
881
+ });
882
+ test('replacement for case with no else', () => {
883
+ const e = (0, test_translator_1.expr) `case
884
+ when ai = 42 then 'the answer'
885
+ when ai = 54 then 'the questionable answer'
886
+ end`;
887
+ e.translator.translate();
888
+ expect(e.translator.logger.getLog()[0].replacement).toBe("pick 'the answer' when ai = 42 pick 'the questionable answer' when ai = 54 else null");
889
+ });
890
+ test('replacement for case with value', () => {
891
+ const e = (0, test_translator_1.expr) `case ai
892
+ when 42 then 'a'
893
+ when 54 then 'b'
894
+ end`;
895
+ e.translator.translate();
896
+ expect(e.translator.logger.getLog()[0].replacement).toBe("ai ? pick 'a' when 42 pick 'b' when 54 else null");
897
+ });
898
+ test('interaction with pick', () => {
899
+ expect((0, test_translator_1.expr) `
900
+ pick case when true then 'hooray' end when true else null
901
+ `).toLog((0, test_translator_1.warning)('sql-case'));
902
+ });
903
+ });
738
904
  describe('pick statements', () => {
739
905
  test('full', () => {
740
906
  expect((0, test_translator_1.expr) `
@@ -33,11 +33,11 @@ function rangeToStr(loc) {
33
33
  }
34
34
  return 'undefined';
35
35
  }
36
- function ensureNoProblems(trans) {
36
+ function ensureNoProblems(trans, warningsOkay = false) {
37
37
  if (trans.logger === undefined) {
38
38
  throw new Error('JESTERY BROKEN, CANT FIND ERORR LOG');
39
39
  }
40
- if (!trans.logger.empty()) {
40
+ if (warningsOkay ? trans.logger.hasErrors() : !trans.logger.empty()) {
41
41
  return {
42
42
  message: () => `Translation problems:\n${trans.prettyErrors()}`,
43
43
  pass: false,
@@ -114,8 +114,8 @@ function xlator(ts) {
114
114
  }
115
115
  return ts.translator || new test_translator_1.TestTranslator(ts.code);
116
116
  }
117
- function xlated(tt) {
118
- const errorCheck = ensureNoProblems(tt);
117
+ function xlated(tt, warningsOkay = false) {
118
+ const errorCheck = ensureNoProblems(tt, warningsOkay);
119
119
  if (!errorCheck.pass) {
120
120
  return errorCheck;
121
121
  }
@@ -157,6 +157,19 @@ function eToStr(e, symbols) {
157
157
  case 'true':
158
158
  case 'false':
159
159
  return e.node;
160
+ case 'case': {
161
+ const caseStmt = ['case'];
162
+ if (e.kids.caseValue !== undefined) {
163
+ caseStmt.push(`${subExpr(e.kids.caseValue)}`);
164
+ }
165
+ for (let i = 0; i < e.kids.caseWhen.length; i += 1) {
166
+ caseStmt.push(`when ${subExpr(e.kids.caseWhen[i])} then ${subExpr(e.kids.caseThen[i])}`);
167
+ }
168
+ if (e.kids.caseElse !== undefined) {
169
+ caseStmt.push(`else ${subExpr(e.kids.caseElse)}`);
170
+ }
171
+ return `{${caseStmt.join(' ')}}`;
172
+ }
160
173
  case 'regexpMatch':
161
174
  return `{${subExpr(e.kids.expr)} regex-match ${subExpr(e.kids.regex)}}`;
162
175
  }
@@ -185,13 +198,13 @@ expect.extend({
185
198
  toReturnType: function (exprText, returnType) {
186
199
  const exprModel = new test_translator_1.BetaExpression(exprText);
187
200
  exprModel.compile();
188
- const ok = xlated(exprModel);
201
+ const ok = xlated(exprModel, true);
189
202
  if (!ok.pass) {
190
203
  return ok;
191
204
  }
192
205
  const d = exprModel.generated();
193
206
  const pass = d.dataType === returnType;
194
- const msg = `Expression type ${d.dataType} ${pass ? '=' : '!='} $[returnType`;
207
+ const msg = `Expression type ${d.dataType} ${pass ? '=' : '!='} ${returnType}`;
195
208
  return { pass, message: () => msg };
196
209
  },
197
210
  toLog: function (s, ...msgs) {
@@ -1,5 +1,5 @@
1
1
  import { Dialect, DialectFieldList } from '../dialect';
2
- import { AggregateFunctionType, Annotation, CompiledQuery, Expr, FieldDef, Filtered, FunctionOverloadDef, FunctionParameterDef, JoinRelationship, ModelDef, OrderBy, OutputFieldNode, Parameter, ParameterNode, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SegmentFieldDef, StructDef, StructRef, TurtleDef, FunctionOrderBy, Argument, AggregateExpr, FilterCondition, GenericSQLExpr, FieldnameNode, FunctionCallNode, UngroupNode, SourceReferenceNode, PickExpr, SpreadExpr, FilteredExpr, SourceDef, AtomicFieldDef, BooleanFieldDef, QueryToMaterialize, PrepareResultOptions } from './malloy_types';
2
+ import { AggregateFunctionType, Annotation, CompiledQuery, Expr, FieldDef, Filtered, FunctionOverloadDef, FunctionParameterDef, JoinRelationship, ModelDef, OrderBy, OutputFieldNode, Parameter, ParameterNode, PipeSegment, Query, QueryFieldDef, QuerySegment, ResultMetadataDef, ResultStructMetadataDef, SearchIndexResult, SegmentFieldDef, StructDef, StructRef, TurtleDef, FunctionOrderBy, Argument, AggregateExpr, FilterCondition, GenericSQLExpr, FieldnameNode, FunctionCallNode, UngroupNode, SourceReferenceNode, SpreadExpr, FilteredExpr, SourceDef, AtomicFieldDef, BooleanFieldDef, QueryToMaterialize, PrepareResultOptions, CaseExpr } from './malloy_types';
3
3
  import { Connection } from '../connection/types';
4
4
  import { AndChain } from './utils';
5
5
  import { QueryInfo } from '../dialect/dialect';
@@ -92,7 +92,7 @@ declare class QueryField extends QueryNode {
92
92
  generateSourceReference(resultSet: FieldInstanceResult, context: QueryStruct, expr: SourceReferenceNode): string;
93
93
  getAnalyticPartitions(resultStruct: FieldInstanceResult, extraPartitionFields?: string[]): string[];
94
94
  generateAnalyticFragment(dialect: string, resultStruct: FieldInstanceResult, context: QueryStruct, expr: Expr, overload: FunctionOverloadDef, state: GenerateState, args: Expr[], partitionByFields?: string[], funcOrdering?: string): string;
95
- generatePickSQL(pf: PickExpr): string;
95
+ generateCaseSQL(pf: CaseExpr): string;
96
96
  exprToSQL(resultSet: FieldInstanceResult, context: QueryStruct, exprToTranslate: Expr, state?: GenerateState): string;
97
97
  isNestedInParent(parentDef: FieldDef): boolean;
98
98
  isArrayElement(parentDef: FieldDef): boolean;
@@ -708,12 +708,18 @@ class QueryField extends QueryNode {
708
708
  }
709
709
  return retExpr;
710
710
  }
711
- generatePickSQL(pf) {
711
+ generateCaseSQL(pf) {
712
712
  const caseStmt = ['CASE'];
713
- for (let i = 0; i < pf.kids.pickWhen.length; i += 1) {
714
- caseStmt.push(`WHEN ${pf.kids.pickWhen[i].sql} THEN ${pf.kids.pickThen[i].sql}`);
713
+ if (pf.kids.caseValue !== undefined) {
714
+ caseStmt.push(`${pf.kids.caseValue.sql}`);
715
715
  }
716
- caseStmt.push(`ELSE ${pf.kids.pickElse.sql}`, 'END');
716
+ for (let i = 0; i < pf.kids.caseWhen.length; i += 1) {
717
+ caseStmt.push(`WHEN ${pf.kids.caseWhen[i].sql} THEN ${pf.kids.caseThen[i].sql}`);
718
+ }
719
+ if (pf.kids.caseElse !== undefined) {
720
+ caseStmt.push(`ELSE ${pf.kids.caseElse.sql}`);
721
+ }
722
+ caseStmt.push('END');
717
723
  return caseStmt.join(' ');
718
724
  }
719
725
  exprToSQL(resultSet, context, exprToTranslate, state = new GenerateState()) {
@@ -740,6 +746,8 @@ class QueryField extends QueryNode {
740
746
  expr = { ...exprToTranslate };
741
747
  const oldKids = exprToTranslate.kids;
742
748
  for (const [name, kidExpr] of Object.entries(oldKids)) {
749
+ if (kidExpr === null)
750
+ continue;
743
751
  if (Array.isArray(kidExpr)) {
744
752
  expr.kids[name] = kidExpr.map(e => {
745
753
  return { ...e, sql: subExpr(this, e) };
@@ -852,8 +860,8 @@ class QueryField extends QueryNode {
852
860
  return expr.node;
853
861
  case 'null':
854
862
  return 'NULL';
855
- case 'pick':
856
- return this.generatePickSQL(expr);
863
+ case 'case':
864
+ return this.generateCaseSQL(expr);
857
865
  case '':
858
866
  return '';
859
867
  case 'filterCondition':
@@ -24,7 +24,7 @@ export type AnyExpr = ExprE | ExprOptionalE | ExprWithKids | ExprLeaf;
24
24
  export declare function exprHasKids(e: AnyExpr): e is ExprWithKids;
25
25
  export declare function exprHasE(e: AnyExpr): e is ExprE;
26
26
  export declare function exprIsLeaf(e: AnyExpr): boolean;
27
- export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | NowNode | MeasureTimeExpr | TimeDeltaExpr | TimeTruncExpr | TimeExtractExpr | TypecastExpr | RegexMatchExpr | RegexLiteralNode | TimeLiteralNode | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | PickExpr | ArrayEachExpr | ErrorNode;
27
+ export type Expr = BinaryExpr | UnaryExpr | FunctionCallNode | OutputFieldNode | FilterCondition | FilteredExpr | AggregateExpr | EmptyExpr | UngroupNode | FunctionParameterNode | SpreadExpr | AggregateOrderByNode | AggregateLimitNode | FieldnameNode | SourceReferenceNode | ParameterNode | NowNode | MeasureTimeExpr | TimeDeltaExpr | TimeTruncExpr | TimeExtractExpr | TypecastExpr | RegexMatchExpr | RegexLiteralNode | TimeLiteralNode | StringLiteralNode | NumberLiteralNode | BooleanLiteralNode | RecordLiteralNode | ArrayLiteralNode | FunctionOrderBy | GenericSQLExpr | NullNode | CaseExpr | ArrayEachExpr | ErrorNode;
28
28
  interface HasDataType {
29
29
  dataType: AtomicFieldType;
30
30
  }
@@ -210,12 +210,13 @@ export interface GenericSQLExpr extends ExprWithKids {
210
210
  export interface NullNode extends ExprLeaf {
211
211
  node: 'null';
212
212
  }
213
- export interface PickExpr extends ExprWithKids {
214
- node: 'pick';
213
+ export interface CaseExpr extends ExprWithKids {
214
+ node: 'case';
215
215
  kids: {
216
- pickWhen: Expr[];
217
- pickThen: Expr[];
218
- pickElse: Expr;
216
+ caseValue?: Expr;
217
+ caseWhen: Expr[];
218
+ caseThen: Expr[];
219
+ caseElse?: Expr;
219
220
  };
220
221
  }
221
222
  export interface ArrayEachExpr extends ExprLeaf {
@@ -119,7 +119,7 @@ function* exprKids(eNode) {
119
119
  if (Array.isArray(kidEnt)) {
120
120
  yield* kidEnt;
121
121
  }
122
- else {
122
+ else if (kidEnt !== null) {
123
123
  yield kidEnt;
124
124
  }
125
125
  }
@@ -150,7 +150,7 @@ function exprMap(eNode, mapFunc) {
150
150
  if (Array.isArray(kidEnt)) {
151
151
  parentNode.kids[name] = kidEnt.map(kidEl => mapFunc(kidEl));
152
152
  }
153
- else {
153
+ else if (kidEnt !== null) {
154
154
  parentNode.kids[name] = mapFunc(kidEnt);
155
155
  }
156
156
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.197";
1
+ export declare const MALLOY_VERSION = "0.0.198";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.197';
5
+ exports.MALLOY_VERSION = '0.0.198';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.197",
3
+ "version": "0.0.198-dev241010181909",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",