@malloydata/malloy-tests 0.0.135 → 0.0.136-dev240326234246

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 (118) hide show
  1. package/dist/api.spec.d.ts +1 -0
  2. package/dist/api.spec.js +113 -0
  3. package/dist/api.spec.js.map +1 -0
  4. package/dist/databases/all/db_index.spec.d.ts +1 -0
  5. package/dist/databases/all/db_index.spec.js +126 -0
  6. package/dist/databases/all/db_index.spec.js.map +1 -0
  7. package/dist/databases/all/expr.spec.d.ts +1 -0
  8. package/dist/databases/all/expr.spec.js +652 -0
  9. package/dist/databases/all/expr.spec.js.map +1 -0
  10. package/dist/databases/all/functions.spec.d.ts +1 -0
  11. package/dist/databases/all/functions.spec.js +1180 -0
  12. package/dist/databases/all/functions.spec.js.map +1 -0
  13. package/dist/databases/all/join.spec.d.ts +1 -0
  14. package/dist/databases/all/join.spec.js +255 -0
  15. package/dist/databases/all/join.spec.js.map +1 -0
  16. package/dist/databases/all/lenses.spec.d.ts +1 -0
  17. package/dist/databases/all/lenses.spec.js +374 -0
  18. package/dist/databases/all/lenses.spec.js.map +1 -0
  19. package/dist/databases/all/nomodel.spec.d.ts +1 -0
  20. package/dist/databases/all/nomodel.spec.js +1070 -0
  21. package/dist/databases/all/nomodel.spec.js.map +1 -0
  22. package/dist/databases/all/orderby.spec.d.ts +1 -0
  23. package/dist/databases/all/orderby.spec.js +170 -0
  24. package/dist/databases/all/orderby.spec.js.map +1 -0
  25. package/dist/databases/all/problems.spec.d.ts +1 -0
  26. package/dist/databases/all/problems.spec.js +106 -0
  27. package/dist/databases/all/problems.spec.js.map +1 -0
  28. package/dist/databases/all/sql_expressions.spec.d.ts +1 -0
  29. package/dist/databases/all/sql_expressions.spec.js +73 -0
  30. package/dist/databases/all/sql_expressions.spec.js.map +1 -0
  31. package/dist/databases/all/time.spec.d.ts +1 -0
  32. package/dist/databases/all/time.spec.js +602 -0
  33. package/dist/databases/all/time.spec.js.map +1 -0
  34. package/dist/databases/bigquery/double_truncation.spec.d.ts +1 -0
  35. package/dist/databases/bigquery/double_truncation.spec.js +50 -0
  36. package/dist/databases/bigquery/double_truncation.spec.js.map +1 -0
  37. package/dist/databases/bigquery/handexpr.spec.d.ts +1 -0
  38. package/dist/databases/bigquery/handexpr.spec.js +723 -0
  39. package/dist/databases/bigquery/handexpr.spec.js.map +1 -0
  40. package/dist/databases/bigquery/injestion_time_partitioning.spec.d.ts +1 -0
  41. package/dist/databases/bigquery/injestion_time_partitioning.spec.js +235 -0
  42. package/dist/databases/bigquery/injestion_time_partitioning.spec.js.map +1 -0
  43. package/dist/databases/bigquery/joined_filters.spec.d.ts +1 -0
  44. package/dist/databases/bigquery/joined_filters.spec.js +72 -0
  45. package/dist/databases/bigquery/joined_filters.spec.js.map +1 -0
  46. package/dist/databases/bigquery/json.spec.d.ts +1 -0
  47. package/dist/databases/bigquery/json.spec.js +66 -0
  48. package/dist/databases/bigquery/json.spec.js.map +1 -0
  49. package/dist/databases/bigquery/malloy_query.spec.d.ts +1 -0
  50. package/dist/databases/bigquery/malloy_query.spec.js +840 -0
  51. package/dist/databases/bigquery/malloy_query.spec.js.map +1 -0
  52. package/dist/databases/bigquery/performance.skipped.spec.d.ts +1 -0
  53. package/dist/databases/bigquery/performance.skipped.spec.js +70 -0
  54. package/dist/databases/bigquery/performance.skipped.spec.js.map +1 -0
  55. package/dist/databases/bigquery/time.spec.d.ts +1 -0
  56. package/dist/databases/bigquery/time.spec.js +52 -0
  57. package/dist/databases/bigquery/time.spec.js.map +1 -0
  58. package/dist/databases/bigquery/wildcard_table_names.spec.d.ts +1 -0
  59. package/dist/databases/bigquery/wildcard_table_names.spec.js +212 -0
  60. package/dist/databases/bigquery/wildcard_table_names.spec.js.map +1 -0
  61. package/dist/databases/bigquery-duckdb/nested_source_table.spec.d.ts +1 -0
  62. package/dist/databases/bigquery-duckdb/nested_source_table.spec.js +213 -0
  63. package/dist/databases/bigquery-duckdb/nested_source_table.spec.js.map +1 -0
  64. package/dist/databases/duckdb/duckdb.spec.d.ts +1 -0
  65. package/dist/databases/duckdb/duckdb.spec.js +124 -0
  66. package/dist/databases/duckdb/duckdb.spec.js.map +1 -0
  67. package/dist/databases/duckdb/streaming.spec.d.ts +1 -0
  68. package/dist/databases/duckdb/streaming.spec.js +142 -0
  69. package/dist/databases/duckdb/streaming.spec.js.map +1 -0
  70. package/dist/databases/multi-connection/multi_connection.spec.d.ts +1 -0
  71. package/dist/databases/multi-connection/multi_connection.spec.js +120 -0
  72. package/dist/databases/multi-connection/multi_connection.spec.js.map +1 -0
  73. package/dist/databases/postgres/postgres.spec.d.ts +1 -0
  74. package/dist/databases/postgres/postgres.spec.js +140 -0
  75. package/dist/databases/postgres/postgres.spec.js.map +1 -0
  76. package/dist/databases/shared/test_list.d.ts +3 -0
  77. package/dist/databases/shared/test_list.js +5 -0
  78. package/dist/databases/shared/test_list.js.map +1 -0
  79. package/dist/databases/streaming/streaming.spec.d.ts +1 -0
  80. package/dist/databases/streaming/streaming.spec.js +93 -0
  81. package/dist/databases/streaming/streaming.spec.js.map +1 -0
  82. package/dist/dependencies.spec.d.ts +1 -0
  83. package/dist/dependencies.spec.js +63 -0
  84. package/dist/dependencies.spec.js.map +1 -0
  85. package/dist/index.d.ts +4 -0
  86. package/dist/index.js +49 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/jestMatcher.spec.d.ts +1 -0
  89. package/dist/jestMatcher.spec.js +81 -0
  90. package/dist/jestMatcher.spec.js.map +1 -0
  91. package/dist/model/sql_source.spec.d.ts +1 -0
  92. package/dist/model/sql_source.spec.js +47 -0
  93. package/dist/model/sql_source.spec.js.map +1 -0
  94. package/dist/models/faa_model.d.ts +5 -0
  95. package/dist/models/faa_model.js +997 -0
  96. package/dist/models/faa_model.js.map +1 -0
  97. package/dist/models/medicare_model.d.ts +4 -0
  98. package/dist/models/medicare_model.js +259 -0
  99. package/dist/models/medicare_model.js.map +1 -0
  100. package/dist/render/drill.spec.d.ts +1 -0
  101. package/dist/render/drill.spec.js +107 -0
  102. package/dist/render/drill.spec.js.map +1 -0
  103. package/dist/render/render.spec.d.ts +1 -0
  104. package/dist/render/render.spec.js +548 -0
  105. package/dist/render/render.spec.js.map +1 -0
  106. package/dist/runtimes.d.ts +35 -0
  107. package/dist/runtimes.js +180 -0
  108. package/dist/runtimes.js.map +1 -0
  109. package/dist/tags.spec.d.ts +8 -0
  110. package/dist/tags.spec.js +490 -0
  111. package/dist/tags.spec.js.map +1 -0
  112. package/dist/util/db-jest-matchers.d.ts +30 -0
  113. package/dist/util/db-jest-matchers.js +157 -0
  114. package/dist/util/db-jest-matchers.js.map +1 -0
  115. package/dist/util/index.d.ts +15 -0
  116. package/dist/util/index.js +182 -0
  117. package/dist/util/index.js.map +1 -0
  118. package/package.json +8 -8
@@ -0,0 +1,1180 @@
1
+ "use strict";
2
+ /* eslint-disable no-console */
3
+ /*
4
+ * Copyright 2023 Google LLC
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining
7
+ * a copy of this software and associated documentation files
8
+ * (the "Software"), to deal in the Software without restriction,
9
+ * including without limitation the rights to use, copy, modify, merge,
10
+ * publish, distribute, sublicense, and/or sell copies of the Software,
11
+ * and to permit persons to whom the Software is furnished to do so,
12
+ * subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be
15
+ * included in all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const runtimes_1 = require("../../runtimes");
27
+ const util_1 = require("../../util");
28
+ require("../../util/db-jest-matchers");
29
+ const runtimes = new runtimes_1.RuntimeList((0, util_1.databasesFromEnvironmentOr)(runtimes_1.allDatabases));
30
+ function modelText(databaseName) {
31
+ return `
32
+ source: aircraft_models is ${databaseName}.table('malloytest.aircraft_models') extend {
33
+ primary_key: aircraft_model_code
34
+ }
35
+
36
+ source: aircraft is ${databaseName}.table('malloytest.aircraft') extend {
37
+ primary_key: tail_num
38
+ join_one: aircraft_models with aircraft_model_code
39
+ measure: aircraft_count is count()
40
+ }
41
+
42
+ source: airports is ${databaseName}.table('malloytest.airports')
43
+
44
+ source: state_facts is ${databaseName}.table('malloytest.state_facts')
45
+
46
+ source: flights is ${databaseName}.table('malloytest.flights')
47
+
48
+ source: carriers is ${databaseName}.table('malloytest.carriers')
49
+ `;
50
+ }
51
+ const expressionModels = new Map();
52
+ runtimes.runtimeMap.forEach((runtime, databaseName) => expressionModels.set(databaseName, runtime.loadModel(modelText(databaseName))));
53
+ expressionModels.forEach((expressionModel, databaseName) => {
54
+ const funcTestGeneral = async (expr, type, expected) => {
55
+ const run = async () => {
56
+ return await expressionModel
57
+ .loadQuery(`
58
+ run: aircraft -> { ${type}: f is ${expr} }`)
59
+ .run();
60
+ };
61
+ if (expected.success !== undefined) {
62
+ const result = await run();
63
+ expect(result.data.path(0, 'f').value).toBe(expected.success);
64
+ }
65
+ else {
66
+ expect(run).rejects.toThrowError(expected.error);
67
+ }
68
+ };
69
+ const funcTest = (expr, expexted) => funcTestGeneral(expr, 'group_by', { success: expexted });
70
+ const funcTestAgg = (expr, expexted) => funcTestGeneral(expr, 'aggregate', { success: expexted });
71
+ const funcTestMultiple = async (...testCases) => {
72
+ const run = async () => {
73
+ return await expressionModel
74
+ .loadQuery(`
75
+ run: aircraft -> { ${testCases.map((testCase, i) => `group_by: f${i} is ${testCase[0]}`)} }`)
76
+ .run();
77
+ };
78
+ const result = await run();
79
+ testCases.forEach((testCase, i) => {
80
+ expect(result.data.path(0, `f${i}`).value).toBe(testCase[1]);
81
+ });
82
+ };
83
+ describe('concat', () => {
84
+ it(`works - ${databaseName}`, async () => {
85
+ var _a;
86
+ const expected = {
87
+ 'bigquery': 'foo2003-01-01 12:00:00+00',
88
+ 'snowflake': 'foo2003-01-01T12:00:00.000Z',
89
+ };
90
+ await funcTestMultiple(["concat('foo', 'bar')", 'foobar'], ["concat(1, 'bar')", '1bar'], [
91
+ "concat('cons', true)",
92
+ databaseName === 'postgres' ? 'const' : 'construe',
93
+ ], ["concat('foo', @2003)", 'foo2003-01-01'], [
94
+ "concat('foo', @2003-01-01 12:00:00)",
95
+ (_a = expected[databaseName]) !== null && _a !== void 0 ? _a : 'foo2003-01-01 12:00:00',
96
+ ],
97
+ // TODO Maybe implement consistent null behavior
98
+ // ["concat('foo', null)", null],
99
+ ['concat()', '']);
100
+ });
101
+ });
102
+ describe('round', () => {
103
+ it(`works - ${databaseName}`, async () => {
104
+ await funcTestMultiple(['round(1.2)', 1],
105
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
106
+ // that are fixed in 0.8.
107
+ ...(databaseName === 'duckdb_wasm'
108
+ ? []
109
+ : [['round(12.222, 1)', 12.2]]), ['round(12.2, -1)', 10], ['round(null)', null], ['round(1, null)', null]);
110
+ });
111
+ });
112
+ describe('floor', () => {
113
+ it(`works - ${databaseName}`, async () => {
114
+ await funcTestMultiple(['floor(1.9)', 1],
115
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
116
+ // that are fixed in 0.8.
117
+ ...(databaseName === 'duckdb_wasm'
118
+ ? []
119
+ : [['floor(-1.9)', -2]]), ['floor(null)', null]);
120
+ await funcTest('floor(1.9)', 1);
121
+ });
122
+ });
123
+ describe('ceil', () => {
124
+ it(`works - ${databaseName}`, async () => {
125
+ await funcTestMultiple(['ceil(1.9)', 2],
126
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
127
+ // that are fixed in 0.8.
128
+ ...(databaseName === 'duckdb_wasm'
129
+ ? []
130
+ : [['ceil(-1.9)', -1]]), ['ceil(null)', null]);
131
+ });
132
+ });
133
+ describe('length', () => {
134
+ it(`works - ${databaseName}`, async () => {
135
+ await funcTestMultiple(["length('foo')", 3], ['length(null)', null]);
136
+ });
137
+ });
138
+ describe('lower', () => {
139
+ it(`works - ${databaseName}`, async () => {
140
+ await funcTestMultiple(["lower('FoO')", 'foo'], ['lower(null)', null]);
141
+ });
142
+ });
143
+ describe('upper', () => {
144
+ it(`works - ${databaseName}`, async () => {
145
+ await funcTestMultiple(["upper('fOo')", 'FOO'], ['upper(null)', null]);
146
+ });
147
+ });
148
+ describe('regexp_extract', () => {
149
+ it(`works - ${databaseName}`, async () => {
150
+ await funcTestMultiple(["regexp_extract('I have a dog', r'd[aeiou]g')", 'dog'], ["regexp_extract(null, r'd[aeiou]g')", null], ["regexp_extract('foo', null)", null], ["regexp_extract('I have a d0g', r'd\\dg')", 'd0g']);
151
+ });
152
+ });
153
+ describe('replace', () => {
154
+ it(`works - ${databaseName}`, async () => {
155
+ await funcTestMultiple(["replace('aaaa', 'a', 'c')", 'cccc'], ["replace('aaaa', r'.', 'c')", 'cccc'], [
156
+ "replace('axbxc', r'(a).(b).(c)', '\\\\0 - \\\\1 - \\\\2 - \\\\3')",
157
+ databaseName === 'postgres' ? '\\0 - a - b - c' : 'axbxc - a - b - c',
158
+ ], ["replace('aaaa', '', 'c')", 'aaaa'], ["replace(null, 'a', 'c')", null], ["replace('aaaa', null, 'c')", null], ["replace('aaaa', 'a', null)", null]);
159
+ });
160
+ });
161
+ describe('substr', () => {
162
+ it(`works - ${databaseName}`, async () => {
163
+ await funcTestMultiple(["substr('foo', 2)", 'oo'], ["substr('foo', 2, 1)", 'o'], ["substr('foo bar baz', -3)", 'baz'], ['substr(null, 1, 2)', null], ["substr('aaaa', null, 1)", null], ["substr('aaaa', 1, null)", null]);
164
+ });
165
+ });
166
+ describe('raw function call', () => {
167
+ it(`works - ${databaseName}`, async () => {
168
+ await funcTestMultiple(['floor(cbrt!(27)::number)', 3], ['floor(cbrt!number(27))', 3], ["substr('foo bar baz', -3)", 'baz'], ['substr(null, 1, 2)', null], ["substr('aaaa', null, 1)", null], ["substr('aaaa', 1, null)", null]);
169
+ });
170
+ });
171
+ describe('stddev', () => {
172
+ // TODO symmetric aggregates don't work with custom aggregate functions in BQ currently
173
+ if (['bigquery', 'snowflake'].includes(databaseName))
174
+ return;
175
+ it(`works - ${databaseName}`, async () => {
176
+ await funcTestAgg('round(stddev(aircraft_models.seats))', 29);
177
+ });
178
+ it(`works with struct - ${databaseName}`, async () => {
179
+ await funcTestAgg('round(aircraft_models.stddev(aircraft_models.seats))', 41);
180
+ });
181
+ it(`works with implicit parameter - ${databaseName}`, async () => {
182
+ await funcTestAgg('round(aircraft_models.seats.stddev())', 41);
183
+ });
184
+ it(`works with filter - ${databaseName}`, async () => {
185
+ await funcTestAgg('round(aircraft_models.seats.stddev() { where: 1 = 1 })', 41);
186
+ await funcTestAgg('round(aircraft_models.seats.stddev() { where: aircraft_models.seats > 4 })', 69);
187
+ });
188
+ });
189
+ describe('row_number', () => {
190
+ it(`works when the order by is a dimension - ${databaseName}`, async () => {
191
+ const result = await expressionModel
192
+ .loadQuery(`run: state_facts -> {
193
+ group_by: state
194
+ calculate: row_num is row_number()
195
+ }`)
196
+ .run();
197
+ expect(result.data.path(0, 'row_num').value).toBe(1);
198
+ expect(result.data.path(1, 'row_num').value).toBe(2);
199
+ });
200
+ it(`works when the order by is a dimension in the other order - ${databaseName}`, async () => {
201
+ const result = await expressionModel
202
+ .loadQuery(`run: state_facts -> {
203
+ calculate: row_num is row_number()
204
+ group_by: state
205
+ }`)
206
+ .run();
207
+ expect(result.data.path(0, 'row_num').value).toBe(1);
208
+ expect(result.data.path(1, 'row_num').value).toBe(2);
209
+ });
210
+ it(`works when the order by is a measure - ${databaseName}`, async () => {
211
+ const result = await expressionModel
212
+ .loadQuery(`run: state_facts -> {
213
+ group_by: popular_name
214
+ aggregate: c is count()
215
+ calculate: row_num is row_number()
216
+ }`)
217
+ .run();
218
+ expect(result.data.path(0, 'row_num').value).toBe(1);
219
+ expect(result.data.path(1, 'row_num').value).toBe(2);
220
+ });
221
+ it(`works when the order by is a measure but there is no group by - ${databaseName}`, async () => {
222
+ const result = await expressionModel
223
+ .loadQuery(`run: state_facts -> {
224
+ aggregate: c is count()
225
+ calculate: row_num is row_number()
226
+ }`)
227
+ .run();
228
+ expect(result.data.path(0, 'row_num').value).toBe(1);
229
+ });
230
+ it(`works inside nest - ${databaseName}`, async () => {
231
+ const result = await expressionModel
232
+ .loadQuery(`run: state_facts extend { join_one: airports on airports.state = state } -> {
233
+ group_by: state
234
+ nest: q is {
235
+ group_by: airports.county
236
+ calculate: row_num is row_number()
237
+ }
238
+ }`)
239
+ .run();
240
+ expect(result.data.path(0, 'q', 0, 'row_num').value).toBe(1);
241
+ expect(result.data.path(0, 'q', 1, 'row_num').value).toBe(2);
242
+ expect(result.data.path(1, 'q', 0, 'row_num').value).toBe(1);
243
+ expect(result.data.path(1, 'q', 1, 'row_num').value).toBe(2);
244
+ });
245
+ test(`works outside nest, but with a nest nearby - ${databaseName}`, async () => {
246
+ const result = await expressionModel
247
+ .loadQuery(`run: state_facts -> {
248
+ group_by: state
249
+ calculate: row_num is row_number()
250
+ nest: nested is {
251
+ group_by: state
252
+ }
253
+ }`)
254
+ .run();
255
+ expect(result.data.path(0, 'row_num').value).toBe(1);
256
+ expect(result.data.path(1, 'row_num').value).toBe(2);
257
+ });
258
+ });
259
+ describe('rank', () => {
260
+ it(`works ordered by dimension - ${databaseName}`, async () => {
261
+ const result = await expressionModel
262
+ .loadQuery(`run: state_facts -> {
263
+ group_by:
264
+ state,
265
+ births_ballpark is ceil(births / 1000000) * 1000000
266
+ order_by: births_ballpark desc
267
+ calculate: births_ballpark_rank is rank()
268
+ limit: 20
269
+ }`)
270
+ .run({ rowLimit: 20 });
271
+ expect(result.data.path(0, 'births_ballpark_rank').value).toBe(1);
272
+ expect(result.data.path(1, 'births_ballpark_rank').value).toBe(2);
273
+ expect(result.data.path(8, 'births_ballpark_rank').value).toBe(9);
274
+ expect(result.data.path(9, 'births_ballpark_rank').value).toBe(9);
275
+ expect(result.data.path(10, 'births_ballpark_rank').value).toBe(9);
276
+ expect(result.data.path(11, 'births_ballpark_rank').value).toBe(12);
277
+ });
278
+ it(`works ordered by aggregate - ${databaseName}`, async () => {
279
+ const result = await expressionModel
280
+ .loadQuery(`run: state_facts -> {
281
+ group_by: first_letter is substr(state, 1, 1)
282
+ aggregate: states_with_first_letter_ish is round(count() / 2) * 2
283
+ calculate: r is rank()
284
+ }`)
285
+ .run();
286
+ expect(result.data.path(0, 'r').value).toBe(1);
287
+ expect(result.data.path(1, 'r').value).toBe(1);
288
+ expect(result.data.path(2, 'r').value).toBe(3);
289
+ expect(result.data.path(3, 'r').value).toBe(3);
290
+ });
291
+ it(`works using unary minus in calculate block - ${databaseName}`, async () => {
292
+ const result = await expressionModel
293
+ .loadQuery(`run: state_facts -> {
294
+ group_by: first_letter is substr(state, 1, 1)
295
+ aggregate: states_with_first_letter_ish is round(count() / 2) * 2
296
+ calculate:
297
+ r is rank()
298
+ neg_r is -r
299
+ }`)
300
+ .run();
301
+ expect(result.data.path(0, 'neg_r').value).toBe(-1);
302
+ expect(result.data.path(1, 'neg_r').value).toBe(-1);
303
+ expect(result.data.path(2, 'neg_r').value).toBe(-3);
304
+ expect(result.data.path(3, 'neg_r').value).toBe(-3);
305
+ });
306
+ });
307
+ describe('lag', () => {
308
+ it(`works with one param - ${databaseName}`, async () => {
309
+ const result = await expressionModel
310
+ .loadQuery(`run: state_facts -> {
311
+ group_by: state
312
+ calculate: prev_state is lag(state)
313
+ }`)
314
+ .run();
315
+ expect(result.data.path(0, 'state').value).toBe('AK');
316
+ expect(result.data.path(0, 'prev_state').value).toBe(null);
317
+ expect(result.data.path(1, 'prev_state').value).toBe('AK');
318
+ expect(result.data.path(1, 'state').value).toBe('AL');
319
+ expect(result.data.path(2, 'prev_state').value).toBe('AL');
320
+ });
321
+ it(`works with expression field - ${databaseName}`, async () => {
322
+ const result = await expressionModel
323
+ .loadQuery(`run: state_facts -> {
324
+ group_by: lower_state is lower(state)
325
+ calculate: prev_state is lag(lower_state)
326
+ }`)
327
+ .run();
328
+ expect(result.data.path(0, 'lower_state').value).toBe('ak');
329
+ expect(result.data.path(0, 'prev_state').value).toBe(null);
330
+ expect(result.data.path(1, 'prev_state').value).toBe('ak');
331
+ expect(result.data.path(1, 'lower_state').value).toBe('al');
332
+ expect(result.data.path(2, 'prev_state').value).toBe('al');
333
+ });
334
+ it(`works with expression - ${databaseName}`, async () => {
335
+ const result = await expressionModel
336
+ .loadQuery(`run: state_facts -> {
337
+ group_by: state
338
+ calculate: prev_state is lag(lower(state))
339
+ }`)
340
+ .run();
341
+ expect(result.data.path(0, 'state').value).toBe('AK');
342
+ expect(result.data.path(0, 'prev_state').value).toBe(null);
343
+ expect(result.data.path(1, 'prev_state').value).toBe('ak');
344
+ expect(result.data.path(1, 'state').value).toBe('AL');
345
+ expect(result.data.path(2, 'prev_state').value).toBe('al');
346
+ });
347
+ it(`works with field, ordering by expression field - ${databaseName}`, async () => {
348
+ const result = await expressionModel
349
+ .loadQuery(`run: state_facts -> {
350
+ group_by: lower_state is lower(state)
351
+ aggregate: c is count()
352
+ order_by: lower_state
353
+ calculate: prev_count is lag(c)
354
+ }`)
355
+ .run();
356
+ expect(result.data.path(0, 'lower_state').value).toBe('ak');
357
+ expect(result.data.path(0, 'prev_count').value).toBe(null);
358
+ expect(result.data.path(1, 'prev_count').value).toBe(1);
359
+ expect(result.data.path(1, 'lower_state').value).toBe('al');
360
+ expect(result.data.path(2, 'prev_count').value).toBe(1);
361
+ });
362
+ it(`works with offset - ${databaseName}`, async () => {
363
+ const result = await expressionModel
364
+ .loadQuery(`run: state_facts -> {
365
+ group_by: state
366
+ calculate: prev_prev_state is lag(state, 2)
367
+ }`)
368
+ .run();
369
+ expect(result.data.path(0, 'state').value).toBe('AK');
370
+ expect(result.data.path(0, 'prev_prev_state').value).toBe(null);
371
+ expect(result.data.path(1, 'prev_prev_state').value).toBe(null);
372
+ expect(result.data.path(2, 'prev_prev_state').value).toBe('AK');
373
+ expect(result.data.path(1, 'state').value).toBe('AL');
374
+ expect(result.data.path(3, 'prev_prev_state').value).toBe('AL');
375
+ });
376
+ it(`works with default value - ${databaseName}`, async () => {
377
+ const result = await expressionModel
378
+ .loadQuery(`run: state_facts -> {
379
+ group_by: state
380
+ calculate: prev_state is lag(state, 1, 'NONE')
381
+ }`)
382
+ .run();
383
+ expect(result.data.path(0, 'prev_state').value).toBe('NONE');
384
+ });
385
+ it(`works with now as the default value - ${databaseName}`, async () => {
386
+ const result = await expressionModel
387
+ .loadQuery(`
388
+ run: state_facts -> {
389
+ group_by: state
390
+ calculate: lag_val is lag(@2011-11-11 11:11:11, 1, now).year = now.year
391
+ }`)
392
+ .run();
393
+ expect(result.data.path(0, 'lag_val').value).toBe(true);
394
+ expect(result.data.path(1, 'lag_val').value).toBe(false);
395
+ });
396
+ });
397
+ describe('output field in calculate', () => {
398
+ it(`output field referenceable in calculate - ${databaseName}`, async () => {
399
+ const result = await expressionModel
400
+ .loadQuery(`run: aircraft -> {
401
+ group_by: s is aircraft_models.seats
402
+ calculate: a is lag(s)
403
+ }`)
404
+ .run();
405
+ expect(result.data.path(1, 'a').value).toBe(result.data.path(0, 's').value);
406
+ });
407
+ });
408
+ describe('first_value', () => {
409
+ test(`works in nest - ${databaseName}`, async () => {
410
+ const result = await expressionModel
411
+ .loadQuery(`
412
+ run: aircraft -> {
413
+ group_by: state
414
+ where: state != null
415
+ nest: by_county is {
416
+ limit: 2
417
+ group_by: county
418
+ aggregate: aircraft_count
419
+ calculate: first_count is first_value(count())
420
+ }
421
+ }`)
422
+ .run();
423
+ expect(result.data.path(0, 'by_county', 1, 'first_count').value).toBe(result.data.path(0, 'by_county', 0, 'aircraft_count').value);
424
+ expect(result.data.path(1, 'by_county', 1, 'first_count').value).toBe(result.data.path(1, 'by_county', 0, 'aircraft_count').value);
425
+ });
426
+ it(`works outside nest - ${databaseName}`, async () => {
427
+ const result = await expressionModel
428
+ .loadQuery(`
429
+ run: state_facts -> {
430
+ group_by: state, births
431
+ order_by: births desc
432
+ calculate: most_births is first_value(births)
433
+ }`)
434
+ .run();
435
+ const firstBirths = result.data.path(0, 'births').value;
436
+ expect(result.data.path(0, 'most_births').value).toBe(firstBirths);
437
+ expect(result.data.path(1, 'most_births').value).toBe(firstBirths);
438
+ });
439
+ it(`works with an aggregate which is not in the query - ${databaseName}`, async () => {
440
+ const result = await expressionModel
441
+ .loadQuery(`
442
+ run: airports extend { measure: airport_count is count() } -> {
443
+ group_by: state
444
+ where: state != null
445
+ calculate: prev_airport_count is lag(airport_count)
446
+ }`)
447
+ .run();
448
+ expect(result.data.path(0, 'prev_airport_count').value).toBe(null);
449
+ expect(result.data.path(1, 'prev_airport_count').value).toBe(608);
450
+ expect(result.data.path(2, 'prev_airport_count').value).toBe(260);
451
+ });
452
+ it(`works with a localized aggregate - ${databaseName}`, async () => {
453
+ const result = await expressionModel
454
+ .loadQuery(`
455
+ run: aircraft -> {
456
+ group_by: aircraft_models.seats,
457
+ calculate: prev_sum_of_seats is lag(aircraft_models.seats.sum())
458
+ }`)
459
+ .run();
460
+ expect(result.data.path(0, 'prev_sum_of_seats').value).toBe(null);
461
+ expect(result.data.path(1, 'prev_sum_of_seats').value).toBe(0);
462
+ expect(result.data.path(2, 'prev_sum_of_seats').value).toBe(230);
463
+ });
464
+ });
465
+ describe('trunc', () => {
466
+ it(`works - ${databaseName}`, async () => {
467
+ await funcTestMultiple(['trunc(1.9)', 1],
468
+ // TODO Remove when we upgrade to DuckDB 0.8.X -- DuckDB has some bugs with rounding
469
+ // that are fixed in 0.8.
470
+ ...(databaseName === 'duckdb_wasm'
471
+ ? []
472
+ : [['trunc(-1.9)', -1]]), ['trunc(12.29, 1)', 12.2], ['trunc(19.2, -1)', 10], ['trunc(null)', null], ['trunc(1, null)', null]);
473
+ });
474
+ });
475
+ describe('log', () => {
476
+ it(`works - ${databaseName}`, async () => {
477
+ await funcTestMultiple(['log(10, 10)', 1], ['log(100, 10)', 2], ['log(32, 2)', 5], ['log(null, 2)', null], ['log(10, null)', null]);
478
+ });
479
+ });
480
+ describe('ln', () => {
481
+ it(`works - ${databaseName}`, async () => {
482
+ await funcTestMultiple(['ln(exp(1))', 1], ['ln(exp(2))', 2], ['ln(null)', null]);
483
+ });
484
+ });
485
+ describe('exp', () => {
486
+ it(`works - ${databaseName}`, async () => {
487
+ await funcTestMultiple(['exp(0)', 1], ['ln(exp(1))', 1], ['exp(null)', null]);
488
+ });
489
+ });
490
+ // TODO trig functions could have more exhaustive tests -- these are mostly just here to
491
+ // ensure they exist
492
+ describe('cos', () => {
493
+ it(`works - ${databaseName}`, async () => {
494
+ await funcTestMultiple(['cos(0)', 1], ['cos(null)', null]);
495
+ });
496
+ });
497
+ describe('acos', () => {
498
+ it(`works - ${databaseName}`, async () => {
499
+ await funcTestMultiple(['acos(1)', 0], ['acos(null)', null]);
500
+ });
501
+ });
502
+ describe('sin', () => {
503
+ it(`works - ${databaseName}`, async () => {
504
+ await funcTestMultiple(['sin(0)', 0], ['sin(null)', null]);
505
+ });
506
+ });
507
+ describe('asin', () => {
508
+ it(`works - ${databaseName}`, async () => {
509
+ await funcTestMultiple(['asin(0)', 0], ['asin(null)', null]);
510
+ });
511
+ });
512
+ describe('tan', () => {
513
+ it(`works - ${databaseName}`, async () => {
514
+ await funcTestMultiple(['tan(0)', 0], ['tan(null)', null]);
515
+ });
516
+ });
517
+ describe('atan', () => {
518
+ it(`works - ${databaseName}`, async () => {
519
+ await funcTestMultiple(['atan(0)', 0], ['atan(null)', null]);
520
+ });
521
+ });
522
+ describe('atan2', () => {
523
+ it(`works - ${databaseName}`, async () => {
524
+ await funcTestMultiple(['atan2(0, 1)', 0], ['atan2(null, 1)', null], ['atan2(1, null)', null]);
525
+ });
526
+ });
527
+ describe('sqrt', () => {
528
+ it(`works - ${databaseName}`, async () => {
529
+ await funcTestMultiple(['sqrt(9)', 3], ['sqrt(6.25)', 2.5], ['sqrt(null)', null]);
530
+ });
531
+ });
532
+ describe('pow', () => {
533
+ it(`works - ${databaseName}`, async () => {
534
+ await funcTestMultiple(['pow(2, 3)', 8], ['pow(null, 3)', null], ['pow(2, null)', null]);
535
+ });
536
+ });
537
+ describe('abs', () => {
538
+ it(`works - ${databaseName}`, async () => {
539
+ await funcTestMultiple(['abs(-3)', 3], ['abs(3)', 3], ['abs(null)', null]);
540
+ });
541
+ });
542
+ describe('sign', () => {
543
+ it(`works - ${databaseName}`, async () => {
544
+ await funcTestMultiple(['sign(100)', 1], ['sign(-2)', -1], ['sign(0)', 0], ['sign(null)', null]);
545
+ });
546
+ });
547
+ describe('is_inf', () => {
548
+ it(`works - ${databaseName}`, async () => {
549
+ await funcTestMultiple(["is_inf('+inf'::number)", true], ['is_inf(100)', false], ['is_inf(null)', false]);
550
+ });
551
+ });
552
+ describe('is_nan', () => {
553
+ it(`works - ${databaseName}`, async () => {
554
+ await funcTestMultiple(["is_nan('NaN'::number)", true], ['is_nan(100)', false], ['is_nan(null)', false]);
555
+ });
556
+ });
557
+ describe('greatest', () => {
558
+ it(`works - ${databaseName}`, async () => {
559
+ await funcTestMultiple(['greatest(1, 10, -100)', 10], ['greatest(@2003, @2004, @1994) = @2004', true], [
560
+ 'greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00',
561
+ true,
562
+ ], ["greatest('a', 'b')", 'b'], ['greatest(1, null, 0)', null], ['greatest(null, null)', null]);
563
+ });
564
+ });
565
+ describe('least', () => {
566
+ it(`works - ${databaseName}`, async () => {
567
+ await funcTestMultiple(['least(1, 10, -100)', -100], ['least(@2003, @2004, @1994) = @1994', true], [
568
+ 'least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00',
569
+ true,
570
+ ], ["least('a', 'b')", 'a'], ['least(1, null, 0)', null], ['least(null, null)', null]);
571
+ });
572
+ });
573
+ describe('div', () => {
574
+ it(`works - ${databaseName}`, async () => {
575
+ await funcTestMultiple(['div(3, 2)', 1], ['div(null, 2)', null], ['div(2, null)', null]);
576
+ });
577
+ });
578
+ describe('strpos', () => {
579
+ it(`works - ${databaseName}`, async () => {
580
+ await funcTestMultiple(["strpos('123456789', '3')", 3], ["strpos('123456789', '0')", 0], ["strpos(null, '0')", null], ["strpos('123456789', null)", null]);
581
+ });
582
+ });
583
+ describe('starts_with', () => {
584
+ it(`works - ${databaseName}`, async () => {
585
+ await funcTestMultiple(["starts_with('hello world', 'hello')", true], ["starts_with('hello world', 'world')", false], ["starts_with(null, 'world')", false], ["starts_with('hello world', null)", false]);
586
+ });
587
+ });
588
+ describe('ends_with', () => {
589
+ it(`works - ${databaseName}`, async () => {
590
+ await funcTestMultiple(["ends_with('hello world', 'world')", true], ["ends_with('hello world', 'hello')", false], ["ends_with(null, 'world')", false], ["ends_with('hello world', null)", false]);
591
+ });
592
+ });
593
+ describe('trim', () => {
594
+ it(`works - ${databaseName}`, async () => {
595
+ await funcTestMultiple(["trim(' keep this ')", 'keep this'], ["trim('_ _keep_this_ _', '_ ')", 'keep_this'], ["trim(' keep everything ', '')", ' keep everything '], ["trim('null example', null)", null], ["trim(null, 'a')", null], ['trim(null)', null]);
596
+ });
597
+ });
598
+ describe('ltrim', () => {
599
+ it(`works - ${databaseName}`, async () => {
600
+ await funcTestMultiple(["ltrim(' keep this -> ')", 'keep this -> '], ["ltrim('_ _keep_this -> _ _', '_ ')", 'keep_this -> _ _'], ["ltrim(' keep everything ', '')", ' keep everything '], ["ltrim('null example', null)", null], ["ltrim(null, 'a')", null], ['ltrim(null)', null]);
601
+ });
602
+ });
603
+ describe('rtrim', () => {
604
+ it(`works - ${databaseName}`, async () => {
605
+ await funcTestMultiple(["rtrim(' <- keep this ')", ' <- keep this'], ["rtrim('_ _ <- keep_this_ _', '_ ')", '_ _ <- keep_this'], ["rtrim(' keep everything ', '')", ' keep everything '], ["rtrim('null example', null)", null], ["rtrim(null, 'a')", null], ['rtrim(null)', null]);
606
+ });
607
+ });
608
+ describe('rand', () => {
609
+ it(`is usually not the same value - ${databaseName}`, async () => {
610
+ // There are around a billion values that rand() can be, so if this
611
+ // test fails, most likely something is broken. Otherwise, you're the lucky
612
+ // one in a billion!
613
+ await funcTest('rand() = rand()', false);
614
+ });
615
+ });
616
+ describe('pi', () => {
617
+ it(`is pi - ${databaseName}`, async () => {
618
+ await funcTest('abs(pi() - 3.141592653589793) < 0.0000000000001', true);
619
+ });
620
+ });
621
+ describe('byte_length', () => {
622
+ it(`works - ${databaseName}`, async () => {
623
+ await funcTestMultiple(["byte_length('hello')", 5], ["byte_length('©')", 2], ['byte_length(null)', null]);
624
+ });
625
+ });
626
+ describe('ifnull', () => {
627
+ it(`works - ${databaseName}`, async () => {
628
+ await funcTestMultiple(["ifnull('a', 'b')", 'a'], ["ifnull(null, 'b')", 'b'], ["ifnull('a', null)", 'a'], ['ifnull(null, null)', null]);
629
+ });
630
+ });
631
+ describe('coalesce', () => {
632
+ it(`works - ${databaseName}`, async () => {
633
+ await funcTestMultiple(
634
+ // ["coalesce('a')", 'a'],
635
+ ["coalesce('a', 'b')", 'a'], ["coalesce(null, 'a', 'b')", 'a'], ["coalesce(null, 'b')", 'b'], ["coalesce('a', null)", 'a'], ['coalesce(null, null)', null]
636
+ // ['coalesce(null)', null]
637
+ );
638
+ });
639
+ });
640
+ describe('nullif', () => {
641
+ it(`works - ${databaseName}`, async () => {
642
+ await funcTestMultiple(["nullif('a', 'a')", null], ["nullif('a', 'b')", 'a'], ["nullif('a', null)", 'a'], ['nullif(null, null)', null], ['nullif(null, 2)', null]);
643
+ });
644
+ });
645
+ describe('chr', () => {
646
+ it(`works - ${databaseName}`, async () => {
647
+ await funcTestMultiple(['chr(65)', 'A'], ['chr(255)', 'ÿ']
648
+ // BigQuery's documentation says that `chr(0)` returns the empty string, but it doesn't,
649
+ // it actually returns the null character. We generate code so that it does this.
650
+ // ['chr(0)', '']
651
+ // ['chr(null)', null]
652
+ );
653
+ });
654
+ });
655
+ describe('ascii', () => {
656
+ it(`works - ${databaseName}`, async () => {
657
+ await funcTestMultiple(["ascii('A')", 65], ["ascii('ABC')", 65], ["ascii('')", 0], ['ascii(null)', null]);
658
+ });
659
+ });
660
+ describe('unicode', () => {
661
+ it(`works - ${databaseName}`, async () => {
662
+ await funcTestMultiple(["unicode('A')", 65], ["unicode('â')", 226], ["unicode('âBC')", 226], ["unicode('')", 0], ['unicode(null)', null]);
663
+ });
664
+ });
665
+ describe('repeat', () => {
666
+ it(`works - ${databaseName}`, async () => {
667
+ await funcTestMultiple(["repeat('foo', 0)", ''], ["repeat('foo', 1)", 'foo'], ["repeat('foo', 2)", 'foofoo'], ['repeat(null, 2)', null], ["repeat('foo', null)", null]);
668
+ });
669
+ // TODO how does a user do this: the second argument needs to be an integer, but floor doesn't cast to "integer" type.
670
+ it.skip(`works floor decimal - ${databaseName}`, async () => {
671
+ await funcTest("repeat('foo', floor(2.5))", 'foofoo');
672
+ });
673
+ // undefined behavior when negative, undefined behavior (likely error) when non-integer
674
+ });
675
+ describe('reverse', () => {
676
+ it(`works - ${databaseName}`, async () => {
677
+ await funcTestMultiple(["reverse('foo')", 'oof'], ["reverse('')", ''], ['reverse(null)', null]);
678
+ });
679
+ });
680
+ describe('lead', () => {
681
+ it(`works with one param - ${databaseName}`, async () => {
682
+ const result = await expressionModel
683
+ .loadQuery(`run: state_facts -> {
684
+ group_by: state
685
+ calculate: next_state is lead(state)
686
+ }`)
687
+ .run();
688
+ expect(result.data.path(0, 'state').value).toBe('AK');
689
+ expect(result.data.path(0, 'next_state').value).toBe('AL');
690
+ expect(result.data.path(1, 'state').value).toBe('AL');
691
+ });
692
+ it(`works with offset - ${databaseName}`, async () => {
693
+ const result = await expressionModel
694
+ .loadQuery(`run: state_facts -> {
695
+ group_by: state
696
+ calculate: next_next_state is lead(state, 2)
697
+ }`)
698
+ .run();
699
+ expect(result.data.path(0, 'state').value).toBe('AK');
700
+ expect(result.data.path(0, 'next_next_state').value).toBe('AR');
701
+ expect(result.data.path(1, 'next_next_state').value).toBe('AZ');
702
+ expect(result.data.path(1, 'state').value).toBe('AL');
703
+ expect(result.data.path(2, 'state').value).toBe('AR');
704
+ expect(result.data.path(3, 'state').value).toBe('AZ');
705
+ });
706
+ it(`works with default value - ${databaseName}`, async () => {
707
+ const result = await expressionModel
708
+ .loadQuery(`run: state_facts -> { select: *; limit: 10 } -> {
709
+ group_by: state
710
+ calculate: next_state is lead(state, 1, 'NONE')
711
+ }`)
712
+ .run();
713
+ expect(result.data.path(9, 'next_state').value).toBe('NONE');
714
+ });
715
+ });
716
+ describe('last_value', () => {
717
+ it(`works - ${databaseName}`, async () => {
718
+ const result = await expressionModel
719
+ .loadQuery(`
720
+ run: state_facts -> {
721
+ group_by: state, births
722
+ order_by: births desc
723
+ calculate: least_births is last_value(births)
724
+ }`)
725
+ .run({ rowLimit: 100 });
726
+ const numRows = result.data.toObject().length;
727
+ const lastBirths = result.data.path(numRows - 1, 'births').value;
728
+ expect(result.data.path(0, 'least_births').value).toBe(lastBirths);
729
+ expect(result.data.path(1, 'least_births').value).toBe(lastBirths);
730
+ });
731
+ });
732
+ describe('avg_moving', () => {
733
+ it(`works - ${databaseName}`, async () => {
734
+ const result = await expressionModel
735
+ .loadQuery(`
736
+ run: state_facts -> {
737
+ group_by: state, births
738
+ order_by: births desc
739
+ calculate: rolling_avg is avg_moving(births, 2)
740
+ }`)
741
+ .run({ rowLimit: 100 });
742
+ const births0 = result.data.path(0, 'births').number.value;
743
+ const births1 = result.data.path(1, 'births').number.value;
744
+ const births2 = result.data.path(2, 'births').number.value;
745
+ const births3 = result.data.path(3, 'births').number.value;
746
+ expect(result.data.path(0, 'rolling_avg').number.value).toBe(births0);
747
+ expect(Math.floor(result.data.path(1, 'rolling_avg').number.value)).toBe(Math.floor((births0 + births1) / 2));
748
+ expect(Math.floor(result.data.path(2, 'rolling_avg').number.value)).toBe(Math.floor((births0 + births1 + births2) / 3));
749
+ expect(Math.floor(result.data.path(3, 'rolling_avg').number.value)).toBe(Math.floor((births1 + births2 + births3) / 3));
750
+ });
751
+ it(`works forward - ${databaseName}`, async () => {
752
+ const result = await expressionModel
753
+ .loadQuery(`
754
+ run: state_facts -> { select: *; limit: 3 } -> {
755
+ group_by: state, births
756
+ order_by: births desc
757
+ calculate: rolling_avg is avg_moving(births, 0, 2)
758
+ }`)
759
+ .run({ rowLimit: 100 });
760
+ const births0 = result.data.path(0, 'births').number.value;
761
+ const births1 = result.data.path(1, 'births').number.value;
762
+ const births2 = result.data.path(2, 'births').number.value;
763
+ expect(Math.floor(result.data.path(0, 'rolling_avg').number.value)).toBe(Math.floor((births0 + births1 + births2) / 3));
764
+ expect(Math.floor(result.data.path(1, 'rolling_avg').number.value)).toBe(Math.floor((births1 + births2) / 2));
765
+ expect(result.data.path(2, 'rolling_avg').number.value).toBe(births2);
766
+ });
767
+ });
768
+ describe('sum_moving', () => {
769
+ it(`works - ${databaseName}`, async () => {
770
+ await expect(`
771
+ run: state_facts -> {
772
+ group_by: state, b is births
773
+ order_by: b desc
774
+ calculate: s is sum_moving(b, 2)
775
+ limit: 5
776
+ }`).malloyResultMatches(expressionModel, [
777
+ { b: 28810563, s: 28810563 },
778
+ { b: 23694136, s: 23694136 + 28810563 },
779
+ { b: 21467359, s: 21467359 + 23694136 + 28810563 },
780
+ { b: 16661910, s: 16661910 + 21467359 + 23694136 },
781
+ { b: 15178876, s: 15178876 + 16661910 + 21467359 },
782
+ ]);
783
+ });
784
+ it(`works forward - ${databaseName}`, async () => {
785
+ await expect(`
786
+ run: state_facts -> {
787
+ group_by: state, b is births
788
+ order_by: b desc
789
+ calculate: s is sum_moving(b, 0, 2)
790
+ limit: 7
791
+ }`).malloyResultMatches(expressionModel, [
792
+ { b: 28810563, s: 28810563 + 23694136 + 21467359 },
793
+ { b: 23694136, s: 23694136 + 21467359 + 16661910 },
794
+ { b: 21467359, s: 21467359 + 16661910 + 15178876 },
795
+ { b: 16661910, s: 16661910 + 15178876 + 14201526 },
796
+ { b: 15178876, s: 15178876 + 14201526 + 11643455 },
797
+ { b: 14201526 },
798
+ { b: 11643455 },
799
+ ]);
800
+ });
801
+ });
802
+ describe('min, max, sum / window, cumulative', () => {
803
+ it(`works - ${databaseName}`, async () => {
804
+ const result = await expressionModel
805
+ .loadQuery(`
806
+ run: state_facts -> { select: *; limit: 5 } -> {
807
+ group_by: state, births
808
+ order_by: births asc
809
+ calculate: min_c is min_cumulative(births)
810
+ calculate: max_c is max_cumulative(births)
811
+ calculate: sum_c is sum_cumulative(births)
812
+ calculate: min_w is min_window(births)
813
+ calculate: max_w is max_window(births)
814
+ calculate: sum_w is sum_window(births)
815
+ }`)
816
+ .run({ rowLimit: 100 });
817
+ const births0 = result.data.path(0, 'births').number.value;
818
+ const births1 = result.data.path(1, 'births').number.value;
819
+ const births2 = result.data.path(2, 'births').number.value;
820
+ const births3 = result.data.path(3, 'births').number.value;
821
+ const births4 = result.data.path(4, 'births').number.value;
822
+ const births = [births0, births1, births2, births3, births4];
823
+ for (let r = 0; r < 5; r++) {
824
+ expect(result.data.path(r, 'min_c').number.value).toBe(births0);
825
+ expect(result.data.path(r, 'max_c').number.value).toBe(births[r]);
826
+ expect(result.data.path(r, 'sum_c').number.value).toBe(births.slice(0, r + 1).reduce((a, b) => a + b));
827
+ expect(result.data.path(r, 'min_w').number.value).toBe(births0);
828
+ expect(result.data.path(r, 'max_w').number.value).toBe(births4);
829
+ expect(result.data.path(r, 'sum_w').number.value).toBe(births.reduce((a, b) => a + b));
830
+ }
831
+ });
832
+ });
833
+ });
834
+ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
835
+ const expressionModel = runtime.loadModel(modelText(databaseName));
836
+ describe('string_agg', () => {
837
+ it(`works no order by - ${databaseName}`, async () => {
838
+ await expect(`run: aircraft -> {
839
+ where: name = 'RUTHERFORD PAT R JR'
840
+ aggregate: f is string_agg(name)
841
+ }`).malloyResultMatches(expressionModel, { f: 'RUTHERFORD PAT R JR' });
842
+ });
843
+ it(`works with dotted shortcut - ${databaseName}`, async () => {
844
+ await expect(`run: aircraft -> {
845
+ where: name = 'RUTHERFORD PAT R JR'
846
+ aggregate: f is name.string_agg()
847
+ }`).malloyResultMatches(expressionModel, { f: 'RUTHERFORD PAT R JR' });
848
+ });
849
+ it(`works with order by field - ${databaseName}`, async () => {
850
+ await expect(`##! experimental { aggregate_order_by }
851
+ run: aircraft -> {
852
+ where: name ~ r'.*RUTHERFORD.*'
853
+ aggregate: f is string_agg(name, ',') {
854
+ order_by: name
855
+ }
856
+ }`).malloyResultMatches(expressionModel, {
857
+ f: 'RUTHERFORD JAMES C,RUTHERFORD PAT R JR',
858
+ });
859
+ });
860
+ it(`works with order by direction - ${databaseName}`, async () => {
861
+ expect(`##! experimental { aggregate_order_by }
862
+ run: aircraft -> {
863
+ where: name ~ r'.*RUTHERFORD.*'
864
+ aggregate: f is string_agg(name, ',') {
865
+ order_by: asc
866
+ }
867
+ }`).malloyResultMatches(expressionModel, {
868
+ f: 'RUTHERFORD JAMES C,RUTHERFORD PAT R JR',
869
+ });
870
+ });
871
+ it(`works with multiple order_bys - ${databaseName}`, async () => {
872
+ await expect(`##! experimental { aggregate_order_by }
873
+ run: aircraft -> {
874
+ where: name ~ r'.*RUTHERFORD.*'
875
+ aggregate: f is string_agg(name, ',') {
876
+ order_by: city, name
877
+ }
878
+ }`).malloyResultMatches(expressionModel, {
879
+ f: 'RUTHERFORD PAT R JR,RUTHERFORD JAMES C',
880
+ });
881
+ });
882
+ it(`works with order by expression - ${databaseName}`, async () => {
883
+ await expect(`##! experimental { aggregate_order_by }
884
+ run: aircraft -> {
885
+ where: name ~ r'.*FLY.*'
886
+ group_by: name
887
+ order_by: name desc
888
+ limit: 3
889
+ } -> {
890
+ aggregate: f is string_agg(name, ',') {
891
+ order_by: length(name)
892
+ }
893
+ }`).malloyResultMatches(expressionModel, {
894
+ f: 'YANKEE FLYING CLUB INC,WESTCHESTER FLYING CLUB,WILSON FLYING SERVICE INC',
895
+ });
896
+ });
897
+ it(`works with order by join expression - ${databaseName}`, async () => {
898
+ await expect(`##! experimental { aggregate_order_by }
899
+ run: aircraft -> {
900
+ where: name ~ r'.*ADVENTURE.*'
901
+ aggregate: f is string_agg(name, ',') { order_by: aircraft_models.model }
902
+ }`).malloyResultMatches(expressionModel, {
903
+ f: 'ADVENTURE INC,SEA PLANE ADVENTURE INC,A BALLOON ADVENTURES ALOFT,A AERONAUTICAL ADVENTURE INC',
904
+ });
905
+ });
906
+ it(`works with order asc - ${databaseName}`, async () => {
907
+ await expect(`##! experimental { aggregate_order_by }
908
+ run: aircraft -> {
909
+ where: name ~ r'.*FLY.*'
910
+ group_by: name
911
+ order_by: name desc
912
+ limit: 3
913
+ } -> {
914
+ aggregate: f is string_agg(name, ',') { order_by: name asc }
915
+ }`).malloyResultMatches(expressionModel, {
916
+ f: 'WESTCHESTER FLYING CLUB,WILSON FLYING SERVICE INC,YANKEE FLYING CLUB INC',
917
+ });
918
+ });
919
+ it(`works with order desc - ${databaseName}`, async () => {
920
+ await expect(`##! experimental { aggregate_order_by }
921
+ run: aircraft -> {
922
+ where: name ~ r'.*FLY.*'
923
+ group_by: name
924
+ order_by: name desc
925
+ limit: 3
926
+ } -> {
927
+ aggregate: f is string_agg(name, ',') { order_by: name desc }
928
+ }`).malloyResultMatches(expressionModel, {
929
+ f: 'YANKEE FLYING CLUB INC,WILSON FLYING SERVICE INC,WESTCHESTER FLYING CLUB',
930
+ });
931
+ });
932
+ it(`works with fanout and order_by - ${databaseName}`, async () => {
933
+ // TODO bigquery cannot handle both fanout and order_by today
934
+ if (['bigquery', 'snowflake'].includes(databaseName))
935
+ return;
936
+ await expect(`##! experimental.aggregate_order_by
937
+ run: state_facts extend { join_many:
938
+ state_facts2 is ${databaseName}.table('malloytest.state_facts')
939
+ on state_facts2.state = state
940
+ } -> {
941
+ aggregate: c is state_facts2.count()
942
+ aggregate: s is string_agg(state) {
943
+ order_by: popular_name, state
944
+ }
945
+ }`).malloyResultMatches(expressionModel, {
946
+ s: 'IA,LA,MN,AL,AR,IN,ME,MT,NC,AZ,CA,CO,CT,FL,GA,HI,IL,KS,KY,MA,MO,NJ,NM,NV,NY,OH,OK,PA,RI,TN,TX,WV,WY,DC,MS,SC,ID,NE,UT,VA,AK,DE,MD,MI,ND,NH,OR,SD,VT,WA,WI',
947
+ c: 51,
948
+ });
949
+ });
950
+ it(`works with fanout - ${databaseName}`, async () => {
951
+ // Snowflake cannot handle the fanout case today
952
+ if (databaseName === 'snowflake')
953
+ return;
954
+ await expect(`##! experimental.aggregate_order_by
955
+ run: state_facts extend { join_many:
956
+ state_facts2 is ${databaseName}.table('malloytest.state_facts')
957
+ on state_facts2.state = state
958
+ } -> {
959
+ aggregate: c is state_facts2.count()
960
+ aggregate: s is string_agg('o')
961
+ }`).malloyResultMatches(expressionModel, {
962
+ s: 'o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o',
963
+ c: 51,
964
+ });
965
+ });
966
+ it(`works with fanout and separator - ${databaseName}`, async () => {
967
+ // Snowflake cannot handle the fanout case today
968
+ if (databaseName === 'snowflake')
969
+ return;
970
+ await expect(`##! experimental.aggregate_order_by
971
+ run: state_facts extend { join_many:
972
+ state_facts2 is ${databaseName}.table('malloytest.state_facts')
973
+ on state_facts2.state = state
974
+ } -> {
975
+ aggregate: c is state_facts2.count()
976
+ aggregate: s is string_agg('o', '')
977
+ }`).malloyResultMatches(expressionModel, {
978
+ s: 'ooooooooooooooooooooooooooooooooooooooooooooooooooo',
979
+ c: 51,
980
+ });
981
+ });
982
+ it(`works with limit - ${databaseName}`, async () => {
983
+ const query = `##! experimental { aggregate_order_by aggregate_limit }
984
+ run: aircraft -> {
985
+ where: name ~ r'.*FLY.*'
986
+ group_by: name
987
+ order_by: name desc
988
+ limit: 3
989
+ } -> {
990
+ aggregate: f is string_agg(name, ',') {
991
+ order_by: name desc
992
+ limit: 2
993
+ }
994
+ }`;
995
+ if (databaseName === 'bigquery') {
996
+ await expect(query).malloyResultMatches(expressionModel, {
997
+ f: 'YANKEE FLYING CLUB INC,WILSON FLYING SERVICE INC',
998
+ });
999
+ }
1000
+ else {
1001
+ await expect(expressionModel.loadQuery(query).run()).rejects.toThrow('Function string_agg does not support limit');
1002
+ }
1003
+ });
1004
+ });
1005
+ describe('string_agg_distinct', () => {
1006
+ it(`actually distincts - ${databaseName}`, async () => {
1007
+ await expect(`##! experimental { aggregate_order_by }
1008
+ source: aircraft is ${databaseName}.table('malloytest.aircraft') extend {
1009
+ primary_key: tail_num
1010
+ }
1011
+
1012
+ source: aircraft_models is ${databaseName}.table('malloytest.aircraft_models') extend {
1013
+ primary_key: aircraft_model_code
1014
+ join_many: aircraft on aircraft_model_code = aircraft.aircraft_model_code
1015
+ }
1016
+
1017
+ run: aircraft_models -> {
1018
+ where: aircraft.name = 'RAYTHEON AIRCRAFT COMPANY' | 'FOWLER IRA R DBA'
1019
+ aggregate: f_dist is aircraft.name.string_agg_distinct() { order_by: asc }
1020
+ aggregate: f_all is aircraft.name.string_agg() { order_by: aircraft.name }
1021
+ }`).malloyResultMatches(runtime, {
1022
+ f_dist: 'FOWLER IRA R DBA,RAYTHEON AIRCRAFT COMPANY',
1023
+ f_all: 'FOWLER IRA R DBA,FOWLER IRA R DBA,RAYTHEON AIRCRAFT COMPANY,RAYTHEON AIRCRAFT COMPANY',
1024
+ });
1025
+ });
1026
+ it(`works no order by - ${databaseName}`, async () => {
1027
+ await expect(`run: aircraft -> {
1028
+ where: name = 'RUTHERFORD PAT R JR'
1029
+ aggregate: f is string_agg_distinct(name)
1030
+ }`).malloyResultMatches(expressionModel, {
1031
+ f: 'RUTHERFORD PAT R JR',
1032
+ });
1033
+ });
1034
+ it(`works with dotted shortcut - ${databaseName}`, async () => {
1035
+ await expect(`run: aircraft -> {
1036
+ where: name = 'RUTHERFORD PAT R JR'
1037
+ aggregate: f is name.string_agg_distinct()
1038
+ }`).malloyResultMatches(expressionModel, {
1039
+ f: 'RUTHERFORD PAT R JR',
1040
+ });
1041
+ });
1042
+ it(`works with order by direction - ${databaseName}`, async () => {
1043
+ await expect(`##! experimental { aggregate_order_by }
1044
+ run: aircraft -> {
1045
+ where: name ~ r'.*RUTHERFORD.*'
1046
+ aggregate: f is string_agg_distinct(name, ',') {
1047
+ order_by: asc
1048
+ }
1049
+ }`).malloyResultMatches(expressionModel, {
1050
+ f: 'RUTHERFORD JAMES C,RUTHERFORD PAT R JR',
1051
+ });
1052
+ });
1053
+ it(`works with order asc - ${databaseName}`, async () => {
1054
+ await expect(`##! experimental { aggregate_order_by }
1055
+ run: aircraft -> {
1056
+ where: name ~ r'.*FLY.*'
1057
+ group_by: name
1058
+ order_by: name desc
1059
+ limit: 3
1060
+ } -> {
1061
+ aggregate: f is string_agg_distinct(name, ',') { order_by: asc }
1062
+ }`).malloyResultMatches(expressionModel, {
1063
+ f: 'WESTCHESTER FLYING CLUB,WILSON FLYING SERVICE INC,YANKEE FLYING CLUB INC',
1064
+ });
1065
+ });
1066
+ it(`works with order desc - ${databaseName}`, async () => {
1067
+ await expect(`##! experimental { aggregate_order_by }
1068
+ run: aircraft -> {
1069
+ where: name ~ r'.*FLY.*'
1070
+ group_by: name
1071
+ order_by: name desc
1072
+ limit: 3
1073
+ } -> {
1074
+ aggregate: f is string_agg_distinct(name, ',') { order_by: desc }
1075
+ }`).malloyResultMatches(expressionModel, {
1076
+ f: 'YANKEE FLYING CLUB INC,WILSON FLYING SERVICE INC,WESTCHESTER FLYING CLUB',
1077
+ });
1078
+ });
1079
+ it(`works with limit - ${databaseName}`, async () => {
1080
+ const query = `##! experimental { aggregate_order_by aggregate_limit }
1081
+ run: aircraft -> {
1082
+ where: name ~ r'.*FLY.*'
1083
+ group_by: name
1084
+ order_by: name desc
1085
+ limit: 3
1086
+ } -> {
1087
+ aggregate: f is string_agg_distinct(name, ',') {
1088
+ order_by: desc
1089
+ limit: 2
1090
+ }
1091
+ }`;
1092
+ if (databaseName === 'bigquery') {
1093
+ await expect(query).malloyResultMatches(expressionModel, {
1094
+ f: 'YANKEE FLYING CLUB INC,WILSON FLYING SERVICE INC',
1095
+ });
1096
+ }
1097
+ else {
1098
+ await expect(expressionModel.loadQuery(query).run()).rejects.toThrow('Function string_agg_distinct does not support limit');
1099
+ }
1100
+ });
1101
+ });
1102
+ describe('partition_by', () => {
1103
+ it(`works - ${databaseName}`, async () => {
1104
+ await expect(`
1105
+ run: flights -> {
1106
+ group_by:
1107
+ yr is year(dep_time)
1108
+ qtr is quarter(dep_time)
1109
+
1110
+ aggregate:
1111
+ qtr_flights is count()
1112
+
1113
+ calculate:
1114
+ last_yr_qtr_flights is lag(qtr_flights) {
1115
+ partition_by: qtr
1116
+ order_by: yr asc
1117
+ }
1118
+ order_by: yr, qtr
1119
+ where: dep_time < @2002
1120
+ }`).malloyResultMatches(expressionModel, [
1121
+ { yr: 2000, qtr: 1, qtr_flights: 12148, last_yr_qtr_flights: null },
1122
+ { yr: 2000, qtr: 2, qtr_flights: 11599, last_yr_qtr_flights: null },
1123
+ { yr: 2000, qtr: 3, qtr_flights: 12075, last_yr_qtr_flights: null },
1124
+ { yr: 2000, qtr: 4, qtr_flights: 11320, last_yr_qtr_flights: null },
1125
+ { yr: 2001, qtr: 1, qtr_flights: 11612, last_yr_qtr_flights: 12148 },
1126
+ { yr: 2001, qtr: 2, qtr_flights: 13186, last_yr_qtr_flights: 11599 },
1127
+ { yr: 2001, qtr: 3, qtr_flights: 12663, last_yr_qtr_flights: 12075 },
1128
+ { yr: 2001, qtr: 4, qtr_flights: 11714, last_yr_qtr_flights: 11320 },
1129
+ ]);
1130
+ });
1131
+ it(`works with aggregate - ${databaseName}`, async () => {
1132
+ await expect(`
1133
+ run: state_facts -> {
1134
+ aggregate: c is count()
1135
+ group_by: l is substr(state, 1, 1)
1136
+
1137
+ calculate:
1138
+ prev is lag(l) {
1139
+ partition_by: c
1140
+ }
1141
+ order_by: l
1142
+ limit: 5
1143
+ }`).malloyResultMatches(expressionModel, [
1144
+ { l: 'A', c: 4, prev: null },
1145
+ { l: 'C', c: 3, prev: null },
1146
+ { l: 'D', c: 2, prev: null },
1147
+ { l: 'F', c: 1, prev: null },
1148
+ { l: 'G', c: 1, prev: 'F' },
1149
+ ]);
1150
+ });
1151
+ it(`works with multiple order_bys - ${databaseName}`, async () => {
1152
+ await expect(`
1153
+ run: aircraft -> {
1154
+ where: name =
1155
+ "UNITED AIR LINES INC"
1156
+ | "FEDERAL EXPRESS CORP"
1157
+ | "AMERICAN AIRLINES INC"
1158
+ | "CESSNA AIRCRAFT COMPANY"
1159
+ group_by: name
1160
+ calculate:
1161
+ # label="Rank by model count then seat count"
1162
+ r is rank() {
1163
+ order_by:
1164
+ aircraft_models.count() desc,
1165
+ aircraft_models.seats.sum() desc
1166
+ }
1167
+ order_by: name
1168
+ }`).malloyResultMatches(expressionModel, [
1169
+ { name: 'AMERICAN AIRLINES INC', r: 3 },
1170
+ { name: 'CESSNA AIRCRAFT COMPANY', r: 4 },
1171
+ { name: 'FEDERAL EXPRESS CORP', r: 2 },
1172
+ { name: 'UNITED AIR LINES INC', r: 1 },
1173
+ ]);
1174
+ });
1175
+ });
1176
+ });
1177
+ afterAll(async () => {
1178
+ await runtimes.closeAll();
1179
+ });
1180
+ //# sourceMappingURL=functions.spec.js.map