@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 +9 -9
- package/postgres/postgres_start.sh +41 -0
- package/postgres/postgres_stop.sh +7 -0
- package/src/core/bugless.spec.ts +32 -0
- package/src/databases/all/db_filter_expressions.spec.ts +43 -0
- package/src/databases/all/nomodel.spec.ts +55 -0
- package/src/databases/all/time.spec.ts +62 -0
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.
|
|
25
|
-
"@malloydata/db-duckdb": "0.0.
|
|
26
|
-
"@malloydata/db-postgres": "0.0.
|
|
27
|
-
"@malloydata/db-snowflake": "0.0.
|
|
28
|
-
"@malloydata/db-trino": "0.0.
|
|
29
|
-
"@malloydata/malloy": "0.0.
|
|
30
|
-
"@malloydata/malloy-tag": "0.0.
|
|
31
|
-
"@malloydata/render": "0.0.
|
|
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.
|
|
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
|
package/src/core/bugless.spec.ts
CHANGED
|
@@ -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 () => {
|