@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.
- package/mysql/mysql_start.sh +7 -2
- package/mysql/mysql_stop.sh +1 -1
- package/package.json +8 -8
- package/src/databases/all/compound-atomic.spec.ts +509 -0
- package/src/databases/duckdb/materialization.spec.ts +1 -1
- package/src/databases/duckdb/reference-id.spec.ts +1 -1
- package/src/databases/presto-trino/presto-trino.spec.ts +13 -1
- package/src/events.spec.ts +1 -1
- package/src/util/db-jest-matchers.ts +3 -3
package/mysql/mysql_start.sh
CHANGED
|
@@ -5,11 +5,13 @@ rm -rf .tmp
|
|
|
5
5
|
mkdir .tmp
|
|
6
6
|
|
|
7
7
|
# run docker
|
|
8
|
-
SCRIPTDIR=$(dirname $0)
|
|
9
|
-
|
|
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;'
|
package/mysql/mysql_stop.sh
CHANGED
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-
|
|
25
|
-
"@malloydata/db-duckdb": "^0.0.220-
|
|
26
|
-
"@malloydata/db-postgres": "^0.0.220-
|
|
27
|
-
"@malloydata/db-snowflake": "^0.0.220-
|
|
28
|
-
"@malloydata/db-trino": "^0.0.220-
|
|
29
|
-
"@malloydata/malloy": "^0.0.220-
|
|
30
|
-
"@malloydata/render": "^0.0.220-
|
|
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-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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 }`
|
package/src/events.spec.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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' ||
|