fix(selfhost): translate SQLite AUTOINCREMENT so migrations apply on Postgres - #10143
Merged
Conversation
…Postgres
Migration 0209 declares `id INTEGER PRIMARY KEY AUTOINCREMENT`. Postgres
cannot parse AUTOINCREMENT (SQLSTATE 42601), so runSelfHostMigrations aborted
and the process exited: every Orb upgrading past 0208 crash-looped on boot.
Reproduced deploying orb-v3.7.0-beta.6 to edge-nl-01; rolled back to beta.5.
No data damage -- migrations apply in a transaction, so the table was never
created and the ledger still ends at 0208.
Migrations are authored in SQLite dialect and translated by translateDdl,
which covers SQLite date/json functions and INSERT OR IGNORE. Its doc comment
records the assumption behind the gap -- "column types are PG-native" -- which
is true of types, but AUTOINCREMENT is a CONSTRAINT. 0209 is the only
migration that uses it; every other table uses plain INTEGER PRIMARY KEY,
which parses on both dialects, so the gap stayed latent.
Stripping the keyword would be worse than leaving it. Bare INTEGER PRIMARY KEY
auto-assigns in SQLite but is an ordinary NOT NULL integer in Postgres, so the
DDL would parse and every id-omitting insert would fail at runtime instead --
silently, since the writer is best effort and swallows errors. It is rendered
as a Postgres identity column instead: BY DEFAULT rather than ALWAYS, because
SQLite permits an explicit id, and BIGINT to match the 64-bit rowid.
The migration file is deliberately untouched. contentSha256 hashes the raw
source, so editing it would throw selfhost_migration_content_drift on every
Orb that already applied it -- and on D1 the SQLite syntax is valid.
The real-Postgres suite already catches this ("applies every migration") but
only runs in selfhost.yml, which is push-to-main only: it went red on the very
commit that introduced 0209 and stayed red across five runs while PRs kept
merging. The pre-merge counterpart added here applies translateDdl to every
migration and fails if a SQLite-only construct survives -- no Postgres, no
service container, ~11ms inside the unit run that already happens, because CI
runtime is the constraint that moved that workflow post-merge to begin with.
Checked post-translation rather than against a banned-string list so it cannot
drift from the translator, and comment-stripped so the many migrations that
merely DISCUSS these constructs are not flagged.
selfhost.yml's header claimed ci.yml validated the pg integration suites
pre-merge. It does not and cannot -- there is no service container, so without
PG_TEST_URL they skip. Corrected in place.
Closes #10138
Contributor
|
Important 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏳ LoopOver is waiting…LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting |
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #10143 +/- ##
=======================================
Coverage 91.99% 91.99%
=======================================
Files 931 931
Lines 114000 114001 +1
Branches 27523 27523
=======================================
+ Hits 104877 104878 +1
Misses 7823 7823
Partials 1300 1300
Flags with carried forward coverage won't be shown. Click here to find out more.
|
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.
Closes #10138
P0 — this blocked every Orb upgrade past 0208. Found by deploying
orb-v3.7.0-beta.6to edge-nl-01: the container crash-looped, rolled back toorb-v3.7.0-beta.5, healthy. ~90s of downtime.migrations/0209_service_status_samples.sql:21declaresid INTEGER PRIMARY KEY AUTOINCREMENT. Postgres cannot parse it,runSelfHostMigrationsthrows, the process exits.No data damage. Migrations apply inside a transaction —
service_status_sampleswas never created and_selfhost_migrationsstill ends at0208. It failed closed, which is the one good thing about it.Why the dialect layer missed it
Migrations are authored in SQLite dialect and translated by
translateDdl(pg-dialect.ts), applied atpg-adapter.ts:138/150. It handles SQLite date/json functions andINSERT OR IGNORE. Its own doc comment states the assumption that left the hole:True of types — but
AUTOINCREMENTis a constraint.0209is the only migration in the repo that uses it; every other table uses plainINTEGER PRIMARY KEY, which parses on both dialects, so the gap stayed latent until someone wrote one.Three fixes considered, two rejected
Strip the keyword — rejected, strictly worse. Bare
INTEGER PRIMARY KEYauto-assigns in SQLite (it aliases the rowid) but is an ordinaryNOT NULLinteger in Postgres. The DDL would parse, then every id-omitting insert would fail at runtime — andrecordServiceStatusSamplesis best-effort and swallows errors, so the status board would silently record nothing. Trading a loud boot failure for silent data loss is not a fix. The mutation test for this variant fails 8 assertions.Edit the migration — rejected, would cause a second outage.
contentSha256hashes the raw source file (migrate.ts:152), so changing 0209's bytes throwsselfhost_migration_content_drifton every Orb that already applied it. On D1 the SQLite syntax is also perfectly valid. Shipped migrations are immutable for exactly this reason.Translate it — taken.
BY DEFAULTnotALWAYS: SQLite permits an explicit id andALWAYSwould reject one, turning a legal insert into a Postgres-only runtime error — precisely the dialect-dependent divergence this layer exists to prevent.BIGINTmatches SQLite's 64-bit rowid rather than narrowing to int4.The real gap, and why this is not a new CI job
The real-Postgres suite already catches this —
test/integration/selfhost-pg.test.ts, "applies every migration". It ran on the very commit that introduced 0209 (a7673e24, #9990) and failed correctly. But it only runs inselfhost.yml, which is push-to-main only, so the signal landed after the merge. It then stayed red across five consecutive runs while PRs kept merging, because a post-merge failure blocks nothing and pages no one.The obvious fix — a Postgres service job in
ci.yml— is the wrong tool here: CI runtime is already the binding constraint (26k+ tests per PR), and runtime cost is exactly what movedselfhost.ymlpost-merge in the first place. Adding back what was deliberately removed would be trading one problem for the one it was created to solve.So the pre-merge guard is
test/unit/migration-dialect-portability.test.ts: appliestranslateDdlto every migration, fails if a SQLite-only construct survives. No Postgres, no service container, no new job — 13 tests in ~11ms inside the unit run that already happens.Two design points that make it hold up:
translateDdl, so adding a translation automatically satisfies it and removing one automatically fails it. A hand-maintained banned-string list would be a second source of truth that drifts on the first change.0054and0176documentINSERT OR REPLACEin header comments, and0180annotates a column "NOT autoincrement". All three run fine on Postgres today. A checker that cries wolf gets muted.It proves parseable, not correct;
selfhost.ymlremains the only real-Postgres coverage and its red is worth acting on. That is now said in its header, which previously claimedci.ymlvalidated the pg suites pre-merge — it does not and cannot, since there is no service container and they skip withoutPG_TEST_URL.Verification
typecheck/dead-exports/dead-source/actionlintgreen.translateDdl(the outage)GENERATED ALWAYSinstead ofBY DEFAULTPlus a self-test asserting the check still fires on real DDL and is exempt only inside comments — so comment-stripping cannot silently neuter it.