liquibase.statement.core.CreateSequenceStatement: mssql, but the value is an empty list. That value is empty because it has been initialized before, during the initialization of XMLChangeLogSAXHandler: {noformat} main@1975, prio=5, in group 'main', status: 'RUNNING'

JIRA | Alina Mylka | 3 years ago
  1. 0

    Version 3.1.0 contained a fix to CORE-1661. Unfortunately that fix isn't enough. When I try to use the 'update' goal of liquibase-maven-plugin I still get validation errors with "createSequence is not supported on mssql" messages. I tried to debug the issue and AFAIU the problem is that there are two instances of MSSQLDatabase. 1. Instance one has no connection. It is created in DatabaseFactory.getInstance() and later stored in the DatabaseFactory.implementedDatabases field. That instance is used by the SqlGeneratorFactory to see if statements are supported on the database. The supportsSequences implementation in MSSQLDatabase returns false if the connection is null, so as far as the validation code is concerned - sequences are never supported. 2. Second instance has a connection, it is created in DatabaseFactory.findCorrectDatabaseImplementation called from CommandLineUtils.createDatabaseObject. It is not stored anywhere, but the entire real work is performed on this instance. It quickly gets a real database connection. With the connection, the supportsSequences method returns true. During the validation I caught a breakpoint in SqlGeneratorFactory.getGenerators. The overall stack trace looks like this: {noformat} main@1975, prio=5, in group 'main', status: 'RUNNING' at liquibase.sqlgenerator.SqlGeneratorFactory.getGenerators(SqlGeneratorFactory.java:94) at liquibase.sqlgenerator.SqlGeneratorFactory.supports(SqlGeneratorFactory.java:224) at liquibase.change.AbstractChange.supports(AbstractChange.java:290) at liquibase.change.AbstractChange.validate(AbstractChange.java:344) at liquibase.changelog.visitor.ValidatingVisitor.visit(ValidatingVisitor.java:92) at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:64) at liquibase.changelog.DatabaseChangeLog.validate(DatabaseChangeLog.java:151) at liquibase.Liquibase.update(Liquibase.java:198) at liquibase.Liquibase.update(Liquibase.java:181) at org.liquibase.maven.plugins.LiquibaseUpdate.doUpdate(LiquibaseUpdate.java:31) at org.liquibase.maven.plugins.AbstractLiquibaseUpdateMojo.performLiquibaseTask(AbstractLiquibaseUpdateMojo.java:24) at org.liquibase.maven.plugins.AbstractLiquibaseMojo.execute(AbstractLiquibaseMojo.java:377) {noformat} On that breakpoint, the input parameters are an instance of CreateSequenceStatement and a proper MSSQLDatabase instance with a connection. Unfortunately control reaches this code: {code} if (generatorsByKey.containsKey(key)) { return generatorsByKey.get(key); } {code} In my case generatorsByKey contains the key liquibase.statement.core.CreateSequenceStatement:mssql, but the value is an empty list. That value is empty because it has been initialized before, during the initialization of XMLChangeLogSAXHandler: {noformat} main@1975, prio=5, in group 'main', status: 'RUNNING' at liquibase.database.AbstractJdbcDatabase.getDatabaseMajorVersion(AbstractJdbcDatabase.java:204) at liquibase.database.core.MSSQLDatabase.supportsSequences(MSSQLDatabase.java:111) at liquibase.sqlgenerator.core.CreateSequenceGenerator.supports(CreateSequenceGenerator.java:17) at liquibase.sqlgenerator.core.CreateSequenceGenerator.supports(CreateSequenceGenerator.java:13) at liquibase.sqlgenerator.SqlGeneratorFactory.checkType(SqlGeneratorFactory.java:167) at liquibase.sqlgenerator.SqlGeneratorFactory.getGenerators(SqlGeneratorFactory.java:107) at liquibase.sqlgenerator.SqlGeneratorFactory.generateStatementsVolatile(SqlGeneratorFactory.java:206) at liquibase.change.AbstractChange.generateStatementsVolatile(AbstractChange.java:250) at liquibase.change.AbstractChange.supports(AbstractChange.java:282) at liquibase.change.ChangeParameterMetaData.analyzeSupportedDatabases(ChangeParameterMetaData.java:89) at liquibase.change.ChangeParameterMetaData.<init>(ChangeParameterMetaData.java:72) at liquibase.change.AbstractChange.createChangeParameterMetadata(AbstractChange.java:130) at liquibase.change.AbstractChange.createChangeMetaData(AbstractChange.java:72) at liquibase.change.ChangeFactory.getChangeMetaData(ChangeFactory.java:84) at liquibase.change.ChangeFactory.register(ChangeFactory.java:62) at liquibase.change.ChangeFactory.init(ChangeFactory.java:32) at liquibase.change.ChangeFactory.getInstance(ChangeFactory.java:42) at liquibase.parser.core.xml.XMLChangeLogSAXHandler.<init>(XMLChangeLogSAXHandler.java:120) at liquibase.parser.core.xml.XMLChangeLogSAXParser.parse(XMLChangeLogSAXParser.java:99) at liquibase.Liquibase.getDatabaseChangeLog(Liquibase.java:216) at liquibase.Liquibase.update(Liquibase.java:194) {noformat} So, during the first call to ChangeFactory.getInstance(), the metadata of each change is initialized. During that initialization several calls to SqlGeneratorFactory.getGenerators are made (through ChangeParameterMetaData.analyzeSupportedDatabases). Each of those calls is made with a 'passive', disconnected Database instance and modifies the state of the SqlGeneratorFactory because the results of getGenerators are cached. AFAIU the bug is that that cache, i.e. the content of SqlGeneratorFactory.generatorsByKey is WRONG when getGenerators is later called with a different, connected Database instance. The easiest solution would be to drop the cache, delete the SqlGeneratorFactory.generatorsByType field and compute appropriate values on each call. That would cost some performance though. My workaround is to introduce my own class {code} public class MSSQL2012Database extends MSSQLDatabase { public int getDatabaseMajorVersion() throws DatabaseException { return 11; } public int getPriority() { return LiquibaseDataType.PRIORITY_DATABASE; } } {code} I hope someone with more in-depth knowledge of Liquibase can come up with a better solution.

    JIRA | 3 years ago | Alina Mylka
    liquibase.statement.core.CreateSequenceStatement: mssql, but the value is an empty list. That value is empty because it has been initialized before, during the initialization of XMLChangeLogSAXHandler: {noformat} main@1975, prio=5, in group 'main', status: 'RUNNING'

    Root Cause Analysis

    1. liquibase.statement.core.CreateSequenceStatement

      mssql, but the value is an empty list. That value is empty because it has been initialized before, during the initialization of XMLChangeLogSAXHandler: {noformat} main@1975, prio=5, in group 'main', status: 'RUNNING'

      at liquibase.database.AbstractJdbcDatabase.getDatabaseMajorVersion()
    2. Liquibase Core
      Liquibase.update
      1. liquibase.database.AbstractJdbcDatabase.getDatabaseMajorVersion(AbstractJdbcDatabase.java:204)
      2. liquibase.database.core.MSSQLDatabase.supportsSequences(MSSQLDatabase.java:111)
      3. liquibase.sqlgenerator.core.CreateSequenceGenerator.supports(CreateSequenceGenerator.java:17)
      4. liquibase.sqlgenerator.core.CreateSequenceGenerator.supports(CreateSequenceGenerator.java:13)
      5. liquibase.sqlgenerator.SqlGeneratorFactory.checkType(SqlGeneratorFactory.java:167)
      6. liquibase.sqlgenerator.SqlGeneratorFactory.getGenerators(SqlGeneratorFactory.java:107)
      7. liquibase.sqlgenerator.SqlGeneratorFactory.generateStatementsVolatile(SqlGeneratorFactory.java:206)
      8. liquibase.change.AbstractChange.generateStatementsVolatile(AbstractChange.java:250)
      9. liquibase.change.AbstractChange.supports(AbstractChange.java:282)
      10. liquibase.change.ChangeParameterMetaData.analyzeSupportedDatabases(ChangeParameterMetaData.java:89)
      11. liquibase.change.ChangeParameterMetaData.<init>(ChangeParameterMetaData.java:72)
      12. liquibase.change.AbstractChange.createChangeParameterMetadata(AbstractChange.java:130)
      13. liquibase.change.AbstractChange.createChangeMetaData(AbstractChange.java:72)
      14. liquibase.change.ChangeFactory.getChangeMetaData(ChangeFactory.java:84)
      15. liquibase.change.ChangeFactory.register(ChangeFactory.java:62)
      16. liquibase.change.ChangeFactory.init(ChangeFactory.java:32)
      17. liquibase.change.ChangeFactory.getInstance(ChangeFactory.java:42)
      18. liquibase.parser.core.xml.XMLChangeLogSAXHandler.<init>(XMLChangeLogSAXHandler.java:120)
      19. liquibase.parser.core.xml.XMLChangeLogSAXParser.parse(XMLChangeLogSAXParser.java:99)
      20. liquibase.Liquibase.getDatabaseChangeLog(Liquibase.java:216)
      21. liquibase.Liquibase.update(Liquibase.java:194)
      21 frames