@malloydata/malloy-tests 0.0.69-dev230808200646 → 0.0.69-dev230809221417

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/databases/all/db_index.spec.d.ts +1 -1
  2. package/dist/databases/all/db_index.spec.js +116 -6
  3. package/dist/databases/all/db_index.spec.js.map +1 -1
  4. package/dist/databases/all/expr.spec.js +542 -6
  5. package/dist/databases/all/expr.spec.js.map +1 -1
  6. package/dist/databases/all/functions.spec.d.ts +1 -1
  7. package/dist/databases/all/functions.spec.js +745 -6
  8. package/dist/databases/all/functions.spec.js.map +1 -1
  9. package/dist/databases/all/join.spec.d.ts +1 -1
  10. package/dist/databases/all/join.spec.js +272 -6
  11. package/dist/databases/all/join.spec.js.map +1 -1
  12. package/dist/databases/all/nomodel.spec.d.ts +1 -1
  13. package/dist/databases/all/nomodel.spec.js +919 -6
  14. package/dist/databases/all/nomodel.spec.js.map +1 -1
  15. package/dist/databases/all/orderby.spec.d.ts +1 -1
  16. package/dist/databases/all/orderby.spec.js +187 -6
  17. package/dist/databases/all/orderby.spec.js.map +1 -1
  18. package/dist/databases/all/problems.spec.d.ts +1 -1
  19. package/dist/databases/all/problems.spec.js +76 -6
  20. package/dist/databases/all/problems.spec.js.map +1 -1
  21. package/dist/databases/all/sql_expressions.spec.d.ts +1 -1
  22. package/dist/databases/all/sql_expressions.spec.js +58 -6
  23. package/dist/databases/all/sql_expressions.spec.js.map +1 -1
  24. package/dist/databases/all/time.spec.d.ts +1 -1
  25. package/dist/databases/all/time.spec.js +609 -5
  26. package/dist/databases/all/time.spec.js.map +1 -1
  27. package/dist/databases/shared/test_list.js +1 -20
  28. package/dist/databases/shared/test_list.js.map +1 -1
  29. package/dist/index.d.ts +0 -8
  30. package/dist/index.js +1 -17
  31. package/dist/index.js.map +1 -1
  32. package/package.json +6 -6
  33. package/src/databases/all/db_index.spec.ts +137 -6
  34. package/src/databases/all/expr.spec.ts +661 -7
  35. package/src/databases/all/functions.spec.ts +1092 -6
  36. package/src/databases/all/join.spec.ts +309 -6
  37. package/src/databases/all/nomodel.spec.ts +1114 -7
  38. package/src/databases/all/orderby.spec.ts +229 -6
  39. package/src/databases/all/problems.spec.ts +82 -6
  40. package/src/databases/all/sql_expressions.spec.ts +65 -6
  41. package/src/databases/all/time.spec.ts +734 -5
  42. package/src/databases/shared/test_list.ts +1 -20
  43. package/src/index.ts +0 -9
  44. package/dist/databases/shared/db_index.d.ts +0 -3
  45. package/dist/databases/shared/db_index.js +0 -123
  46. package/dist/databases/shared/db_index.js.map +0 -1
  47. package/dist/databases/shared/expr.d.ts +0 -3
  48. package/dist/databases/shared/expr.js +0 -551
  49. package/dist/databases/shared/expr.js.map +0 -1
  50. package/dist/databases/shared/functions.d.ts +0 -3
  51. package/dist/databases/shared/functions.js +0 -754
  52. package/dist/databases/shared/functions.js.map +0 -1
  53. package/dist/databases/shared/join.d.ts +0 -3
  54. package/dist/databases/shared/join.js +0 -302
  55. package/dist/databases/shared/join.js.map +0 -1
  56. package/dist/databases/shared/nomodel.d.ts +0 -3
  57. package/dist/databases/shared/nomodel.js +0 -950
  58. package/dist/databases/shared/nomodel.js.map +0 -1
  59. package/dist/databases/shared/orderby.d.ts +0 -3
  60. package/dist/databases/shared/orderby.js +0 -217
  61. package/dist/databases/shared/orderby.js.map +0 -1
  62. package/dist/databases/shared/problems.d.ts +0 -3
  63. package/dist/databases/shared/problems.js +0 -106
  64. package/dist/databases/shared/problems.js.map +0 -1
  65. package/dist/databases/shared/sql_expressions.d.ts +0 -3
  66. package/dist/databases/shared/sql_expressions.js +0 -88
  67. package/dist/databases/shared/sql_expressions.js.map +0 -1
  68. package/dist/databases/shared/time.d.ts +0 -3
  69. package/dist/databases/shared/time.js +0 -640
  70. package/dist/databases/shared/time.js.map +0 -1
  71. package/src/databases/shared/db_index.ts +0 -167
  72. package/src/databases/shared/expr.ts +0 -695
  73. package/src/databases/shared/functions.ts +0 -1126
  74. package/src/databases/shared/join.ts +0 -340
  75. package/src/databases/shared/nomodel.ts +0 -1150
  76. package/src/databases/shared/orderby.ts +0 -260
  77. package/src/databases/shared/problems.ts +0 -113
  78. package/src/databases/shared/sql_expressions.ts +0 -96
  79. package/src/databases/shared/time.ts +0 -786
@@ -24,13 +24,667 @@
24
24
 
25
25
  import {RuntimeList, allDatabases} from '../../runtimes';
26
26
  import '../../util/db-jest-matchers';
27
- import {databasesFromEnvironmentOr} from '../../util';
28
- import {exprSharedTests} from '../..';
27
+ import {databasesFromEnvironmentOr, mkSqlEqWith, testIf} from '../../util';
29
28
 
30
29
  const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
31
30
 
32
- /*
33
- * This test file reuses common tests definitions.
34
- * For actual test development please go to: test/src/databases/shared/expr.spec.ts
35
- */
36
- exprSharedTests(runtimes);
31
+ const expressionModelText = `
32
+ source: aircraft_models is table('malloytest.aircraft_models') extend {
33
+ primary_key: aircraft_model_code
34
+ measure:
35
+ airport_count is count(*),
36
+ aircraft_model_count is count(),
37
+ total_seats is sum(seats),
38
+ boeing_seats is sum(seats) {? manufacturer ? 'BOEING'},
39
+ percent_boeing is boeing_seats / total_seats * 100,
40
+ percent_boeing_floor is floor(boeing_seats / total_seats * 100),
41
+ dimension: seats_bucketed is floor(seats/20)*20.0
42
+ }
43
+
44
+ source: aircraft is table('malloytest.aircraft') extend {
45
+ primary_key: tail_num
46
+ join_one: aircraft_models with aircraft_model_code
47
+ measure: aircraft_count is count(*)
48
+ query: by_manufacturer is {
49
+ top: 5
50
+ group_by: aircraft_models.manufacturer
51
+ aggregate: aircraft_count
52
+ }
53
+ }
54
+ `;
55
+
56
+ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
57
+ const expressionModel = runtime.loadModel(expressionModelText);
58
+ // basic calculations for sum, filtered sum, without a join.
59
+ it('basic calculations', async () => {
60
+ const result = await expressionModel
61
+ .loadQuery(
62
+ `
63
+ query: aircraft_models->{
64
+ aggregate:
65
+ total_seats,
66
+ total_seats2 is sum(seats),
67
+ boeing_seats,
68
+ boeing_seats2 is sum(seats) {? manufacturer ? 'BOEING'},
69
+ boeing_seats3 is total_seats {? manufacturer ? 'BOEING'},
70
+ percent_boeing,
71
+ percent_boeing2 is boeing_seats / total_seats * 100,
72
+ -- percent_boeing_floor,
73
+ -- percent_boeing_floor2 is FLOOR(boeing_seats / total_seats * 100)
74
+ }
75
+ `
76
+ )
77
+ .run();
78
+ expect(result.data.path(0, 'total_seats').value).toBe(452415);
79
+ expect(result.data.path(0, 'total_seats2').value).toBe(452415);
80
+ expect(result.data.path(0, 'boeing_seats').value).toBe(252771);
81
+ expect(result.data.path(0, 'boeing_seats2').value).toBe(252771);
82
+ expect(result.data.path(0, 'boeing_seats3').value).toBe(252771);
83
+ expect(Math.floor(result.data.path(0, 'percent_boeing').number.value)).toBe(
84
+ 55
85
+ );
86
+ expect(
87
+ Math.floor(result.data.path(0, 'percent_boeing2').number.value)
88
+ ).toBe(55);
89
+ // expect(result.data.path(0, "percent_boeing_floor").value).toBe(55);
90
+ // expect(result.data.path(0, "percent_boeing_floor2").value).toBe(55);
91
+ });
92
+
93
+ // Floor is broken (doesn't compile because the expression returned isn't an aggregate.)
94
+ it('Floor() -or any function bustage with aggregates', async () => {
95
+ const result = await expressionModel
96
+ .loadQuery(
97
+ `
98
+ query: aircraft_models->{
99
+ aggregate:
100
+ percent_boeing_floor
101
+ percent_boeing_floor2 is FLOOR(boeing_seats / total_seats * 100)
102
+ }
103
+ `
104
+ )
105
+ .run();
106
+ expect(result.data.path(0, 'percent_boeing_floor').value).toBe(55);
107
+ expect(result.data.path(0, 'percent_boeing_floor2').value).toBe(55);
108
+ });
109
+
110
+ // Model based version of sums.
111
+ it('model: expression fixups.', async () => {
112
+ const result = await expressionModel
113
+ .loadQuery(
114
+ `
115
+ query: aircraft->{
116
+ aggregate:
117
+ aircraft_models.total_seats
118
+ aircraft_models.boeing_seats
119
+ }
120
+ `
121
+ )
122
+ .run();
123
+ expect(result.data.path(0, 'total_seats').value).toBe(18294);
124
+ expect(result.data.path(0, 'boeing_seats').value).toBe(6244);
125
+ });
126
+
127
+ // turtle expressions
128
+ it('model: turtle', async () => {
129
+ const result = await expressionModel
130
+ .loadQuery(
131
+ `
132
+ query: aircraft->by_manufacturer
133
+ `
134
+ )
135
+ .run();
136
+ expect(result.data.path(0, 'manufacturer').value).toBe('CESSNA');
137
+ });
138
+
139
+ // filtered turtle expressions
140
+ testIf(runtime.supportsNesting)('model: filtered turtle', async () => {
141
+ const result = await expressionModel
142
+ .loadQuery(
143
+ `
144
+ query: aircraft->{
145
+ nest: b is by_manufacturer{? aircraft_models.manufacturer ?~'B%'}
146
+ }
147
+ `
148
+ )
149
+ .run();
150
+ expect(result.data.path(0, 'b', 0, 'manufacturer').value).toBe('BEECH');
151
+ });
152
+
153
+ // having.
154
+ it('model: simple having', async () => {
155
+ const result = await expressionModel
156
+ .loadQuery(
157
+ `
158
+ query: aircraft->{
159
+ having: aircraft_count >90
160
+ group_by: state
161
+ aggregate: aircraft_count
162
+ order_by: 2
163
+ }
164
+ `
165
+ )
166
+ .run();
167
+ expect(result.data.path(0, 'aircraft_count').value).toBe(91);
168
+ });
169
+
170
+ testIf(runtime.supportsNesting)('model: turtle having2', async () => {
171
+ const result = await expressionModel
172
+ .loadQuery(
173
+ `
174
+ -- hacking a null test for now
175
+ query: aircraft->{
176
+ top: 10
177
+ order_by: 1
178
+ where: region != NULL
179
+ group_by: region
180
+ nest: by_state is {
181
+ top: 10
182
+ order_by: 1 desc
183
+ having: aircraft_count > 50
184
+ group_by: state
185
+ aggregate: aircraft_count
186
+ }
187
+ }
188
+ `
189
+ )
190
+ .run();
191
+ expect(result.data.path(0, 'by_state', 0, 'state').value).toBe('VA');
192
+ });
193
+
194
+ testIf(runtime.supportsNesting)('model: turtle having on main', async () => {
195
+ const result = await expressionModel
196
+ .loadQuery(
197
+ `
198
+ query: aircraft->{
199
+ order_by: 2 asc
200
+ having: aircraft_count ? >500
201
+ group_by: region
202
+ aggregate: aircraft_count
203
+ nest: by_state is {
204
+ order_by: 2 asc
205
+ having: aircraft_count >45
206
+ group_by: state
207
+ aggregate: aircraft_count
208
+ nest: by_city is {
209
+ order_by: 2 asc
210
+ having: aircraft_count ? >5
211
+ group_by: city
212
+ aggregate: aircraft_count
213
+ }
214
+ }
215
+ }
216
+ `
217
+ )
218
+ .run();
219
+ expect(result.data.path(0, 'by_state', 0, 'by_city', 0, 'city').value).toBe(
220
+ 'ALBUQUERQUE'
221
+ );
222
+ });
223
+
224
+ // bigquery doesn't like to partition by floats,
225
+ testIf(runtime.supportsNesting)(
226
+ 'model: having float group by partition',
227
+ async () => {
228
+ await expect(runtime).queryMatches(
229
+ `${expressionModelText}
230
+ query: aircraft_models->{
231
+ order_by: 1
232
+ where: seats_bucketed > 0
233
+ having: aircraft_model_count > 400
234
+ group_by: seats_bucketed
235
+ aggregate: aircraft_model_count
236
+ nest: foo is {
237
+ group_by: engines
238
+ aggregate: aircraft_model_count
239
+ }
240
+ }`,
241
+ {aircraft_model_count: 448}
242
+ );
243
+ }
244
+ );
245
+
246
+ it('model: aggregate functions distinct min max', async () => {
247
+ const result = await expressionModel
248
+ .loadQuery(
249
+ `
250
+ query: aircraft_models->{
251
+ aggregate:
252
+ distinct_seats is count(distinct seats),
253
+ boeing_distinct_seats is count(distinct seats) {?manufacturer ? 'BOEING'},
254
+ min_seats is min(seats),
255
+ cessna_min_seats is min(seats) {? manufacturer ? 'CESSNA'},
256
+ max_seats is max(seats),
257
+ cessna_max_seats is max(seats) {? manufacturer ? 'CESSNA'},
258
+ min_code is min(aircraft_model_code),
259
+ boeing_min_model is min(model) {? manufacturer ? 'BOEING'},
260
+ max_model is max(model),
261
+ boeing_max_model is max(model) {? manufacturer ? 'BOEING'},
262
+ }
263
+ `
264
+ )
265
+ .run();
266
+ expect(result.data.path(0, 'distinct_seats').value).toBe(187);
267
+ expect(result.data.path(0, 'boeing_distinct_seats').value).toBe(85);
268
+ expect(result.data.path(0, 'min_seats').value).toBe(0);
269
+ expect(result.data.path(0, 'cessna_min_seats').value).toBe(1);
270
+ expect(result.data.path(0, 'max_seats').value).toBe(660);
271
+ expect(result.data.path(0, 'min_code').value).toBe('0030109');
272
+ expect(result.data.path(0, 'cessna_max_seats').value).toBe(14);
273
+ expect(result.data.path(0, 'boeing_min_model').value).toBe('100');
274
+ expect(result.data.path(0, 'max_model').value).toBe('ZWEIFEL PA18');
275
+ expect(result.data.path(0, 'boeing_max_model').value).toBe('YL-15');
276
+ });
277
+
278
+ (databaseName !== 'bigquery' ? it.skip : it)(
279
+ 'model: dates named',
280
+ async () => {
281
+ const result = await expressionModel
282
+ .loadQuery(
283
+ `
284
+ query: table('malloytest.alltypes')->{
285
+ group_by:
286
+ t_date,
287
+ t_date_month is t_date.month,
288
+ t_date_year is t_date.year,
289
+ t_timestamp,
290
+ t_timestamp_date is t_timestamp.day,
291
+ t_timestamp_hour is t_timestamp.hour,
292
+ t_timestamp_minute is t_timestamp.minute,
293
+ t_timestamp_second is t_timestamp.second,
294
+ t_timestamp_month is t_timestamp.month,
295
+ t_timestamp_year is t_timestamp.year,
296
+ }
297
+
298
+ `
299
+ )
300
+ .run();
301
+ expect(result.data.path(0, 't_date').value).toEqual(
302
+ new Date('2020-03-02')
303
+ );
304
+ expect(result.data.path(0, 't_date_month').value).toEqual(
305
+ new Date('2020-03-01')
306
+ );
307
+ expect(result.data.path(0, 't_date_year').value).toEqual(
308
+ new Date('2020-01-01')
309
+ );
310
+ expect(result.data.path(0, 't_timestamp').value).toEqual(
311
+ new Date('2020-03-02T12:35:56.000Z')
312
+ );
313
+ expect(result.data.path(0, 't_timestamp_second').value).toEqual(
314
+ new Date('2020-03-02T12:35:56.000Z')
315
+ );
316
+ expect(result.data.path(0, 't_timestamp_minute').value).toEqual(
317
+ new Date('2020-03-02T12:35:00.000Z')
318
+ );
319
+ expect(result.data.path(0, 't_timestamp_hour').value).toEqual(
320
+ new Date('2020-03-02T12:00:00.000Z')
321
+ );
322
+ expect(result.data.path(0, 't_timestamp_date').value).toEqual(
323
+ new Date('2020-03-02')
324
+ );
325
+ expect(result.data.path(0, 't_timestamp_month').value).toEqual(
326
+ new Date('2020-03-01')
327
+ );
328
+ expect(result.data.path(0, 't_timestamp_year').value).toEqual(
329
+ new Date('2020-01-01')
330
+ );
331
+ }
332
+ );
333
+
334
+ it.skip('defines in model', async () => {
335
+ // const result1 = await model.makeQuery(`
336
+ // define a is ('malloytest.alltypes');
337
+ // explore a | reduce x is count(*)
338
+ // `);
339
+ // const result = await model.makeQuery(`
340
+ // define a is ('malloytest.alltypes');
341
+ // explore a | reduce x is count(*)
342
+ // `);
343
+ });
344
+
345
+ it('named query metadata undefined', async () => {
346
+ const result = await expressionModel
347
+ .loadQuery(
348
+ `
349
+ query: aircraft->{
350
+ aggregate: aircraft_count is count()
351
+ }
352
+ `
353
+ )
354
+ .run();
355
+ // TODO The result explore should really be unnamed. This test currently
356
+ // inspects inner information because we have no way to have unnamed
357
+ // explores today.
358
+ // expect(result.getResultExplore().name).toBe(undefined);
359
+ expect(result._queryResult.queryName).toBe(undefined);
360
+ });
361
+
362
+ it('named query metadata named', async () => {
363
+ const result = await expressionModel
364
+ .loadQuery(
365
+ `
366
+ query: aircraft->by_manufacturer
367
+ `
368
+ )
369
+ .run();
370
+ expect(result.resultExplore.name).toBe('by_manufacturer');
371
+ });
372
+
373
+ it('named query metadata named head of pipeline', async () => {
374
+ const result = await expressionModel
375
+ .loadQuery(
376
+ `
377
+ query: aircraft->by_manufacturer->{ aggregate: c is count()}
378
+ `
379
+ )
380
+ .run();
381
+ // TODO Same as above -- this test should check the explore name
382
+ // expect(result.getResultExplore().name).toBe(undefined);
383
+ expect(result._queryResult.queryName).toBe(undefined);
384
+ });
385
+
386
+ it('filtered explores basic', async () => {
387
+ const result = await expressionModel
388
+ .loadQuery(
389
+ `
390
+ source: b is aircraft{ where: aircraft_models.manufacturer ? ~'B%' }
391
+
392
+ query: b->{aggregate: m_count is count(distinct aircraft_models.manufacturer) }
393
+ `
394
+ )
395
+ .run();
396
+ expect(result.data.path(0, 'm_count').value).toBe(63);
397
+ });
398
+
399
+ testIf(runtime.supportsNesting)(
400
+ 'query with aliasname used twice',
401
+ async () => {
402
+ const result = await expressionModel
403
+ .loadQuery(
404
+ `
405
+ query: aircraft->{
406
+ group_by: first is substr(city,1,1)
407
+ aggregate: aircraft_count is count()
408
+ nest: aircraft is {
409
+ group_by: first_two is substr(city,1,2)
410
+ aggregate: aircraft_count is count()
411
+ nest: aircraft is {
412
+ group_by: first_three is substr(city,1,3)
413
+ aggregate: aircraft_count is count()
414
+ }
415
+ }
416
+ } -> {
417
+ project:
418
+ aircraft.aircraft.first_three
419
+ aircraft_count
420
+ order_by: 2 desc, 1
421
+ }
422
+ `
423
+ )
424
+ .run();
425
+ expect(result.data.path(0, 'first_three').value).toBe('SAB');
426
+ }
427
+ );
428
+
429
+ it.skip('join foreign_key reverse', async () => {
430
+ const result = await expressionModel
431
+ .loadQuery(
432
+ `
433
+ source: a is table('malloytest.aircraft') {
434
+ primary_key: tail_num
435
+ measure: aircraft_count is count()
436
+ }
437
+ query: table('malloytest.aircraft_models') {
438
+ primary_key: aircraft_model_code
439
+ join_many: a on a.aircraft_model_code
440
+
441
+ some_measures is {
442
+ aggregate: am_count is count()
443
+ aggregate: a.aircraft_count
444
+ }
445
+ } -> some_measure
446
+ `
447
+ )
448
+ .run();
449
+ expect(result.data.path(0, 'first_three').value).toBe('SAN');
450
+ });
451
+
452
+ it('joined filtered explores', async () => {
453
+ const result = await expressionModel
454
+ .loadQuery(
455
+ `
456
+ source: a_models is table('malloytest.aircraft_models'){
457
+ where: manufacturer ? ~'B%'
458
+ primary_key: aircraft_model_code
459
+ measure:model_count is count()
460
+ }
461
+
462
+ source: aircraft2 is table('malloytest.aircraft'){
463
+ join_one: model is a_models with aircraft_model_code
464
+ measure: aircraft_count is count()
465
+ }
466
+
467
+ query: aircraft2->{
468
+ aggregate:
469
+ model.model_count
470
+ aircraft_count
471
+ }
472
+ `
473
+ )
474
+ .run();
475
+ expect(result.data.path(0, 'model_count').value).toBe(244);
476
+ expect(result.data.path(0, 'aircraft_count').value).toBe(3599);
477
+ });
478
+
479
+ it('joined filtered explores with dependancies', async () => {
480
+ const result = await expressionModel
481
+ .loadQuery(
482
+ `
483
+ source: bo_models is
484
+ from(
485
+ table('malloytest.aircraft_models') {? manufacturer ? ~ 'BO%' }
486
+ -> { project: aircraft_model_code, manufacturer, seats }
487
+ ) {
488
+ primary_key: aircraft_model_code
489
+ measure: bo_count is count()
490
+ }
491
+
492
+ source: b_models is
493
+ from(
494
+ table('malloytest.aircraft_models') {? manufacturer ? ~ 'B%' }
495
+ -> { project: aircraft_model_code, manufacturer, seats }
496
+ ) {
497
+ where: bo_models.seats > 200
498
+ primary_key: aircraft_model_code
499
+ measure: b_count is count()
500
+ join_one: bo_models with aircraft_model_code
501
+ }
502
+
503
+ source: models is table('malloytest.aircraft_models') {
504
+ join_one: b_models with aircraft_model_code
505
+ measure: model_count is count()
506
+ }
507
+
508
+ query: models -> {
509
+ aggregate: model_count
510
+ aggregate: b_models.b_count
511
+ -- aggregate: b_models.bo_models.bo_count
512
+ }
513
+ `
514
+ )
515
+ .run();
516
+ expect(result.data.path(0, 'model_count').value).toBe(60461);
517
+ expect(result.data.path(0, 'b_count').value).toBe(355);
518
+ });
519
+
520
+ it('group by explore - simple group by', async () => {
521
+ const result = await expressionModel
522
+ .loadQuery(
523
+ `
524
+ query: aircraft->{
525
+ group_by: aircraft_models
526
+ aggregate: aircraft_count
527
+ }
528
+ `
529
+ )
530
+ .run();
531
+ expect(result.data.path(0, 'aircraft_count').value).toBe(58);
532
+ expect(result.data.path(0, 'aircraft_models_id').value).toBe('7102802');
533
+ });
534
+
535
+ it('group by explore - pipeline', async () => {
536
+ const result = await expressionModel
537
+ .loadQuery(
538
+ `
539
+ query: aircraft->{
540
+ group_by: aircraft_models
541
+ aggregate: aircraft_count
542
+ } -> {
543
+ group_by: aircraft_models.manufacturer
544
+ aggregate: aircraft_count is aircraft_count.sum()
545
+ }
546
+ `
547
+ )
548
+ .run();
549
+ expect(result.data.path(0, 'aircraft_count').value).toBe(1048);
550
+ expect(result.data.path(0, 'manufacturer').value).toBe('CESSNA');
551
+ });
552
+
553
+ it('group by explore - pipeline 2 levels', async () => {
554
+ const result = await expressionModel
555
+ .loadQuery(
556
+ `
557
+ source: f is table('malloytest.flights'){
558
+ join_one: a is table('malloytest.aircraft') {
559
+ join_one: state_facts is table('malloytest.state_facts'){primary_key: state} with state
560
+ } on tail_num = a.tail_num
561
+ }
562
+
563
+ query: f-> {
564
+ group_by: a.state_facts
565
+ aggregate: flight_count is count()
566
+ } -> {
567
+ group_by: state_facts.popular_name
568
+ aggregate: flight_count is flight_count.sum()
569
+ }
570
+ `
571
+ )
572
+ .run();
573
+ // console.log(result.data.toObject());
574
+ expect(result.data.path(0, 'flight_count').value).toBe(199726);
575
+ expect(result.data.path(0, 'popular_name').value).toBe('Isabella');
576
+ });
577
+ });
578
+
579
+ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
580
+ const sqlEq = mkSqlEqWith(runtime, {
581
+ malloy: `+ {
582
+ dimension: friName is 'friday'
583
+ dimension: friDay is 5
584
+ dimension: satName is 'saturday'
585
+ dimension: satDay is 6
586
+ }`,
587
+ });
588
+
589
+ describe.skip('alternations with not-eq', () => {
590
+ /*
591
+ Here's the desired truth table ...
592
+
593
+ x x != y | z
594
+ ====== ============
595
+ y false
596
+ z false
597
+ ^[yz] true
598
+ */
599
+ test('x not-eq y or z : x eq y', async () => {
600
+ const result = await sqlEq('6 != (6|7)', false);
601
+ expect(result).isSqlEq();
602
+ });
603
+ test('x not-eq y or z : x eq z', async () => {
604
+ const result = await sqlEq('7 != (6|7)', false);
605
+ expect(result).isSqlEq();
606
+ });
607
+ test('x not-eq y or z : else', async () => {
608
+ const result = await sqlEq('5 != (6|7)', true);
609
+ expect(result).isSqlEq();
610
+ });
611
+ /*
612
+ Writing this the old way, should have the same truth table ...
613
+ x != y & != z
614
+ */
615
+ test('x not-eq y and not-eq z : x eq y', async () => {
616
+ const result = await sqlEq('6 != (6 & !=7)', false);
617
+ expect(result).isSqlEq();
618
+ });
619
+ test('x not-eq y and not-eq z : x eq z', async () => {
620
+ const result = await sqlEq('7 != (6 & != 7)', false);
621
+ expect(result).isSqlEq();
622
+ });
623
+ test('x not-eq y and not-eq z : else', async () => {
624
+ const result = await sqlEq('5 != (6 & !=7)', true);
625
+ expect(result).isSqlEq();
626
+ });
627
+ });
628
+
629
+ describe('string literal quoting', () => {
630
+ const dq = '"';
631
+ const tick = "'";
632
+ const back = '\\';
633
+ test('quote single character', async () => {
634
+ expect(await sqlEq(`'${back}x'`, 'x')).isSqlEq();
635
+ });
636
+ test('quote single quote', async () => {
637
+ expect(await sqlEq(`'${back}${tick}'`, tick)).isSqlEq();
638
+ });
639
+ test('quote double quote', async () => {
640
+ await expect(runtime).queryMatches(
641
+ `sql: x is {connection:"${databaseName}" select:"""SELECT 1 as x"""}
642
+ query: from_sql(x) -> {
643
+ project: double_quote is "${back}${dq}"
644
+ }
645
+ `,
646
+ {double_quote: '"'}
647
+ );
648
+ });
649
+ test('quote backslash', async () => {
650
+ expect(await sqlEq(`'${back}${back}'`, back)).isSqlEq();
651
+ });
652
+ });
653
+
654
+ test('nullish ?? operator', async () => {
655
+ await expect(runtime).queryMatches(
656
+ `sql: nullTest is { connection: "${databaseName}" select: """
657
+ SELECT '' as null_value, '' as string_value
658
+ UNION ALL SELECT null, 'correct'
659
+ """ }
660
+ query: from_sql(nullTest) -> {
661
+ where: null_value = null
662
+ project:
663
+ found_null is null_value ?? 'correct',
664
+ else_pass is string_value ?? 'incorrect'
665
+ literal_null is null ?? 'correct'
666
+ }`,
667
+ {found_null: 'correct', else_pass: 'correct', literal_null: 'correct'}
668
+ );
669
+ });
670
+
671
+ test('dimension expressions expanded with parens properly', async () => {
672
+ await expect(runtime).queryMatches(
673
+ `
674
+ sql: tbl is { connection: "${databaseName}" select: """SELECT 1 as n"""}
675
+ query: from_sql(tbl) + {
676
+ dimension: fot is (false) or (true)
677
+ } -> {
678
+ project:
679
+ no_paren is false and fot
680
+ paren is false and (fot)
681
+ }
682
+ `,
683
+ {paren: false, no_paren: false}
684
+ );
685
+ });
686
+ });
687
+
688
+ afterAll(async () => {
689
+ await runtimes.closeAll();
690
+ });