Run each migration and its bookkeeping row in one transaction #49
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The hazard
db.rs::run_if_pendingexecutes a migration's statements and then, as a separate query, inserts its row into_migrations. There is no transaction around the pair.If the process dies between the two, the schema change is applied but unrecorded. Every subsequent start replays the migration and fails — for a bare
ALTER TABLE … ADD COLUMNthat isduplicate column name: <col>, which needs manual SQL against the database to recover. ER will not boot in the meantime.Not a regression
Migrations 003 through 011 are all bare
ALTERs with exactly the same exposure. 013 only widens the window slightly by carrying three statements where the others carry one; itsCREATE TABLE IF NOT EXISTSandDROP TABLE IF EXISTSare self-guarding, and theALTERis not. Idempotency as actually invoked is fine — therun_if_pendinggate means each runs once on a healthy path.So this is a pre-existing robustness gap found while reviewing 013, not something that branch introduced, which is why it is filed rather than fixed in the release.
Fix
Wrap the statements and the
_migrationsinsert in a single transaction. SQLite supports transactional DDL, includingALTER TABLE ADD COLUMN, so this works for every migration already written and needs no per-migration changes.Worth doing at the same time: a migration that fails partway currently leaves whatever its earlier statements did. With a transaction the whole file rolls back, which makes a failed migration diagnosable instead of leaving a half-shaped schema.
Test to write first
Hard to test the crash window directly. The testable property is the one that matters: applying a migration whose later statement fails must leave the schema untouched and
_migrationswithout its row, so the next start retries cleanly rather than finding a half-applied schema.