@malloydata/malloy 0.0.240-dev250311202829 → 0.0.240-dev250311214430

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/api/core.d.ts +1 -0
  2. package/dist/api/core.js +2 -1
  3. package/dist/lang/malloy-parse-info.d.ts +2 -10
  4. package/dist/lang/malloy-to-ast.d.ts +1 -0
  5. package/dist/lang/malloy-to-ast.js +9 -6
  6. package/dist/lang/malloy-to-stable-query.d.ts +76 -0
  7. package/dist/lang/malloy-to-stable-query.js +660 -0
  8. package/dist/lang/parse-log.d.ts +1 -0
  9. package/dist/lang/parse-malloy.d.ts +2 -9
  10. package/dist/lang/parse-malloy.js +7 -112
  11. package/dist/lang/parse-tree-walkers/model-annotation-walker.js +1 -1
  12. package/dist/lang/run-malloy-parser.d.ts +3 -0
  13. package/dist/lang/run-malloy-parser.js +53 -0
  14. package/dist/lang/syntax-errors/malloy-parser-error-listener.d.ts +4 -3
  15. package/dist/lang/syntax-errors/malloy-parser-error-listener.js +8 -4
  16. package/dist/lang/utils.d.ts +27 -1
  17. package/dist/lang/utils.js +78 -1
  18. package/package.json +3 -3
  19. package/dist/api/asynchronous.spec.d.ts +0 -1
  20. package/dist/api/asynchronous.spec.js +0 -240
  21. package/dist/api/sessioned.spec.d.ts +0 -1
  22. package/dist/api/sessioned.spec.js +0 -476
  23. package/dist/api/stateless.spec.d.ts +0 -1
  24. package/dist/api/stateless.spec.js +0 -400
  25. package/dist/lang/test/annotation.spec.d.ts +0 -14
  26. package/dist/lang/test/annotation.spec.js +0 -809
  27. package/dist/lang/test/composite-field-usage.spec.d.ts +0 -1
  28. package/dist/lang/test/composite-field-usage.spec.js +0 -261
  29. package/dist/lang/test/document-help-context-walker.spec.d.ts +0 -1
  30. package/dist/lang/test/document-help-context-walker.spec.js +0 -55
  31. package/dist/lang/test/document-symbol-walker.spec.d.ts +0 -1
  32. package/dist/lang/test/document-symbol-walker.spec.js +0 -209
  33. package/dist/lang/test/expressions.spec.d.ts +0 -1
  34. package/dist/lang/test/expressions.spec.js +0 -1181
  35. package/dist/lang/test/field-symbols.spec.d.ts +0 -1
  36. package/dist/lang/test/field-symbols.spec.js +0 -194
  37. package/dist/lang/test/find-table-path-walker.spec.d.ts +0 -1
  38. package/dist/lang/test/find-table-path-walker.spec.js +0 -50
  39. package/dist/lang/test/imports.spec.d.ts +0 -1
  40. package/dist/lang/test/imports.spec.js +0 -283
  41. package/dist/lang/test/lenses.spec.d.ts +0 -1
  42. package/dist/lang/test/lenses.spec.js +0 -384
  43. package/dist/lang/test/literals.spec.d.ts +0 -1
  44. package/dist/lang/test/literals.spec.js +0 -267
  45. package/dist/lang/test/locations.spec.d.ts +0 -1
  46. package/dist/lang/test/locations.spec.js +0 -679
  47. package/dist/lang/test/model-annotation-walker.spec.d.ts +0 -1
  48. package/dist/lang/test/model-annotation-walker.spec.js +0 -46
  49. package/dist/lang/test/model_serialization.spec.d.ts +0 -5
  50. package/dist/lang/test/model_serialization.spec.js +0 -116
  51. package/dist/lang/test/parameters.spec.d.ts +0 -1
  52. package/dist/lang/test/parameters.spec.js +0 -619
  53. package/dist/lang/test/parse.spec.d.ts +0 -1
  54. package/dist/lang/test/parse.spec.js +0 -893
  55. package/dist/lang/test/pretranslate.spec.d.ts +0 -1
  56. package/dist/lang/test/pretranslate.spec.js +0 -80
  57. package/dist/lang/test/query.spec.d.ts +0 -1
  58. package/dist/lang/test/query.spec.js +0 -1149
  59. package/dist/lang/test/source.spec.d.ts +0 -1
  60. package/dist/lang/test/source.spec.js +0 -570
  61. package/dist/lang/test/sql-block.spec.d.ts +0 -1
  62. package/dist/lang/test/sql-block.spec.js +0 -145
  63. package/dist/lang/test/syntax-errors.spec.d.ts +0 -1
  64. package/dist/lang/test/syntax-errors.spec.js +0 -142
  65. package/dist/model/utils.spec.d.ts +0 -1
  66. package/dist/model/utils.spec.js +0 -38
@@ -1,1181 +0,0 @@
1
- "use strict";
2
- /*
3
- * Copyright 2023 Google LLC
4
- *
5
- * Permission is hereby granted, free of charge, to any person obtaining
6
- * a copy of this software and associated documentation files
7
- * (the "Software"), to deal in the Software without restriction,
8
- * including without limitation the rights to use, copy, modify, merge,
9
- * publish, distribute, sublicense, and/or sell copies of the Software,
10
- * and to permit persons to whom the Software is furnished to do so,
11
- * subject to the following conditions:
12
- *
13
- * The above copyright notice and this permission notice shall be
14
- * included in all copies or substantial portions of the Software.
15
- *
16
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
- */
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- const test_translator_1 = require("./test-translator");
26
- require("./parse-expects");
27
- describe('expressions', () => {
28
- describe('timeframes', () => {
29
- const timeframes = [
30
- ['second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'],
31
- ];
32
- test.each(timeframes)('timestamp truncate %s', unit => {
33
- const truncSrc = (0, test_translator_1.model) `run: a->{select: tts is ats.${unit}}`;
34
- expect(truncSrc).toTranslate();
35
- const tQuery = truncSrc.translator.getQuery(0);
36
- expect(tQuery).toBeDefined();
37
- const tField = (0, test_translator_1.getQueryFieldDef)(tQuery.pipeline[0], 'tts');
38
- expect(tField['timeframe']).toEqual(unit);
39
- });
40
- const dateTF = [['week', 'month', 'quarter', 'year']];
41
- test.each(dateTF)('date truncate %s', unit => {
42
- const truncSrc = (0, test_translator_1.model) `run: a->{select: td is ad.${unit}}`;
43
- expect(truncSrc).toTranslate();
44
- const tQuery = truncSrc.translator.getQuery(0);
45
- expect(tQuery).toBeDefined();
46
- const tField = (0, test_translator_1.getQueryFieldDef)(tQuery.pipeline[0], 'td');
47
- expect(tField['timeframe']).toEqual(unit);
48
- });
49
- // mtoy todo units missing: implement, or document
50
- const diffable = [['second', 'minute', 'hour', 'day']];
51
- test.each(diffable)('timestamp difference - %s', unit => {
52
- expect(new test_translator_1.BetaExpression(`${unit}(@2021 to ats)`)).toParse();
53
- });
54
- test.each(diffable)('timestamp difference - %s', unit => {
55
- expect(new test_translator_1.BetaExpression(`${unit}(ats to @2030)`)).toParse();
56
- });
57
- });
58
- test('field name', () => {
59
- expect((0, test_translator_1.expr) `astr`).compilesTo('astr');
60
- });
61
- test('function call', () => {
62
- expect((0, test_translator_1.expr) `concat('foo')`).toTranslate();
63
- });
64
- test('raw function call codegen', () => {
65
- expect((0, test_translator_1.expr) `special_function!(aweird, 'foo')`).compilesTo('special_function({aweird},{{"foo"}})');
66
- });
67
- describe('operators', () => {
68
- test('addition', () => {
69
- expect('42 + 7').compilesTo('{42 + 7}');
70
- });
71
- test('typecheck addition lhs', () => {
72
- const wrong = (0, test_translator_1.expr) `${'"string"'} + 1`;
73
- expect(wrong).toLog((0, test_translator_1.errorMessage)("The '+' operator requires a number, not a 'string'"));
74
- });
75
- test('typecheck addition rhs', () => {
76
- const wrong = (0, test_translator_1.expr) `1 + ${'"string"'}`;
77
- expect(wrong).toLog((0, test_translator_1.errorMessage)("The '+' operator requires a number, not a 'string'"));
78
- });
79
- test('subtraction', () => {
80
- expect('42 - 7').compilesTo('{42 - 7}');
81
- });
82
- test('multiplication', () => {
83
- expect('42 * 7').compilesTo('{42 * 7}');
84
- });
85
- test('mod', () => {
86
- expect('42 % 7').compilesTo('{42 % 7}');
87
- });
88
- test('division', () => {
89
- expect('42 / 7').compilesTo('{42 / 7}');
90
- });
91
- test('unary negation', () => {
92
- expect('- ai').compilesTo('{unary- ai}');
93
- });
94
- test('equal', () => {
95
- expect('42 = 7').compilesTo('{42 = 7}');
96
- });
97
- test('not equal', () => {
98
- expect('42 != 7').compilesTo('{42 != 7}');
99
- });
100
- test('greater than', () => {
101
- expect('42 > 7').compilesTo('{42 > 7}');
102
- });
103
- test('greater than or equal', () => {
104
- expect('42 >= 7').compilesTo('{42 >= 7}');
105
- });
106
- test('less than or equal', () => {
107
- expect('42 <= 7').compilesTo('{42 <= 7}');
108
- });
109
- test('less than', () => {
110
- expect('42 < 7').compilesTo('{42 < 7}');
111
- });
112
- test('match', () => {
113
- expect("'forty-two' ~ 'fifty-four'").compilesTo('{{"forty-two"} like {"fifty-four"}}');
114
- });
115
- test('not match', () => {
116
- expect("'forty-two' !~ 'fifty-four'").compilesTo('{{"forty-two"} !like {"fifty-four"}}');
117
- });
118
- test('regexp-match', () => {
119
- expect("'forty-two' ~ r'fifty-four'").compilesTo('{{"forty-two"} regex-match /fifty-four/}');
120
- });
121
- test('not regexp-match', () => {
122
- expect("'forty-two' !~ r'fifty-four'").compilesTo('{not {{"forty-two"} regex-match /fifty-four/}}');
123
- });
124
- test('apply as equality', () => {
125
- expect("'forty-two' ? 'fifty-four'").compilesTo('{{"forty-two"} = {"fifty-four"}}');
126
- });
127
- test('not', () => {
128
- expect('not true').compilesTo('{not true}');
129
- });
130
- test('and', () => {
131
- expect('true and false').compilesTo('{true and false}');
132
- });
133
- test('or', () => {
134
- expect('true or false').compilesTo('{true or false}');
135
- });
136
- test('null-check (??)', () => {
137
- expect('ai ?? 7').compilesTo('{ai coalesce 7}');
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
- });
151
- test('coalesce type mismatch', () => {
152
- expect(new test_translator_1.BetaExpression('ai ?? @2003')).toLog((0, test_translator_1.errorMessage)('Mismatched types for coalesce (number, date)'));
153
- });
154
- test('disallow date OP number', () => {
155
- expect(new test_translator_1.BetaExpression('@2001 = 7')).toLog((0, test_translator_1.errorMessage)('Cannot compare a date to a number'));
156
- });
157
- test('disallow date OP timestamp', () => {
158
- expect(new test_translator_1.BetaExpression('ad = ats')).toLog((0, test_translator_1.errorMessage)('Cannot compare a date to a timestamp'));
159
- });
160
- test('disallow interval from date to timestamp', () => {
161
- expect(new test_translator_1.BetaExpression('days(ad to ats)')).toLog((0, test_translator_1.errorMessage)('Cannot measure from date to timestamp'));
162
- });
163
- test('compare to truncation uses straight comparison', () => {
164
- expect('ad = ad.quarter').compilesTo('{ad = {timeTrunc-quarter ad}}');
165
- });
166
- test('compare to granular result expression uses straight comparison', () => {
167
- expect('ad = ad.quarter + 1').compilesTo('{ad = {+quarter {timeTrunc-quarter ad} 1}}');
168
- });
169
- test('apply granular-truncation uses range', () => {
170
- expect('ad ? ad.quarter').compilesTo('{{ad >= {timeTrunc-quarter ad}} and {ad < {+quarter {timeTrunc-quarter ad} 1}}}');
171
- });
172
- test('apply granular-literal alternation uses all literals for range', () => {
173
- expect('ad ? @2020').compilesTo('{{ad >= @2020-01-01} and {ad < @2021-01-01}}');
174
- });
175
- test('apply followed by another condition', () => {
176
- expect('ai ? (10 | 20) and ai is not null').toLog((0, test_translator_1.errorMessage)("no viable alternative at input 'ai'"));
177
- });
178
- test('apply followed by another condition, with parenthesis', () => {
179
- expect('(ai ? (10 | 20)) and ai is not null').compilesTo('{({ai in {10,20}}) and {is-not-null ai}}');
180
- });
181
- test('apply or-tree granular-literal doesnt turn into IN', () => {
182
- expect('ad ? @2020 | @2022').compilesTo('{{{ad >= @2020-01-01} and {ad < @2021-01-01}} or {{ad >= @2022-01-01} and {ad < @2023-01-01}}}');
183
- });
184
- test('comparison promotes date literal to timestamp', () => {
185
- expect((0, test_translator_1.expr) `@2001 = ats`).toTranslate();
186
- });
187
- test('can apply range to date', () => {
188
- expect((0, test_translator_1.expr) `ad ? @2001 for 1 day`).toTranslate();
189
- });
190
- const noOffset = ['second', 'minute', 'hour'];
191
- test.each(noOffset.map(x => [x]))('disallow date delta %s', unit => {
192
- expect(new test_translator_1.BetaExpression(`ad + 10 ${unit}s`)).toLog((0, test_translator_1.errorMessage)(`Cannot offset date by ${unit}`));
193
- });
194
- test('apply with parens', () => {
195
- expect((0, test_translator_1.expr) `ai ? (> 1 & < 100)`).toTranslate();
196
- });
197
- describe('sql friendly warnings', () => {
198
- test('= null with warning', () => {
199
- const warnSrc = (0, test_translator_1.expr) `${'ai = null'}`;
200
- expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use 'is null' to check for NULL instead of '= null'"));
201
- expect(warnSrc).compilesTo('{is-null ai}');
202
- const warning = warnSrc.translator.problems()[0];
203
- expect(warning.replacement).toEqual('ai is null');
204
- });
205
- test('is not null with warning', () => {
206
- const warnSrc = (0, test_translator_1.expr) `${'ai != null'}`;
207
- expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use 'is not null' to check for NULL instead of '!= null'"));
208
- expect(warnSrc).compilesTo('{is-not-null ai}');
209
- const warning = warnSrc.translator.problems()[0];
210
- expect(warning.replacement).toEqual('ai is not null');
211
- });
212
- test('like with warning', () => {
213
- const warnSrc = (0, test_translator_1.expr) `astr like 'a'`;
214
- expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use Malloy operator '~' instead of 'LIKE'"));
215
- expect(warnSrc).compilesTo('{astr like {"a"}}');
216
- const warning = warnSrc.translator.problems()[0];
217
- expect(warning.replacement).toEqual("astr ~ 'a'");
218
- });
219
- test('NOT LIKE with warning', () => {
220
- const warnSrc = (0, test_translator_1.expr) `astr not like 'a'`;
221
- expect(warnSrc).toLog((0, test_translator_1.warningMessage)("Use Malloy operator '!~' instead of 'NOT LIKE'"));
222
- expect(warnSrc).compilesTo('{astr !like {"a"}}');
223
- const warning = warnSrc.translator.problems()[0];
224
- expect(warning.replacement).toEqual("astr !~ 'a'");
225
- });
226
- test('x is expr y is not null', () => {
227
- const isNullSrc = (0, test_translator_1.model) `source: xa is a extend { dimension: x is 1 y is not null }`;
228
- expect(isNullSrc).toTranslate();
229
- const xaModel = isNullSrc.translator.translate().modelDef;
230
- const xa = (0, test_translator_1.getExplore)(xaModel, 'xa');
231
- const x = (0, test_translator_1.getFieldDef)(xa, 'x');
232
- expect(x).toMatchObject({ e: { node: 'numberLiteral' } });
233
- const y = (0, test_translator_1.getFieldDef)(xa, 'y');
234
- expect(y).toMatchObject({ e: { node: 'not' } });
235
- });
236
- test('not null::number', () => {
237
- const notNull = (0, test_translator_1.expr) `not null::number`;
238
- expect(notNull).toLog((0, test_translator_1.errorMessage)("'not' Can't use type number"));
239
- });
240
- test('(not null)::number', () => {
241
- const notNull = (0, test_translator_1.expr) `(not null)::number`;
242
- expect(notNull).toTranslate();
243
- });
244
- });
245
- });
246
- test('filtered measure', () => {
247
- expect((0, test_translator_1.expr) `acount {where: astr = 'why?' }`).toTranslate();
248
- });
249
- test('filtered ungrouped aggregate', () => {
250
- expect(`
251
- run: a -> {
252
- group_by: ai
253
- aggregate: x is all(avg(ai)) { where: true }
254
- }
255
- `).toTranslate();
256
- });
257
- test('correctly flags filtered scalar', () => {
258
- const e = new test_translator_1.BetaExpression('ai { where: true }');
259
- expect(e).toLog((0, test_translator_1.errorMessage)('Filtered expression requires an aggregate computation'));
260
- });
261
- test('correctly flags filtered analytic', () => {
262
- expect((0, test_translator_1.markSource) `
263
- run: a -> {
264
- group_by: ai
265
- calculate: l is lag(ai) { where: true }
266
- }
267
- `).toLog((0, test_translator_1.errorMessage)('Filtered expression requires an aggregate computation'));
268
- });
269
- test('can use calculate with partition by in select', () => {
270
- expect((0, test_translator_1.markSource) `
271
- ##! experimental { partition_by function_order_by }
272
- run: a -> {
273
- select: ai, astr
274
- calculate: prev is lag(ai) {
275
- partition_by: astr
276
- order_by: ai asc
277
- }
278
- order_by: ai asc, astr asc
279
- }`).toTranslate();
280
- });
281
- describe('expr props', () => {
282
- test('aggregate order by not allowed without experiments enabled', () => {
283
- expect((0, test_translator_1.markSource) `
284
- run: a -> {
285
- group_by: ai
286
- aggregate: x1 is string_agg(astr) { order_by: ai }
287
- }
288
- `).toLog((0, test_translator_1.errorMessage)('Enable experiment `aggregate_order_by` to use `order_by` with an aggregate function'));
289
- });
290
- test('aggregate limit not allowed without experiments enabled', () => {
291
- expect((0, test_translator_1.markSource) `
292
- run: a -> {
293
- group_by: ai
294
- aggregate: x3 is string_agg(astr) { limit: 10 }
295
- }
296
- `).toLog((0, test_translator_1.error)('experiment-not-enabled', { experimentId: 'aggregate_limit' }));
297
- });
298
- test('aggregate order_by not allowed with different experiment enabled', () => {
299
- expect((0, test_translator_1.markSource) `
300
- ##! experimental.something_else
301
- run: a -> {
302
- group_by: ai
303
- aggregate: x1 is string_agg(astr) { order_by: ai }
304
- }
305
- `).toLog((0, test_translator_1.errorMessage)('Enable experiment `aggregate_order_by` to use `order_by` with an aggregate function'));
306
- });
307
- test('aggregate limit not allowed with different experiment enabled', () => {
308
- expect((0, test_translator_1.markSource) `
309
- ##! experimental.something_else
310
- run: a -> {
311
- group_by: ai
312
- group_by: x3 is string_agg(astr) { limit: 10 }
313
- }
314
- `).toLog((0, test_translator_1.error)('experiment-not-enabled', { experimentId: 'aggregate_limit' }));
315
- });
316
- test('props not allowed on most expressions', () => {
317
- expect((0, test_translator_1.markSource) `
318
- ##! experimental { aggregate_order_by aggregate_limit }
319
- run: a -> {
320
- group_by: x1 is 1 { order_by: ai }
321
- group_by: x2 is 1 { partition_by: ai }
322
- group_by: x3 is 1 { limit: 10 }
323
- group_by: x4 is 1 { where: ai }
324
- }
325
- `).toLog((0, test_translator_1.errorMessage)('`order_by` is not supported for this kind of expression'), (0, test_translator_1.errorMessage)('`partition_by` is not supported for this kind of expression'), (0, test_translator_1.errorMessage)('`limit` is not supported for this kind of expression'), (0, test_translator_1.errorMessage)('Filtered expression requires an aggregate computation'));
326
- });
327
- test('analytics can take parititon_by and order_by', () => {
328
- expect((0, test_translator_1.markSource) `
329
- run: a -> {
330
- group_by: ai
331
- calculate: x is lag(ai) { partition_by: ai; order_by: ai }
332
- }
333
- `).toTranslate();
334
- });
335
- test('partition by works with scalar and aggregate', () => {
336
- expect((0, test_translator_1.markSource) `
337
- run: a -> {
338
- group_by: ai
339
- aggregate: c is count()
340
- calculate: x is lag(ai) { partition_by: ai, c }
341
- }
342
- `).toTranslate();
343
- });
344
- test('partition by fails with analytic and ungrouped aggregate', () => {
345
- expect((0, test_translator_1.markSource) `
346
- run: a -> {
347
- group_by: ai
348
- aggregate: ac is all(count())
349
- calculate: x is lag(ai) { partition_by: ac }
350
- calculate: y is lag(ai) { partition_by: x }
351
- }
352
- `).toLog((0, test_translator_1.errorMessage)('Partition expression must be scalar or aggregate'), (0, test_translator_1.errorMessage)('Partition expression must be scalar or aggregate'));
353
- });
354
- test('analytics order_by requires expression', () => {
355
- expect((0, test_translator_1.markSource) `
356
- ##! experimental { aggregate_order_by }
357
- run: a -> {
358
- group_by: ai
359
- calculate: x is lag(ai) { order_by: asc }
360
- }
361
- `).toLog((0, test_translator_1.errorMessage)('analytic `order_by` must specify an aggregate expression or output field reference'));
362
- });
363
- test('string_agg_distinct order by cannot specify expression', () => {
364
- expect((0, test_translator_1.markSource) `
365
- ##! experimental { aggregate_order_by }
366
- run: a -> {
367
- group_by: ai
368
- aggregate: x is string_agg_distinct(astr) { order_by: ai }
369
- }
370
- `).toLog((0, test_translator_1.errorMessage)('`order_by` must be only `asc` or `desc` with no expression'));
371
- });
372
- test('string_agg_distinct order by can be just direction', () => {
373
- expect((0, test_translator_1.markSource) `
374
- ##! experimental { aggregate_order_by }
375
- run: a -> {
376
- group_by: ai
377
- aggregate: x is string_agg_distinct(astr) { order_by: asc }
378
- }
379
- `).toTranslate();
380
- });
381
- test('string_agg order by can be just direction', () => {
382
- expect((0, test_translator_1.markSource) `
383
- ##! experimental { aggregate_order_by }
384
- run: a -> {
385
- group_by: ai
386
- aggregate: x is string_agg(astr) { order_by: asc }
387
- }
388
- `).toTranslate();
389
- });
390
- test('can specify multiple partition_bys', () => {
391
- expect((0, test_translator_1.markSource) `
392
- run: a -> {
393
- group_by: ai, astr, abool
394
- calculate: x is lag(ai) {
395
- partition_by: ai
396
- partition_by: astr, abool
397
- }
398
- }
399
- `).toTranslate();
400
- });
401
- test('can specify multiple order_bys', () => {
402
- expect((0, test_translator_1.markSource) `
403
- ##! experimental { aggregate_order_by }
404
- run: a -> {
405
- group_by: ai, astr, abool
406
- calculate: x is lag(ai) {
407
- order_by: ai
408
- order_by: astr, abool
409
- }
410
- }
411
- `).toTranslate();
412
- });
413
- test('aggregate order by cannot be aggregate', () => {
414
- expect((0, test_translator_1.markSource) `
415
- ##! experimental { aggregate_order_by }
416
- run: a -> {
417
- aggregate: x is string_agg(astr) {
418
- order_by: sum(ai)
419
- }
420
- }
421
- `).toLog((0, test_translator_1.errorMessage)('aggregate `order_by` must be scalar'));
422
- });
423
- test('aggregate order by cannot be analytic', () => {
424
- expect((0, test_translator_1.markSource) `
425
- ##! experimental { aggregate_order_by }
426
- run: a -> {
427
- aggregate: x is string_agg(astr) {
428
- order_by: rank()
429
- }
430
- }
431
- `).toLog((0, test_translator_1.errorMessage)('aggregate `order_by` must be scalar'));
432
- });
433
- test('analytic order by can be an aggregate', () => {
434
- expect((0, test_translator_1.markSource) `
435
- ##! experimental { aggregate_order_by }
436
- run: a -> {
437
- group_by: abool
438
- calculate: x is lag(abool) {
439
- order_by: sum(ai)
440
- }
441
- }
442
- `).toTranslate();
443
- });
444
- test('analytic order by can be an output field', () => {
445
- expect((0, test_translator_1.markSource) `
446
- ##! experimental { aggregate_order_by }
447
- run: a -> {
448
- group_by: ai
449
- calculate: x is lag(ai) {
450
- order_by: ai
451
- }
452
- }
453
- `).toTranslate();
454
- });
455
- test('analytic order by must be an output field', () => {
456
- expect((0, test_translator_1.markSource) `
457
- ##! experimental { aggregate_order_by }
458
- run: a -> {
459
- group_by: abool
460
- calculate: x is lag(abool) {
461
- order_by: ai
462
- }
463
- }
464
- `).toLog((0, test_translator_1.errorMessage)('analytic `order_by` must be an aggregate or an output field reference'));
465
- });
466
- test('can specify multiple wheres', () => {
467
- expect((0, test_translator_1.markSource) `
468
- ##! experimental { aggregate_order_by }
469
- run: a -> {
470
- aggregate: x is count() {
471
- where: ai > 10
472
- where: astr ~ '%foo%'
473
- }
474
- }
475
- `).toTranslate();
476
- });
477
- test('string_agg can take order_by', () => {
478
- expect((0, test_translator_1.markSource) `
479
- ##! experimental { aggregate_order_by }
480
- run: a -> {
481
- aggregate: x1 is string_agg(astr) { order_by: ai }
482
- aggregate: x2 is string_agg(astr) { order_by: ai * 2 }
483
- aggregate: x3 is string_agg(astr) { order_by: ai desc }
484
- aggregate: x4 is string_agg(astr) { order_by: ai asc }
485
- aggregate: x5 is string_agg(astr) { order_by: ai asc, ai }
486
- }
487
- `).toTranslate();
488
- });
489
- });
490
- describe('aggregate forms', () => {
491
- const m = (0, test_translator_1.model) `
492
- source: root is a extend {
493
- rename: column is ai
494
- rename: nested is astruct
495
- rename: inline is aninline
496
- dimension: field is column * 2
497
- dimension: field_and_one_field is column + one.column
498
- dimension: many_field is many.column * 2
499
- dimension: many_one_field is many.column + one.column
500
- join_one: one is a extend {
501
- rename: column is ai
502
- dimension: field is column * 2
503
- dimension: many_field is many.column * 2
504
- join_many: many is a extend {
505
- rename: column is ai
506
- dimension: field is column * 2
507
- dimension: constant is 1
508
- } on true
509
- } on true
510
- join_many: many is a extend {
511
- rename: column is ai
512
- dimension: field is column * 2
513
- dimension: constant is 1
514
- join_one: one is a extend {
515
- rename: column is ai
516
- dimension: field is column * 2
517
- join_one: one is a extend {
518
- rename: column is ai
519
- dimension: field is column * 2
520
- } on true
521
- } on true
522
- } on true
523
- join_cross: cross is a extend {
524
- rename: column is ai
525
- dimension: field is column * 2
526
- } on true
527
- }
528
- `;
529
- m.translator.translate();
530
- expect(m).toTranslate();
531
- const modelX = (0, test_translator_1.makeModelFunc)({
532
- model: m.translator.modelDef,
533
- wrap: x => `run: root -> { aggregate: x is ${x} }`,
534
- });
535
- test('one.column.min()', () => {
536
- expect(modelX `one.column.min()`).toTranslate();
537
- });
538
- test('one.min(one.column)', () => {
539
- expect(modelX `one.min(one.column)`).toLog((0, test_translator_1.errorMessage)('Symmetric aggregate function `min` must be written as `min(expression)` or `path.to.field.min()`'));
540
- });
541
- test('min(one.column)', () => {
542
- expect(modelX `min(one.column)`).toTranslate();
543
- });
544
- test('min(many.column)', () => {
545
- expect(modelX `min(many.column)`).toTranslate();
546
- });
547
- test('min()', () => {
548
- expect(modelX `min()`).toLog((0, test_translator_1.errorMessage)('Symmetric aggregate function `min` must be written as `min(expression)` or `path.to.field.min()`'));
549
- });
550
- test('source.min(column)', () => {
551
- expect(modelX `source.min(column)`).toTranslate();
552
- });
553
- test('many.column.max()', () => {
554
- expect(modelX `many.column.max()`).toTranslate();
555
- });
556
- test('max(many.column)', () => {
557
- expect(modelX `max(many.column)`).toTranslate();
558
- });
559
- test('max()', () => {
560
- expect(modelX `max()`).toLog((0, test_translator_1.errorMessage)('Symmetric aggregate function `max` must be written as `max(expression)` or `path.to.field.max()`'));
561
- });
562
- test('source.max(many.column)', () => {
563
- expect(modelX `source.max(many.column)`).toTranslate();
564
- });
565
- test('many.column.count()', () => {
566
- expect((0, test_translator_1.expr) `many.column.count()`).toTranslate();
567
- });
568
- test('count()', () => {
569
- expect(modelX `count()`).toTranslate();
570
- });
571
- test('count(many.column)', () => {
572
- expect(modelX `count(many.column)`).toTranslate();
573
- });
574
- test('source.count()', () => {
575
- expect(modelX `source.count()`).toTranslate();
576
- });
577
- test('many.count()', () => {
578
- expect(modelX `many.count()`).toTranslate();
579
- });
580
- test('sum()', () => {
581
- expect(modelX `sum()`).toLog((0, test_translator_1.errorMessage)('Asymmetric aggregate function `sum` must be written as `path.to.field.sum()`, `path.to.join.sum(expression)`, or `sum(expression)`'));
582
- });
583
- test('sum(column)', () => {
584
- expect(modelX `sum(column)`).toTranslate();
585
- });
586
- test('sum(column * 2)', () => {
587
- expect(modelX `sum(column * 2)`).toTranslate();
588
- });
589
- test('column.sum()', () => {
590
- expect(modelX `column.sum()`).toTranslate();
591
- });
592
- test('source.sum(column)', () => {
593
- expect(modelX `source.sum(column)`).toTranslate();
594
- });
595
- test('sum(many.column)', () => {
596
- expect(modelX `sum(many.column)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.column.sum()`'));
597
- });
598
- test('source.sum(many.column)', () => {
599
- expect(modelX `source.sum(many.column)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many.column.sum()`'));
600
- });
601
- test('many.column.sum()', () => {
602
- expect(modelX `many.column.sum()`).toTranslate();
603
- });
604
- test('many.sum(many.column)', () => {
605
- expect(modelX `many.sum(many.column)`).toTranslate();
606
- });
607
- test('sum(one.column)', () => {
608
- expect(modelX `sum(one.column)`).toLog((0, test_translator_1.warningMessage)('Join path is required for this calculation; use `one.column.sum()` or `source.sum(one.column)` to get a result weighted with respect to `source`'));
609
- });
610
- test('sum(many.constant)', () => {
611
- expect(modelX `sum(many.constant)`).toTranslate();
612
- });
613
- test('source.sum(many.constant)', () => {
614
- expect(modelX `source.sum(many.constant)`).toTranslate();
615
- });
616
- test('sum(nested.column)', () => {
617
- expect(modelX `sum(nested.column)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `nested.column.sum()`'));
618
- });
619
- test('nested.column.sum()', () => {
620
- expect(modelX `nested.column.sum()`).toTranslate();
621
- });
622
- test('source.sum(nested.column)', () => {
623
- expect(modelX `source.sum(nested.column)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across repeated relationship `nested`; use `nested.column.sum()`'));
624
- });
625
- test('can aggregate field defined with no join usage', () => {
626
- expect((0, test_translator_1.markSource) `
627
- ##! experimental { sql_functions }
628
- source: s is a extend {
629
- measure: c is count()
630
- dimension: f is 1
631
- }
632
- run: s -> {
633
- aggregate: v is f.sum()
634
- }
635
- `).toTranslate();
636
- });
637
- test('sum(inline.column)', () => {
638
- expect(modelX `sum(inline.column)`).toLog((0, test_translator_1.warningMessage)('Join path is required for this calculation; use `inline.column.sum()` or `source.sum(inline.column)` to get a result weighted with respect to `source`'));
639
- });
640
- test('inline.column.sum()', () => {
641
- expect(modelX `inline.column.sum()`).toTranslate();
642
- });
643
- test('source.sum(inline.column)', () => {
644
- expect(modelX `source.sum(inline.column)`).toTranslate();
645
- });
646
- test('sum(many.field)', () => {
647
- expect(modelX `sum(many.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.field.sum()`'));
648
- });
649
- test('source.sum(many.field)', () => {
650
- expect(modelX `source.sum(many.field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many.field.sum()`'));
651
- });
652
- test('many.field.sum()', () => {
653
- expect(modelX `many.field.sum()`).toTranslate();
654
- });
655
- test('many.sum(many.field)', () => {
656
- expect(modelX `many.sum(many.field)`).toTranslate();
657
- });
658
- test('sum(many.field + many.field)', () => {
659
- expect(modelX `sum(many.field + many.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.sum(many.field + many.field)`'));
660
- });
661
- test('source.sum(many.field + many.field)', () => {
662
- expect(modelX `source.sum(many.field + many.field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many.sum(many.field + many.field)`'));
663
- });
664
- test('many.field + many.field.sum()', () => {
665
- expect(modelX `many.field + many.field.sum()`).toTranslate();
666
- });
667
- test('many.sum(many.field + many.field)', () => {
668
- expect(modelX `many.sum(many.field + many.field)`).toTranslate();
669
- });
670
- test('sum(many_field)', () => {
671
- expect(modelX `sum(many_field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many_field.sum()`'));
672
- });
673
- test('source.sum(many_field)', () => {
674
- expect(modelX `source.sum(many_field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `many_field.sum()`'));
675
- });
676
- test('many_field.sum()', () => {
677
- expect(modelX `many_field.sum()`).toTranslate();
678
- });
679
- test('many.sum(many_field)', () => {
680
- expect(modelX `many.sum(many_field)`).toTranslate();
681
- });
682
- test('sum(one.many_field)', () => {
683
- expect(modelX `sum(one.many_field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `one.many_field.sum()`'));
684
- });
685
- test('source.sum(one.many_field)', () => {
686
- expect(modelX `source.sum(one.many_field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `sum` across `join_many` relationship `many`; use `one.many_field.sum()`'));
687
- });
688
- test('one.many_field.sum()', () => {
689
- expect(modelX `one.many_field.sum()`).toTranslate();
690
- });
691
- test('many.sum(one.many_field)', () => {
692
- expect(modelX `one.many.sum(one.many_field)`).toTranslate();
693
- });
694
- test('sum(many.field + one.field)', () => {
695
- expect(modelX `sum(many.field + one.field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
696
- });
697
- test('source.sum(many.field + one.field)', () => {
698
- expect(modelX `source.sum(many.field + one.field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
699
- });
700
- test('many.sum(many.field + one.field)', () => {
701
- expect(modelX `many.sum(many.field + one.field)`).toTranslate();
702
- });
703
- test('many_one_field.sum()', () => {
704
- expect(modelX `many_one_field.sum()`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
705
- });
706
- test('sum(many_one_field)', () => {
707
- expect(modelX `sum(many_one_field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
708
- });
709
- test('source.sum(many_one_field)', () => {
710
- expect(modelX `source.sum(many_one_field)`).toLog((0, test_translator_1.errorMessage)('Aggregated dimensional expression contains multiple join paths; rewrite, for example `sum(first_join.field + second_join.field)` as `first_join.field.sum() + second_join.field.sum()`'));
711
- });
712
- test('many.sum(many_one_field)', () => {
713
- expect(modelX `many.sum(many_one_field)`).toTranslate();
714
- });
715
- test('sum(many.one.field)', () => {
716
- expect(modelX `sum(many.one.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.one.field.sum()` or `many.sum(many.one.field)` to get a result weighted with respect to `many`'));
717
- });
718
- test('sum(many.one.one.field)', () => {
719
- expect(modelX `sum(many.one.one.field)`).toLog((0, test_translator_1.errorMessage)('Join path is required for this calculation; use `many.one.one.field.sum()` or `many.sum(many.one.one.field)` to get a result weighted with respect to `many`'));
720
- });
721
- test('many.avg(field)', () => {
722
- expect(modelX `many.avg(field)`).toTranslate();
723
- });
724
- test('one.avg(field)', () => {
725
- expect(modelX `one.avg(field)`).toTranslate();
726
- });
727
- test('cross.avg(field)', () => {
728
- expect(modelX `cross.avg(field)`).toLog((0, test_translator_1.errorMessage)('Cannot compute `avg` across `join_cross` relationship `cross`; use `field.avg()`'));
729
- });
730
- test('cross.avg(cross.field)', () => {
731
- expect(modelX `cross.avg(cross.field)`).toTranslate();
732
- });
733
- test('one.column.sum()', () => {
734
- expect(modelX `one.column.sum()`).toTranslate();
735
- });
736
- test('one.sum(one.column)', () => {
737
- expect(modelX `one.sum(one.column)`).toTranslate();
738
- });
739
- test('source.sum(one.column)', () => {
740
- expect(modelX `source.sum(one.column)`).toTranslate();
741
- });
742
- test('sum(one.column + one.column)', () => {
743
- expect(modelX `sum(one.column + one.column)`).toLog((0, test_translator_1.warningMessage)('Join path is required for this calculation; use `one.sum(one.column + one.column)` or `source.sum(one.column + one.column)` to get a result weighted with respect to `source`'));
744
- });
745
- test('one.sum(one.column + one.column)', () => {
746
- expect(modelX `one.sum(one.column + one.column)`).toTranslate();
747
- });
748
- test('source.sum(one.column + one.column)', () => {
749
- expect(modelX `source.sum(one.column + one.column)`).toTranslate();
750
- });
751
- test('lag(sum(output))', () => {
752
- expect((0, test_translator_1.model) `
753
- ##! m4warnings=warn
754
- run: a -> {
755
- group_by: output is 1
756
- calculate: bar is lag(sum(output))
757
- }`).toLog((0, test_translator_1.errorMessage)("'output' is not defined"));
758
- });
759
- });
760
- describe('case statements', () => {
761
- test('full', () => {
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
- else 'random'
767
- end
768
- `;
769
- expect(e).toLog((0, test_translator_1.warning)('sql-case'));
770
- expect(e).compilesTo('{case when {ai = 42} then {"the answer"} when {ai = 54} then {"the questionable answer"} else {"random"}}');
771
- });
772
- test('with value', () => {
773
- const e = (0, test_translator_1.expr) `
774
- case ai
775
- when 42 then 'the answer'
776
- when 54 then 'the questionable answer'
777
- else 'random'
778
- end
779
- `;
780
- expect(e).toLog((0, test_translator_1.warning)('sql-case'));
781
- expect(e).compilesTo('{case ai when 42 then {"the answer"} when 54 then {"the questionable answer"} else {"random"}}');
782
- });
783
- test('no else', () => {
784
- const e = (0, test_translator_1.expr) `
785
- case
786
- when ai = 42 then 'the answer'
787
- when ai = 54 then 'the questionable answer'
788
- end
789
- `;
790
- expect(e).toLog((0, test_translator_1.warning)('sql-case'));
791
- expect(e).compilesTo('{case when {ai = 42} then {"the answer"} when {ai = 54} then {"the questionable answer"}}');
792
- });
793
- test('wrong then type', () => {
794
- expect((0, test_translator_1.expr) `
795
- case
796
- when ai = 42 then 'the answer'
797
- when ai = 54 then 7
798
- end
799
- `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-then-type-does-not-match', {
800
- thenType: 'number',
801
- returnType: 'string',
802
- }));
803
- });
804
- test('wrong when type', () => {
805
- expect((0, test_translator_1.expr) `
806
- case ai
807
- when 42 then 'the answer'
808
- when 'forty-two' then 'the answer but string'
809
- end
810
- `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-when-type-does-not-match', {
811
- whenType: 'string',
812
- valueType: 'number',
813
- }));
814
- });
815
- test('wrong else type', () => {
816
- expect((0, test_translator_1.expr) `
817
- case
818
- when ai = 42 then 'the answer'
819
- else @2020
820
- end
821
- `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-else-type-does-not-match', {
822
- elseType: 'date',
823
- returnType: 'string',
824
- }));
825
- });
826
- test('null then type okay second', () => {
827
- expect((0, test_translator_1.expr) `
828
- case
829
- when ai = 42 then 'the answer'
830
- when ai = 54 then null
831
- end
832
- `).toLog((0, test_translator_1.warning)('sql-case'));
833
- });
834
- test('null then type okay first', () => {
835
- expect((0, test_translator_1.expr) `
836
- case
837
- when ai = 54 then null
838
- when ai = 42 then 'the answer'
839
- end
840
- `).toLog((0, test_translator_1.warning)('sql-case'));
841
- });
842
- test('null else type okay', () => {
843
- expect((0, test_translator_1.expr) `
844
- case
845
- when ai = 42 then 'the answer'
846
- else null
847
- end
848
- `).toLog((0, test_translator_1.warning)('sql-case'));
849
- });
850
- test('null then type before else okay', () => {
851
- expect((0, test_translator_1.expr) `
852
- case
853
- when ai = 42 then null
854
- else 'not the answer'
855
- end
856
- `).toLog((0, test_translator_1.warning)('sql-case'));
857
- });
858
- test('non boolean when', () => {
859
- expect((0, test_translator_1.expr) `
860
- case when ai then null end
861
- `).toLog((0, test_translator_1.warning)('sql-case'), (0, test_translator_1.error)('case-when-must-be-boolean'));
862
- });
863
- test('type of null then second', () => {
864
- expect(`
865
- case
866
- when ai = 42 then 'the answer'
867
- when ai = 54 then null
868
- end
869
- `).toReturnType('string');
870
- });
871
- test('type of null then first', () => {
872
- expect(`
873
- case
874
- when ai = 54 then null
875
- when ai = 42 then 'the answer'
876
- end
877
- `).toReturnType('string');
878
- });
879
- test('type of null else', () => {
880
- expect(`
881
- case
882
- when ai = 42 then 'the answer'
883
- else null
884
- end
885
- `).toReturnType('string');
886
- });
887
- test('type of null then type before else', () => {
888
- expect(`
889
- case
890
- when ai = 42 then null
891
- else 'not the answer'
892
- end
893
- `).toReturnType('string');
894
- });
895
- test('replacement for full case', () => {
896
- const e = (0, test_translator_1.expr) `case
897
- when ai = 42 then 'the answer'
898
- when ai = 54 then 'the questionable answer'
899
- else 'random'
900
- end`;
901
- e.translator.translate();
902
- expect(e.translator.logger.getLog()[0].replacement).toBe("pick 'the answer' when ai = 42 pick 'the questionable answer' when ai = 54 else 'random'");
903
- });
904
- test('replacement for case with no else', () => {
905
- const e = (0, test_translator_1.expr) `case
906
- when ai = 42 then 'the answer'
907
- when ai = 54 then 'the questionable answer'
908
- end`;
909
- e.translator.translate();
910
- expect(e.translator.logger.getLog()[0].replacement).toBe("pick 'the answer' when ai = 42 pick 'the questionable answer' when ai = 54 else null");
911
- });
912
- test('replacement for case with value', () => {
913
- const e = (0, test_translator_1.expr) `case ai
914
- when 42 then 'a'
915
- when 54 then 'b'
916
- end`;
917
- e.translator.translate();
918
- expect(e.translator.logger.getLog()[0].replacement).toBe("ai ? pick 'a' when 42 pick 'b' when 54 else null");
919
- });
920
- test('interaction with pick', () => {
921
- expect((0, test_translator_1.expr) `
922
- pick case when true then 'hooray' end when true else null
923
- `).toLog((0, test_translator_1.warning)('sql-case'));
924
- });
925
- });
926
- describe('pick statements', () => {
927
- test('full', () => {
928
- expect((0, test_translator_1.expr) `
929
- pick 'the answer' when ai = 42
930
- pick 'the questionable answer' when ai = 54
931
- else 'random'
932
- `).toTranslate();
933
- });
934
- test('applied', () => {
935
- expect((0, test_translator_1.expr) `
936
- astr ?
937
- pick 'the answer' when = '42'
938
- pick 'the questionable answer' when = '54'
939
- else 'random'
940
- `).toTranslate();
941
- });
942
- test('filtering', () => {
943
- expect((0, test_translator_1.expr) `astr ? pick 'missing value' when NULL`).toTranslate();
944
- });
945
- test('null branch with else', () => {
946
- expect("astr ? pick null when = '42' else 3").toReturnType('number');
947
- });
948
- test('null branch no else', () => {
949
- expect("astr ? pick null when = '42'").toReturnType('string');
950
- });
951
- test('null branch no apply', () => {
952
- expect('pick null when 1 = 1 else 3').toReturnType('number');
953
- });
954
- test('tiering', () => {
955
- expect((0, test_translator_1.expr) `
956
- ai ?
957
- pick 1 when < 10
958
- pick 10 when < 100
959
- pick 100 when < 1000
960
- else 10000
961
- `).toTranslate();
962
- });
963
- test('transforming', () => {
964
- expect((0, test_translator_1.expr) `
965
- ai ?
966
- pick 'small' when < 10
967
- pick 'medium' when < 100
968
- else 'large'
969
- `).toTranslate();
970
- });
971
- test('when single values', () => {
972
- expect((0, test_translator_1.expr) `
973
- ai ?
974
- pick 'one' when 1
975
- else 'a lot'
976
- `).toTranslate();
977
- });
978
- test('n-ary without else', () => {
979
- return expect(`
980
- source: na is a extend { dimension: d is
981
- pick 7 when true and true
982
- }
983
- `).toLog((0, test_translator_1.error)('pick-missing-else'));
984
- });
985
- test('n-ary with mismatch when clauses', () => {
986
- expect((0, test_translator_1.markSource) `
987
- source: na is a extend { dimension: d is
988
- pick 7 when true and true
989
- pick '7' when true or true
990
- else 7
991
- }
992
- `).toLog((0, test_translator_1.error)('pick-type-does-not-match', {
993
- pickType: 'string',
994
- returnType: 'number',
995
- }));
996
- });
997
- test('n-ary with mismatched else clause', () => {
998
- expect((0, test_translator_1.markSource) `
999
- source: na is a extend { dimension: d is
1000
- pick 7 when true and true
1001
- else '7'
1002
- }
1003
- `).toLog((0, test_translator_1.error)('pick-else-type-does-not-match', {
1004
- elseType: 'string',
1005
- returnType: 'number',
1006
- }));
1007
- });
1008
- test('applied else mismatch', () => {
1009
- expect((0, test_translator_1.markSource) `
1010
- source: na is a extend { dimension: d is
1011
- 7 ? pick 7 when 7 else 'not seven'
1012
- }
1013
- `).toLog((0, test_translator_1.error)('pick-else-type-does-not-match', {
1014
- elseType: 'string',
1015
- returnType: 'number',
1016
- }));
1017
- });
1018
- test('applied default mismatch', () => {
1019
- expect((0, test_translator_1.markSource) `
1020
- source: na is a extend { dimension: d is
1021
- 7 ? pick 'seven' when 7
1022
- }
1023
- `).toLog((0, test_translator_1.error)('pick-default-type-does-not-match', {
1024
- defaultType: 'number',
1025
- returnType: 'string',
1026
- }));
1027
- });
1028
- test('applied when mismatch', () => {
1029
- expect((0, test_translator_1.markSource) `
1030
- source: na is a extend { dimension: d is
1031
- 7 ? pick 'seven' when 7 pick 6 when 6
1032
- }
1033
- `).toLog((0, test_translator_1.error)('pick-type-does-not-match', {
1034
- pickType: 'number',
1035
- returnType: 'string',
1036
- }));
1037
- });
1038
- });
1039
- test('paren and applied div', () => {
1040
- expect('1+(3/4)').compilesTo('{1 + ({3 / 4})}');
1041
- });
1042
- test.each([
1043
- ['ats', 'timestamp'],
1044
- ['ad', 'date'],
1045
- ['ai', 'number'],
1046
- ['astr', 'string'],
1047
- ['abool', 'boolean'],
1048
- ])('Can compare field %s (type %s) to NULL', (name, _datatype) => {
1049
- expect((0, test_translator_1.expr) `${name} IS NULL`).toTranslate();
1050
- });
1051
- });
1052
- describe('alternations as in', () => {
1053
- test('a=b|c', () => {
1054
- expect('ai=1|2').compilesTo('{ai in {1,2}}');
1055
- });
1056
- test('a!=b|c', () => {
1057
- expect('ai!=1|2').compilesTo('{ai not in {1,2}}');
1058
- });
1059
- test('a=(b|c)', () => {
1060
- expect('ai=(1|2)').compilesTo('{ai in {1,2}}');
1061
- });
1062
- test('a?b|c', () => {
1063
- expect('ai?1|2').compilesTo('{ai in {1,2}}');
1064
- });
1065
- test('a=(b)|c', () => {
1066
- expect('ai=(1)|2').compilesTo('{ai in {1,2}}');
1067
- });
1068
- test('a=b|c|d', () => {
1069
- expect('ai=1|2|3').compilesTo('{ai in {1,2,3}}');
1070
- });
1071
- test('a=(b|c)|d', () => {
1072
- expect('ai=(1|2)|3').compilesTo('{ai in {1,2,3}}');
1073
- });
1074
- test('a=b|(c|d)', () => {
1075
- expect('ai=1|(2|3)').compilesTo('{ai in {1,2,3}}');
1076
- });
1077
- test('a=b|c&d', () => {
1078
- expect('ai=1|2&3').compilesTo('{{ai = 1} or {{ai = 2} and {ai = 3}}}');
1079
- });
1080
- test('a=b|>d', () => {
1081
- expect('ai=1|>2').compilesTo('{{ai = 1} or {ai > 2}}');
1082
- expect((0, test_translator_1.expr) `ai=1|>2`).toLog((0, test_translator_1.warningMessage)('Only | seperated values are legal when used with = operator'));
1083
- });
1084
- test('a ? (= (b | c))', () => {
1085
- expect('ai ? (= (1 | 2))').compilesTo('{ai in {1,2}}');
1086
- });
1087
- test.skip('a ? (( =1) | 2)', () => {
1088
- // Current grammar doesn't allow a partial on the LHS of an orbar
1089
- // mtoy todo turn this test on or delete it when we fix the grammar
1090
- expect('ai ? (( =1) | 2)').compilesTo('{{a1 = 1} or {ai = 2}}');
1091
- });
1092
- test('legacy in', () => {
1093
- const inExpr = (0, test_translator_1.expr) `ai in (1,2,3)`;
1094
- expect(inExpr).compilesTo('{ai in {1,2,3}}');
1095
- expect(inExpr).toLog((0, test_translator_1.warningMessage)('Use = (a|b|c) instead of IN (a,b,c)'));
1096
- });
1097
- });
1098
- describe('rigor around ? and =', () => { });
1099
- describe('sql native fields in schema', () => {
1100
- test('sql native reference in result allowed', () => {
1101
- const uModel = new test_translator_1.TestTranslator('run: a->{ group_by: aun }');
1102
- expect(uModel).toTranslate();
1103
- });
1104
- test('sql native reference can be compared to NULL', () => {
1105
- const uModel = new test_translator_1.TestTranslator('run: a->{ where: aun is not null; select: * }');
1106
- expect(uModel).toTranslate();
1107
- });
1108
- test('flag unsupported equality', () => {
1109
- // because we don't know if the two unsupported types are comparable
1110
- const uModel = new test_translator_1.TestTranslator('run: ab->{ where: aun = b.aun select: * }');
1111
- expect(uModel).toLog((0, test_translator_1.error)('sql-native-not-allowed-in-expression', { rawType: undefined }));
1112
- });
1113
- test('flag unsupported compare', () => {
1114
- // because we don't know if the two unsupported types are comparable
1115
- const uModel = new test_translator_1.TestTranslator('run: ab->{ where: aun > b.aun select: * }');
1116
- expect(uModel).toLog((0, test_translator_1.error)('sql-native-not-allowed-in-expression', { rawType: undefined }));
1117
- });
1118
- test('allow unsupported equality when raw types match', () => {
1119
- const uModel = new test_translator_1.TestTranslator('run: ab->{ where: aweird = b.aweird select: * }');
1120
- expect(uModel).toTranslate();
1121
- });
1122
- test('flag not applied to unsupported', () => {
1123
- const uModel = new test_translator_1.TestTranslator('source: x is a extend { dimension: notUn is not aun }');
1124
- expect(uModel).toLog((0, test_translator_1.error)('sql-native-not-allowed-in-expression', { rawType: undefined }));
1125
- });
1126
- test('allow unsupported to be cast', () => {
1127
- const uModel = new test_translator_1.TestTranslator('source: x is a extend { dimension: notUn is aun::string }');
1128
- expect(uModel).toTranslate();
1129
- });
1130
- test('negative numbers are not tokens', () => {
1131
- expect((0, test_translator_1.expr) `ai-1`).toTranslate();
1132
- });
1133
- describe('sql functions', () => {
1134
- test('can aggregate a sql_ function', () => {
1135
- expect(`
1136
- ##! experimental.sql_functions
1137
- run: a -> {
1138
- aggregate: x is sum(sql_number("\${ai} * 2"))
1139
- }
1140
- `).toTranslate();
1141
- });
1142
- test('error when interpolating field that does not exist', () => {
1143
- expect(`
1144
- ##! experimental.sql_functions
1145
- run: a -> {
1146
- group_by: x is sql_number("\${asdfasdf} * 2")
1147
- }
1148
- `).toLog((0, test_translator_1.errorMessage)("Invalid interpolation: 'asdfasdf' is not defined"));
1149
- });
1150
- test('error when using sql_ function without experiment', () => {
1151
- expect(`
1152
- run: a -> {
1153
- group_by: x is sql_number("\${asdfasdf} * 2")
1154
- }
1155
- `).toLog((0, test_translator_1.errorMessage)('Cannot use sql_function `sql_number`; use `sql_functions` experiment to enable this behavior'));
1156
- });
1157
- });
1158
- describe('cast', () => {
1159
- // The "+ 1"s are there to make sure the result is of type 'number'
1160
- test('sql cast', () => {
1161
- expect((0, test_translator_1.expr) `ai::'integer' + 1`).toTranslate();
1162
- expect((0, test_translator_1.expr) `ai::"integer" + 1`).toTranslate();
1163
- expect((0, test_translator_1.expr) `ai::"""integer""" + 1`).toTranslate();
1164
- });
1165
- test('sql safe cast', () => {
1166
- expect((0, test_translator_1.expr) `astr:::'integer' + 1`).toTranslate();
1167
- expect((0, test_translator_1.expr) `astr:::"integer" + 1`).toTranslate();
1168
- expect((0, test_translator_1.expr) `astr:::"""integer""" + 1`).toTranslate();
1169
- });
1170
- test('malloy cast', () => {
1171
- expect((0, test_translator_1.expr) `astr::number + 1`).toTranslate();
1172
- });
1173
- test('malloy safe cast', () => {
1174
- expect((0, test_translator_1.expr) `astr:::number + 1`).toTranslate();
1175
- });
1176
- test('sql cast illegal type name', () => {
1177
- expect((0, test_translator_1.expr) `astr::"stuff 'n' things"`).toLog((0, test_translator_1.errorMessage)("Cast type `stuff 'n' things` is invalid for standardsql dialect"));
1178
- });
1179
- });
1180
- });
1181
- //# sourceMappingURL=expressions.spec.js.map