fix(hub): dollar-quote-aware SQL migration splitter#63
Merged
Conversation
The previous splitter in hub/src/db/migrate.ts naively split schema.sql on
`;`, ignoring PostgreSQL dollar-quoted string syntax. Every `DO $$ BEGIN ...
EXCEPTION WHEN ... END $$;` block got split mid-string and rejected by
Postgres. Production logs showed 7/142 statements failing on boot, leaving
two CHECK constraints (`users_claude_session_threshold_pct_range`,
`users_claude_week_threshold_pct_range`) and a re-added
`scheduled_task_runs_status_check` constraint un-applied.
Fix: replace `split(';')` with a small state-machine splitter
`splitSqlStatements()` that respects:
- $$...$$ and tagged $tag$...$tag$ dollar-quoted strings
- single-quoted strings with '' escape
- double-quoted identifiers with "" escape
- line comments (-- to newline)
- block comments (nested per Postgres semantics)
- bare positional params like $1 (not a dollar-quote tag)
schema.sql is unchanged — the DO blocks are valid SQL; only the parser was
wrong.
Tests: 13 new unit tests in hub/test/migrate.test.ts covering each shape,
plus a real-file assertion that every DO block in the live schema.sql now
parses to a single statement with balanced $$ tokens.
Note (separate concern, NOT fixed here): runMigrations() still logs failed
statements and continues rather than aborting boot. That latent
silent-failure is what allowed this bug to live in production for weeks
without an obvious crash signal. Worth a follow-up PR to fail loudly on
migration error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
hub/src/db/migrate.tssplitschema.sqlon;without respecting PostgreSQL's\$\$...\$\$dollar-quoted string syntax. EveryDO \$\$ BEGIN ... EXCEPTION WHEN ... END \$\$;block got split mid-string. Production boot logs:Net effect: 7/142 statements failed. Two CHECK constraints (
users_claude_session_threshold_pct_range,users_claude_week_threshold_pct_range) and the re-addedscheduled_task_runs_status_checknever landed.Fix
Replace naive
split(';')with a state-machine splittersplitSqlStatements()that respects:\$\$ ... \$\$and tagged\$tag\$ ... \$tag\$dollar-quoted strings'...') with''escape"...") with""escape-- ... \n)/* ... */), nested per Postgres semantics\$1(NOT a dollar-quote tag)schema.sqlis not modified — the DO blocks are valid SQL; only the parser was wrong.Test coverage
13 new unit tests in
hub/test/migrate.test.ts:\$body\$ ... \$body\$with embedded;in a string literal;''inside string literal;;;\$1positional param not treated as a dollar quoteschema.sqlnow parses to a single statement with balanced\$\$token countsAll 13 pass. No regressions in the rest of the hub test suite (5 pre-existing failures in
insert-run-started-at.test.tsare unrelated and exist onmainHEAD prior to this branch).Follow-up flagged (NOT in this PR)
runMigrations()currently catches per-statement errors and continues, only logging them. That silent-failure mode is what let this bug live in production. Worth a separate PR to abort boot on any migration error — but out of scope here per the task brief.Verification
bun test test/migrate.test.ts→ 13/13 passbun test(full hub suite) → 295 pass, 63 skip, 5 pre-existing unrelated fails🤖 Generated with Claude Code