SQLcl Liquibase tags offer plenty of powerful possibilities, but the documentation is a bit lacking. What are the parameters? How to use them? Don’t worry – this article is here to answer all these nagging questions.
Here at Pretius, we love Liquibase. It is a perfect tool that works at many levels of complexity, from simple generate schema no git setup to complex CI\CD pipelines that automatically install and upgrade dozens of environments. This article will focus on the more complex use of Oracle-flavored Liquibase. If you need a refresher on Liquibase and its use, I advise you to read the great Liquibase tutorial written by my friend Rafał.
Let’s imagine you would like to write your own XML changeset using all the bells and whistles that Oracle provides. What are your options?
SQLcl documentation gives you a nice table, but it doesn’t provide enough information. What are the parameters for those tags? What are they used for? How to use them? Do we even know?
How to use them?
To use those tags, you must add a new XSD (XML Schema Definition) to your database change log – notice the xmlns:n0 parameter in the example below – and use SQLcl to run your changes. Alternatively, you can add the extension to the standalone Liquibase. A description of how to do it is in the SQLcl documentation.
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:n0="http://www.oracle.com/xml/ns/dbchangelog-ext" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
The dbchangelog-ext extension
Unfortunately, while Oracle gives you a path to an XSD to add to your XMLs, this link leads to nowhere. I did some digging and found the file in SQLcl, but it is missing a lot of tags that Oracle specifies can be used. Hopefully, we will get a working link and a full XSD in future iterations.
Tags
Most tags do exactly what is written on the tin, but not all. Parameter specification is taken from dbchangelog-ext where possible, but most result from me tinkering with them.
Be sure the objectName attribute matches the name of the object you are creating. SQLcl will use that attribute to generate rollback statements and put it into quotation marks. Unless you use mixed case letters in your tables, triggers, sequences etc., always use all caps for this attribute.
createOracleDictionary
Missing from the XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the dictionary to create |
objectType | attribute string optional | This should always be set to DICTIONARY |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the dictionary |
Example:
<n0:createOracleDirectory objectName="DATA_PUMP_DIR" objectType="DIRECTORY" ownerName="ADMIN" replaceIfExists="true" > <n0:source><![CDATA[CREATE OR REPLACE DIRECTORY "DATA_PUMP_DIR" AS 'dpdump';]]></n0:source> </n0:createOracleDirectory>
createOracleProcedure
Missing from the XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the procedure to create |
objectType | attribute string optional | This should be always set to PROCEDURE |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the procedure |
<n0:createOracleProcedure objectName="HELLOLIQUIBASE" ownerName="ADMIN" replaceIfExists="true" > <n0:source><![CDATA[CREATE OR REPLACE EDITIONABLE PROCEDURE "ADMIN"."HELLOLIQUIBASE" ( NAME IN VARCHAR2 ) AS BEGIN dbms_output.put_line('Hello '||name); END HELLOLIQUIBASE; /]]></n0:source> </n0:createOracleProcedure>
createOracleConstraint
Missing from the XSD.
A couple of problems with this one. First of all, it’s missing from XSD, and lb generate-schema or lb generate-object will not use this tag, so there is no way for me to work out its intended use.
Secondly, I tinkered with it a bit, and in the example, you can see what I found working, but lb rollback will not be able to run properly. The same goes for createOracleRefConstraint, although it gave me a slightly different error.
I would advise you not to use those tags currently.
<n0:createOracleConstraint objectName="TEST_CONST2" ownerName="SC_CORE" > <n0:source><![CDATA[ ALTER TABLE test_table ADD CONSTRAINT test_const2 UNIQUE (column3); ]]> </n0:source> </n0:createOracleConstraint>
createOracleGrant
Missing from the XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the grant to create |
objectType | attribute string optional | This should always be set to OBJECT_GRANT |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the grant |
<n0:createOracleGrant objectName="object_grant0" objectType="OBJECT_GRANT" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ GRANT INSERT ON "SC_CORE"."TEST_TABLE" TO "LIQUIBASE_USER"]]></n0:source> </n0:createOracleGrant>
createOraclePackageBody
Name | Type | Comment |
objectName | attribute string required | Name of the Package to create |
objectType | attribute string optional | This should always be set to PACKAGE_BODY |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the package specification |
<n0:createOraclePackageBody objectName="TEST" objectType="PACKAGE_BODY" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ CREATE OR REPLACE EDITIONABLE PACKAGE BODY "SC_CORE"."TEST" as procedure p_test as begin null; end p_test; end TEST;]]></n0:source> </n0:createOraclePackageBody>
createOracleJob
Missing from the XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the job to create |
objectType | attribute string optional | This should always be set to JOB |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put code here to create the job |
<n0:createOracleJob objectName="LIQUI_JOB" objectType="JOB" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ BEGIN dbms_scheduler.create_job('LIQUI_JOB', job_type=>'PLSQL_BLOCK', job_action=> 'begin null; end;' , number_of_arguments=>0, start_date=>TO_TIMESTAMP_TZ('04-OCT-2023 10.01.38.534232000 PM EUROPE/ATHENS','DD-MON-RRRR HH.MI.SSXFF AM TZR','NLS_DATE_LANGUAGE=english'), repeat_interval=> 'FREQ=YEARLY;BYDATE=1101;BYTIME=213700' , end_date=>NULL, job_class=>'DEFAULT_JOB_CLASS', enabled=>FALSE, auto_drop=>FALSE,comments=> 'My test job for blogpost' ); sys.dbms_scheduler.set_attribute('LIQUI_JOB','NLS_ENV','NLS_LANGUAGE=''ENGLISH'' NLS_TERRITORY=''POLAND'' NLS_CURRENCY=''zł'' NLS_ISO_CURRENCY=''POLAND'' NLS_NUMERIC_CHARACTERS='', '' NLS_CALENDAR=''GREGORIAN'' NLS_DATE_FORMAT=''RR/MM/DD'' NLS_DATE_LANGUAGE=''ENGLISH'' NLS_SORT=''BINARY'' NLS_TIME_FORMAT=''HH24:MI:SSXFF'' NLS_TIMESTAMP_FORMAT=''RR/MM/DD HH24:MI:SSXFF'' NLS_TIME_TZ_FORMAT=''HH24:MI:SSXFF TZR'' NLS_TIMESTAMP_TZ_FORMAT=''RR/MM/DD HH24:MI:SSXFF TZR'' NLS_DUAL_CURRENCY=''zł'' NLS_COMP=''BINARY'' NLS_LENGTH_SEMANTICS=''BYTE'' NLS_NCHAR_CONV_EXCP=''FALSE'''); dbms_scheduler.enable('LIQUI_JOB'); COMMIT; END; ]]></n0:source> </n0:createOracleJob>
createOraclePackageSpec
Name | Type | Comment |
objectName | attribute string required | Name of the Package to create |
objectType | attribute string optional | This should always be set to PACKAGE_SPEC |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the package specification |
<n0:createOraclePackageSpec objectName="TEST" objectType="PACKAGE_SPEC" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ CREATE OR REPLACE EDITIONABLE PACKAGE "SC_CORE"."TEST" AS procedure p_test; end; ]]></n0:source> </n0:createOraclePackageSpec>
createOraclePublicSynonym
Missing from the XSD. Lb generate-schema will not use this tag. Functions the same as createOracleSynonym.
createOracleRefConstraint
Missing from XSD. See createOracleConstraint.
createOracleSynonym
Missing from XSD. Lb generate-schema will use this tag even for public synonyms.
Name | Type | Comment |
objectName | attribute string required | Name of the Package to create |
objectType | attribute string optional | This should always be set to PACKAGE_SPEC |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the package specification |
<n0:createOracleSynonym objectName="LOGGER_LOGS_5_MIN" objectType="SYNONYM" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ CREATE OR REPLACE EDITIONABLE SYNONYM "SC_CORE"."LOGGER_LOGS_5_MIN" FOR "LOGGER_USER"."LOGGER_LOGS_5_MIN"]]></n0:source> </n0:createOracleSynonym>
createOracleTrigger
Name | Type | Comment |
objectName | attribute string required | Name of the Trigger to create |
objectType | attribute string optional | This should always be set to TRIGGER |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the trigger |
<n0:createOracleTrigger objectName="TEST_TRIGGER" objectType="TRIGGER" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ CREATE OR REPLACE EDITIONABLE TRIGGER "SC_CORE"."TEST_TRIGGER" BEFORE DELETE OR INSERT OR UPDATE ON TEST_TABLE BEGIN NULL; END; / ALTER TRIGGER "SC_CORE"."TEST_TRIGGER" ENABLE]]></n0:source> </n0:createOracleTrigger>
createOracleTypeBody
Missing from XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the type body to create |
objectType | attribute string optional | This should always be set to TYPE_BODY |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the type body |
<n0:createOracleTypeBody objectName="DATA_TYP1" objectType="TYPE_BODY" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ CREATE OR REPLACE EDITIONABLE TYPE BODY "SC_CORE"."DATA_TYP1" IS MEMBER FUNCTION prod (invent NUMBER) RETURN NUMBER IS BEGIN RETURN (year + invent); END; END; ]]></n0:source> </n0:createOracleTypeBody>
createOracleTypeSpec
Missing from XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the Type Specification to create |
objectType | attribute string optional | This should always be set to TRIGGER |
ownerName | attribute string required | Name of the schema owning this object |
replaceIfExists | attribute boolean required | Should object be recreated on execution |
source | element string required | Put full DDL here to create the type specification |
<n0:createOracleTypeSpec objectName="DATA_TYP1" objectType="TYPE_SPEC" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ CREATE OR REPLACE EDITIONABLE TYPE "SC_CORE"."DATA_TYP1" AS OBJECT ( year NUMBER, MEMBER FUNCTION prod(invent NUMBER) RETURN NUMBER ); ]]></n0:source>
createSxmlObject
A bit strange but can be useful in a pinch. This tag lets you create an object defined by SXML. What is SXML? In this context, it’s a specification of a given object generated by dbms_metadata with SXML transformation applied. Oracle uses this tag to generate table changesets when you run lb generate-schema instead of doing separate changesets for each ‘parts’ of the table.
Name | Type | Comment |
objectName | attribute string | Name of the object to create |
objectType | attribute string | Type of the object that will be created |
ownerName | attribute string | Name of the schema owning this object |
replaceIfExists | attribute boolean ?? | Should the object be recreated on execution. This parameter doesn’t exist in XSD, but Oracle adds it when generating this tag |
source | element string required | Full SXML goes here |
<n0:createSxmlObject objectName="TEST_TABLE" objectType="TABLE" ownerName="SC_CORE" replaceIfExists="true" > <n0:source><![CDATA[ <TABLE xmlns="http://xmlns.oracle.com/ku" version="1.0"> <SCHEMA>SC_CORE</SCHEMA> <NAME>TEST_TABLE</NAME> <RELATIONAL_TABLE> <COL_LIST> <COL_LIST_ITEM> <NAME>COLUMN1</NAME> <DATATYPE>NUMBER</DATATYPE> <NOT_NULL></NOT_NULL> </COL_LIST_ITEM> <COL_LIST_ITEM> <NAME>COLUMN3</NAME> <DATATYPE>VARCHAR2</DATATYPE> <LENGTH>20</LENGTH> <COLLATE_NAME>USING_NLS_COMP</COLLATE_NAME> </COL_LIST_ITEM> <COL_LIST_ITEM> <NAME>COLUMN2</NAME> <DATATYPE>DATE</DATATYPE> </COL_LIST_ITEM> </COL_LIST> <PRIMARY_KEY_CONSTRAINT_LIST> <PRIMARY_KEY_CONSTRAINT_LIST_ITEM> <NAME>TEST_TABLE_PK</NAME> <COL_LIST> <COL_LIST_ITEM> <NAME>COLUMN1</NAME> </COL_LIST_ITEM> </COL_LIST> <USING_INDEX> <INDEX_ATTRIBUTES> <PCTFREE>10</PCTFREE> <INITRANS>20</INITRANS> <MAXTRANS>255</MAXTRANS> <TABLESPACE>DATA</TABLESPACE> <LOGGING>Y</LOGGING> </INDEX_ATTRIBUTES> </USING_INDEX> </PRIMARY_KEY_CONSTRAINT_LIST_ITEM> </PRIMARY_KEY_CONSTRAINT_LIST> <DEFAULT_COLLATION>USING_NLS_COMP</DEFAULT_COLLATION> <PHYSICAL_PROPERTIES> <HEAP_TABLE> <SEGMENT_ATTRIBUTES> <SEGMENT_CREATION_DEFERRED></SEGMENT_CREATION_DEFERRED> <PCTFREE>10</PCTFREE> <PCTUSED>40</PCTUSED> <INITRANS>10</INITRANS> <MAXTRANS>255</MAXTRANS> <TABLESPACE>DATA</TABLESPACE> <LOGGING>Y</LOGGING> </SEGMENT_ATTRIBUTES> <COMPRESS>N</COMPRESS> </HEAP_TABLE> </PHYSICAL_PROPERTIES> </RELATIONAL_TABLE> </TABLE>]]></n0:source> </n0:createSxmlObject>
dropSxmlObject
So, this should remove your object. It will just try and drop anything you will give it. I allowed myself a small joke in this example (lb up will try to execute it).
Name | Type | Comment |
objectName | attribute string required | Name of the object to drop |
objectType | attribute string required | Type of the object that will be dropped |
ownerName | attribute string required | Name of the schema owning this object |
source | element string required | From my testing, it doesn’t matter what you put here, but it can’t be empty |
<n0:dropSxmlObject objectName="HOT" ownerName="SC_CORE" objectType='IT LIKE IT'S ' > <n0:source>Anything can go here </n0:source> </n0:dropSxmlObject>
dropOracleProcedure
Name | Type | Comment |
objectName | attribute string required | Name of the procedure to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOracleprocedure objectName="HELLOLIQUIBASE" ownerName="ADMIN"></n0:dropOracleprocedure>
dropOracleFunction
Name | Type | Comment |
objectName | attribute string required | Name of the function to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOracleFunction objectName="HELLOLIQUIBASE" ownerName="ADMIN"></n0:dropOracleFunction>
dropOracleGrant
Missing from the XSD. I was unable to make that one work. It requires objectName and ownerName parameters, but even those trow an error.
dropOraclePackageBody
Name | Type | Comment |
objectName | attribute string required | Name of the package to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOraclePackageBody objectName="HELLOLIQUIBASE" ownerName="ADMIN"></n0:dropOraclePackageBody>
dropOraclePackageSpec
Name | Type | Comment |
objectName | attribute string required | Name of the package to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOraclePackageSpec objectName="HELLOLIQUIBASE" ownerName="ADMIN"></n0:dropOraclePackageSpec>
dropOracleRefConstraint
Missing from XSD. Unfortunately, I wasn’t able to make that one work.
dropOracleTrigger
Name | Type | Comment |
objectName | attribute string required | Name of the trigger to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOracleTrigger objectName="HELLOLIQUIBASE" ownerName="ADMIN"></n0:dropOracleTrigger>
dropOracleTypeBody
Missing from XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the type to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOracleTypeBody objectName="DATA_TYP1" ownerName="SC_CORE" > </n0:dropOracleTypeBody>
dropOracleTypeSpec
Missing from XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the type to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOracleTypeSpec objectName="DATA_TYP1" ownerName="SC_CORE" > </n0:dropOracleTypeSpec>
dropOracleSynonym
Missing from XSD.
Name | Type | Comment |
objectName | attribute string required | Name of the type to drop |
ownerName | attribute string required | Name of the schema owning this object |
<n0:dropOracleSynonym objectName="DATA_TYP1" ownerName="SC_CORE" > </n0:dropOracleSynonym>
runOracleScript
This one is well documented here.
Name | Type | Comment |
objectName | attribute string required | This is the name of the script to run. It doesn’t seem to do anything |
ownerName | attribute string required | Name of the schema to run this script on |
sourceType | attribute string required | Set to URL, STRING or FILE |
source | element string required | Depending on sourceType it’s either the content of the script to run or a path or URL to script to execute |
Example is taken from documentation:
<n0:runOracleScript objectName="myScript" ownerName="JDOE" sourceType="STRING"> <n0:source><![CDATA[DEFINE table_name = RUNNERSTRING;create table &&table_name (id number);]]></n0:source> </n0:runOracleScript>
runApexScripts
This one is undocumented and used by Oracle in creating changesets that install Oracle APEX applications (check out Matt Mulvaney’s Oracle APEX tutorial to learn more about this low-code platform). I didn’t do any testing on this one. It seems that compared to runOracleScript, it adds some context values and probably sets the correct security group.
Name | Type | Comment |
objectName | attribute string required | This is the name of the script to run. It doesn’t seem to do anything |
ownerName | attribute string required | Name of the schema to run this script on |
sourceType | attribute string required | Set to URL, STRING or FILE |
source | element string required | Depending on sourceType it’s either the content of the script to run or a path or URL to script to execute |
<n0:runApexScript objectName="install" objectType="SCRIPT" ownerName="LIQUIBASE_USER" sourceType="STRING" > <n0:source><![CDATA[ -- your apex script goes here -- you can also call a file like this --@@path/to/file/install.sql ]]> <n0:source> </n0:runApexScript>
Conclusion – what’s next?
And that’s about it. Hopefully, this list will save you some time when you write your own XML changesets. In the future, I plan to release a follow-up to this article, in which I will look at how these tags translate into automatic rollbacks. If you have any questions, reach out to me at jdobruchowski@pretius.com. I also advise you to check other Liquibase-related articles on the Pretius blog:
- What is Liquibase and how to start using it? Automate your database scripts deployment with this Liquibase tutorial
- Liquibase for teams: GIT collaboration and easy deployment
- Boost the management of your Oracle Database version control changes with Liquibase
- Liquibase rollback – A smart way to do it with Jenkins
- Testcontainers + Liquibase: Make integration testing easier
- Use Liquibase to track DB changes without direct access– A quick guide
- Use SQLcl Liquibase to move all database objects from DEV to the UAT environment