Automatic and Custom Rollbacks
Liquibase supports multiple ways to revert your database to a previous state. For many Change Types, such as createTable, addColumn, and renameColumn, Liquibase automatically generates the SQL statements necessary to complete the rollback, so you can simply run a command like rollback
on your desired changeset(s) and be done.
However, Liquibase cannot automatically generate rollback SQL for Change Types like dropTable and insert. To roll back database updates that include these changesets, you must write custom rollback statements in your desired changeset(s) and then run the rollback
command. You must write custom rollback statements for all formatted SQL changelogs, as they do not support auto rollback, regardless of the Change Type. You can also write your own rollbacks if you want to override the default rollback statement for a Change Type that supports auto rollback.
For a complete list of Change Types that allow auto rollback, see the "Auto rollback Change Types" section.
Automatic rollback statements
When you run the update command on a createTable
changeset, Liquibase executes the SQL statement CREATE TABLE myTable
. When you run the rollback
command to revert that changeset, Liquibase recognizes that the inverse of the original change is DROP TABLE myTable
and executes that statement. In this case, you aren't required to add anything special to your changelog.
Liquibase command 'rollback' was executed successfully.
Liquibase: Rollback has been successful.
However, if you try to run rollback
on dropTable
, Liquibase cannot automatically generate the rollback SQL:
Unexpected error running Liquibase: No inverse to liquibase.change.core.DropTableChange created
There is only one state the database could be in right before a createTable
statement—a state in which the table does not exist. However, there are multiple states the database could be in right before a dropTable
statement, so Liquibase cannot reliably look through your changelog to "recreate" a table you dropped. This is because getting the table to its final state before being dropped may have involved a large number of changesets, raw SQL statements in sql
or modifySql
tags, Contexts, or manual changes to the changelog.
To roll back your dropTable
statement, you have to specify custom logic in your changelog so that Liquibase knows how to correctly restore your database.
Custom rollback statements
Custom rollbacks
Liquibase cannot automatically roll back all Change Types. For example, Liquibase cannot automatically roll back dropTable
changes because the inverse SQL could be more than one statement.
In these cases, you must specify custom rollback syntax in your changelog for every changeset that you might want to roll back. This way, when you run the rollback
command, Liquibase knows what to do.
Note: When rolling back stored logic, Liquibase does not restore the previously stored version. Instead, Liquibase rolls back to the exact file/code specified in the custom rollback.
Note: Liquibase does not support automatic rollback for any Formatted SQL changesets. To roll back Formatted SQL changes, you must always specify a custom rollback.
--changeset liquibaseuser:1
create table testTable ( id int primary key, name varchar(255) );
--rollback drop table testTable;
--changeset liquibaseuser:2
insert into testTable values ('1','The First', 'Country')
insert into testTable values ('2','The Second', 'Country2')
--rollback delete from testTable where id='1'
--rollback delete from testTable where id='2'
In Liquibase 4.19.0 and later, you can specify multiple rollback statements in a block comment:
--changeset liquibaseuser:2
insert into testTable values ('1','The First', 'Country')
insert into testTable values ('2','The Second', 'Country2')
/* liquibase rollback
rollback delete from testTable where id='1'
rollback delete from testTable where id='2'
*/
In Liquibase 4.26.0 and later, you can use a rollbackSqlFile
statement to specify rollback SQL for a changeset in a separate file:
--changeset liquibase-user:1
DROP PROCEDURE hello_world;
--rollbackSqlFile path:release_1.0/rollback_45895.sql
In your rollbackSqlFile
statement, you can specify parameters to change the behavior of your rollback, such as a unique end delimiter. For more information, see Example Changelogs: SQL Format.
databaseChangeLog:
- changeSet:
id: 2
author: liquibaseuser
changes:
- dropTable:
tableName: person
rollback:
createTable:
catalogName: cat
columns:
- column:
name: address
type: varchar(255)
remarks: A String
schemaName: public
tableName: person
For more information, see Example Changelogs: YAML Format.
{
"databaseChangeLog": [
{
"changeSet": {
"id": "2",
"author": "liquibaseuser",
"changes": [
{
"dropTable": {
"tableName": "person"
}
}
],
"rollback": [
{
"createTable": {
"catalogName": "cat",
"columns": [
{
"column": {
"name": "address",
"type": "varchar(255)"
}
}
],
"remarks": "A String",
"schemaName": "public",
"tableName": "person"
}
}
]
}
}
]
}
For more information, see Example Changelogs: JSON Format.
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/pro
http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd">
<changeSet author="liquibaseuser" id="2">
<dropTable tableName="person"/>
<rollback>
<createTable catalogName="department"
remarks="A String"
schemaName="public"
tableName="person"
<column name="address" type="varchar(255)"/>
</createTable>
</rollback>
</changeSet>
</databaseChangeLog>
For more information, see Example Changelogs: XML Format.
Override default rollback commands
The <rollback>
tag describes how to roll back a change using SQL statements, Change Types, or a reference to a previous changeset. You can use any Change Type in the <rollback>
element, such as dropTable, sql, and sqlFile:
- changeSet:
id: 1
author: liquibaseuser
changes:
- createTable:
tableName: testTable
columns:
- column:
name: id
rollback:
- dropTable:
tableName: testTable
{
"changeSet": {
"id": 1,
"author": "liquibaseuser",
"changes": [
{
"createTable": {
"tableName": "testTable"
}
}
],
"rollback": [
{
"dropTable": {
"tableName": "testTable"
}
}
]
}
}
<changeSet id="1" author="liquibaseuser">
<createTable tableName="testTable">
<column name="id" type="int"/>
</createTable>
<rollback>
<dropTable tableName="testTable"/>
</rollback>
</changeSet>
Alternatively, you can have raw SQL in the content part of the <rollback>
element. For example:
<changeSet id="1" author="liquibaseuser">
<createTable tableName="testTable">
<column name="id" type="int"/>
</createTable>
<rollback>
drop table testTable
</rollback>
</changeSet>
Liquibase treats the raw SQL within <rollback>
the same as in the <sql>
Change Type, with stripComments
set to true
, splitStatements
set to true
, and endDelimiter
set to ;
. For more details, see the XML example from the sql documentation.
Multiple rollbacks
You can also specify multiple Change Types within a single <rollback>
statement or across multiple <rollback>
statements:
- changeSet:
id: multiRollbackTest
author: liquibaseuser
changes:
- createTable:
tableName: multiRollback1
columns:
- column:
name: id
- createTable:
tableName: multiRollback2
columns:
- column:
name: id
- createTable:
tableName: multiRollback3
columns:
- column:
name: id
rollback:
- dropTable:
tableName: multiRollback1
- dropTable:
tableName: multiRollback2
rollback:
- dropTable:
tableName: multiRollback3
{
"changeSet": {
"id": multiRollbackTest,
"author": "liquibaseuser",
"changes": [
{
"createTable": {
"tableName": "multiRollback1",
"columns": [
{
"column": {
"name": "id"
}
}
]
},
"createTable": {
"tableName": "multiRollback2"
"columns": [
{
"column": {
"name": "id"
}
}
]
},
"createTable": {
"tableName": "multiRollback3"
"columns": [
{
"column": {
"name": "id"
}
}
]
}
}
],
"rollback": [
{
"dropTable": {
"tableName": "multiRollback1"
},
"dropTable": {
"tableName": "multiRollback2"
}
}
],
"rollback": [
{
"dropTable": {
"tableName": "multiRollback3"
}
}
],
}
}
Change Type syntax:
<changeSet id="multiRollbackTest" author="liquibaseuser">
<createTable tableName="multiRollback1">
<column name="id" type="int"/>
</createTable>
<createTable tableName="multiRollback2">
<column name="id" type="int"/>
</createTable>
<createTable tableName="multiRollback3">
<column name="id" type="int"/>
</createTable>
<rollback>
<dropTable tableName="multiRollback1"/>
<dropTable tableName="multiRollback2"/>
</rollback>
<rollback>
<dropTable tableName="multiRollback3"/>
</rollback>
</changeSet>
Raw SQL:
<changeSet id="multiRollbackTest" author="liquibaseuser">
<createTable tableName="multiRollback1">
<column name="id" type="int"/>
</createTable>
<createTable tableName="multiRollback2">
<column name="id" type="int"/>
</createTable>
<createTable tableName="multiRollback3">
<column name="id" type="int"/>
</createTable>
<rollback>
drop table multiRollback1;
drop table multiRollback2;
</rollback>
<rollback>drop table multiRollback3</rollback>
</changeSet>
Directly reference changeset
The following example shows how you can use a <rollback>
tag to reference the changeset that originally created a statement. This example uses changeset 2
to implement rollback logic against changeset 1
:
--changeset liquibaseuser:1
create table testTable ( id int primary key, name varchar(255) );
--changeset liquibaseuser:2
--rollback drop table testTable;
--rollback changesetId:1 changesetAuthor:liquibaseuser changesetPath:optional/path/to/myChangeLog.sql
- changeSet:
id: 1
author: liquibaseuser
changes:
- createTable:
tableName: testTable
columns:
- column:
name: id
- changeSet:
id: 2
author: liquibaseuser
rollback:
- dropTable:
tableName: testTable
changesetId: 1
changesetAuthor: liquibaseuser
changesetpath: optional/path/to/myChangeLog.yaml
{
"changeSet": {
"id": 1,
"author": "liquibaseuser",
"changes": [
{
"createTable": {
"tableName": "testTable",
"columns": [
{
"column": {
"name": "id",
"type": "varchar(255)"
}
}
]
}
}
]
{
"changeSet": {
"id": 2,
"author": "liquibaseuser",
"rollback": [
{
"dropTable": {
"tableName": "testTable"
}
},
"changesetId": "1",
"changesetAuthor": "liquibaseuser",
"changesetPath": "optional/path/to/myChangeLog.json"
]
}
}
<changeSet id="1" author="liquibaseuser">
<createTable tableName="testTable"/>
<column name="id" type="varchar(255)"/>
</createTable>
</changeSet>
<changeSet id="2" author="liquibaseuser">
<dropTable tableName="testTable"/>
<rollback changesetId="1" changesetAuthor="liquibaseuser" changesetPath="optional/path/to/myChangeLog.xml"/>
</changeSet>
Empty rollback statements
If you do not want to revert a change in a rollback mode, use either the keyword empty
or the keyword not required
inside the rollback
tag. In XML, YAML, and JSON changelogs, you can also use an empty string inside the rollback
tag.
--changeset liquibaseuser:1
create table testTable ( id int primary key, name varchar(255) );
--rollback empty
- changeSet:
id: 1
author: liquibase
changes:
- createTable:
tableName: testTable
columns:
- column:
name: id
type: int
rollback: empty
You can also use an empty string (rollback: ""
).
{
"changeSet": {
"id": 1,
"author": "example",
"changes": [
{
"createTable": {
"tableName": "testTable",
"columns": [
{
"column": {
"name": "id",
"type": "int"
}
}
]
}
}
],
"rollback": "empty"
}
}
You can also use an empty string ("rollback": ""
).
<changeSet id="3" author="liquibaseuser">
<createTable tableName="testTable">
<column name="id" type="int"/>
</createTable>
<rollback>empty</rollback>
</changeSet>
You can also use an empty string (<rollback></rollback>
) or a self-closing-tag (<rollback/>
).
Note: The empty
and output
Change Types support automatic rollbacks.
Auto rollback Change Types
You can use an automatic rollback with XML, JSON, and YAML changelogs for any Change Type marked as "Supported" in the following table:
Change Type | Supported | Behavior |
---|---|---|
addAutoIncrement
|
Not Supported | |
addCheckConstraint
|
Supported | Drop check constraint |
addColumn
|
Supported | Drop column |
addDefaultValue
|
Supported | Drop default value |
addForeignKeyConstraint
|
Supported | Drop foreign key constraint |
addLookupTable
|
Supported | Drop foreign key constraint and drop table |
addNotNullConstraint
|
Supported | Drop not null constraint |
addPrimaryKey
|
Supported | Drop primary key |
addUniqueConstraint
|
Supported | Drop unique constraint |
alterSequence
|
Not Supported | |
createFunction
|
Not Supported | |
createIndex
|
Supported | Drop index |
createPackage
|
Not Supported | |
createPackageBody
|
Not Supported | |
createProcedure
|
Not Supported | |
createSequence
|
Supported | Drop sequence |
createSynonym
|
Supported | Drop synonym |
createTable
|
Supported | Drop table |
createTrigger
|
Not Supported | |
createView
|
Supported | Drop view |
customChange
|
Not Supported | |
delete
|
Not Supported | |
disableCheckConstraint
|
Supported | Enable check constraint |
disableTrigger
|
Supported | Enable trigger |
dropAllForeignKeyConstraints
|
Not Supported | |
dropCheckConstraint
|
Not Supported | |
dropColumn
|
Not Supported | |
dropDefaultValue
|
Not Supported | |
dropForeignKeyConstraint
|
Not Supported | |
dropFunction
|
Not Supported | |
dropIndex
|
Not Supported | |
dropNotNullConstraint
|
Supported | Add not null constraint |
dropPackage
|
Not Supported | |
dropPackageBody
|
Not Supported | |
dropPrimaryKey
|
Not Supported | |
dropProcedure
|
Not Supported | |
dropSequence
|
Not Supported | |
dropSynonym
|
Not Supported | |
dropTable
|
Not Supported | |
dropTrigger
|
Not Supported | |
dropUniqueConstraint
|
Not Supported | |
dropView
|
Not Supported | |
empty
|
Supported | Return empty change |
enableCheckConstraint
|
Supported | Disable check constraint |
enableTrigger
|
Supported | Disable trigger |
executeCommand
|
Not Supported | |
insert
|
Not Supported | |
loadData
|
Not Supported | |
loadUpdateData
|
Not Supported | |
markUnused
|
Not Supported | |
mergeColumns
|
Not Supported | |
modifyDataType
|
Not Supported | |
output
|
Supported | Return empty change |
renameColumn
|
Supported | Rename with oldColumnName |
renameSequence
|
Supported | Rename with oldSequenceName |
renameTable
|
Supported | Rename with oldTableName |
renameTrigger
|
Supported | Rename with oldTriggerName . |
renameView
|
Supported | Rename with oldViewName |
setColumnRemarks
|
Not Supported | |
setTableRemarks
|
Not Supported | |
sql
|
Not Supported | |
sqlFile
|
Not Supported | |
stop
|
Not Supported | |
tagDatabase
|
Supported | Deletes the tag |
update
|
Not Supported |