Modify SQL changeset - liquibase

Liquibase provides a <validCheckSum> tag to allow for a new checksum to be specified in case we want to modify an existing changeset.
However, this tag is not a valid attribute for SQL-formatted changesets. There is runOnChange, but that's different.
Is there any way of achieving that?
Basically, I made a mistake on a changeset, and I can't even add a rollback command because liquibase detects the checksum change and spits an error, so I'm stuck.
EDIT
The concrete changeset that I'm trying to change is:
--liquibase formatted sql
--changeset myname:0
ALTER TABLE `customers`
CHANGE COLUMN `name` `firstName` VARCHAR(45) NULL;
--changeset myname:1
ALTER TABLE `customers`
ADD COLUMN `lastName` VARCHAR(45) NULL AFTER `firstName`;
And I keep it in a file changelog_1.05.sql. Finally, I include that file in my changelog.xml:
<include file="changelog_1.05.sql" relativeToChangelogFile="true"/>
I can't add <validCheckSum> because is a SQL-formatted file, so no xml tags can be added there.

Even though it is not documented, looking at the source it appears that validCheckSum is a valid attribute in a formatted sql changelog. You can see line 105 in FormattedSqlChangelogParser.java has code to look for this attribute.

I ended up here trying to use the validCheckSum with SQL files in Liquibase 3.9.0.
It works, but only when the "--validCheckSum" is in a new line without other attributes (as opposed to other attributes such as "--runAlways":
--changeset me:test --runAlways:true --splitStatements:false
--validCheckSum: 1:any
This seems to be due to the regex for parsing the attribute:
https://github.com/liquibase/liquibase/blob/17fcfe4f8dae96ddb4caa6793051e47ce64ad933/liquibase-core/src/main/java/liquibase/parser/core/formattedsql/FormattedSqlChangeLogParser.java#L87

Related

Liquibase preconditions to all changelog file in sql

I would like to run a SQL precondition checking for each changeSet in my SQL changeLogFile. It is actually a precondition on the changeLog itself
Here is an extract of it :
--liquibase formatted sql
--preconditions onFail:HALT onError:HALT
--precondition-sql-check expectedResult:"1.0" SELECT VERSION FROM VERSION_TABLE;
--changeset bob:1 failOnError:true dbms:oracle
ALTER INDEX XXX RENAME TO YYY;
--rollback YYY RENAME TO XXX;
Even if the precondition is actually not respected, liquibase still run all the changeset.
Does somebody knows if it is an error from my side or if liquibase does not allow preconditions on entire changeLog for SQL changeLog file ?
Thanks in advance !
If you go through the documentation, then its stated that we can only apply pre-conditions on a specific change set. Also, only the SQL Check precondition is supported.
Liquibase doc for sql changelog files - https://www.liquibase.org/documentation/sql_format.html
One thing you could do is have a top-level master changelog in XML/YAML/JSON format and then use <include> or <includeAll> elements to include your liquibase formatted sql changelogs. If you do that, then you can have changelog-level preconditions.

Liquibase unable to execute schema (Snowflake) with mixed case eg. (This_Schema)

I have tried using liquibase tool for our snowflake db. It is all working with where SCHEMA name is in all CAPITAL(UPPERCASE). But liquibase is not picking up any of my schema's with mixed case, eg (This_Schema).
I have tried putting this but didn't help.
<defaultSchemaName>This_Schema</defaultSchemaName>
POM.XML configuration example:
<driver>net.snowflake.client.jdbc.SnowflakeDriver</driver>
<url>jdbc:snowflake://${env.SNOWFLAKE_ACCOUNT}.eu-central-1.snowflakecomputing.com/?db=${env.SNOWFLAKE_DB}&schema=${env.SNOWFLAKE_SCHEMA}&warehouse=${env.SNOWFLAKE_WH}&role=${env.SNOWFLAKE_ROLE}</url>
<username>${env.SNOWFLAKE_USERNAME}</username>
<password>${env.SNOWFLAKE_PASSWORD}</password>
Error setting up or running Liquibase: liquibase.exception.DatabaseException: SQL compilation error:
[ERROR] Schema 'LIQUIBASE_DB.THIS_SCHEMA' does not exist. [Failed SQL: CREATE TABLE THIS_SCHEMA.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP_NTZ, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID))]
NOTE: "This_Schema" is the name of my schema as it is showing here, but upon executing liquibase update this automatically changes to UPPERCASE value as in error above.
Found this comment in the README file from the liquibase snowflake extension.
The Snowflake JDBC drivers implementation of
DatabaseMetadata.getTables() hard codes quotes around the catalog,
schema and table names, resulting in queries of the form:
show tables like 'DATABASECHANGELOG' in schema "sample_db"."sample_schema"
This results in the DATABASECHANGELOG table not being found, even
after it has been created. Since Snowflake stores catalog and schema
names in upper case, the getJdbcCatalogName returns an upper case
value.
Could this explain your problems?...

Liquibase includeAll incompatible with rollback and tagging?

When using the includeAll option in the databaseChangeLog, is there any way to use rollback or tagging? It seems really nice to have all my changes in file 01.sql to 99.sql run in order. Do I have to go back to specifying the individual files and rollbacks to make this work?
You can use SQL Format
e.g.:
--changeset nvoxland:1
create table test1 (
id int primary key,
name varchar(255)
);
--rollback drop table test1;
As of Liquibase 2.0, Liquibase includes support for “plain SQL”
changelog files. These changelogs may be included from XML changelogs
and may contain arbitrary SQL statements. The statements are converted
to custom_sql refactorings.
Formatted SQL files use comments to provide Liquibase with metadata.
Read more about SQL Format

How can I alter a UDT in HSQLDB?

In HSLQDB v 2.3.1 there is a create type clause for defining UDTs. But there appears to be no alter type clause, as far as the docs are concerned (and the db returns a unexpected token error if I try this).
Is it possible to amend/drop a UDT in HSQLDB? What would be the best practice, if for example I originally created
create type CURRENCY_ID as char(3)
because I decide I'm going to use ISO codes. But then I actually decide that I'm going to store the codes as integers instead. What is the best way to modify the schema in my db? (this is a synthetic example, obviously I wouldn't use integers in this case).
I guess I might do
alter table inventory alter column ccy set data type int
drop type CURRENCY_ID
create type CURRENCY_ID as int
alter table inventory alter column ccy set data type CURRENCY_ID
but is there a better way to do this?
After trying various methods, I ended up writing a script to edit the *.script file of the database directly. It's a plain text file with SQL commands that recreates the DB programmatically. In detail:
open db, shutdown compact
Edit the script file: replace the type definition, e.g. create type XXX as int to create type XXX as char(4)
For each table, replace the insert into table XXX values (i,...) with insert into table XXX values('str',...). This was done with a script that had the mappings from the old (int) value into the new (char) value.
In my particular case, I was changing a primary key, so I had to remove the identity directive from the create table statement, and also I had to remove a line that had a alter table XXX alter column YYY restart sequence 123.
save and close script file, open db, shutdown compact
This isn't great, but it worked. Advantages :
Ability to re-define UDT.
Ability to map the table values programmatically.
Method is generic and can be used for other schema changes, beside UDTs.
Cons
No checking that schema is consistent (although it does throw up errors if it can't read the script).
Dangerous when reading file as a text file. e.g. what if I have a VARCHAR column with newlines in it? When I parse the script file and write it back, this would need to be escaped.
Not sure if this works with non-memory DBs. i.e. those that don't only have a *.script file when shutdown.
Probably not efficient for large DBs. My DB was small ~ 1MB.

Does liquibase recognize if table is already up to date before executing changeSet?

In our case liquibase is used to update databses for existing installation. New installations are already up to date.
Assuming we have got a new installation. Starting the application will force to execute liquibase changesets (e.g. change type of a column) but as I mentioned before there is nothing to update as the column already was created with the correct type.
Does liquibase recognize that the table column is already up to date or does it try to execute the changeset as there is no entry within the databasechangelog table for it?
Liquibase uses an alternative approach that avoids a need to analyze the target database's data dictionary. This makes DB operations simpler and more cross platform.
A special table "DATABASECHANGELOG" keeps a record of the changesets applied to the target database instance. This table also contains a checksum (calculated at runtime) to determine if changsets are altered between runs of liquibase.
So if you altered the type of a table column, liquibase can detect this and can throw an error, when run against an existing database. (Obviously, on a new DB, the table would be created as expected).
Finally, the changeset documentation describes two optional attributes ("runAlways" and "runOnChange") which could tell lqiuibase to reapply a changeset more than once to a database. There is also a "clearCheckSums" command that can be used to reset the checksums on an existing database. Obviously you need to know what you're doing when using such an option :-)
Liquibase will not recognize anything automatically.
But you can use <preConditions/> in your changeSet to check if your changeSet must be applied or not.