Postgres: How to create index concurrently using liquibase - indexing

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.

Related

WHERE clause in liquibase changelog to create database

How can I generate this SQL statement in my changelog file with liquibase?
CREATE UNIQUE INDEX RL_UK on RUN_LOG(status) WHERE status = 'R'
I have tried it like this but it doesnt work:
<changeSet author="Ferid (generated)" id="1528876614155-232">
<createIndex indexName="RL_UK" tableName="RUN_LOG" unique="true">
<where> status='R'</where>
</createIndex>
I dont use this changelog file to update the database but to create it.
The clause is not available.
As suggested in comments, you could write custom SQL statments with tag but you will have to take care of the rollback yourself :
<changeSet author="Ferid (generated)" id="1528876614155-232">
<sql>
CREATE UNIQUE INDEX RL_UK on RUN_LOG(status) WHERE status = 'R';
</sql>
<rollback>
<sql>
DROP INDEX RL_UK on RUN_LOG;
</sql>
</rollback>
</changeSet>
Although I would personnally suggest that you use the tag properly without the WHERE CLAUSE because :
I can't understand what you are trying to achieve with this where clause. Espacially since it is on the same column
Your changeset probably won't be compatible with all databases supported by Liquibase so it will work with Postgres and fail with Oracle for example. The WHERE CLAUSE in the CREATE INDEX statement is probably not even ANSI-SQL
You add bothering about the rollback for a very simple statement => not worth. Auto-rollback is available on every supported databases for this tag. Check it here : http://www.liquibase.org/documentation/changes/create_index.html

how to recreate an index if it already exists with 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.

execute parameterized liquibase changeset multiple times with different parameters

I have been struggling with a liquibase challenge for some time and I hope somebody here can help me:
I would like to execute a simple parameterized liquibase script multiple times on the same db schema with different parameters:
<changeSet id="1" author="me" dbms="Oracle" runOnChange="false" failOnError="true">
<sql splitStatements="true">
GRANT SELECT on SOME_VIEW to ${db_user};
</sql>
</changeSet>
Now I execute liquibase one time with -Ddb_user=first_user and than with -Ddb_user=second_user. The second run fails, because liquibase calculates the checkSum after replacing the ${db_user} parameter (what makes perfect sense) and therefore the combination of id/author/filename and checkSum is already present in the DATABASECHANGELOG table.
Is there a best practice way to solve this problem?
Thanks in advance.
There is a runOnChange as an attribute for the changeSet which will run your changeset each time it was changed. Maybe this does what you are looking for?
You can always use runOnChange=true or <validCheckSum>any</validCheckSum>
There is an issue that explains this decision here: https://liquibase.jira.com/browse/CORE-2506

Liquibase alter column constraints

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.

Liquibase changeSet with failOnError="false" are always ran?

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?