Содержание
- Precondition Processing Does not Seem to Work as Documented #1889
- Comments
- Environment
- Description
- Steps To Reproduce
- Actual Behavior
- Expected/Desired Behavior
- Additional Context
- How to fail Liquibase changeset when prerequisites are missing
- Precondition 1 — enough disk space
- Precondition 2 — specific user
- Precondition 3 — complex case
- Summary
- Preconditions
- Syntax
- Multiple preconditions
- Examples
- Handling failures and errors
- PostgreSQL Precondition sequenceExists is not working. #1163
- Comments
- Environment
- Description
- Steps To Reproduce
- Actual Behavior
- Expected/Desired Behavior
Precondition Processing Does not Seem to Work as Documented #1889
Environment
Liquibase Version: 4.3.1
Liquibase Integration & Version: Gradle
Liquibase Extension(s) & Version: N/A
Database Vendor & Version: PostgreSQL 13.2
Operating System Type & Version: Mac OS 10.15.7
Description
Note: Preconditions are checked at the beginning of the execution of a particular changelog. If you use the include tag
and only have preconditions on the child changelog, those preconditions will not be checked until the migrator reaches
that file. However, this behavior may change in future releases.
I have encountered a situation in which this is not the case.
Steps To Reproduce
- Create an empty databaseChangeLog in a file called database-changelog-master.yml
- Create a new databaseChangeLog in a file called add_version_table.yml
- Update add_version_table.yml to define the structure for a very simple table called ‘version’
- Using the include tag, include add_version_table.yml to database-changelog-master.yml
- Create a new databaseChangeLog in a file called rename_version_table.yml
- Update rename_version_table.yml to rename the ‘version’ table to ‘version_legacy’
- Using the include tag, include rename_version_table.yml to the end of database-changelog-master.yml
- Create a new databaseChangeLog in a file called remove_version_legacy_table.yml
- Update remove_version_legacy_table.yml to drop the ‘version_legacy’ table
- Add a precondition to remove_version_legacy_table.yml which performs a sqlCheck to ensure that the ‘version_legacy’ table is empty before dropping.
- Using the include tag, include remove_version_legacy_table.yml to the end of database-changelog-master.yml
Actual Behavior
If liquibase update is run every time a new file is included in ‘database-changelog-master.yml’ (which would be after steps 4, 7, and 11 above) everything works as expected. If liquibase is run against a fresh database or a database which already has a version table (at any step in the process above prior to step 7) the above process fails with the following error:
[2021-06-07 12:43:45] SEVERE [liquibase.integration] Unexpected error running Liquibase: Validation Failed:
1 preconditions generated an error
src/main/resources/database-changelog-master.yml : liquibase.precondition.core.SqlPrecondition4e28bdd1 : > ERROR: relation «version_legacy» does not exist
Position: 32
Expected/Desired Behavior
The behavior which is documented should be restored. Alternately, if this change was intentional, the documentation should be updated and preconditions should be updated to allow users to specify that a precondition should not be checked until the migrator reaches that file.
Additional Context
The case illustrated above is a simplified version of what I am attempting to accomplish in real life. I have an existing table, and new functionality requires me to refactor that table to have a subset of its data residing in related tables. In order to do this with the lowest possible risk of data loss I am first renaming the table, then creating the tables that will replace it, then migrating the data, and only once I have checked that the new table has the same number of rows as the old table (using a sqlCheck precondition) do I want to delete the old table.
The text was updated successfully, but these errors were encountered:
Источник
How to fail Liquibase changeset when prerequisites are missing
How to fail Liquibase changeset when prerequisites are missing
Index creation should not start when there is not enough disk space. An old table should not be dropped until it is completely empty. A database upgrade may happen only if a DBA has changed a default value of some setting. Many database upgrades have some prerequisites. If they are not met, the whole change should not even start. You may write a checklist during development and follow it on the upgrade but there is a better way — preconditions in Liquibase.
Precondition 1 — enough disk space
Data migration estimated for 10 hours that fails after 8 hours because of running out of disk space is a disaster. It would be very inconvenient even if rolling back was easy. Otherwise it could be catastrophic.
Assuming that a changeset that does the migration should not even start without at least 10 GB of free space in a datafile, the following Liquibase file presents a solution.
When the data file has more than 10GB of free space, the changeset executes, but when the precondition fails, you can see the following in the command line.
Remember about setting logLevel , otherwise you will not see a meaningful error.
Precondition 2 — specific user
A database upgrade may require special privileges. Running it without them will fail so you have already created a special user on all environments and you would like to make sure they are used for all upgrades done by Liquibase. A key is all upgrades. This time the precondition applies to the whole upgrade not only a specific changeset. That is easy because preConditions tag can be applied inside a changeset but also inside a databaseChangeLog.
In that case, the precondition is verified before any changeset is attempted to apply. It becomes a prerequisite for the whole database upgrade.
Notice also that there is a predefined tag runningAs which checks the name of the database user who is running the upgrade. There are more such predefined conditions listed in the Liquibase manual.
Precondition 3 — complex case
One precondition per changeset would be too easy if it was always enough. Real life scenarios often require a little bit more. Fortunately, preConditions tag may contain multiple prerequisites joined together with logical operators.
The above example will allow the upgrade only when it is run by dba user, there is at least 10GB of space in the datafile and the upgrade is executed on SQL Server or Oracle.
Summary
Prerequisites are very common in database upgrades. We usually create checklists and manually verify them which is error prone. At some point it makes sense to automate at least some of them. The less items you have to remember about, the more creative your work can become. Preconditions are there to help you.
If anything goes wrong, the upgrade can be rolled back with autorollback feature.
If you are new to Liquibase watch my introduction to Liquibase and find more Liquibase articles.
Would you like to learn db performance? Enroll to my course on Udemy.
50% discount only till February 2. Hurry up!
Источник
Preconditions
Preconditions are changelog or changeset tags which control the execution of an update based on the state of the database.
You can use preconditions to:
- Document what assumptions the author of the changelog had when creating it.
- Enforce that those assumptions are not violated by users running the changelog .
- Perform data checks before performing an unrecoverable change such as dropTable.
- Control what changeset s are run and not run based on the state of the database.
You can use all Liquibase preconditions in XML, YAML, and JSON changelog s. The only supported precondition for SQL changelog s is sqlCheck . For a list of preconditions, see Available preconditions.
Syntax
You can use one
The sample changelog will only be run if the database executed against is Oracle and the database user executing the script is SYSTEM. Also, it will run the changeset with the sqlCheck precondition and dropTable.
If the preconditions check fails, you will receive a warning and it will continue executing the changeset as normal because of the onFail=»WARN» precondition. To prevent the execution of the changeset when the precondition fails, you can set HALT or CONTINUE values. For more information, see onFail/onError values.
Multiple preconditions
In XML, JSON, and YAML changelog s, you can set multiple preconditions in one
Examples
The following syntax example will check that the update is running on Oracle and with the SYSTEM user but will only generate a warning message if the precondition fails:
The following will require running the changelog on Oracle or MySQL:
You can also see the precondition running as SYSTEM if executing against an Oracle database or running as SA if executing against a MS SQL database.
Handling failures and errors
Liquibase defines two types of preconditions:
- Precondition failures which represent that the check failed
- Precondition errors that are the exceptions thrown in the execution of a check
The process of both can be controlled through the onFail and onError attribute s on the type – the type of database expected. Multiple dbms values can be specified using comma-separated values. (required) changeLogPropertyDefined
Checks whether given changelog attribute is present. It fails if the value is not the same as given.
- property – the name of the property to check. (required)
- value – the required value for a given property.
changeSetExecuted
Defines if the specified changeset has already been executed.
- Id – the changeset id. (required)
- author – the changeset author. (required)
- changelog-file – the file name (including searchPath relative path) of the changeset . (required)
sqlCheck
Executes an SQL string and checks the returned value. The SQL must return a single row with a single value.
- To check a number of rows, use the count SQL function.
- To check for ranges of values, perform the check in the SQL and return a value that can be easily compared against.
expectedResult – the value to compare the SQL result to. (required) rowCount
Checks that the number of rows in a table matches an expected value.
- expectedRows – the number of rows the user expects in a table (required)
- tableName – the name of the column’s table (required)
runningAs
Defines if the database user executed under matches the username specified.
username – the database user script which is expected to run as. (required) columnExists
Defines if the specified column exists in the database.
- schemaName – the name of the table’s schema.
- tableName – the name of the column’s table. (required)
- columnName – the name of the column. (required)
foreignKeyConstraintExists
Defines if the specified foreign key exists in the database.
- schemaName – the name of the foreign key’s schema.
- foreignKeyName – the name of the foreign key. (required)
indexExists
Defines if the specified index exists in the database. You can either specify the indexName attribute or tableName and columnNames attribute s.
Note: There are a few databases where the indexName is not unique, that’s why both indexName and tableName can be used.
- schemaName – the name of the index’s schema.
- indexName – the name of the index.
- tableName – the name of the table.
- columnNames – the name of the column.
primaryKeyExists
Defines if the specified primary key exists in the database.
- schemaName – the name of the primary key’s schema.
- primaryKeyName – the name of the primary key constraint.
- tableName – the name of the table containing primary key.
( tableName or primaryKeyName is required)
sequenceExists
Defines if the specified sequence exists in the database.
- schemaName – the name of the sequences’ schema.
- sequenceName – the name of the sequence. (required)
tableExists
Defines if the specified table exists in the database.
- schemaName – the name of the table’s schema.
- tableName – the name of the table. (required)
uniqueConstraintExists
Checks for the existence of unique constraints before running the update. (since Liquibase 4.9.0)
- constraintName – the name of the unique constraint
- tableName – the name of the column’s table (required)
- columnNames – the name of the column
viewExists
Defines if the specified view exists in the database.
- schemaName – the name of the view’s schema.
- viewName – the name of the view. (required)
customPrecondition
Can be created by adding a class that implements the liquibase.precondition.CustomPrecondition interface. Parameters on custom classes are set through reflection based on the
sub-tags. Pass parameters as strings to the custom precondition.
className – the name of the custom precondition class. (required)
The customPrecondition sub-tags:
Источник
PostgreSQL Precondition sequenceExists is not working. #1163
Environment
Liquibase Version: 3.6.2
Liquibase Integration & Version: spring boot
Liquibase Extension(s) & Version: n/a
Database Vendor & Version: PostgreSQL 10.6
Operating System Type & Version: Ubuntu 10.6-0ubuntu0.18.04.1
Description
Precondition sequenceExists failed to check existing auto increment sequence.
Application log:
2018-12-28 17:20:30.539 INFO 13697 — [ main] liquibase.executor.jvm.JdbcExecutor : SELECT c.relname AS SEQUENCE_NAME FROM pg_class c join pg_namespace on c.relnamespace = pg_namespace.oid WHERE c.relkind=’S’ AND nspname = ‘null’ AND c.oid not in (select d.objid FROM pg_depend d where d.refobjsubid > 0)
2018-12-28 17:20:30.544 INFO 13697 — [ main] liquibase.executor.jvm.JdbcExecutor : SELECT c.relname AS SEQUENCE_NAME FROM pg_class c join pg_namespace on c.relnamespace = pg_namespace.oid WHERE c.relkind=’S’ AND nspname = ‘public’ AND c.oid not in (select d.objid FROM pg_depend d where d.refobjsubid > 0)
2018-12-28 17:20:30.548 INFO 13697 — [ main] liquibase.executor.jvm.JdbcExecutor : CREATE SEQUENCE public.analytics_queue_id_seq START WITH 1 INCREMENT BY 1
2018-12-28 17:20:30.550 ERROR 13697 — [ main] liquibase.changelog.ChangeSet : Change Set changelogs/6.xml::11::931@company.ru failed. Error: ERROR: relation «analytics_queue_id_seq» already exists [Failed SQL: CREATE SEQUENCE public.analytics_queue_id_seq START WITH 1 INCREMENT BY 1]
Steps To Reproduce
- Apply DDL from db_ddl.txt
- Create sequence manually: CREATE SEQUENCE public.analytics_queue_id_seq START WITH 1 INCREMENT BY 1
- Run the changeset migration.
Actual Behavior
Precondition passed successfully.
Applying changeset failed, because sequence already exists.
Expected/Desired Behavior
Precondition should be failed.
Changeset should be marked as RAN and should not be applied.
The text was updated successfully, but these errors were encountered:
Источник
Index creation should not start when there is not enough disk space. An old table should not be dropped until it is completely empty. A database upgrade may happen only if a DBA has changed a default value of some setting. Many database upgrades have some prerequisites. If they are not met, the whole change should not even start. You may write a checklist during development and follow it on the upgrade but there is a better way — preconditions in Liquibase.
Precondition 1 — enough disk space
Data migration estimated for 10 hours that fails after 8 hours because of running out of disk space is a disaster. It would be very inconvenient even if rolling back was easy. Otherwise it could be catastrophic.
Assuming that a changeset that does the migration should not even start without at least 10 GB of free space in a datafile, the following Liquibase file presents a solution.
<?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"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"><
changeSet id="Data migration" author="DBA presents">
<preConditions onFailMessage="Preconditions failed - at least 10GB of free space">
<sqlCheck expectedResult="1">
select case when size/128.0 - cast(fileproperty(name, 'SpaceUsed') as int)/128.0 > 10000 then 1 else 0 end
from sys.database_files
where name = 'MyDatabase'
</sqlCheck>
</preConditions>
<sql>
insert into NEWTABLE (ID, NAME, VALUE)
select ID, NAME, VALUE
from OLDTABLE;
</sql>
</changeSet></
databaseChangeLog>
When the data file has more than 10GB of free space, the changeset executes, but when the precondition fails, you can see the following in the command line.
liquibase --driver=com.microsoft.sqlserver.jdbc.SQLServerDriver --classpath="c:Program FilesMicrosoft JDBC Driver 4.0 for SQL Serversqljdbc_4.0enusqljdbc4.jar" --changeLogFile=databaseChangeLog.xml --url="jdbc:sqlserver://localhostDEVSQL01;databaseName=MyDatabase" --username=sa --password=xyz --logLevel="severe" update
SEVERE 29.07.2018, 14:18: liquibase: databaseChangeLog.xml: databaseChangeLog.xml::Data migration::DBA presents: Change Set databaseChangeLog.xml::Data migration::DBA presents failed. Error: Migration failed for change set databaseChangeLog.xml::Data migration::DBA presents:
Reason:
databaseChangeLog.xml : Preconditions failed - at least 10GB of free spaceliquibase.exception.MigrationFailedException: Migration failed for change set databaseChangeLog.xml::Data migration::DBA presents:
Reason:
databaseChangeLog.xml : Preconditions failed - at least 10GB of free spaceat liquibase.changelog.ChangeSet.execute(ChangeSet.java:463)
at liquibase.changelog.visitor.UpdateVisitor.visit(UpdateVisitor.java:43)
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:70)
at liquibase.Liquibase.update(Liquibase.java:195)
at liquibase.Liquibase.update(Liquibase.java:174)
at liquibase.integration.commandline.Main.doMigration(Main.java:997)
at liquibase.integration.commandline.Main.run(Main.java:170)
at liquibase.integration.commandline.Main.main(Main.java:89)
Caused by: liquibase.exception.PreconditionFailedException: Preconditions Failed
at liquibase.precondition.core.PreconditionContainer.check(PreconditionContainer.java:220)
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:449)
... 7 more
Unexpected error running Liquibase: Preconditions FailedSEVERE 29.07.2018, 14:18: liquibase: databaseChangeLog.xml::Data migration::DBA presents: Preconditions Failed
liquibase.exception.MigrationFailedException: Migration failed for change set databaseChangeLog.xml::Data migration::DBA presents:
Reason:
databaseChangeLog.xml : Preconditions failed - at least 10GB of free spaceat liquibase.changelog.ChangeSet.execute(ChangeSet.java:463)
at liquibase.changelog.visitor.UpdateVisitor.visit(UpdateVisitor.java:43)
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:70)
at liquibase.Liquibase.update(Liquibase.java:195)
at liquibase.Liquibase.update(Liquibase.java:174)
at liquibase.integration.commandline.Main.doMigration(Main.java:997)
at liquibase.integration.commandline.Main.run(Main.java:170)
at liquibase.integration.commandline.Main.main(Main.java:89)
Caused by: liquibase.exception.PreconditionFailedException: Preconditions Failed
at liquibase.precondition.core.PreconditionContainer.check(PreconditionContainer.java:220)
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:449)
... 7 more
For more information, use the --logLevel flag
Remember about setting logLevel, otherwise you will not see a meaningful error.
Precondition 2 — specific user
A database upgrade may require special privileges. Running it without them will fail so you have already created a special user on all environments and you would like to make sure they are used for all upgrades done by Liquibase. A key is all upgrades. This time the precondition applies to the whole upgrade not only a specific changeset. That is easy because preConditions tag can be applied inside a changeset but also inside a databaseChangeLog.
<?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"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"><
preConditions onFailMessage="Preconditions failed - dba user is required">
<runningAs username="dba" />
</preConditions><
changeSet id="Data migration" author="DBA presents">
<sql>
insert into NEWTABLE (ID, NAME, VALUE)
select ID, NAME, VALUE
from OLDTABLE;
</sql>
</changeSet></
databaseChangeLog>
liquibase --driver=com.microsoft.sqlserver.jdbc.SQLServerDriver --classpath="c:Program FilesMicrosoft JDBC Driver 4.0 for SQL Serversqljdbc_4.0enusqljdbc4.jar" --changeLogFile=databaseChangeLog.xml --url="jdbc:sqlserver://localhostDEVSQL01;databaseName=MyDatabase" --username=sa --password=xyz --logLevel="severe" update
Unexpected error running Liquibase: Validation Failed:
1 preconditions failed
databaseChangeLog.xml : Preconditions failed - dba user is required
SEVERE 29.07.2018, 14:50: liquibase: Validation Failed:
1 preconditions failed
databaseChangeLog.xml : Preconditions failed - dba user is requiredliquibase.exception.ValidationFailedException: Validation Failed:
1 preconditions failed
databaseChangeLog.xml : Preconditions failed - dba user is requiredat liquibase.changelog.DatabaseChangeLog.validate(DatabaseChangeLog.java:181)
at liquibase.Liquibase.update(Liquibase.java:191)
at liquibase.Liquibase.update(Liquibase.java:174)
at liquibase.integration.commandline.Main.doMigration(Main.java:997)
at liquibase.integration.commandline.Main.run(Main.java:170)
at liquibase.integration.commandline.Main.main(Main.java:89)
For more information, use the --logLevel flag
In that case, the precondition is verified before any changeset is attempted to apply. It becomes a prerequisite for the whole database upgrade.
Notice also that there is a predefined tag runningAs which checks the name of the database user who is running the upgrade. There are more such predefined conditions listed in the Liquibase manual.
Precondition 3 — complex case
One precondition per changeset would be too easy if it was always enough. Real life scenarios often require a little bit more. Fortunately, preConditions tag may contain multiple prerequisites joined together with logical operators.
<?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"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"><
preConditions onFailMessage="Preconditions failed - dba user, 10GB of free space and MSSQL or Oracle db engine">
<and>
<runningAs username="dba" />
<sqlCheck expectedResult="1">
select case when size/128.0 - cast(fileproperty(name, 'SpaceUsed') as int)/128.0 > 10000 then 1 else 0 end
from sys.database_files
where name = 'MyDatabase'
</sqlCheck>
<or>
<dbms type="mssql" />
<dbms type="oracle" />
</or>
</and></
preConditions><
changeSet id="Data migration" author="DBA presents">
<sql>
insert into NEWTABLE (ID, NAME, VALUE)
select ID, NAME, VALUE
from OLDTABLE;
</sql>
</changeSet></
databaseChangeLog>
The above example will allow the upgrade only when it is run by dba user, there is at least 10GB of space in the datafile and the upgrade is executed on SQL Server or Oracle.
Summary
Prerequisites are very common in database upgrades. We usually create checklists and manually verify them which is error prone. At some point it makes sense to automate at least some of them. The less items you have to remember about, the more creative your work can become. Preconditions are there to help you.
If anything goes wrong, the upgrade can be rolled back with autorollback feature.
If you are new to Liquibase watch my introduction to Liquibase and find more Liquibase articles.
Environment
Liquibase Version:
./bin/liquibase --version
####################################################
## _ _ _ _ ##
## | | (_) (_) | ##
## | | _ __ _ _ _ _| |__ __ _ ___ ___ ##
## | | | |/ _` | | | | | '_ / _` / __|/ _ ##
## | |___| | (_| | |_| | | |_) | (_| __ __/ ##
## _____/_|__, |__,_|_|_.__/ __,_|___/___| ##
## | | ##
## |_| ##
## ##
## Get documentation at docs.liquibase.com ##
## Get certified courses at learn.liquibase.com ##
## Free schema change activity reports at ##
## https://hub.liquibase.com ##
## ##
####################################################
Starting Liquibase at 17:02:20 (version 4.7.0 #1140 built at 2022-01-07 19:26+0000)
Liquibase Version: 4.7.0
Liquibase Community 4.7.0 by Liquibase
Running Java under /usr/java/jdk1.8.0_144/jre (Version 1.8.0_144)
Note: The same Liquibase changeSet worked in Liquibase Version: 3.10.2
Liquibase Integration & Version: CLI
Liquibase Extension(s) & Version:
Database Vendor & Version:
> SELECT version();
version
------------------------------------------------------------------------------
PostgreSQL 10.13 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.9.3, 64-bit
Operating System Type & Version:
$ cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
Description
foreignKeyConstraintExists
doesn’t seem to be working.
I’m trying to MARK_RAN
if foreignKey already exists so I just create the FK if it is absent.
This was working with Liquibase 3.10.2. I’m in the process of upgrading it in order to improve performance as a simple migration took multiple hours with the previous version.
Steps To Reproduce
<changeSet ...> <preConditions onFail="MARK_RAN"> <not> <foreignKeyConstraintExists foreignKeyName="FK_NAME"/> </not> </preConditions> <addForeignKeyConstraint constraintName="FK_NAME" ... /> </changeSet>
Actual Behavior
changeSet execution fails trying to execute the addForeignKeyConstraint
(since the constraint with that name already exists).
Expected/Desired Behavior
The precondition onFail should’ve been called (adding the changeSet as MARK_RAN
to liquibase changelog), and, as such, the changeSet shouldn’t execute addForeignKeyContstraint
.
Additional Context
I was able to implement a temporary workaround specific to the database vendor that confirms that the FK exists.
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">
SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_name='FK_NAME' AND table_schema = '<x>'
</sqlCheck>
</preConditions>
From my (limited) understanding, Liquibase does some sort of DB snapshot, which it uses to verify some of these pre-conditions (not the sqlCheck
) and it doesn’t seem to be importing the ForeignKey to that snapshot.
In our previous blog post, we discussed how we can apply different changelogs to different database environments. It is more than often, that when applying a changelog, changeset writer assumes database in a certain state. Like when you are adding a column to the database, you would assume that corresponding table is present. Or when you are dropping a table, it has no data in it. Or we assume that underlying database connection is of a particular nature. We can check for and decide what to do by using the concept of Preconditions in the Liquibase. Using preconditions allows to validate underlying assumption and decide the course of action. Preconditions can be attached to changelogs or changesets to control the execution of an update based on the state of the database.
Types of Preconditions and their meaning
Precondition Name | Checks Performed |
---|---|
dbms | Checks if the script is running against a specific DBMS (e.g. SQL Server or Oracle) |
runningAs | Checks if the script is running as a specific database user |
changesetExecuted | Checks if the changeset has already been executed |
tableExists | Checks if a table exists in the database |
columnExists | Checks if a column exists in a table in the database |
viewExists | Checks if a view exists in the database |
indexExists | Checks if a index exists in the database |
foreignKeyConstraintExists | Checks if a foreign key exists in a table |
primaryKeyExists | Checks if a table has the specified primary key |
sequenceExists | Checks if a sequence exists in the database |
sqlCheck | Runs a SQL command and checks the output returned. The SQL command must return a single value (one row and column) like the output of COUNT, SUM, MIN, MAX or AVG. |
Each of these preconditions can again have one or more attributes which are necessary to define the precondition.
Handling Failures and Errors
Naturally, you would want to take some actions when a precondition fails. There can be two scenarios:
Failure: When the precondition check fails
Error: When the check itself cannot be performed and an exception is thrown
Both of above situations can be handled by a number of attributes. Some of these include:
Attribute | Description |
---|---|
onFail | What to do when preconditions fail |
onError | What to do when preconditions error |
onUpdateSQL | What to do in updateSQL mode |
onFailMessage | Custom message to output when preconditions fail |
onErrorMessage | Custom message to output when preconditions fail |
Again, now that the underlying Precondition failed or the check for precondition itself failed, you need to decide the course of action. It can be set using below attributes:
Value | Description |
---|---|
HALT | Immediately halt the execution of the entire change log. [DEFAULT] |
CONTINUE | Skip over the change set. Execution of the change set will be attempted again on the next update. Continue with the change log. |
MARK_RAN | Skip over the change set, but mark it as executed. Continue with the change log. |
WARN | Output a warning and continue executing the change set/change log as normal. |
The possible actions for onUpdateSQL are a little different.
Fitting it all together
In our database, we have a table [dbo].[CustomerDetails] which has only two columns: CustomerTypeID and CustomerDesc. We want to add a new column to this table, say CustomerAddress but we want to make sure that the said table exists before we. For this, we can use below changelog:
Let’s go ahead and apply our change using liquibase update
. Since there are no preconditions fails, we’ll not see any indication of same in the logs:
Now, let’s try to apply below changelog where we are trying to drop a table:
Here, in the precondition we have specified that table contains no records. However, our table contains 2 records. Let’s run liquibase and see what happens:
We can see that liquibase command has failed to proceed as instructed. At this point, if we check records in the DATABASECHANGELOG table, we would see no new entries as no change has been made.
Conditional logic in Preconditions
We can use as many preconditions as we need to make sure underlying assumptions are valid. By default all preconditions are associated by AND. The possible association can be defined using AND, OR and NOT.
Для
бизнеса
-
По заинтересованным лицам
-
IT-лидеры
-
Независимые разработчики ПО
-
Архитекторы корпоративных приложений
-
По отраслям
-
Истории успеха
-
По вариантам использования
-
Модернизация приложений
-
Сокращение расходов на ПО
-
Автоматизация внутренних процессов