@malloydata/malloy-tests 0.0.432 → 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.
- package/CONTEXT.md +14 -5
- package/data/parquet_loader.ts +106 -0
- package/duckdb/load_test_data.ts +15 -48
- package/mssql/connection_string.ts +12 -0
- package/mssql/load_test_data.sh +12 -0
- package/mssql/load_test_data.ts +63 -0
- package/mssql/mssql_start.sh +13 -132
- package/mysql/load_test_data.sh +12 -0
- package/mysql/load_test_data.ts +39 -0
- package/mysql/mysql_start.sh +36 -30
- package/package.json +10 -10
- package/postgres/load_test_data.sh +12 -0
- package/postgres/load_test_data.ts +34 -0
- package/postgres/postgres_start.sh +2 -6
- package/presto/Dockerfile.slim +12 -6
- package/presto/hive.properties +8 -0
- package/presto/presto_start.sh +58 -36
- package/src/core/restricted-mode.spec.ts +27 -0
- package/src/core/tags.spec.ts +22 -0
- package/src/core/virtual-sql.spec.ts +253 -0
- package/src/databases/all/expr.spec.ts +150 -18
- package/src/databases/all/nest.spec.ts +237 -0
- package/src/databases/all/nomodel.spec.ts +0 -75
- package/src/databases/all/problems.spec.ts +32 -0
- package/src/runtimes.ts +1 -0
- package/trino/hive.properties +11 -0
- package/trino/hive_ddl.ts +148 -0
- package/trino/trino_start.sh +54 -38
- package/tsconfig.json +12 -1
- package/bin/dump_malloytest.sh +0 -1
- package/bin/postgres_init.sh +0 -10
- package/bin/postgres_start.sh +0 -2
- package/bin/postgres_stop.sh +0 -2
- package/data/mysql/malloytest.mysql.gz +0 -0
- package/data/postgres/malloytest-postgres.sql.gz +0 -0
- package/data/postgres/state_facts.sql +0 -366
package/CONTEXT.md
CHANGED
|
@@ -140,7 +140,7 @@ These commands are optimized for CI and may not work correctly in local developm
|
|
|
140
140
|
|
|
141
141
|
### The test corpus
|
|
142
142
|
|
|
143
|
-
The shared cross-database parquet files live in `test/data/malloytest-parquet/` (~
|
|
143
|
+
The shared cross-database parquet files live in `test/data/malloytest-parquet/` (~15 MB, checked into git). They are the only description of the test data: every database is loaded from them by a script in the repo (BigQuery excepted, see below). When the parquet changes, the loaders run again; there is no dump to edit. DuckDB-only files remain in `test/data/duckdb/`.
|
|
144
144
|
|
|
145
145
|
#### Shared parquets (`test/data/malloytest-parquet/`)
|
|
146
146
|
|
|
@@ -171,14 +171,23 @@ All cross-database tests (`test/src/databases/all/`) reference tables as `malloy
|
|
|
171
171
|
|
|
172
172
|
| Dialect | Method | Files | Notes |
|
|
173
173
|
|---|---|---|---|
|
|
174
|
-
| DuckDB |
|
|
175
|
-
| PostgreSQL |
|
|
176
|
-
| MySQL |
|
|
174
|
+
| DuckDB | DuckDB `CREATE TABLE AS SELECT FROM read_parquet()` | `test/duckdb/load_test_data.sh` (wraps `load_test_data.ts`) | Run via `sh test/duckdb/load_test_data.sh` (or `npm run build-duckdb-db`). Creates `test/data/duckdb/duckdb_test.db` |
|
|
175
|
+
| PostgreSQL | DuckDB's `postgres` extension: `ATTACH` the server, `CREATE TABLE AS SELECT FROM read_parquet()` | `test/postgres/load_test_data.sh` (wraps `load_test_data.ts`) | Run by `test/postgres/postgres_start.sh` and by CI against a running server; connects with the `PG*` variables. No `ga_sample` (no anonymous record type) |
|
|
176
|
+
| MySQL | DuckDB's `mysql` extension, likewise | `test/mysql/load_test_data.sh` (wraps `load_test_data.ts`) | Run by `test/mysql/mysql_start.sh`; connects with the `MYSQL_*` variables. No `ga_sample`, and `alltypes` without its array columns |
|
|
177
|
+
| SQL Server | DuckDB's community `mssql` extension, likewise | `test/mssql/load_test_data.sh` (wraps `load_test_data.ts`) | Run by `test/mssql/mssql_start.sh`; connects as the test runtime does (`sa`, localhost:1433). Waits for the server itself. Same table set as MySQL. Not in CI |
|
|
178
|
+
| Trino/Presto | The hive connector reads the parquet in place: each file is mounted into the container and declared as an external table | `test/trino/hive_ddl.ts`, `test/trino/hive.properties`, `test/presto/hive.properties` | `trino_start.sh` / `presto_start.sh` derive the DDL from the parquet schemas at container start. The hive metastore lowercases names, so a table with nested columns (`ga_sample`) is a `_hive` table plus a view that casts the nested field names back; top-level column names stay lowercase, since Trino lowercases those in every connector |
|
|
177
179
|
| BigQuery | Pre-loaded manually in `malloydata-org` project | (none) | No loader script in repo. Data assumed to exist. |
|
|
178
180
|
| Snowflake | SQL: `PUT` local parquet → stage, `COPY INTO` table | `test/snowflake/load_test_data.sh` (wraps `load_test_data.sql`) | Run via `sh test/snowflake/load_test_data.sh` (needs the `snowsql` CLI) |
|
|
179
|
-
| Trino/Presto | Docker containers with pre-loaded data | `test/trino/trino_start.sh` | Uses Docker volumes |
|
|
180
181
|
| Databricks | TS script: upload to Volume via REST, `CREATE TABLE AS SELECT FROM read_files()` | `test/databricks/load_test_data.sh` (wraps `load_test_data.ts`) | Run manually with the `DATABRICKS_*` env vars set: `sh test/databricks/load_test_data.sh` |
|
|
181
182
|
|
|
183
|
+
The DuckDB-driven loaders share `test/data/parquet_loader.ts`, which runs everything through Malloy's own `DuckDBConnection`, so a database is built by the same DuckDB the tests read it with. It reads the table list from the parquet directory and takes a `LoadPlan` naming what a target cannot hold.
|
|
184
|
+
|
|
185
|
+
### The flights timestamps
|
|
186
|
+
|
|
187
|
+
`flights.dep_time` and `arr_time` are UTC instants whose digits are the local clock at the departure and arrival airport. The source recorded each airport's wall clock and the zone was not kept, so a 6:45 pm departure from LAX is the instant 18:45 UTC. The parquet declares them so (`tz=UTC`), every dialect holds them as instants, and under Malloy's UTC session pin every dialect renders the airport clock. An instant is the one time type every dialect can hold; Malloy has no wall-clock type, and several dialects have none either. Elapsed time between the two columns is not computable from them on a route that crosses zones; `flight_time`, `taxi_out` and `taxi_in` carry it.
|
|
188
|
+
|
|
189
|
+
A loader must carry the instant through unchanged. Malloy's `DuckDBConnection`, which the loaders run through, pins its session zone to UTC, so a target without a zoned type (MySQL `datetime`) receives the same digits, where a loader in the machine's zone would shift them. Trino and Presto read the parquet's UTC microseconds directly.
|
|
190
|
+
|
|
182
191
|
### Cloud warehouse considerations
|
|
183
192
|
|
|
184
193
|
Cloud SQL warehouses (BigQuery, Snowflake, Databricks) can't read local files via SQL. Each needs a mechanism to get parquet data into the warehouse:
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Contributors to the Malloy project
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-disable no-console */
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* The malloytest tables are the parquet files in test/data/malloytest-parquet,
|
|
10
|
+
* one table per file. Each database's load_test_data script copies them into
|
|
11
|
+
* that database through DuckDB, so the parquet is the only description of the
|
|
12
|
+
* test data. See "Test Data" in test/CONTEXT.md.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {QueryRecord} from '@malloydata/malloy';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import {DuckDBConnection} from '../../packages/malloy-db-duckdb';
|
|
19
|
+
|
|
20
|
+
export const parquetDir = path.resolve(__dirname, 'malloytest-parquet');
|
|
21
|
+
|
|
22
|
+
export function parquetTables(): string[] {
|
|
23
|
+
return fs
|
|
24
|
+
.readdirSync(parquetDir)
|
|
25
|
+
.filter(file => file.endsWith('.parquet'))
|
|
26
|
+
.map(file => file.slice(0, -'.parquet'.length))
|
|
27
|
+
.sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parquetPath(table: string): string {
|
|
31
|
+
return path.join(parquetDir, `${table}.parquet`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LoadPlan {
|
|
35
|
+
/** Tables the target cannot hold at all */
|
|
36
|
+
skip?: string[];
|
|
37
|
+
/** Columns to leave out of a table, for types the target lacks */
|
|
38
|
+
omit?: {table: string; columns: string[]}[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The plan for a target with no array or record types */
|
|
42
|
+
export const scalarTypesOnly: LoadPlan = {
|
|
43
|
+
skip: ['ga_sample'],
|
|
44
|
+
omit: [{table: 'alltypes', columns: ['t_array_string', 't_array_int64']}],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** A DuckDB session that runs SQL and returns the rows */
|
|
48
|
+
export interface TestDataDuckDB {
|
|
49
|
+
run(sql: string): Promise<QueryRecord[]>;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Malloy's own DuckDB connection, so a database is built by the same DuckDB
|
|
55
|
+
* the tests read it with. That connection pins the session zone to UTC,
|
|
56
|
+
* which a loader needs: the parquet timestamps are instants, and a target
|
|
57
|
+
* without a zoned timestamp type must receive their UTC digits.
|
|
58
|
+
*/
|
|
59
|
+
export async function openDuckDB(
|
|
60
|
+
databasePath = ':memory:'
|
|
61
|
+
): Promise<TestDataDuckDB> {
|
|
62
|
+
const connection = new DuckDBConnection('loader', databasePath);
|
|
63
|
+
return {
|
|
64
|
+
run: async sql => (await connection.runRawSQL(sql)).rows,
|
|
65
|
+
close: () => connection.close(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function loadExtension(
|
|
70
|
+
db: TestDataDuckDB,
|
|
71
|
+
extension: string,
|
|
72
|
+
repository?: 'community'
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
const from = repository ? ` FROM ${repository}` : '';
|
|
75
|
+
await db.run(`INSTALL ${extension}${from}`);
|
|
76
|
+
await db.run(`LOAD ${extension}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Replace `schema` with the malloytest tables. `schema` may live in an
|
|
81
|
+
* attached database (`pg.malloytest`).
|
|
82
|
+
*/
|
|
83
|
+
export async function loadMalloytest(
|
|
84
|
+
db: TestDataDuckDB,
|
|
85
|
+
schema: string,
|
|
86
|
+
plan: LoadPlan = {}
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
await db.run(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);
|
|
89
|
+
await db.run(`CREATE SCHEMA ${schema}`);
|
|
90
|
+
for (const table of parquetTables()) {
|
|
91
|
+
if (plan.skip?.includes(table)) {
|
|
92
|
+
console.log(` ${table}: skipped`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const omit = plan.omit?.find(o => o.table === table)?.columns ?? [];
|
|
96
|
+
const columns =
|
|
97
|
+
omit.length > 0
|
|
98
|
+
? `* EXCLUDE (${omit.map(c => `"${c}"`).join(', ')})`
|
|
99
|
+
: '*';
|
|
100
|
+
await db.run(
|
|
101
|
+
`CREATE TABLE ${schema}.${table} AS SELECT ${columns} FROM read_parquet('${parquetPath(table)}')`
|
|
102
|
+
);
|
|
103
|
+
const rows = await db.run(`SELECT count(*) AS n FROM ${schema}.${table}`);
|
|
104
|
+
console.log(` ${table}: ${rows[0]['n']} rows`);
|
|
105
|
+
}
|
|
106
|
+
}
|
package/duckdb/load_test_data.ts
CHANGED
|
@@ -5,57 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
/* eslint-disable no-console */
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const parquetDir = './test/data/malloytest-parquet/';
|
|
13
|
-
const duckdbDir = './test/data/duckdb/';
|
|
14
|
-
const databasePath = `${duckdbDir}duckdb_test.db`;
|
|
15
|
-
if (fs.existsSync(databasePath)) {
|
|
16
|
-
console.log(`Database at ${databasePath} already exists, removing`);
|
|
17
|
-
fs.rmSync(databasePath);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const database = new DuckDBConnection('duckdb', databasePath);
|
|
8
|
+
/*
|
|
9
|
+
* Build the DuckDB test database, test/data/duckdb/duckdb_test.db, from the
|
|
10
|
+
* parquet files.
|
|
11
|
+
*/
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import {loadMalloytest, openDuckDB} from '../data/parquet_loader';
|
|
25
16
|
|
|
26
|
-
|
|
17
|
+
const databasePath = path.resolve(__dirname, '../data/duckdb/duckdb_test.db');
|
|
27
18
|
|
|
28
19
|
(async () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`CREATE TABLE malloytest.aircraft AS SELECT * FROM parquet_scan('${parquetDir}aircraft.parquet')`
|
|
33
|
-
);
|
|
34
|
-
await run(
|
|
35
|
-
`CREATE TABLE malloytest.aircraft_models AS SELECT * FROM parquet_scan('${parquetDir}aircraft_models.parquet')`
|
|
36
|
-
);
|
|
37
|
-
await run(
|
|
38
|
-
`CREATE TABLE malloytest.airports AS SELECT * FROM parquet_scan('${parquetDir}airports.parquet')`
|
|
39
|
-
);
|
|
40
|
-
await run(
|
|
41
|
-
`CREATE TABLE malloytest.alltypes AS SELECT * FROM parquet_scan('${parquetDir}alltypes.parquet')`
|
|
42
|
-
);
|
|
43
|
-
await run(
|
|
44
|
-
`CREATE TABLE malloytest.carriers AS SELECT * FROM parquet_scan('${parquetDir}carriers.parquet')`
|
|
45
|
-
);
|
|
46
|
-
await run(
|
|
47
|
-
`CREATE TABLE malloytest.flights AS SELECT * FROM parquet_scan('${parquetDir}flights.parquet')`
|
|
48
|
-
);
|
|
49
|
-
await run(
|
|
50
|
-
`CREATE TABLE malloytest.state_facts AS SELECT * FROM parquet_scan('${parquetDir}state_facts.parquet')`
|
|
51
|
-
);
|
|
52
|
-
await run(
|
|
53
|
-
`CREATE TABLE malloytest.ga_sample AS SELECT * FROM parquet_scan('${parquetDir}ga_sample.parquet')`
|
|
54
|
-
);
|
|
55
|
-
console.log('Finished populating database with data from parqeut files');
|
|
56
|
-
} catch (e) {
|
|
57
|
-
console.log(e);
|
|
20
|
+
if (fs.existsSync(databasePath)) {
|
|
21
|
+
console.log(`Database at ${databasePath} already exists, removing`);
|
|
22
|
+
fs.rmSync(databasePath);
|
|
58
23
|
}
|
|
59
|
-
|
|
60
|
-
await
|
|
24
|
+
console.log(`Creating database at ${databasePath}`);
|
|
25
|
+
const loader = await openDuckDB(databasePath);
|
|
26
|
+
await loadMalloytest(loader, 'malloytest');
|
|
27
|
+
await loader.close();
|
|
61
28
|
})();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Contributors to the Malloy project
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The SQL Server the mssql tests and loader connect to: the container
|
|
8
|
+
* mssql_start.sh runs, with the password it sets.
|
|
9
|
+
*/
|
|
10
|
+
export function mssqlConnectionString(database: string): string {
|
|
11
|
+
return `Server=localhost;Port=1433;Database=${database};User Id=sa;Password=Malloy_Test_123;TrustServerCertificate=true`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#! /bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Load the malloytest tables into a running SQL Server from
|
|
4
|
+
# test/data/malloytest-parquet. Waits for the server itself.
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SCRIPTDIR=$(cd $(dirname $0); pwd)
|
|
9
|
+
ROOTDIR=$(cd "$SCRIPTDIR/../.."; pwd)
|
|
10
|
+
|
|
11
|
+
cd "$ROOTDIR"
|
|
12
|
+
npx ts-node test/mssql/load_test_data.ts
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Contributors to the Malloy project
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-disable no-console */
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Load the malloytest tables into a running SQL Server from the parquet
|
|
10
|
+
* files, through DuckDB's mssql extension. Connects as the mssql test
|
|
11
|
+
* connection does. Waits for the server first, since the container image
|
|
12
|
+
* ships no client to wait with.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
loadExtension,
|
|
17
|
+
loadMalloytest,
|
|
18
|
+
openDuckDB,
|
|
19
|
+
scalarTypesOnly,
|
|
20
|
+
} from '../data/parquet_loader';
|
|
21
|
+
import {mssqlConnectionString} from './connection_string';
|
|
22
|
+
|
|
23
|
+
const database = 'malloytest';
|
|
24
|
+
|
|
25
|
+
function sleep(ms: number): Promise<void> {
|
|
26
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
(async () => {
|
|
30
|
+
const db = await openDuckDB();
|
|
31
|
+
await loadExtension(db, 'mssql', 'community');
|
|
32
|
+
|
|
33
|
+
// Attach the always-present master database, retrying while the server
|
|
34
|
+
// comes up, then recreate the test database from scratch.
|
|
35
|
+
const deadline = Date.now() + 120_000;
|
|
36
|
+
for (;;) {
|
|
37
|
+
try {
|
|
38
|
+
await db.run(
|
|
39
|
+
`ATTACH '${mssqlConnectionString('master')}' AS ms (TYPE mssql)`
|
|
40
|
+
);
|
|
41
|
+
break;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (Date.now() > deadline) {
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
const reason = e instanceof Error ? e.message.split('\n')[0] : `${e}`;
|
|
47
|
+
console.log(` waiting for SQL Server: ${reason}`);
|
|
48
|
+
await sleep(2000);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await db.run(
|
|
52
|
+
`SELECT mssql_exec('ms', 'IF DB_ID(''${database}'') IS NOT NULL DROP DATABASE ${database}')`
|
|
53
|
+
);
|
|
54
|
+
await db.run(`SELECT mssql_exec('ms', 'CREATE DATABASE ${database}')`);
|
|
55
|
+
await db.run('DETACH ms');
|
|
56
|
+
await db.run(
|
|
57
|
+
`ATTACH '${mssqlConnectionString(database)}' AS ms (TYPE mssql)`
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
console.log('Loading malloytest into mssql');
|
|
61
|
+
await loadMalloytest(db, `ms.${database}`, scalarTypesOnly);
|
|
62
|
+
await db.close();
|
|
63
|
+
})();
|
package/mssql/mssql_start.sh
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Start a SQL Server container and load the malloytest tables into it from
|
|
4
|
+
# test/data/malloytest-parquet.
|
|
5
|
+
#
|
|
2
6
|
set -e
|
|
3
7
|
|
|
4
8
|
SCRIPTDIR=$(cd $(dirname $0); pwd)
|
|
5
|
-
DATADIR=$(dirname $SCRIPTDIR)/data/malloytest-parquet
|
|
6
9
|
CONTAINER_NAME="mssql-malloy"
|
|
10
|
+
# Must match test/mssql/connection_string.ts
|
|
7
11
|
SA_PASSWORD="Malloy_Test_123"
|
|
8
|
-
DB_NAME="malloytest"
|
|
9
|
-
|
|
10
|
-
# Require duckdb CLI (used for readiness check and data loading)
|
|
11
|
-
if ! command -v duckdb > /dev/null 2>&1; then
|
|
12
|
-
echo "Error: duckdb CLI is required but not found"
|
|
13
|
-
exit 1
|
|
14
|
-
fi
|
|
15
12
|
|
|
16
13
|
# Check for existing container
|
|
17
14
|
if docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1; then
|
|
@@ -21,13 +18,8 @@ if docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1; then
|
|
|
21
18
|
fi
|
|
22
19
|
echo "Restarting existing $CONTAINER_NAME container..."
|
|
23
20
|
docker start "$CONTAINER_NAME"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
duckdb -c "LOAD mssql; ATTACH 'Server=localhost;Port=1433;Database=master;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=true' AS _ping (TYPE mssql);" > /dev/null 2>&1 && break
|
|
27
|
-
echo -n "."
|
|
28
|
-
sleep 2
|
|
29
|
-
done
|
|
30
|
-
echo " ready"
|
|
21
|
+
# The loader waits for the server; reloading is cheap
|
|
22
|
+
sh "$SCRIPTDIR/load_test_data.sh"
|
|
31
23
|
echo "MSSQL running on port 1433"
|
|
32
24
|
exit 0
|
|
33
25
|
fi
|
|
@@ -48,122 +40,11 @@ docker run -d \
|
|
|
48
40
|
-p 1433:1433 \
|
|
49
41
|
"$IMAGE"
|
|
50
42
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
while true; do
|
|
55
|
-
duckdb -c "LOAD mssql; ATTACH 'Server=localhost;Port=1433;Database=master;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=true' AS _ping (TYPE mssql);" > /dev/null 2>&1 && break
|
|
56
|
-
counter=$((counter + 1))
|
|
57
|
-
if [ $counter -ge 60 ]; then
|
|
58
|
-
echo
|
|
59
|
-
echo "MSSQL did not start within 2 minutes"
|
|
60
|
-
docker logs "$CONTAINER_NAME" 2>&1 | tail -20
|
|
61
|
-
docker rm -f "$CONTAINER_NAME"
|
|
62
|
-
exit 1
|
|
63
|
-
fi
|
|
64
|
-
echo -n "."
|
|
65
|
-
sleep 2
|
|
66
|
-
done
|
|
67
|
-
echo " ready"
|
|
68
|
-
|
|
69
|
-
# Load test data using DuckDB + mssql extension
|
|
43
|
+
# The loader waits for the server. A container without its tables must not
|
|
44
|
+
# survive to be "already running".
|
|
45
|
+
trap 'docker rm -f "$CONTAINER_NAME" > /dev/null; echo "MSSQL setup failed"' ERR
|
|
70
46
|
echo "Loading test data..."
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-- Create database
|
|
75
|
-
ATTACH 'Server=localhost;Port=1433;Database=master;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=true' AS msdb (TYPE mssql);
|
|
76
|
-
SELECT mssql_exec('msdb', 'IF DB_ID(''$DB_NAME'') IS NOT NULL DROP DATABASE $DB_NAME');
|
|
77
|
-
SELECT mssql_exec('msdb', 'CREATE DATABASE $DB_NAME');
|
|
78
|
-
DETACH msdb;
|
|
79
|
-
|
|
80
|
-
-- Connect to test database
|
|
81
|
-
ATTACH 'Server=localhost;Port=1433;Database=$DB_NAME;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=true' AS msdb (TYPE mssql);
|
|
82
|
-
|
|
83
|
-
-- Create malloytest schema so table paths (malloytest.flights etc.) match DuckDB tests
|
|
84
|
-
SELECT mssql_exec('msdb', 'CREATE SCHEMA malloytest');
|
|
85
|
-
|
|
86
|
-
-- Create tables: schemas must match the parquet files in test/data/malloytest-parquet/.
|
|
87
|
-
-- If a parquet schema changes, update the corresponding CREATE TABLE below.
|
|
88
|
-
-- All columns explicitly nullable to match parquet semantics.
|
|
89
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.aircraft (
|
|
90
|
-
id BIGINT NULL, tail_num NVARCHAR(MAX) NULL, aircraft_serial NVARCHAR(MAX) NULL,
|
|
91
|
-
aircraft_model_code NVARCHAR(MAX) NULL, aircraft_engine_code NVARCHAR(MAX) NULL,
|
|
92
|
-
year_built INT NULL, aircraft_type_id INT NULL, aircraft_engine_type_id INT NULL,
|
|
93
|
-
registrant_type_id INT NULL, name NVARCHAR(MAX) NULL, address1 NVARCHAR(MAX) NULL,
|
|
94
|
-
address2 NVARCHAR(MAX) NULL, city NVARCHAR(MAX) NULL, state NVARCHAR(MAX) NULL,
|
|
95
|
-
zip NVARCHAR(MAX) NULL, region NVARCHAR(MAX) NULL, county NVARCHAR(MAX) NULL,
|
|
96
|
-
country NVARCHAR(MAX) NULL, certification NVARCHAR(MAX) NULL, status_code NVARCHAR(MAX) NULL,
|
|
97
|
-
mode_s_code NVARCHAR(MAX) NULL, fract_owner NVARCHAR(MAX) NULL,
|
|
98
|
-
last_action_date NVARCHAR(MAX) NULL, cert_issue_date NVARCHAR(MAX) NULL,
|
|
99
|
-
air_worth_date NVARCHAR(MAX) NULL
|
|
100
|
-
)');
|
|
101
|
-
|
|
102
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.aircraft_models (
|
|
103
|
-
aircraft_model_code NVARCHAR(MAX) NULL, manufacturer NVARCHAR(MAX) NULL,
|
|
104
|
-
model NVARCHAR(MAX) NULL, aircraft_type_id INT NULL, aircraft_engine_type_id INT NULL,
|
|
105
|
-
aircraft_category_id INT NULL, amateur INT NULL, engines INT NULL,
|
|
106
|
-
seats INT NULL, weight INT NULL, speed INT NULL
|
|
107
|
-
)');
|
|
108
|
-
|
|
109
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.airports (
|
|
110
|
-
id INT NULL, code NVARCHAR(MAX) NULL, site_number NVARCHAR(MAX) NULL,
|
|
111
|
-
fac_type NVARCHAR(MAX) NULL, fac_use NVARCHAR(MAX) NULL, faa_region NVARCHAR(MAX) NULL,
|
|
112
|
-
faa_dist NVARCHAR(MAX) NULL, city NVARCHAR(MAX) NULL, county NVARCHAR(MAX) NULL,
|
|
113
|
-
state NVARCHAR(MAX) NULL, full_name NVARCHAR(MAX) NULL, own_type NVARCHAR(MAX) NULL,
|
|
114
|
-
longitude FLOAT NULL, latitude FLOAT NULL, elevation INT NULL,
|
|
115
|
-
aero_cht NVARCHAR(MAX) NULL, cbd_dist INT NULL, cbd_dir NVARCHAR(MAX) NULL,
|
|
116
|
-
act_date NVARCHAR(MAX) NULL, cert NVARCHAR(MAX) NULL, fed_agree NVARCHAR(MAX) NULL,
|
|
117
|
-
cust_intl NVARCHAR(MAX) NULL, c_ldg_rts NVARCHAR(MAX) NULL, joint_use NVARCHAR(MAX) NULL,
|
|
118
|
-
mil_rts NVARCHAR(MAX) NULL, cntl_twr NVARCHAR(MAX) NULL, major NVARCHAR(MAX) NULL
|
|
119
|
-
)');
|
|
120
|
-
|
|
121
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.carriers (
|
|
122
|
-
code NVARCHAR(MAX) NULL, name NVARCHAR(MAX) NULL, nickname NVARCHAR(MAX) NULL
|
|
123
|
-
)');
|
|
124
|
-
|
|
125
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.flights (
|
|
126
|
-
carrier NVARCHAR(MAX) NULL, origin NVARCHAR(MAX) NULL, destination NVARCHAR(MAX) NULL,
|
|
127
|
-
flight_num INT NULL, flight_time INT NULL, tail_num NVARCHAR(MAX) NULL,
|
|
128
|
-
dep_time DATETIME NULL, arr_time DATETIME NULL, dep_delay INT NULL,
|
|
129
|
-
arr_delay INT NULL, taxi_out INT NULL, taxi_in INT NULL, distance INT NULL,
|
|
130
|
-
cancelled NVARCHAR(MAX) NULL, diverted NVARCHAR(MAX) NULL, id2 INT NULL
|
|
131
|
-
)');
|
|
132
|
-
|
|
133
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.state_facts (
|
|
134
|
-
state NVARCHAR(MAX) NULL, aircraft_count INT NULL, airport_count INT NULL,
|
|
135
|
-
births INT NULL, popular_name NVARCHAR(MAX) NULL
|
|
136
|
-
)');
|
|
137
|
-
|
|
138
|
-
SELECT mssql_exec('msdb', 'CREATE TABLE malloytest.alltypes (
|
|
139
|
-
t_int64 BIGINT NULL, t_float64 FLOAT NULL,
|
|
140
|
-
t_numeric DECIMAL(38,9) NULL, t_bignumeric DECIMAL(38,9) NULL,
|
|
141
|
-
string NVARCHAR(MAX) NULL,
|
|
142
|
-
t_bool_true BIT NULL, t_bool_false BIT NULL, t_bool_null BIT NULL,
|
|
143
|
-
t_date DATE NULL, t_datetime DATETIME NULL, t_timestamp DATETIME NULL
|
|
144
|
-
)');
|
|
145
|
-
|
|
146
|
-
SELECT mssql_refresh_cache('msdb');
|
|
147
|
-
|
|
148
|
-
-- Load data from parquet files
|
|
149
|
-
INSERT INTO msdb.malloytest.aircraft SELECT * FROM read_parquet('$DATADIR/aircraft.parquet');
|
|
150
|
-
INSERT INTO msdb.malloytest.aircraft_models SELECT * FROM read_parquet('$DATADIR/aircraft_models.parquet');
|
|
151
|
-
INSERT INTO msdb.malloytest.airports SELECT * FROM read_parquet('$DATADIR/airports.parquet');
|
|
152
|
-
INSERT INTO msdb.malloytest.carriers SELECT * FROM read_parquet('$DATADIR/carriers.parquet');
|
|
153
|
-
INSERT INTO msdb.malloytest.flights SELECT * FROM read_parquet('$DATADIR/flights.parquet');
|
|
154
|
-
INSERT INTO msdb.malloytest.state_facts SELECT * FROM read_parquet('$DATADIR/state_facts.parquet');
|
|
155
|
-
INSERT INTO msdb.malloytest.alltypes SELECT t_int64, t_float64, t_numeric, t_bignumeric, string, t_bool_true, t_bool_false, t_bool_null, t_date, t_datetime, t_timestamp FROM read_parquet('$DATADIR/alltypes.parquet');
|
|
156
|
-
|
|
157
|
-
-- Verify row counts
|
|
158
|
-
SELECT 'aircraft' as tbl, COUNT(*) as rows FROM msdb.malloytest.aircraft
|
|
159
|
-
UNION ALL SELECT 'aircraft_models', COUNT(*) FROM msdb.malloytest.aircraft_models
|
|
160
|
-
UNION ALL SELECT 'airports', COUNT(*) FROM msdb.malloytest.airports
|
|
161
|
-
UNION ALL SELECT 'carriers', COUNT(*) FROM msdb.malloytest.carriers
|
|
162
|
-
UNION ALL SELECT 'flights', COUNT(*) FROM msdb.malloytest.flights
|
|
163
|
-
UNION ALL SELECT 'state_facts', COUNT(*) FROM msdb.malloytest.state_facts
|
|
164
|
-
UNION ALL SELECT 'alltypes', COUNT(*) FROM msdb.malloytest.alltypes;
|
|
165
|
-
"
|
|
47
|
+
sh "$SCRIPTDIR/load_test_data.sh"
|
|
48
|
+
trap - ERR
|
|
166
49
|
|
|
167
|
-
echo
|
|
168
|
-
echo "MSSQL running on port 1433, database: $DB_NAME"
|
|
169
|
-
echo " Connection: Server=localhost;Port=1433;Database=$DB_NAME;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=true"
|
|
50
|
+
echo "MSSQL running on port 1433, database: malloytest"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#! /bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Load the malloytest tables into a running mysql from
|
|
4
|
+
# test/data/malloytest-parquet. Connects with the MYSQL_* variables.
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SCRIPTDIR=$(cd $(dirname $0); pwd)
|
|
9
|
+
ROOTDIR=$(cd "$SCRIPTDIR/../.."; pwd)
|
|
10
|
+
|
|
11
|
+
cd "$ROOTDIR"
|
|
12
|
+
npx ts-node test/mysql/load_test_data.ts
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Contributors to the Malloy project
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-disable no-console */
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Load the malloytest tables into a running mysql from the parquet files.
|
|
10
|
+
* Connects with the same MYSQL_* variables, read the same way, as the mysql
|
|
11
|
+
* test connection.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {MySQLExecutor} from '../../packages/malloy-db-mysql';
|
|
15
|
+
import {
|
|
16
|
+
loadExtension,
|
|
17
|
+
loadMalloytest,
|
|
18
|
+
openDuckDB,
|
|
19
|
+
scalarTypesOnly,
|
|
20
|
+
} from '../data/parquet_loader';
|
|
21
|
+
|
|
22
|
+
const options = MySQLExecutor.getConnectionOptionsFromEnv();
|
|
23
|
+
const attach = [
|
|
24
|
+
...(options.host ? [`host=${options.host}`] : []),
|
|
25
|
+
...(options.port ? [`port=${options.port}`] : []),
|
|
26
|
+
...(options.user ? [`user=${options.user}`] : []),
|
|
27
|
+
...(options.password ? [`password=${options.password}`] : []),
|
|
28
|
+
// The system database is always there; the load creates malloytest itself
|
|
29
|
+
'database=mysql',
|
|
30
|
+
].join(' ');
|
|
31
|
+
|
|
32
|
+
(async () => {
|
|
33
|
+
const db = await openDuckDB();
|
|
34
|
+
await loadExtension(db, 'mysql');
|
|
35
|
+
await db.run(`ATTACH '${attach}' AS my (TYPE mysql)`);
|
|
36
|
+
console.log('Loading malloytest into mysql');
|
|
37
|
+
await loadMalloytest(db, 'my.malloytest', scalarTypesOnly);
|
|
38
|
+
await db.close();
|
|
39
|
+
})();
|
package/mysql/mysql_start.sh
CHANGED
|
@@ -4,11 +4,31 @@ set -e
|
|
|
4
4
|
rm -rf .tmp
|
|
5
5
|
mkdir .tmp
|
|
6
6
|
|
|
7
|
-
# run docker
|
|
8
7
|
SCRIPTDIR=$(cd $(dirname $0); pwd)
|
|
9
|
-
DATADIR=$(dirname $SCRIPTDIR)/data/mysql
|
|
10
8
|
CONTAINER_NAME="mysql-malloy"
|
|
11
9
|
|
|
10
|
+
# The image starts mysqld twice: a temporary server to initialize the data
|
|
11
|
+
# directory, listening on the unix socket only, and then the real server on
|
|
12
|
+
# 3306. Both log "mysqld: ready for connections", so the log cannot say which
|
|
13
|
+
# one is up. Wait on a TCP connection, which is what the loader needs.
|
|
14
|
+
wait_for_mysql() {
|
|
15
|
+
local counter=0
|
|
16
|
+
echo -n "Waiting for $CONTAINER_NAME ..."
|
|
17
|
+
until docker exec "$CONTAINER_NAME" mysqladmin ping --protocol=TCP -h127.0.0.1 -uroot --silent > /dev/null 2>&1
|
|
18
|
+
do
|
|
19
|
+
counter=$((counter+1))
|
|
20
|
+
# give up after 2 minutes
|
|
21
|
+
if [ $counter -eq 60 ]
|
|
22
|
+
then
|
|
23
|
+
echo
|
|
24
|
+
return 1
|
|
25
|
+
fi
|
|
26
|
+
echo -n .
|
|
27
|
+
sleep 2
|
|
28
|
+
done
|
|
29
|
+
echo
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
# Check for existing container
|
|
13
33
|
if docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1; then
|
|
14
34
|
if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME")" = "true" ]; then
|
|
@@ -17,39 +37,25 @@ if docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1; then
|
|
|
17
37
|
fi
|
|
18
38
|
echo "Restarting existing $CONTAINER_NAME container..."
|
|
19
39
|
docker start "$CONTAINER_NAME"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
if ! wait_for_mysql; then
|
|
41
|
+
docker logs "$CONTAINER_NAME" >& ./.tmp/mysql-malloy.logs
|
|
42
|
+
echo "MySQL did not restart successfully, check .tmp/mysql-malloy.logs"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
23
45
|
echo "MySQL running on port 3306"
|
|
24
46
|
exit 0
|
|
25
47
|
fi
|
|
26
48
|
|
|
27
|
-
docker run -p 3306:3306 -d
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
counter=$((counter+1))
|
|
36
|
-
# if doesn't start after 2 minutes, output logs and kill process
|
|
37
|
-
if [ $counter -eq 120 ]
|
|
38
|
-
then
|
|
39
|
-
docker logs "$CONTAINER_NAME" >& ./.tmp/mysql-malloy.logs
|
|
40
|
-
docker rm -f "$CONTAINER_NAME"
|
|
41
|
-
echo "MySQL did not start successfully, check .tmp/mysql-malloy.logs"
|
|
42
|
-
exit 1
|
|
43
|
-
break
|
|
44
|
-
fi
|
|
45
|
-
echo -n ...
|
|
46
|
-
done
|
|
49
|
+
docker run -p 3306:3306 -d --name "$CONTAINER_NAME" -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:8.4.2
|
|
50
|
+
|
|
51
|
+
if ! wait_for_mysql; then
|
|
52
|
+
docker logs "$CONTAINER_NAME" >& ./.tmp/mysql-malloy.logs
|
|
53
|
+
docker rm -f "$CONTAINER_NAME"
|
|
54
|
+
echo "MySQL did not start successfully, check .tmp/mysql-malloy.logs"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
47
57
|
|
|
48
|
-
# load the test data.
|
|
49
|
-
echo
|
|
50
58
|
echo Loading Test Data
|
|
51
|
-
|
|
52
|
-
docker exec "$CONTAINER_NAME" gunzip /tmp/malloytest.mysql.gz
|
|
53
|
-
docker exec "$CONTAINER_NAME" mysql -P3306 -h127.0.0.1 -uroot -e 'drop database if exists malloytest; create database malloytest; use malloytest; source /tmp/malloytest.mysql;'
|
|
59
|
+
MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 MYSQL_USER=root sh "$SCRIPTDIR/load_test_data.sh"
|
|
54
60
|
|
|
55
61
|
echo "MySQL running on port 3306"
|
package/package.json
CHANGED
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"malloyc": "ts-node ../scripts/malloy-to-json"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@malloydata/db-bigquery": "0.0.
|
|
24
|
-
"@malloydata/db-duckdb": "0.0.
|
|
25
|
-
"@malloydata/db-postgres": "0.0.
|
|
26
|
-
"@malloydata/db-snowflake": "0.0.
|
|
27
|
-
"@malloydata/db-trino": "0.0.
|
|
28
|
-
"@malloydata/malloy": "0.0.
|
|
29
|
-
"@malloydata/malloy-tag": "0.0.
|
|
30
|
-
"@malloydata/render": "0.0.
|
|
31
|
-
"@malloydata/render-validator": "0.0.
|
|
23
|
+
"@malloydata/db-bigquery": "0.0.434",
|
|
24
|
+
"@malloydata/db-duckdb": "0.0.434",
|
|
25
|
+
"@malloydata/db-postgres": "0.0.434",
|
|
26
|
+
"@malloydata/db-snowflake": "0.0.434",
|
|
27
|
+
"@malloydata/db-trino": "0.0.434",
|
|
28
|
+
"@malloydata/malloy": "0.0.434",
|
|
29
|
+
"@malloydata/malloy-tag": "0.0.434",
|
|
30
|
+
"@malloydata/render": "0.0.434",
|
|
31
|
+
"@malloydata/render-validator": "0.0.434",
|
|
32
32
|
"events": "^3.3.0",
|
|
33
33
|
"jsdom": "^22.1.0",
|
|
34
34
|
"luxon": "^3.7.2",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"overrides": {
|
|
43
43
|
"typescript": "^6.0.3"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.0.
|
|
45
|
+
"version": "0.0.434"
|
|
46
46
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#! /bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Load the malloytest tables into a running postgres from
|
|
4
|
+
# test/data/malloytest-parquet. Connects with the PG* variables.
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SCRIPTDIR=$(cd $(dirname $0); pwd)
|
|
9
|
+
ROOTDIR=$(cd "$SCRIPTDIR/../.."; pwd)
|
|
10
|
+
|
|
11
|
+
cd "$ROOTDIR"
|
|
12
|
+
npx ts-node test/postgres/load_test_data.ts
|