bashliquibase

How to properly use liquibase `searchPath` option to indicate the respective resource folders?


I'm trying to invoke the update command of liquibase like follows:

liquibase update --changelog-file=./persistence/src/main/resources/changelog/db.changelog-dev.xml \
                 --url="jdbc:postgresql://localhost:5432/sigma"

This results in:

[...]
Starting Liquibase at 23:44:47 (version 4.17.2 #5255 built at 2022-11-01 18:07+0000)
Liquibase Version: 4.17.2
Liquibase Community 4.17.2 by Liquibase

Unexpected error running Liquibase: The file classpath:/changelog/db.changelog-master.xml was not found in the configured search path:
    - /Users/ikaerom/Dev/sigma-backend
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/liquibase-core.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/lib
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/jaybird.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/ojdbc8.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/snakeyaml.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/snowflake-jdbc.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/picocli.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/jaxb-runtime.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/jaxb-api.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/jaxb-core.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/hsqldb.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/connector-api.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/mssql-jdbc.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/h2.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/mariadb-java-client.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/liquibase-commercial.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/commons-lang3.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/postgresql.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/sqlite-jdbc.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/opencsv.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/commons-text.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/commons-collections4.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib/jcc.jar
    - /opt/homebrew/Cellar/liquibase/4.17.2/libexec/internal/lib
More locations can be added with the 'searchPath' parameter.

The db.changelog-dev.xml is essentially including db.changelog-master.xml, which then also references some SQL scripts. The two XML files lie in the same resource folder $PROJECT_ROOT/persistence/src/main/resources/changelog. The imported/included SQL files referenced within the changelog XML all lie in the resource folder's subfolders.

Any way of specifying this eluding searchPath or even --search-path parameter (as indicated in the documentation) seems to fail spectacularly:

$> liquibase update --searchPath="./persistence/src/main/resources/" --changelog-file=./persistence/src/main/resources/changelog/db.changelog-dev.xml --url="jdbc:postgresql://localhost:5432/sigma"
Unexpected argument(s): --searchPath=./persistence/src/main/resources/

So let's try the other indicated syntax:

$> liquibase update --search-path="./persistence/src/main/resources/" --changelog-file=./persistence/src/main/resources/changelog/db.changelog-dev.xml --url="jdbc:postgresql://localhost:5432/sigma"
Unexpected argument(s): --search-path=./persistence/src/main/resources/

If I attempt to use LIQUIBASE_SEARCH_PATH=, I end up with this:

[...]
Liquibase Version: 4.17.2
Liquibase Community 4.17.2 by Liquibase

Liquibase Community detected and ignored the following environment variables:
- LIQUIBASE_SEARCH_PATH
To configure Liquibase with environment variables requires a Liquibase Pro or Liquibase Labs license. Get a free trial at https://liquibase.com/trial. Options include the liquibase.licenseKey in the defaults file, adding a flag in the CLI, and more. Learn more at https://docs.liquibase.com.
[...]

I don't really want to buy a pro version just to get this feature working ;).

My question is: how do I specify the search path for liquibase to pick it up in my bash shell?

I find it hard to believe that this wouldn't work, given liquibase is so well documented, and it tries to always give you the correct hints and pointers, if you don't use it correctly. What did I miss?

Update: I have a suspicion that the order of invocation matters. So, the update command should be last in the list. However, no luck so far:

$> liquibase \
          --changelog-file=./persistence/src/main/resources/changelog/db.changelog-dev.xml \
          --url="jdbc:postgresql://localhost:5432/sigma" \
          --searchpath="./persistence/src/main/resources/changelog/" \
          update
[...]
Starting Liquibase at 14:29:51 (version 4.17.2 #5255 built at 2022-11-01 18:07+0000)
Liquibase Version: 4.17.2
Liquibase Community 4.17.2 by Liquibase

Unexpected error running Liquibase: The file ./persistence/src/main/resources/changelog/db.changelog-dev.xml was not found in the configured search path:
    - /Users/ikaerom/Dev/sigma-backend/persistence/src/main/resources/changelog
More locations can be added with the 'searchPath' parameter.

For more information, please use the --log-level flag

Solution

  • Found the solution myself, after digging through the liquibase source code.

    In my db.changelog-dev.xm I had a line which included db.changelog-master.xml as follows. That classpath:/ has to be removed:

    -    <include file="classpath:/changelog/db.changelog-master.xml"/>
    +    <include file="changelog/db.changelog-master.xml"/>
    

    Then, this invocation finally works (mind the adapted searchPath and the relative designation of the changelog parameter settings):

        liquibase \
              --hub-mode=off \
              --headless=true \
              --url="jdbc:postgresql://localhost:5432/sigma" \
              --searchPath="./persistence/src/main/resources" \
              --changelog-file=changelog/db.changelog-dev.xml \
              update 2>&1 | grep -Ev -- "^##"
    

    The --hub-mode=off will prevent liquibase from asking if you want to connect to the liquibase hub. The rest is sugar-coating.

    The only problem open is that when liquibase is invoked from the shell CLI, the user ending up owning the changelog/lock tables is the user invoking the liquibase command:

    ikaerom@/tmp:sigma> \dt databasechangeloglock
    +--------+-----------------------+-------+---------+
    | Schema | Name                  | Type  | Owner   |
    |--------+-----------------------+-------+---------|
    | public | databasechangeloglock | table | ikaerom |
    +--------+-----------------------+-------+---------+
    SELECT 1
    Time: 0.011s
    ikaerom@/tmp:sigma> \dt databasechangeloglock
    +--------+-----------------------+-------+---------+
    | Schema | Name                  | Type  | Owner   |
    |--------+-----------------------+-------+---------|
    | public | databasechangeloglock | table | ikaerom |
    +--------+-----------------------+-------+---------+
    SELECT 1
    Time: 0.010s
    

    However, when liquibase is updated by invoking the Spring boot application, then the table owner user is the one the application context is setting (in my case sigma):

    ikaerom@/tmp:sigma> \dt databasechangeloglock
    +--------+-----------------------+-------+-------+
    | Schema | Name                  | Type  | Owner |
    |--------+-----------------------+-------+-------|
    | public | databasechangeloglock | table | sigma |
    +--------+-----------------------+-------+-------+
    SELECT 1
    Time: 0.010s
    ikaerom@/tmp:sigma> \dt databasechangelog
    +--------+-------------------+-------+-------+
    | Schema | Name              | Type  | Owner |
    |--------+-------------------+-------+-------|
    | public | databasechangelog | table | sigma |
    +--------+-------------------+-------+-------+
    SELECT 1
    Time: 0.009s
    

    This clashes if you run your liquibase update first:

    Caused by: liquibase.exception.DatabaseException: ERROR: relation "databasechangeloglock" already exists [Failed SQL: (0) CREATE TABLE public.databasechangeloglock (ID INTEGER NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP WITHOUT TIME ZONE, LOCKEDBY VARCHAR(255), CONSTRAINT databasechangeloglock_pkey PRIMARY KEY (ID))]
        at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:397)
        at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:83)
        at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:151)
        at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:135)
        at liquibase.lockservice.StandardLockService.init(StandardLockService.java:115)
        at liquibase.lockservice.StandardLockService.acquireLock(StandardLockService.java:286)
        ... 94 common frames omitted
    Caused by: org.postgresql.util.PSQLException: ERROR: relation "databasechangeloglock" already exists
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2675)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2365)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:355)
        at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:490)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:408)
        at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:329)
        at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:315)
        at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:291)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:286)
        at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94)
        at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
        at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:393)
    

    This again can be solved by a proper GRANT for the sigma or a re-assignment of the owner to the rightful user. Or simply by adding the --username property to the name of the spring boot application context or database user owner:

        liquibase \
              --hub-mode=off \
              --headless=true \
              --username="sigma" \
              --url="jdbc:postgresql://localhost:5432/sigma" \
              --searchPath="./persistence/src/main/resources" \
              --changelog-file=changelog/db.changelog-dev.xml \
              update 2>&1 | grep -Ev -- "^##"