Does Persistent support atomic rollback for destructive schema migrations
0 reputation · 01 Nov 2023, 09:41 UTC
0 reputation · 01 Nov 2023, 09:41 UTC
The Persistent library uses runMigration to synchronize database state with schema definitions written in its Haskell DSL. Additive changes are generally handled, but the behavior for destructive operations such as column renames or deletions is less clear.
A key concern is that the library does not natively generate reversible DDL scripts. When a column is renamed in the DSL, Persistent may interpret the change as a drop followed by an add. That sequence creates a risk of data loss if the migration fails partway through or if a manual rollback is required, since schema evolution itself may not be wrapped in a transaction.
Is there a mechanism within Persistent to ensure schema changes are rolled back if the migration fails? How can developers avoid data loss during renames without moving entirely to external SQL migration tools?
31775 reputation · 01 Nov 2023, 12:02 UTC
No. Persistent’s runMigration runs each individual migration step in a transaction, but it does not treat an entire migration file (or a destructive change such as a column rename) as a single atomic unit. A rename is translated into a DROP COLUMN followed by an ADD COLUMN, and the generated DDL is not wrapped in a transaction. If the migration aborts mid‑way, the schema can be left in a partially altered state.
withTransaction block to achieve atomicity. SQLite and many MySQL storage engines do not support transactional DDL, so even a manual transaction cannot protect you there.runMigration $ do
withTransaction $ do
-- manual DDL for rename
execute_ "ALTER TABLE users RENAME COLUMN old_name TO new_name"
sql-migrate or flyway) that produces up/down scripts, then apply those scripts outside of Persistent.runMigration, then abort the Haskell process (or throw an exception) and inspect whether the old column still exists.withTransaction wrapper and confirm both DDL statements roll back.Which database backend are you targeting? If it’s PostgreSQL, a manual transaction can give you atomic rollback; if it’s SQLite or MySQL, you must rely on backups or an external migration framework.
Use comments to ask for clarification. Post a solution as an answer.
31,775 reputation · 01 Nov 2023, 21:29 UTC
While runMigration executes each MigrationStep in its own transaction, you can group a drop‑and‑add pair (as generated for a column rename) into a single transaction by supplying a custom Migration that uses withTransaction. This works only on backends that support transactional DDL (e.g., PostgreSQL). Example:
runMigration $ do
withTransaction $ do
execute_ "ALTER TABLE users RENAME COLUMN old_name TO new_name"
If the backend does not support transactional DDL (SQLite, many MySQL engines), the manual transaction cannot prevent a partially applied schema, so you must rely on external migration tools or manual backups.