Search code examples
grailsgrails-ormliquibase

Grails database migration gorm diff yields no changes


Background

I have a relatively new Grails project using 3.0.14. I am looking to integrate liquibase for database migrations via the Database Migration plugin (2.0.0.RC4).

I have a large enough domain model so far that I have used the plugin to 'seed' an initial changelog. This is straight from the docs, and works as intended:

grails dbm-generate-gorm-changelog changelog.groovy

What I am now trying to test/get working is the dbm-gorm-diff command, which will take changes to the domain model and create a changelog that can be applied. This is where I am running into issues.

The Grails documentation suggest removing the dbCreate block from the datasource to ensure that Hibernate doesn't do the updating, and Liquibase can take over. Great, exactly what I want.

The Issue

When I remove dbCreate, Grails/hibernate still seems to update the database before the Database Migration plugin has a chance to do the diff. When doing the diff, it is already too late to see changes, so the changelogs do not contain the right data.

Config

dataSource:
    pooled: true
    jmxExport: true
    driverClassName: org.h2.Driver
    username: sa
    password:

environments:
    development:
        dataSource:
            dbCreate: verify
            driverClassName: org.postgresql.Driver
            dialect: org.hibernate.dialect.PostgreSQLDialect
            url: jdbc:postgresql://127.0.0.1:5432/liquibase_test
            username: dbuser
            password: dbuser
            logSql: false
            formatSql: true

(I am aware that the dbCreate is set to verify. More on this later)

Steps Taken

  1. Create a new postgres database - dbcreate -U dbuser liquibase_test
  2. Run the initial changelog on the new database - grails dbm-update
  3. Verify that the database is now up to date, and check that select * from databasechangelog equals the number of changes in changelog.groovy
  4. Add a new simple domain class:

    class TestDomain {
        int testInt
    }
    
  5. Run the plugin to get the diff - grails dbm-gorm-diff add-simple-domain.groovy. The command fails with an exception:

     :DataModel:dbmGormDiff
     Command execution error: liquibase.command.CommandExecutionException: java.lang.NullPointerException
     DataModel:dbmGormDiff FAILED
    
  6. Now, remove the config dbCreate: verify from above, and run again

  7. This completes successfully without exception, but there are issues:

    • the command created add-simple-domain.groovy, but it has no mention of the new domain class I just created. (It has index/sequences, but I think this is a known issue)
    • the new domain class has been added to the database(!?) (checked in PgAdmin)
    • the table databasechangelog still has the original row count, and even when interrogated no reference to the new domain class

So, I'm at a loss to explain what is going on. I can deal with the extra create/drop indexes & sequences, but I can't seem to get the liquibase stuff working. Can anyone shed some light on this for me?

Edit

I did some more digging into the NullPointer, and it seems to come from the class liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGenerator.java:45, where the plugin is trying to construct a foreign key to the inherited table id field (using tablePerHierarchy false for this inheritance). I couldn't find anything that seemed related to this error after a decent search.

Edit #2

I have found an issue on Github for the tablePerHierarchy NPE: https://github.com/grails-plugins/grails-database-migration/issues/68


Solution

  • I ended up getting this to work by setting hibernate.hbm2ddl.auto = 'none' in my application.groovy. Interestingly, when I tried to instead put this same config in my application.yml it had no effect.

    I suspect there may be other forces at play here as I tried replicating the behaviour on a fresh Grails project without issue.

    For the time being I have settled on using the hibernate property in the groovy file, though I am still curious as to why I couldn't get the config to work for me like a vanilla project.