Snapshot fabro.sqlite3 before applying new migrations#585
Merged
Conversation
A binary downgrade after new SQLite migrations have been applied fails
sqlx's startup validation ("migration was previously applied but is
missing in the resolved migrations") and previously left the operator
with no rollback artifact: the shared database had no backup, so
recovering meant hand-editing _sqlx_migrations and dropping tables.
Database::migrate now writes a consistent single-file snapshot to
<db>.pre-migration.bak (via VACUUM INTO, mode 0600) before applying any
migration the database has not seen. Rollback is: stop the server,
replace the database file with the snapshot, delete -wal/-shm siblings,
start the previous binary. Fresh databases and no-op migrates skip the
snapshot, so the file always preserves the state from immediately before
the most recent schema change. A snapshot failure fails the migration:
no rollback artifact, no schema change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.
Tip: disable this comment in your organization's Code Review settings.
- Detect applied migrations via sqlx's Migrate trait (ensure_migrations_table + list_applied_migrations) instead of hand-querying the _sqlx_migrations bookkeeping table, so the check cannot drift from what Migrator::run actually applies. - Write the snapshot to a staging file and rename it into place, so a failure mid-copy never leaves a partial file at the snapshot path. - Derive the database path from the pool's connect options instead of storing a duplicate copy on Database. - Drop the invented "fabro.sqlite3" fallback filename from pre_migration_snapshot_path; append the suffix to the path directly. - Deduplicate the snapshot-inspection blocks in the test behind small connect_read_only/table_exists helpers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
Ran a four-angle cleanup review (reuse, simplification, efficiency, altitude) over this PR and applied the surviving findings in f97ac8d. Applied
Noted but not changed
🤖 Generated with Claude Code |
This was referenced Jul 22, 2026
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.
Summary
Database::migratenow writes a rollback snapshot to<db>.pre-migration.bakbefore applying any migration the database has not seen yet.Why
A binary downgrade after new SQLite migrations have been applied fails sqlx's startup validation outright —
migration N was previously applied but is missing in the resolved migrations— before the server reads anything else. Until now there was no rollback artifact for the shared database: the file-based legacy imports rename their sources to.bak, but the database itself had no equivalent, so recovering from a bad upgrade meant hand-deleting_sqlx_migrationsrows and dropping tables.This matters right now because #570/#571/#572/#573 each add new migrations to the shared
fabro.sqlite3(which already holds variables and environments on existing deployments). With this change merged first, the release that ships those migrations automatically leaves every upgraded deployment a one-file rollback path.Behavior
VACUUM INTOfrom the live pool: a consistent single-file copy, no-wal/-shmsiblings needed to restore. Mode 0600.fabro.sqlite3with the snapshot, delete any-wal/-shmfiles, start the previous binary. Writes made after the upgrade are lost, as with any point-in-time restore.Testing
cargo nextest run -p fabro-db -p fabro-environment -p fabro-variable -p fabro-server: 771/774 pass locally; the 3 failures are the graphviz render tests (nodoton this machine) and fail identically on main.-D warningsandfmt --checkclean.🤖 Generated with Claude Code