@malloydata/malloy-tests 0.0.69-dev230808200646 → 0.0.69

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,12 +24,1098 @@
24
24
 
25
25
  import {RuntimeList, allDatabases} from '../../runtimes';
26
26
  import {databasesFromEnvironmentOr} from '../../util';
27
- import {functionsSharedTests} from '../shared/functions';
27
+ import '../../util/db-jest-matchers';
28
+ import * as malloy from '@malloydata/malloy';
28
29
 
29
30
  const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
30
31
 
31
- /*
32
- * This test file reuses common tests definitions.
33
- * For actual test development please go to: test/src/databases/shared/functions.spec.ts
34
- */
35
- functionsSharedTests(runtimes);
32
+ const expressionModelText = `
33
+ source: aircraft_models is table('malloytest.aircraft_models'){
34
+ primary_key: aircraft_model_code
35
+ }
36
+
37
+ source: aircraft is table('malloytest.aircraft'){
38
+ primary_key: tail_num
39
+ join_one: aircraft_models with aircraft_model_code
40
+ measure: aircraft_count is count()
41
+ }
42
+
43
+ source: airports is table('malloytest.airports') {}
44
+
45
+ source: state_facts is table('malloytest.state_facts') {}
46
+ `;
47
+
48
+ const expressionModels = new Map<string, malloy.ModelMaterializer>();
49
+ runtimes.runtimeMap.forEach((runtime, databaseName) =>
50
+ expressionModels.set(databaseName, runtime.loadModel(expressionModelText))
51
+ );
52
+
53
+ expressionModels.forEach((expressionModel, databaseName) => {
54
+ const funcTestGeneral = async (
55
+ expr: string,
56
+ type: 'group_by' | 'aggregate',
57
+ expected:
58
+ | {error: string; success?: undefined}
59
+ | {success: string | boolean | number | null; error?: undefined}
60
+ ) => {
61
+ const run = async () => {
62
+ return await expressionModel
63
+ .loadQuery(
64
+ `
65
+ query: aircraft -> { ${type}: f is ${expr} }`
66
+ )
67
+ .run();
68
+ };
69
+
70
+ if (expected.success !== undefined) {
71
+ const result = await run();
72
+ expect(result.data.path(0, 'f').value).toBe(expected.success);
73
+ } else {
74
+ expect(run).rejects.toThrowError(expected.error);
75
+ }
76
+ };
77
+
78
+ const funcTest = (expr: string, expexted: string | boolean | number | null) =>
79
+ funcTestGeneral(expr, 'group_by', {success: expexted});
80
+
81
+ const funcTestAgg = (
82
+ expr: string,
83
+ expexted: string | boolean | number | null
84
+ ) => funcTestGeneral(expr, 'aggregate', {success: expexted});
85
+
86
+ const funcTestMultiple = async (
87
+ ...testCases: [string, string | boolean | number | null][]
88
+ ) => {
89
+ const run = async () => {
90
+ return await expressionModel
91
+ .loadQuery(
92
+ `
93
+ query: aircraft -> { ${testCases.map(
94
+ (testCase, i) => `group_by: f${i} is ${testCase[0]}`
95
+ )} }`
96
+ )
97
+ .run();
98
+ };
99
+
100
+ const result = await run();
101
+ testCases.forEach((testCase, i) => {
102
+ expect(result.data.path(0, `f${i}`).value).toBe(testCase[1]);
103
+ });
104
+ };
105
+
106
+ describe('concat', () => {
107
+ it(`works - ${databaseName}`, async () => {
108
+ await funcTestMultiple(
109
+ ["concat('foo', 'bar')", 'foobar'],
110
+ ["concat(1, 'bar')", '1bar'],
111
+ [
112
+ "concat('cons', true)",
113
+ databaseName === 'postgres' ? 'const' : 'construe',
114
+ ],
115
+ ["concat('foo', @2003)", 'foo2003-01-01'],
116
+ [
117
+ "concat('foo', @2003-01-01 12:00:00)",
118
+ databaseName === 'bigquery'
119
+ ? 'foo2003-01-01 12:00:00+00'
120
+ : 'foo2003-01-01 12:00:00',
121
+ ],
122
+ // TODO Maybe implement consistent null behavior
123
+ // ["concat('foo', null)", null],
124
+ ['concat()', '']
125
+ );
126
+ });
127
+ });
128
+
129
+ describe('round', () => {
130
+ it(`works - ${databaseName}`, async () => {
131
+ await funcTestMultiple(
132
+ ['round(1.2)', 1],
133
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
134
+ // that are fixed in 0.8.
135
+ ...(databaseName === 'duckdb_wasm'
136
+ ? []
137
+ : ([['round(12.222, 1)', 12.2]] as [string, number][])),
138
+ ['round(12.2, -1)', 10],
139
+ ['round(null)', null],
140
+ ['round(1, null)', null]
141
+ );
142
+ });
143
+ });
144
+
145
+ describe('floor', () => {
146
+ it(`works - ${databaseName}`, async () => {
147
+ await funcTestMultiple(
148
+ ['floor(1.9)', 1],
149
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
150
+ // that are fixed in 0.8.
151
+ ...(databaseName === 'duckdb_wasm'
152
+ ? []
153
+ : ([['floor(-1.9)', -2]] as [string, number][])),
154
+ ['floor(null)', null]
155
+ );
156
+ await funcTest('floor(1.9)', 1);
157
+ });
158
+ });
159
+
160
+ describe('ceil', () => {
161
+ it(`works - ${databaseName}`, async () => {
162
+ await funcTestMultiple(
163
+ ['ceil(1.9)', 2],
164
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
165
+ // that are fixed in 0.8.
166
+ ...(databaseName === 'duckdb_wasm'
167
+ ? []
168
+ : ([['ceil(-1.9)', -1]] as [string, number][])),
169
+ ['ceil(null)', null]
170
+ );
171
+ });
172
+ });
173
+
174
+ describe('length', () => {
175
+ it(`works - ${databaseName}`, async () => {
176
+ await funcTestMultiple(["length('foo')", 3], ['length(null)', null]);
177
+ });
178
+ });
179
+
180
+ describe('lower', () => {
181
+ it(`works - ${databaseName}`, async () => {
182
+ await funcTestMultiple(["lower('FoO')", 'foo'], ['lower(null)', null]);
183
+ });
184
+ });
185
+
186
+ describe('upper', () => {
187
+ it(`works - ${databaseName}`, async () => {
188
+ await funcTestMultiple(["upper('fOo')", 'FOO'], ['upper(null)', null]);
189
+ });
190
+ });
191
+
192
+ describe('regexp_extract', () => {
193
+ it(`works - ${databaseName}`, async () => {
194
+ await funcTestMultiple(
195
+ ["regexp_extract('I have a dog', r'd[aeiou]g')", 'dog'],
196
+ ["regexp_extract(null, r'd[aeiou]g')", null],
197
+ ["regexp_extract('foo', null)", null],
198
+ ["regexp_extract('I have a d0g', r'd\\dg')", 'd0g']
199
+ );
200
+ });
201
+ });
202
+
203
+ describe('replace', () => {
204
+ it(`works - ${databaseName}`, async () => {
205
+ await funcTestMultiple(
206
+ ["replace('aaaa', 'a', 'c')", 'cccc'],
207
+ ["replace('aaaa', r'.', 'c')", 'cccc'],
208
+ [
209
+ "replace('axbxc', r'(a).(b).(c)', '\\\\0 - \\\\1 - \\\\2 - \\\\3')",
210
+ databaseName === 'postgres' ? '\\0 - a - b - c' : 'axbxc - a - b - c',
211
+ ],
212
+ ["replace('aaaa', '', 'c')", 'aaaa'],
213
+ ["replace(null, 'a', 'c')", null],
214
+ ["replace('aaaa', null, 'c')", null],
215
+ ["replace('aaaa', 'a', null)", null]
216
+ );
217
+ });
218
+ });
219
+
220
+ describe('substr', () => {
221
+ it(`works - ${databaseName}`, async () => {
222
+ await funcTestMultiple(
223
+ ["substr('foo', 2)", 'oo'],
224
+ ["substr('foo', 2, 1)", 'o'],
225
+ ["substr('foo bar baz', -3)", 'baz'],
226
+ ['substr(null, 1, 2)', null],
227
+ ["substr('aaaa', null, 1)", null],
228
+ ["substr('aaaa', 1, null)", null]
229
+ );
230
+ });
231
+ });
232
+
233
+ describe('raw function call', () => {
234
+ it(`works - ${databaseName}`, async () => {
235
+ await funcTestMultiple(
236
+ ['floor(cbrt!(27)::number)', 3],
237
+ ['floor(cbrt!number(27))', 3],
238
+ ["substr('foo bar baz', -3)", 'baz'],
239
+ ['substr(null, 1, 2)', null],
240
+ ["substr('aaaa', null, 1)", null],
241
+ ["substr('aaaa', 1, null)", null]
242
+ );
243
+ });
244
+ });
245
+
246
+ describe('stddev', () => {
247
+ // TODO symmetric aggregates don't work with custom aggregate functions in BQ currently
248
+ if (databaseName === 'bigquery') return;
249
+ it(`works - ${databaseName}`, async () => {
250
+ await funcTestAgg('round(stddev(aircraft_models.seats))', 29);
251
+ });
252
+
253
+ it(`works with struct - ${databaseName}`, async () => {
254
+ await funcTestAgg(
255
+ 'round(aircraft_models.stddev(aircraft_models.seats))',
256
+ 41
257
+ );
258
+ });
259
+
260
+ it(`works with implicit parameter - ${databaseName}`, async () => {
261
+ await funcTestAgg('round(aircraft_models.seats.stddev())', 41);
262
+ });
263
+
264
+ it(`works with filter - ${databaseName}`, async () => {
265
+ await funcTestAgg(
266
+ 'round(aircraft_models.seats.stddev() { where: 1 = 1 })',
267
+ 41
268
+ );
269
+ await funcTestAgg(
270
+ 'round(aircraft_models.seats.stddev() { where: aircraft_models.seats > 4 })',
271
+ 69
272
+ );
273
+ });
274
+ });
275
+
276
+ describe('row_number', () => {
277
+ it(`works when the order by is a dimension - ${databaseName}`, async () => {
278
+ const result = await expressionModel
279
+ .loadQuery(
280
+ `query: state_facts -> {
281
+ group_by: state
282
+ calculate: row_num is row_number()
283
+ }`
284
+ )
285
+ .run();
286
+ expect(result.data.path(0, 'row_num').value).toBe(1);
287
+ expect(result.data.path(1, 'row_num').value).toBe(2);
288
+ });
289
+
290
+ it(`works when the order by is a dimension in the other order - ${databaseName}`, async () => {
291
+ const result = await expressionModel
292
+ .loadQuery(
293
+ `query: state_facts -> {
294
+ calculate: row_num is row_number()
295
+ group_by: state
296
+ }`
297
+ )
298
+ .run();
299
+ expect(result.data.path(0, 'row_num').value).toBe(1);
300
+ expect(result.data.path(1, 'row_num').value).toBe(2);
301
+ });
302
+
303
+ it(`works when the order by is a measure - ${databaseName}`, async () => {
304
+ const result = await expressionModel
305
+ .loadQuery(
306
+ `query: state_facts -> {
307
+ group_by: popular_name
308
+ aggregate: c is count()
309
+ calculate: row_num is row_number()
310
+ }`
311
+ )
312
+ .run();
313
+ expect(result.data.path(0, 'row_num').value).toBe(1);
314
+ expect(result.data.path(1, 'row_num').value).toBe(2);
315
+ });
316
+
317
+ it(`works when the order by is a measure but there is no group by - ${databaseName}`, async () => {
318
+ const result = await expressionModel
319
+ .loadQuery(
320
+ `query: state_facts -> {
321
+ aggregate: c is count()
322
+ calculate: row_num is row_number()
323
+ }`
324
+ )
325
+ .run();
326
+ expect(result.data.path(0, 'row_num').value).toBe(1);
327
+ });
328
+
329
+ it(`works inside nest - ${databaseName}`, async () => {
330
+ const result = await expressionModel
331
+ .loadQuery(
332
+ `query: state_facts { join_one: airports on airports.state = state } -> {
333
+ group_by: state
334
+ nest: q is {
335
+ group_by: airports.county
336
+ calculate: row_num is row_number()
337
+ }
338
+ }`
339
+ )
340
+ .run();
341
+ expect(result.data.path(0, 'q', 0, 'row_num').value).toBe(1);
342
+ expect(result.data.path(0, 'q', 1, 'row_num').value).toBe(2);
343
+ expect(result.data.path(1, 'q', 0, 'row_num').value).toBe(1);
344
+ expect(result.data.path(1, 'q', 1, 'row_num').value).toBe(2);
345
+ });
346
+
347
+ test(`works outside nest, but with a nest nearby - ${databaseName}`, async () => {
348
+ const result = await expressionModel
349
+ .loadQuery(
350
+ `query: state_facts -> {
351
+ group_by: state
352
+ calculate: row_num is row_number()
353
+ nest: nested is {
354
+ group_by: state
355
+ }
356
+ }`
357
+ )
358
+ .run();
359
+ expect(result.data.path(0, 'row_num').value).toBe(1);
360
+ expect(result.data.path(1, 'row_num').value).toBe(2);
361
+ });
362
+ });
363
+
364
+ describe('rank', () => {
365
+ it(`works ordered by dimension - ${databaseName}`, async () => {
366
+ const result = await expressionModel
367
+ .loadQuery(
368
+ `query: state_facts -> {
369
+ group_by:
370
+ state,
371
+ births_ballpark is ceil(births / 1000000) * 1000000
372
+ order_by: births_ballpark desc
373
+ calculate: births_ballpark_rank is rank()
374
+ limit: 20
375
+ }`
376
+ )
377
+ .run({rowLimit: 20});
378
+ expect(result.data.path(0, 'births_ballpark_rank').value).toBe(1);
379
+ expect(result.data.path(1, 'births_ballpark_rank').value).toBe(2);
380
+ expect(result.data.path(8, 'births_ballpark_rank').value).toBe(9);
381
+ expect(result.data.path(9, 'births_ballpark_rank').value).toBe(9);
382
+ expect(result.data.path(10, 'births_ballpark_rank').value).toBe(9);
383
+ expect(result.data.path(11, 'births_ballpark_rank').value).toBe(12);
384
+ });
385
+
386
+ it(`works ordered by aggregate - ${databaseName}`, async () => {
387
+ const result = await expressionModel
388
+ .loadQuery(
389
+ `query: state_facts -> {
390
+ group_by: first_letter is substr(state, 1, 1)
391
+ aggregate: states_with_first_letter_ish is round(count() / 2) * 2
392
+ calculate: r is rank()
393
+ }`
394
+ )
395
+ .run();
396
+ expect(result.data.path(0, 'r').value).toBe(1);
397
+ expect(result.data.path(1, 'r').value).toBe(1);
398
+ expect(result.data.path(2, 'r').value).toBe(3);
399
+ expect(result.data.path(3, 'r').value).toBe(3);
400
+ });
401
+ });
402
+
403
+ describe('lag', () => {
404
+ it(`works with one param - ${databaseName}`, async () => {
405
+ const result = await expressionModel
406
+ .loadQuery(
407
+ `query: state_facts -> {
408
+ group_by: state
409
+ calculate: prev_state is lag(state)
410
+ }`
411
+ )
412
+ .run();
413
+ expect(result.data.path(0, 'state').value).toBe('AK');
414
+ expect(result.data.path(0, 'prev_state').value).toBe(null);
415
+ expect(result.data.path(1, 'prev_state').value).toBe('AK');
416
+ expect(result.data.path(1, 'state').value).toBe('AL');
417
+ expect(result.data.path(2, 'prev_state').value).toBe('AL');
418
+ });
419
+
420
+ it(`works with expression field - ${databaseName}`, async () => {
421
+ const result = await expressionModel
422
+ .loadQuery(
423
+ `query: state_facts -> {
424
+ group_by: lower_state is lower(state)
425
+ calculate: prev_state is lag(lower_state)
426
+ }`
427
+ )
428
+ .run();
429
+ expect(result.data.path(0, 'lower_state').value).toBe('ak');
430
+ expect(result.data.path(0, 'prev_state').value).toBe(null);
431
+ expect(result.data.path(1, 'prev_state').value).toBe('ak');
432
+ expect(result.data.path(1, 'lower_state').value).toBe('al');
433
+ expect(result.data.path(2, 'prev_state').value).toBe('al');
434
+ });
435
+
436
+ it(`works with expression - ${databaseName}`, async () => {
437
+ const result = await expressionModel
438
+ .loadQuery(
439
+ `query: state_facts -> {
440
+ group_by: state
441
+ calculate: prev_state is lag(lower(state))
442
+ }`
443
+ )
444
+ .run();
445
+ expect(result.data.path(0, 'state').value).toBe('AK');
446
+ expect(result.data.path(0, 'prev_state').value).toBe(null);
447
+ expect(result.data.path(1, 'prev_state').value).toBe('ak');
448
+ expect(result.data.path(1, 'state').value).toBe('AL');
449
+ expect(result.data.path(2, 'prev_state').value).toBe('al');
450
+ });
451
+
452
+ it(`works with field, ordering by expression field - ${databaseName}`, async () => {
453
+ const result = await expressionModel
454
+ .loadQuery(
455
+ `query: state_facts -> {
456
+ group_by: lower_state is lower(state)
457
+ aggregate: c is count()
458
+ order_by: lower_state
459
+ calculate: prev_count is lag(c)
460
+ }`
461
+ )
462
+ .run();
463
+ expect(result.data.path(0, 'lower_state').value).toBe('ak');
464
+ expect(result.data.path(0, 'prev_count').value).toBe(null);
465
+ expect(result.data.path(1, 'prev_count').value).toBe(1);
466
+ expect(result.data.path(1, 'lower_state').value).toBe('al');
467
+ expect(result.data.path(2, 'prev_count').value).toBe(1);
468
+ });
469
+
470
+ it(`works with offset - ${databaseName}`, async () => {
471
+ const result = await expressionModel
472
+ .loadQuery(
473
+ `query: state_facts -> {
474
+ group_by: state
475
+ calculate: prev_prev_state is lag(state, 2)
476
+ }`
477
+ )
478
+ .run();
479
+ expect(result.data.path(0, 'state').value).toBe('AK');
480
+ expect(result.data.path(0, 'prev_prev_state').value).toBe(null);
481
+ expect(result.data.path(1, 'prev_prev_state').value).toBe(null);
482
+ expect(result.data.path(2, 'prev_prev_state').value).toBe('AK');
483
+ expect(result.data.path(1, 'state').value).toBe('AL');
484
+ expect(result.data.path(3, 'prev_prev_state').value).toBe('AL');
485
+ });
486
+
487
+ it(`works with default value - ${databaseName}`, async () => {
488
+ const result = await expressionModel
489
+ .loadQuery(
490
+ `query: state_facts -> {
491
+ group_by: state
492
+ calculate: prev_state is lag(state, 1, 'NONE')
493
+ }`
494
+ )
495
+ .run();
496
+ expect(result.data.path(0, 'prev_state').value).toBe('NONE');
497
+ });
498
+
499
+ it(`works with now as the default value - ${databaseName}`, async () => {
500
+ const result = await expressionModel
501
+ .loadQuery(
502
+ `
503
+ query: state_facts -> {
504
+ group_by: state
505
+ calculate: lag_val is lag(@2011-11-11 11:11:11, 1, now).year = now.year
506
+ }`
507
+ )
508
+ .run();
509
+ expect(result.data.path(0, 'lag_val').value).toBe(true);
510
+ expect(result.data.path(1, 'lag_val').value).toBe(false);
511
+ });
512
+ });
513
+
514
+ describe('output field in calculate', () => {
515
+ it(`dotted aggregates work with an output field - ${databaseName}`, async () => {
516
+ const result = await expressionModel
517
+ .loadQuery(
518
+ `query: aircraft -> {
519
+ group_by: aircraft_models.seats
520
+ aggregate: s is aircraft_models.seats.sum()
521
+ calculate: a is lag(seats.sum())
522
+ }`
523
+ )
524
+ .run();
525
+ expect(result.data.path(1, 'a').value).toBe(
526
+ result.data.path(0, 's').value
527
+ );
528
+ });
529
+ });
530
+
531
+ describe('first_value', () => {
532
+ test(`works in nest - ${databaseName}`, async () => {
533
+ const result = await expressionModel
534
+ .loadQuery(
535
+ `
536
+ query: aircraft -> {
537
+ group_by: state
538
+ where: state != null
539
+ nest: by_county is {
540
+ limit: 2
541
+ group_by: county
542
+ aggregate: aircraft_count
543
+ calculate: first_count is first_value(count())
544
+ }
545
+ }`
546
+ )
547
+ .run();
548
+ expect(result.data.path(0, 'by_county', 1, 'first_count').value).toBe(
549
+ result.data.path(0, 'by_county', 0, 'aircraft_count').value
550
+ );
551
+ expect(result.data.path(1, 'by_county', 1, 'first_count').value).toBe(
552
+ result.data.path(1, 'by_county', 0, 'aircraft_count').value
553
+ );
554
+ });
555
+ it(`works outside nest - ${databaseName}`, async () => {
556
+ const result = await expressionModel
557
+ .loadQuery(
558
+ `
559
+ query: state_facts -> {
560
+ group_by: state, births
561
+ order_by: births desc
562
+ calculate: most_births is first_value(births)
563
+ }`
564
+ )
565
+ .run();
566
+ const firstBirths = result.data.path(0, 'births').value;
567
+ expect(result.data.path(0, 'most_births').value).toBe(firstBirths);
568
+ expect(result.data.path(1, 'most_births').value).toBe(firstBirths);
569
+ });
570
+ it(`works with an aggregate which is not in the query - ${databaseName}`, async () => {
571
+ const result = await expressionModel
572
+ .loadQuery(
573
+ `
574
+ query: airports { measure: airport_count is count() } -> {
575
+ group_by: state
576
+ where: state != null
577
+ calculate: prev_airport_count is lag(airport_count)
578
+ }`
579
+ )
580
+ .run();
581
+ expect(result.data.path(0, 'prev_airport_count').value).toBe(null);
582
+ expect(result.data.path(1, 'prev_airport_count').value).toBe(608);
583
+ expect(result.data.path(2, 'prev_airport_count').value).toBe(260);
584
+ });
585
+ it(`works with a localized aggregate - ${databaseName}`, async () => {
586
+ const result = await expressionModel
587
+ .loadQuery(
588
+ `
589
+ query: aircraft -> {
590
+ group_by: aircraft_models.seats,
591
+ calculate: prev_sum_of_seats is lag(aircraft_models.seats.sum())
592
+ }`
593
+ )
594
+ .run();
595
+ expect(result.data.path(0, 'prev_sum_of_seats').value).toBe(null);
596
+ expect(result.data.path(1, 'prev_sum_of_seats').value).toBe(0);
597
+ expect(result.data.path(2, 'prev_sum_of_seats').value).toBe(230);
598
+ });
599
+ });
600
+
601
+ describe('trunc', () => {
602
+ it(`works - ${databaseName}`, async () => {
603
+ await funcTestMultiple(
604
+ ['trunc(1.9)', 1],
605
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
606
+ // that are fixed in 0.8.
607
+ ...(databaseName === 'duckdb_wasm'
608
+ ? []
609
+ : ([['trunc(-1.9)', -1]] as [string, number][])),
610
+ ['trunc(12.29, 1)', 12.2],
611
+ ['trunc(19.2, -1)', 10],
612
+ ['trunc(null)', null],
613
+ ['trunc(1, null)', null]
614
+ );
615
+ });
616
+ });
617
+ describe('log', () => {
618
+ it(`works - ${databaseName}`, async () => {
619
+ await funcTestMultiple(
620
+ ['log(10, 10)', 1],
621
+ ['log(100, 10)', 2],
622
+ ['log(32, 2)', 5],
623
+ ['log(null, 2)', null],
624
+ ['log(10, null)', null]
625
+ );
626
+ });
627
+ });
628
+ describe('ln', () => {
629
+ it(`works - ${databaseName}`, async () => {
630
+ await funcTestMultiple(
631
+ ['ln(exp(1))', 1],
632
+ ['ln(exp(2))', 2],
633
+ ['ln(null)', null]
634
+ );
635
+ });
636
+ });
637
+ describe('exp', () => {
638
+ it(`works - ${databaseName}`, async () => {
639
+ await funcTestMultiple(
640
+ ['exp(0)', 1],
641
+ ['ln(exp(1))', 1],
642
+ ['exp(null)', null]
643
+ );
644
+ });
645
+ });
646
+
647
+ // TODO trig functions could have more exhaustive tests -- these are mostly just here to
648
+ // ensure they exist
649
+ describe('cos', () => {
650
+ it(`works - ${databaseName}`, async () => {
651
+ await funcTestMultiple(['cos(0)', 1], ['cos(null)', null]);
652
+ });
653
+ });
654
+ describe('acos', () => {
655
+ it(`works - ${databaseName}`, async () => {
656
+ await funcTestMultiple(['acos(1)', 0], ['acos(null)', null]);
657
+ });
658
+ });
659
+
660
+ describe('sin', () => {
661
+ it(`works - ${databaseName}`, async () => {
662
+ await funcTestMultiple(['sin(0)', 0], ['sin(null)', null]);
663
+ });
664
+ });
665
+ describe('asin', () => {
666
+ it(`works - ${databaseName}`, async () => {
667
+ await funcTestMultiple(['asin(0)', 0], ['asin(null)', null]);
668
+ });
669
+ });
670
+
671
+ describe('tan', () => {
672
+ it(`works - ${databaseName}`, async () => {
673
+ await funcTestMultiple(['tan(0)', 0], ['tan(null)', null]);
674
+ });
675
+ });
676
+ describe('atan', () => {
677
+ it(`works - ${databaseName}`, async () => {
678
+ await funcTestMultiple(['atan(0)', 0], ['atan(null)', null]);
679
+ });
680
+ });
681
+ describe('atan2', () => {
682
+ it(`works - ${databaseName}`, async () => {
683
+ await funcTestMultiple(
684
+ ['atan2(0, 1)', 0],
685
+ ['atan2(null, 1)', null],
686
+ ['atan2(1, null)', null]
687
+ );
688
+ });
689
+ });
690
+ describe('sqrt', () => {
691
+ it(`works - ${databaseName}`, async () => {
692
+ await funcTestMultiple(
693
+ ['sqrt(9)', 3],
694
+ ['sqrt(6.25)', 2.5],
695
+ ['sqrt(null)', null]
696
+ );
697
+ });
698
+ });
699
+ describe('pow', () => {
700
+ it(`works - ${databaseName}`, async () => {
701
+ await funcTestMultiple(
702
+ ['pow(2, 3)', 8],
703
+ ['pow(null, 3)', null],
704
+ ['pow(2, null)', null]
705
+ );
706
+ });
707
+ });
708
+ describe('abs', () => {
709
+ it(`works - ${databaseName}`, async () => {
710
+ await funcTestMultiple(
711
+ ['abs(-3)', 3],
712
+ ['abs(3)', 3],
713
+ ['abs(null)', null]
714
+ );
715
+ });
716
+ });
717
+ describe('sign', () => {
718
+ it(`works - ${databaseName}`, async () => {
719
+ await funcTestMultiple(
720
+ ['sign(100)', 1],
721
+ ['sign(-2)', -1],
722
+ ['sign(0)', 0],
723
+ ['sign(null)', null]
724
+ );
725
+ });
726
+ });
727
+ describe('is_inf', () => {
728
+ it(`works - ${databaseName}`, async () => {
729
+ await funcTestMultiple(
730
+ ["is_inf('+inf'::number)", true],
731
+ ['is_inf(100)', false],
732
+ ['is_inf(null)', false]
733
+ );
734
+ });
735
+ });
736
+ describe('is_nan', () => {
737
+ it(`works - ${databaseName}`, async () => {
738
+ await funcTestMultiple(
739
+ ["is_nan('NaN'::number)", true],
740
+ ['is_nan(100)', false],
741
+ ['is_nan(null)', false]
742
+ );
743
+ });
744
+ });
745
+ describe('greatest', () => {
746
+ it(`works - ${databaseName}`, async () => {
747
+ await funcTestMultiple(
748
+ ['greatest(1, 10, -100)', 10],
749
+ ['greatest(@2003, @2004, @1994) = @2004', true],
750
+ [
751
+ 'greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00',
752
+ true,
753
+ ],
754
+ ["greatest('a', 'b')", 'b'],
755
+ ['greatest(1, null, 0)', null],
756
+ ['greatest(null, null)', null]
757
+ );
758
+ });
759
+ });
760
+ describe('least', () => {
761
+ it(`works - ${databaseName}`, async () => {
762
+ await funcTestMultiple(
763
+ ['least(1, 10, -100)', -100],
764
+ ['least(@2003, @2004, @1994) = @1994', true],
765
+ [
766
+ 'least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00',
767
+ true,
768
+ ],
769
+ ["least('a', 'b')", 'a'],
770
+ ['least(1, null, 0)', null],
771
+ ['least(null, null)', null]
772
+ );
773
+ });
774
+ });
775
+ describe('div', () => {
776
+ it(`works - ${databaseName}`, async () => {
777
+ await funcTestMultiple(
778
+ ['div(3, 2)', 1],
779
+ ['div(null, 2)', null],
780
+ ['div(2, null)', null]
781
+ );
782
+ });
783
+ });
784
+ describe('strpos', () => {
785
+ it(`works - ${databaseName}`, async () => {
786
+ await funcTestMultiple(
787
+ ["strpos('123456789', '3')", 3],
788
+ ["strpos('123456789', '0')", 0],
789
+ ["strpos(null, '0')", null],
790
+ ["strpos('123456789', null)", null]
791
+ );
792
+ });
793
+ });
794
+ describe('starts_with', () => {
795
+ it(`works - ${databaseName}`, async () => {
796
+ await funcTestMultiple(
797
+ ["starts_with('hello world', 'hello')", true],
798
+ ["starts_with('hello world', 'world')", false],
799
+ ["starts_with(null, 'world')", false],
800
+ ["starts_with('hello world', null)", false]
801
+ );
802
+ });
803
+ });
804
+ describe('ends_with', () => {
805
+ it(`works - ${databaseName}`, async () => {
806
+ await funcTestMultiple(
807
+ ["ends_with('hello world', 'world')", true],
808
+ ["ends_with('hello world', 'hello')", false],
809
+ ["ends_with(null, 'world')", false],
810
+ ["ends_with('hello world', null)", false]
811
+ );
812
+ });
813
+ });
814
+ describe('trim', () => {
815
+ it(`works - ${databaseName}`, async () => {
816
+ await funcTestMultiple(
817
+ ["trim(' keep this ')", 'keep this'],
818
+ ["trim('_ _keep_this_ _', '_ ')", 'keep_this'],
819
+ ["trim(' keep everything ', '')", ' keep everything '],
820
+ ["trim('null example', null)", null],
821
+ ["trim(null, 'a')", null],
822
+ ['trim(null)', null]
823
+ );
824
+ });
825
+ });
826
+ describe('ltrim', () => {
827
+ it(`works - ${databaseName}`, async () => {
828
+ await funcTestMultiple(
829
+ ["ltrim(' keep this -> ')", 'keep this -> '],
830
+ ["ltrim('_ _keep_this -> _ _', '_ ')", 'keep_this -> _ _'],
831
+ ["ltrim(' keep everything ', '')", ' keep everything '],
832
+ ["ltrim('null example', null)", null],
833
+ ["ltrim(null, 'a')", null],
834
+ ['ltrim(null)', null]
835
+ );
836
+ });
837
+ });
838
+ describe('rtrim', () => {
839
+ it(`works - ${databaseName}`, async () => {
840
+ await funcTestMultiple(
841
+ ["rtrim(' <- keep this ')", ' <- keep this'],
842
+ ["rtrim('_ _ <- keep_this_ _', '_ ')", '_ _ <- keep_this'],
843
+ ["rtrim(' keep everything ', '')", ' keep everything '],
844
+ ["rtrim('null example', null)", null],
845
+ ["rtrim(null, 'a')", null],
846
+ ['rtrim(null)', null]
847
+ );
848
+ });
849
+ });
850
+ describe('rand', () => {
851
+ it(`is usually not the same value - ${databaseName}`, async () => {
852
+ // There are around a billion values that rand() can be, so if this
853
+ // test fails, most likely something is broken. Otherwise, you're the lucky
854
+ // one in a billion!
855
+ await funcTest('rand() = rand()', false);
856
+ });
857
+ });
858
+ describe('pi', () => {
859
+ it(`is pi - ${databaseName}`, async () => {
860
+ await funcTest('abs(pi() - 3.141592653589793) < 0.0000000000001', true);
861
+ });
862
+ });
863
+
864
+ describe('byte_length', () => {
865
+ it(`works - ${databaseName}`, async () => {
866
+ await funcTestMultiple(
867
+ ["byte_length('hello')", 5],
868
+ ["byte_length('©')", 2],
869
+ ['byte_length(null)', null]
870
+ );
871
+ });
872
+ });
873
+ describe('ifnull', () => {
874
+ it(`works - ${databaseName}`, async () => {
875
+ await funcTestMultiple(
876
+ ["ifnull('a', 'b')", 'a'],
877
+ ["ifnull(null, 'b')", 'b'],
878
+ ["ifnull('a', null)", 'a'],
879
+ ['ifnull(null, null)', null]
880
+ );
881
+ });
882
+ });
883
+ describe('coalesce', () => {
884
+ it(`works - ${databaseName}`, async () => {
885
+ await funcTestMultiple(
886
+ ["coalesce('a')", 'a'],
887
+ ["coalesce('a', 'b')", 'a'],
888
+ ["coalesce(null, 'a', 'b')", 'a'],
889
+ ["coalesce(null, 'b')", 'b'],
890
+ ["coalesce('a', null)", 'a'],
891
+ ['coalesce(null, null)', null],
892
+ ['coalesce(null)', null]
893
+ );
894
+ });
895
+ });
896
+ describe('nullif', () => {
897
+ it(`works - ${databaseName}`, async () => {
898
+ await funcTestMultiple(
899
+ ["nullif('a', 'a')", null],
900
+ ["nullif('a', 'b')", 'a'],
901
+ ["nullif('a', null)", 'a'],
902
+ ['nullif(null, null)', null],
903
+ ['nullif(null, 2)', null]
904
+ );
905
+ });
906
+ });
907
+ describe('chr', () => {
908
+ it(`works - ${databaseName}`, async () => {
909
+ await funcTestMultiple(
910
+ ['chr(65)', 'A'],
911
+ ['chr(255)', 'ÿ'],
912
+ ['chr(null)', null],
913
+ // BigQuery's documentation says that `chr(0)` returns the empty string, but it doesn't,
914
+ // it actually returns the null character. We generate code so that it does this.
915
+ ['chr(0)', '']
916
+ );
917
+ });
918
+ });
919
+ describe('ascii', () => {
920
+ it(`works - ${databaseName}`, async () => {
921
+ await funcTestMultiple(
922
+ ["ascii('A')", 65],
923
+ ["ascii('ABC')", 65],
924
+ ["ascii('')", 0],
925
+ ['ascii(null)', null]
926
+ );
927
+ });
928
+ });
929
+ describe('unicode', () => {
930
+ it(`works - ${databaseName}`, async () => {
931
+ await funcTestMultiple(
932
+ ["unicode('A')", 65],
933
+ ["unicode('â')", 226],
934
+ ["unicode('âBC')", 226],
935
+ ["unicode('')", 0],
936
+ ['unicode(null)', null]
937
+ );
938
+ });
939
+ });
940
+
941
+ describe('repeat', () => {
942
+ it(`works - ${databaseName}`, async () => {
943
+ await funcTestMultiple(
944
+ ["repeat('foo', 0)", ''],
945
+ ["repeat('foo', 1)", 'foo'],
946
+ ["repeat('foo', 2)", 'foofoo'],
947
+ ['repeat(null, 2)', null],
948
+ ["repeat('foo', null)", null]
949
+ );
950
+ });
951
+ // TODO how does a user do this: the second argument needs to be an integer, but floor doesn't cast to "integer" type.
952
+ it.skip(`works floor decimal - ${databaseName}`, async () => {
953
+ await funcTest("repeat('foo', floor(2.5))", 'foofoo');
954
+ });
955
+ // undefined behavior when negative, undefined behavior (likely error) when non-integer
956
+ });
957
+ describe('reverse', () => {
958
+ it(`works - ${databaseName}`, async () => {
959
+ await funcTestMultiple(
960
+ ["reverse('foo')", 'oof'],
961
+ ["reverse('')", ''],
962
+ ['reverse(null)', null]
963
+ );
964
+ });
965
+ });
966
+
967
+ describe('lead', () => {
968
+ it(`works with one param - ${databaseName}`, async () => {
969
+ const result = await expressionModel
970
+ .loadQuery(
971
+ `query: state_facts -> {
972
+ group_by: state
973
+ calculate: next_state is lead(state)
974
+ }`
975
+ )
976
+ .run();
977
+ expect(result.data.path(0, 'state').value).toBe('AK');
978
+ expect(result.data.path(0, 'next_state').value).toBe('AL');
979
+ expect(result.data.path(1, 'state').value).toBe('AL');
980
+ });
981
+
982
+ it(`works with offset - ${databaseName}`, async () => {
983
+ const result = await expressionModel
984
+ .loadQuery(
985
+ `query: state_facts -> {
986
+ group_by: state
987
+ calculate: next_next_state is lead(state, 2)
988
+ }`
989
+ )
990
+ .run();
991
+ expect(result.data.path(0, 'state').value).toBe('AK');
992
+ expect(result.data.path(0, 'next_next_state').value).toBe('AR');
993
+ expect(result.data.path(1, 'next_next_state').value).toBe('AZ');
994
+ expect(result.data.path(1, 'state').value).toBe('AL');
995
+ expect(result.data.path(2, 'state').value).toBe('AR');
996
+ expect(result.data.path(3, 'state').value).toBe('AZ');
997
+ });
998
+
999
+ it(`works with default value - ${databaseName}`, async () => {
1000
+ const result = await expressionModel
1001
+ .loadQuery(
1002
+ `query: state_facts -> { project: *; limit: 10 } -> {
1003
+ group_by: state
1004
+ calculate: next_state is lead(state, 1, 'NONE')
1005
+ }`
1006
+ )
1007
+ .run();
1008
+ expect(result.data.path(9, 'next_state').value).toBe('NONE');
1009
+ });
1010
+ });
1011
+ describe('last_value', () => {
1012
+ it(`works - ${databaseName}`, async () => {
1013
+ const result = await expressionModel
1014
+ .loadQuery(
1015
+ `
1016
+ query: state_facts -> {
1017
+ group_by: state, births
1018
+ order_by: births desc
1019
+ calculate: least_births is last_value(births)
1020
+ }`
1021
+ )
1022
+ .run({rowLimit: 100});
1023
+ const numRows = result.data.toObject().length;
1024
+ const lastBirths = result.data.path(numRows - 1, 'births').value;
1025
+ expect(result.data.path(0, 'least_births').value).toBe(lastBirths);
1026
+ expect(result.data.path(1, 'least_births').value).toBe(lastBirths);
1027
+ });
1028
+ });
1029
+ describe('avg_moving', () => {
1030
+ it(`works - ${databaseName}`, async () => {
1031
+ const result = await expressionModel
1032
+ .loadQuery(
1033
+ `
1034
+ query: state_facts -> {
1035
+ group_by: state, births
1036
+ order_by: births desc
1037
+ calculate: rolling_avg is avg_moving(births, 2)
1038
+ }`
1039
+ )
1040
+ .run({rowLimit: 100});
1041
+ const births0 = result.data.path(0, 'births').number.value;
1042
+ const births1 = result.data.path(1, 'births').number.value;
1043
+ const births2 = result.data.path(2, 'births').number.value;
1044
+ const births3 = result.data.path(3, 'births').number.value;
1045
+ expect(result.data.path(0, 'rolling_avg').number.value).toBe(births0);
1046
+ expect(Math.floor(result.data.path(1, 'rolling_avg').number.value)).toBe(
1047
+ Math.floor((births0 + births1) / 2)
1048
+ );
1049
+ expect(Math.floor(result.data.path(2, 'rolling_avg').number.value)).toBe(
1050
+ Math.floor((births0 + births1 + births2) / 3)
1051
+ );
1052
+ expect(Math.floor(result.data.path(3, 'rolling_avg').number.value)).toBe(
1053
+ Math.floor((births1 + births2 + births3) / 3)
1054
+ );
1055
+ });
1056
+
1057
+ it(`works forward - ${databaseName}`, async () => {
1058
+ const result = await expressionModel
1059
+ .loadQuery(
1060
+ `
1061
+ query: state_facts -> { project: *; limit: 3 } -> {
1062
+ group_by: state, births
1063
+ order_by: births desc
1064
+ calculate: rolling_avg is avg_moving(births, 0, 2)
1065
+ }`
1066
+ )
1067
+ .run({rowLimit: 100});
1068
+ const births0 = result.data.path(0, 'births').number.value;
1069
+ const births1 = result.data.path(1, 'births').number.value;
1070
+ const births2 = result.data.path(2, 'births').number.value;
1071
+ expect(Math.floor(result.data.path(0, 'rolling_avg').number.value)).toBe(
1072
+ Math.floor((births0 + births1 + births2) / 3)
1073
+ );
1074
+ expect(Math.floor(result.data.path(1, 'rolling_avg').number.value)).toBe(
1075
+ Math.floor((births1 + births2) / 2)
1076
+ );
1077
+ expect(result.data.path(2, 'rolling_avg').number.value).toBe(births2);
1078
+ });
1079
+ });
1080
+ describe('min, max, sum / window, cumulative', () => {
1081
+ it(`works - ${databaseName}`, async () => {
1082
+ const result = await expressionModel
1083
+ .loadQuery(
1084
+ `
1085
+ query: state_facts -> { project: *; limit: 5 } -> {
1086
+ group_by: state, births
1087
+ order_by: births asc
1088
+ calculate: min_c is min_cumulative(births)
1089
+ calculate: max_c is max_cumulative(births)
1090
+ calculate: sum_c is sum_cumulative(births)
1091
+ calculate: min_w is min_window(births)
1092
+ calculate: max_w is max_window(births)
1093
+ calculate: sum_w is sum_window(births)
1094
+ }`
1095
+ )
1096
+ .run({rowLimit: 100});
1097
+ const births0 = result.data.path(0, 'births').number.value;
1098
+ const births1 = result.data.path(1, 'births').number.value;
1099
+ const births2 = result.data.path(2, 'births').number.value;
1100
+ const births3 = result.data.path(3, 'births').number.value;
1101
+ const births4 = result.data.path(4, 'births').number.value;
1102
+ const births = [births0, births1, births2, births3, births4];
1103
+ for (let r = 0; r < 5; r++) {
1104
+ expect(result.data.path(r, 'min_c').number.value).toBe(births0);
1105
+ expect(result.data.path(r, 'max_c').number.value).toBe(births[r]);
1106
+ expect(result.data.path(r, 'sum_c').number.value).toBe(
1107
+ births.slice(0, r + 1).reduce((a, b) => a + b)
1108
+ );
1109
+ expect(result.data.path(r, 'min_w').number.value).toBe(births0);
1110
+ expect(result.data.path(r, 'max_w').number.value).toBe(births4);
1111
+ expect(result.data.path(r, 'sum_w').number.value).toBe(
1112
+ births.reduce((a, b) => a + b)
1113
+ );
1114
+ }
1115
+ });
1116
+ });
1117
+ });
1118
+
1119
+ afterAll(async () => {
1120
+ await runtimes.closeAll();
1121
+ });