@malloydata/malloy-tests 0.0.220-dev241203204946 → 0.0.220-dev241204170603

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.
@@ -5,11 +5,13 @@ rm -rf .tmp
5
5
  mkdir .tmp
6
6
 
7
7
  # run docker
8
- SCRIPTDIR=$(dirname $0)
9
- docker run -p 3306:3306 -d -v $SCRIPTDIR/../data/mysql:/init_data --name mysql-malloy -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:8.4.2
8
+ SCRIPTDIR=$(cd $(dirname $0); pwd)
9
+ DATADIR=$(dirname $SCRIPTDIR)/data/mysql
10
+ docker run -p 3306:3306 -d -v $DATADIR:/init_data --name mysql-malloy -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:8.4.2
10
11
 
11
12
  # wait for server to start
12
13
  counter=0
14
+ echo -n Starting Docker ...
13
15
  while ! docker logs mysql-malloy 2>&1 | grep -q "mysqld: ready for connections"
14
16
  do
15
17
  sleep 10
@@ -23,9 +25,12 @@ do
23
25
  exit 1
24
26
  break
25
27
  fi
28
+ echo -n ...
26
29
  done
27
30
 
28
31
  # load the test data.
32
+ echo
33
+ echo Loading Test Data
29
34
  docker exec mysql-malloy cp /init_data/malloytest.mysql.gz /tmp
30
35
  docker exec mysql-malloy gunzip /tmp/malloytest.mysql.gz
31
36
  docker exec mysql-malloy mysql -P3306 -h127.0.0.1 -uroot -e 'drop database if exists malloytest; create database malloytest; use malloytest; source /tmp/malloytest.mysql;'
@@ -4,4 +4,4 @@
4
4
  rm -rf .tmp
5
5
 
6
6
  # stop container
7
- docker rm -f mysql-malloy
7
+ docker rm -f mysql-malloy
package/package.json CHANGED
@@ -21,13 +21,13 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@jest/globals": "^29.4.3",
24
- "@malloydata/db-bigquery": "^0.0.220-dev241203204946",
25
- "@malloydata/db-duckdb": "^0.0.220-dev241203204946",
26
- "@malloydata/db-postgres": "^0.0.220-dev241203204946",
27
- "@malloydata/db-snowflake": "^0.0.220-dev241203204946",
28
- "@malloydata/db-trino": "^0.0.220-dev241203204946",
29
- "@malloydata/malloy": "^0.0.220-dev241203204946",
30
- "@malloydata/render": "^0.0.220-dev241203204946",
24
+ "@malloydata/db-bigquery": "^0.0.220-dev241204170603",
25
+ "@malloydata/db-duckdb": "^0.0.220-dev241204170603",
26
+ "@malloydata/db-postgres": "^0.0.220-dev241204170603",
27
+ "@malloydata/db-snowflake": "^0.0.220-dev241204170603",
28
+ "@malloydata/db-trino": "^0.0.220-dev241204170603",
29
+ "@malloydata/malloy": "^0.0.220-dev241204170603",
30
+ "@malloydata/render": "^0.0.220-dev241204170603",
31
31
  "events": "^3.3.0",
32
32
  "jsdom": "^22.1.0",
33
33
  "luxon": "^2.4.0",
@@ -37,5 +37,5 @@
37
37
  "@types/jsdom": "^21.1.1",
38
38
  "@types/luxon": "^2.4.0"
39
39
  },
40
- "version": "0.0.220-dev241203204946"
40
+ "version": "0.0.220-dev241204170603"
41
41
  }
@@ -0,0 +1,509 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import {RuntimeList, allDatabases} from '../../runtimes';
9
+ import {databasesFromEnvironmentOr} from '../../util';
10
+ import '../../util/db-jest-matchers';
11
+ import {
12
+ RecordLiteralNode,
13
+ ArrayLiteralNode,
14
+ ArrayTypeDef,
15
+ FieldDef,
16
+ Expr,
17
+ } from '@malloydata/malloy';
18
+
19
+ const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
20
+
21
+ /*
22
+ * Tests for the composite atomic data types "record", "array of values",
23
+ * and "array of records". Each starts with a test that the dialect functions
24
+ * for literals work, and then bases the rest of the tests on literals,
25
+ * so fix that one first if the tests are failing.
26
+ */
27
+
28
+ describe.each(runtimes.runtimeList)(
29
+ 'compound atomic datatypes %s',
30
+ (conName, runtime) => {
31
+ const supportsNestedArrays = runtime.dialect.nestedArrays;
32
+ const quote = runtime.dialect.sqlMaybeQuoteIdentifier;
33
+ function literalNum(num: Number): Expr {
34
+ const literal = num.toString();
35
+ return {node: 'numberLiteral', literal, sql: literal};
36
+ }
37
+ const empty = `${conName}.sql("SELECT 0 as z")`;
38
+ function arraySelectVal(...val: Number[]): string {
39
+ const literal: ArrayLiteralNode = {
40
+ node: 'arrayLiteral',
41
+ typeDef: {
42
+ type: 'array',
43
+ name: 'evens',
44
+ join: 'many',
45
+ elementTypeDef: {type: 'number'},
46
+ fields: [],
47
+ dialect: runtime.dialect.name,
48
+ },
49
+ kids: {values: val.map(v => literalNum(v))},
50
+ };
51
+ return runtime.dialect.sqlLiteralArray(literal);
52
+ }
53
+ function recordLiteral(fromObj: Record<string, number>): RecordLiteralNode {
54
+ const kids: Record<string, Expr> = {};
55
+ const fields: FieldDef[] = Object.keys(fromObj).map(name => {
56
+ kids[name] = literalNum(fromObj[name]);
57
+ return {
58
+ type: 'number',
59
+ name,
60
+ };
61
+ });
62
+ const literal: RecordLiteralNode = {
63
+ node: 'recordLiteral',
64
+ typeDef: {
65
+ type: 'record',
66
+ name: 'evens',
67
+ join: 'one',
68
+ dialect: runtime.dialect.name,
69
+ fields,
70
+ },
71
+ kids,
72
+ };
73
+ literal.sql = runtime.dialect.sqlLiteralRecord(literal);
74
+ return literal;
75
+ }
76
+
77
+ function recordSelectVal(fromObj: Record<string, number>): string {
78
+ return runtime.dialect.sqlLiteralRecord(recordLiteral(fromObj));
79
+ }
80
+ const canReadCompoundSchema = runtime.dialect.compoundObjectInSchema;
81
+
82
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
83
+ const ab = recordSelectVal({a: 0, b: 1});
84
+
85
+ const malloySizes = 'sizes is {s is 0, m is 1, l is 2, xl is 3}';
86
+ const sizesObj = {s: 0, m: 1, l: 2, xl: 3};
87
+ const sizesSQL = recordSelectVal(sizesObj);
88
+ // Keeping the pipeline simpler makes debugging easier, so don't add
89
+ // and extra stage unless you have to
90
+ const sizes = canReadCompoundSchema
91
+ ? `${conName}.sql(""" SELECT ${sizesSQL} AS ${quote('sizes')} """)`
92
+ : `${conName}.sql('SELECT 0 AS O') -> { select: ${malloySizes}}`;
93
+ const evensObj = [2, 4, 6, 8];
94
+ const evensSQL = arraySelectVal(...evensObj);
95
+ const evens = `${conName}.sql("""
96
+ SELECT ${evensSQL} AS ${quote('evens')}
97
+ """)`;
98
+
99
+ describe('simple arrays', () => {
100
+ test('array literal dialect function', async () => {
101
+ await expect(`
102
+ run: ${evens}`).malloyResultMatches(runtime, {
103
+ evens: evensObj,
104
+ });
105
+ });
106
+ test('select array', async () => {
107
+ await expect(`
108
+ # test.verbose
109
+ run: ${evens}->{select: nn is evens}
110
+ `).malloyResultMatches(runtime, {nn: evensObj});
111
+ });
112
+ test.when(canReadCompoundSchema)(
113
+ 'schema read allows array-un-nest on each',
114
+ async () => {
115
+ await expect(`
116
+ run: ${evens}->{ select: n is evens.each }
117
+ `).malloyResultMatches(
118
+ runtime,
119
+ evensObj.map(n => ({n}))
120
+ );
121
+ }
122
+ );
123
+ test('array can be passed to !function', async () => {
124
+ // Used as a standin for "unknown function user might call"
125
+ const nameOfArrayLenFunction = {
126
+ 'duckdb': 'LEN',
127
+ 'standardsql': 'ARRAY_LENGTH',
128
+ 'postgres': 'JSONB_ARRAY_LENGTH',
129
+ 'presto': 'CARDINALITY',
130
+ 'trino': 'CARDINALITY',
131
+ 'mysql': 'JSON_LENGTH',
132
+ 'snowflake': 'ARRAY_SIZE',
133
+ };
134
+ const dialect = runtime.dialect.name;
135
+ const missing = `Dialect '${dialect}' missing array length function in nameOfArrayLenFunction`;
136
+ const fn = nameOfArrayLenFunction[dialect] ?? missing;
137
+ expect(fn).not.toEqual(missing);
138
+ await expect(
139
+ `run: ${evens}->{ select: nby2 is ${fn}!number(evens); } `
140
+ ).malloyResultMatches(runtime, {nby2: evensObj.length});
141
+ });
142
+ test('array.each in source', async () => {
143
+ await expect(`
144
+ run: ${empty}
145
+ extend { dimension: d4 is [1,2,3,4] }
146
+ -> { select: die_roll is d4.each }
147
+ `).malloyResultMatches(runtime, [
148
+ {die_roll: 1},
149
+ {die_roll: 2},
150
+ {die_roll: 3},
151
+ {die_roll: 4},
152
+ ]);
153
+ });
154
+ test('array.each in extend block', async () => {
155
+ await expect(`
156
+ run: ${empty} -> {
157
+ extend: { dimension: d4 is [1,2,3,4] }
158
+ select: die_roll is d4.each
159
+ }
160
+ `).malloyResultMatches(runtime, [
161
+ {die_roll: 1},
162
+ {die_roll: 2},
163
+ {die_roll: 3},
164
+ {die_roll: 4},
165
+ ]);
166
+ });
167
+ test.skip('cross join arrays', async () => {
168
+ await expect(`
169
+ run: ${empty} extend {
170
+ dimension: d1 is [1,2,3,4]
171
+ join_cross: d2 is [1,2,3,4]
172
+ } -> {
173
+ group_by: roll is d1.each + d2.each
174
+ aggregate: rolls is count()
175
+ }
176
+ `).malloyResultMatches(runtime, [
177
+ {roll: 2, rolls: 1},
178
+ {roll: 3, rolls: 2},
179
+ {roll: 4, rolls: 3},
180
+ {roll: 5, rolls: 4},
181
+ {roll: 6, rolls: 3},
182
+ {roll: 7, rolls: 2},
183
+ {roll: 8, rolls: 1},
184
+ ]);
185
+ });
186
+ // can't use special chars in column names in bq
187
+ test.when(conName !== 'bigquery')(
188
+ 'array stored field with special chars in name',
189
+ async () => {
190
+ const special_chars = ["'", '"', '.', '`'];
191
+ for (const c of special_chars) {
192
+ const qname = '`_\\' + c + '_`';
193
+ const malloySrc = `
194
+ # test.verbose
195
+ run: ${empty}
196
+ ->{ select: ${qname} is [1]}
197
+ -> { select: num is ${qname}.each }`;
198
+ await expect(malloySrc).malloyResultMatches(runtime, {});
199
+ const result = await runtime.loadQuery(malloySrc).run();
200
+ const ok =
201
+ result.data.path(0, 'num').value === 1
202
+ ? 'ok'
203
+ : `Array containing ${c} character is not ok`;
204
+ expect(ok).toEqual('ok');
205
+ }
206
+ }
207
+ );
208
+ test.when(supportsNestedArrays)('bare array of array', async () => {
209
+ await expect(`
210
+ run: ${empty} -> { select: aoa is [[1,2]] }
211
+ `).malloyResultMatches(runtime, {aoa: [[1, 2]]});
212
+ });
213
+ test.when(supportsNestedArrays)('each.each array of array', async () => {
214
+ await expect(`
215
+ run: ${empty} extend { dimension: aoa is [[1,2]] } -> { select: aoa.each.each }
216
+ `).malloyResultMatches(runtime, [{each: 1}, {each: 2}]);
217
+ });
218
+ });
219
+ describe('record', () => {
220
+ function rec_eq(as?: string): Record<string, Number> {
221
+ const name = as ?? 'sizes';
222
+ return {
223
+ [`${name}/s`]: 0,
224
+ [`${name}/m`]: 1,
225
+ [`${name}/l`]: 2,
226
+ [`${name}/xl`]: 3,
227
+ };
228
+ }
229
+ test('record literal object', async () => {
230
+ await expect(`
231
+ run: ${conName}.sql("select 0 as o")
232
+ -> { select: ${malloySizes}}
233
+ `).malloyResultMatches(runtime, rec_eq());
234
+ });
235
+ // can't use special chars in column names in bq
236
+ test.when(conName !== 'bigquery')(
237
+ 'special character in record property name',
238
+ async () => {
239
+ const special_chars = ["'", '"', '.', '`'];
240
+ for (const c of special_chars) {
241
+ const qname = '_\\' + c + '_';
242
+ const name = '_' + c + '_';
243
+ const malloySrc = `run: ${empty} -> { select: \`${qname}\` is 'ok' }`;
244
+ // no malloyResultMatches because it treats a special in an expect key
245
+ const query = runtime.loadQuery(malloySrc);
246
+ const result = await query.run();
247
+ const p =
248
+ result.data.path(0, name).value === 'ok'
249
+ ? 'ok'
250
+ : `Name containing the ${c} character was not ok`;
251
+ expect(p).toEqual('ok');
252
+ }
253
+ }
254
+ );
255
+ // can't use special chars in column names in bq
256
+ test.when(conName !== 'bigquery')(
257
+ 'record stored in field with special chars in name',
258
+ async () => {
259
+ const special_chars = ["'", '"', '.', '`'];
260
+ for (const c of special_chars) {
261
+ const qname = '`_\\' + c + '_`';
262
+ const malloySrc = `
263
+ run: ${empty}
264
+ ->{ select: ${qname} is {rnum is 1}}
265
+ -> { select: num is ${qname}.rnum }`;
266
+ const result = await runtime.loadQuery(malloySrc).run();
267
+ const ok =
268
+ result.data.path(0, 'num').value === 1
269
+ ? 'ok'
270
+ : `Array containing ${c} character is not ok`;
271
+ expect(ok).toEqual('ok');
272
+ }
273
+ }
274
+ );
275
+ test.when(canReadCompoundSchema)(
276
+ 'can read schema of record object',
277
+ async () => {
278
+ await expect(`run: ${conName}.sql("""
279
+ SELECT ${sizesSQL} AS ${quote('sizes')}
280
+ """)`).malloyResultMatches(runtime, rec_eq());
281
+ }
282
+ );
283
+ test('simple record.property access', async () => {
284
+ await expect(`
285
+ run: ${sizes} -> { select: small is sizes.s }`).malloyResultMatches(
286
+ runtime,
287
+ {small: 0}
288
+ );
289
+ });
290
+ test('nested data looks like a record', async () => {
291
+ await expect(`
292
+ run: ${conName}.sql('SELECT 1 as ${quote('o')}') -> {
293
+ group_by: row is 'one_row'
294
+ nest: sizes is {
295
+ aggregate:
296
+ s is sum(o) - 1,
297
+ m is sum(o),
298
+ x is sum(o) + 1,
299
+ xl is sum(o) + 2
300
+ }
301
+ } -> { select: small is sizes.s }`).malloyResultMatches(runtime, {
302
+ small: 0,
303
+ });
304
+ });
305
+ test('record can be selected', async () => {
306
+ await expect(
307
+ `
308
+ run: ${sizes} -> { select: sizes }`
309
+ ).malloyResultMatches(runtime, rec_eq());
310
+ });
311
+ test('record literal can be selected', async () => {
312
+ await expect(`
313
+ run: ${sizes} -> { select: record is sizes }
314
+ `).malloyResultMatches(runtime, rec_eq('record'));
315
+ });
316
+ test('select record literal from a source', async () => {
317
+ await expect(`
318
+ run: ${empty} -> {
319
+ extend: { dimension: ${malloySizes} }
320
+ select: sizes
321
+ }
322
+ `).malloyResultMatches(runtime, rec_eq());
323
+ });
324
+ test('computed record.property from a source', async () => {
325
+ await expect(`
326
+ run: ${empty}
327
+ extend { dimension: record is {s is 0, m is 1, l is 2, xl is 3} }
328
+ -> { select: small is record.s }
329
+ `).malloyResultMatches(runtime, {small: 0});
330
+ });
331
+ test('record.property from an extend block', async () => {
332
+ await expect(`
333
+ run: ${empty} -> {
334
+ extend: { dimension: record is {s is 0, m is 1, l is 2, xl is 3} }
335
+ select: small is record.s
336
+ }
337
+ `).malloyResultMatches(runtime, {small: 0});
338
+ });
339
+ test('simple each on array property inside record', async () => {
340
+ await expect(`
341
+ run: ${empty} -> { select: nums is { odds is [1,3], evens is [2,4]} }
342
+ -> { select: odd is nums.odds.value }
343
+ `).malloyResultMatches(runtime, [{odd: 1}, {odd: 3}]);
344
+ });
345
+ test('each on array property inside record from source', async () => {
346
+ await expect(`
347
+ run: ${empty} extend { dimension: nums is { odds is [1,3], evens is [2,4]} }
348
+ -> { select: odd is nums.odds.each }
349
+ `).malloyResultMatches(runtime, [{odd: 1}, {odd: 3}]);
350
+ });
351
+ const abc = "rec is {a is 'a', bc is {b is 'b', c is 'c'}}";
352
+ test('record with a record property', async () => {
353
+ await expect(`
354
+ run: ${empty} -> { select: ${abc} }
355
+ -> { select: rec.a, rec.bc.b, rec.bc.c }
356
+ `).malloyResultMatches(runtime, {a: 'a', b: 'b', c: 'c'});
357
+ });
358
+ test('record in source with a record property', async () => {
359
+ await expect(`
360
+ run: ${empty} extend { dimension: ${abc} }
361
+ -> { select: rec.a, rec.bc.b, rec.bc.c }
362
+ `).malloyResultMatches(runtime, {a: 'a', b: 'b', c: 'c'});
363
+ });
364
+ test('record dref in source with a record property', async () => {
365
+ await expect(`
366
+ run: ${empty} extend { dimension: ${abc} }
367
+ -> { select: b is pick rec.bc.b when true else 'b' }
368
+ `).malloyResultMatches(runtime, {b: 'b'});
369
+ });
370
+ test.todo('array or record where first entries are null');
371
+ });
372
+ describe('repeated record', () => {
373
+ const abType: ArrayTypeDef = {
374
+ type: 'array',
375
+ dialect: runtime.dialect.name,
376
+ join: 'many',
377
+ elementTypeDef: {type: 'record_element'},
378
+ fields: [
379
+ {name: 'a', type: 'number'},
380
+ {name: 'b', type: 'number'},
381
+ ],
382
+ name: '',
383
+ };
384
+ const values = [
385
+ recordLiteral({a: 10, b: 11}),
386
+ recordLiteral({a: 20, b: 21}),
387
+ ];
388
+
389
+ const ab = runtime.dialect.sqlLiteralArray({
390
+ node: 'arrayLiteral',
391
+ typeDef: abType,
392
+ kids: {values},
393
+ });
394
+ const ab_eq = [
395
+ {a: 10, b: 11},
396
+ {a: 20, b: 21},
397
+ ];
398
+ const abMalloy = '[{a is 10, b is 11}, {a is 20, b is 21}]';
399
+ function selectAB(n: string) {
400
+ return `SELECT ${ab} AS ${quote(n)}`;
401
+ }
402
+
403
+ test('repeated record from nest', async () => {
404
+ await expect(`
405
+ run: ${conName}.sql("""
406
+ SELECT
407
+ 10 as ${quote('a')},
408
+ 11 as ${quote('b')}
409
+ UNION ALL SELECT 20 , 21
410
+ """) -> { nest: ab is { select: a, b } }
411
+ -> { select: ab.a, ab.b ; order_by: a}
412
+ `).malloyResultMatches(runtime, ab_eq);
413
+ });
414
+ test('select repeated record from literal dialect functions', async () => {
415
+ await expect(`
416
+ run: ${conName}.sql(""" ${selectAB('ab')} """)
417
+ `).malloyResultMatches(runtime, {ab: ab_eq});
418
+ });
419
+ test('repeat record from malloy literal', async () => {
420
+ await expect(`
421
+ run: ${empty}
422
+ -> { select: ab is ${abMalloy} }
423
+ `).malloyResultMatches(runtime, {ab: ab_eq});
424
+ });
425
+ test('repeated record can be selected and renamed', async () => {
426
+ const src = `
427
+ run: ${conName}.sql("""
428
+ ${selectAB('sqlAB')}
429
+ """) -> { select: ab is sqlAB }
430
+ `;
431
+ await expect(src).malloyResultMatches(runtime, {ab: ab_eq});
432
+ });
433
+ test('select repeated record passed down pipeline', async () => {
434
+ await expect(`
435
+ run: ${empty}
436
+ -> { select: pipeAb is ${abMalloy} }
437
+ -> { select: ab is pipeAb }
438
+ `).malloyResultMatches(runtime, {ab: ab_eq});
439
+ });
440
+ test('deref repeat record passed down pipeline', async () => {
441
+ await expect(`
442
+ run: ${empty}
443
+ -> { select: pipeAb is ${abMalloy} }
444
+ -> { select: pipeAb.a, pipeAb.b }
445
+ `).malloyResultMatches(runtime, ab_eq);
446
+ });
447
+ test('select array of records from source', async () => {
448
+ await expect(`
449
+ run: ${empty}
450
+ extend { dimension: abSrc is ${abMalloy} }
451
+ -> { select: ab is abSrc }
452
+ `).malloyResultMatches(runtime, {ab: ab_eq});
453
+ });
454
+ test('deref array of records from source', async () => {
455
+ await expect(`
456
+ run: ${empty}
457
+ extend { dimension: ab is ${abMalloy} }
458
+ -> { select: ab.a, ab.b }
459
+ `).malloyResultMatches(runtime, ab_eq);
460
+ });
461
+ test('repeated record in source wth record property', async () => {
462
+ await expect(`
463
+ run: ${empty} extend { dimension: rec is [ {bc is {b is 'b'}} ] }
464
+ -> { select: rec.bc.b }
465
+ `).malloyResultMatches(runtime, {b: 'b'});
466
+ });
467
+ test('piped repeated record containing an array', async () => {
468
+ await expect(`
469
+ run: ${empty} -> {
470
+ select: rrec is [
471
+ { val is 1, names is ['uno', 'one'] },
472
+ { val is 2, names is ['due', 'two'] }
473
+ ]
474
+ } -> {
475
+ select: val is rrec.val, name is rrec.names.each
476
+ order_by: val desc, name asc
477
+ }
478
+ `).malloyResultMatches(runtime, [
479
+ {val: 2, name: 'due'},
480
+ {val: 2, name: 'two'},
481
+ {val: 1, name: 'one'},
482
+ {val: 1, name: 'uno'},
483
+ ]);
484
+ });
485
+ test('source repeated record containing an array', async () => {
486
+ await expect(`
487
+ run: ${empty} extend {
488
+ dimension: rrec is [
489
+ { val is 1, names is ['uno', 'one'] },
490
+ { val is 2, names is ['due', 'two'] }
491
+ ]
492
+ } -> {
493
+ select: val is rrec.val, name is rrec.names.each
494
+ order_by: val desc, name asc
495
+ }
496
+ `).malloyResultMatches(runtime, [
497
+ {val: 2, name: 'due'},
498
+ {val: 2, name: 'two'},
499
+ {val: 1, name: 'one'},
500
+ {val: 1, name: 'uno'},
501
+ ]);
502
+ });
503
+ });
504
+ }
505
+ );
506
+
507
+ afterAll(async () => {
508
+ await runtimes.closeAll();
509
+ });
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
5
+ * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
8
  import {RuntimeList} from '../../runtimes';
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
5
+ * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
8
  import {RuntimeList} from '../../runtimes';
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
5
+ * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
8
  /* eslint-disable no-console */
@@ -22,6 +22,18 @@ describe.each(runtimes.runtimeList)(
22
22
  throw new Error("Couldn't build runtime");
23
23
  }
24
24
 
25
+ test.when(databaseName === 'presto')('presto explain parser', async () => {
26
+ const abrec = 'CAST(ROW(0,1) AS ROW(a DOUBLE,b DOUBLE))';
27
+ await expect(`
28
+ run: ${databaseName}.sql("""
29
+ SELECT
30
+ ${abrec} as "abrec",
31
+ ARRAY['c', 'd'] as str_array,
32
+ array[1,2,3] as int_array,
33
+ ARRAY[${abrec}] as array_of_abrec
34
+ """)
35
+ `).malloyResultMatches(runtime, {});
36
+ });
25
37
  it(`runs an sql query - ${databaseName}`, async () => {
26
38
  await expect(
27
39
  `run: ${databaseName}.sql("SELECT 1 as n") -> { select: n }`
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
5
+ * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
8
  import {runtimeFor} from './runtimes';
@@ -203,7 +203,7 @@ expect.extend({
203
203
  const actuallyGot = got instanceof Date ? got.getTime() : got;
204
204
  if (typeof mustBe === 'number' && typeof actuallyGot !== 'number') {
205
205
  fails.push(`${expected} Got: Non Numeric '${pGot}'`);
206
- } else if (actuallyGot !== mustBe) {
206
+ } else if (!objectsMatch(actuallyGot, mustBe)) {
207
207
  fails.push(`${expected} Got: ${pGot}`);
208
208
  }
209
209
  } catch (e) {
@@ -332,8 +332,8 @@ function humanReadable(thing: unknown): string {
332
332
 
333
333
  // b is "expected"
334
334
  // a is "actual"
335
- // If expected is an object, all of the keys should also
336
- // match, buy the expected is allowed to have other keys that are not matched
335
+ // If expected is an object, all of the keys should also match,
336
+ // but the expected is allowed to have other keys that are not matched
337
337
  function objectsMatch(a: unknown, b: unknown): boolean {
338
338
  if (
339
339
  typeof b === 'string' ||