feat(multiplayer): add down migrations and a rollback runner

Add migrations.Rollback(ctx, db, dir, steps): reverses the N most
recently applied migrations, newest first, each in its own committed
transaction under the same advisory lock Apply uses. Down SQL lives in
migrations/down/<version>.sql (a subdirectory, so Apply's *.sql glob
over the main directory is untouched); a missing down file for a
migration being rolled back is a hard error rather than a silent
partial reversal. Wire it into cmd/migrate as --rollback=N.

Add down files for all six existing migrations, each dropping objects
in FK-safe reverse dependency order.

Adversarial review: could not run the new integration test
(TestPostgreSQLMigrationsRollBackAndReapplyCleanly, gated behind
COSMIC_CLASH_POSTGRES_DSN / scripts/run_postgres_integration.sh)
against a real database in this sandbox - Docker Desktop's own
overlayfs ran out of space pulling postgres:17-alpine, unrelated to
this change. Verified instead by hand-tracing every DROP against its
forward migration's FK graph, confirming Apply's directory glob does
not pick up the down/ subdirectory, and a clean go build/vet/test
-tags integration. Worth an explicit real run before this is trusted
in CI.
This commit is contained in:
Josh Creek
2026-09-01 12:41:30 +01:00
parent 7e5cfdeceb
commit 67609d71c0
10 changed files with 191 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
-- Down migration for 0001_initial.sql. Tables drop in FK-safe reverse
-- dependency order (a child table always drops before anything it
-- references); dropping a table drops its own indexes with it.
DROP TABLE IF EXISTS audit_events;
DROP TABLE IF EXISTS outbox;
DROP TABLE IF EXISTS result_receipts;
DROP TABLE IF EXISTS penalties;
DROP TABLE IF EXISTS ranked_season_rollovers;
DROP TABLE IF EXISTS seasons;
DROP TABLE IF EXISTS ratings;
DROP TABLE IF EXISTS match_participants;
DROP TABLE IF EXISTS matches;
DROP TABLE IF EXISTS proposal_participants;
DROP TABLE IF EXISTS proposals;
DROP TABLE IF EXISTS queue_tickets;
DROP TABLE IF EXISTS idempotency_keys;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS identities;