Preconditions

Preconditions are tags you add to your changelog or individual changesets to control the execution of an update based on the state of the database. Preconditions let you specify security and standardization requirements for your changesets. If a precondition on a changeset fails, Liquibase does not deploy that changeset.

Uses

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 changesets are run and not run based on the state of the database.

You can use all Liquibase preconditions in XML, YAML, and JSON changelogs. Only some preconditions are supported in SQL changelogs. For a list of preconditions, see Available preconditions.

Syntax

You can use one local <preConditions> tag per changeset and one global <preConditions> tag in the changelog (outside any changeset). In XML, YAML, and JSON changelogs, you can use conditional logic to add multiple preconditions within a single <preConditions> tag.

Preconditions at the changelog level are applied to all changesets, not just those listed in the current changelog or its child changelogs.

The following example XML, YAML, and JSON changelogs will only be run if the database executed against is Oracle and the database user executing the script is SYSTEM. Also, they will run the changeset with the sqlCheck precondition and createTable. The example SQL changelog only runs the sqlCheck precondition and createTable.

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 precondition values.

Example changelogs with preconditions

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<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">

    <preConditions>
        <dbms  type="oracle"  />
        <runningAs  username="SYSTEM"  />
    </preConditions>

    <changeSet  id="1"  author="Liquibase User">
        <preConditions  onFail="WARN">
            <sqlCheck  expectedResult="0">SELECT COUNT(*) FROM example_table</sqlCheck>
        </preConditions>
        <comment>Comments should go after the precondition. Otherwise, Liquibase returns an error.</comment>

        <createTable  tableName="example_table">
            <column name="id" type="int" autoIncrement="true">
                <constraints primaryKey="true" nullable="false">
            </column>
            <column name="firstname" type="varchar(50)">
            <column name="lastname" type="varchar(50)">
                <constraints nullable="false">
            </column>
            <column name="state" type="char(2)">
        </createTable>
    </changeSet>
</databaseChangeLog>
databaseChangeLog:
 - preConditions:
     - dbms:
        type: oracle
     - runningAs:
        username: SYSTEM
 - changeSet:
     id:  1
     author: Liquibase User
     preConditions:
       - onFail: WARN
       - sqlCheck:
          expectedResult: 0
          sql: SELECT COUNT(*) FROM example_table
     comment: Comments should go after the precondition. Otherwise, Liquibase returns an error.
     changes:
     - createTable:
         tableName: example_table
         columns:
          - column:
             name: id
             type: int
             autoIncrement: true
             constraints:
              primaryKey: true
              nullable: false
          - column:
             name:  firstname
	         type:  varchar(50)
          - column:
             name:  lastname
             type:  varchar(50)
             constraints:
              nullable:  false
          - column:
             name:  state
             type:  char(2)
{
  "databaseChangeLog": [
    {
      "preConditions": [
        {
          "dmbs": {
            "type": "oracle"
          }
        },
        {
          "runningAs": {
            "username": "SYSTEM"
          }
        }
      ],
      "changeSet": {
        "id": "1",
        "author": "Liquibase User",
        "preConditions": [
          {
            "onFail": "WARN"
          },
          {
            "sqlCheck": {
              "expectedResult": 0,
              "sql": "SELECT COUNT(*) FROM example_table"
            }
          }
        ],
        "comment": "Comments should go after the precondition. Otherwise, Liquibase returns an error.",
        "changes": [
          {
            "createTable": {
              "tableName": "example_table",
              "columns": [
                {
                  "column": {
                    "name": "id",
                    "type": "int",
                    "autoIncrement": true,
                    "constraints": {
                      "primaryKey": true,
                      "nullable": false
                    }
                  }
                },
                {
                  "column": {
                    "name": "firstname",
                    "type": "varchar(50)"
                  }
                },
                {
                  "column": {
                    "name": "lastname",
                    "type": "varchar(50)",
                    "constraints": {
                      "nullable": false
                    }
                  }
                },
                {
                  "column": {
                    "name": "state",
                    "type": "char(2)"
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
}
--liquibase formatted sql
			
--changeset Liquibase User:1
--preconditions onFail:WARN 
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM example_table
--comment: /* Comments should go after the precondition. Otherwise, Liquibase returns an error. */
create table example_table (  
	id int primary key,
	firstname varchar(50),
	lastname varchar(50) not null,
	state char(2)
)

Multiple preconditions

In XML, JSON, and YAML changelogs, you can set multiple preconditions in one <preConditions> tag by using nestable AND, OR, and NOT conditional logic tags. If no conditional tags are specified, the default value is AND.

Liquibase uses lazy evaluation (call-by-need) when running preconditions. For example, if the first condition of an AND expression fails, Liquibase does not evaluate the second condition. If the first condition of an OR expression succeeds, Liquibase does not evaluate the second condition.

<preConditions>
    <or>
        <CONDITION_1>
        <CONDITION_2>
    </or>
</preConditions>
- preConditions:
    - or:
        - CONDITION_1
        - CONDITION_2
{
  "preConditions": [
    {
      "or": [
        {
          {CONDITION_1}
        },
        {
          {CONDITION_2}
        }
      ]
    }
  ]
}

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:

<preConditions  onFail="WARN">
    <dbms  type="oracle"  />
    <runningAs  username="SYSTEM"  />
</preConditions>

The following will require running the changelog on Oracle or MySQL:

<preConditions>
   <or>
      <dbms  type="oracle"  />
      <dbms  type="mysql"  />
   </or>
</preConditions>

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.

<preConditions>
    <or>
       <and>
          <dbms  type="oracle"  />  
          <runningAs  username="SYSTEM"  />
       </and>
       <and>
          <dbms  type="mssql"  />
          <runningAs  username="sa"  />
       </and>
     </or>
</preConditions>

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 attributes on the <preConditions> tag.

Available attributes

Attribute Description
onError Controls what happens if there is an error checking whether the precondition passed or not.
onErrorMessage Provides a custom message to output when preconditions fail. Since 2.0
onFail Controls what happens if the preconditions check fails.
onFailMessage Provides a custom message to output when preconditions fail. Since 2.0
onSqlOutput Controls how preconditions are evaluated with the update-sql command for XML, YAML, and JSON changelogs. Since 1.9.5
onUpdateSql Controls how preconditions are evaluated with the update-sql command for formatted SQL changelogs.

onFail/onError precondition values

Value Description
CONTINUE Skips over the changeset. Execution of the changeset will be attempted again on the next update. Continues with the changelog.
HALT Halts the execution of the entire changelog (default). HALT can be put outside a changeset (e.g. at the beginning of the changelog).
MARK_RAN Skips over the changeset but mark it as executed. Continues with the changelog.
WARN Sends a warning and continues executing the changeset / changelog as normal. WARN can be put outside a changeset (e.g. at the beginning of the changelog).

onSqlOutput/onUpdateSql values

Value Description
FAIL When you run the update-sql command, Liquibase fails any preconditions.
IGNORE When you run the update-sql command, Liquibase ignores any preconditions.
TEST When you run the update-sql command, Liquibase evaluates preconditions normally.

Failures and errors will cause Liquibase to return non-zero exit codes in command output. Warnings will cause Liquibase to return exit codes of zero.

Example failures/errors

The following is a sample failure on a changelog:

Unexpected error running Liquibase: Validation Failed:
1 preconditions failed
myChangelog.xml : DBMS Precondition failed: expected oracle, got postgresql

The following is a sample error on a changelog:

Unexpected error running Liquibase: Validation Failed:
1 preconditions generated an error
myChangelog.xml : liquibase.precondition.core.SqlPrecondition@5745ca0e : ERROR: relation "public.table_does_not_exist" does not exist

The following is a sample warning on a changeset:

WARNING: Executing myChangelog.xml::01-new::wonder.woman despite precondition failure due to onFail='WARN':
1 preconditions failed
myChangelog.xml : Column public.customer.name_new does not exist

Available preconditions

The examples of preconditions shown in the table doesn't include the schemaName attribute. It is best practice not to keep the schemaName in the changeset and rely on the default schema except for times you don't want to create something in the default schema.

Additionally, you can include the onError or onFail attributes with the WARN, HALT, CONTINUE, or MARK_RAN value in the preconditions tag, however, the CONTINUE and MARK_RAN options can only be applied to preconditions inside a changeset.

changeLogPropertyDefined

Checks whether given changelogattribute is present. It fails if the value is not the same as given.

Attribute Description Requirement
property

The name of the property to check.

Required
value The required value for a given property. Optional
<preConditions onFail="WARN">
    <changeLogPropertyDefined property="myproperty"
                              value="requiredvalue"/>
</preConditions>
- preConditions:
    - onFail: WARN
    - changeLogPropertyDefined:
       property: myproperty
       value: requiredvalue
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "changeLogPropertyDefined": {
        "property": "myproperty",
        "value": "requiredvalue"
      }
    }
  ]
}

changeSetExecuted

Defines if the specified changeset has already been executed.

Attribute Description Requirement
id The changeset id. Required
author The changeset author. Required
changeLogFile The file name (including searchPath relative path) of the changeset. Required
<preConditions onFail="HALT">
    <changeSetExecuted id="1"
                          author="liquibase"
                          changeLogFile="changelog.xml" />
</preConditions>
- preConditions:
    - onFail: HALT
    - changeSetExecuted:
       id: 1
       author: liquibase
       changeLogFile: changelog.xml
{
  "preConditions": [
    {
      "onFail": "HALT"
    },
    {
      "changeSetExecuted": {
        "id": "1",
        "author": "liquibase",
        "changeLogFile": "changelog.xml"
      }
    }
  ]
}

columnExists

Defines if the specified column exists in the database.

Attribute Description Requirement
columnName The name of the column. Required
tableName The name of the column's table. Required
schemaName The name of the table's schema. Optional
<preConditions onFail="WARN">
    <columnExists tableName="table1"
                  columnName="column1" />
</preConditions>
- preConditions:
    - onFail: WARN
    - columnExists:
       tableName: table1
       columnName: column1
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "columnExists": {
        "tableName": "table1",
        "columnName": "column1"
      }
    }
  ]
}

dbms

Defines if the database executed against matches the type specified.

Attribute Description Requirement
type

The type of database expected. Multiple dbms values can be specified using comma-separated values.

Required
<preConditions onFail="WARN">
  <or>
    <dbms  type="oracle"  />
    <dbms  type="mysql"  />
  </or>
</preConditions>
- preConditions:
    - onFail: WARN
    - or:
       - dbms:
          type: oracle
       - dbms:
          type: mysql
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "or": [
        {
          "dbms": {
            "type": "oracle"
          }
        },
        {
          "dbms": {
            "type": "mysql"
          }
        }
      ]
    }
  ]
}

foreignKeyConstraintExists

Defines if the specified foreign key exists in a table in the database.

Attribute Description Requirement
foreignKeyName The name of the foreign key. Required
foreignKeyTableName The name of the table the foreign key is in. Required in 4.7.0+
schemaName The name of the foreign key's schema. Optional
<preConditions onFail="WARN">
    <foreignKeyConstraintExists foreignKeyName="PK_2354"
                                foreignKeyTableName="myTable" />
</preConditions>
- preConditions:
    - onFail: WARN
    - foreignKeyConstraintExists:
       foreignKeyName: PK_2354
       foreignKeyTableName: myTable
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "foreignKeyConstraintExists": {
        "foreignKeyName": "PK_2354",
        "foreignKeyTableName": "myTable"
      }
    }
  ]
}

indexExists

Defines if the specified index exists in the database. You can either specify the indexName attribute or tableName and columnNames attributes.

Note: There are a few databases where the indexName is not unique, which is why both indexName and tableName can be used.

Attribute Description Requirement
indexName The name of the index. Optional
columnNames The name of the column. Optional
tableName The name of the table. Optional
schemaName The name of the index's schema. Optional
<preConditions onFail="WARN">
    <indexExists indexName="originindex"/>
</preConditions>
<preConditions onFail="WARN">
    <indexExists tableName="MY_NEW_TABLE"
                 columnNames="ID"/>
</preConditions>
<preConditions onFail="WARN">
    <indexExists indexName="originindex"
                 tableName="MY_NEW_TABLE"/>
</preConditions>
- preConditions:
    - onFail: WARN
    - indexExists:
       indexName: originindex
- preConditions:
    - onFail: WARN
    - indexExists:
       tableName: MY_NEW_TABLE
       columnNames: ID
- preConditions:
    - onFail: WARN
    - indexExists:
       indexName: originindex
       tableName: MY_NEW_TABLE
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "indexExists": {
        "indexName": "originindex"
      }
    }
  ]
}
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "indexExists": {
        "tableName": "MY_NEW_TABLE",
        "columnNames": "ID"
      }
    }
  ]
}
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "indexExists": {
        "indexName": "originindex",
        "tableName": "MY_NEW_TABLE"
      }
    }
  ]
}

primaryKeyExists

Defines if the specified primary key exists in the database.

Attribute Description Requirement
primaryKeyName The name of the primary key constraint. Either tableName or primaryKeyName is required
tableName The name of the table containing primary key. Either tableName or primaryKeyName is required

Note: On DB2, H2, HSQLDB, MySQL, and SQLite, tableName is required even if you also specify primaryKeyName.

schemaName The name of the primary key's schema. Optional
<preConditions onFail="HALT">
    <primaryKeyExists primaryKeyName="ID_2354"
                      tableName="table1" />
</preConditions>
- preConditions:
    - onFail: HALT
    - primaryKeyExists:
       primaryKeyName: ID_2354
       tableName: table1
{
  "preConditions": [
    {
      "onFail": "HALT"
    },
    {
      "primaryKeyExists": {
        "primaryKeyName": "ID_2354",
        "tableName": "table1"
      }
    }
  ]
}

rowCount

Checks that the number of rows in a table matches an expected value.

Attribute Description Requirement
expectedRows The number of rows the user expects in a table. Required
tableName The name of the column's table. Required
<preConditions onFail="WARN">
    <rowCount expectedRows="5"
              tableName="person"/>
</preConditions>
- preConditions:
    - onFail: WARN
    - rowCount:
       expectedRows: 5
       tableName: person
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "rowCount": {
        "expectedRows": "5",
        "tableName": "person"
      }
    }
  ]
}

runningAs

Defines if the database user executed under matches the username specified.

Attribute Description Requirement
username The database user script which is expected to run as. Required
<preConditions  onFail="WARN">
    <dbms  type="oracle"  />
    <runningAs  username="SYSTEM"  />
</preConditions>
- preConditions:
    - onFail: WARN
    - dbms:
       type: oracle
    - runningAs:
       username: SYSTEM
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "dbms": {
        "type": "oracle"
      }
    },
    {
      "runningAs": {
        "username": "SYSTEM"
      }
    }
  ]
}

sequenceExists

Defines if the specified sequence exists in the database.

Attribute Description Requirement
sequenceName The name of the sequence. Required
schemaName The name of the sequence's schema. Optional
<preConditions onFail="WARN">
    <sequenceExists sequenceName="idnumber" />
</preConditions>
- preConditions:
    - onFail: WARN
    - sequenceExists:
       sequenceName: idnumber
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "sequenceExists": {
        "sequenceName": "idnumber"
      }
    }
  ]
}

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.
Attribute Description Requirement
expectedResult The value to compare the SQL result to. Required
sql In JSON and YAML changelogs, the SQL statement to run. In XML changelogs, insert the SQL statement directly within the <sqlCheck></sqlCheck> tags. IN SQL changelogs, insert the SQL statement directly in the precondition header. Required
<preConditions onFail="WARN">
    <sqlCheck expectedResult="1">
        SELECT COUNT(1)
        FROM pg_tables
        WHERE TABLENAME = 'myRequiredTable'
    </sqlCheck>
</preConditions>
- preConditions:
    - onFail: WARN
    - sqlCheck:
       expectedResult: 1
       sql: SELECT COUNT(1) FROM pg_tables WHERE TABLENAME = 'myRequiredTable'
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "sqlCheck": {
        "expectedResult": "1",
        "sql": "SELECT COUNT(1) FROM pg_tables WHERE TABLENAME = 'myRequiredTable'"
      }
    }
  ]
}
--changeset your.name:1
--preconditions onFail:WARN
--precondition-sql-check expectedResult:1 SELECT COUNT(1) FROM pg_tables WHERE TABLENAME = 'myRequiredTable'
create table example_table (
	id int primary key,
	firstname varchar(50),
	lastname varchar(50) not null,
	state char(2)
)

tableExists

Defines if the specified table exists in the database.

Attribute Description Requirement
tableName The name of the table. Required
schemaName The name of the table's schema. Optional
<changeSet author="author" id="1">
   <preConditions onFail="MARK_RAN">
       <not>
           <tableExists tableName="MY_NEW_TABLE"/>
       </not> 
   </preConditions>

   <createTable tableName="MY_NEW_TABLE">
       <column name="IDX"
               type="${integer.type}">
           <constraints nullable="false"/> 
       </column>
       <column name="INTVAL"
               type="${integer.type}"/>
   </createTable>
</changeSet>
- changeSet:
    preConditions:
      - onFail: MARK_RAN
      - not:
          - tableExists:
             tableName: MY_NEW_TABLE
    changes:
      - createTable:
          tableName: MY_NEW_TABLE
          columns:
            - column:
               name: INTVAL
               type: ${integer.type}
               constraints:
                 nullable: false
{
  "changeSet": {
    "preConditions": [
      {
        "onFail": "MARK_RAN"
      },
      {
        "not": [
          {
            "tableExists": {
              "tableName": "MY_NEW_TABLE"
            }
          }
        ]
      }
    ],
    "changes": [
      {
        "createTable": {
          "tableName": "MY_NEW_TABLE",
          "columns": [
            {
              "column": {
                "name": "INTVAL",
                "type": "${integer.type}",
                "constraints": {
                  "nullable": "false"
                }
              }
            }
          ]
        }
      }
    ]
  }
}

Note: tableExists is supported for SQL changelogs in Liquibase 4.27.0 and later.

--changeset your.name:1
--preconditions onFail:MARK_RAN
--precondition-table-exists table:table1 schema:12345
create table table2 (
    id int primary key
);

uniqueConstraintExists

Checks for the existence of unique constraints before running the update. (since Liquibase 4.9.0)

Attribute Description Requirement
tableName The name of the column's table. Required
columnNames The name of the column. Optional
constraintName The name of the unique constraint. Optional
<preConditions onFail="WARN">
    <uniqueConstraintExists constraintName="uq_constraint_name"
                            tableName="test_table"/>
</preConditions>
<preConditions onFail="WARN">
    <uniqueConstraintExists tableName="test_table"
                            columnNames="first_name, last_name"/>
</preConditions>
- preConditions:
    - onFail: WARN
    - uniqueConstraintExists:
       constraintName: uq_constraint_name
       tableName: test_table
- preConditions:
    - onFail: WARN
    - uniqueConstraintExists:
       tableName: test_table
       columnNames: first_name, last_name
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "uniqueConstraintExists": {
        "constraintName": "uq_constraint_name",
        "tableName": "test_table"
      }
    }
  ]
}
{
  "preConditions": [
    {
      "onFail": "WARN"
    },
    {
      "uniqueConstraintExists": {
        "tableName": "test_table",
        "columnNames": "first_name, last_name"
      }
    }
  ]
}

viewExists

Defines if the specified view exists in the database.

Attribute Description Requirement
viewName The name of the view. Required
schemaName The name of the view's schema. Optional
<preConditions onFail="HALT">
    <viewExists viewName="liquibase" />
</preConditions>
- preConditions:
    - onFail: HALT
    - viewExists:
       viewName: liquibase
{
  "preConditions": [
    {
      "onFail": "HALT"
    },
    {
      "viewExists": {
        "viewName": "liquibase"
      }
    }
  ]
}

Note: viewExists is supported for SQL changelogs in Liquibase 4.27.0 and later.

--changeset your.name:1
--preconditions onFail:MARK_RAN
--precondition-view-exists view:view1 schema:12345
create table table1 (
    id int primary key
);

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 <param> sub-tags. Pass parameters as strings to the custom precondition.

Attribute Description Requirement
className The name of the custom precondition class. Required

<param> sub-tag

The parameter to pass to the custom precondition.

Attribute Description Requirement
name The name of the parameter to set. Required
value A string value to set parameter to. Required
<customPrecondition className="com.example.CustomTableCheck">
     <param name="tableName"
            value="our_table"/>
     <param name="count"
            value="42"/>
</customPrecondition>
- customPrecondition:
    - className: com.example.CustomTableCheck
    - param:
       name: tableName
       value: our_table
    - param:
       name: count
       value: 42
{
  "customPrecondition": [
    {
      "className": "com.example.CustomTableCheck"
    },
    {
      "param": {
        "name": "tableName",
        "value": "our_table"
      }
    },
    {
      "param": {
        "name": "count",
        "value": "42"
      }
    }
  ]
}