@malloydata/malloy-tests 0.0.434 → 0.0.436
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/CONTEXT.md +5 -2
- package/mssql/connection_string.ts +19 -3
- package/mssql/mssql_start.sh +19 -13
- package/mssql/mssql_stop.sh +2 -2
- package/package.json +12 -11
- package/src/core/experimental-dialects.spec.ts +125 -41
- package/src/core/persist.spec.ts +275 -3
- package/src/databases/all/db_filter_expressions.spec.ts +7 -0
- package/src/databases/all/db_index.spec.ts +4 -2
- package/src/databases/all/expr.spec.ts +9 -8
- package/src/databases/all/functions.spec.ts +83 -57
- package/src/databases/all/new-matchers.spec.ts +17 -11
- package/src/databases/all/orderby.spec.ts +19 -12
- package/src/databases/all/test-select.spec.ts +5 -2
- package/src/databases/all/time.spec.ts +191 -45
- package/src/databases/sqlserver/sqlserver.spec.ts +509 -0
- package/src/runtimes.ts +49 -10
- package/src/test-select.ts +10 -14
- package/tsconfig.json +3 -0
|
@@ -697,6 +697,13 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
|
|
|
697
697
|
run: range + { where: t ~ f'2001' }
|
|
698
698
|
`).toMatchRows(range, inRange);
|
|
699
699
|
});
|
|
700
|
+
test('date in month, in a query time zone east of UTC', async () => {
|
|
701
|
+
// Midnight starting a month in Tokyo is still the previous day in UTC
|
|
702
|
+
const range = mkDateRange('2001-01-01', '2001-02-01');
|
|
703
|
+
await expect(`
|
|
704
|
+
run: range + { timezone: 'Asia/Tokyo'; where: t ~ f'2001-01' }
|
|
705
|
+
`).toMatchRows(range, inRange);
|
|
706
|
+
});
|
|
700
707
|
test('2 days ago', async () => {
|
|
701
708
|
nowIs('2001-01-15 12:00:00');
|
|
702
709
|
const rangeQuery = mkRangeQuery(
|
|
@@ -79,11 +79,13 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
|
|
|
79
79
|
`).toMatchResult(testModel, {fieldName: 'one', weight: 51});
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
// bigquery doesn't support row count based sampling
|
|
82
|
+
// bigquery doesn't support row count based sampling; sqlserver's TABLESAMPLE
|
|
83
|
+
// returns whole pages, not the count asked for.
|
|
83
84
|
test.when(
|
|
84
85
|
databaseName !== 'bigquery' &&
|
|
85
86
|
databaseName !== 'trino' &&
|
|
86
|
-
databaseName !== 'presto'
|
|
87
|
+
databaseName !== 'presto' &&
|
|
88
|
+
databaseName !== 'sqlserver'
|
|
87
89
|
)(`index rows count - ${databaseName}`, async () => {
|
|
88
90
|
await expect(`
|
|
89
91
|
run: ${databaseName}.table('malloytest.state_facts') extend {
|
|
@@ -527,12 +527,13 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
527
527
|
source: a is ${databaseName}.table('malloytest.aircraft_models') extend { where: aircraft_model_code ? '0270202' }
|
|
528
528
|
|
|
529
529
|
run: a -> {
|
|
530
|
-
group_by:
|
|
531
|
-
|
|
530
|
+
group_by:
|
|
531
|
+
boolean_1 is pick 'yes' when sql_boolean("\${seats} > 20") else 'no'
|
|
532
|
+
boolean_2 is pick 'yes' when sql_boolean("\${engines} = 2") else 'no'
|
|
532
533
|
}
|
|
533
534
|
`).toMatchResult(testModel, {
|
|
534
|
-
boolean_1:
|
|
535
|
-
boolean_2:
|
|
535
|
+
boolean_1: 'yes',
|
|
536
|
+
boolean_2: 'no',
|
|
536
537
|
});
|
|
537
538
|
});
|
|
538
539
|
|
|
@@ -1067,12 +1068,12 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
1067
1068
|
dimension: fot is (false) or (true)
|
|
1068
1069
|
} -> {
|
|
1069
1070
|
select:
|
|
1070
|
-
no_paren is false and fot
|
|
1071
|
-
paren is false and (fot)
|
|
1071
|
+
no_paren is pick 'yes' when false and fot else 'no'
|
|
1072
|
+
paren is pick 'yes' when false and (fot) else 'no'
|
|
1072
1073
|
}`
|
|
1073
1074
|
).toMatchResult(testModel, {
|
|
1074
|
-
paren:
|
|
1075
|
-
no_paren:
|
|
1075
|
+
paren: 'no',
|
|
1076
|
+
no_paren: 'no',
|
|
1076
1077
|
});
|
|
1077
1078
|
});
|
|
1078
1079
|
});
|
|
@@ -51,8 +51,6 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
51
51
|
const expressionModel = x.expressionModel;
|
|
52
52
|
const runtime = x.runtime;
|
|
53
53
|
const testModel = wrapTestModel(runtime, modelText(databaseName));
|
|
54
|
-
const dbTrue = runtime.dialect.resultBoolean(true);
|
|
55
|
-
const dbFalse = runtime.dialect.resultBoolean(false);
|
|
56
54
|
const funcTestGeneral = async (
|
|
57
55
|
expr: string,
|
|
58
56
|
type: 'group_by' | 'aggregate',
|
|
@@ -124,14 +122,6 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
124
122
|
await funcTestMultiple(
|
|
125
123
|
["concat('foo', 'bar')", 'foobar'],
|
|
126
124
|
["concat(1, 'bar')", '1bar'],
|
|
127
|
-
[
|
|
128
|
-
"concat('cons', true)",
|
|
129
|
-
databaseName === 'postgres'
|
|
130
|
-
? 'const'
|
|
131
|
-
: databaseName === 'mysql'
|
|
132
|
-
? 'cons1'
|
|
133
|
-
: 'construe',
|
|
134
|
-
],
|
|
135
125
|
["concat('foo', @2003)", 'foo2003-01-01'],
|
|
136
126
|
[
|
|
137
127
|
"concat('foo', @2003-01-01 12:00:00)",
|
|
@@ -142,6 +132,20 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
142
132
|
['concat()', '']
|
|
143
133
|
);
|
|
144
134
|
});
|
|
135
|
+
it.when(
|
|
136
|
+
runtime.dialect.booleanType !== 'none' &&
|
|
137
|
+
!brokenIn('trino', databaseName) &&
|
|
138
|
+
!brokenIn('presto', databaseName)
|
|
139
|
+
)(`works with a boolean - ${databaseName}`, async () => {
|
|
140
|
+
await funcTest(
|
|
141
|
+
"concat('cons', true)",
|
|
142
|
+
databaseName === 'postgres'
|
|
143
|
+
? 'const'
|
|
144
|
+
: databaseName === 'mysql'
|
|
145
|
+
? 'cons1'
|
|
146
|
+
: 'construe'
|
|
147
|
+
);
|
|
148
|
+
});
|
|
145
149
|
});
|
|
146
150
|
|
|
147
151
|
describe('round', () => {
|
|
@@ -624,16 +628,13 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
624
628
|
# test.debug
|
|
625
629
|
run: state_facts -> {
|
|
626
630
|
group_by: state
|
|
627
|
-
calculate: lag_val is
|
|
631
|
+
calculate: lag_val is
|
|
632
|
+
pick 'now' when lag(@2011-11-11 11:11:11, 1, now).year = now.year else 'then'
|
|
628
633
|
}`
|
|
629
634
|
)
|
|
630
635
|
.run();
|
|
631
|
-
expect(result.data.path(0, 'lag_val').value).toBe(
|
|
632
|
-
|
|
633
|
-
);
|
|
634
|
-
expect(result.data.path(1, 'lag_val').value).toBe(
|
|
635
|
-
runtime.dialect.resultBoolean(false)
|
|
636
|
-
);
|
|
636
|
+
expect(result.data.path(0, 'lag_val').value).toBe('now');
|
|
637
|
+
expect(result.data.path(1, 'lag_val').value).toBe('then');
|
|
637
638
|
});
|
|
638
639
|
});
|
|
639
640
|
|
|
@@ -855,32 +856,45 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
855
856
|
: "'+inf'::number";
|
|
856
857
|
it.when(databaseName !== 'mysql')(`works - ${databaseName}`, async () => {
|
|
857
858
|
await funcTestMultiple(
|
|
858
|
-
[`is_inf(${inf})`,
|
|
859
|
-
['is_inf(100)',
|
|
860
|
-
['is_inf(null)',
|
|
859
|
+
[`pick 'yes' when is_inf(${inf}) else 'no'`, 'yes'],
|
|
860
|
+
["pick 'yes' when is_inf(100) else 'no'", 'no'],
|
|
861
|
+
["pick 'yes' when is_inf(null) else 'no'", 'no']
|
|
861
862
|
);
|
|
862
863
|
});
|
|
864
|
+
// A null input is false, not null
|
|
865
|
+
it.when(databaseName !== 'mysql' && runtime.dialect.booleanType !== 'none')(
|
|
866
|
+
`is false for null - ${databaseName}`,
|
|
867
|
+
async () => {
|
|
868
|
+
await funcTest('is_inf(null)', runtime.dialect.resultBoolean(false));
|
|
869
|
+
}
|
|
870
|
+
);
|
|
863
871
|
});
|
|
864
872
|
describe('is_nan', () => {
|
|
865
873
|
it.when(databaseName !== 'mysql')(`works - ${databaseName}`, async () => {
|
|
866
874
|
await funcTestMultiple(
|
|
867
|
-
["is_nan('NaN'::number)",
|
|
868
|
-
['is_nan(100)',
|
|
869
|
-
['is_nan(null)',
|
|
875
|
+
["pick 'yes' when is_nan('NaN'::number) else 'no'", 'yes'],
|
|
876
|
+
["pick 'yes' when is_nan(100) else 'no'", 'no'],
|
|
877
|
+
["pick 'yes' when is_nan(null) else 'no'", 'no']
|
|
870
878
|
);
|
|
871
879
|
});
|
|
880
|
+
it.when(databaseName !== 'mysql' && runtime.dialect.booleanType !== 'none')(
|
|
881
|
+
`is false for null - ${databaseName}`,
|
|
882
|
+
async () => {
|
|
883
|
+
await funcTest('is_nan(null)', runtime.dialect.resultBoolean(false));
|
|
884
|
+
}
|
|
885
|
+
);
|
|
872
886
|
});
|
|
873
887
|
describe('greatest', () => {
|
|
874
888
|
it(`works - ${databaseName}`, async () => {
|
|
875
889
|
await funcTestMultiple(
|
|
876
890
|
['greatest(1, 10, -100)', 10],
|
|
877
891
|
[
|
|
878
|
-
'greatest(@2003, @2004, @1994) = @2004',
|
|
879
|
-
|
|
892
|
+
"pick 'yes' when greatest(@2003, @2004, @1994) = @2004 else 'no'",
|
|
893
|
+
'yes',
|
|
880
894
|
],
|
|
881
895
|
[
|
|
882
|
-
'greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00',
|
|
883
|
-
|
|
896
|
+
"pick 'yes' when greatest(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:59:00 else 'no'",
|
|
897
|
+
'yes',
|
|
884
898
|
],
|
|
885
899
|
["greatest('a', 'b')", 'b'],
|
|
886
900
|
['greatest(1, null, 0)', null],
|
|
@@ -892,13 +906,10 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
892
906
|
it(`works - ${databaseName}`, async () => {
|
|
893
907
|
await funcTestMultiple(
|
|
894
908
|
['least(1, 10, -100)', -100],
|
|
909
|
+
["pick 'yes' when least(@2003, @2004, @1994) = @1994 else 'no'", 'yes'],
|
|
895
910
|
[
|
|
896
|
-
'least(@
|
|
897
|
-
|
|
898
|
-
],
|
|
899
|
-
[
|
|
900
|
-
'least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00',
|
|
901
|
-
runtime.dialect.resultBoolean(true),
|
|
911
|
+
"pick 'yes' when least(@2023-05-26 11:58:00, @2023-05-26 11:59:00) = @2023-05-26 11:58:00 else 'no'",
|
|
912
|
+
'yes',
|
|
902
913
|
],
|
|
903
914
|
["least('a', 'b')", 'a'],
|
|
904
915
|
['least(1, null, 0)', null],
|
|
@@ -929,36 +940,48 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
929
940
|
it(`works - ${databaseName}`, async () => {
|
|
930
941
|
await funcTestMultiple(
|
|
931
942
|
[
|
|
932
|
-
"starts_with('hello world', 'hello')",
|
|
933
|
-
|
|
943
|
+
"pick 'yes' when starts_with('hello world', 'hello') else 'no'",
|
|
944
|
+
'yes',
|
|
934
945
|
],
|
|
935
|
-
[
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
],
|
|
939
|
-
["starts_with(null, 'world')", runtime.dialect.resultBoolean(false)],
|
|
940
|
-
[
|
|
941
|
-
"starts_with('hello world', null)",
|
|
942
|
-
runtime.dialect.resultBoolean(false),
|
|
943
|
-
]
|
|
946
|
+
["pick 'yes' when starts_with('hello world', 'world') else 'no'", 'no'],
|
|
947
|
+
["pick 'yes' when starts_with(null, 'world') else 'no'", 'no'],
|
|
948
|
+
["pick 'yes' when starts_with('hello world', null) else 'no'", 'no']
|
|
944
949
|
);
|
|
945
950
|
});
|
|
951
|
+
it.when(runtime.dialect.booleanType !== 'none')(
|
|
952
|
+
`is false for null - ${databaseName}`,
|
|
953
|
+
async () => {
|
|
954
|
+
await funcTestMultiple(
|
|
955
|
+
["starts_with(null, 'world')", runtime.dialect.resultBoolean(false)],
|
|
956
|
+
[
|
|
957
|
+
"starts_with('hello world', null)",
|
|
958
|
+
runtime.dialect.resultBoolean(false),
|
|
959
|
+
]
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
);
|
|
946
963
|
});
|
|
947
964
|
describe('ends_with', () => {
|
|
948
965
|
it(`works - ${databaseName}`, async () => {
|
|
949
966
|
await funcTestMultiple(
|
|
950
|
-
[
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
]
|
|
954
|
-
[
|
|
955
|
-
"ends_with('hello world', 'hello')",
|
|
956
|
-
runtime.dialect.resultBoolean(false),
|
|
957
|
-
],
|
|
958
|
-
["ends_with(null, 'world')", runtime.dialect.resultBoolean(false)],
|
|
959
|
-
["ends_with('hello world', null)", runtime.dialect.resultBoolean(false)]
|
|
967
|
+
["pick 'yes' when ends_with('hello world', 'world') else 'no'", 'yes'],
|
|
968
|
+
["pick 'yes' when ends_with('hello world', 'hello') else 'no'", 'no'],
|
|
969
|
+
["pick 'yes' when ends_with(null, 'world') else 'no'", 'no'],
|
|
970
|
+
["pick 'yes' when ends_with('hello world', null) else 'no'", 'no']
|
|
960
971
|
);
|
|
961
972
|
});
|
|
973
|
+
it.when(runtime.dialect.booleanType !== 'none')(
|
|
974
|
+
`is false for null - ${databaseName}`,
|
|
975
|
+
async () => {
|
|
976
|
+
await funcTestMultiple(
|
|
977
|
+
["ends_with(null, 'world')", runtime.dialect.resultBoolean(false)],
|
|
978
|
+
[
|
|
979
|
+
"ends_with('hello world', null)",
|
|
980
|
+
runtime.dialect.resultBoolean(false),
|
|
981
|
+
]
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
);
|
|
962
985
|
});
|
|
963
986
|
describe('trim', () => {
|
|
964
987
|
it(`trim works - ${databaseName}`, async () => {
|
|
@@ -1001,14 +1024,17 @@ expressionModels.forEach((x, databaseName) => {
|
|
|
1001
1024
|
// There are around a billion values that rand() can be, so if this
|
|
1002
1025
|
// test fails, most likely something is broken. Otherwise, you're the lucky
|
|
1003
1026
|
// one in a billion!
|
|
1004
|
-
await funcTest(
|
|
1027
|
+
await funcTest(
|
|
1028
|
+
"pick 'same' when rand() = rand() else 'differ'",
|
|
1029
|
+
'differ'
|
|
1030
|
+
);
|
|
1005
1031
|
});
|
|
1006
1032
|
});
|
|
1007
1033
|
describe('pi', () => {
|
|
1008
1034
|
it(`is pi - ${databaseName}`, async () => {
|
|
1009
1035
|
await funcTest(
|
|
1010
|
-
'abs(pi() - 3.141592653589793) < 0.0000000000001',
|
|
1011
|
-
|
|
1036
|
+
"pick 'yes' when abs(pi() - 3.141592653589793) < 0.0000000000001 else 'no'",
|
|
1037
|
+
'yes'
|
|
1012
1038
|
);
|
|
1013
1039
|
});
|
|
1014
1040
|
});
|
|
@@ -11,8 +11,11 @@ import '@malloydata/malloy/test/matchers';
|
|
|
11
11
|
const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
|
|
12
12
|
|
|
13
13
|
describe.each(runtimes.runtimeList)('New matchers for %s', (db, runtime) => {
|
|
14
|
+
// A dialect without boolean values has no boolean column
|
|
15
|
+
const booleans = runtime.dialect.booleanType !== 'none';
|
|
16
|
+
|
|
14
17
|
describe('mkTestModel and TV', () => {
|
|
15
|
-
test('basic types with type inference', async () => {
|
|
18
|
+
test.when(booleans)('basic types with type inference', async () => {
|
|
16
19
|
const tm = mkTestModel(runtime, {
|
|
17
20
|
data: [
|
|
18
21
|
{t_int: 1, t_string: 'a', t_bool: true},
|
|
@@ -155,16 +158,19 @@ describe.each(runtimes.runtimeList)('New matchers for %s', (db, runtime) => {
|
|
|
155
158
|
});
|
|
156
159
|
});
|
|
157
160
|
|
|
158
|
-
test(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
test.when(booleans)(
|
|
162
|
+
'plain booleans work with dialect boolean handling',
|
|
163
|
+
async () => {
|
|
164
|
+
const tm = mkTestModel(runtime, {
|
|
165
|
+
data: [{a: true, b: false}],
|
|
166
|
+
});
|
|
167
|
+
// Booleans are handled by dialect.resultBoolean() in the matcher
|
|
168
|
+
await expect('run: data -> { select: * }').toMatchResult(tm, {
|
|
169
|
+
a: true,
|
|
170
|
+
b: false,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
);
|
|
168
174
|
});
|
|
169
175
|
|
|
170
176
|
describe('test.debug tag', () => {
|
|
@@ -24,20 +24,26 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
24
24
|
}`
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// A dialect without boolean values cannot group by one
|
|
28
|
+
it.when(runtime.dialect.booleanType !== 'none')(
|
|
29
|
+
`boolean type - ${databaseName}`,
|
|
30
|
+
async () => {
|
|
31
|
+
await expect(`
|
|
29
32
|
run: models-> {
|
|
30
33
|
group_by: big is seats >=20
|
|
31
34
|
aggregate: model_count is count()
|
|
32
35
|
}
|
|
33
36
|
`).toMatchResult(orderByModel, {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
big: false,
|
|
38
|
+
model_count: 58451,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
);
|
|
38
42
|
|
|
39
|
-
it(
|
|
40
|
-
|
|
43
|
+
it.when(runtime.dialect.booleanType !== 'none')(
|
|
44
|
+
`boolean in pipeline - ${databaseName}`,
|
|
45
|
+
async () => {
|
|
46
|
+
await expect(`
|
|
41
47
|
run: models->{
|
|
42
48
|
group_by:
|
|
43
49
|
manufacturer,
|
|
@@ -48,10 +54,11 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
|
|
|
48
54
|
aggregate: model_count is model_count.sum()
|
|
49
55
|
}
|
|
50
56
|
`).toMatchResult(orderByModel, {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
big: false,
|
|
58
|
+
model_count: 58500,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
);
|
|
55
62
|
|
|
56
63
|
it(`filtered measures in model are aggregates #352 - ${databaseName}`, async () => {
|
|
57
64
|
await expect(`
|
|
@@ -15,8 +15,11 @@ describe.each(runtimes.runtimeList)('TestSelect for %s', (db, runtime) => {
|
|
|
15
15
|
const ts = new TestSelect(runtime.dialect);
|
|
16
16
|
const testModel = wrapTestModel(runtime, '');
|
|
17
17
|
|
|
18
|
+
// A dialect without boolean values has no boolean column
|
|
19
|
+
const booleans = runtime.dialect.booleanType !== 'none';
|
|
20
|
+
|
|
18
21
|
// Basic Type Tests
|
|
19
|
-
test(`${db} inferred basic types`, async () => {
|
|
22
|
+
test.when(booleans)(`${db} inferred basic types`, async () => {
|
|
20
23
|
const sql = ts.generate(
|
|
21
24
|
{t_int: 1, t_string: 'a', t_bool: true, t_float: 1.5},
|
|
22
25
|
{t_int: 2, t_string: 'b', t_bool: false, t_float: 2.5}
|
|
@@ -27,7 +30,7 @@ describe.each(runtimes.runtimeList)('TestSelect for %s', (db, runtime) => {
|
|
|
27
30
|
]);
|
|
28
31
|
});
|
|
29
32
|
|
|
30
|
-
test(`${db} explicit type hints`, async () => {
|
|
33
|
+
test.when(booleans)(`${db} explicit type hints`, async () => {
|
|
31
34
|
const sql = ts.generate({
|
|
32
35
|
t_int: ts.mk_int(1),
|
|
33
36
|
t_float: ts.mk_float(1.5),
|