I perform a liquibase update command with a given ChangeLog XML file and tag it with tag1.
For example:
liquibase --driver=org.postgresql.Driver --url=... --changeLogFile=change1.xml update
liquibase --driver=org.postgresql.Driver --url=... --tag tag1
I then perform several additional updates commands with other ChangeLog XML files:
liquibase --driver=org.postgresql.Driver --url=... --changeLogFile=change2.xml update
liquibase --driver=org.postgresql.Driver --url=... --changeLogFile=change3.xml update
Now I would like to rollback to tag1:
liquibase --driver=org.postgresql.Driver --url=... --changeLogFile=??? rollback tag1
Which file should I specify in --ChangeLogFile? Is there a way to define multiple files? Is there a way Liquibase can store embed the rollback commands without the need to supply the update XMLs?
Rather than using multiple changelog files from the command line, typical usage is to just have one changelog file that is used for the whole application. It is possible to have multiple changelogs, but in that pattern you have a single 'master' changelog that then includes other changelogs. see https://www.liquibase.org/bestpractices.html for some examples of this.
When rolling back, liquibase needs to know what the actual changes are, so it can then do something else to do the rollback. The DATABASECHANGELOG table does not store the actual contents of each changeset that has been applied, it just keeps the id, author, filepath, and a checksum. It has no way of knowing that changeset id 123778 by steve in file changelog.xml was a create table or an alter column or anything. So the changelog file MUST be there to be able to roll things back.
For certain change types like 'create table', Liquibase can 'automatically' generate a rollback for you - it is just 'drop table'. But if the change was something like 'drop table', it cannot generate a rollback - drop table command only has the name of the table to drop, and doesn't know what columns were in that table, etc.
So that is why Liquibase requires that you always supply a changelog file, and if you want to be able to do rollbacks, you may also need to specify how to roll back each change.
Related
Liquibase does a pretty good job in keeping the applied changesets consistent with their source-folder.
If you modify a changeset that has already been applied to the db, Liquibase refuses to do anything, even operations that are not related with the modified changeset.
The rule enforced here is: anything that has been applied to the database must be unapplied before changing it (this is a usual workflow during development).
The problem of 'orphaned' changesets
Unfortunately this rule doesn't apply if you just delete the changeset completely.
In this case there will be what I call an 'orphaned' changeset, that is a record on the DATABASECHANGELOG table (and the database object, of course) with liquibase not complaining at all of a missing changeset in the source.
I expected an error at least when you 'bump' into the missing changeset, that is when you try to rollback it, but liquibase simply seems to ignore its presence, it skips it and rolls back the next one. This can be a problem.
The question is: can we change this liquibase behavior via settings? Is this design needed for some use case that I haven't thought of?
I think what I'm asking should be clear enough, however here is an example as demonstration.
Example
<databaseChangeLog ... >
<include relativeToChangelogFile="true" file="CS1.sql"/>
<include relativeToChangelogFile="true" file="CS2.sql"/>
</databaseChangeLog>
My changesets are SQL based files like this:
-- liquibase formatted sql
-- changeset agostinox:-1
CREATE TABLE T1 (
X INT
)
-- rollback DROP TABLE T1
And CS2.sql is the same for the T2 table.
Now i can apply my changesets like this:
PS > liquibase update
...
Liquibase Version: 4.19.0
Liquibase Open Source 4.19.0 by Liquibase
...
Running Changeset: CS1.sql::-1::agostinox
Running Changeset: CS2.sql::-1::agostinox
Liquibase command 'update' was executed successfully.
And on my db, the DATABASECHANGELOG has the following content:
ID
AUTHOR
FILENAME
DATEEXECUTED
ORDEREXECUTED
EXECTYPE
MD5SUM
-1
agostinox
CS1.sql
2023-01-18 18:52:08.476689
1
EXECUTED
8:d966f9ba2b90eaea9b917a6d93962eff
-1
agostinox
CS2.sql
2023-01-18 18:52:08.666667
2
EXECUTED
8:7f2a735fa83b196a0c72885c95362b81
So far so good. Now, now I get to the point.
Let's mess with the CS1.sql, by adding a comment:
-- liquibase formatted sql
-- changeset agostinox:-1
CREATE TABLE T1 (
X INT --Added comment, very harmless but enough to annoy liquibase :-)
)
-- rollback DROP TABLE T1
Now, I try to rollback the last changeset.
PS > liquibase rollbackcount 1
...
Unexpected error running Liquibase: Validation Failed:
1 changesets check sum
CS1.sql::-1::agostinox was: 8:d966f9ba2b90eaea9b917a6d93962eff but is now:
8:2cea5484e81eb542fa94bb67ba2ffdf5
For more information, please use the --log-level flag
You can see that liquibase complains about CS1.sql been changed even if we are not even rolling back CS1.sql but CS2.sql. Actually this change blocks any further operation, so it have to be reverted in order to break the deadlock.
However, if you just remove the changeset, liquibase doesn't complain anymore:
<databaseChangeLog ... >
<!--REMOVED <include relativeToChangelogFile="true" file="CS1.sql"/> -->
<include relativeToChangelogFile="true" file="CS2.sql"/>
</databaseChangeLog>
PS > liquibase rollbackcount 1
...
Rolling Back Changeset: CS2.sql::-1::agostinox
Liquibase command 'rollbackCount' was executed successfully.
But even if liquibase says that everything is successfull, it is not really the case, infact the table DATABASECHANGELOG now contains the 'orphaned' changeset (and the database contains the T1 table since also the rollback code is gone).
ID
AUTHOR
FILENAME
DATEEXECUTED
ORDEREXECUTED
EXECTYPE
MD5SUM
-1
agostinox
CS1.sql
2023-01-18 18:52:08.476689
1
EXECUTED
8:d966f9ba2b90eaea9b917a6d93962eff
For db people, it seems like liquibase does a left join between changesets source folder and records in DATABASECHANGELOG (that is: all the items on the left set are taken and are associated with the matching items on the right set); this way liquibase can see applied migrations and check if their checksum matches. It can also see not-yet-applied migrations (changesets in source folder without a DATABASECHANGELOG record associated) in order to apply them in the next 'update' call.
But it can't see records from DATABASECHANGELOG (the right set) that don't have an associated changeset in the source folder.
This parallel with join operation well explains the liquibase behavior, so let's use it for describing what a better behavior might be.
Liquibase should do a full-join, that is: also the items on the right side that don't have a source changeset associated are considered. Those records indicate an anomaly that is possibly just a 'major version' of the one that you have when a checksum doesn't match. Indeed you can easily think of a null file as a particolar case of checksum, the checksum of null being something necessarily different from the one in the table. Therefore the existence of those non associated record should give the same kind of error. This is what i would expected to truly ensure consistence between source an DATABASECHANGELOG.
I've been curious about this also. I'd recommend opening a github issue so a Liquibase employee can address why they don't do this check. I'm guessing this was done on purpose.
https://github.com/liquibase/liquibase/issues
I'm in the process of integrating the springboot microservices with Liquibase. Prior to executing any changesets, I would like extract the "initial" state of an existing database(Oracle) and store in Liquibase DATABASECHANGELOG table. Is there way to do this?
What you would do is use the diffChangeLog command to generate a changelog.xml that contains all the changes needed to update a pristine database to the existing state of your database. If you already have a changelog, this would append to the end of that changelog, and you might want to manually rearrange the changesets so they are in the correct order.
You then use the changeLogSync command to populate the existing database with a DATABASECHANGELOG table that shows all of those changes have been deployed to that database.
When using the includeAll option in the databaseChangeLog, is there any way to use rollback or tagging? It seems really nice to have all my changes in file 01.sql to 99.sql run in order. Do I have to go back to specifying the individual files and rollbacks to make this work?
You can use SQL Format
e.g.:
--changeset nvoxland:1
create table test1 (
id int primary key,
name varchar(255)
);
--rollback drop table test1;
As of Liquibase 2.0, Liquibase includes support for “plain SQL”
changelog files. These changelogs may be included from XML changelogs
and may contain arbitrary SQL statements. The statements are converted
to custom_sql refactorings.
Formatted SQL files use comments to provide Liquibase with metadata.
Read more about SQL Format
I see an attribute runOnChange that re-runs changeset when it is changed. But is it possible to apply a rollback for this changeset before re-applying automatically?
For example, I have a script that is called from changeset. I made some changes there and want to re-apply, but before it rollback need to be called and after it new version of the script should be applied.
Thank you!
There is no feature to automatically roll back a changeSet on checksum change. Not sure if it is possible in general because if the configuration has changed you don't know what the old value was to roll back.
Depending on what you are doing in your script and your database, can roll back the changes manually in the script and use the liquibase runOnChange="true" changeSet flag.
For example, if you have a script that creates a stored procedure, you could use <changeSet runOnChange="true"> and then define your procedure as "CREATE OR REPLACE"
If you have a script that defines a view, you could add a <sql>IF EXISTS VIEW_NAME DROP VIEW VIEW_NAME</sql>
Is it OK to manually delete rows from Databasechangelog?
Btw, rollback to a TAG is not an option in my case.
Deleting rows from the table will simulate a rollback without actually performing one.... Leaving your database in an unknown state (the whole point is that liquibase manages the schema for you).
Why not just rollback a specified number of changesets instead?
liquibase rollbackCount 5