@malloydata/malloy-tests 0.0.196-dev241007190115 → 0.0.196-dev241007230836
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/data/mysql/malloytest.mysql.gz +0 -0
- package/mysql/mysql_start.sh +32 -0
- package/mysql/mysql_stop.sh +7 -0
- package/package.json +8 -8
- package/src/databases/all/expr.spec.ts +22 -16
- package/src/databases/all/functions.spec.ts +93 -36
- package/src/databases/all/nomodel.spec.ts +26 -17
- package/src/databases/all/orderby.spec.ts +5 -5
- package/src/runtimes.ts +29 -0
- package/src/util/index.ts +16 -0
- package/tsconfig.json +3 -0
|
Binary file
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#! /bin/bash
|
|
2
|
+
rm -rf .tmp
|
|
3
|
+
mkdir .tmp
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# run docker
|
|
8
|
+
docker run -p 3306:3306 -d -v $PWD/../data/mysql:/init_data --name mysql-malloy -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:8.4.2
|
|
9
|
+
|
|
10
|
+
# wait for server to start
|
|
11
|
+
counter=0
|
|
12
|
+
while ! docker logs mysql-malloy 2>&1 | grep -q "mysqld: ready for connections"
|
|
13
|
+
do
|
|
14
|
+
sleep 10
|
|
15
|
+
counter=$((counter+1))
|
|
16
|
+
# if doesn't start after 2 minutes, output logs and kill process
|
|
17
|
+
if [ $counter -eq 120 ]
|
|
18
|
+
then
|
|
19
|
+
docker logs mysql-malloy >& ./.tmp/mysql-malloy.logs
|
|
20
|
+
docker rm -f mysql-malloy
|
|
21
|
+
echo "MySQL did not start successfully, check .tmp/mysql-malloy.logs"
|
|
22
|
+
exit 1
|
|
23
|
+
break
|
|
24
|
+
fi
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
# load the test data.
|
|
28
|
+
docker exec mysql-malloy cp /init_data/malloytest.mysql.gz /tmp
|
|
29
|
+
docker exec mysql-malloy gunzip /tmp/malloytest.mysql.gz
|
|
30
|
+
docker exec mysql-malloy mysql -uroot -e 'drop database if exists malloytest; create database malloytest; use malloytest; source /tmp/malloytest.mysql'
|
|
31
|
+
|
|
32
|
+
echo "MySQL running on port 3306"
|
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.196-
|
|
25
|
-
"@malloydata/db-duckdb": "^0.0.196-
|
|
26
|
-
"@malloydata/db-postgres": "^0.0.196-
|
|
27
|
-
"@malloydata/db-snowflake": "^0.0.196-
|
|
28
|
-
"@malloydata/db-trino": "^0.0.196-
|
|
29
|
-
"@malloydata/malloy": "^0.0.196-
|
|
30
|
-
"@malloydata/render": "^0.0.196-
|
|
24
|
+
"@malloydata/db-bigquery": "^0.0.196-dev241007230836",
|
|
25
|
+
"@malloydata/db-duckdb": "^0.0.196-dev241007230836",
|
|
26
|
+
"@malloydata/db-postgres": "^0.0.196-dev241007230836",
|
|
27
|
+
"@malloydata/db-snowflake": "^0.0.196-dev241007230836",
|
|
28
|
+
"@malloydata/db-trino": "^0.0.196-dev241007230836",
|
|
29
|
+
"@malloydata/malloy": "^0.0.196-dev241007230836",
|
|
30
|
+
"@malloydata/render": "^0.0.196-dev241007230836",
|
|
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.196-
|
|
40
|
+
"version": "0.0.196-dev241007230836"
|
|
41
41
|
}
|
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
import {RuntimeList, allDatabases} from '../../runtimes';
|
|
26
26
|
import '../../util/db-jest-matchers';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
booleanResult,
|
|
29
|
+
databasesFromEnvironmentOr,
|
|
30
|
+
mkSqlEqWith,
|
|
31
|
+
} from '../../util';
|
|
28
32
|
|
|
29
33
|
const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
|
|
30
34
|
|
|
@@ -129,6 +133,7 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
129
133
|
// simple turtle expressions
|
|
130
134
|
it('simple turtle', async () => {
|
|
131
135
|
await expect(`
|
|
136
|
+
// # test.debug
|
|
132
137
|
run: ${databaseName}.table('malloytest.state_facts') -> {
|
|
133
138
|
group_by: popular_name
|
|
134
139
|
aggregate: airport_count.sum()
|
|
@@ -396,30 +401,28 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
396
401
|
`).malloyResultMatches(expressionModel, {m_count: 63});
|
|
397
402
|
});
|
|
398
403
|
|
|
404
|
+
const nativeInt = databaseName === 'mysql' ? 'signed' : 'integer';
|
|
399
405
|
it('sql cast', async () => {
|
|
400
406
|
await expect(`
|
|
401
407
|
run: aircraft -> {
|
|
402
|
-
group_by: a is "312"::"
|
|
408
|
+
group_by: a is "312"::"${nativeInt}"
|
|
403
409
|
}
|
|
404
410
|
`).malloyResultMatches(expressionModel, {a: 312});
|
|
405
411
|
});
|
|
406
412
|
|
|
407
|
-
test.when(
|
|
408
|
-
|
|
409
|
-
async () => {
|
|
410
|
-
await expect(`
|
|
413
|
+
test.when(runtime.dialect.supportsSafeCast)('sql safe cast', async () => {
|
|
414
|
+
await expect(`
|
|
411
415
|
run: ${databaseName}.sql('SELECT 1 as one') -> { select:
|
|
412
416
|
bad_date is '12a':::date
|
|
413
417
|
bad_number is 'abc':::number
|
|
414
|
-
good_number is "312":::"
|
|
418
|
+
good_number is "312":::"${nativeInt}"
|
|
415
419
|
}
|
|
416
420
|
`).malloyResultMatches(expressionModel, {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
);
|
|
421
|
+
bad_date: null,
|
|
422
|
+
bad_number: null,
|
|
423
|
+
good_number: 312,
|
|
424
|
+
});
|
|
425
|
+
});
|
|
423
426
|
|
|
424
427
|
it('many_field.sum() has correct locality', async () => {
|
|
425
428
|
await expect(`
|
|
@@ -494,8 +497,8 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
494
497
|
group_by: boolean_2 is sql_boolean("\${engines} = 2")
|
|
495
498
|
}
|
|
496
499
|
`).malloyResultMatches(expressionModel, {
|
|
497
|
-
boolean_1: true,
|
|
498
|
-
boolean_2: false,
|
|
500
|
+
boolean_1: booleanResult(true, databaseName),
|
|
501
|
+
boolean_2: booleanResult(false, databaseName),
|
|
499
502
|
});
|
|
500
503
|
});
|
|
501
504
|
|
|
@@ -817,7 +820,10 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
817
820
|
no_paren is false and fot
|
|
818
821
|
paren is false and (fot)
|
|
819
822
|
}`
|
|
820
|
-
).malloyResultMatches(runtime, {
|
|
823
|
+
).malloyResultMatches(runtime, {
|
|
824
|
+
paren: booleanResult(false, databaseName),
|
|
825
|
+
no_paren: booleanResult(false, databaseName),
|
|
826
|
+
});
|
|
821
827
|
});
|
|
822
828
|
});
|
|
823
829
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import {RuntimeList, allDatabases} from '../../runtimes';
|
|
26
|
-
import {brokenIn, databasesFromEnvironmentOr} from '../../util';
|
|
26
|
+
import {booleanResult, brokenIn, databasesFromEnvironmentOr} from '../../util';
|
|
27
27
|
import '../../util/db-jest-matchers';
|
|
28
28
|
import * as malloy from '@malloydata/malloy';
|
|
29
29
|
|
|
@@ -107,7 +107,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
107
107
|
return await expressionModel
|
|
108
108
|
.loadQuery(
|
|
109
109
|
`
|
|
110
|
-
run:
|
|
110
|
+
run: state_facts -> { ${testCases.map(
|
|
111
111
|
(testCase, i) => `group_by: f${i} is ${testCase[0]}`
|
|
112
112
|
)} }`
|
|
113
113
|
)
|
|
@@ -137,7 +137,11 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
137
137
|
["concat(1, 'bar')", '1bar'],
|
|
138
138
|
[
|
|
139
139
|
"concat('cons', true)",
|
|
140
|
-
databaseName === 'postgres'
|
|
140
|
+
databaseName === 'postgres'
|
|
141
|
+
? 'const'
|
|
142
|
+
: databaseName === 'mysql'
|
|
143
|
+
? 'cons1'
|
|
144
|
+
: 'construe',
|
|
141
145
|
],
|
|
142
146
|
["concat('foo', @2003)", 'foo2003-01-01'],
|
|
143
147
|
[
|
|
@@ -216,7 +220,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
216
220
|
["regexp_extract('I have a dog', r'd[aeiou]g')", 'dog'],
|
|
217
221
|
["regexp_extract(null, r'd[aeiou]g')", null],
|
|
218
222
|
["regexp_extract('foo', null)", null],
|
|
219
|
-
["regexp_extract('I have a d0g', r'd
|
|
223
|
+
["regexp_extract('I have a d0g', r'd.g')", 'd0g']
|
|
220
224
|
);
|
|
221
225
|
});
|
|
222
226
|
});
|
|
@@ -230,7 +234,9 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
230
234
|
"replace('axbxc', r'(a).(b).(c)', '\\\\0 - \\\\1 - \\\\2 - \\\\3')",
|
|
231
235
|
databaseName === 'postgres'
|
|
232
236
|
? '\\0 - a - b - c'
|
|
233
|
-
: databaseName === 'trino' ||
|
|
237
|
+
: databaseName === 'trino' ||
|
|
238
|
+
databaseName === 'presto' ||
|
|
239
|
+
databaseName === 'mysql'
|
|
234
240
|
? '0 - 1 - 2 - 3'
|
|
235
241
|
: 'axbxc - a - b - c',
|
|
236
242
|
],
|
|
@@ -263,8 +269,8 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
263
269
|
describe('raw function call', () => {
|
|
264
270
|
it(`works - ${databaseName}`, async () => {
|
|
265
271
|
await funcTestMultiple(
|
|
266
|
-
['floor(
|
|
267
|
-
['floor(
|
|
272
|
+
['floor(sqrt!(25)::number)', 5],
|
|
273
|
+
['floor(sqrt!number(25))', 5],
|
|
268
274
|
["substr('foo bar baz', -3)", 'baz'],
|
|
269
275
|
["substr(nullif('x','x'), 1, 2)", null], // nullMatchesFunctionSignature
|
|
270
276
|
["substr('aaaa', null, 1)", null],
|
|
@@ -275,7 +281,11 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
275
281
|
|
|
276
282
|
describe('stddev', () => {
|
|
277
283
|
// TODO symmetric aggregates don't work with custom aggregate functions in BQ currently
|
|
278
|
-
if (
|
|
284
|
+
if (
|
|
285
|
+
['bigquery', 'snowflake', 'trino', 'presto', 'mysql'].includes(
|
|
286
|
+
databaseName
|
|
287
|
+
)
|
|
288
|
+
)
|
|
279
289
|
return;
|
|
280
290
|
it(`works - ${databaseName}`, async () => {
|
|
281
291
|
await funcTestAgg('round(stddev(aircraft_models.seats))', 29);
|
|
@@ -357,16 +367,35 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
357
367
|
expect(result.data.path(0, 'row_num').value).toBe(1);
|
|
358
368
|
});
|
|
359
369
|
|
|
370
|
+
// should rework the tests to this form....
|
|
371
|
+
// it(`boolean type - ${databaseName}`, async () => {
|
|
372
|
+
// await expect(`
|
|
373
|
+
// # test.debug
|
|
374
|
+
// run: state_facts extend { join_one: airports on airports.state = state } -> {
|
|
375
|
+
// group_by: state
|
|
376
|
+
// nest: q is {
|
|
377
|
+
// group_by: airports.county
|
|
378
|
+
// calculate: row_num is row_number()
|
|
379
|
+
// }
|
|
380
|
+
// }
|
|
381
|
+
// `).malloyResultMatches(expressionModel, {
|
|
382
|
+
// big: 1,
|
|
383
|
+
// model_count: 58451,
|
|
384
|
+
// });
|
|
385
|
+
// });
|
|
386
|
+
|
|
360
387
|
it(`works inside nest - ${databaseName}`, async () => {
|
|
361
388
|
const result = await expressionModel
|
|
362
389
|
.loadQuery(
|
|
363
|
-
`
|
|
390
|
+
`
|
|
391
|
+
run: state_facts extend { join_one: airports on airports.state = state } -> {
|
|
364
392
|
group_by: state
|
|
365
393
|
nest: q is {
|
|
366
394
|
group_by: airports.county
|
|
367
395
|
calculate: row_num is row_number()
|
|
368
396
|
}
|
|
369
|
-
}
|
|
397
|
+
}
|
|
398
|
+
`
|
|
370
399
|
)
|
|
371
400
|
.run();
|
|
372
401
|
expect(result.data.path(0, 'q', 0, 'row_num').value).toBe(1);
|
|
@@ -582,8 +611,12 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
582
611
|
}`
|
|
583
612
|
)
|
|
584
613
|
.run();
|
|
585
|
-
expect(result.data.path(0, 'lag_val').value).toBe(
|
|
586
|
-
|
|
614
|
+
expect(result.data.path(0, 'lag_val').value).toBe(
|
|
615
|
+
booleanResult(true, databaseName)
|
|
616
|
+
);
|
|
617
|
+
expect(result.data.path(1, 'lag_val').value).toBe(
|
|
618
|
+
booleanResult(false, databaseName)
|
|
619
|
+
);
|
|
587
620
|
});
|
|
588
621
|
});
|
|
589
622
|
|
|
@@ -803,7 +836,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
803
836
|
const inf = ['trino', 'presto'].includes(databaseName)
|
|
804
837
|
? 'infinity!()'
|
|
805
838
|
: "'+inf'::number";
|
|
806
|
-
it(`works - ${databaseName}`, async () => {
|
|
839
|
+
it.when(databaseName !== 'mysql')(`works - ${databaseName}`, async () => {
|
|
807
840
|
await funcTestMultiple(
|
|
808
841
|
[`is_inf(${inf})`, true],
|
|
809
842
|
['is_inf(100)', false],
|
|
@@ -812,7 +845,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
812
845
|
});
|
|
813
846
|
});
|
|
814
847
|
describe('is_nan', () => {
|
|
815
|
-
it(`works - ${databaseName}`, async () => {
|
|
848
|
+
it.when(databaseName !== 'mysql')(`works - ${databaseName}`, async () => {
|
|
816
849
|
await funcTestMultiple(
|
|
817
850
|
["is_nan('NaN'::number)", true],
|
|
818
851
|
['is_nan(100)', false],
|
|
@@ -824,10 +857,13 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
824
857
|
it(`works - ${databaseName}`, async () => {
|
|
825
858
|
await funcTestMultiple(
|
|
826
859
|
['greatest(1, 10, -100)', 10],
|
|
827
|
-
[
|
|
860
|
+
[
|
|
861
|
+
'greatest(@2003, @2004, @1994) = @2004',
|
|
862
|
+
booleanResult(true, databaseName),
|
|
863
|
+
],
|
|
828
864
|
[
|
|
829
865
|
'greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00',
|
|
830
|
-
true,
|
|
866
|
+
booleanResult(true, databaseName),
|
|
831
867
|
],
|
|
832
868
|
["greatest('a', 'b')", 'b'],
|
|
833
869
|
['greatest(1, null, 0)', null],
|
|
@@ -839,10 +875,13 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
839
875
|
it(`works - ${databaseName}`, async () => {
|
|
840
876
|
await funcTestMultiple(
|
|
841
877
|
['least(1, 10, -100)', -100],
|
|
842
|
-
[
|
|
878
|
+
[
|
|
879
|
+
'least(@2003, @2004, @1994) = @1994',
|
|
880
|
+
booleanResult(true, databaseName),
|
|
881
|
+
],
|
|
843
882
|
[
|
|
844
883
|
'least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00',
|
|
845
|
-
true,
|
|
884
|
+
booleanResult(true, databaseName),
|
|
846
885
|
],
|
|
847
886
|
["least('a', 'b')", 'a'],
|
|
848
887
|
['least(1, null, 0)', null],
|
|
@@ -872,25 +911,37 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
872
911
|
describe('starts_with', () => {
|
|
873
912
|
it(`works - ${databaseName}`, async () => {
|
|
874
913
|
await funcTestMultiple(
|
|
875
|
-
[
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
914
|
+
[
|
|
915
|
+
"starts_with('hello world', 'hello')",
|
|
916
|
+
booleanResult(true, databaseName),
|
|
917
|
+
],
|
|
918
|
+
[
|
|
919
|
+
"starts_with('hello world', 'world')",
|
|
920
|
+
booleanResult(false, databaseName),
|
|
921
|
+
],
|
|
922
|
+
["starts_with(null, 'world')", booleanResult(false, databaseName)],
|
|
923
|
+
["starts_with('hello world', null)", booleanResult(false, databaseName)]
|
|
879
924
|
);
|
|
880
925
|
});
|
|
881
926
|
});
|
|
882
927
|
describe('ends_with', () => {
|
|
883
928
|
it(`works - ${databaseName}`, async () => {
|
|
884
929
|
await funcTestMultiple(
|
|
885
|
-
[
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
930
|
+
[
|
|
931
|
+
"ends_with('hello world', 'world')",
|
|
932
|
+
booleanResult(true, databaseName),
|
|
933
|
+
],
|
|
934
|
+
[
|
|
935
|
+
"ends_with('hello world', 'hello')",
|
|
936
|
+
booleanResult(false, databaseName),
|
|
937
|
+
],
|
|
938
|
+
["ends_with(null, 'world')", booleanResult(false, databaseName)],
|
|
939
|
+
["ends_with('hello world', null)", booleanResult(false, databaseName)]
|
|
889
940
|
);
|
|
890
941
|
});
|
|
891
942
|
});
|
|
892
943
|
describe('trim', () => {
|
|
893
|
-
it(`works - ${databaseName}`, async () => {
|
|
944
|
+
it(`trim works - ${databaseName}`, async () => {
|
|
894
945
|
await funcTestMultiple(
|
|
895
946
|
["trim(' keep this ')", 'keep this'],
|
|
896
947
|
["trim('_ _keep_this_ _', '_ ')", 'keep_this'],
|
|
@@ -902,7 +953,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
902
953
|
});
|
|
903
954
|
});
|
|
904
955
|
describe('ltrim', () => {
|
|
905
|
-
it(`works - ${databaseName}`, async () => {
|
|
956
|
+
it(`ltrim works - ${databaseName}`, async () => {
|
|
906
957
|
await funcTestMultiple(
|
|
907
958
|
["ltrim(' keep this -> ')", 'keep this -> '],
|
|
908
959
|
["ltrim('_ _keep_this -> _ _', '_ ')", 'keep_this -> _ _'],
|
|
@@ -914,7 +965,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
914
965
|
});
|
|
915
966
|
});
|
|
916
967
|
describe('rtrim', () => {
|
|
917
|
-
it(`works - ${databaseName}`, async () => {
|
|
968
|
+
it(`rtrim works - ${databaseName}`, async () => {
|
|
918
969
|
await funcTestMultiple(
|
|
919
970
|
["rtrim(' <- keep this ')", ' <- keep this'],
|
|
920
971
|
["rtrim('_ _ <- keep_this_ _', '_ ')", '_ _ <- keep_this'],
|
|
@@ -930,12 +981,15 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
930
981
|
// There are around a billion values that rand() can be, so if this
|
|
931
982
|
// test fails, most likely something is broken. Otherwise, you're the lucky
|
|
932
983
|
// one in a billion!
|
|
933
|
-
await funcTest('rand() = rand()', false);
|
|
984
|
+
await funcTest('rand() = rand()', booleanResult(false, databaseName));
|
|
934
985
|
});
|
|
935
986
|
});
|
|
936
987
|
describe('pi', () => {
|
|
937
988
|
it(`is pi - ${databaseName}`, async () => {
|
|
938
|
-
await funcTest(
|
|
989
|
+
await funcTest(
|
|
990
|
+
'abs(pi() - 3.141592653589793) < 0.0000000000001',
|
|
991
|
+
booleanResult(true, databaseName)
|
|
992
|
+
);
|
|
939
993
|
});
|
|
940
994
|
});
|
|
941
995
|
|
|
@@ -1098,8 +1152,8 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
1098
1152
|
aggregate: also_passes is abs(count_approx(airport_count)-count(airport_count))/count(airport_count) < 0.3
|
|
1099
1153
|
}
|
|
1100
1154
|
`).malloyResultMatches(runtime, {
|
|
1101
|
-
'passes': true,
|
|
1102
|
-
'also_passes': true,
|
|
1155
|
+
'passes': booleanResult(true, databaseName),
|
|
1156
|
+
'also_passes': booleanResult(true, databaseName),
|
|
1103
1157
|
});
|
|
1104
1158
|
});
|
|
1105
1159
|
test.when(supported)('works with fanout', async () => {
|
|
@@ -1111,7 +1165,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
1111
1165
|
run: state_facts_fanout -> {
|
|
1112
1166
|
aggregate: x is state_facts.state.count_approx() > 0
|
|
1113
1167
|
}
|
|
1114
|
-
`).malloyResultMatches(runtime, {x: true});
|
|
1168
|
+
`).malloyResultMatches(runtime, {x: booleanResult(true, databaseName)});
|
|
1115
1169
|
});
|
|
1116
1170
|
});
|
|
1117
1171
|
describe('last_value', () => {
|
|
@@ -1262,7 +1316,10 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
1262
1316
|
describe('duckdb', () => {
|
|
1263
1317
|
const duckdb = it.when(databaseName === 'duckdb');
|
|
1264
1318
|
duckdb('to_timestamp', async () => {
|
|
1265
|
-
await funcTest(
|
|
1319
|
+
await funcTest(
|
|
1320
|
+
'to_timestamp(1725555835) = @2024-09-05 17:03:55',
|
|
1321
|
+
booleanResult(true, databaseName)
|
|
1322
|
+
);
|
|
1266
1323
|
});
|
|
1267
1324
|
});
|
|
1268
1325
|
|
|
@@ -1271,7 +1328,7 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
1271
1328
|
trino('from_unixtime', async () => {
|
|
1272
1329
|
await funcTest(
|
|
1273
1330
|
'from_unixtime(1725555835) = @2024-09-05 17:03:55',
|
|
1274
|
-
true
|
|
1331
|
+
booleanResult(true, databaseName)
|
|
1275
1332
|
);
|
|
1276
1333
|
});
|
|
1277
1334
|
});
|
|
@@ -64,8 +64,10 @@ afterAll(async () => {
|
|
|
64
64
|
runtimes.runtimeMap.forEach((runtime, databaseName) => {
|
|
65
65
|
const q = runtime.getQuoter();
|
|
66
66
|
// Issue #1824
|
|
67
|
-
it(
|
|
68
|
-
|
|
67
|
+
it.when(runtime.dialect.nativeBoolean)(
|
|
68
|
+
`not boolean field with null - ${databaseName}`,
|
|
69
|
+
async () => {
|
|
70
|
+
await expect(`
|
|
69
71
|
run: ${databaseName}.sql("""
|
|
70
72
|
SELECT
|
|
71
73
|
CASE WHEN 1=1 THEN NULL ELSE false END as ${q`n`}
|
|
@@ -73,14 +75,17 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
|
|
|
73
75
|
select:
|
|
74
76
|
is_true is not n
|
|
75
77
|
}
|
|
76
|
-
`).malloyResultMatches(runtime, {
|
|
77
|
-
|
|
78
|
+
`).malloyResultMatches(runtime, {
|
|
79
|
+
is_true: true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
);
|
|
78
83
|
|
|
79
84
|
// Issue: #1284
|
|
80
85
|
it(`parenthesize output field values - ${databaseName}`, async () => {
|
|
81
86
|
await expect(`
|
|
82
87
|
run: ${databaseName}.table('malloytest.aircraft') -> {
|
|
83
|
-
group_by: r is 1
|
|
88
|
+
group_by: r is 1.0
|
|
84
89
|
|
|
85
90
|
calculate:
|
|
86
91
|
zero is 1 - rank()
|
|
@@ -413,11 +418,13 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
|
|
|
413
418
|
});
|
|
414
419
|
});
|
|
415
420
|
|
|
416
|
-
it(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
+
it.when(runtime.dialect.supportsFullJoin)(
|
|
422
|
+
`join full - ${databaseName}`,
|
|
423
|
+
async () => {
|
|
424
|
+
// a cross join produces a Many to Many result.
|
|
425
|
+
// symmetric aggregate are needed on both sides of the join
|
|
426
|
+
// Check the row count and that sums on each side work properly.
|
|
427
|
+
await expect(`
|
|
421
428
|
${matrixModel}
|
|
422
429
|
run: ac_states -> {
|
|
423
430
|
extend: {
|
|
@@ -431,12 +438,13 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
|
|
|
431
438
|
|
|
432
439
|
}
|
|
433
440
|
`).malloyResultMatches(runtime, {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
441
|
+
ac_count: 49,
|
|
442
|
+
ac_sum: 21336,
|
|
443
|
+
am_count: 12,
|
|
444
|
+
am_sum: 4139,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
);
|
|
440
448
|
|
|
441
449
|
it(`leafy count - ${databaseName}`, async () => {
|
|
442
450
|
// in a joined table when the joined is leafiest
|
|
@@ -948,12 +956,13 @@ SELECT row_to_json(finalStage) as row FROM __stage0 AS finalStage`);
|
|
|
948
956
|
).malloyResultMatches(runtime, {a: 1});
|
|
949
957
|
});
|
|
950
958
|
|
|
959
|
+
// weirdly '*' must be the first thing in the select list in MySQL
|
|
951
960
|
it(`sql with turducken- ${databaseName}`, async () => {
|
|
952
961
|
const turduckenQuery = `
|
|
953
962
|
run: ${databaseName}.sql("""
|
|
954
963
|
SELECT
|
|
955
|
-
'something' as SOMETHING,
|
|
956
964
|
*
|
|
965
|
+
, 'something' as SOMETHING
|
|
957
966
|
FROM %{
|
|
958
967
|
${databaseName}.table('malloytest.state_facts') -> {
|
|
959
968
|
group_by: popular_name
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import {RuntimeList, allDatabases} from '../../runtimes';
|
|
26
|
-
import {databasesFromEnvironmentOr} from '../../util';
|
|
26
|
+
import {booleanResult, databasesFromEnvironmentOr} from '../../util';
|
|
27
27
|
import '../../util/db-jest-matchers';
|
|
28
28
|
|
|
29
29
|
const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
|
|
@@ -45,7 +45,7 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
45
45
|
aggregate: model_count is count()
|
|
46
46
|
}
|
|
47
47
|
`).malloyResultMatches(orderByModel, {
|
|
48
|
-
big: false,
|
|
48
|
+
big: booleanResult(false, databaseName),
|
|
49
49
|
model_count: 58451,
|
|
50
50
|
});
|
|
51
51
|
});
|
|
@@ -62,7 +62,7 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
62
62
|
aggregate: model_count is model_count.sum()
|
|
63
63
|
}
|
|
64
64
|
`).malloyResultMatches(orderByModel, {
|
|
65
|
-
big: false,
|
|
65
|
+
big: booleanResult(false, databaseName),
|
|
66
66
|
model_count: 58500,
|
|
67
67
|
});
|
|
68
68
|
});
|
|
@@ -91,9 +91,9 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
91
91
|
`reserved words are quoted in turtles - ${databaseName}`,
|
|
92
92
|
async () => {
|
|
93
93
|
await expect(`
|
|
94
|
-
run:
|
|
94
|
+
run: ${databaseName}.table('malloytest.state_facts')->{
|
|
95
95
|
nest: withx is {
|
|
96
|
-
group_by: select is
|
|
96
|
+
group_by: select is upper(popular_name)
|
|
97
97
|
aggregate: fetch is count()
|
|
98
98
|
}
|
|
99
99
|
} -> {
|
package/src/runtimes.ts
CHANGED
|
@@ -38,6 +38,10 @@ import {PooledPostgresConnection} from '@malloydata/db-postgres';
|
|
|
38
38
|
import {TrinoConnection, TrinoExecutor} from '@malloydata/db-trino';
|
|
39
39
|
import {SnowflakeExecutor} from '@malloydata/db-snowflake/src/snowflake_executor';
|
|
40
40
|
import {PrestoConnection} from '@malloydata/db-trino/src/trino_connection';
|
|
41
|
+
import {
|
|
42
|
+
MySQLConnection,
|
|
43
|
+
MySQLExecutor,
|
|
44
|
+
} from '@malloydata/db-mysql/src/mysql_connection';
|
|
41
45
|
import {EventEmitter} from 'events';
|
|
42
46
|
|
|
43
47
|
export class SnowflakeTestConnection extends SnowflakeConnection {
|
|
@@ -72,6 +76,23 @@ export class BigQueryTestConnection extends BigQueryConnection {
|
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
export class MySQLTestConnection extends MySQLConnection {
|
|
80
|
+
// we probably need a better way to do this.
|
|
81
|
+
|
|
82
|
+
public async runSQL(
|
|
83
|
+
sqlCommand: string,
|
|
84
|
+
options?: RunSQLOptions
|
|
85
|
+
): Promise<MalloyQueryData> {
|
|
86
|
+
try {
|
|
87
|
+
return await super.runSQL(sqlCommand, options);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(`Error in SQL:\n ${sqlCommand}`);
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
75
96
|
export class PostgresTestConnection extends PooledPostgresConnection {
|
|
76
97
|
// we probably need a better way to do this.
|
|
77
98
|
|
|
@@ -175,6 +196,13 @@ export function runtimeFor(dbName: string): SingleConnectionRuntime {
|
|
|
175
196
|
TrinoExecutor.getConnectionOptionsFromEnv(dbName)
|
|
176
197
|
);
|
|
177
198
|
break;
|
|
199
|
+
case 'mysql':
|
|
200
|
+
connection = new MySQLConnection(
|
|
201
|
+
dbName,
|
|
202
|
+
MySQLExecutor.getConnectionOptionsFromEnv(),
|
|
203
|
+
{}
|
|
204
|
+
);
|
|
205
|
+
break;
|
|
178
206
|
case 'presto':
|
|
179
207
|
connection = new PrestoConnection(
|
|
180
208
|
dbName,
|
|
@@ -208,6 +236,7 @@ export const allDatabases = [
|
|
|
208
236
|
'duckdb_wasm',
|
|
209
237
|
'snowflake',
|
|
210
238
|
'trino',
|
|
239
|
+
'mysql',
|
|
211
240
|
];
|
|
212
241
|
|
|
213
242
|
type RuntimeDatabaseNames = (typeof allDatabases)[number];
|
package/src/util/index.ts
CHANGED
|
@@ -198,3 +198,19 @@ export async function runQuery(runtime: Runtime, querySrc: string) {
|
|
|
198
198
|
|
|
199
199
|
return result;
|
|
200
200
|
}
|
|
201
|
+
|
|
202
|
+
export function booleanResult(value: boolean, dbName: string) {
|
|
203
|
+
if (dbName === 'mysql') {
|
|
204
|
+
return value ? 1 : 0;
|
|
205
|
+
} else {
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function booleanCode(value: boolean, dbName: string) {
|
|
211
|
+
if (dbName === 'mysql') {
|
|
212
|
+
return value ? '1' : '0';
|
|
213
|
+
} else {
|
|
214
|
+
return value ? 'true' : 'false';
|
|
215
|
+
}
|
|
216
|
+
}
|