Liquibase, besides its the most obvious functionality which is making sure each change set is executed once on a database, it offers a possibility of handling some change sets in a different way. In most cases, it does not make sense to execute a change more than once. For example a table creation - if a table was created during migration to version 2, it should not be created again during migration to version 3. But some changes are more natural to be executed every time an upgrade occurs. Liquibase supports such cases and this is what I would like to share with you today.
Run-once change sets
As I stated in the introduction, most of the changes are one-time actions. Table creation, column extension, even row insertion are usually tied to a specific version. Any database migration to that specific version should involve executing scripts that do this work. However, if the database is already in this version, there is no point to execute those scripts - the table already exists, the column is wide enough, the row is where it should be. Moreover, the scripts would probably fail if executed again - CREATE TABLE A statement will throw an exception if the table A has already been created.
Run on change
There is a separate category of changes - repeatable. For instance, there might be some work needed after each database migration. It might not matter much what is the target version. It could be setting up some access, making sure settings are reset to default etc.
Sometimes it might be just easier to modify an existing change set instead of creating a new one based on differences. Lets take a look at the following Liquibase script.
<?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-3.1.xsd">
<changeSet id="create translations table" author="dba-presents.com">
<createTable tableName="translations">
<column name="TRANSL_ID" type="int" autoIncrement="true">
<constraints primaryKey="true" primaryKeyName="PK_TRANSLATIONS" />
</column>
<column name="TRANSL_KEY" type="varchar(100)" />
<column name="TRANSL_LANG" type="char(2)" />
<column name="TRANSL_VALUE" type="nvarchar(1000)" />
</createTable>
</changeSet>
<changeSet id="update translations" author="dba-presents.com" runOnChange="true">
<delete tableName="translations" />
<insert tableName="translations">
<column name="TRANSL_KEY" value="en" />
<column name="TRANSL_KEY" value="WEBSITE_NAME" />
<column name="TRANSL_VALUE" value="www.db-presents.com" />
</insert>
<insert tableName="translations">
<column name="TRANSL_KEY" value="en" />
<column name="TRANSL_KEY" value="BTN_LOGIN" />
<column name="TRANSL_VALUE" value="Login" />
</insert>
</changeSet>
</databaseChangeLog>
The first change set (create translations table) is a standard run-once change set. Table creation is a one-time action. Once it is done, I do not want it to be executed never again on this database (assuming it will not have been rolled back either). That is simple.
The second change set (update translations) fills the table with data - translations. It could have been done as a standard run-once change set also, but this data is prone to update. When one translation changes, a new change set could be created with an UPDATE statement. If it happens often it is ineffective. A better approach could be to modify the original change set to contain correct translations. However, according to the whole concept that Liquibase is built on, that change set cannot be executed again on the same database so the new value will not be applied. That is not good.
Fortunately, the changeSet tag has an attribute runOnChange that amends this behaviour when set to true. If Liquibase notices that the change set (update translations) has been modified since the last execution, it will rerun it. The change is recognized by calculating a hash of the new change set code and comparing it to the one saved in DATABASECHANGELOG.
Here is a sample scenario:
- The script is created.
- Liquibase is executed on database A - both change sets are executed automatically.
- Liquibase is executed again on database A - none of the change sets are applied.
- The update translations change set is modified.
- Liquibase is executed on database A - the update translations change set is reapplied.
I hope the runOnChange attribute is clear.
Run always
There is also another attribute that is related to the subject. It is runAlways. If it is set to true, everytime Liquibase is executed, the change set with this attribute is applied no matter it changed or not.