Rollbacks and recovery¶
Rolling back¶
Every generated migration has a rollback script in rollback/<same name>.sql. To undo the newest
migration:
$ migrax rollback --yes
Rolling back 1 migration(s), newest first:
0006_create_categories_seq_and_more.sql
Rolled back migration '0006_create_categories_seq_and_more.sql'.
| Option | Effect |
|---|---|
--steps 3 |
Roll back the three newest migrations |
--to 0004_add_index.sql |
Roll back everything after 0004_add_index.sql |
--dry-run |
List what would be rolled back |
--yes |
Required: confirms the change. Back up first. |
The rolled-back migrations become pending again, so migrax migrate re-applies them.
Structure, not data
A rollback restores the structure: re-creating a dropped column brings the column back, not its values. The header of each rollback script says what cannot be restored:
Hand-written migrations from migrax new get an empty rollback script for you to fill in. Java
migrations roll back through JavaMigration.rollback(Connection).
migrax verify proves the rollback scripts work: it rolls every migration back on a
throwaway database and applies them again (Production safety).
When a migration fails¶
A failed migration is recorded in migrax_failures, and migrate stops until you decide what
happened:
$ migrax status
[X] 0005_fix_naming.sql applied 2026-10-08 11:04:45
[!] 0006_create_categories_seq_and_more.sql FAILED: SQLSyntaxErrorException: Table 'categories_seq' already exists
6 applied, 1 failed.
Some migrations need attention. See 'migrax help repair'.
The message is the database's own error. Look at the database, then tell Migrax which case you are in:
Every statement in the migration is in the database (for example, you finished it by hand):
Keep a record of every repair. Never edit an applied migration or delete history rows by hand.
Migrations that are safe to re-run¶
A migration that starts with -- migrax:resume-safe can be re-run after a failure with
migrax migrate --resume, without a repair. Only mark migrations whose statements can run
twice (for example CREATE TABLE IF NOT EXISTS, idempotent updates).
A migration file went missing¶
status reports applied migrations whose file is gone, and migrate stops:
$ migrax migrate
error[MXE105]: Applied migration file(s) are missing from the configured migration location: 0001_initial.sql. Restore the files from version control; applied migrations must not be deleted. If you deleted them on purpose, remove them from the history with: migrax repair 0001_initial.sql --action forget --yes
Deleted by accident? Restore the file from git (git show <commit>:<path>), unchanged. This
is the right fix in almost every project: other environments and new databases still need it.
Deleted on purpose, for example to start a test database over? Remove the migrations from the history:
$ migrax repair 0001_initial.sql 0002_add_email.sql --action forget --yes
Removed 0001_initial.sql from the migration history.
Removed 0002_add_email.sql from the migration history.
The database keeps the changes these migrations made. Run 'migrax status' to check the history.
The tables and columns those migrations created stay in the database; only the record goes.
Migrax refuses to forget a migration whose file still exists, because migrate would run it
again.
Starting over with the current database as the baseline
To make the current database the starting point, forget the deleted migrations, delete
.migrax/snapshot.json and the .migrax/history folder, and run migrax generate: with no
snapshot, it compares the entities with the database. It writes the current tables to
0001_baseline.sql (recorded as applied, not run) and what is really different to a second
migration. Review that migration before running migrax migrate.
From then on the migrations start at the current database. On other databases that still have the old history, the baseline would try to create tables that exist, so for shared environments restore the deleted files instead.
Starting a development database over¶
migrax clean drops every table, view and sequence in the database, the migration history
included, so migrax migrate can build it again from the migrations:
$ migrax clean --dry-run
Would drop from jdbc:postgresql://localhost:5432/shop:
table customers
table migrax_history
...
$ migrax clean --yes
Dropped 5 table(s), 2 sequence(s) from jdbc:postgresql://localhost:5432/shop.
Run 'migrax migrate' to rebuild the schema from the migrations.
$ migrax migrate
All data is gone afterwards, so use it only on development and test databases. On servers
where it must never run, set MIGRAX_CLEAN_DISABLED=true: clean then refuses.