Liquibase preconditions to all changelog file in sql - liquibase

I would like to run a SQL precondition checking for each changeSet in my SQL changeLogFile. It is actually a precondition on the changeLog itself
Here is an extract of it :
--liquibase formatted sql
--preconditions onFail:HALT onError:HALT
--precondition-sql-check expectedResult:"1.0" SELECT VERSION FROM VERSION_TABLE;
--changeset bob:1 failOnError:true dbms:oracle
ALTER INDEX XXX RENAME TO YYY;
--rollback YYY RENAME TO XXX;
Even if the precondition is actually not respected, liquibase still run all the changeset.
Does somebody knows if it is an error from my side or if liquibase does not allow preconditions on entire changeLog for SQL changeLog file ?
Thanks in advance !

If you go through the documentation, then its stated that we can only apply pre-conditions on a specific change set. Also, only the SQL Check precondition is supported.
Liquibase doc for sql changelog files - https://www.liquibase.org/documentation/sql_format.html

One thing you could do is have a top-level master changelog in XML/YAML/JSON format and then use <include> or <includeAll> elements to include your liquibase formatted sql changelogs. If you do that, then you can have changelog-level preconditions.

Related

liquibase checks that changesets have not been changed, but doesn't check that they've not been removed: is there a setting to fix this?

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

How to rollback multiple databaseChangeLog file at once

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.

Liquibase includeAll incompatible with rollback and tagging?

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

tagDatabase in Formatted SQL changeset

I'm trying to tag a Formatted SQL changeset so a matching ID and tag are written to the DATABASECHANGELOG table (for rollback purposes - see changeset fragment below). The Phing liquibase task is being used to apply the 'update' command for a single changelog.
Although the 'tagDatabase' attribute isn't listed for Formatted SQL changelogs (http://www.liquibase.org/documentation/sql_format.html), neither is logicalFilePath, and that seems to be working OK!
Can someone let me know definitively if tagDatabase is not supported for a Formatted SQL changeset?
Many thanks in advance,
IR8
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
--changeset id:123 logicalFilePath:path-independent
ALTER TABLE test1
ADD COLUMN text_column
text NULL;
--rollback ALTER TABLE test1 DROP COLUMN text_column;
--changeset id:tag123 tagDatabase:123;
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Unexpected error running Liquibase: No SQL for changeset ../db/changelog/databaseChangeLog.sql::tag123::id
Execution of target "migrate" failed for the following reason: C:\data\htdocs\TestLiquibase\deploy\build.xml:49:1: Liquibase exited with code -1
I think it's not included.
The java class FormattedSqlChangeLogParser takes care of parsing the formatted sql file and has a couple of Patterns defined for this. There is a logicalFilePathPattern but nothing for tagDatabase.
So this is not implemented yet.
From the official document,
When you run the updateToTag command or the Maven update goal with the liquibase.toTag attribute, and there is a row in the DATABASECHANGELOG table corresponding to the tagDatabase changeset in the changelog, the updateToTag command or the update Maven goal deploys all changesets starting with the first changeset at the top of the changelog file and moving down to the changeset up to the tag specified by the tagDatabase Change Type.
We can do one thing that is, after you executing all the SQL format changesets, we can create a new database tag changeset and update with liquibase
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="sivaramr" id="7">
<tagDatabase tag="v_1.0.0"/>
</changeSet>
</databaseChangeLog>
If you are using any liquibase command tool or liquibase plugins, then you can perform tag command to add the tag to the last changelog as follow
Here It is illustrated with gradle plugin.
./gradlew tag -PliquibaseCommandValue=v1.0.0
Before executing the above tag command, you can check the tag is already exist or not by tagexists command
./gradlew tagExists -PliquibaseCommandValue=v1.0.0

Liquibase: Convert createTable changeSet entry to DDL SQL statement

I'd like to use JDBC to create tables in a database agnostic way. I'm pretty sure that Liquibase has solved this problem since it can take a generic createTable XML changeSet element and convert it into a database specific SQL DDL statement.
Can someone please tell me which liquibase classes / utililities are involved in converting a generic createTable changeSet into a database specific create table SQL script?. Sample usage (ie a test case) would be great.
Please note that I do not wish to invoke the entire liquibase pipeline. In particular I do NOT want the databasechangelog table.
I'd recommend reading the liquibase unit tests.