@malloydata/malloy-tests 0.0.310 → 0.0.312

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,14 +21,14 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@jest/globals": "^29.4.3",
24
- "@malloydata/db-bigquery": "0.0.310",
25
- "@malloydata/db-duckdb": "0.0.310",
26
- "@malloydata/db-postgres": "0.0.310",
27
- "@malloydata/db-snowflake": "0.0.310",
28
- "@malloydata/db-trino": "0.0.310",
29
- "@malloydata/malloy": "0.0.310",
30
- "@malloydata/malloy-tag": "0.0.310",
31
- "@malloydata/render": "0.0.310",
24
+ "@malloydata/db-bigquery": "0.0.312",
25
+ "@malloydata/db-duckdb": "0.0.312",
26
+ "@malloydata/db-postgres": "0.0.312",
27
+ "@malloydata/db-snowflake": "0.0.312",
28
+ "@malloydata/db-trino": "0.0.312",
29
+ "@malloydata/malloy": "0.0.312",
30
+ "@malloydata/malloy-tag": "0.0.312",
31
+ "@malloydata/render": "0.0.312",
32
32
  "events": "^3.3.0",
33
33
  "jsdom": "^22.1.0",
34
34
  "luxon": "^2.4.0",
@@ -38,5 +38,5 @@
38
38
  "@types/jsdom": "^21.1.1",
39
39
  "@types/luxon": "^2.4.0"
40
40
  },
41
- "version": "0.0.310"
41
+ "version": "0.0.312"
42
42
  }
@@ -0,0 +1,41 @@
1
+ #! /bin/bash
2
+ #
3
+ # Setup postgres as a docker container
4
+ #
5
+ set -e
6
+
7
+ rm -rf .tmp
8
+ mkdir .tmp
9
+
10
+ # run docker
11
+ SCRIPTDIR=$(cd $(dirname $0); pwd)
12
+ DATADIR=$(dirname $SCRIPTDIR)/data/postgres
13
+
14
+ // set these in your enviornment
15
+ export PGHOST=localhost
16
+ export PGPORT=5432
17
+ export PGUSER=root
18
+ export PGPASSWORD=postgres
19
+
20
+ docker run -p 5432:5432 -d -v $DATADIR:/init_data \
21
+ --name postgres-malloy \
22
+ -e POSTGRES_USER=root -e POSTGRES_PASSWORD=postgres \
23
+ --health-cmd pg_isready \
24
+ --health-interval 10s \
25
+ --health-timeout 5s \
26
+ --health-retries 5 \
27
+ -d postgres
28
+
29
+ CONTAINER_NAME="postgres-malloy"
30
+
31
+ echo "Waiting for container $CONTAINER_NAME to become healthy..."
32
+
33
+ while [ "$(docker inspect -f {{.State.Health.Status}} $CONTAINER_NAME)" != "healthy" ]; do
34
+ sleep 2; # Adjust the sleep duration as needed
35
+ done
36
+
37
+ echo "Container $CONTAINER_NAME is now healthy!"
38
+
39
+ # configure
40
+ echo CREATE EXTENSION tsm_system_rows\; | psql
41
+ gunzip -c ${DATADIR}/malloytest-postgres.sql.gz | psql
@@ -0,0 +1,7 @@
1
+ #! /bin/bash
2
+
3
+ # clear tmp files
4
+ rm -rf .tmp
5
+
6
+ # stop container
7
+ docker rm -f postgres-malloy
@@ -18,4 +18,36 @@ describe('misc tests for regressions that have no better home', () => {
18
18
  } -> { group_by: carriers.airline; limit: 1 }
19
19
  `).malloyResultMatches(runtime, [{}]);
20
20
  });
21
+
22
+ test('result data structure contains time zones for nested queries', async () => {
23
+ const query = runtime.loadQuery(`
24
+ run: duckdb.table('malloytest.flights') -> {
25
+ nest: arrive_yekaterinburg is {
26
+ timezone: 'Asia/Yekaterinburg'
27
+ group_by: utc_time is arr_time::string, civil_time is arr_time
28
+ limit: 5
29
+ }
30
+ }
31
+ `);
32
+ const result = await query.run();
33
+
34
+ // Inspect the result schema to find timezone metadata
35
+ const schema = result.resultExplore;
36
+
37
+ // Find the nested field
38
+ const nestedField = schema.getFieldByName('arrive_yekaterinburg');
39
+ expect(nestedField).toBeDefined();
40
+
41
+ if (nestedField?.isExploreField()) {
42
+ // Check if timezone information is present in the result data structure
43
+ const queryTimezone = nestedField.queryTimezone;
44
+
45
+ // Verify timezone is accessible in the result structure
46
+ expect(queryTimezone).toBe('Asia/Yekaterinburg');
47
+
48
+ // The timezone info should be available for later serialization
49
+ // (this is what gets turned into annotations during the to-stable process)
50
+ expect(nestedField.queryTimezone).toBeDefined();
51
+ }
52
+ });
21
53
  });
@@ -183,6 +183,21 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
183
183
  select: nm
184
184
  }`).malloyResultMatches(abc, got('xback'));
185
185
  });
186
+ test('string or with pipe', async () => {
187
+ await expect(`
188
+ run: abc -> {
189
+ where: s ~ f'abc | def'
190
+ select: nm; order_by: nm asc
191
+ }`).malloyResultMatches(abc, got('abc,def'));
192
+ });
193
+
194
+ test('string and with semicolon', async () => {
195
+ await expect(`
196
+ run: abc -> {
197
+ where: s ~ f'%b% ; %c'
198
+ select: nm; order_by: nm asc
199
+ }`).malloyResultMatches(abc, got('abc'));
200
+ });
186
201
  });
187
202
 
188
203
  describe('numeric filter expressions', () => {
@@ -319,6 +334,13 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
319
334
  select: n; order_by: n asc
320
335
  }`).malloyResultMatches(nums, [{n: 0}, {n: 1}]);
321
336
  });
337
+ test('not <=1', async () => {
338
+ await expect(`
339
+ run: nums -> {
340
+ where: n ~ f'not <=1'
341
+ select: n; order_by: n asc
342
+ }`).malloyResultMatches(nums, [{n: 2}, {n: 3}, {n: 4}]);
343
+ });
322
344
  });
323
345
 
324
346
  const testBoolean = db.dialect.booleanType === 'supported';
@@ -365,6 +387,27 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
365
387
  select: t; order_by: t asc
366
388
  }`).malloyResultMatches(facts, [{t: 'false'}, {t: 'true'}]);
367
389
  });
390
+ test.when(testBoolean)('not true', async () => {
391
+ await expect(`
392
+ run: facts -> {
393
+ where: b ~ f'not true'
394
+ select: t; order_by: t asc
395
+ }`).malloyResultMatches(facts, [{t: 'false'}, {t: 'null'}]);
396
+ });
397
+ test.when(testBoolean)('not false', async () => {
398
+ await expect(`
399
+ run: facts -> {
400
+ where: b ~ f'not false'
401
+ select: t; order_by: t asc
402
+ }`).malloyResultMatches(facts, [{t: 'true'}]);
403
+ });
404
+ test.when(testBoolean)('not =false', async () => {
405
+ await expect(`
406
+ run: facts -> {
407
+ where: b ~ f'not =false'
408
+ select: t; order_by: t asc
409
+ }`).malloyResultMatches(facts, [{t: 'null'}, {t: 'true'}]);
410
+ });
368
411
  test.when(testBoolean)('empty boolean filter', async () => {
369
412
  await expect(`
370
413
  run: facts -> {
@@ -274,6 +274,33 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
274
274
  });
275
275
  });
276
276
 
277
+ it(`symmetric sum and average large - ${databaseName}`, async () => {
278
+ await expect(`
279
+ source: a is ${databaseName}.table('malloytest.airports') extend {
280
+ primary_key: code
281
+ dimension: big_elevation is elevation * 100000
282
+ measure:
283
+ total_elevation is elevation.sum()
284
+ average_elevation is floor(elevation.avg())
285
+ total_big_elevation is big_elevation.sum()
286
+ average_big_elevation is floor(big_elevation.avg())
287
+ }
288
+ query: two_rows is ${databaseName}.table('malloytest.state_facts') -> {select: state; limit: 2}
289
+ source: b is two_rows extend {
290
+ join_cross: a on 1=1
291
+ }
292
+
293
+ run: b -> {aggregate: a.total_elevation, a.average_elevation, a.total_big_elevation, a.average_big_elevation}
294
+ // run: two_rows
295
+
296
+ `).malloyResultMatches(runtime, {
297
+ total_elevation: 22629146,
298
+ average_elevation: 1143,
299
+ total_big_elevation: 2262914600000,
300
+ average_big_elevation: 114329035,
301
+ });
302
+ });
303
+
277
304
  it(`limit - provided - ${databaseName}`, async () => {
278
305
  // a cross join produces a Many to Many result.
279
306
  // symmetric aggregate are needed on both sides of the join
@@ -919,6 +946,34 @@ SELECT row_to_json(finalStage) as row FROM __stage0 AS finalStage`);
919
946
  `).malloyResultMatches(runtime, {'fun.t1': 52});
920
947
  });
921
948
 
949
+ // not sure this works on all dialect.
950
+ it("stage names don't conflict- ${databaseName}", async () => {
951
+ await expect(`
952
+ source: airports is ${databaseName}.table('malloytest.state_facts') extend {
953
+ }
954
+
955
+ query: st0 is airports -> {
956
+ select: state
957
+ } -> {
958
+ select: *
959
+ }
960
+
961
+ query: st1 is airports -> {
962
+ select: state
963
+ } -> {
964
+ select: *
965
+ }
966
+
967
+ query: u is ${databaseName}.sql("""SELECT * FROM %{st0 } as x UNION ALL %{st1 }""") -> {
968
+ select: *
969
+ }
970
+ // # test.debug
971
+ run: u -> {
972
+ aggregate: c is count()
973
+ }
974
+ `).malloyResultMatches(runtime, {c: 102});
975
+ });
976
+
922
977
  const sql1234 = `${databaseName}.sql('SELECT 1 as ${q`a`}, 2 as ${q`b`} UNION ALL SELECT 3, 4')`;
923
978
 
924
979
  it(`sql as source - ${databaseName}`, async () => {
@@ -31,6 +31,7 @@ import {
31
31
  runQuery,
32
32
  } from '../../util';
33
33
  import {DateTime as LuxonDateTime} from 'luxon';
34
+ import {API} from '@malloydata/malloy';
34
35
 
35
36
  const runtimes = new RuntimeList(databasesFromEnvironmentOr(allDatabases));
36
37
 
@@ -736,6 +737,67 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
736
737
  }`
737
738
  ).malloyResultMatches(runtime, {mex_ts: zone_2020.toJSDate()});
738
739
  });
740
+
741
+ // Test for timezone rendering issue with nested queries
742
+ test.when(runtime.supportsNesting)(
743
+ 'nested queries preserve timezone in rendering',
744
+ async () => {
745
+ const result = await runQuery(
746
+ runtime,
747
+ `run: ${dbName}.table('malloytest.flights') extend {
748
+ view: arrivals is {
749
+ group_by: arr_time.hour
750
+ order_by: arr_time desc
751
+ limit: 1
752
+ }
753
+ } -> {
754
+ nest: arrive_utc is arrivals
755
+ nest: arrive_yekaterinburg is arrivals + { timezone: 'Asia/Yekaterinburg' }
756
+ }`
757
+ );
758
+
759
+ // First, check that the raw result has the correct timezone metadata
760
+ const rawFields = result.resultExplore.structDef.fields;
761
+ const rawArriveUtc = rawFields.find(f => f.name === 'arrive_utc');
762
+ const rawArriveYek = rawFields.find(
763
+ f => f.name === 'arrive_yekaterinburg'
764
+ );
765
+
766
+ expect(rawArriveUtc).toBeDefined();
767
+ expect(rawArriveYek).toBeDefined();
768
+
769
+ // Check timezone properties on raw array/record fields
770
+ expect(rawArriveUtc!['queryTimezone']).toBeUndefined(); // Should be undefined for UTC
771
+ expect(rawArriveYek!['queryTimezone']).toBe('Asia/Yekaterinburg');
772
+
773
+ // Now check that the wrapped result also preserves timezone metadata
774
+ const wrappedResult = API.util.wrapResult(result);
775
+ const wrappedFields = wrappedResult.schema.fields;
776
+ const wrappedArriveUtc = wrappedFields.find(f => f.name === 'arrive_utc');
777
+ const wrappedArriveYek = wrappedFields.find(
778
+ f => f.name === 'arrive_yekaterinburg'
779
+ );
780
+
781
+ expect(wrappedArriveUtc).toBeDefined();
782
+ expect(wrappedArriveYek).toBeDefined();
783
+
784
+ // Check timezone properties in the wrapped result
785
+ // For nested views, the timezone should be in the annotations
786
+ const arriveUtcAnnotations = wrappedArriveUtc?.annotations;
787
+ const arriveYekAnnotations = wrappedArriveYek?.annotations;
788
+
789
+ // Check if timezone info is present in annotations
790
+ const utcTimezoneAnnotation = arriveUtcAnnotations?.find(ann =>
791
+ ann.value.includes('query_timezone')
792
+ );
793
+ const yekTimezoneAnnotation = arriveYekAnnotations?.find(ann =>
794
+ ann.value.includes('query_timezone')
795
+ );
796
+
797
+ expect(utcTimezoneAnnotation).toBeUndefined(); // UTC should have no timezone annotation
798
+ expect(yekTimezoneAnnotation?.value).toContain('Asia/Yekaterinburg');
799
+ }
800
+ );
739
801
  });
740
802
 
741
803
  afterAll(async () => {