Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit2397b12unknown_key

fix(migrate): handle multi-statement SQL files for Neon HTTP driver

fix(migrate): handle multi-statement SQL files for Neon HTTP driver

Tonight's `fly deploy` failed at the release_command step because the
migration runner sent the entire `0000_init.sql` file as a single
prepared statement to Neon, which rejected it with:

  cannot insert multiple commands into a prepared statement

Root cause: the splitter only knew Drizzle's
`--> statement-breakpoint` marker. Hand-written migrations like
`0000_init.sql` (which has multiple `CREATE TABLE` blocks separated
by `;`) had no markers, so the splitter returned the whole file as
one statement.

Fix: extract `splitMigrationStatements` and add a fallback path:
when the drizzle marker isn't present, split on `;` followed by a
newline (after stripping comment-only lines so a `;` inside `--`
doesn't trigger a false split). Each statement is then sent
individually, which Neon accepts. The existing "already exists"
catch in the apply loop continues to swallow benign errors when a
hand-written CREATE TABLE collides with the live schema — so this
is also the path that lets a host with a pre-existing schema (like
production today, where the schema was set up manually before
`_migrations` existed) reconcile and start tracking properly.

This unblocks `fly deploy` to ship the SW loop fix +
self-bootstrap + emergency-PAT API.

Tests still 1994 pass / 0 fail / 2 skip; typecheck clean.

https://claude.ai/code/session_01QFLWDxWw65DX6enMcS5Lwe
Test User committed on May 16, 2026Parent: 96942a6
1 file changed+42142397b12620b04fb6a3215940caa33aeb261325f0
1 changed file+42−14
Modifiedsrc/db/migrate.ts+42−14View fileUnifiedSplit
1515
1616type Exec = (query: string, params?: unknown[]) => Promise<unknown>;
1717
18/**
19 * Split a migration file into individual SQL statements.
20 *
21 * Two formats are supported:
22 * 1. Drizzle-generated migrations use `--> statement-breakpoint` markers
23 * between statements. Detect and split on those.
24 * 2. Hand-written migrations (like `0000_init.sql`) just have multiple
25 * `CREATE TABLE` etc statements separated by `;`. Detect when the
26 * drizzle marker isn't present and fall back to a semicolon split
27 * that's safe for our schemas (no PL/pgSQL `$$...$$` blocks today).
28 *
29 * Comment-only lines (`--`) are stripped before splitting so a `;`
30 * inside a comment doesn't trigger a false split. Empty fragments are
31 * dropped. Trailing semicolons are removed.
32 */
33function splitMigrationStatements(content: string): string[] {
34 if (/-->\s*statement-breakpoint/.test(content)) {
35 return content
36 .split(/-->\s*statement-breakpoint/)
37 .map((s) =>
38 s
39 .split("\n")
40 .filter((line) => !line.trim().startsWith("--"))
41 .join("\n")
42 .trim()
43 )
44 .filter((s) => s.length > 0);
45 }
46 // No breakpoint markers — split on `;` at end of (logical) line. Strip
47 // pure-comment lines first.
48 const stripped = content
49 .split("\n")
50 .filter((line) => !line.trim().startsWith("--"))
51 .join("\n");
52 return stripped
53 .split(/;\s*\n/)
54 .map((s) => s.trim().replace(/;\s*$/, "").trim())
55 .filter((s) => s.length > 0);
56}
57
1858async function runMigrations() {
1959 if (!config.databaseUrl) {
2060 throw new Error("DATABASE_URL is not set");
70110
71111 console.log(`[migrate] applying ${file}...`);
72112 const content = await readFile(join(migrationsDir, file), "utf8");
113 const statements = splitMigrationStatements(content);
73114
74 // Split on the drizzle breakpoint marker. Each chunk is one statement.
75 const statements = content
76 .split(/-->\s*statement-breakpoint/)
77 .map((s) => s.trim())
78 .filter((s) => s.length > 0);
79
80 for (const raw of statements) {
81 const stripped = raw
82 .split("\n")
83 .filter((line) => !line.trim().startsWith("--"))
84 .join("\n")
85 .trim();
86 if (!stripped) continue;
87
115 for (const stripped of statements) {
88116 try {
89117 await exec(stripped);
90118 } catch (err) {
91119