Skip to content

Commit aa04dbe

Browse files
committed
test(reporting): share one statement splitter across the SQLite, D1 and MySQL lanes; key the D1 binding per server
The SQLite and D1 lanes split a script on every ';', so a seed value holding one would break there and pass on MySQL. All three lanes now use the end-of-line splitter from sql-script.ts. The D1 api-contract server hands its binding to the generated db module through a per-server global key instead of one shared name.
1 parent c6ad00c commit aa04dbe

5 files changed

Lines changed: 27 additions & 20 deletions

File tree

‎server/typescript/packages/integration-tests/src/api-contract-report-sqlite-server.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// EMITTED <Report>.routes.ts files unmodified and mount them.
1313

1414
import Fastify, { type FastifyInstance } from "fastify";
15+
import { randomUUID } from "node:crypto";
1516
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
1617
import { dirname, join } from "node:path";
1718
import { fileURLToPath, pathToFileURL } from "node:url";
@@ -161,17 +162,19 @@ export async function startSqliteReportServer(
161162
async function openEngine(engine: SqliteReportEngine, tmp: string): Promise<EngineClient> {
162163
if (engine === "d1") {
163164
const local = await startLocalD1();
164-
(globalThis as { __reportD1?: unknown }).__reportD1 = local.d1;
165+
// One key per server, so two servers in flight never read each other's binding.
166+
const key = `__reportD1_${randomUUID().replaceAll("-", "")}`;
167+
(globalThis as Record<string, unknown>)[key] = local.d1;
165168
writeFileSync(join(tmp, "db.ts"), `
166169
import { drizzle } from "drizzle-orm/d1";
167-
export const db = drizzle((globalThis as { __reportD1?: never }).__reportD1 as never);
170+
export const db = drizzle((globalThis as Record<string, never>)[${JSON.stringify(key)}]);
168171
`, "utf8");
169172
return {
170173
run: async (sql, params = []) => {
171174
await local.d1.prepare(sql).bind(...params).all();
172175
},
173176
close: async () => {
174-
delete (globalThis as { __reportD1?: unknown }).__reportD1;
177+
delete (globalThis as Record<string, unknown>)[key];
175178
await local.dispose();
176179
},
177180
};

‎server/typescript/packages/integration-tests/src/query-scenario-d1.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { CANONICAL_COLUMN_NAMING, generateCanonicalSchemaSql } from "./canonical
1515
import { d1Dialect, type D1Like } from "./d1-kysely.ts";
1616
import { loadMetadataDir } from "./load-metadata.ts";
1717
import { runScenarioQueries } from "./query-scenario.ts";
18-
import { stripSqlComments } from "./query-scenario-sqlite.ts";
18+
import { splitStatements } from "./sql-script.ts";
1919
import type { QueryScenario } from "./scenario.ts";
2020

2121
interface D1Database extends D1Like {
@@ -43,7 +43,7 @@ export async function startLocalD1(): Promise<LocalD1> {
4343

4444
/** D1's `exec` runs one statement per line; fold each statement onto one line first. */
4545
export async function execScript(d1: D1Database, script: string): Promise<void> {
46-
for (const stmt of stripSqlComments(script).split(";").map((s) => s.trim()).filter(Boolean)) {
46+
for (const stmt of splitStatements(script)) {
4747
await d1.exec(stmt.replace(/\s*\n\s*/g, " "));
4848
}
4949
}

‎server/typescript/packages/integration-tests/src/query-scenario-mysql.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import { Kysely, MysqlDialect } from "kysely";
1818
import { createPool } from "mysql2";
1919
import mysql from "mysql2/promise";
2020

21+
import { splitStatements } from "./sql-script.ts";
22+
23+
export { splitStatements };
24+
2125
import { CANONICAL_COLUMN_NAMING } from "./canonical-schema.ts";
2226
import { readCanonicalMysqlSchemaSql } from "./canonical-schema-mysql.ts";
2327
import { loadMetadataDir } from "./load-metadata.ts";
@@ -55,13 +59,6 @@ export function toMysqlSeed(seed: string): string {
5559
return out.replace(/'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)Z'/g, "'$1'");
5660
}
5761

58-
/** The statements of a script: split on `;` at the end of a line (no body carries one mid-line). */
59-
export function splitStatements(script: string): string[] {
60-
return script
61-
.split("\n").filter((l) => !l.trim().startsWith("--")).join("\n")
62-
.split(/;[ \t]*(?:\n|$)/).map((s) => s.trim()).filter(Boolean);
63-
}
64-
6562
/** Every table and view the schema artifact creates, dropped first so a rerun starts clean. */
6663
export async function resetSchema(conn: mysql.Connection, schemaSql: string): Promise<void> {
6764
const statements = splitStatements(schemaSql);

‎server/typescript/packages/integration-tests/src/query-scenario-sqlite.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,16 @@ import { join } from "node:path";
1919
import { CANONICAL_COLUMN_NAMING, readCanonicalSchemaSql } from "./canonical-schema.ts";
2020
import { loadMetadataDir } from "./load-metadata.ts";
2121
import { runScenarioQueries } from "./query-scenario.ts";
22+
import { splitStatements } from "./sql-script.ts";
2223
import type { QueryScenario } from "./scenario.ts";
2324

24-
/** libsql executes one statement per call; no schema or seed statement carries an inner `;`. */
25+
/** libsql executes one statement per call. */
2526
export async function applySqlScript(db: Kysely<never>, script: string): Promise<void> {
26-
for (const stmt of script.split(";").map((s) => s.trim()).filter(Boolean)) {
27+
for (const stmt of splitStatements(script)) {
2728
await sql.raw(stmt).execute(db);
2829
}
2930
}
3031

31-
/** `sql` is `--` comment lines + statements; drop the comments before splitting on `;`. */
32-
export function stripSqlComments(script: string): string {
33-
return script.split("\n").filter((l) => !l.trim().startsWith("--")).join("\n");
34-
}
35-
3632
export async function runQueryScenarioSqlite(
3733
scenario: QueryScenario,
3834
canonicalDir: string,
@@ -41,7 +37,7 @@ export async function runQueryScenarioSqlite(
4137
const dir = mkdtempSync(join(tmpdir(), "query-scenario-sqlite-"));
4238
const kysely = new Kysely<never>({ dialect: new LibsqlDialect({ url: `file:${join(dir, "test.db")}` }) });
4339
try {
44-
await applySqlScript(kysely, stripSqlComments(schemaSql));
40+
await applySqlScript(kysely, schemaSql);
4541
const seed = scenario.seedDataEngine?.sqlite ?? scenario.seedData;
4642
if (seed && seed.trim().length > 0) await applySqlScript(kysely, seed);
4743

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// sql-script.ts — split a committed SQL script (the schema artifacts, a scenario's seed) into
2+
// statements for a driver that runs one statement per call.
3+
//
4+
// A `;` ends a statement only at the end of a line, so one inside a string literal's data (a
5+
// title, a JSON payload) does not break it. `--` comment lines are dropped first.
6+
7+
export function splitStatements(script: string): string[] {
8+
return script
9+
.split("\n").filter((l) => !l.trim().startsWith("--")).join("\n")
10+
.split(/;[ \t]*(?:\n|$)/).map((s) => s.trim()).filter(Boolean);
11+
}

0 commit comments

Comments
 (0)