Making dbm-rollback safe after destructive schema changes in the Grails database-migration plugin
0 reputation · 02 Nov 2022, 11:53 UTC
0 reputation · 02 Nov 2022, 11:53 UTC
Our Grails application uses the database-migration plugin (Liquibase-based) with changelogs under grails-app/migrations, and we are standardizing how schema changes ship to production. The goal is a rollback story that actually works when a release must be reverted, not just one that exists on paper.
The uncertainty is around destructive changeSets. Liquibase can auto-generate rollback for additive changes like createTable or addColumn, but dropColumn, dropTable, and raw sql changeSets either need explicit rollback blocks or cannot restore dropped data at all. We also generate initial drafts with dbm-gorm-diff, and those diffs appear to lack rollback definitions by default. Additionally, some environments still run with dbCreate set to update, which may let Hibernate alter the schema outside Liquibase's tracking and make rollback ordering unreliable.
Assume a recent Grails 5/6 line with the matching database-migration plugin; exact plugin version compatibility still needs confirmation against the documented matrix.
Specific questions:
33275 reputation · 02 Nov 2022, 22:40 UTC
<rollback> block that recreates the object. However, the block only restores the schema – the data that was stored in the dropped column or table is lost unless you have an external backup. Because of that, the industry‑standard “expand‑and‑contract” pattern (add a new table/column, move data, drop the old object in a later release) is still the safest way to guarantee a full rollback.
dbCreate=update lets Hibernate touch the schema outside Liquibase’s control, which can reorder columns, add missing constraints, or even drop objects that Liquibase expects to exist. During a rollback, such changes can cause Liquibase to fail because the DATABASECHANGELOG table no longer matches the actual schema. Switching to none (or validate if you want Hibernate to throw on mismatches) guarantees that only Liquibase moves the schema.
dropTable or dropColumn, the typical workflow is:
DATABASECHANGELOG table and re‑run the migrations to bring the checksum state back into sync.DATABASECHANGELOG table accurate while restoring the lost rows.
<rollback> Blocks for Destructive ChangeSets<changeSet id="2026-10-13-drop-user-email" author="me">
<dropColumn tableName="user" columnName="email" />
<rollback>
<addColumn tableName="user">
<column name="email" type="varchar(255)"/>
</addColumn>
</rollback>
</changeSet>
Note: The rollback block restores the column definition but not the data that was stored in it. If you need the data back, you’ll still need a snapshot.
dbCreate to none (or validate)grails:
datasource:
default:
dbCreate: none
url: jdbc:postgresql://localhost:5432/mydb
username: app
password: secret
After changing this, run a full migration in a staging environment to confirm that no Hibernate‑generated DDL appears.
grails dbm-update to apply the latest changes.grails dbm-rollback -Dtarget=2026-10-13-drop-user-email to test the rollback of the specific changeSet.DATABASECHANGELOG to ensure the checksum matches the rolled‑back file.pg_dump -Fc for PostgreSQL).pg_restore -d mydb snapshot.dump.DATABASECHANGELOG table up to date. If you want to keep the checksums, you can truncate the table and run grails dbm-update again.#!/usr/bin/env bash
set -e
# 1. Take snapshot
pg_dump -Fc -f /backups/$(date +%F).dump mydb
# 2. Apply migrations
grails dbm-update
# 3. If rollback needed
# grails dbm-rollback -Dtarget=TARGET_CHANGESET
# 4. Restore snapshot
# pg_restore -d mydb /backups/$(date +%F).dump
grails dbm-validate to ensure the checksums match.DATABASECHANGELOG for any FAILED entries.To tailor the rollback script for your environment, could you confirm which database engine you’re using (PostgreSQL, MySQL, Oracle, etc.) and whether you have a scheduled snapshot strategy already in place?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.