@malloydata/malloy 0.0.80-dev230830211713 → 0.0.80-dev230903192621

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.
@@ -23,9 +23,7 @@
23
23
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- const model_1 = require("../../model");
27
26
  const test_translator_1 = require("./test-translator");
28
- const granular_result_1 = require("../ast/types/granular-result");
29
27
  require("./parse-expects");
30
28
  describe('model statements', () => {
31
29
  describe('table method', () => {
@@ -58,2446 +56,124 @@ describe('model statements', () => {
58
56
  `).toTranslateWithWarnings("`table('connection_name:table_path')` is deprecated; use `connection_name.table('table_path')`");
59
57
  });
60
58
  });
61
- describe('source:', () => {
62
- test('table', () => {
63
- expect("source: testA is table('aTable')").toTranslate();
64
- });
65
- test('shorcut fitlered table', () => {
66
- expect("source: xA is table('aTable') {? astr ~ 'a%' }").toTranslate();
67
- });
68
- test('shorcut fitlered table m4warning', () => {
69
- expect(`
70
- ##! m4warnings
71
- source: xA is conn.table('aTable') extend {? astr ~ 'a%' }
72
- `).toTranslateWithWarnings('Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
73
- });
74
- test('fitlered table', () => {
75
- expect("source: testA is table('aTable') { where: astr ~ 'a%' }").toTranslate();
76
- });
77
- test('ref source with no refinement', () => {
78
- expect('source: testA is a').toTranslate();
79
- });
80
- test('from(query)', () => {
81
- expect('source: testA is from(a->{group_by: astr})').toTranslate();
82
- });
83
- test('refine source', () => {
84
- expect('source: aa is a { dimension: a is astr }').toTranslate();
85
- });
86
- test('source refinement preserves original', () => {
87
- const x = new test_translator_1.TestTranslator('source: na is a + { dimension: one is 1 }');
88
- expect(x).toTranslate();
89
- const a = x.getSourceDef('a');
90
- if (a) {
91
- const aFields = a.fields.map(f => f.as || f.name);
92
- expect(aFields).toContain('astr');
93
- expect(aFields).not.toContain('one');
94
- }
95
- });
96
- });
97
- describe('query:', () => {
98
- test('anonymous query', () => {
99
- expect((0, test_translator_1.markSource) `query: ${"table('aTable') -> { group_by: astr }"}`).toTranslate();
100
- });
101
- test('anonymous query', () => {
102
- expect((0, test_translator_1.markSource) `##! m4warnings
103
- query: ${"conn.table('aTable') -> { group_by: astr }"}`).toTranslateWithWarnings('Anonymous `query:` statements are deprecated, use `run:` instead');
104
- });
105
- test('run query', () => expect("run: table('aTable') -> { group_by: astr }").toTranslate());
106
- test('run query ref', () => expect(`
107
- query: foo is table('aTable') -> { group_by: astr }
108
- run: foo
109
- `).toTranslate());
110
- test('query', () => expect("query: name is table('aTable') -> { group_by: astr }").toTranslate());
111
- test('query', () => {
112
- expect("query: name is table('aTable') -> { group_by: astr }").toTranslate();
113
- });
114
- test('query from query', () => {
115
- expect(`
116
- query: q1 is ab->{ group_by: astr limit: 10 }
117
- query: q2 is ->q1
118
- `).toTranslate();
119
- });
120
- test('query with refinements from query', () => {
121
- expect(`
122
- query: q1 is ab->{ group_by: astr limit: 10 }
123
- query: q2 is ->q1 { aggregate: acount }
124
- `).toTranslate();
125
- });
126
- test('chained query operations', () => {
127
- expect(`
128
- query: ab
129
- -> { group_by: astr; aggregate: acount }
130
- -> { top: 5; where: astr ~ 'a%' group_by: astr }
131
- `).toTranslate();
132
- });
133
- test('from(query) refined into query', () => {
134
- expect('query: from(ab -> {group_by: astr}) { dimension: bigstr is upper(astr) } -> { group_by: bigstr }').toTranslate();
135
- });
136
- test('query with shortcut filtered turtle', () => {
137
- expect("query: allA is ab->aturtle {? astr ~ 'a%' }").toTranslate();
138
- });
139
- test('query with shortcut filtered turtle m4warning', () => {
140
- expect(`
141
- ##! m4warnings
142
- query: allA is ab->aturtle refine {? astr ~ 'a%' }
143
- `).toTranslateWithWarnings('Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
144
- });
145
- test('query with filtered turtle', () => {
146
- expect("query: allA is ab->aturtle { where: astr ~ 'a%' }").toTranslate();
147
- });
148
- test('nest: in group_by:', () => {
149
- expect(`
150
- query: ab -> {
151
- group_by: astr;
152
- nest: nested_count is {
153
- aggregate: acount
154
- }
155
- }
156
- `).toTranslate();
157
- });
158
- test('reduce pipe project', () => {
159
- expect(`
160
- query: a -> { aggregate: f is count() } -> { project: f2 is f + 1 }
161
- `).toTranslate();
162
- });
163
- test('refine and extend query', () => {
164
- expect(`
165
- query: a_by_str is a -> { group_by: astr }
166
- query: -> a_by_str { aggregate: str_count is count() }
167
- `).toTranslate();
168
- });
169
- test('query refinement preserves original', () => {
170
- const x = new test_translator_1.TestTranslator(`
171
- query: q is a -> { aggregate: acount is count() }
172
- query: nq is -> q + { group_by: astr }
173
- `);
174
- expect(x).toTranslate();
175
- const q = x.getQuery('q');
176
- expect(q).toBeDefined();
177
- if (q) {
178
- const qFields = q.pipeline[0].fields;
179
- expect(qFields.length).toBe(1);
180
- }
181
- });
182
- test('query composition preserves original', () => {
183
- const x = new test_translator_1.TestTranslator(`
184
- query: q is ab -> { aggregate: acount }
185
- query: nq is -> q -> { project: * }
186
- `);
187
- expect(x).toTranslate();
188
- const q = x.getQuery('q');
189
- expect(q).toBeDefined();
190
- if (q) {
191
- expect(q.pipeline.length).toBe(1);
192
- }
193
- });
194
- test('all ungroup with args', () => {
195
- expect(`
196
- query: a -> {
197
- group_by: astr
198
- nest: by_int is {
199
- group_by: ai
200
- aggregate: bi_count is all(count(), ai)
201
- }
202
- }
203
- `).toTranslate();
204
- });
205
- test('all ungroup checks args', () => {
206
- expect(`
207
- query: a -> {
208
- group_by: astr
209
- nest: by_int is {
210
- group_by: ai
211
- aggregate: bi_count is all(count(), afloat)
212
- }
213
- }
214
- `).translationToFailWith("all() 'afloat' is missing from query output");
215
- });
216
- test('exclude ungroup with args', () => {
217
- expect(`
218
- query: a -> {
219
- group_by: aa is 'a'
220
- nest: by_b is {
221
- group_by: bb is 'b'
222
- nest: by_c is {
223
- group_by: cc is 'c'
224
- aggregate: bb_count is exclude(count(), aa, cc)
225
- }
226
- }
227
- }
228
- `).toTranslate();
229
- });
230
- test('exclude ungroup checks args', () => {
231
- expect(`
232
- query: a -> {
233
- group_by: aa is 'a'
234
- nest: by_b is {
235
- group_by: bb is 'b'
236
- nest: by_c is {
237
- group_by: cc is 'c'
238
- aggregate: bb_count is exclude(count(), aaa, cc)
239
- }
240
- }
241
- }
242
- `).translationToFailWith("exclude() 'aaa' is missing from query output");
243
- });
244
- test('exclude problem revealed by production models', () => {
245
- expect(`
246
- source: carriers is table('malloytest.carriers') {
247
- primary_key: code
248
- }
249
- source: flights is table('malloytest.flights') {
250
- primary_key: id2
251
- join_one: carriers with carrier
252
-
253
- query: carrier_overview is {
254
- group_by: carrier_name is carriers.nickname
255
- nest: top_destinations is {
256
- group_by: destination
257
- aggregate:
258
- flights_to_dest is exclude(count(), carrier_name)*100
259
- }
260
- }
261
- }
262
- `).toTranslate();
263
- });
264
- describe('query operation typechecking', () => {
265
- describe('field declarations', () => {
266
- test('cannot use aggregate in group_by', () => {
267
- expect('query: a -> { group_by: s is count()}').translationToFailWith('Cannot use an aggregate field in a group_by operation, did you mean to use an aggregate operation instead?');
268
- });
269
- test('cannot use ungrouped_aggregate in group_by', () => {
270
- expect('query: a -> { group_by: s is all(count())}').translationToFailWith('Cannot use an aggregate field in a group_by operation, did you mean to use an aggregate operation instead?');
271
- });
272
- test('cannot use analytic in group_by', () => {
273
- expect('query: a -> { group_by: s is row_number()}').translationToFailWith('Cannot use an analytic field in a group_by operation, did you mean to use a calculate operation instead?');
274
- });
275
- test('cannot use aggregate in dimension', () => {
276
- expect('source: a1 is a { dimension: s is count()}').translationToFailWith('Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?');
277
- });
278
- test('cannot use ungrouped_aggregate in dimension', () => {
279
- expect('source: a1 is a { dimension: s is all(count())}').translationToFailWith('Cannot use an aggregate field in a dimension declaration, did you mean to use a measure declaration instead?');
280
- });
281
- test('cannot use analytic in dimension', () => {
282
- expect('source: a1 is a { dimension: s is row_number()}').translationToFailWith('Cannot use an analytic field in a dimension declaration');
283
- });
284
- test('cannot use scalar in measure', () => {
285
- expect('source: a1 is a { measure: s is 1}').translationToFailWith('Cannot use a scalar field in a measure declaration, did you mean to use a dimension declaration instead?');
286
- });
287
- test('cannot use analytic in measure', () => {
288
- expect('source: a1 is a { measure: s is lag(count())}').translationToFailWith('Cannot use an analytic field in a measure declaration');
289
- });
290
- test('cannot use scalar in aggregate', () => {
291
- expect('query: a -> { aggregate: s is 1}').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or project operation instead?');
292
- });
293
- test('cannot use analytic in aggregate', () => {
294
- expect('query: a -> { aggregate: s is lag(count())}').translationToFailWith('Cannot use an analytic field in an aggregate operation, did you mean to use a calculate operation instead?');
295
- });
296
- test('cannot use scalar in calculate', () => {
297
- expect('query: a -> { group_by: a is 1; calculate: s is 1 }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or project operation instead?');
298
- });
299
- test('cannot use aggregate in calculate', () => {
300
- expect('query: a -> { group_by: a is 1; calculate: s is count() }').translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
301
- });
302
- test('cannot use aggregate in project', () => {
303
- expect('query: a -> { project: s is count() }').translationToFailWith('Cannot use an aggregate field in a project operation, did you mean to use an aggregate operation instead?');
304
- });
305
- test('cannot use analytic in project', () => {
306
- expect('query: a -> { project: s is row_number() }').translationToFailWith('Cannot use an analytic field in a project operation, did you mean to use a calculate operation instead?');
307
- });
308
- test('cannot use analytic in declare', () => {
309
- expect('query: a -> { group_by: a is 1; declare: s is row_number() }').translationToFailWith('Analytic expressions can not be used in a declare block');
310
- });
311
- test('cannot use aggregate in index', () => {
312
- expect('query: a { measure: acount is count() } -> { index: acount }').translationToFailWith('Cannot use an aggregate field in an index operation');
313
- });
314
- test('can use aggregate in except', () => {
315
- expect(`
316
- source: b1 is a { measure: acount is count() }
317
- source: c1 is b1 { except: acount }
318
- `).toTranslate();
319
- });
320
- });
321
- describe('field references', () => {
322
- test('cannot use aggregate in group_by', () => {
323
- expect('query: a -> { declare: acount is count(); group_by: acount }').translationToFailWith('Cannot use an aggregate field in a group_by operation, did you mean to use an aggregate operation instead?');
324
- });
325
- test('cannot use query in group_by', () => {
326
- expect('query: a { query: q is { group_by: x is 1 } } -> { group_by: q }').translationToFailWith('Cannot use a query field in a group_by operation, did you mean to use a nest operation instead?');
327
- });
328
- test('cannot use scalar in aggregate', () => {
329
- expect('query: a -> { declare: aconst is 1; aggregate: aconst }').translationToFailWith('Cannot use a scalar field in an aggregate operation, did you mean to use a group_by or project operation instead?');
330
- });
331
- test('cannot use scalar in calculate', () => {
332
- expect('query: a -> { declare: aconst is 1; group_by: x is 1; calculate: aconst }').translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or project operation instead?');
333
- });
334
- test('cannot use aggregate in calculate', () => {
335
- expect('query: a -> { declare: acount is count(); group_by: x is 1; calculate: acount }').translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
336
- });
337
- test('cannot use query in project', () => {
338
- expect('query: a { query: q is { group_by: x is 1 } } -> { project: q }').translationToFailWith('Cannot use a query field in a project operation, did you mean to use a nest operation instead?');
339
- });
340
- test('cannot use query in index', () => {
341
- expect('query: a { query: q is { group_by: x is 1 } } -> { index: q }').translationToFailWith('Cannot use a query field in an index operation');
342
- });
343
- test('cannot use query in calculate', () => {
344
- expect('query: a { query: q is { group_by: x is 1 } } -> { group_by: x is 1; calculate: q }').translationToFailWith('Cannot use a query field in a calculate operation, did you mean to use a nest operation instead?');
345
- });
346
- test('cannot use query in aggregate', () => {
347
- expect('query: a { query: q is { group_by: x is 1 } } -> { aggregate: q }').translationToFailWith('Cannot use a query field in an aggregate operation, did you mean to use a nest operation instead?');
348
- });
349
- test('cannot use aggregate in calculate, preserved over refinement', () => {
350
- expect(`query: a1 is a -> {
351
- aggregate: c is count()
352
- }
353
- query: -> a1 {
354
- calculate: b is c
355
- }`).translationToFailWith('Cannot use an aggregate field in a calculate operation, did you mean to use an aggregate operation instead?');
356
- });
357
- test('cannot use scalar in calculate, preserved over refinement', () => {
358
- expect(`query: a1 is a -> {
359
- group_by: c is 1
360
- }
361
- query: -> a1 {
362
- calculate: b is c
363
- }`).translationToFailWith('Cannot use a scalar field in a calculate operation, did you mean to use a group_by or project operation instead?');
364
- });
365
- test('cannot use analytic in group_by, preserved over refinement', () => {
366
- expect(`query: a1 is a -> {
367
- group_by: c is 1
368
- calculate: c2 is lag(c)
369
- }
370
- query: -> a1 {
371
- group_by: b is c2
372
- }`).translationToFailWith(
373
- // c2 is not defined because group_by doesn't know to look in the output space
374
- "'c2' is not defined");
375
- });
376
- test('cannot use analytic in order_by, preserved over refinement', () => {
377
- expect(`query: a1 is a -> {
378
- group_by: c is 1
379
- calculate: c2 is lag(c)
380
- }
381
- query: -> a1 {
382
- order_by: c2
383
- }`).translationToFailWith('Illegal order by of analytic field c2');
384
- });
385
- test('cannot ungroup an ungrouped', () => {
386
- expect(`query: a1 is a -> {
387
- group_by: c is 1
388
- aggregate: c2 is all(all(sum(ai)))
389
- }`).translationToFailWith('all() expression must not already be ungrouped');
390
- });
391
- test('cannot aggregate an ungrouped', () => {
392
- expect(`query: a1 is a -> {
393
- group_by: c is 1
394
- aggregate: c2 is sum(all(sum(ai)))
395
- }`).translationToFailWith('Aggregate expression cannot be aggregate');
396
- });
397
- test('cannot aggregate an aggregate', () => {
398
- expect(`query: a1 is a -> {
399
- group_by: c is 1
400
- aggregate: c2 is sum(sum(ai))
401
- }`).translationToFailWith('Aggregate expression cannot be aggregate');
402
- });
403
- test('can use field def in group_by, preserved over refinement', () => {
404
- expect(`query: a1 is a -> {
405
- group_by: c is 1
406
- }
407
- query: -> a1 {
408
- order_by: c
409
- }`).toTranslate();
410
- });
411
- test('can use field ref in group_by, preserved over refinement', () => {
412
- expect(`query: a1 is a -> {
413
- group_by: c is astr
414
- }
415
- query: -> a1 {
416
- order_by: c
417
- }`).toTranslate();
418
- });
419
- });
420
- });
421
- describe('function typechecking', () => {
422
- test('use function correctly', () => {
423
- expect(`query: a -> {
424
- group_by: s is concat('a', 'b')
425
- }`).toTranslate();
426
- });
427
- test('function incorrect case', () => {
428
- expect(`query: a -> {
429
- group_by: s is CONCAT('a', 'b')
430
- }`).toTranslateWithWarnings("Case insensitivity for function names is deprecated, use 'concat' instead");
431
- });
432
- test('function no matching overload', () => {
433
- expect(`query: a -> {
434
- group_by: s is floor('a', 'b')
435
- }`).translationToFailWith('No matching overload for function floor(string, string)');
436
- });
437
- test('unknown function', () => {
438
- expect(`query: a -> {
439
- group_by: s is asdfasdf()
440
- }`).translationToFailWith("Unknown function 'asdfasdf'. Use 'asdfasdf!(...)' to call a SQL function directly.");
441
- });
442
- test('can select different overload', () => {
443
- expect('query: a -> { group_by: s is concat() }').toTranslate();
444
- });
445
- test('can pass different expression types', () => {
446
- expect(`query: a -> {
447
- group_by: f1 is sqrt(1)
448
- aggregate: f2 is sqrt(count())
449
- aggregate: f3 is sqrt(all(count()))
450
- calculate: f4 is sqrt(lag(f1))
451
- calculate: f5 is sqrt(lag(count()))
452
- }`).toTranslate();
453
- });
454
- test('function return type correct', () => {
455
- expect(`query: a -> {
456
- group_by: s is floor(1.2) + 1
457
- }`).toTranslate();
458
- });
459
- test('function return type incorrect', () => {
460
- expect(`query: a -> {
461
- group_by: s is floor(1.2) + 'a'
462
- }`).translationToFailWith("Non numeric('number,string') value with '+'");
463
- });
464
- test('can use output value in calculate', () => {
465
- expect(`query: a -> {
466
- group_by: x is 1
467
- calculate: s is lag(x)
468
- }`).toTranslate();
469
- });
470
- test('cannot use output value in group_by', () => {
471
- expect(`query: a -> {
472
- group_by: x is 1
473
- group_by: y is x
474
- }`).translationToFailWith("'x' is not defined");
475
- });
476
- test('lag can check that other args are constant', () => {
477
- expect(`query: a -> {
478
- group_by: x is 1
479
- calculate: s is lag(x, 1, x)
480
- }`).translationToFailWith(
481
- // TODO improve this error message
482
- "Parameter 3 ('default') of lag must be literal or constant, but received output");
483
- });
484
- test('lag can check that other args are literal', () => {
485
- expect(`query: a -> {
486
- group_by: x is 1
487
- calculate: s is lag(x, 1 + 1)
488
- }`).translationToFailWith(
489
- // TODO improve this error message
490
- "Parameter 2 ('offset') of lag must be literal, but received constant");
491
- });
492
- test('lag can check that other args are nonnull', () => {
493
- expect(`query: a -> {
494
- group_by: x is 1
495
- calculate: s is lag(x, null)
496
- }`).translationToFailWith("Parameter 2 ('offset') of lag must not be a literal null");
497
- });
498
- test('lag can use constant values for other args', () => {
499
- expect(`query: a -> {
500
- group_by: x is 1
501
- calculate: s is lag(x, 2)
502
- }`).toTranslate();
503
- });
504
- test('cannot name top level objects same as functions', () => {
505
- expect((0, test_translator_1.markSource) `query: ${'concat is a -> { group_by: x is 1 }'}`).translationToFailWith(
506
- // TODO improve this error message
507
- "'concat' is already defined, cannot redefine");
508
- });
509
- test('`now` is considered constant`', () => {
510
- expect(`query: a -> {
511
- group_by: n is now
512
- calculate: l is lag(n, 1, now)
513
- }`).toTranslate();
514
- });
515
- // TODO it might be nice to reference a field which is a constant, and be able to
516
- // use that as a constant param. Same with a literal.
517
- test('cannot use a field which is a constant in a constant param', () => {
518
- expect(`query: a -> {
519
- group_by: ai, pi is pi()
520
- calculate: l is lag(ai, 1, pi)
521
- }`).translationToFailWith("Parameter 3 ('default') of lag must be literal or constant, but received output");
522
- });
523
- // TODO we don't handle referencing a join as a field correctly in all cases today.
524
- // For now, it at least is considered type `struct` and therefore fails to parse
525
- // as a function argument.
526
- // We add <join_name>_id to the query, but it's not included in the output space
527
- test('cannot use struct in function arg', () => {
528
- expect(`query: a {join_one: b with astr } -> {
529
- group_by: b
530
- calculate: foo is lag(b)
531
- }`).translationToFailWith('No matching overload for function lag(struct)');
532
- });
533
- // TODO this doesn't work today, we're not rigorous enough with integer
534
- // subtypes. But we should probably make this typecheck properly.
535
- test.skip('cannot use float in round precision', () => {
536
- expect(`query: a -> {
537
- group_by: x is round(1.5, 1.6)
538
- }`).translationToFailWith(
539
- // TODO improve this error message
540
- "Parameter 2 ('precision') for round must be integer, received float");
541
- });
542
- test('cannot use stddev with no arguments', () => {
543
- expect(`query: a -> {
544
- aggregate: x is stddev()
545
- }`).translationToFailWith('No matching overload for function stddev()');
546
- });
547
- test('can use stddev with postfix syntax', () => {
548
- expect(`query: a -> {
549
- declare: y is 1
550
- aggregate: x is y.stddev()
551
- }`).toTranslate();
552
- });
553
- test('can use stddev with postfix syntax on join', () => {
554
- expect(`query: a -> {
555
- join_one: b with astr
556
- aggregate: x is b.ai.stddev()
557
- }`).toTranslate();
558
- });
559
- test('can use calculate with a measure', () => {
560
- expect(`query: a { measure: c is count() } -> {
561
- group_by: y is 1
562
- calculate: x is lag(c)
563
- }`).toTranslate();
564
- });
565
- test('cannot use calculate with input fields', () => {
566
- expect(`query: a -> {
567
- group_by: y is 1
568
- calculate: x is lag(ai)
569
- }`).translationToFailWith(
570
- // TODO improve this error message:
571
- // Parameter 1 ('value') of 'lag' must be a constant, an aggregate, or an expression using
572
- // only fields that appear in the query output. Received an expression which uses a field
573
- // that is not in the query output.
574
- "Parameter 1 ('value') of lag must be literal, constant or output, but received input");
575
- });
576
- test('can use calculate with aggregate field which is not in query', () => {
577
- expect(`query: a { measure: acount is count() } -> {
578
- group_by: astr
579
- calculate: pc is lag(acount)
580
- }`).toTranslate();
581
- });
582
- test('cannot use agregate as argument to agg function', () => {
583
- expect(`query: a -> {
584
- aggregate: x is stddev(count())
585
- }`).translationToFailWith("Parameter 1 ('value') of stddev must be scalar, but received aggregate");
586
- });
587
- test('cannot use calculate with no other fields', () => {
588
- expect(`query: a -> {
589
- calculate: x is row_number()
590
- }`).translationToFailWith("Can't determine query type (group_by/aggregate/nest,project,index)");
591
- });
592
- // TODO someday make it so we can order by an analytic function
593
- test('today: cannot order by analytic function', () => {
594
- expect(`query: a -> {
595
- group_by: astr
596
- calculate: row_num is row_number()
597
- order_by: row_num desc
598
- }`).translationToFailWith('Illegal order by of analytic field row_num');
599
- });
600
- test('cannot use analytic in calculate -- and preserved over refinement', () => {
601
- expect(`query: a1 is a -> {
602
- group_by: astr
603
- calculate: p is lag(astr)
604
- }
605
- query: -> a1 {
606
- calculate: p1 is lag(p)
607
- }`).translationToFailWith("Parameter 1 ('value') of lag must be scalar or aggregate, but received scalar_analytic");
608
- });
609
- test('cannot use aggregate analytic in project', () => {
610
- expect(`query: a -> {
611
- project: astr
612
- calculate: p is lag(count())
613
- }`).translationToFailWith('Cannot add aggregate analyics to project');
614
- });
615
- test('reference field in join', () => {
616
- expect(`query: a -> {
617
- join_one: b with astr
618
- group_by: b.ai
619
- }`).toTranslate();
620
- });
621
- // TODO decide whether this syntax should be legal, really...
622
- test('reference join name in group_by', () => {
623
- expect(`query: a -> {
624
- join_one: b with astr
625
- group_by: b
626
- }`).toTranslate();
627
- });
628
- test('can reference project: inline join.* field in calculate', () => {
629
- expect(`query: a -> {
630
- join_one: b with astr
631
- project: b.*
632
- calculate: s is lag(ai)
633
- }`).toTranslate();
634
- });
635
- test('can reference project: join.* field in calculate', () => {
636
- expect(`query: a { join_one: b with astr } -> {
637
- project: b.*
638
- calculate: s is lag(ai)
639
- }`).toTranslate();
640
- });
641
- test('can reference implied output entries in calculate', () => {
642
- expect(`source: aaa is a extend { dimension: big_i is ai+10 }
643
- query: a extend { join_one: aaa on astr=aaa.astr } -> {
644
- group_by: aaa.big_i
645
- aggregate: s is aaa.big_i.sum()
646
- calculate: a is lag(big_i.sum())
647
- }`).toTranslate();
648
- });
649
- });
650
- });
651
59
  test('errors on redefinition of query', () => {
652
60
  expect('query: q1 is a -> { project: * }, q1 is a -> { project: * }').translationToFailWith("'q1' is already defined, cannot redefine");
653
61
  });
654
62
  });
655
- describe('source properties', () => {
656
- test('single dimension', () => {
657
- expect('source: aa is a { dimension: x is 1 }').toTranslate();
658
- });
659
- test('multiple dimensions', () => {
63
+ describe('error handling', () => {
64
+ test('field and query with same name does not overflow', () => {
660
65
  expect(`
661
- source: aa is a {
662
- dimension:
663
- x is 1
664
- y is 2
66
+ source: flights is table('malloytest.flights') {
67
+ query: carrier is { group_by: carrier }
665
68
  }
666
- `).toTranslate();
667
- });
668
- test('single declare', () => {
669
- expect('source: aa is a { declare: x is 1 }').toTranslate();
69
+ `).translationToFailWith("Cannot redefine 'carrier'");
670
70
  });
671
- test('multiple declare', () => {
672
- expect(`
673
- source: aa is a {
674
- declare:
675
- x is 1
676
- y is 2
71
+ test('redefine source', () => {
72
+ expect((0, test_translator_1.markSource) `
73
+ source: airports is table('malloytest.airports') + {
74
+ primary_key: code
677
75
  }
678
- `).toTranslate();
679
- });
680
- test('single measure', () => {
681
- expect('source: aa is a { measure: x is count() }').toTranslate();
682
- });
683
- test('multiple measures', () => {
684
- expect(`
685
- source: aa is a {
686
- measure:
687
- x is count()
688
- y is x * x
76
+ source: airports is table('malloytest.airports') + {
77
+ primary_key: code
689
78
  }
690
- `).toTranslate();
79
+ `).translationToFailWith("Cannot redefine 'airports'");
691
80
  });
692
- test('single where', () => {
693
- expect('source: aa is a { where: ai > 10 }').toTranslate();
81
+ test('query from undefined source', () => {
82
+ expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y }`).translationToFailWith("Undefined query or source 'x'");
694
83
  });
695
- test('multiple where', () => {
696
- expect(`
697
- source: aa is a {
698
- where:
699
- ai > 10,
700
- af < 1000
701
- }
702
- `).toTranslate();
84
+ test('query with expression from undefined source', () => {
85
+ // Regression check: Once upon a time this died with an exception even
86
+ // when "query: x->{ group_by: y}" (above) generated the correct error.
87
+ expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y is z / 2 }`).translationToFailWith("Undefined query or source 'x'");
703
88
  });
704
- test('where clause can use the join namespace in source refined query', () => {
705
- expect(`
706
- source: flights is table('malloytest.flights') + {
707
- query: boo is {
708
- join_one: carriers is table('malloytest.carriers') on carrier = carriers.code
709
- where: carriers.code = 'WN' | 'AA'
710
- group_by: carriers.nickname
711
- aggregate: flight_count is count()
712
- }
713
- }`).toTranslate();
89
+ test('join reference before definition', () => {
90
+ expect((0, test_translator_1.markSource) `
91
+ source: newAB is a { join_one: newB is ${'bb'} on astring }
92
+ source: newB is b
93
+ `).translationToFailWith("Undefined query or source 'bb'");
714
94
  });
715
- describe('joins', () => {
716
- test('with', () => {
717
- expect('source: x is a { join_one: b with astr }').toTranslate();
718
- });
719
- test('with', () => {
720
- expect((0, test_translator_1.model) `source: x is a { join_one: y is b with astr }`).toTranslate();
721
- });
722
- test('with dotted ref', () => {
723
- expect((0, test_translator_1.model) `source: x is ab { join_one: xz is a with b.astr }`).toTranslate();
724
- });
725
- test('one on', () => {
726
- expect((0, test_translator_1.model) `source: x is a { join_one: b on astr = b.astr }`).toTranslate();
727
- });
728
- test('one is on', () => {
729
- expect('source: x is a { join_one: y is b on astr = y.astr }').toTranslate();
730
- });
731
- test('many on', () => {
732
- expect('source: nab is a { join_many: b on astr = b.astr }').toTranslate();
733
- });
734
- test('many is on', () => {
735
- expect('source: y is a { join_many: x is b on astr = x.astr }').toTranslate();
736
- });
737
- test('cross', () => {
738
- expect('source: nab is a { join_cross: b }').toTranslate();
739
- });
740
- test('cross is', () => {
741
- expect('source: nab is a { join_cross: xb is b }').toTranslate();
742
- });
743
- test('cross on', () => {
744
- expect('source: nab is a { join_cross: b on true}').toTranslate();
745
- });
746
- test('multiple joins', () => {
747
- expect(`
748
- source: nab is a {
749
- join_one:
750
- b with astr,
751
- br is b with astr
752
- }
753
- `).toTranslate();
754
- });
755
- test('with requires primary key', () => {
756
- expect((0, test_translator_1.markSource) `
757
- source: nb is b {
758
- join_one: ${"bb is table('aTable') with astr"}
759
- }
760
- `).translationToFailWith('join_one: Cannot use with unless source has a primary key');
761
- });
95
+ test('non-rename rename', () => {
96
+ expect('source: na is a { rename: astr is astr }').translationToFailWith("Can't rename field to itself");
762
97
  });
763
- test('primary_key', () => {
764
- expect('source: c is a { primary_key: ai }').toTranslate();
98
+ test('reference to field in its definition', () => {
99
+ expect('source: na is a { dimension: ustr is upper(ustr) } ').translationToFailWith("Circular reference to 'ustr' in definition");
765
100
  });
766
- test('rename', () => {
767
- expect('source: c is a { rename: nn is ai }').toTranslate();
101
+ test('empty model', () => {
102
+ expect('').toTranslate();
768
103
  });
769
- test('accept single', () => {
770
- const onlyAstr = new test_translator_1.TestTranslator('source: c is a { accept: astr }');
771
- expect(onlyAstr).toTranslate();
772
- const c = onlyAstr.getSourceDef('c');
773
- if (c) {
774
- expect(c.fields.length).toBe(1);
775
- }
104
+ test('one line model ', () => {
105
+ expect('\n').toTranslate();
776
106
  });
777
- test('accept multi', () => {
778
- expect('source: c is a { accept: astr, af }').toTranslate();
107
+ test('query without fields', () => {
108
+ expect('query: a -> { top: 5 }').translationToFailWith("Can't determine query type (group_by/aggregate/nest,project,index)");
779
109
  });
780
- test('except single', () => {
781
- const noAstr = new test_translator_1.TestTranslator('source: c is a { except: astr }');
782
- expect(noAstr).toTranslate();
783
- const c = noAstr.getSourceDef('c');
784
- if (c) {
785
- const foundAstr = c.fields.find(f => f.name === 'astr');
786
- expect(foundAstr).toBeUndefined();
787
- }
110
+ test('refine cannot change query type', () => {
111
+ expect('query: ab -> aturtle { project: astr }').translationToFailWith(/Not legal in grouping query/);
788
112
  });
789
- test('except multi', () => {
790
- expect('source: c is a { except: astr, af }').toTranslate();
113
+ test('undefined field ref in query', () => {
114
+ expect('query: ab -> { aggregate: xyzzy }').translationToFailWith("'xyzzy' is not defined");
791
115
  });
792
- test('explore-query', () => {
793
- expect('source: c is a {query: q is { group_by: astr } }').toTranslate();
116
+ test('query on source with errors', () => {
117
+ expect((0, test_translator_1.markSource) `
118
+ source: na is a { join_one: ${'n'} on astr }
119
+ `).translationToFailWith("Undefined source 'n'");
794
120
  });
795
- test('refined explore-query', () => {
796
- expect(`
797
- source: abNew is ab {
798
- query: for1 is aturtle {? ai = 1 }
799
- }
800
- `).toTranslate();
121
+ test('detect duplicate output field names', () => {
122
+ expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'astr'} }`).translationToFailWith("Output already has a field named 'astr'");
801
123
  });
802
- test('refined explore-query m4warning', () => {
803
- expect(`
804
- ##! m4warnings
805
- source: abNew is ab extend {
806
- query: for1 is aturtle refine {? ai = 1 }
807
- }
808
- `).toTranslateWithWarnings('Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
124
+ test('detect join tail overlap existing ref', () => {
125
+ expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'b.astr'} }`).translationToFailWith("Output already has a field named 'astr'");
809
126
  });
810
- test('chained explore-query', () => {
127
+ test('undefined in expression with regex compare', () => {
811
128
  expect(`
812
- source: c is a {
813
- query: chain is {
814
- group_by: astr
815
- } -> {
816
- top: 10; order_by: astr
817
- project: *
129
+ source: c is a {
130
+ dimension: d is meaning_of_life ~ r'(forty two|fifty four)'
818
131
  }
819
- }
820
- `).toTranslate();
821
- });
822
- test('multiple explore-query', () => {
823
- expect(`
824
- source: abNew is ab {
825
- query:
826
- q1 is { group_by: astr },
827
- q2 is { group_by: ai }
828
- }
829
- `).toTranslate();
830
- });
831
- });
832
- describe('qops', () => {
833
- test('group by single', () => {
834
- expect('query: a->{ group_by: astr }').toTranslate();
835
- });
836
- test("group_by x is x'", () => {
837
- expect('query: a->{ group_by: ai is ai/2 }').toTranslate();
838
- });
839
- test('group by multiple', () => {
840
- expect('query: a->{ group_by: astr,ai }').toTranslate();
841
- });
842
- test('aggregate single', () => {
843
- expect('query: a->{ aggregate: num is count() }').toTranslate();
844
- });
845
- test('calculate in reduce', () => {
846
- expect('query: a->{ group_by: astr, ai calculate: num is lag(ai) }').toTranslate();
847
- });
848
- test('calculate in project', () => {
849
- expect('query: a->{ project: astr, ai calculate: num is lag(ai) }').toTranslate();
850
- });
851
- test('aggregate reference', () => {
852
- var _a;
853
- const doc = (0, test_translator_1.model) `query: a->{ aggregate: ai.sum() }`;
854
- expect(doc).toTranslate();
855
- const q = doc.translator.getQuery(0);
856
- expect(q).toBeDefined();
857
- const ai = (_a = q === null || q === void 0 ? void 0 : q.pipeline[0]) === null || _a === void 0 ? void 0 : _a.fields[0];
858
- expect(ai).toMatchObject({
859
- name: 'ai',
860
- type: 'number',
861
- expressionType: 'aggregate',
862
- });
863
- });
864
- test('timeunit reference', () => {
865
- var _a;
866
- const doc = (0, test_translator_1.model) `query: a->{ group_by: ats.day }`;
867
- expect(doc).toTranslate();
868
- const q = doc.translator.getQuery(0);
869
- expect(q).toBeDefined();
870
- const ats = (_a = q === null || q === void 0 ? void 0 : q.pipeline[0]) === null || _a === void 0 ? void 0 : _a.fields[0];
871
- expect(ats).toMatchObject({ name: 'ats', type: 'timestamp' });
132
+ `).translationToFailWith("'meaning_of_life' is not defined");
872
133
  });
873
- test('aggregate multiple', () => {
134
+ test('detect output collision on join references', () => {
874
135
  expect(`
875
- query: a->{
876
- aggregate: num is count(), total is sum(ai)
136
+ query: ab -> {
137
+ group_by: astr, b.astr
877
138
  }
878
- `).toTranslate();
879
- });
880
- test('project ref', () => {
881
- expect('query:ab->{ project: b.astr }').toTranslate();
882
- });
883
- test('project *', () => {
884
- expect('query:ab->{ project: * }').toTranslate();
885
- });
886
- test('project def', () => {
887
- expect('query:ab->{ project: one is 1 }').toTranslate();
139
+ `).translationToFailWith("Output already has a field named 'astr'");
888
140
  });
889
- test('project multiple', () => {
141
+ test('rejoin a query is renamed', () => {
890
142
  expect(`
891
- query: a->{
892
- project: one is 1, astr
893
- }
894
- `).toTranslate();
895
- });
896
- test('index single', () => {
897
- expect('query:a->{index: astr}').toTranslate();
898
- });
899
- test('index path', () => {
900
- expect('query:ab->{index: ab.astr}').toTranslate();
901
- });
902
- test('index unique on path', () => {
903
- expect('query:ab->{index: b.astr, ab.astr}').toTranslate();
904
- });
905
- test('index join.*', () => {
906
- expect('query:ab->{index: ab.*}').toTranslate();
907
- });
908
- test('index multiple', () => {
909
- const model = new test_translator_1.TestTranslator('query:a->{index: af, astr}');
910
- expect(model).toTranslate();
911
- const q = model.getQuery(0);
912
- expect(q).toBeDefined();
913
- if (q) {
914
- const index = q.pipeline[0];
915
- expect(index.type).toBe('index');
916
- expect(index.fields).toEqual(['af', 'astr']);
917
- }
918
- });
919
- test('index star', () => {
920
- const model = new test_translator_1.TestTranslator('query:a->{index: *, astr}');
921
- expect(model).toTranslate();
922
- const q = model.getQuery(0);
923
- expect(q).toBeDefined();
924
- if (q) {
925
- const index = q.pipeline[0];
926
- expect(index.type).toBe('index');
927
- expect(index.fields).toEqual(['*', 'astr']);
928
- }
929
- });
930
- test('index by', () => {
931
- expect('query:a->{index: * by ai}').toTranslate();
932
- });
933
- test('index sampled', () => {
934
- expect('query:a->{index: *; sample: true}').toTranslate();
935
- });
936
- test('index unsampled', () => {
937
- expect('query:a->{index: *; sample: false}').toTranslate();
938
- });
939
- test('index sample-percent', () => {
940
- const model = new test_translator_1.TestTranslator('query:a->{index: *; sample: 42%}');
941
- expect(model).toTranslate();
942
- const q = model.getQuery(0);
943
- expect(q).toBeDefined();
944
- if (q) {
945
- const index = q.pipeline[0];
946
- expect(index.type).toBe('index');
947
- if (index.type === 'index') {
948
- expect(index.sample).toEqual({ percent: 42 });
949
- }
143
+ source: querySrc is from(
144
+ table('malloytest.flights')->{
145
+ group_by: origin
146
+ nest: nested is { group_by: destination }
950
147
  }
951
- });
952
- test('index sample-rows', () => {
953
- expect('query:a->{index: *; sample: 100000}').toTranslate();
954
- });
955
- test('top N', () => {
956
- expect('query: a->{ top: 5; group_by: astr }').toTranslate();
957
- });
958
- test('top N by field', () => {
959
- expect('query: a->{top: 5 by astr; group_by: astr}').toTranslate();
960
- });
961
- test('top N by expression', () => {
962
- expect('query: ab->{top: 5 by ai + 1; group_by: ai}').toTranslate();
963
- });
964
- test('top N by field must be in the output space', () => expect('query: a->{top: 5 by af; group_by: astr}').translationToFailWith('Unknown field af in output space'));
965
- test('limit N', () => {
966
- expect('query: a->{ limit: 5; group_by: astr }').toTranslate();
967
- });
968
- test('order by', () => {
969
- expect('query: a->{ order_by: astr; group_by: astr }').toTranslate();
970
- });
971
- test('order by preserved over refinement', () => {
972
- expect(`
973
- query: a1 is a -> { group_by: astr }
974
- query: -> a1 { order_by: astr }
975
- `).toTranslate();
976
- });
977
- test('order by must be in the output space', () => expect('query: a -> { order_by: af; group_by: astr }').translationToFailWith('Unknown field af in output space'));
978
- test('order by asc', () => {
979
- expect('query: a->{ order_by: astr asc; group_by: astr }').toTranslate();
980
- });
981
- test('order by desc', () => {
982
- expect('query: a->{ order_by: astr desc; group_by: astr }').toTranslate();
983
- });
984
- test('order by N', () => {
985
- expect('query: a->{ order_by: 1 asc; group_by: astr }').toTranslate();
986
- });
987
- test('order by multiple', () => {
988
- expect(`
989
- query: a->{
990
- order_by: 1 asc, af desc
991
- group_by: astr, af
148
+ )
149
+
150
+ source: refineQuerySrc is querySrc {
151
+ join_one: rejoin is querySrc on 7=8
152
+ query: broken is {
153
+ group_by: rejoin.nested.destination
992
154
  }
155
+ }
993
156
  `).toTranslate();
994
157
  });
995
- test('where single', () => {
996
- expect('query:a->{ group_by: astr; where: af > 10 }').toTranslate();
997
- });
998
- test('having single', () => {
999
- expect('query:ab->{ aggregate: acount; group_by: astr; having: acount > 10 }').toTranslate();
1000
- });
1001
- test('compound having still works', () => {
1002
- expect('query:ab->{ aggregate: acount; having: acount > 10 and acount < 100 }').toTranslate();
158
+ test('popping out of embedding when not embedded', () => {
159
+ expect('}%').translationToFailWith(/extraneous input '}%' expecting/);
1003
160
  });
1004
- test('compound aggregate still works', () => {
1005
- expect('query:ab->{ aggregate: thing is acount > 10 and acount < 100 }').toTranslate();
1006
- });
1007
- test('where multiple', () => {
1008
- expect("query:a->{ group_by: astr; where: af > 10,astr~'a%' }").toTranslate();
1009
- });
1010
- test('filters preserve source formatting in code:', () => {
1011
- const model = new test_translator_1.TestTranslator("source: notb is a + { where: astr != 'b' }");
1012
- expect(model).toTranslate();
1013
- const notb = model.getSourceDef('notb');
1014
- expect(notb).toBeDefined();
1015
- if (notb) {
1016
- const f = notb.filterList;
1017
- expect(f).toBeDefined();
1018
- if (f) {
1019
- expect(f[0].code).toBe("astr != 'b'");
1020
- }
1021
- }
1022
- });
1023
- test('field expressions preserve source formatting in code:', () => {
1024
- const model = new test_translator_1.TestTranslator('source: notb is a + { dimension: d is 1 + 2 }');
1025
- expect(model).toTranslate();
1026
- const notb = model.getSourceDef('notb');
1027
- expect(notb).toBeDefined();
1028
- if (notb) {
1029
- const d = notb.fields.find(f => f.as || f.name === 'd');
1030
- expect(d).toBeDefined();
1031
- expect(d === null || d === void 0 ? void 0 : d.type).toBe('number');
1032
- if ((d === null || d === void 0 ? void 0 : d.type) === 'number') {
1033
- expect(d.code).toBe('1 + 2');
1034
- }
1035
- }
1036
- });
1037
- test('nest single', () => {
1038
- expect(`
1039
- query: a->{
1040
- group_by: ai
1041
- nest: nestbystr is { group_by: astr; aggregate: N is count() }
1042
- }
1043
- `).toTranslate();
1044
- });
1045
- test('nest multiple', () => expect(`
1046
- query: a->{
1047
- group_by: ai
1048
- nest:
1049
- nestbystr is { group_by: astr; aggregate: N is count() },
1050
- renest is { group_by: astr; aggregate: N is count() }
1051
- }
1052
- `).toTranslate());
1053
- test('nest ref', () => expect('query: ab->{group_by: ai; nest: aturtle}').toTranslate());
1054
- describe('extend block', () => {
1055
- test('works with dimension', () => {
1056
- expect('query: a -> { extend: { dimension: x is 1 }; group_by: x }').toTranslate();
1057
- });
1058
- test('works with measure', () => {
1059
- expect('query: a -> { extend: { measure: x is count() }; aggregate: x }').toTranslate();
1060
- });
1061
- test('works with join_one', () => {
1062
- expect('query: a -> { extend: { join_one: bb is b on bb.astr = astr }; group_by: bb.astr }').toTranslate();
1063
- });
1064
- test('works with join_many', () => {
1065
- expect('query: a -> { extend: { join_many: b on astr = b.astr }; group_by: b.astr }').toTranslate();
1066
- });
1067
- test('works with join_cross', () => {
1068
- expect('query: a -> { extend: { join_cross: b on true }; group_by: b.astr }').toTranslate();
1069
- });
1070
- test('works with multiple in one block', () => {
1071
- expect('query: a -> { extend: { dimension: x is 1, y is 2 }; group_by: x, y }').toTranslate();
1072
- });
1073
- test('works with multiple blocks', () => {
1074
- expect('query: a -> { extend: { dimension: x is 1; dimension: y is 2; measure: c is count() }; group_by: x, y; aggregate: c }').toTranslate();
1075
- });
1076
- });
1077
- describe('declare/query join warnings', () => {
1078
- test('declare warning in query', () => {
1079
- expect((0, test_translator_1.markSource) `##! m4warnings
1080
- run: a -> { ${'declare: x is 1'}; group_by: x }`).toTranslateWithWarnings('`declare:` is deprecated; use `dimension:` or `measure:` inside a source or `extend:` block');
1081
- });
1082
- test('declare warning in source', () => {
1083
- expect((0, test_translator_1.markSource) `##! m4warnings
1084
- source: a2 is a extend { ${'declare: x is 1'} }`).toTranslateWithWarnings('`declare:` is deprecated; use `dimension:` or `measure:` inside a source or `extend:` block');
1085
- });
1086
- test('joins in query', () => {
1087
- expect((0, test_translator_1.markSource) `##! m4warnings
1088
- run: a -> { ${'join_one: b on true'}; ${'join_many: c is b on true'}; ${'join_cross: d is b on true'}; group_by: b.astr }`).toTranslateWithWarnings('Joins in queries are deprecated, move into an `extend:` block.', 'Joins in queries are deprecated, move into an `extend:` block.', 'Joins in queries are deprecated, move into an `extend:` block.');
1089
- });
1090
- });
1091
- test('refine query with extended source', () => {
1092
- const m = new test_translator_1.TestTranslator(`
1093
- source: nab is ab {
1094
- query: xturtle is aturtle + {
1095
- declare: aratio is ai / acount
1096
- }
1097
- }
1098
- query: nab -> xturtle + { aggregate: aratio }
1099
- `);
1100
- expect(m).toTranslate();
1101
- const t = m.translate();
1102
- if (t.translated) {
1103
- const q = t.translated.queryList[0].pipeline[0];
1104
- expect(q).toBeDefined();
1105
- if (q.type === 'reduce' && q.extendSource) {
1106
- expect(q.extendSource.length).toBe(1);
1107
- const f = q.extendSource[0];
1108
- expect(f.type).toBe('number');
1109
- if (f.type === 'number') {
1110
- expect((0, model_1.expressionIsCalculation)(f.expressionType)).toBeTruthy();
1111
- }
1112
- }
1113
- else {
1114
- fail('Did not generate extendSource');
1115
- }
1116
- }
1117
- });
1118
- test('refine query source with field', () => {
1119
- const m = new test_translator_1.TestTranslator(`
1120
- query: ab -> aturtle + {
1121
- declare: aratio is ai / acount
1122
- aggregate: aratio
1123
- }
1124
- `);
1125
- expect(m).toTranslate();
1126
- const t = m.translate();
1127
- if (t.translated) {
1128
- const q = t.translated.queryList[0].pipeline[0];
1129
- if (q.type === 'reduce' && q.extendSource) {
1130
- expect(q.extendSource.length).toBe(1);
1131
- const f = q.extendSource[0];
1132
- expect(f.type).toBe('number');
1133
- if (f.type === 'number') {
1134
- expect((0, model_1.expressionIsCalculation)(f.expressionType)).toBeTruthy();
1135
- }
1136
- }
1137
- else {
1138
- fail('Did not generate extendSource');
1139
- }
1140
- }
1141
- });
1142
- test('refine query source with join', () => {
1143
- const m = new test_translator_1.TestTranslator(`
1144
- query: ab -> aturtle + {
1145
- join_one: bb is b on bb.astr = astr
1146
- group_by: foo is bb.astr
1147
- }
1148
- `);
1149
- expect(m).toTranslate();
1150
- const t = m.translate();
1151
- if (t.translated) {
1152
- const q = t.translated.queryList[0].pipeline[0];
1153
- if (q.type === 'reduce' && q.extendSource) {
1154
- expect(q.extendSource.length).toBe(1);
1155
- expect(q.extendSource[0].type).toBe('struct');
1156
- }
1157
- else {
1158
- fail('Did not generate extendSource');
1159
- }
1160
- }
1161
- });
1162
- });
1163
- describe('literals', () => {
1164
- test('integer', () => {
1165
- expect((0, test_translator_1.expr) `42`).toTranslate();
1166
- });
1167
- test('string', () => {
1168
- const m = new test_translator_1.BetaExpression("'forty two'");
1169
- expect(m).toTranslate();
1170
- const m42 = m.generated().value[0];
1171
- expect(m42).toMatchObject({ literal: 'forty two' });
1172
- });
1173
- test('string with quoted quote', () => {
1174
- const str = "'Isn" + '\\' + "'t this nice'";
1175
- expect(new test_translator_1.BetaExpression(str)).toTranslate();
1176
- });
1177
- test('string with quoted backslash', () => {
1178
- const str = "'Is " + '\\' + '\\' + " nice'";
1179
- expect(new test_translator_1.BetaExpression(str)).toTranslate();
1180
- });
1181
- const literalTimes = [
1182
- ['@1960', 'date', 'year', { literal: '1960-01-01' }],
1183
- ['@1960-Q2', 'date', 'quarter', { literal: '1960-04-01' }],
1184
- ['@1960-06', 'date', 'month', { literal: '1960-06-01' }],
1185
- ['@1960-06-26-WK', 'date', 'week', { literal: '1960-06-26' }],
1186
- ['@1960-06-30', 'date', 'day', { literal: '1960-06-30' }],
1187
- ['@1960-06-30 10', 'timestamp', 'hour', { literal: '1960-06-30 10:00:00' }],
1188
- [
1189
- '@1960-06-30 10:30',
1190
- 'timestamp',
1191
- 'minute',
1192
- { literal: '1960-06-30 10:30:00' },
1193
- ],
1194
- [
1195
- '@1960-06-30 10:30:00',
1196
- 'timestamp',
1197
- undefined,
1198
- { literal: '1960-06-30 10:30:00' },
1199
- ],
1200
- [
1201
- '@1960-06-30 10:30:00.123',
1202
- 'timestamp',
1203
- undefined,
1204
- { literal: '1960-06-30 10:30:00.123' },
1205
- ],
1206
- [
1207
- '@1960-06-30T10:30:00',
1208
- 'timestamp',
1209
- undefined,
1210
- { literal: '1960-06-30 10:30:00' },
1211
- ],
1212
- [
1213
- '@1960-06-30 10:30:00[America/Los_Angeles]',
1214
- 'timestamp',
1215
- undefined,
1216
- {
1217
- literal: '1960-06-30 10:30:00',
1218
- timezone: 'America/Los_Angeles',
1219
- },
1220
- ],
1221
- ];
1222
- test.each(literalTimes)('%s', (expr, timeType, timeframe, result) => {
1223
- const exprModel = new test_translator_1.BetaExpression(expr);
1224
- expect(exprModel).toTranslate();
1225
- const ir = exprModel.generated();
1226
- expect(ir.dataType).toEqual(timeType);
1227
- if (timeframe) {
1228
- expect((0, granular_result_1.isGranularResult)(ir)).toBeTruthy();
1229
- if ((0, granular_result_1.isGranularResult)(ir)) {
1230
- expect(ir.timeframe).toEqual(timeframe);
1231
- }
1232
- }
1233
- else {
1234
- expect((0, granular_result_1.isGranularResult)(ir)).toBeFalsy();
1235
- }
1236
- expect(ir.value[0]).toEqual(expect.objectContaining(result));
1237
- });
1238
- const morphicLiterals = [
1239
- ['@1960', '1960-01-01 00:00:00'],
1240
- ['@1960-Q2', '1960-04-01 00:00:00'],
1241
- ['@1960-06', '1960-06-01 00:00:00'],
1242
- ['@1960-06-26-Wk', '1960-06-26 00:00:00'],
1243
- ['@1960-06-30', '1960-06-30 00:00:00'],
1244
- ['@1960-06-30 00:00', undefined],
1245
- ];
1246
- test.each(morphicLiterals)('morphic value for %s is %s', (expr, morphic) => {
1247
- const exprModel = new test_translator_1.BetaExpression(expr);
1248
- expect(exprModel).toTranslate();
1249
- const ir = exprModel.generated();
1250
- const morphTo = ir.morphic && ir.morphic['timestamp'];
1251
- if (morphic) {
1252
- expect(morphTo).toBeDefined();
1253
- if (morphTo) {
1254
- expect(morphTo[0]).toEqual(expect.objectContaining({ literal: morphic }));
1255
- }
1256
- }
1257
- else {
1258
- expect(morphTo).toBeUndefined();
1259
- }
1260
- });
1261
- test('minute+locale', () => {
1262
- expect((0, test_translator_1.expr) `@1960-06-30 10:30[America/Los_Angeles]`).toTranslate();
1263
- });
1264
- test('second 8601', () => {
1265
- expect((0, test_translator_1.expr) `@1960-06-30T10:30:31`).toTranslate();
1266
- });
1267
- test('null', () => {
1268
- expect((0, test_translator_1.expr) `null`).toTranslate();
1269
- });
1270
- test('now', () => {
1271
- expect((0, test_translator_1.expr) `now`).toTranslate();
1272
- });
1273
- test('true', () => {
1274
- expect((0, test_translator_1.expr) `true`).toTranslate();
1275
- });
1276
- test('false', () => {
1277
- expect((0, test_translator_1.expr) `false`).toTranslate();
1278
- });
1279
- test('regex', () => {
1280
- expect((0, test_translator_1.expr) `r'RegularExpression'`).toTranslate();
1281
- });
1282
- });
1283
- describe('expressions', () => {
1284
- describe('timeframes', () => {
1285
- const timeframes = [
1286
- 'second',
1287
- 'minute',
1288
- 'hour',
1289
- 'day',
1290
- 'week',
1291
- 'month',
1292
- 'quarter',
1293
- 'year',
1294
- ];
1295
- test.each(timeframes.map(x => [x]))('truncate %s', unit => {
1296
- expect(new test_translator_1.BetaExpression(`ats.${unit}`)).toParse();
1297
- });
1298
- // mtoy todo units missing: implement, or document
1299
- const diffable = ['second', 'minute', 'hour', 'day'];
1300
- test.each(diffable.map(x => [x]))('timestamp difference - %s', unit => {
1301
- expect(new test_translator_1.BetaExpression(`${unit}(@2021 to ats)`)).toParse();
1302
- });
1303
- test.each(diffable.map(x => [x]))('timestamp difference - %s', unit => {
1304
- expect(new test_translator_1.BetaExpression(`${unit}(ats to @2030)`)).toParse();
1305
- });
1306
- });
1307
- test('field name', () => {
1308
- expect((0, test_translator_1.expr) `astr`).toTranslate();
1309
- });
1310
- test('function call', () => {
1311
- expect((0, test_translator_1.expr) `concat('foo')`).toTranslate();
1312
- });
1313
- describe('operators', () => {
1314
- test('addition', () => {
1315
- expect((0, test_translator_1.expr) `42 + 7`).toTranslate();
1316
- });
1317
- test('subtraction', () => {
1318
- expect((0, test_translator_1.expr) `42 - 7`).toTranslate();
1319
- });
1320
- test('multiplication', () => {
1321
- expect((0, test_translator_1.expr) `42 * 7`).toTranslate();
1322
- });
1323
- test('mod', () => {
1324
- expect((0, test_translator_1.expr) `42 % 7`).toTranslate();
1325
- });
1326
- test('division', () => {
1327
- expect((0, test_translator_1.expr) `42 / 7`).toTranslate();
1328
- });
1329
- test('unary negation', () => {
1330
- expect((0, test_translator_1.expr) `- ai`).toTranslate();
1331
- });
1332
- test('equal', () => {
1333
- expect((0, test_translator_1.expr) `42 = 7`).toTranslate();
1334
- });
1335
- test('not equal', () => {
1336
- expect((0, test_translator_1.expr) `42 != 7`).toTranslate();
1337
- });
1338
- test('greater than', () => {
1339
- expect((0, test_translator_1.expr) `42 > 7`).toTranslate();
1340
- });
1341
- test('greater than or equal', () => {
1342
- expect((0, test_translator_1.expr) `42 >= 7`).toTranslate();
1343
- });
1344
- test('less than or equal', () => {
1345
- expect((0, test_translator_1.expr) `42 <= 7`).toTranslate();
1346
- });
1347
- test('less than', () => {
1348
- expect((0, test_translator_1.expr) `42 < 7`).toTranslate();
1349
- });
1350
- test('match', () => {
1351
- expect((0, test_translator_1.expr) `'forty-two' ~ 'fifty-four'`).toTranslate();
1352
- });
1353
- test('not match', () => {
1354
- expect((0, test_translator_1.expr) `'forty-two' !~ 'fifty-four'`).toTranslate();
1355
- });
1356
- test('apply', () => {
1357
- expect((0, test_translator_1.expr) `'forty-two' ? 'fifty-four'`).toTranslate();
1358
- });
1359
- test('not', () => {
1360
- expect((0, test_translator_1.expr) `not true`).toTranslate();
1361
- });
1362
- test('and', () => {
1363
- expect((0, test_translator_1.expr) `true and false`).toTranslate();
1364
- });
1365
- test('or', () => {
1366
- expect((0, test_translator_1.expr) `true or false`).toTranslate();
1367
- });
1368
- test('null-check (??)', () => {
1369
- expect((0, test_translator_1.expr) `ai ?? 7`).toTranslate();
1370
- });
1371
- test('coalesce type mismatch', () => {
1372
- expect(new test_translator_1.BetaExpression('ai ?? @2003')).translationToFailWith('Mismatched types for coalesce (number, date)');
1373
- });
1374
- test('disallow date OP number', () => {
1375
- expect(new test_translator_1.BetaExpression('@2001 = 7')).translationToFailWith('Cannot compare a date to a number');
1376
- });
1377
- test('disallow date OP timestamp', () => {
1378
- expect(new test_translator_1.BetaExpression('ad = ats')).translationToFailWith('Cannot compare a date to a timestamp');
1379
- });
1380
- test('disallow interval from date to timestamp', () => {
1381
- expect(new test_translator_1.BetaExpression('days(ad to ats)')).translationToFailWith('Cannot measure from date to timestamp');
1382
- });
1383
- test('comparison promotes date literal to timestamp', () => {
1384
- expect((0, test_translator_1.expr) `@2001 = ats`).toTranslate();
1385
- });
1386
- test('can apply range to date', () => {
1387
- expect((0, test_translator_1.expr) `ad ? @2001 for 1 day`).toTranslate();
1388
- });
1389
- const noOffset = ['second', 'minute', 'hour'];
1390
- test.each(noOffset.map(x => [x]))('disallow date delta %s', unit => {
1391
- expect(new test_translator_1.BetaExpression(`ad + 10 ${unit}s`)).translationToFailWith(`Cannot offset date by ${unit}`);
1392
- });
1393
- test('apply with parens', () => {
1394
- expect((0, test_translator_1.expr) `ai ? (> 1 & < 100)`).toTranslate();
1395
- });
1396
- });
1397
- test('filtered measure', () => {
1398
- expect((0, test_translator_1.expr) `acount {? astr = 'why?' }`).toTranslate();
1399
- });
1400
- test('filtered ungrouped aggregate', () => {
1401
- expect(`
1402
- query: a -> {
1403
- group_by: ai
1404
- aggregate: x is all(avg(ai)) { where: true }
1405
- }
1406
- `).toTranslate();
1407
- });
1408
- test('shortcut filtered measure m4warning', () => {
1409
- expect(`
1410
- ##! m4warnings
1411
- run: a -> {
1412
- group_by: ai
1413
- aggregate: x is avg(ai) {? astr = 'why?' }
1414
- }
1415
- `).toTranslateWithWarnings('Filter shortcut `{? condition }` is deprecated; use `{ where: condition } instead');
1416
- });
1417
- test('correctly flags filtered scalar', () => {
1418
- const e = new test_translator_1.BetaExpression('ai { where: true }');
1419
- expect(e).translationToFailWith('Filtered expression requires an aggregate computation');
1420
- });
1421
- test('correctly flags filtered analytic', () => {
1422
- expect((0, test_translator_1.markSource) `
1423
- query: a -> {
1424
- group_by: ai
1425
- calculate: l is lag(ai) { where: true }
1426
- }
1427
- `).translationToFailWith('Filtered expression requires an aggregate computation');
1428
- });
1429
- describe('aggregate forms', () => {
1430
- test('count', () => {
1431
- expect((0, test_translator_1.expr) `count()`).toTranslate();
1432
- });
1433
- test('count distinct', () => {
1434
- expect((0, test_translator_1.expr) `count(distinct astr)`).toTranslate();
1435
- });
1436
- test('join.count()', () => {
1437
- expect((0, test_translator_1.expr) `b.count()`).toTranslate();
1438
- });
1439
- for (const f of ['sum', 'min', 'max', 'avg']) {
1440
- const fOfT = `${f}(af)`;
1441
- test(fOfT, () => {
1442
- expect(new test_translator_1.BetaExpression(fOfT)).toTranslate();
1443
- });
1444
- if (f !== 'min' && f !== 'max') {
1445
- const joinDot = `b.af.${f}()`;
1446
- test(joinDot, () => {
1447
- expect(new test_translator_1.BetaExpression(joinDot)).toTranslate();
1448
- });
1449
- const joinAgg = `b.${f}(af)`;
1450
- test(joinAgg, () => {
1451
- expect(new test_translator_1.BetaExpression(joinAgg)).toTranslate();
1452
- });
1453
- }
1454
- }
1455
- });
1456
- describe('pick statements', () => {
1457
- test('full', () => {
1458
- expect((0, test_translator_1.expr) `
1459
- pick 'the answer' when ai = 42
1460
- pick 'the questionable answer' when ai = 54
1461
- else 'random'
1462
- `).toTranslate();
1463
- });
1464
- test('applied', () => {
1465
- expect((0, test_translator_1.expr) `
1466
- astr ?
1467
- pick 'the answer' when = '42'
1468
- pick 'the questionable answer' when = '54'
1469
- else 'random'
1470
- `).toTranslate();
1471
- });
1472
- test('filtering', () => {
1473
- expect((0, test_translator_1.expr) `astr ? pick 'missing value' when NULL`).toTranslate();
1474
- });
1475
- test('null branch with else', () => {
1476
- expect("astr ? pick null when = '42' else 3").toReturnType('number');
1477
- });
1478
- test('null branch no else', () => {
1479
- expect("astr ? pick null when = '42'").toReturnType('string');
1480
- });
1481
- test('null branch no apply', () => {
1482
- expect('pick null when 1 = 1 else 3').toReturnType('number');
1483
- });
1484
- test('tiering', () => {
1485
- expect((0, test_translator_1.expr) `
1486
- ai ?
1487
- pick 1 when < 10
1488
- pick 10 when < 100
1489
- pick 100 when < 1000
1490
- else 10000
1491
- `).toTranslate();
1492
- });
1493
- test('transforming', () => {
1494
- expect((0, test_translator_1.expr) `
1495
- ai ?
1496
- pick 'small' when < 10
1497
- pick 'medium' when < 100
1498
- else 'large'
1499
- `).toTranslate();
1500
- });
1501
- test('when single values', () => {
1502
- expect((0, test_translator_1.expr) `
1503
- ai ?
1504
- pick 'one' when 1
1505
- else 'a lot'
1506
- `).toTranslate();
1507
- });
1508
- test('n-ary without else', () => {
1509
- expect(`
1510
- source: na is a + { dimension: d is
1511
- pick 7 when true and true
1512
- }
1513
- `).translationToFailWith("pick incomplete, missing 'else'");
1514
- });
1515
- test('n-ary with mismatch when clauses', () => {
1516
- expect((0, test_translator_1.markSource) `
1517
- source: na is a + { dimension: d is
1518
- pick 7 when true and true
1519
- pick '7' when true or true
1520
- else 7
1521
- }
1522
- `).translationToFailWith("pick type 'string', expected 'number'");
1523
- });
1524
- test('n-ary with mismatched else clause', () => {
1525
- expect((0, test_translator_1.markSource) `
1526
- source: na is a + { dimension: d is
1527
- pick 7 when true and true
1528
- else '7'
1529
- }
1530
- `).translationToFailWith("else type 'string', expected 'number'");
1531
- });
1532
- test('applied else mismatch', () => {
1533
- expect((0, test_translator_1.markSource) `
1534
- source: na is a + { dimension: d is
1535
- 7 ? pick 7 when 7 else 'not seven'
1536
- }
1537
- `).translationToFailWith("else type 'string', expected 'number'");
1538
- });
1539
- test('applied default mismatch', () => {
1540
- expect((0, test_translator_1.markSource) `
1541
- source: na is a + { dimension: d is
1542
- 7 ? pick 'seven' when 7
1543
- }
1544
- `).translationToFailWith("pick default type 'number', expected 'string'");
1545
- });
1546
- test('applied when mismatch', () => {
1547
- expect((0, test_translator_1.markSource) `
1548
- source: na is a + { dimension: d is
1549
- 7 ? pick 'seven' when 7 pick 6 when 6
1550
- }
1551
- `).translationToFailWith("pick type 'number', expected 'string'");
1552
- });
1553
- });
1554
- test('paren and applied div', () => {
1555
- var _a, _b;
1556
- const modelSrc = 'query: z is a -> { group_by: x is 1+(3/4) }';
1557
- const m = new test_translator_1.TestTranslator(modelSrc);
1558
- expect(m).toTranslate();
1559
- const queryDef = (_b = (_a = m.translate()) === null || _a === void 0 ? void 0 : _a.translated) === null || _b === void 0 ? void 0 : _b.modelDef.contents['z'];
1560
- expect(queryDef).toBeDefined();
1561
- expect(queryDef === null || queryDef === void 0 ? void 0 : queryDef.type).toBe('query');
1562
- if (queryDef && queryDef.type === 'query') {
1563
- const x = queryDef.pipeline[0].fields[0];
1564
- if (typeof x !== 'string' &&
1565
- !(0, model_1.isFilteredAliasedName)(x) &&
1566
- (0, model_1.isFieldTypeDef)(x) &&
1567
- x.type === 'number' &&
1568
- x.e) {
1569
- expect(x).toMatchObject({
1570
- 'e': [
1571
- {
1572
- 'function': 'numberLiteral',
1573
- 'literal': '1',
1574
- 'type': 'dialect',
1575
- },
1576
- // TODO not sure why there are TWO sets of parentheses... A previous version of this test
1577
- // just checked that there were ANY parens, so that went under the radar. Not fixing now.
1578
- '+((',
1579
- {
1580
- 'denominator': [
1581
- {
1582
- 'function': 'numberLiteral',
1583
- 'literal': '4',
1584
- 'type': 'dialect',
1585
- },
1586
- ],
1587
- 'function': 'div',
1588
- 'numerator': [
1589
- {
1590
- 'function': 'numberLiteral',
1591
- 'literal': '3',
1592
- 'type': 'dialect',
1593
- },
1594
- ],
1595
- 'type': 'dialect',
1596
- },
1597
- '))',
1598
- ],
1599
- });
1600
- }
1601
- else {
1602
- fail('expression with parens compiled oddly');
1603
- }
1604
- }
1605
- });
1606
- test.each([
1607
- ['ats', 'timestamp'],
1608
- ['ad', 'date'],
1609
- ['ai', 'number'],
1610
- ['astr', 'string'],
1611
- ['abool', 'boolean'],
1612
- ])('Can compare field %s (type %s) to NULL', (name, _datatype) => {
1613
- expect((0, test_translator_1.expr) `${name} = NULL`).toTranslate();
1614
- });
1615
- });
1616
- describe('unspported fields in schema', () => {
1617
- test('unsupported reference in result allowed', () => {
1618
- const uModel = new test_translator_1.TestTranslator('query: a->{ group_by: aun }');
1619
- expect(uModel).toTranslate();
1620
- });
1621
- test('unsupported reference can be compared to NULL', () => {
1622
- const uModel = new test_translator_1.TestTranslator('query: a->{ where: aun != NULL; project: * }');
1623
- expect(uModel).toTranslate();
1624
- });
1625
- test('flag unsupported equality', () => {
1626
- // because we don't know if the two unsupported types are comparable
1627
- const uModel = new test_translator_1.TestTranslator('query: ab->{ where: aun = b.aun project: * }');
1628
- expect(uModel).translationToFailWith('Unsupported type not allowed in expression');
1629
- });
1630
- test('flag unsupported compare', () => {
1631
- // because we don't know if the two unsupported types are comparable
1632
- const uModel = new test_translator_1.TestTranslator('query: ab->{ where: aun > b.aun project: * }');
1633
- expect(uModel).translationToFailWith('Unsupported type not allowed in expression');
1634
- });
1635
- test('allow unsupported equality when raw types match', () => {
1636
- const uModel = new test_translator_1.TestTranslator('query: ab->{ where: aweird = b.aweird project: * }');
1637
- expect(uModel).toTranslate();
1638
- });
1639
- test('flag not applied to unsupported', () => {
1640
- const uModel = new test_translator_1.TestTranslator('source: x is a { dimension: notUn is not aun }');
1641
- expect(uModel).translationToFailWith("'not' Can't use type unsupported");
1642
- });
1643
- test('allow unsupported to be cast', () => {
1644
- const uModel = new test_translator_1.TestTranslator('source: x is a { dimension: notUn is aun::string }');
1645
- expect(uModel).toTranslate();
1646
- });
1647
- });
1648
- describe('cast', () => {
1649
- // The "+ 1"s are there to make sure the result is of type 'number'
1650
- test('sql cast', () => {
1651
- expect((0, test_translator_1.expr) `ai::'integer' + 1`).toTranslate();
1652
- expect((0, test_translator_1.expr) `ai::"integer" + 1`).toTranslate();
1653
- expect((0, test_translator_1.expr) `ai::"""integer""" + 1`).toTranslate();
1654
- });
1655
- test('sql safe cast', () => {
1656
- expect((0, test_translator_1.expr) `astr:::'integer' + 1`).toTranslate();
1657
- expect((0, test_translator_1.expr) `astr:::"integer" + 1`).toTranslate();
1658
- expect((0, test_translator_1.expr) `astr:::"""integer""" + 1`).toTranslate();
1659
- });
1660
- test('malloy cast', () => {
1661
- expect((0, test_translator_1.expr) `astr::number + 1`).toTranslate();
1662
- });
1663
- test('malloy safe cast', () => {
1664
- expect((0, test_translator_1.expr) `astr:::number + 1`).toTranslate();
1665
- });
1666
- test('sql cast illegal type name', () => {
1667
- expect((0, test_translator_1.expr) `astr::"stuff 'n' things"`).translationToFailWith("Cast type `stuff 'n' things` is invalid for standardsql dialect");
1668
- });
1669
- });
1670
- describe('error handling', () => {
1671
- test('field and query with same name does not overflow', () => {
1672
- expect(`
1673
- source: flights is table('malloytest.flights') {
1674
- query: carrier is { group_by: carrier }
1675
- }
1676
- `).translationToFailWith("Cannot redefine 'carrier'");
1677
- });
1678
- test('redefine source', () => {
1679
- expect((0, test_translator_1.markSource) `
1680
- source: airports is table('malloytest.airports') + {
1681
- primary_key: code
1682
- }
1683
- source: airports is table('malloytest.airports') + {
1684
- primary_key: code
1685
- }
1686
- `).translationToFailWith("Cannot redefine 'airports'");
1687
- });
1688
- test('query from undefined source', () => {
1689
- expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y }`).translationToFailWith("Undefined query or source 'x'");
1690
- });
1691
- test('query with expression from undefined source', () => {
1692
- // Regression check: Once upon a time this died with an exception even
1693
- // when "query: x->{ group_by: y}" (above) generated the correct error.
1694
- expect((0, test_translator_1.markSource) `query: ${'x'}->{ project: y is z / 2 }`).translationToFailWith("Undefined query or source 'x'");
1695
- });
1696
- test('join reference before definition', () => {
1697
- expect((0, test_translator_1.markSource) `
1698
- source: newAB is a { join_one: newB is ${'bb'} on astring }
1699
- source: newB is b
1700
- `).translationToFailWith("Undefined query or source 'bb'");
1701
- });
1702
- test('non-rename rename', () => {
1703
- expect('source: na is a { rename: astr is astr }').translationToFailWith("Can't rename field to itself");
1704
- });
1705
- test('reference to field in its definition', () => {
1706
- expect('source: na is a { dimension: ustr is upper(ustr) } ').translationToFailWith("Circular reference to 'ustr' in definition");
1707
- });
1708
- test('empty model', () => {
1709
- expect('').toTranslate();
1710
- });
1711
- test('one line model ', () => {
1712
- expect('\n').toTranslate();
1713
- });
1714
- test('query without fields', () => {
1715
- expect('query: a -> { top: 5 }').translationToFailWith("Can't determine query type (group_by/aggregate/nest,project,index)");
1716
- });
1717
- test('refine cannot change query type', () => {
1718
- expect('query: ab -> aturtle { project: astr }').translationToFailWith(/Not legal in grouping query/);
1719
- });
1720
- test('undefined field ref in query', () => {
1721
- expect('query: ab -> { aggregate: xyzzy }').translationToFailWith("'xyzzy' is not defined");
1722
- });
1723
- test('query on source with errors', () => {
1724
- expect((0, test_translator_1.markSource) `
1725
- source: na is a { join_one: ${'n'} on astr }
1726
- `).translationToFailWith("Undefined source 'n'");
1727
- });
1728
- test('detect duplicate output field names', () => {
1729
- expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'astr'} }`).translationToFailWith("Output already has a field named 'astr'");
1730
- });
1731
- test('detect join tail overlap existing ref', () => {
1732
- expect((0, test_translator_1.markSource) `query: ab -> { group_by: astr, ${'b.astr'} }`).translationToFailWith("Output already has a field named 'astr'");
1733
- });
1734
- test('undefined in expression with regex compare', () => {
1735
- expect(`
1736
- source: c is a {
1737
- dimension: d is meaning_of_life ~ r'(forty two|fifty four)'
1738
- }
1739
- `).translationToFailWith("'meaning_of_life' is not defined");
1740
- });
1741
- test('detect output collision on join references', () => {
1742
- expect(`
1743
- query: ab -> {
1744
- group_by: astr, b.astr
1745
- }
1746
- `).translationToFailWith("Output already has a field named 'astr'");
1747
- });
1748
- test('rejoin a query is renamed', () => {
1749
- expect(`
1750
- source: querySrc is from(
1751
- table('malloytest.flights')->{
1752
- group_by: origin
1753
- nest: nested is { group_by: destination }
1754
- }
1755
- )
1756
-
1757
- source: refineQuerySrc is querySrc {
1758
- join_one: rejoin is querySrc on 7=8
1759
- query: broken is {
1760
- group_by: rejoin.nested.destination
1761
- }
1762
- }
1763
- `).toTranslate();
1764
- });
1765
- test('popping out of embedding when not embedded', () => {
1766
- expect('}%').translationToFailWith(/extraneous input '}%' expecting/);
1767
- });
1768
- test('bad sql in sql block', () => {
1769
- const badSelect = (0, test_translator_1.model) `sql: someName is { select: """)""" }`;
1770
- const badTrans = badSelect.translator;
1771
- expect(badSelect).toParse();
1772
- const needSchema = badTrans.translate();
1773
- expect(needSchema.compileSQL).toBeDefined();
1774
- if (needSchema.compileSQL) {
1775
- badTrans.update({
1776
- errors: {
1777
- compileSQL: {
1778
- [needSchema.compileSQL.name]: 'ZZZZ',
1779
- },
1780
- },
1781
- });
1782
- }
1783
- expect(badTrans).translationToFailWith('Invalid SQL, ZZZZ');
1784
- });
1785
- });
1786
- function getSelectOneStruct(sqlBlock) {
1787
- const selectThis = sqlBlock.select[0];
1788
- if (!(0, model_1.isSQLFragment)(selectThis)) {
1789
- throw new Error('weird test support error sorry');
1790
- }
1791
- return {
1792
- type: 'struct',
1793
- name: sqlBlock.name,
1794
- dialect: 'standardsql',
1795
- structSource: {
1796
- type: 'sql',
1797
- method: 'subquery',
1798
- sqlBlock: {
1799
- type: 'sqlBlock',
1800
- name: sqlBlock.name,
1801
- selectStr: selectThis.sql,
1802
- },
1803
- },
1804
- structRelationship: { type: 'basetable', connectionName: 'bigquery' },
1805
- fields: [{ type: 'number', name: 'one' }],
1806
- };
1807
- }
1808
- describe('source locations', () => {
1809
- test('renamed source location', () => {
1810
- const source = (0, test_translator_1.markSource) `source: ${'na is a'}`;
1811
- const m = new test_translator_1.TestTranslator(source.code);
1812
- expect(m).toParse();
1813
- expect((0, test_translator_1.getExplore)(m.modelDef, 'na').location).toMatchObject(source.locations[0]);
1814
- });
1815
- test('refined source location', () => {
1816
- const source = (0, test_translator_1.markSource) `source: ${'na is a {}'}`;
1817
- const m = new test_translator_1.TestTranslator(source.code);
1818
- expect(m).toParse();
1819
- expect((0, test_translator_1.getExplore)(m.modelDef, 'na').location).toMatchObject(source.locations[0]);
1820
- });
1821
- test('location of defined dimension', () => {
1822
- const source = (0, test_translator_1.markSource) `source: na is a { dimension: ${'x is 1'} }`;
1823
- const m = new test_translator_1.TestTranslator(source.code);
1824
- expect(m).toTranslate();
1825
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1826
- const x = (0, test_translator_1.getFieldDef)(na, 'x');
1827
- expect(x.location).toMatchObject(source.locations[0]);
1828
- });
1829
- test('location of defined measure', () => {
1830
- const source = (0, test_translator_1.markSource) `source: na is a { measure: ${'x is count()'} }`;
1831
- const m = new test_translator_1.TestTranslator(source.code);
1832
- expect(m).toTranslate();
1833
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1834
- const x = (0, test_translator_1.getFieldDef)(na, 'x');
1835
- expect(x.location).toMatchObject(source.locations[0]);
1836
- });
1837
- test('location of defined query', () => {
1838
- const source = (0, test_translator_1.markSource) `source: na is a { query: ${'x is { group_by: y is 1 }'} }`;
1839
- const m = new test_translator_1.TestTranslator(source.code);
1840
- expect(m).toTranslate();
1841
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1842
- const x = (0, test_translator_1.getFieldDef)(na, 'x');
1843
- expect(x.location).toMatchObject(source.locations[0]);
1844
- });
1845
- test('location of defined field inside a query', () => {
1846
- const source = (0, test_translator_1.markSource) `
1847
- source: na is a {
1848
- query: x is {
1849
- group_by: ${'y is 1'}
1850
- }
1851
- }`;
1852
- const m = new test_translator_1.TestTranslator(source.code);
1853
- expect(m).toTranslate();
1854
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1855
- const x = (0, test_translator_1.getQueryField)(na, 'x');
1856
- const y = (0, test_translator_1.getFieldDef)(x.pipeline[0], 'y');
1857
- expect(y.location).toMatchObject(source.locations[0]);
1858
- });
1859
- test('location of filtered field inside a query', () => {
1860
- const source = (0, test_translator_1.markSource) `
1861
- source: na is a {
1862
- measure: y is count()
1863
- query: x is {
1864
- aggregate: ${'z is y { where: true }'}
1865
- }
1866
- }`;
1867
- const m = new test_translator_1.TestTranslator(source.code);
1868
- expect(m).toTranslate();
1869
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1870
- const x = (0, test_translator_1.getQueryField)(na, 'x');
1871
- const z = (0, test_translator_1.getFieldDef)(x.pipeline[0], 'z');
1872
- expect(z.location).toMatchObject(source.locations[0]);
1873
- });
1874
- test('location of field inherited from table', () => {
1875
- const source = (0, test_translator_1.markSource) `source: na is ${"table('aTable')"}`;
1876
- const m = new test_translator_1.TestTranslator(source.code);
1877
- expect(m).toTranslate();
1878
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1879
- const abool = (0, test_translator_1.getFieldDef)(na, 'abool');
1880
- expect(abool.location).toMatchObject(source.locations[0]);
1881
- });
1882
- test('location of field inherited from sql block', () => {
1883
- const source = (0, test_translator_1.markSource) `--- comment
1884
- sql: s is { select: ${'"""SELECT 1 as one """'} }
1885
- source: na is from_sql(s)
1886
- `;
1887
- const m = new test_translator_1.TestTranslator(source.code);
1888
- expect(m).toParse();
1889
- const compileSql = m.translate().compileSQL;
1890
- expect(compileSql).toBeDefined();
1891
- if (compileSql) {
1892
- m.update({
1893
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
1894
- });
1895
- expect(m).toTranslate();
1896
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1897
- const one = (0, test_translator_1.getFieldDef)(na, 'one');
1898
- expect(one.location).isLocationIn(source.locations[0], source.code);
1899
- }
1900
- });
1901
- test('location of fields inherited from a query', () => {
1902
- const source = (0, test_translator_1.markSource) `
1903
- source: na is from(
1904
- ${"table('aTable')"} -> {
1905
- group_by:
1906
- abool
1907
- ${'y is 1'}
1908
- }
1909
- )
1910
- `;
1911
- const m = new test_translator_1.TestTranslator(source.code);
1912
- expect(m).toTranslate();
1913
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1914
- const abool = (0, test_translator_1.getFieldDef)(na, 'abool');
1915
- expect(abool.location).toMatchObject(source.locations[0]);
1916
- const y = (0, test_translator_1.getFieldDef)(na, 'y');
1917
- expect(y.location).toMatchObject(source.locations[1]);
1918
- });
1919
- test('location of named query', () => {
1920
- const source = (0, test_translator_1.markSource) `query: ${'q is a -> { project: * }'}`;
1921
- const m = new test_translator_1.TestTranslator(source.code);
1922
- expect(m).toTranslate();
1923
- const q = (0, test_translator_1.getExplore)(m.modelDef, 'q');
1924
- expect(q.location).toMatchObject(source.locations[0]);
1925
- });
1926
- test('location of field in named query', () => {
1927
- const source = (0, test_translator_1.markSource) `query: q is a -> { group_by: ${'b is 1'} }`;
1928
- const m = new test_translator_1.TestTranslator(source.code);
1929
- expect(m).toTranslate();
1930
- const q = (0, test_translator_1.getModelQuery)(m.modelDef, 'q');
1931
- const a = (0, test_translator_1.getFieldDef)(q.pipeline[0], 'b');
1932
- expect(a.location).toMatchObject(source.locations[0]);
1933
- });
1934
- test('location of named SQL block', () => {
1935
- const source = (0, test_translator_1.markSource) `${'sql: s is { select: """SELECT 1 as one""" }'}`;
1936
- const m = new test_translator_1.TestTranslator(source.code);
1937
- expect(m).toParse();
1938
- const compileSql = m.translate().compileSQL;
1939
- expect(compileSql).toBeDefined();
1940
- if (compileSql) {
1941
- m.update({
1942
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
1943
- });
1944
- expect(m).toTranslate();
1945
- const s = m.sqlBlocks[0];
1946
- expect(s.location).isLocationIn(source.locations[0], source.code);
1947
- }
1948
- });
1949
- test('location of renamed field', () => {
1950
- const source = (0, test_translator_1.markSource) `
1951
- source: na is a {
1952
- rename: ${'bbool is abool'}
1953
- }
1954
- `;
1955
- const m = new test_translator_1.TestTranslator(source.code);
1956
- expect(m).toTranslate();
1957
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1958
- const bbool = (0, test_translator_1.getFieldDef)(na, 'bbool');
1959
- expect(bbool.location).toMatchObject(source.locations[0]);
1960
- });
1961
- test('location of join on', () => {
1962
- const source = (0, test_translator_1.markSource) `
1963
- source: na is a {
1964
- join_one: ${'x is a { primary_key: abool } on abool'}
1965
- }
1966
- `;
1967
- const m = new test_translator_1.TestTranslator(source.code);
1968
- expect(m).toTranslate();
1969
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1970
- const x = (0, test_translator_1.getFieldDef)(na, 'x');
1971
- expect(x.location).toMatchObject(source.locations[0]);
1972
- });
1973
- test('location of join with', () => {
1974
- const source = (0, test_translator_1.markSource) `
1975
- source: na is a {
1976
- join_one: ${'x is a { primary_key: astr } with astr'}
1977
- }
1978
- `;
1979
- const m = new test_translator_1.TestTranslator(source.code);
1980
- expect(m).toTranslate();
1981
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1982
- const x = (0, test_translator_1.getFieldDef)(na, 'x');
1983
- expect(x.location).toMatchObject(source.locations[0]);
1984
- });
1985
- test('location of field in join', () => {
1986
- const source = (0, test_translator_1.markSource) `
1987
- source: na is a {
1988
- join_one: x is a {
1989
- primary_key: abool
1990
- dimension: ${'y is 1'}
1991
- } on abool
1992
- }
1993
- `;
1994
- const m = new test_translator_1.TestTranslator(source.code);
1995
- expect(m).toTranslate();
1996
- const na = (0, test_translator_1.getExplore)(m.modelDef, 'na');
1997
- const x = (0, test_translator_1.getJoinField)(na, 'x');
1998
- const y = (0, test_translator_1.getFieldDef)(x, 'y');
1999
- expect(y.location).toMatchObject(source.locations[0]);
2000
- });
2001
- // Since """ strings are not single tokens, I don't know how to do this.
2002
- // test("multi line sql block token span is correct", () => {
2003
- // const sqlSource = `sql: { select: """// line 0\n//line 1\n// line 2""" }`;
2004
- // const m = new TestTranslator(sqlSource);
2005
- // expect(m).not.toParse();
2006
- // const errList = m.errors().errors;
2007
- // expect(errList[0].at?.range.end).toEqual({ line: 2, character: 11 });
2008
- // });
2009
- test('undefined query location', () => {
2010
- expect((0, test_translator_1.model) `query: ${'-> xyz'}`).translationToFailWith("Reference to undefined query 'xyz'");
2011
- });
2012
- test('undefined field reference', () => {
2013
- expect((0, test_translator_1.model) `query: a -> { group_by: ${'xyz'} }`).translationToFailWith("'xyz' is not defined");
2014
- });
2015
- test('bad query', () => {
2016
- expect((0, test_translator_1.model) `query: a -> { group_by: astr; ${'project: *'} }`).translationToFailWith(/Not legal in grouping query/);
2017
- });
2018
- test.skip('undefined field reference in top', () => {
2019
- expect((0, test_translator_1.model) `query: a -> { group_by: one is 1; top: 1 by ${'xyz'} }`).translationToFailWith("'xyz' is not defined");
2020
- });
2021
- test.skip('undefined field reference in order_by', () => {
2022
- expect((0, test_translator_1.model) `query: a -> { group_by: one is 1; order_by: ${'xyz'} }`).translationToFailWith("'xyz' is not defined");
2023
- });
2024
- });
2025
- describe('source references', () => {
2026
- test('reference to explore', () => {
2027
- const source = (0, test_translator_1.markSource) `
2028
- source: ${'na is a'}
2029
- query: ${'na'} -> { project: * }
2030
- `;
2031
- const m = new test_translator_1.TestTranslator(source.code);
2032
- expect(m).toTranslate();
2033
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2034
- location: source.locations[1],
2035
- type: 'exploreReference',
2036
- text: 'na',
2037
- definition: {
2038
- location: source.locations[0],
2039
- },
2040
- });
2041
- });
2042
- test('reference to query in query', () => {
2043
- const source = (0, test_translator_1.markSource) `
2044
- source: t is a {
2045
- query: ${'q is { project: * }'}
2046
- }
2047
- query: t -> ${'q'}
2048
- `;
2049
- const m = new test_translator_1.TestTranslator(source.code);
2050
- expect(m).toTranslate();
2051
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2052
- location: source.locations[1],
2053
- type: 'fieldReference',
2054
- text: 'q',
2055
- definition: {
2056
- location: source.locations[0],
2057
- },
2058
- });
2059
- });
2060
- test('reference to query in query (version 2)', () => {
2061
- const source = (0, test_translator_1.markSource) `
2062
- source: na is a { query: ${'x is { group_by: y is 1 }'} }
2063
- query: na -> ${'x'}
2064
- `;
2065
- const m = new test_translator_1.TestTranslator(source.code);
2066
- expect(m).toTranslate();
2067
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2068
- location: source.locations[1],
2069
- type: 'fieldReference',
2070
- text: 'x',
2071
- definition: {
2072
- location: source.locations[0],
2073
- },
2074
- });
2075
- });
2076
- test('reference to sql block', () => {
2077
- const source = (0, test_translator_1.markSource) `
2078
- ${'sql: s is {select:"""SELECT 1 as one"""}'}
2079
- source: na is from_sql(${'s'})
2080
- `;
2081
- const m = new test_translator_1.TestTranslator(source.code);
2082
- expect(m).toParse();
2083
- const compileSql = m.translate().compileSQL;
2084
- expect(compileSql).toBeDefined();
2085
- if (compileSql) {
2086
- m.update({
2087
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
2088
- });
2089
- expect(m).toTranslate();
2090
- const ref = m.referenceAt(pos(source.locations[1]));
2091
- expect(ref).toMatchObject({
2092
- location: source.locations[1],
2093
- type: 'sqlBlockReference',
2094
- text: 's',
2095
- definition: {
2096
- ...getSelectOneStruct(compileSql),
2097
- location: source.locations[0],
161
+ test('bad sql in sql block', () => {
162
+ const badSelect = (0, test_translator_1.model) `sql: someName is { select: """)""" }`;
163
+ const badTrans = badSelect.translator;
164
+ expect(badSelect).toParse();
165
+ const needSchema = badTrans.translate();
166
+ expect(needSchema.compileSQL).toBeDefined();
167
+ if (needSchema.compileSQL) {
168
+ badTrans.update({
169
+ errors: {
170
+ compileSQL: {
171
+ [needSchema.compileSQL.name]: 'ZZZZ',
172
+ },
2098
173
  },
2099
174
  });
2100
175
  }
2101
- });
2102
- test('reference to query in from', () => {
2103
- const source = (0, test_translator_1.markSource) `
2104
- query: ${'q is a -> { project: * }'}
2105
- source: na is from(-> ${'q'})
2106
- `;
2107
- const m = new test_translator_1.TestTranslator(source.code);
2108
- expect(m).toTranslate();
2109
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2110
- location: source.locations[1],
2111
- type: 'queryReference',
2112
- text: 'q',
2113
- definition: {
2114
- location: source.locations[0],
2115
- },
2116
- });
2117
- });
2118
- test('reference to query in query head', () => {
2119
- const source = (0, test_translator_1.markSource) `
2120
- query: ${'q is a -> { project: * }'}
2121
- query: q2 is -> ${'q'} -> { project: * }
2122
- `;
2123
- const m = new test_translator_1.TestTranslator(source.code);
2124
- expect(m).toTranslate();
2125
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2126
- location: source.locations[1],
2127
- type: 'queryReference',
2128
- text: 'q',
2129
- definition: {
2130
- location: source.locations[0],
2131
- },
2132
- });
2133
- });
2134
- test('reference to query in refined query', () => {
2135
- const source = (0, test_translator_1.markSource) `
2136
- query: ${'q is a -> { project: * }'}
2137
- query: q2 is -> ${'q'} { limit: 10 }
2138
- `;
2139
- const m = new test_translator_1.TestTranslator(source.code);
2140
- expect(m).toTranslate();
2141
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2142
- location: source.locations[1],
2143
- type: 'queryReference',
2144
- text: 'q',
2145
- definition: {
2146
- location: source.locations[0],
2147
- },
2148
- });
2149
- });
2150
- test('reference to field in expression', () => {
2151
- const source = (0, test_translator_1.markSource) `
2152
- source: na is ${"table('aTable')"}
2153
- query: na -> { project: bbool is not ${'abool'} }
2154
- `;
2155
- const m = new test_translator_1.TestTranslator(source.code);
2156
- expect(m).toTranslate();
2157
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2158
- location: source.locations[1],
2159
- type: 'fieldReference',
2160
- text: 'abool',
2161
- definition: {
2162
- location: source.locations[0],
2163
- },
2164
- });
2165
- });
2166
- test('reference to quoted field in expression', () => {
2167
- const source = (0, test_translator_1.markSource) `
2168
- source: na is a {
2169
- dimension: ${"`name` is 'name'"}
2170
- }
2171
- query: na -> { project: ${'`name`'} }
2172
- `;
2173
- const m = new test_translator_1.TestTranslator(source.code);
2174
- expect(m).toTranslate();
2175
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2176
- location: source.locations[1],
2177
- type: 'fieldReference',
2178
- text: 'name',
2179
- definition: {
2180
- location: source.locations[0],
2181
- },
2182
- });
2183
- });
2184
- test('reference to joined field in expression', () => {
2185
- const source = (0, test_translator_1.markSource) `
2186
- source: na is a {
2187
- join_one: self is ${"table('aTable')"}
2188
- on astr = self.astr
2189
- }
2190
- query: na -> { project: bstr is self.${'astr'} }
2191
- `;
2192
- const m = new test_translator_1.TestTranslator(source.code);
2193
- expect(m).toTranslate();
2194
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2195
- location: source.locations[1],
2196
- type: 'fieldReference',
2197
- text: 'astr',
2198
- definition: {
2199
- location: source.locations[0],
2200
- },
2201
- });
2202
- });
2203
- test('reference to joined join in expression', () => {
2204
- const source = (0, test_translator_1.markSource) `
2205
- source: na is a {
2206
- join_one: ${'self is a on astr = self.astr'}
2207
- }
2208
- query: na -> { project: bstr is ${'self'}.astr }
2209
- `;
2210
- const m = new test_translator_1.TestTranslator(source.code);
2211
- expect(m).toTranslate();
2212
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2213
- location: source.locations[1],
2214
- type: 'joinReference',
2215
- text: 'self',
2216
- definition: {
2217
- location: source.locations[0],
2218
- },
2219
- });
2220
- });
2221
- test('reference to field not in expression (group by)', () => {
2222
- const source = (0, test_translator_1.markSource) `
2223
- query: ${"table('aTable')"} -> { group_by: ${'abool'} }
2224
- `;
2225
- const m = new test_translator_1.TestTranslator(source.code);
2226
- expect(m).toTranslate();
2227
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2228
- location: source.locations[1],
2229
- type: 'fieldReference',
2230
- text: 'abool',
2231
- definition: {
2232
- location: source.locations[0],
2233
- },
2234
- });
2235
- });
2236
- test('reference to field not in expression (project)', () => {
2237
- const source = (0, test_translator_1.markSource) `
2238
- source: na is ${"table('aTable')"}
2239
- query: na -> { project: ${'abool'} }
2240
- `;
2241
- const m = new test_translator_1.TestTranslator(source.code);
2242
- expect(m).toTranslate();
2243
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2244
- location: source.locations[1],
2245
- type: 'fieldReference',
2246
- text: 'abool',
2247
- definition: {
2248
- location: source.locations[0],
2249
- },
2250
- });
2251
- });
2252
- test.skip('reference to field in order by', () => {
2253
- const source = (0, test_translator_1.markSource) `
2254
- query: ${"table('aTable')"} -> {
2255
- group_by: abool
2256
- order_by: ${'abool'}
2257
- }
2258
- `;
2259
- const m = new test_translator_1.TestTranslator(source.code);
2260
- expect(m).toTranslate();
2261
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2262
- location: source.locations[1],
2263
- type: 'fieldReference',
2264
- text: 'abool',
2265
- definition: {
2266
- location: source.locations[0],
2267
- },
2268
- });
2269
- });
2270
- test.skip('reference to field in order by (output space)', () => {
2271
- const source = (0, test_translator_1.markSource) `
2272
- query: a -> {
2273
- group_by: ${'one is 1'}
2274
- order_by: ${'one'}
2275
- }
2276
- `;
2277
- const m = new test_translator_1.TestTranslator(source.code);
2278
- expect(m).toTranslate();
2279
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2280
- location: source.locations[1],
2281
- type: 'fieldReference',
2282
- text: 'abool',
2283
- definition: {
2284
- location: source.locations[0],
2285
- },
2286
- });
2287
- });
2288
- test('reference to field in aggregate', () => {
2289
- const source = (0, test_translator_1.markSource) `
2290
- query: a { measure: ${'c is count()'} } -> {
2291
- group_by: abool
2292
- aggregate: ${'c'}
2293
- }
2294
- `;
2295
- const m = new test_translator_1.TestTranslator(source.code);
2296
- expect(m).toTranslate();
2297
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2298
- location: source.locations[1],
2299
- type: 'fieldReference',
2300
- text: 'c',
2301
- definition: {
2302
- location: source.locations[0],
2303
- },
2304
- });
2305
- });
2306
- test('reference to field in measure', () => {
2307
- const source = (0, test_translator_1.markSource) `
2308
- source: e is a {
2309
- measure: ${'c is count()'}
2310
- measure: c2 is ${'c'}
2311
- }
2312
- `;
2313
- const m = new test_translator_1.TestTranslator(source.code);
2314
- expect(m).toTranslate();
2315
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2316
- location: source.locations[1],
2317
- type: 'fieldReference',
2318
- text: 'c',
2319
- definition: {
2320
- location: source.locations[0],
2321
- },
2322
- });
2323
- });
2324
- test.skip('reference to field in top', () => {
2325
- const source = (0, test_translator_1.markSource) `
2326
- query: ${"table('aTable')"} -> {
2327
- group_by: abool
2328
- top: 10 by ${'abool'}
2329
- }
2330
- `;
2331
- const m = new test_translator_1.TestTranslator(source.code);
2332
- expect(m).toTranslate();
2333
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2334
- location: source.locations[1],
2335
- type: 'fieldReference',
2336
- text: 'abool',
2337
- definition: {
2338
- location: source.locations[0],
2339
- },
2340
- });
2341
- });
2342
- test.skip('reference to field in top (output space)', () => {
2343
- const source = (0, test_translator_1.markSource) `
2344
- query: a -> {
2345
- group_by: ${'one is 1'}
2346
- top: 10 by ${'one'}
2347
- }
2348
- `;
2349
- const m = new test_translator_1.TestTranslator(source.code);
2350
- expect(m).toTranslate();
2351
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2352
- location: source.locations[1],
2353
- type: 'fieldReference',
2354
- text: 'abool',
2355
- definition: {
2356
- location: source.locations[0],
2357
- },
2358
- });
2359
- });
2360
- test('reference to field in filter', () => {
2361
- const source = (0, test_translator_1.markSource) `
2362
- query: ${"table('aTable')"} -> {
2363
- group_by: abool
2364
- where: ${'abool'}
2365
- }
2366
- `;
2367
- const m = new test_translator_1.TestTranslator(source.code);
2368
- expect(m).toTranslate();
2369
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2370
- location: source.locations[1],
2371
- type: 'fieldReference',
2372
- text: 'abool',
2373
- definition: {
2374
- location: source.locations[0],
2375
- },
2376
- });
2377
- });
2378
- test('reference to field in aggregate source', () => {
2379
- const source = (0, test_translator_1.markSource) `
2380
- source: na is ${"table('aTable')"}
2381
- query: na -> { aggregate: ai_sum is ${'ai'}.sum() }
2382
- `;
2383
- const m = new test_translator_1.TestTranslator(source.code);
2384
- expect(m).toTranslate();
2385
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2386
- location: source.locations[1],
2387
- type: 'fieldReference',
2388
- text: 'ai',
2389
- definition: {
2390
- location: source.locations[0],
2391
- },
2392
- });
2393
- });
2394
- function pos(location) {
2395
- return location.range.start;
2396
- }
2397
- test('reference to join in aggregate source', () => {
2398
- const source = (0, test_translator_1.markSource) `
2399
- source: na is a {
2400
- join_one: ${'self is a on astr = self.astr'}
2401
- }
2402
- query: na -> { aggregate: ai_sum is ${'self'}.sum(self.ai) }
2403
- `;
2404
- const m = new test_translator_1.TestTranslator(source.code);
2405
- expect(m).toTranslate();
2406
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2407
- location: source.locations[1],
2408
- type: 'joinReference',
2409
- text: 'self',
2410
- definition: {
2411
- location: source.locations[0],
2412
- },
2413
- });
2414
- });
2415
- test('reference to join in aggregate in expr', () => {
2416
- const source = (0, test_translator_1.markSource) `
2417
- source: na is a {
2418
- join_one: ${'self is a on astr = self.astr'}
2419
- }
2420
- query: na -> { aggregate: ai_sum is self.sum(${'self'}.ai) }
2421
- `;
2422
- const m = new test_translator_1.TestTranslator(source.code);
2423
- expect(m).toTranslate();
2424
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2425
- location: source.locations[1],
2426
- type: 'joinReference',
2427
- text: 'self',
2428
- definition: {
2429
- location: source.locations[0],
2430
- },
2431
- });
2432
- });
2433
- test('reference to sourcein join', () => {
2434
- const source = (0, test_translator_1.markSource) `
2435
- source: ${'exp1 is a'}
2436
- source: exp2 is a {
2437
- join_one: ${'exp1'} on astr = exp1.astr
2438
- }
2439
- `;
2440
- const m = new test_translator_1.TestTranslator(source.code);
2441
- expect(m).toTranslate();
2442
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2443
- location: source.locations[1],
2444
- type: 'exploreReference',
2445
- text: 'exp1',
2446
- definition: {
2447
- location: source.locations[0],
2448
- },
2449
- });
2450
- });
2451
- test('reference to field in aggregate (in expr)', () => {
2452
- const source = (0, test_translator_1.markSource) `
2453
- source: na is ${"table('aTable')"}
2454
- query: na -> { aggregate: ai_sum is sum(${'ai'}) }
2455
- `;
2456
- const m = new test_translator_1.TestTranslator(source.code);
2457
- expect(m).toTranslate();
2458
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2459
- location: source.locations[1],
2460
- type: 'fieldReference',
2461
- text: 'ai',
2462
- definition: {
2463
- location: source.locations[0],
2464
- },
2465
- });
2466
- });
2467
- test('reference to field in rename', () => {
2468
- const source = (0, test_translator_1.markSource) `
2469
- source: na is ${"table('aTable')"} {
2470
- rename: bbool is ${'abool'}
2471
- }
2472
- `;
2473
- const m = new test_translator_1.TestTranslator(source.code);
2474
- expect(m).toTranslate();
2475
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2476
- location: source.locations[1],
2477
- type: 'fieldReference',
2478
- text: 'abool',
2479
- definition: {
2480
- location: source.locations[0],
2481
- },
2482
- });
2483
- });
2484
- test('reference to field in join with', () => {
2485
- const source = (0, test_translator_1.markSource) `
2486
- source: exp1 is a { primary_key: astr }
2487
- source: exp2 is ${"table('aTable')"} {
2488
- join_one: exp1 with ${'astr'}
2489
- }
2490
- `;
2491
- const m = new test_translator_1.TestTranslator(source.code);
2492
- expect(m).toTranslate();
2493
- expect(m.referenceAt(pos(source.locations[1]))).toMatchObject({
2494
- location: source.locations[1],
2495
- type: 'fieldReference',
2496
- text: 'astr',
2497
- definition: {
2498
- location: source.locations[0],
2499
- },
2500
- });
176
+ expect(badTrans).translationToFailWith('Invalid SQL, ZZZZ');
2501
177
  });
2502
178
  });
2503
179
  describe('translate imports', () => {
@@ -2895,7 +571,7 @@ describe('sql expressions', () => {
2895
571
  expect(compileSql).toBeDefined();
2896
572
  if (compileSql) {
2897
573
  m.translator.update({
2898
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
574
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2899
575
  });
2900
576
  expect(m).toTranslate();
2901
577
  }
@@ -2909,7 +585,7 @@ describe('sql expressions', () => {
2909
585
  expect(compileSql).toBeDefined();
2910
586
  if (compileSql) {
2911
587
  m.translator.update({
2912
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
588
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2913
589
  });
2914
590
  expect(m).toTranslate();
2915
591
  }
@@ -2923,7 +599,7 @@ describe('sql expressions', () => {
2923
599
  expect(compileSql).toBeDefined();
2924
600
  if (compileSql) {
2925
601
  m.translator.update({
2926
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
602
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2927
603
  });
2928
604
  expect(m).toTranslate();
2929
605
  }
@@ -2938,7 +614,7 @@ describe('sql expressions', () => {
2938
614
  expect(compileSql).toBeDefined();
2939
615
  if (compileSql) {
2940
616
  m.translator.update({
2941
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
617
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2942
618
  });
2943
619
  expect(m).toTranslate();
2944
620
  }
@@ -2952,7 +628,7 @@ describe('sql expressions', () => {
2952
628
  expect(compileSql).toBeDefined();
2953
629
  if (compileSql) {
2954
630
  m.update({
2955
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
631
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2956
632
  });
2957
633
  expect(m).toTranslate();
2958
634
  }
@@ -2966,7 +642,7 @@ describe('sql expressions', () => {
2966
642
  expect(compileSql).toBeDefined();
2967
643
  if (compileSql) {
2968
644
  m.update({
2969
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
645
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2970
646
  });
2971
647
  expect(m).toTranslate();
2972
648
  }
@@ -2983,7 +659,7 @@ describe('sql expressions', () => {
2983
659
  expect(compileSql).toBeDefined();
2984
660
  if (compileSql) {
2985
661
  m.translator.update({
2986
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
662
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
2987
663
  });
2988
664
  expect(m).toTranslate();
2989
665
  }
@@ -2994,7 +670,7 @@ describe('sql expressions', () => {
2994
670
  const req = m.translate().compileSQL;
2995
671
  if (req) {
2996
672
  m.update({
2997
- compileSQL: { [req.name]: getSelectOneStruct(req) },
673
+ compileSQL: { [req.name]: (0, test_translator_1.getSelectOneStruct)(req) },
2998
674
  });
2999
675
  }
3000
676
  expect(m).toTranslateWithWarnings('`sql:` statement is deprecated, use `connection_name.sql(...)` instead');
@@ -3006,7 +682,7 @@ describe('sql expressions', () => {
3006
682
  const req = m.translate().compileSQL;
3007
683
  if (req) {
3008
684
  m.update({
3009
- compileSQL: { [req.name]: getSelectOneStruct(req) },
685
+ compileSQL: { [req.name]: (0, test_translator_1.getSelectOneStruct)(req) },
3010
686
  });
3011
687
  }
3012
688
  expect(m).toTranslateWithWarnings('`sql:` statement is deprecated, use `connection_name.sql(...)` instead', '`from_sql` is deprecated; use `connection_name.sql(...)` as a source directly');
@@ -3020,7 +696,7 @@ describe('sql expressions', () => {
3020
696
  expect(compileSql).toBeDefined();
3021
697
  if (compileSql) {
3022
698
  m.update({
3023
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
699
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
3024
700
  });
3025
701
  expect(m).toTranslate();
3026
702
  }
@@ -3034,7 +710,7 @@ describe('sql expressions', () => {
3034
710
  expect(compileSql).toBeDefined();
3035
711
  if (compileSql) {
3036
712
  m.update({
3037
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
713
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
3038
714
  });
3039
715
  expect(m).toTranslate();
3040
716
  }
@@ -3048,7 +724,7 @@ describe('sql expressions', () => {
3048
724
  expect(compileSql).toBeDefined();
3049
725
  if (compileSql) {
3050
726
  m.update({
3051
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
727
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
3052
728
  });
3053
729
  expect(m).toTranslate();
3054
730
  }
@@ -3066,7 +742,7 @@ describe('sql expressions', () => {
3066
742
  expect(compileSql).toBeDefined();
3067
743
  if (compileSql) {
3068
744
  m.update({
3069
- compileSQL: { [compileSql.name]: getSelectOneStruct(compileSql) },
745
+ compileSQL: { [compileSql.name]: (0, test_translator_1.getSelectOneStruct)(compileSql) },
3070
746
  });
3071
747
  expect(m).translationToFailWith('Cannot refine a SQL query');
3072
748
  }