@malloydata/malloy-tests 0.0.433 → 0.0.434

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.
@@ -337,11 +337,8 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
337
337
  });
338
338
  });
339
339
 
340
- // TODO not sure why this test needs to be skipped on postgres, feels like an oversight
341
- test.when(!['postgres', 'mysql'].includes(databaseName))(
342
- 'model: dates named',
343
- async () => {
344
- await expect(`
340
+ test('model: dates named', async () => {
341
+ await expect(`
345
342
  run: ${databaseName}.table('malloytest.alltypes')->{
346
343
  group_by:
347
344
  t_date,
@@ -356,19 +353,18 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
356
353
  t_timestamp_year is t_timestamp.year,
357
354
  }
358
355
  `).toMatchResult(testModel, {
359
- t_date: new Date('2020-03-02'),
360
- t_date_month: new Date('2020-03-01'),
361
- t_date_year: new Date('2020-01-01'),
362
- t_timestamp: new Date('2020-03-02T12:35:56.000Z'),
363
- t_timestamp_second: new Date('2020-03-02T12:35:56.000Z'),
364
- t_timestamp_minute: new Date('2020-03-02T12:35:00.000Z'),
365
- t_timestamp_hour: new Date('2020-03-02T12:00:00.000Z'),
366
- t_timestamp_date: new Date('2020-03-02'),
367
- t_timestamp_month: new Date('2020-03-01'),
368
- t_timestamp_year: new Date('2020-01-01'),
369
- });
370
- }
371
- );
356
+ t_date: new Date('2020-03-02'),
357
+ t_date_month: new Date('2020-03-01'),
358
+ t_date_year: new Date('2020-01-01'),
359
+ t_timestamp: new Date('2020-03-02T12:35:56.000Z'),
360
+ t_timestamp_second: new Date('2020-03-02T12:35:56.000Z'),
361
+ t_timestamp_minute: new Date('2020-03-02T12:35:00.000Z'),
362
+ t_timestamp_hour: new Date('2020-03-02T12:00:00.000Z'),
363
+ t_timestamp_date: new Date('2020-03-02'),
364
+ t_timestamp_month: new Date('2020-03-01'),
365
+ t_timestamp_year: new Date('2020-01-01'),
366
+ });
367
+ });
372
368
 
373
369
  it('named query metadata undefined', async () => {
374
370
  const result = await expressionModel
@@ -736,6 +732,142 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
736
732
  }
737
733
  );
738
734
 
735
+ // A filtered join is emitted as a subquery, so every join below it lives
736
+ // inside that subquery and has to be projected back out for the outer query
737
+ // to name it -- at every depth, not just the direct children.
738
+ describe('joins below a filtered join', () => {
739
+ const chain = (filterTwo: string) => `
740
+ source: sf is ${databaseName}.table('malloytest.state_facts')
741
+
742
+ source: three is sf
743
+ source: two is sf extend {
744
+ ${filterTwo}
745
+ join_one: three on three.state = state
746
+ }
747
+ source: one is sf extend {
748
+ where: state ~ 'A%'
749
+ join_one: two on two.state = state
750
+ }
751
+ source: zero is sf extend { join_one: one on one.state = state }
752
+ `;
753
+
754
+ test.when(runtime.dialect.supportsComplexFilteredSources)(
755
+ 'two levels below',
756
+ async () => {
757
+ await expect(`
758
+ ${chain('')}
759
+ run: zero -> {
760
+ aggregate:
761
+ child is one.two.count()
762
+ grandchild is one.two.three.count()
763
+ }
764
+ `).toMatchResult(testModel, {child: 4, grandchild: 4});
765
+
766
+ await expect(`
767
+ ${chain('')}
768
+ run: zero -> {
769
+ group_by: grandchild_state is one.two.three.state
770
+ order_by: grandchild_state
771
+ limit: 1
772
+ }
773
+ `).toMatchResult(testModel, {grandchild_state: 'AK'});
774
+ }
775
+ );
776
+
777
+ // `having:` and `calculate:` name output fields, so their field usage is
778
+ // rooted in the query's output and does not resolve against its source.
779
+ test.when(runtime.dialect.supportsComplexFilteredSources)(
780
+ 'having on an output field, with a filtered join',
781
+ async () => {
782
+ await expect(`
783
+ ${chain('')}
784
+ run: zero -> {
785
+ group_by: grandchild_state is one.two.three.state
786
+ aggregate: rows_here is count()
787
+ having: rows_here > 0
788
+ order_by: grandchild_state
789
+ limit: 1
790
+ }
791
+ `).toMatchResult(testModel, {grandchild_state: 'AK', rows_here: 1});
792
+ }
793
+ );
794
+
795
+ // The packed columns are the source's own column names, so the struct has
796
+ // to quote them on the value side and in any type it declares.
797
+ test.when(runtime.dialect.supportsComplexFilteredSources)(
798
+ 'a packed column name which needs quoting',
799
+ async () => {
800
+ const oddName = runtime.dialect.sqlQuoteIdentifier('order');
801
+ await expect(`
802
+ source: two is ${databaseName}.sql("""SELECT 1 as ${oddName}, 1 as k""")
803
+ source: one is ${databaseName}.sql("""SELECT 1 as k, 2 as j""") extend {
804
+ where: j > 0
805
+ join_one: two on two.k = k
806
+ }
807
+ source: zero is ${databaseName}.sql("""SELECT 1 as k""") extend {
808
+ join_one: one on one.k = k
809
+ }
810
+
811
+ run: zero -> { group_by: g is one.two.\`order\` }
812
+ `).toMatchResult(testModel, {g: 1});
813
+ }
814
+ );
815
+
816
+ // A distinct key is the one thing the packed struct must carry which no
817
+ // expression in the query names, and when the key is computed what the
818
+ // query reads is the columns its expression reads, not the key's name.
819
+ test.when(runtime.dialect.supportsComplexFilteredSources)(
820
+ 'two levels below, reached by a computed primary key',
821
+ async () => {
822
+ await expect(`
823
+ source: sf is ${databaseName}.table('malloytest.state_facts')
824
+
825
+ source: three is sf extend {
826
+ dimension: pk is births
827
+ primary_key: pk
828
+ }
829
+ source: two is sf extend { join_one: three on three.state = state }
830
+ source: one is sf extend {
831
+ where: state ~ 'A%'
832
+ join_one: two on two.state = state
833
+ }
834
+ source: zero is sf extend { join_one: one on one.state = state }
835
+
836
+ run: zero -> {
837
+ aggregate:
838
+ child is one.two.count()
839
+ grandchild is one.two.three.count()
840
+ }
841
+ `).toMatchResult(testModel, {child: 4, grandchild: 4});
842
+ }
843
+ );
844
+
845
+ // `two` is filtered as well, so it becomes a subquery of its own inside
846
+ // the one for `one`, and `three` reaches the outer query through it.
847
+ test.when(runtime.dialect.supportsComplexFilteredSources)(
848
+ 'two levels below, both filtered',
849
+ async () => {
850
+ await expect(`
851
+ ${chain("where: state != 'AK'")}
852
+ run: zero -> {
853
+ aggregate:
854
+ child is one.two.count()
855
+ grandchild is one.two.three.count()
856
+ }
857
+ `).toMatchResult(testModel, {child: 3, grandchild: 3});
858
+
859
+ await expect(`
860
+ ${chain("where: state != 'AK'")}
861
+ run: zero -> {
862
+ group_by: grandchild_state is one.two.three.state
863
+ order_by: grandchild_state
864
+ limit: 1
865
+ }
866
+ `).toMatchResult(testModel, {grandchild_state: 'AL'});
867
+ }
868
+ );
869
+ });
870
+
739
871
  test('joined filtered explores with NO dependencies', async () => {
740
872
  await expect(`
741
873
  source: sf is ${databaseName}.table('malloytest.state_facts') extend {
@@ -0,0 +1,237 @@
1
+ /*
2
+ * Copyright Contributors to the Malloy project
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import {RuntimeList, allDatabases} from '../../runtimes';
7
+ import {databasesFromEnvironmentOr} from '../../util';
8
+ import '@malloydata/malloy/test/matchers';
9
+ import {wrapTestModel} from '@malloydata/malloy/test';
10
+
11
+ const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
12
+
13
+ afterAll(async () => {
14
+ await runtimes.closeAll();
15
+ });
16
+
17
+ // A nest is a column whose value is the result of a query. What that column
18
+ // contains, and in what order, is the subject here.
19
+ //
20
+ // The order is the order of the stage which produced it -- the order the user
21
+ // asked for, or the one Malloy picked. Reading the array back is the only way to
22
+ // see that, and the ordering is attached to the SQL which packs the array, which
23
+ // is a different expression for a one-stage nest than for a pipelined one.
24
+ //
25
+ // The ordering tests nest over the four 'N' states whose popular_name is
26
+ // Isabella, because their four candidate orderings are all distinct:
27
+ // state desc NY NV NM NJ
28
+ // state asc NJ NM NV NY
29
+ // births desc NY NJ NM NV
30
+ // births asc NV NM NJ NY
31
+ // A pipelined test orders its first stage the opposite way from its last, so an
32
+ // array carrying the wrong stage's order fails instead of coincidentally passing.
33
+
34
+ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
35
+ const model = wrapTestModel(
36
+ runtime,
37
+ `
38
+ query: isabellaN is ${databaseName}.table('malloytest.state_facts') -> {
39
+ where: popular_name = 'Isabella' and state ~ 'N%'
40
+ group_by: popular_name
41
+ }`
42
+ );
43
+
44
+ const stateDesc = [{st: 'NY'}, {st: 'NV'}, {st: 'NM'}, {st: 'NJ'}];
45
+ const birthsDesc = [{st: 'NY'}, {st: 'NJ'}, {st: 'NM'}, {st: 'NV'}];
46
+ const keywordStateDesc = [
47
+ {'select': 'NY'},
48
+ {'select': 'NV'},
49
+ {'select': 'NM'},
50
+ {'select': 'NJ'},
51
+ ];
52
+
53
+ test.when(runtime.supportsNesting)(
54
+ `one-stage nest is in the order it asked for - ${databaseName}`,
55
+ async () => {
56
+ await expect(`
57
+ run: isabellaN + {
58
+ nest: n is {
59
+ group_by: st is state
60
+ aggregate: b is births.sum()
61
+ order_by: st desc
62
+ }
63
+ }`).toMatchResult(model, {n: stateDesc});
64
+ }
65
+ );
66
+
67
+ test.when(runtime.supportsNesting)(
68
+ `one-stage nest orders by a name which needs quoting - ${databaseName}`,
69
+ async () => {
70
+ await expect(`
71
+ run: isabellaN + {
72
+ nest: n is {
73
+ group_by: \`select\` is state
74
+ aggregate: b is births.sum()
75
+ order_by: \`select\` desc
76
+ }
77
+ }`).toMatchResult(model, {n: keywordStateDesc});
78
+ }
79
+ );
80
+
81
+ test.when(runtime.supportsNesting)(
82
+ `one-stage nest with no order_by is largest first - ${databaseName}`,
83
+ async () => {
84
+ await expect(`
85
+ run: isabellaN + {
86
+ nest: n is {
87
+ group_by: st is state
88
+ aggregate: b is births.sum()
89
+ }
90
+ }`).toMatchResult(model, {n: birthsDesc});
91
+ }
92
+ );
93
+
94
+ test.when(runtime.dialect.supportsPipelinesInViews)(
95
+ `pipelined nest is in the order its last stage asked for - ${databaseName}`,
96
+ async () => {
97
+ await expect(`
98
+ run: isabellaN + {
99
+ nest: n is {
100
+ group_by: st is state
101
+ aggregate: b is births.sum()
102
+ order_by: st asc
103
+ } -> {
104
+ group_by: st, b
105
+ order_by: st desc
106
+ }
107
+ }`).toMatchResult(model, {n: stateDesc});
108
+ }
109
+ );
110
+
111
+ test.when(runtime.dialect.supportsPipelinesInViews)(
112
+ `pipelined nest orders by a name which needs quoting - ${databaseName}`,
113
+ async () => {
114
+ await expect(`
115
+ run: isabellaN + {
116
+ nest: n is {
117
+ group_by: \`select\` is state
118
+ aggregate: b is births.sum()
119
+ order_by: \`select\` asc
120
+ } -> {
121
+ group_by: \`select\`, b
122
+ order_by: \`select\` desc
123
+ }
124
+ }`).toMatchResult(model, {n: keywordStateDesc});
125
+ }
126
+ );
127
+
128
+ // An ordinal order_by names an output field by number, counting from one.
129
+ test.when(runtime.supportsNesting)(
130
+ `one-stage nest orders by an output field number - ${databaseName}`,
131
+ async () => {
132
+ await expect(`
133
+ run: isabellaN + {
134
+ nest: n is {
135
+ group_by: st is state
136
+ aggregate: b is births.sum()
137
+ order_by: 1 desc
138
+ }
139
+ }`).toMatchResult(model, {n: stateDesc});
140
+ }
141
+ );
142
+
143
+ test.when(runtime.dialect.supportsPipelinesInViews)(
144
+ `pipelined nest orders by an output field number - ${databaseName}`,
145
+ async () => {
146
+ await expect(`
147
+ run: isabellaN + {
148
+ nest: n is {
149
+ group_by: st is state
150
+ aggregate: b is births.sum()
151
+ order_by: st asc
152
+ } -> {
153
+ group_by: st, b
154
+ order_by: 1 desc
155
+ }
156
+ }`).toMatchResult(model, {n: stateDesc});
157
+ }
158
+ );
159
+
160
+ test.when(runtime.dialect.supportsPipelinesInViews)(
161
+ `pipelined nest ending in select orders by an output field number - ${databaseName}`,
162
+ async () => {
163
+ await expect(`
164
+ run: isabellaN + {
165
+ nest: n is {
166
+ group_by: st is state
167
+ aggregate: b is births.sum()
168
+ order_by: st asc
169
+ } -> {
170
+ select: st, b
171
+ order_by: 2 desc
172
+ }
173
+ }`).toMatchResult(model, {n: birthsDesc});
174
+ }
175
+ );
176
+
177
+ test.when(runtime.dialect.supportsPipelinesInViews)(
178
+ `pipelined nest with no order_by is largest first - ${databaseName}`,
179
+ async () => {
180
+ await expect(`
181
+ run: isabellaN + {
182
+ nest: n is {
183
+ group_by: st is state
184
+ aggregate: b is births.sum()
185
+ } -> {
186
+ group_by: st
187
+ aggregate: tot is b.sum()
188
+ }
189
+ }`).toMatchResult(model, {n: birthsDesc});
190
+ }
191
+ );
192
+
193
+ test.when(runtime.supportsNesting)(
194
+ `an aggregate which is null in a nest reads back as null - ${databaseName}`,
195
+ async () => {
196
+ await expect(`
197
+ run: ${databaseName}.table('malloytest.state_facts') -> {
198
+ group_by: state
199
+ nest: ugly is {
200
+ group_by: popular_name
201
+ aggregate: foo is NULLIF(sum(airport_count)*0,0)+1
202
+ }
203
+ }`).toMatchPaths(model, {'ugly.foo': null});
204
+ }
205
+ );
206
+
207
+ // A null value in the grouping above a nest must not null out the nest.
208
+ test.when(runtime.supportsNesting)(
209
+ `a nest under a null group is still a nest - ${databaseName}`,
210
+ async () => {
211
+ const result = await runtime
212
+ .loadQuery(
213
+ `
214
+ run: ${databaseName}.table('malloytest.airports') -> {
215
+ where: faa_region is null
216
+ group_by: faa_region
217
+ aggregate: airport_count is count()
218
+ nest: by_state is {
219
+ where: state is not null
220
+ group_by: state
221
+ aggregate: airport_count is count()
222
+ }
223
+ nest: capped is {
224
+ where: state is not null
225
+ group_by: state
226
+ aggregate: airport_count is count()
227
+ limit: 1
228
+ }
229
+ }`
230
+ )
231
+ .run();
232
+ const row = result.data.toObject()[0] as Record<string, unknown>;
233
+ expect(row['by_state']).not.toBe(null);
234
+ expect(row['capped']).not.toBe(null);
235
+ }
236
+ );
237
+ });
@@ -652,26 +652,6 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
652
652
  });
653
653
  });
654
654
 
655
- test.when(runtime.supportsNesting)(
656
- `number as null 2 - ${databaseName}`,
657
- async () => {
658
- // a cross join produces a Many to Many result.
659
- // symmetric aggregate are needed on both sides of the join
660
- // Check the row count and that sums on each side work properly.
661
- const testModel = wrapTestModel(runtime, '');
662
- await expect(`
663
- # test.verbose
664
- run: ${databaseName}.table('malloytest.state_facts') -> {
665
- group_by: state
666
- nest: ugly is {
667
- group_by: popular_name
668
- aggregate: foo is NULLIF(sum(airport_count)*0,0)+1
669
- }
670
- }
671
- `).toMatchPaths(testModel, {'ugly.foo': null});
672
- }
673
- );
674
-
675
655
  it(`sql block- ${databaseName}`, async () => {
676
656
  const testModel = wrapTestModel(runtime, '');
677
657
  await expect(`
@@ -1279,39 +1259,6 @@ SELECT row_to_json(finalStage) as row FROM __stage0 AS finalStage`);
1279
1259
  `).toMatchResult(testModel, {p: 23001594});
1280
1260
  });
1281
1261
 
1282
- test.when(runtime.supportsNesting)(
1283
- `nest null - ${databaseName}`,
1284
- async () => {
1285
- const result = await runtime
1286
- .loadQuery(
1287
- `
1288
- run: ${databaseName}.table('malloytest.airports') -> {
1289
- where: faa_region is null
1290
- group_by: faa_region
1291
- aggregate: airport_count is count()
1292
- nest: by_state is {
1293
- where: state is not null
1294
- group_by: state
1295
- aggregate: airport_count is count()
1296
- }
1297
- nest: by_state1 is {
1298
- where: state is not null
1299
- group_by: state
1300
- aggregate: airport_count is count()
1301
- limit: 1
1302
- }
1303
- }
1304
- `
1305
- )
1306
- .run();
1307
-
1308
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1309
- const d: any = result.data.toObject();
1310
- expect(d[0]['by_state']).not.toBe(null);
1311
- expect(d[0]['by_state1']).not.toBe(null);
1312
- }
1313
- );
1314
-
1315
1262
  test.when(
1316
1263
  runtime.supportsNesting && runtime.dialect.supportsPipelinesInViews
1317
1264
  )(`Nested pipelines sort properly - ${databaseName}`, async () => {
@@ -1391,28 +1338,6 @@ SELECT row_to_json(finalStage) as row FROM __stage0 AS finalStage`);
1391
1338
  }
1392
1339
  });
1393
1340
 
1394
- test.when(runtime.supportsNesting)(
1395
- `number as null- ${databaseName}`,
1396
- async () => {
1397
- const result = await runtime
1398
- .loadQuery(
1399
- `
1400
- source: s is ${databaseName}.table('malloytest.state_facts') extend {
1401
- }
1402
- run: s-> {
1403
- group_by: state
1404
- nest: ugly is {
1405
- group_by: popular_name
1406
- aggregate: foo is NULLIF(sum(airport_count)*0,0)+1
1407
- }
1408
- }
1409
- `
1410
- )
1411
- .run();
1412
- expect(result.data.path(0, 'ugly', 0, 'foo').value).toBe(null);
1413
- }
1414
- );
1415
-
1416
1341
  it(`removes surpuflous order_by - solo aggregates - ${databaseName}`, async () => {
1417
1342
  const result = await runtime
1418
1343
  .loadQuery(
@@ -36,11 +36,43 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
36
36
  aggregate: \`#\` is count(one)
37
37
  }
38
38
  }
39
+ // A nest of nothing but aggregates is a single record rather than a
40
+ // list, and is built by a different dialect method.
41
+ nest: measures_only is {
42
+ aggregate: \`#\` is count(one)
43
+ aggregate: \`order\` is one.sum()
44
+ }
39
45
  }`).toMatchResult(testModel, {
40
46
  foo: [{'one': 1, '#': 1, 'deepfoo': [{'one': 1, '#': 1}]}],
47
+ measures_only: {'#': 1, 'order': 1},
41
48
  });
42
49
  });
43
50
 
51
+ // A nest's output field names are the user's, so every place a stage
52
+ // declares or reads them has to quote them -- including the row type a
53
+ // multi-stage nest builds to carry its first stage into its second.
54
+ test.when(
55
+ runtime.supportsNesting && runtime.dialect.supportsPipelinesInViews
56
+ )(
57
+ `properly quotes nested field names through a second stage in ${databaseName}`,
58
+ async () => {
59
+ await expect(`
60
+ run: ${databaseName}.table('malloytest.state_facts') -> {
61
+ where: state = 'CA'
62
+ group_by: popular_name
63
+ nest: counted is {
64
+ group_by: \`order\` is state
65
+ aggregate: n is count()
66
+ } -> {
67
+ group_by: \`order\`
68
+ aggregate: t is n.sum()
69
+ }
70
+ }`).toMatchResult(testModel, {
71
+ counted: [{'order': 'CA', 't': 1}],
72
+ });
73
+ }
74
+ );
75
+
44
76
  describe('warnings', () => {
45
77
  // NOTE: This test generates SQL errors on the console because of
46
78
  // a hard-coded console.log() in the duckdb-wasm worker
package/src/runtimes.ts CHANGED
@@ -207,6 +207,7 @@ export function runtimeFor(dbName: string): SingleConnectionRuntime {
207
207
  name: dbName,
208
208
  additionalExtensions: ['mssql'],
209
209
  setupSQL: [
210
+ // Must match test/mssql/connection_string.ts
210
211
  "ATTACH 'Server=localhost;Port=1433;Database=malloytest;User Id=sa;Password=Malloy_Test_123;TrustServerCertificate=true' AS msdb (TYPE mssql)",
211
212
  'USE msdb.malloytest',
212
213
  ].join(';\n'),
@@ -0,0 +1,11 @@
1
+ # The hive catalog serves test/data/malloytest-parquet, mounted read-only
2
+ # under /data/malloytest, with the tables hive_ddl.ts declares. The file
3
+ # metastore lives inside the container's own data directory, which the trino
4
+ # user can write; nothing but the parquet crosses the container boundary.
5
+ connector.name=hive
6
+ hive.metastore=file
7
+ hive.metastore.catalog.dir=local:///trino/metastore
8
+ fs.native-local.enabled=true
9
+ local.location=/data
10
+ # The parquet timestamps are microseconds; the DDL declares TIMESTAMP(6)
11
+ hive.timestamp-precision=MICROSECONDS