Skip to content

fix(migrations): adopt sqlx reversible up/down format and revert tooling (#58) - #63

Open
heymide wants to merge 1 commit into
StellarSend:mainfrom
heymide:fix/58-reversible-migrations
Open

fix(migrations): adopt sqlx reversible up/down format and revert tooling (#58)#63
heymide wants to merge 1 commit into
StellarSend:mainfrom
heymide:fix/58-reversible-migrations

Conversation

@heymide

@heymide heymide commented Aug 17, 2026

Copy link
Copy Markdown

Summary

Fixes #58 — adopts sqlx's reversible migration format for the whole migrations/ directory, adds a .down.sql for every migration, documents the one intentionally-irreversible piece (migration 010's enum ADD VALUE) with an operator runbook, and wires sqlx migrate revert into the developer tooling with a CI smoke test that proves it works.

What changed

Migrations — reversible format, backward-compatible retrofit

  • Renamed NNN_name.sqlNNN_name.up.sql for all 11 migrations. The SQL payload is byte-for-byte unchanged (git reports 100% similarity renames, 0 insertions/deletions).
  • Added NNN_name.down.sql for all 11 migrations. 001–009 and 011 are clean, unconditional reversals (drop table/index/column/type in correct dependency order). Two index-name collisions across migrations (002/005 idx_users_stellar_address, 002/010 idx_transactions_stellar_tx_hash) are handled by ownership: the later migrations' statements were runtime no-ops, so their down scripts do not drop those indexes (documented inline).
  • Migration 010 is the special case. Its ALTER TYPE transaction_status ADD VALUE 'submitted_unconfirmed' is irreversible — Postgres has no ALTER TYPE … DROP VALUE. Its .down.sql reverses the reversible parts (drops batch_submissions, restores the UNIQUE constraint — which intentionally fails loudly if batch legs share a hash, aborting the revert transaction safely) and carries a full operator runbook for removing the orphan enum value (create-new-type → migrate columns via status::text cast → swap → drop old type). The runbook lives in the .down.sql header because editing the .up.sql would change its content checksum and break live databases (see below).

Why the retrofit is safe on already-applied databases

sqlx checksums a reversible migration over its .up.sql file alone (Migration::newsha384(sql) in sqlx-core/src/migrate/migration.rs), and records that checksum on apply. Since the up payload is byte-identical to the old minimal .sql file, the checksums sqlx recorded before this change are preserved. Verified against a real database: after applying all migrations with the old format, running sqlx migrate run against the same DB with the new files is a clean no-op (no VersionMismatch).

Tooling

  • scripts/migrate-revert-smoke.sh — applies all migrations to a fresh disposable DB, reverts 011 → 010 → 009, asserts the schema matches the pre-migration state at each step (via psql), re-applies (idempotency), and tears down.
  • .github/workflows/migrations.yml — runs the smoke test against a disposable Postgres 16 service container on every PR/push to main (scoped to migrations only; general CI remains issue No CI workflow configured (missing .github/workflows) #6).
  • README.md — documents the .up.sql/.down.sql convention, sqlx migrate add -r, the run/revert/info commands, the migration-010 caveat, and the smoke test.

Verification (run locally against PostgreSQL 17)

Before (baseline): sqlx migrate revertNo migrations available to revert.

After:

  • sqlx migrate run on a fresh DB: all 11 applied.
  • sqlx migrate run on the already-applied (old-format) DB: clean no-op — checksums preserved.
  • sqlx migrate revert × 3 (011 → 010 → 009): each reverts, sqlx migrate info flips the version to pending, and psql asserts the schema returned to the pre-migration state — including the documented behavior that 010's enum value survives its revert.
  • Full cascade revert 011 → 001 then re-apply: all 11 down scripts execute, then all 11 re-apply cleanly (idempotent).
  • scripts/migrate-revert-smoke.sh: PASS (full output in the branch).

Acceptance criteria

  • New migrations use the reversible .up.sql/.down.sql pair format (repo-wide now; sqlx migrate add -r documented).
  • Existing cleanly-revertible migrations have .down.sql scripts.
  • Migration 010 documents why it can't be simply reverted + the actual recovery procedure.
  • sqlx migrate revert demonstrated against a fresh test database (smoke script, CI workflow).

Notes / out of scope

  • No Rust code changed — sqlx::migrate!("./migrations") in src/db.rs already supports reversible migrations for forward application.
  • General CI (build/lint/test) is tracked separately in No CI workflow configured (missing .github/workflows) #6; this workflow is strictly the migration-revert smoke test.
  • Retrofitting .down.sql for already-applied environments is safe because checksums are preserved (verified above); no _sqlx_migrations reconciliation is required.

@heymide

heymide commented Aug 17, 2026

Copy link
Copy Markdown
Author

Note on CI: the new \migrations.yml\ workflow won't report a check on this PR itself — GitHub only executes workflows that already exist on the default branch, and this is a new file coming from a fork. It will run on \main\ after merge (\on: push). The smoke test has been run and passed locally against PostgreSQL 17 in the meantime (see Verification above).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No down/rollback migrations exist — migration 010's enum ADD VALUE is not even revertible in Postgres

2 participants