I want to start with a specific example where a table is created with a column having a constraint indicating this column is a foreign key:
<changeSet id="1" author="A1">
<createTable tableName="TABLE_A">
<!-- Other columns -->
<column name="FK_TABLE_B" type="BIGINT">
<!-- the 'TABLE_B' will be renamed soon ! -->
<constraints references="TABLE_B(ID_PK)"
foreignKeyName="JUST_UNIQUE_I_GUESS" nullable="false" />
</column>
</createTable>
</changeSet>
Now a first question: Assume TABLE_B would be renamed to TABLE_NEW... how would i want to change the above constraint so it points to the renamed Tables column ID_PK?
I see a few possibilities:
modify the changeSet itself (no Problem with a H2 inMemory DB... but cmon... thats not the idear right?)
Drop the whole column in my own changeSet and add it again with the new constraint which could make sence since the column name will probably change to FK_TABLE_NEW anyways... but thats not a real possibilitie for a productive environment
somehow alter that constraint -> BUT HOW!? the documentation is no
help at all...
The next question then is why to have this way of constraints if there is a
<addForeignKeyConstraint ...>
as well?
So currently i dont feel Liquibase at all just because this was my very first try to change an existing changeSet and my biggest question is: Is this a issue of not understanding the best practise or acctually a problem of the "not so verbouse" documentation?
Thanks for any help!
What i tried
assumed above constraints definition somehow leads to the same as a when done with a
<addForeignKeyConstraint ...>
tag but with less attributes. So i thought i could just use the
<dropForeignKeyConstraint ...>
tag first delete the ForeignKeyConstraint and then add a new one. But it still tells me it wont find table TABLE_B when trying to execute the first changeSet in a H2 in-memory DB.
My Changeset looked like this:
<changeSet id="1" author="A2">
<dropForeignKeyConstraint baseTableName="TABLE_A"
constraintName="JUST_UNIQUE_I_GUESS" />
<addForeignKeyConstraint
constraintName="JUST_UNIQUE_I_GUESS"
referencedTableName="TABLE_NEW" baseColumnNames="FK_TABLE_B"
baseTableName="TABLE_A" referencedColumnNames="ID_PK" />
</changeSet>
Background Information
Since were currently just building a POC using a in-memory DB only (H2) its no big deal to just change the first changeSet until we have quite of a final scheme-state... but how to deal with such things if you already have a existing DB, millions of entrys and stuff? Currently i highly doubt Liquibase is the right decision for a company with 1k+ developpers without hiring Liquibase Experts...
You should only modify your changesets as long as they (resp. the software/database that go along with the changesets) have not been released.
We keep our changeset files (along with the code) in our source code repository. During development phase everyone is allowed to change changesets defined for the version currently under development.
As soon as the version is released the changeset files are considered to be fixed and should not be changed anymore. (With the release the software is shipped and customers then have a database that reflects whatever the changesets define).
So after release you will have to create new changeSets that
drop the foreign key constraint
rename your table
add a new foreign key constraint
More or less, you need to do exactly what you would do with pure sql on the database as well.
At this point liquibase will more or less just translate your changeSets to sql and apply them to the database. So once you found a way to do your changes with slq it should also be possible to put those into liquibase changesets.
Related
I'm very new at Liquibase, and I need some help.
I have an existing trigger that was not capturing all the data; I made some changes to my local Oracle database. Now I need to add those changes into the Liquibase, but I'm lost how to do that.
I know you cannot breach the contract in liquibase by updating the original .xml file directly.
From my understanding, I need to create a new changelog .XML file and then include the path on the other post_migration file.
My confusion is, do I have to drop the original trigger, then create a new file or?
Thanks!
I never create triggers, procedures or even views within the XML file exactly because this makes things more complicated (I think).
I typically move the actual trigger definition into a SQL script (that I can also run separately during development and testing), then include that SQL file from within the Liquibase changelog:
<changeSet id="42" author="arthur" runOnChange="true">
<sqlFile path="triggers/some_trigger.sql"
stripComments="false"
splitStatements="true"
endDelimiter="/"
relativeToChangelogFile="true"/>
</changeSet>
The some_trigger.sql script is stored in git (svn, ...) together with the XML changelog. The runOnChange="true" is the "magical ingredient" here. You don't have to touch the XML file, you just edit the SQL script. During deployment, Liquibase will check if the (SQL) file has changed and run the script if needec.
So, I believe that you create/update/replace the SQL trigger in your local developer database and right now you want to include the liquibase script to the release distribution package of your product.
The liquibase doesn't provide special xml syntax to create triggers, so you will just add a new changeset that holds your pl/sql script inside the <SQL> tag. The script will be the same that you run on your local database.
The example code here:
<changeSet id="1" author="me">
<sql endDelimiter="/">
CREATE OR REPLACE TRIGGER trigger_name before insert
on table1_name for each row
BEGIN
select seq_myseq.nextval
into :new.myid
from dual;
END;
/
</sql>
</changeSet>
This code just compile trigger in the aimed database when you call liquibase update. In most cases, it is enough. But I strictly recommend you to ask your DBA or team led for rules that your team enforced for writing liquibase scripts. For this reason, the result may be much more complicated.
Looking at the liquibase documention http://www.liquibase.org/documentation/changes/create_index.html, CIC is not possible with create index, as liquibase doesn't have a tag to specify concurrent option.
Is there a way to create index concurrently with liquibase?
You can specify runInTransaction as false to create the index concurrently.
Creating a concurrent index must be done with the arbitrary sql change:
<changeSet runInTransaction="false" id="10-add-widgets-kind-index" author="username">
<sql dbms="postgresql">
CREATE INDEX CONCURRENTLY
IF NOT EXISTS idx_widgets_kind
ON widgets(kind)
</sql>
</changeSet>
This is a combination of a_horse_with_no_name's comment and TheDude's answer.
The previous answers do the job. I would like to offer an alternative that doesn't directly answer the OP's question, but does offer a solution with the same end result with some added advantages. I feel it is good to show other options for people that stumble upon this answer like I did.
In order to create the index using only Liquibase, you would need to use the <sql> tag. I caution against this as it can cause undesired consequences in the event that you use a different database for any reason (development, evaluation, testing, etc). The sql statement will be skipped and you can be left thinking that the index was added when in reality it was not.
Additionally, this can lead to a less controlled migration, assuming you are running this on a production system without taking it down for maintenance and the migration being part of the build process.
I would propose creating the index directly on Postgres and adding the index migration normally using Liquibase and a precondition check.
First, add the index manually:
CREATE INDEX CONCURRENTLY widgets_kind_idx ON widgets (kind);
And then add to your Liquibase changeSet:
<changeSet id="10-add-widgets-kind-index" author="username">
<preConditions onFail="MARK_RAN">
<not>
<indexExists indexName="widgets_kind_idx" />
</not>
</preConditions>
<createIndex tableName="widgets" indexName="widgets_kind_idx">
<column name="kind" />
</createIndex>
</changeSet>
This offers the ability to add the index in any manner desired and keeps your Liquibase migrations in a known state. A fresh database being setup would not require the CONCURRENTLY keyword.
I want to update very old changeset because there is a bug in that. I have updated that changesetid and After running it I got checksum error i.e checksum got modified. Now I have added validCheckSum Tag with this new checkSum. After it is getting successfully I cant not see new changes. Is there any way to update that change set which has already been executed.
Each changeSet tag is uniquely identified by the combination of the “id” tag, the “author” tag so please check whether id and author name already exists or not in DATABASECHANGELOG table. If the combination exists then change the id in your xml file then run the build
If still not works then add runOnChange property in <ChangeSet> and try it
runOnChange - Executes if any changes found it already existing changeset
Example of using runOnChange in changeset with sqlFile tag .
I had a script for Oracle 18 using IDENTITY column type and than have to change it for Oracle 10 which doesn't support IDENTITY column type
<changeSet id="2" author="auto" runOnChange="true">
<sqlFile dbms="oracle" encoding="utf8" endDelimiter=";" path="oracle/230.sql" relativeToChangelogFile="true" splitStatements="true" stripComments="true" />
</changeSet>
If it is a very old changeset as you have stated you should consider not changing the old changeset but add a new changeset instead which fixes the bug since it allows you better control over the versions you have. This is one of the main reasons of using liquibase.
I have a changeset to create an index.
<changeSet author="hilland" id="x-NC-U-y" runOnChange="true">
<createIndex indexName="NC-U-y" tableName="x" unique="true">
<column name="y"/>
</createIndex>
<rollback>
<dropIndex .../>
</rollback>
<modifySql>
<append value=" INCLUDE ( [a],[b]) WITH (DATA_COMPRESSION=page)"/>
</modifySql>
</changeSet>
The problem is that an older version of the index might exist (it will on some targets (dev server, with an older version of this index; no includes), will not on others (eg a fresh deploy to an empty database).
Is there a better way to resolve this so that the script becomes universal, than to include a prefix which says
<sql>IF select {sys.indexes.stuff} is not null {drop the index}</sql>?
Ideally there would be a precondition to drop-if-exists the existing index so that it can be recreated, but i don't think that can be done.
What is the best way to handle this situation?
an alternate solution i thought of would be to have a separate changest "if index v1 exists, drop it" then have a separate v2 changeset, but then that would make rollback hard.
I realize that the run on change condition for the changeset makes rollback hard, but the only rollback i'm concerned with in this case is to and from an empty database, although a pattern which easily accommodates rollbacks to previous version would be welcome also.
You can execute changesets dependent on a precondition. Your changeset could look like this:
<changeSet>
<preConditions onFail="MARK_RAN">
<indexExists indexName="NC-U-y" />
</preConditions>
<dropIndex indexName="NC-U-y" />
</changeSet>
See also the Liquibase Documentation.
I'm trying to execute the following changeSet in liquibase which should create an index. If the index doesn't exist, it should silently fail:
<changeSet failOnError="false" author="sys" id="1">
<createIndex unique="true" indexName="key1" tableName="Table1">
<column name="name" />
</createIndex>
</changeSet>
So far, so good. The Problem is, that this changeSet doesn't get logged into DATABASECHANGELOG table and is therefor executed every time liquibase runs. According to the liquibase documentation and e.g. this answer from Nathen Voxland i thought that the changeset should be marked as ran in the DATABASECHANGELOG table. Instead it isn't logged at all and as i said before executed every time liquibase runs (and fails everytime again).
Am i missing something?
(I'm using MySQL as DBMS)
In the answer given by Nathen Voxland, he recommended the more correct approach of using a precondition to check the state of the database, before running the changeset.
It seems to me that ignoring a failure is a bad idea.... Means you don't fully control the database configuration.... The "failOnError" parameter allows liquibase to continue. Wouldn't it be a bad idea for a build to record a changset as executed, if in fact it didn't because an error occurred?