Contents

Liquibase can be an immense help when it comes to the management of your Oracle Database version control changes, saving you a lot of time. Here’s how to use this technology to your advantage in actual projects – I’ll show you this using the example of a real-life case from my work.

Liquibase is a powerful and flexible tool that can help you take care of your database version control management. I wrote about this solution in a couple of my previous articles so if you haven’t read them yet,I encourage you to do so – last year I’ve covered the Liquibase basics and GIT Collaboration. Now I’d like to deal with other interesting topics.

In this blog post, I’ll show you how to: 

  • customize an existing project to work with Liquibase (with minimal effort)
  • synchronize all existing database objects
  • create new changes with the help of snippets (minimum writing, maximum automatization)
  • automate package numbering and control via context and labels 
  • find and check potential, critical changes before their deployment (Liquibase checks function)
  • create SQLcl scripts for the administrator of your application (assuming the company’s policy doesn’t allow the use of external tools on production, or you don’t have access to production at all)
  • use pre-conditions to deploy changes only in certain cases

Interested? I certainly hope so. Let’s begin!

Prerequisites

Here’s what you’ll need to make full use of the information provided in this article:

An image showing the existing objects.

All my objects are stored in the GIT repository in my local LB_VERSION_CONTROL_PERFECTION\database folder. The files are clean – I’ve just exported them from my SQL Developer. Here’s how my repository and sample object looks.

A screen showing the author's database folder.

STEP 1: Install Liquibase

If you haven’t already, you obviously need to install Liquibase on your PC. If you’re not sure how to do that, check my aforementioned Liquibase tutorial from March 2021 – I’ve covered this topic in more detail there, and there’s no point in repeating the same information here. Besides, it’s a pretty simple process anyway.

STEP 2: Configure your project to work with Liquibase

To configure your project properly, you need to go through a few steps:

Create a liquibase.properties file

You need to create the liquibase.properties file, or, alternatively, you can use my sample where I use my local XE database.

If you want to read more about how a Liquibase property works, you can always check the extensive documentation made available by the technology’s developers. Also, if you want to connect your project to your Oracle ATP database in the cloud, check the fourth subsection in my tutorial.

Create a folder for OJDBC

As long as you point to our driver in liquibase.properties, you need to create a folder and put the OJDBC there. The final result should look like this:

A screen showing driver properties.

Create a folder for changelogs

A “changelog” is the root (a ledger) of all your database changes. You need to create a root changelog file master.xml inside the changelog folder (of course, file and folder names don’t really matter – these are just the ones I used). It will include locations of smaller changelogs, grouped by database object types.

A screen showing the master.xml file.

I will mainly use SQL changelogs in this tutorial, but to maintain the order of executing changes, you need changelogs written in .XML. Every .XML changelog needs to start like this:

<?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.4.xsd">

 

<!--PUT YOUR FILES / OTHER CHANGELOGS HERE–/>

 

</databaseChangeLog>

Of course, you also need those smaller changelogs. 

  1. Tables
  2. Reference constraints
  3. Other coexisting (non-replaceable) database objects – like sequences
  4. Replaceable objects

Below is a screenshot that shows you how every smaller changelog looks inside. As you can see, there is a direct path pointing to the   folders with database objects. Liquibase will execute scripts from those folders, one by one, in the provided order.

A screen showingthe changelog order execution.

What does “includeAll” tag mean? 

It allows you to specify a directory that contains multiple changelog files – take all files inside of that folder and execute them (unless they were already executed).

Important notice:

  • All files inside these folders (like /database/tables/) must have a .sql extension (they could also be .json,.xml or .yaml – but I won’t cover this today)
  • Files inside the folder, e.g. tables, will be executed in alphabetical order

Depending on your project needs, you can create as many smaller changelogs as you want. Just remember: the order of these changelogs in master.xml is the order in which Liquibase will execute them. Order your changelogs in the way you would normally execute scripts during manual deployments, for example:

  1. Create tables
  2. Create reference constraints to those tables 
  3. Packages
  4. Dml scripts executing procedures from packages etc.

STEP 3: Synchronize your existing objects with Liquibase 

In simpler words, you need to tell Liquibase that those objects already exist, and that it’s not supposed to touch them until you change something in these already existing files.

Generate a script preview

In Your Visual Studio Code, run the PowerShell terminal (or other, if you prefer) from the parent location of your database folder. In my example it’s LB_VERSION_CONTROL_PERFECTION\database.

An image showing how to open the terminal.

Then, run the following command:

liquibase ChangelogSyncSQL

A screenshot showing the changelogsync command.

PRO TIP: Adding “SQL” to many Liquibase commands will generate a preview SQL that would normally be executed. It’s a good practice to ALWAYS review your changes by making a script preview. 

You can find the output SQL script of my command in my repo.

As you may have noticed if you analyzed the file, the script is only making inserts into the DATABASECHANGELOG table. Liquibase will create those tables automatically for you. More information about them can be found in my basic tutorial and the technology’s documentation.

In this case, making inserts only to the DATABASECHANGELOG table is ok, since I don’t want to execute my scripts because they already exist in my database. That’s why I used the changelogSync command.

Liquibase treats all your SQL files in folders such as tables, index, and so on, as small changes that should be executed to the database (and it would be executed if I used the update command instead of changelogSync). Such a small change is called a CHANGESET. It’s a unit of change that Liquibase executes to your database. The changes created by multiple changesets are tracked in CHANGELOGS.

Easy right? You’ve already created some changelogs (pointing to folders with changesets) in STEP 2.

A screenshot showing changelogs and changesets.

Execute the script

So, after checking my script generated via the ChangelogSyncSQL command, now I can run it without the sql added at the end.

liquibase changelogSync

You can check the result of that execution in the DATABASECHANGELOG tracking table. The number of SQL scripts in your database folder equals the number of rows in the tracking table.

An image showing the DB changelog.

Now, Liquibase knows that those objects were created earlier. So, running the liquibase changelogSyncSQL command once again will not generate any sql script to execute. Also, running the update command will not make any changes to the database.

Notice that we don’t have much information in the DATABASECHANGELOG columns like  ID, AUTHOR, CONTEXT and LABELS. That makes versioning for a specific LABEL or CONTEXT basically impossible.

In ideal world, every object in your repo (every change called changeset) should be followed with some Liquibase syntax, like this:

A screenshot showing an example changeset.

And the result in the tracking table will look like this (much more info here, isn’t there?):

A screenshot showing a DB changelog with more information.

Now I have the following information displayed:

  • author
  • ID of change
  • context (you can put project or version name here)
  • labels ( it’s common to type task number there)
  • comment

So… wait! What? Should I write those 3 lines to all of the objects in the repository? I have dozens of objects – and you probably have even hundreds! Yes… you should. However, I’ve created a PowerShell (PS) script that will make that work for you in just a few seconds. 🙂 You can find the script in my repo.

If your repository looks like mine, you probably won’t have to do anything – just run the script. Otherwise, adjust it a little to fit your folders (It’s my debut when it comes to writing PS scripts, so please be understanding). After running the PS script my changesets look like this:

An image showing the changeset after running the PS script.

So now, all changesets have:

  • ID: your file name
  • Author: liquibase
  • Context: liquibase_project_start
  • Labels: liquibase_project_start
  • Comment
  • Required liquibase tags

So, let’s run synchronization command once again:

liquibase ChangelogSync

If you’ve already executed this command earlier, just delete all old rows from the DATABASECHANGELOG table – we’ll cover rollbacks later.

And here’s the final, ideal result in my DATABASECHANGELOG table:

A screenshot showing the database changelog after synchronization.

Well done. You have successfully synchronized existing database objects with Liquibase. 

Liquibase won’t execute any changes to those objects until you change something or add new changesets.

STEP 4: Track your new database changes / create new changesets

As you know from my basic tutorial, Liquibase needs a changeset to track your changes. If your changeset is replaceable (like a package or a function), you just change the object and Liquibase will do the rest. If your changeset is associated with a non-replacable object (such as “create table”), you can’t modify it. You should write a new changeset, for example “alter table”.

My goal is to simplify creating changesets. Many developers don’t know Liquibase or don’t want to learn new syntax – that’s why I created snippets for Visual Studio Code. It will simplify your work, help avoid mistakes and standardize the usage of Liquibase in the project.

How to install snippets in Visual Studio Code?

  • Go to File -> Preferences -> User Snippets -> New Global Snippets File
  • Type snippets file name -> pretius_liquibase
  • Replace file content with .extras/ pretius_liquibase.code-snippets (included in my repo)
  • Press Ctrl+S to save changes

How to use the snippets

  • Let’s assume I want to add a new table to my project (so it’s a NON-replaceable changeset)
  • I create new file NEW_TABLE.sql in /database/table/
  • Then, I start typing lb (LB is a shortcut for Liquibase)

An image showing the usage of snippets.

  • Now, I choose the appropriate non-replaceable snippet

 

A screen showing the chosen snippets.

Here we go – we have created a changeset. Now, just use TAB and your cursor will skip to places you should modify.

What about snippets for replaceable files? Let’s create a view file NEW_VIEW.sql and choose a replaceable snippet:

An image showing a replaceable snippet.

Even better, right? Your changeset ID was automatically filled with the file name NEW_VIEW. If you want you can adjust snippets to your needs, and I’ll probably add more of them in the future (ones specific for table, package and views changes). Stay tuned, and check my GitHub repository regularly.

I’ve added scripts to my NEW_TABLE.sql and NEW_VIEW.sql changesets:

An image showing the new changesets.

I will preview the Liquibase Update script:

liquibase updateSQL

An image showing the preview.

Looks okay, so I will execute the changes by running: 

liquibase update

An image showing the executed update.

My new table and view were created. I also have 2 new rows in the databasechangelog tracking table. Take a look at CONTEXT and LABELS columns -> context = version_1, labels = jira_task_1. Always add context and labels to your changesets. It will help you with deployment versioning.

Do you need developers who know Liquibase?

STEP 5: Automate your deployments versioning

Example 1: Deploy “version_1”

Now, I’ll show you how you can quickly version your deployments. Sometimes the information from the DATABASECHANGELOG table is enough, but do you often have a requirement to insert some logs at the beginning and at the end of your deployment? For example, we want information about the version start and the version end in the table called APP_VERSION_LOGS.

My table existed at the beginning of my project, so I’ve added a new changeset in location /database/table/app_version_logs.sql

Now, let’s see what I have by grouping tracking table changes:

A screen showing the grouped contexts.

  • 2 changes from “Version 1”
  • 48 changes from “liquibase_project_start” (those are ones that existed before implementing Liquibase to my project)

In most of my projects, CONTEXT is responsible for version or project name, and I will stick to it. Now, I want to use Liquibase to add INSERT into my APP_VERSION_LOGS every time I create a new version script. How to prepare it?

I’ve created 2 changelogs in the /release/ folder:

  • changelog/release/changelog_release_start.xml
  • changelog/release/changelog_release_end.xml

Those changesets will be responsible only for deployment versioning. I also specified them  in my master.xml changelog, and created two “liquibase property” tags with “version number” and “version author”.

An image showing the changelog release.

Now, let’s go to my changelog_release_start.xml.

A screen showing the changelog release start.

There is 1 defined changeset with values:

<changeSet  author="${version_author}"  id="START_${version_number}" failOnError="true" runOnChange="true" context="${version_number}" labels="${version_number}">

I have also used ${version_number} at insert and rollback (delete) statements. Think of ${version_author} and ${version_number} as variables you can use among your Liquibase files. I’ve defined them only once (in the master.xml file) and will use them in many places.

My changelog_release_end.xml file looks similar but instead of “START”, the value there is “END”.

Let’s preview the Liquibase SQL script now:

liquibase updateSQL

The result can be found in the version_1_release_script.sql file in my repo. As you can see there, Liquibase generated two insert statements into the app_version_logs table – just as I’ve wanted. Okay. Now I want my last 2 changes (with label = jira_task_1) deployed as “version_1”.

A screen showing a Jira task rollback.

First, I will rollback those changes, by using the following command:

liquibase rollbackToDate 2022-01-13'T'12:00:00 --labels=jira_task_1

The command basically says “rollback changes with label jira_task_1 to a state before a specified date”.

And now, I have to deploy again with versioning changesets (inserts into APP_VERSION_LOGS).

liquibase update

So, the final result is:

  • we have 4 new rows in the DATABASECHANGELOG table
  • there are 2 new rows in the APP_VERSION_LOGS

A screen showing the version_1 output.

Example 2: Deploy “version_2”

Okay, so I’ve created some changesets. Now, where’s that promised automation? Read on. 

First, let’s create the “version_2” of my deployment script. It will contain 2 changes:

A screen showing the version_2 changesets.

In “version_2”, I want these changes executed, along with additional inserts into APP_VERSION_LOGS.

Should I change changelog_release_start.xml and changelog_release_end.xml files again? No! I just need to change the value of properties in the master.xml file.

A screen showing the master.xml file of version_2.

Let’s preview the Liquibase script:

liquibase updateSQL

If you run the command above, you should get what you can find in the version_2_release_script.sql file in my repo. Now, if you execute the script:

liquibase update

The result should look like this:

An image showing the version_2 output.

STEP 6: No production access! How to benefit from using Liquibase?

All of the above is great and should work in various circumstances. But what if:

  • client’s policy doesn’t allow you to run external tools like Liquibase on the production database
  • the client’s administrators are responsible for production deployments and they want SQLcl scripts to be delivered with every deployment (often happens in the banking industry)

So, how can we create Liquibase changesets with additional SQLcl specific syntax (note that the free Liquibase Community Edition doesn’t support SQLcl syntax)? 

We can handle this as well. I’ll show you how, using the example of “version_3”. Let’s assume that this version should generate an SQL script – a script for database admins with SQLcl specific tags like SET DEFINE off, spool, prompt or spool off. Besides this, I want to run Liquibase the same as usual on my DEV environment (with full access).

I’ve added a new changeset at the beginning of my changelog_release_start.xml file. It will set a specific SQLcl syntax. I did the same with the changelog_release_end.xml file.

An image showing the set SQLcl.

I’ve also added a small change to my objects for “version_3”.

An image showing the version_3 object change.

After that, running the liquibase update command would normally fail, because it doesn’t recognize SQLcl specific syntax (it can handle this in the Pro version).

An image showing the set SQLcl update error.

But I can still generate an SQL file for the client’s admins:

liquibase updateSQL

The result looks like this (this is the version_3_SQL_CL_release_script.sql file in my GitHub repo).

However, I also want to run the liquibase update command on my DEV environment – just to have the same changes on both environments. That’s why I’ve added the failOnError=”false” parameter to those SQLcl specific changesets. So, now Liquibase will ignore errors in changesets with SQLcl syntax. It won’t show the errors.

An image showing the fail on error communicate.

So, the final result of the liquibase update command is:

A screen showing the version_3 output.

But how to test whether this SQLcl script will pass on production (without access)? I have an easy way to do that (example with the “version_3”).

  • Rollback “version_3” changes already made on DEV
liquibase rollbackToDate 2022-01-13'T'12:00:00 --contexts=version_3
  • Generate the SQL script for client’s administrators
liquibase updateSQL
  • Run the generated script through SQLcl at my DEV database.
  • If it passed, then run changes to DEV through Liquibase once again
liquibase update
  • Send the script to admins 🙂

STEP 7: Catching critical database changes and quality issues before deployment

Imagine that I am a developer responsible for deployment on a UAT or PROD environment. My team pushed some changes into the GIT branch called “version_4”.

An image showing version_4 changes.

Have a look at the lines of code with red marks… What’s wrong with it? Got it?

  • missing comment in changeset,
  • no label defined,
  • critical DROP TABLE command.

Normally, you would probably ask the developer to add a comment, label, and explain why there is a need for a DROP table, right?

But first, you would have to find those missing/critical changes. What if there were hundreds of changes to check? We don’t have time for this.

That’s why I prefer to use the “liquibase checks” feature instead. In order to use them, you have to:

  • Initialize the “check” configure file by running the command and selecting “Y”.
liquibase checks run

It will create a new file liquibase.checks-settings.conf in the /database/ folder.

  • Find out what type of checks are available.
Liquibase checks show

The list of checks will be generated into a sql_preview.sql file (you can read more about Liquibase checks here).

  • As I am using the Liquibase Community Edition, I will have to disable some checks, and leave a maximum of 5.
liquibase checks disable --check-name=SqlGrantWarn

liquibase checks disable --check-name=SqlRevokeWarn

liquibase checks disable --check-name=ChangeDropColumnWarn

liquibase checks disable --check-name=ModifyDataTypeWarn

liquibase checks disable --check-name=SqlUserDefinedPatternCheck

liquibase checks disable --check-name=TableColumnLimit

liquibase checks disable --check-name=SqlGrantSpecificPrivsWarn

liquibase checks disable --check-name=ChangeTruncateTableWarn

liquibase checks disable --check-name=SqlGrantOptionWarn

liquibase checks disable --check-name=SqlGrantAdminWarn
  •  The checks I left are listed here:
ChangesetCommentCheck

ChangesetContextCheck

ChangesetLabelCheck

ChangeDropTableWarn
  • Run my quality checks.
liquibase checks run

Let’s look at the sample of the generated report:

An image showing the checks report screen.

Everything is listed in the report. You don’t have to manually check your changesets. Just review the report and ask developers to correct or explain changes. You can check the full  quality check report in the liquibase_checks_report.sql file in my repo.

STEP 8: Liquibase PreConditions, and executing scripts only if certain requirements are met

Example: I want to run my compile schema script only if objects in invalid state exist.

  • I create database/dba/scripts/compile_invalid.sql.

A screen showing an invalid compilation.

  • I’ve added a new changeset to changelog\releases\changelog_release_end.xml.

An image showing the precondition changeset.

The author, context, and labels will be set using properties defined in the master.xml file. The ID will always be different depending on the version_number value. Notice the <preConditions> and condition => <sqlCheck> as the expected result of the attached SQL = 1.

So, let’s run the update command (I’ve removed the changeset with DROP and fixed missing comments, labels, and contexts – those found in the report from Step 7).

liquibase update

 

An image showing the update with preconditions.

The Script executed successfully with a warning – just as I’ve wanted. The compile schema script was not executed because I don’t have any invalid objects.

It’s worth noting that there are many more types of Liquibase preCondtions. You can read about them in the documentation.

Boost your database version control system

As you can see, Liquibase can make managing database version control changes much easier, making a developer’s life a lot easier. The features I’ve shown you can be used in many different circumstances – not just theoretical situations, but actual projects you might develop for various companies and industries. I hope my tips will be of help to you. And of course, you can expect more articles like that in the future (the topics I plan to cover include automated rollbacks and comparing multiple database schemas). Stay tuned!

Are you looking for database experts?

The Pretius team knows how to fit database technology to the needs of a specific customer. We have a great deal of experience with DB-oriented technology, including Liquibase. We also know a lot about designing software architecture. Do you need help? Drop us a line at hello@pretius.com (or use the contact form below). We’ll get back to you in 48 hours and tell you what we can do for your company.

Share