@malloydata/malloy-tests 0.0.149-dev240706223116 → 0.0.150-dev240714131913

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/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.149-dev240706223116",
25
- "@malloydata/db-duckdb": "^0.0.149-dev240706223116",
26
- "@malloydata/db-postgres": "^0.0.149-dev240706223116",
27
- "@malloydata/db-snowflake": "^0.0.149-dev240706223116",
28
- "@malloydata/db-trino": "^0.0.149-dev240706223116",
29
- "@malloydata/malloy": "^0.0.149-dev240706223116",
30
- "@malloydata/render": "^0.0.149-dev240706223116",
24
+ "@malloydata/db-bigquery": "^0.0.150-dev240714131913",
25
+ "@malloydata/db-duckdb": "^0.0.150-dev240714131913",
26
+ "@malloydata/db-postgres": "^0.0.150-dev240714131913",
27
+ "@malloydata/db-snowflake": "^0.0.150-dev240714131913",
28
+ "@malloydata/db-trino": "^0.0.150-dev240714131913",
29
+ "@malloydata/malloy": "^0.0.150-dev240714131913",
30
+ "@malloydata/render": "^0.0.150-dev240714131913",
31
31
  "jsdom": "^22.1.0",
32
32
  "luxon": "^2.4.0",
33
33
  "madge": "^6.0.0"
@@ -36,5 +36,5 @@
36
36
  "@types/jsdom": "^21.1.1",
37
37
  "@types/luxon": "^2.4.0"
38
38
  },
39
- "version": "0.0.149-dev240706223116"
39
+ "version": "0.0.150-dev240714131913"
40
40
  }
@@ -97,19 +97,20 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
97
97
  });
98
98
 
99
99
  // bigquery doesn't support row count based sampling.
100
- test.when(databaseName !== 'bigquery' && databaseName !== 'trino')(
101
- `index rows count - ${databaseName}`,
102
- async () => {
103
- await expect(`
100
+ test.when(
101
+ databaseName !== 'bigquery' &&
102
+ databaseName !== 'trino' &&
103
+ databaseName !== 'presto'
104
+ )(`index rows count - ${databaseName}`, async () => {
105
+ await expect(`
104
106
  run: ${databaseName}.table('malloytest.state_facts') extend {
105
107
  dimension: one is 'one'
106
108
  } -> {index:one, state; sample: 10 }
107
109
  -> {select: fieldName, weight, fieldValue; order_by: 2 desc; where: fieldName = 'one'}
108
110
  `).malloyResultMatches(runtime, {fieldName: 'one', weight: 10});
109
- }
110
- );
111
+ });
111
112
 
112
- it.when(databaseName !== 'trino')(
113
+ it.when(databaseName !== 'trino' && databaseName !== 'presto')(
113
114
  `index rows count - ${databaseName}`,
114
115
  async () => {
115
116
  await expect(`
@@ -112,32 +112,32 @@ expressionModels.forEach((expressionModel, databaseName) => {
112
112
  };
113
113
 
114
114
  describe('concat', () => {
115
- it.when(!brokenIn('trino', databaseName) /* crswenson */)(
116
- `works - ${databaseName}`,
117
- async () => {
118
- const expected = {
119
- 'bigquery': 'foo2003-01-01 12:00:00+00',
120
- 'snowflake': 'foo2003-01-01T12:00:00.000Z',
121
- };
122
-
123
- await funcTestMultiple(
124
- ["concat('foo', 'bar')", 'foobar'],
125
- ["concat(1, 'bar')", '1bar'],
126
- [
127
- "concat('cons', true)",
128
- databaseName === 'postgres' ? 'const' : 'construe',
129
- ],
130
- ["concat('foo', @2003)", 'foo2003-01-01'],
131
- [
132
- "concat('foo', @2003-01-01 12:00:00)",
133
- expected[databaseName] ?? 'foo2003-01-01 12:00:00',
134
- ],
135
- // TODO Maybe implement consistent null behavior
136
- // ["concat('foo', null)", null],
137
- ['concat()', '']
138
- );
139
- }
140
- );
115
+ it.when(
116
+ !brokenIn('trino', databaseName) &&
117
+ !brokenIn('presto', databaseName) /* crswenson */
118
+ )(`works - ${databaseName}`, async () => {
119
+ const expected = {
120
+ 'bigquery': 'foo2003-01-01 12:00:00+00',
121
+ 'snowflake': 'foo2003-01-01T12:00:00.000Z',
122
+ };
123
+
124
+ await funcTestMultiple(
125
+ ["concat('foo', 'bar')", 'foobar'],
126
+ ["concat(1, 'bar')", '1bar'],
127
+ [
128
+ "concat('cons', true)",
129
+ databaseName === 'postgres' ? 'const' : 'construe',
130
+ ],
131
+ ["concat('foo', @2003)", 'foo2003-01-01'],
132
+ [
133
+ "concat('foo', @2003-01-01 12:00:00)",
134
+ expected[databaseName] ?? 'foo2003-01-01 12:00:00',
135
+ ],
136
+ // TODO Maybe implement consistent null behavior
137
+ // ["concat('foo', null)", null],
138
+ ['concat()', '']
139
+ );
140
+ });
141
141
  });
142
142
 
143
143
  describe('round', () => {
@@ -219,13 +219,15 @@ expressionModels.forEach((expressionModel, databaseName) => {
219
219
  "replace('axbxc', r'(a).(b).(c)', '\\\\0 - \\\\1 - \\\\2 - \\\\3')",
220
220
  databaseName === 'postgres'
221
221
  ? '\\0 - a - b - c'
222
- : databaseName === 'trino'
222
+ : databaseName === 'trino' || databaseName === 'presto'
223
223
  ? '0 - 1 - 2 - 3'
224
224
  : 'axbxc - a - b - c',
225
225
  ],
226
226
  [
227
227
  "replace('aaaa', '', 'c')",
228
- databaseName === 'trino' ? 'cacacacac' : 'aaaa',
228
+ databaseName === 'trino' || databaseName === 'presto'
229
+ ? 'cacacacac'
230
+ : 'aaaa',
229
231
  ],
230
232
  ["replace(null, 'a', 'c')", null],
231
233
  ["replace('aaaa', null, 'c')", null],
@@ -262,7 +264,8 @@ expressionModels.forEach((expressionModel, databaseName) => {
262
264
 
263
265
  describe('stddev', () => {
264
266
  // TODO symmetric aggregates don't work with custom aggregate functions in BQ currently
265
- if (['bigquery', 'snowflake', 'trino'].includes(databaseName)) return;
267
+ if (['bigquery', 'snowflake', 'trino', 'presto'].includes(databaseName))
268
+ return;
266
269
  it(`works - ${databaseName}`, async () => {
267
270
  await funcTestAgg('round(stddev(aircraft_models.seats))', 29);
268
271
  });
@@ -786,7 +789,7 @@ expressionModels.forEach((expressionModel, databaseName) => {
786
789
  });
787
790
  });
788
791
  describe('is_inf', () => {
789
- const inf = ['trino'].includes(databaseName)
792
+ const inf = ['trino', 'presto'].includes(databaseName)
790
793
  ? 'infinity!()'
791
794
  : "'+inf'::number";
792
795
  it(`works - ${databaseName}`, async () => {
@@ -1327,7 +1330,8 @@ describe.each(runtimes.runtimeList)('%s', (databaseName, runtime) => {
1327
1330
 
1328
1331
  it(`works with fanout and order_by - ${databaseName}`, async () => {
1329
1332
  // TODO bigquery cannot handle both fanout and order_by today
1330
- if (['bigquery', 'snowflake', 'trino'].includes(databaseName)) return;
1333
+ if (['bigquery', 'snowflake', 'trino', 'presto'].includes(databaseName))
1334
+ return;
1331
1335
  await expect(`##! experimental.aggregate_order_by
1332
1336
  run: state_facts extend { join_many:
1333
1337
  state_facts2 is ${databaseName}.table('malloytest.state_facts')
@@ -194,10 +194,10 @@ describe('join expression tests', () => {
194
194
  `).malloyResultMatches(joinModel, {f_sum2: 60462});
195
195
  });
196
196
 
197
- test.when(runtime.supportsNesting)(
198
- `model: unnest is left join - ${database}`,
199
- async () => {
200
- await expect(`
197
+ test.when(
198
+ runtime.supportsNesting && runtime.dialect.supportsLeftJoinUnnest
199
+ )(`model: unnest is left join - ${database}`, async () => {
200
+ await expect(`
201
201
  // produce a table with 4 rows that has a nested element
202
202
  query: a_states is ${database}.table('malloytest.state_facts')-> {
203
203
  where: state ? ~ 'A%'
@@ -220,8 +220,7 @@ describe('join expression tests', () => {
220
220
  limit: 5
221
221
  }
222
222
  `).malloyResultMatches(joinModel, [{}, {}, {}, {}, {}]);
223
- }
224
- );
223
+ });
225
224
 
226
225
  // not sure how to solve this one yet, just check for > 4 rows
227
226
  it(`All joins at the same level - ${database}`, async () => {
@@ -52,6 +52,8 @@ function getSplitFunction(db: string) {
52
52
  `split(${column}, '${splitChar}')`,
53
53
  'trino': (column: string, splitChar: string) =>
54
54
  `split(${column}, '${splitChar}')`,
55
+ 'presto': (column: string, splitChar: string) =>
56
+ `split(${column}, '${splitChar}')`,
55
57
  }[db];
56
58
  }
57
59
 
@@ -480,11 +482,13 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
480
482
  expect(q.sql.toLowerCase()).not.toContain('distinct');
481
483
  });
482
484
 
483
- it(`leafy nested count - ${databaseName}`, async () => {
484
- // in a joined table when the joined is leafiest
485
- // we need to make sure we don't count rows that
486
- // don't match the join.
487
- await expect(`
485
+ it.when(runtime.dialect.supportsLeftJoinUnnest)(
486
+ `leafy nested count - ${databaseName}`,
487
+ async () => {
488
+ // in a joined table when the joined is leafiest
489
+ // we need to make sure we don't count rows that
490
+ // don't match the join.
491
+ await expect(`
488
492
  source: am_states is ${databaseName}.table('malloytest.state_facts') -> {
489
493
  group_by: state,popular_name
490
494
  where: state ~ r'^(A|M)'
@@ -496,7 +500,6 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
496
500
  source: states is ${databaseName}.table('malloytest.state_facts') extend {
497
501
  join_many: am_states on state=am_states.state
498
502
  }
499
-
500
503
  run: states -> {
501
504
  where: state = 'CA'
502
505
  group_by:
@@ -507,12 +510,13 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
507
510
  root_count is count()
508
511
  }
509
512
  `).malloyResultMatches(runtime, {
510
- leafy_count: 0,
511
- root_count: 1,
512
- state: 'CA',
513
- am_state: null,
514
- });
515
- });
513
+ leafy_count: 0,
514
+ root_count: 1,
515
+ state: 'CA',
516
+ am_state: null,
517
+ });
518
+ }
519
+ );
516
520
 
517
521
  it(`basic index - ${databaseName}`, async () => {
518
522
  // Make sure basic indexing works.
@@ -548,6 +552,16 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
548
552
  }
549
553
  );
550
554
 
555
+ it(`sql block- ${databaseName}`, async () => {
556
+ await expect(`
557
+ source: one is ${databaseName}.sql("""
558
+ SELECT 2 as ${q`a`}
559
+ """)
560
+ run: one -> {
561
+ select: a
562
+ }`).malloyResultMatches(runtime, {a: 2});
563
+ });
564
+
551
565
  // average should only include non-null values in the denominator
552
566
  it(`avg ignore null- ${databaseName}`, async () => {
553
567
  await expect(`
@@ -628,6 +642,7 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
628
642
  'ungrouped nested with no grouping above - ${databaseName}',
629
643
  async () => {
630
644
  await expect(`
645
+ // # test.debug
631
646
  run: ${databaseName}.table('malloytest.state_facts') extend {
632
647
  measure: total_births is births.sum()
633
648
  measure: births_per_100k is floor(total_births/ all(total_births) * 100000)
@@ -1041,11 +1056,23 @@ SELECT row_to_json(finalStage) as row FROM __stage0 AS finalStage`);
1041
1056
  );
1042
1057
 
1043
1058
  test.when(runtime.supportsNesting && runtime.dialect.readsNestedData)(
1044
- `can unnest from file - ${databaseName}`,
1059
+ `can unnest simply from file - ${databaseName}`,
1045
1060
  async () => {
1046
1061
  await expect(`
1047
1062
  source: ga_sample is ${databaseName}.table('malloytest.ga_sample')
1063
+ run: ga_sample -> {
1064
+ aggregate:
1065
+ h is hits.count()
1066
+ }
1067
+ `).malloyResultMatches(runtime, {h: 13233});
1068
+ }
1069
+ );
1048
1070
 
1071
+ test.when(runtime.supportsNesting && runtime.dialect.readsNestedData)(
1072
+ `can unnest from file - ${databaseName}`,
1073
+ async () => {
1074
+ await expect(`
1075
+ source: ga_sample is ${databaseName}.table('malloytest.ga_sample')
1049
1076
  run: ga_sample -> {
1050
1077
  where: hits.product.productBrand != null
1051
1078
  group_by:
@@ -462,18 +462,17 @@ describe.each(runtimes.runtimeList)('%s date and time', (dbName, runtime) => {
462
462
  });
463
463
  });
464
464
 
465
- test.when(!brokenIn('trino', dbName) /* mtoy */)(
466
- 'dependant join dialect fragments',
467
- async () => {
468
- await expect(`
465
+ test.when(
466
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
467
+ )('dependant join dialect fragments', async () => {
468
+ await expect(`
469
469
  source: timeData is ${dbName}.sql("""${timeSQL}""")
470
470
  run: timeData -> {
471
471
  extend: {join_one: joined is timeData on t_date = joined.t_date}
472
472
  group_by: t_month is joined.t_timestamp.month
473
473
  }
474
474
  `).malloyResultMatches(runtime, {t_month: new Date('2021-02-01')});
475
- }
476
- );
475
+ });
477
476
 
478
477
  describe('timezone set correctly', () => {
479
478
  test('timezone set in source used by query', async () => {
@@ -621,63 +620,60 @@ const utc_2020 = LuxonDateTime.fromObject(
621
620
  );
622
621
 
623
622
  describe.each(runtimes.runtimeList)('%s: tz literals', (dbName, runtime) => {
624
- test.when(!brokenIn('trino', dbName) /* mtoy */)(
625
- `${dbName} - default timezone is UTC`,
626
- async () => {
627
- // this makes sure that the tests which use the test timezome are actually
628
- // testing something ... file this under "abundance of caution". It
629
- // really tests nothing, but I feel calmer with this here.
630
- const query = runtime.loadQuery(
631
- `
623
+ test.when(
624
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
625
+ )(`${dbName} - default timezone is UTC`, async () => {
626
+ // this makes sure that the tests which use the test timezome are actually
627
+ // testing something ... file this under "abundance of caution". It
628
+ // really tests nothing, but I feel calmer with this here.
629
+ const query = runtime.loadQuery(
630
+ `
632
631
  run: ${dbName}.sql("SELECT 1 as one") -> {
633
632
  group_by: literal_time is @2020-02-20 00:00:00
634
633
  }
635
634
  `
636
- );
637
- const result = await query.run();
638
- const literal = result.data.path(0, 'literal_time').value as Date;
639
- const have = LuxonDateTime.fromJSDate(literal);
640
- expect(have.valueOf()).toEqual(utc_2020.valueOf());
641
- }
642
- );
643
-
644
- test.when(!brokenIn('trino', dbName) /* mtoy */)(
645
- 'literal with zone name',
646
- async () => {
647
- const query = runtime.loadQuery(
648
- `
635
+ );
636
+ const result = await query.run();
637
+ const literal = result.data.path(0, 'literal_time').value as Date;
638
+ const have = LuxonDateTime.fromJSDate(literal);
639
+ expect(have.valueOf()).toEqual(utc_2020.valueOf());
640
+ });
641
+
642
+ test.when(
643
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
644
+ )('literal with zone name', async () => {
645
+ const query = runtime.loadQuery(
646
+ `
649
647
  run: ${dbName}.sql("SELECT 1 as one") -> {
650
648
  group_by: literal_time is @2020-02-20 00:00:00[America/Mexico_City]
651
649
  }
652
650
  `
653
- );
654
- const result = await query.run();
655
- const literal = result.data.path(0, 'literal_time').value as Date;
656
- const have = LuxonDateTime.fromJSDate(literal);
657
- expect(have.valueOf()).toEqual(zone_2020.valueOf());
658
- }
659
- );
651
+ );
652
+ const result = await query.run();
653
+ const literal = result.data.path(0, 'literal_time').value as Date;
654
+ const have = LuxonDateTime.fromJSDate(literal);
655
+ expect(have.valueOf()).toEqual(zone_2020.valueOf());
656
+ });
660
657
  });
661
658
 
662
659
  describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
663
660
  const q = runtime.getQuoter();
664
- test.when(!brokenIn('trino', dbName) /* mtoy */)(
665
- 'literal timestamps',
666
- async () => {
667
- const query = runtime.loadQuery(
668
- `
661
+ test.when(
662
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
663
+ )('literal timestamps', async () => {
664
+ const query = runtime.loadQuery(
665
+ `
669
666
  run: ${dbName}.sql("SELECT 1 as one") -> {
670
667
  timezone: '${zone}'
671
668
  group_by: literal_time is @2020-02-20 00:00:00
672
669
  }
673
670
  `
674
- );
675
- const result = await query.run();
676
- const literal = result.data.path(0, 'literal_time').value as Date;
677
- const have = LuxonDateTime.fromJSDate(literal);
678
- expect(have.valueOf()).toEqual(zone_2020.valueOf());
679
- }
680
- );
671
+ );
672
+ const result = await query.run();
673
+ const literal = result.data.path(0, 'literal_time').value as Date;
674
+ const have = LuxonDateTime.fromJSDate(literal);
675
+ expect(have.valueOf()).toEqual(zone_2020.valueOf());
676
+ });
681
677
 
682
678
  test('extract', async () => {
683
679
  await expect(
@@ -691,7 +687,9 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
691
687
  ).malloyResultMatches(runtime, {mex_midnight: 18, mex_day: 19});
692
688
  });
693
689
 
694
- test.when(!brokenIn('trino', dbName) /* mtoy */)('truncate day', async () => {
690
+ test.when(
691
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
692
+ )('truncate day', async () => {
695
693
  // At midnight in london it the 19th in Mexico, so that truncates to
696
694
  // midnight on the 19th
697
695
  const mex_19 = LuxonDateTime.fromISO('2020-02-19T00:00:00', {zone});
@@ -704,32 +702,30 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
704
702
  ).malloyResultMatches(runtime, {mex_day: mex_19.toJSDate()});
705
703
  });
706
704
 
707
- test.when(!brokenIn('trino', dbName) /* mtoy */)(
708
- 'cast timestamp to date',
709
- async () => {
710
- // At midnight in london it is the 19th in Mexico, so when we cast that
711
- // to a date, it should be the 19th.
712
- await expect(
713
- `run: ${dbName}.sql("SELECT 1 as x") -> {
705
+ test.when(
706
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
707
+ )('cast timestamp to date', async () => {
708
+ // At midnight in london it is the 19th in Mexico, so when we cast that
709
+ // to a date, it should be the 19th.
710
+ await expect(
711
+ `run: ${dbName}.sql("SELECT 1 as x") -> {
714
712
  timezone: '${zone}'
715
713
  extend: { dimension: utc_midnight is @2020-02-20 00:00:00[UTC] }
716
714
  select: mex_day is day(utc_midnight::date)
717
715
  }`
718
- ).malloyResultMatches(runtime, {mex_day: 19});
719
- }
720
- );
721
-
722
- test.when(!brokenIn('trino', dbName) /* mtoy */)(
723
- 'cast date to timestamp',
724
- async () => {
725
- await expect(
726
- `run: ${dbName}.sql(""" SELECT DATE '2020-02-20' AS ${q`mex_20`} """) -> {
716
+ ).malloyResultMatches(runtime, {mex_day: 19});
717
+ });
718
+
719
+ test.when(
720
+ !brokenIn('trino', dbName) && !brokenIn('presto', dbName) /* mtoy */
721
+ )('cast date to timestamp', async () => {
722
+ await expect(
723
+ `run: ${dbName}.sql(""" SELECT DATE '2020-02-20' AS ${q`mex_20`} """) -> {
727
724
  timezone: '${zone}'
728
725
  select: mex_ts is mex_20::timestamp
729
726
  }`
730
- ).malloyResultMatches(runtime, {mex_ts: zone_2020.toJSDate()});
731
- }
732
- );
727
+ ).malloyResultMatches(runtime, {mex_ts: zone_2020.toJSDate()});
728
+ });
733
729
  });
734
730
 
735
731
  afterAll(async () => {
package/src/runtimes.ts CHANGED
@@ -37,6 +37,7 @@ import {SnowflakeConnection} from '@malloydata/db-snowflake';
37
37
  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
+ import {PrestoConnection} from '@malloydata/db-trino/src/trino_connection';
40
41
 
41
42
  export class SnowflakeTestConnection extends SnowflakeConnection {
42
43
  public async runSQL(
@@ -166,7 +167,14 @@ export function runtimeFor(dbName: string): SingleConnectionRuntime {
166
167
  connection = new TrinoConnection(
167
168
  dbName,
168
169
  {},
169
- TrinoExecutor.getConnectionOptionsFromEnv()
170
+ TrinoExecutor.getConnectionOptionsFromEnv(dbName)
171
+ );
172
+ break;
173
+ case 'presto':
174
+ connection = new PrestoConnection(
175
+ dbName,
176
+ {},
177
+ TrinoExecutor.getConnectionOptionsFromEnv(dbName) // they share configs.
170
178
  );
171
179
  break;
172
180
  default:
@@ -12,7 +12,7 @@ bigquery.arrow-serialization.enabled=false
12
12
  EOF
13
13
 
14
14
  # run docker
15
- docker run -p 8090:8080 -d -v ./.tmp/bigquery.properties:/etc/trino/catalog/bigquery.properties --name trino-malloy trinodb/trino
15
+ docker run -p 8080:8080 -d -v ./.tmp/bigquery.properties:/etc/trino/catalog/bigquery.properties --name trino-malloy trinodb/trino
16
16
 
17
17
  # wait for server to start
18
18
  counter=0
@@ -21,7 +21,7 @@ do
21
21
  sleep 1
22
22
  counter=$((counter+1))
23
23
  # if doesn't start after 2 minutes, output logs and kill process
24
- if [ $counter -eq 120 ]
24
+ if [ $counter -eq 300 ]
25
25
  then
26
26
  docker logs trino-malloy >& ./.tmp/trino-malloy.logs
27
27
  docker rm -f trino-malloy
@@ -31,4 +31,4 @@ do
31
31
  fi
32
32
  done
33
33
 
34
- echo "Trino running on port localhost:8090"
34
+ echo "Trino running on port localhost:8080"