From 717204873554579e1849c65e95dea5c5f4d0cd03 Mon Sep 17 00:00:00 2001 From: szymonsztuka <szymon.sztuka@r3.com> Date: Mon, 10 Dec 2018 15:54:30 +0000 Subject: [PATCH] CORDA-2291 enable Finance App 3.x on Corda 4.x - Liquibase script is not required(#4382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finance CorDapp v3.0 and core node 3.0 database tables doesn't have Liquibase migration scripts, now in Corda v4.0 the Liquibase has been introduced. Allow older Finance Cordapp v3.0 which doesn't have Liquibase to run in node v4.0 and create Liquibase log entries for FinanceApp only if it has schema migration (so it’s of v4.0). At implementation level: there is new case when database has already Liquibase control tables however it doesn’t contains entries related to tables created by FInnaceApp and if the FinaceApp has Liquibase scheme it means it needs to be added to Liquibase logs. --- .../internal/persistence/SchemaMigration.kt | 109 ++++++++++-------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/SchemaMigration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/SchemaMigration.kt index e7067a3eac..c79482d541 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/SchemaMigration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/SchemaMigration.kt @@ -13,7 +13,7 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger import java.io.ByteArrayInputStream import java.io.InputStream -import java.io.Writer +import java.sql.Statement import javax.sql.DataSource class SchemaMigration( @@ -80,6 +80,8 @@ class SchemaMigration( val resource = getMigrationResource(mappedSchema, classLoader) when { resource != null -> resource + // Corda OS FinanceApp in v3 has no Liquibase script, so no error is raised + (mappedSchema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || mappedSchema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1") && mappedSchema.migrationResource == null -> null else -> throw MissingMigrationException(mappedSchema) } } @@ -104,60 +106,75 @@ class SchemaMigration( return DatabaseFactory.getInstance().findCorrectDatabaseImplementation(conn) } - /** For existing database created before verions 4.0 add Liquibase support - creates DATABASECHANGELOG and DATABASECHANGELOGLOCK tables and mark changesets are executed. */ + /** For existing database created before verions 4.0 add Liquibase support - creates DATABASECHANGELOG and DATABASECHANGELOGLOCK tables and marks changesets as executed. */ private fun migrateOlderDatabaseToUseLiquibase(existingCheckpoints: Boolean): Boolean { - //workaround to detect that if Corda finance module is in use then the most recent version with Liquibase migration scripts was deployed - if (schemas.any { schema -> - (schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1") - && schema.migrationResource == null - }) - throw DatabaseMigrationException("Detected incompatible corda-finance cordapp without database migration scripts, replace the existing corda-finance-VERSION.jar with the latest one.") - - val isExistingDBWithoutLiquibase = dataSource.connection.use { - (it.metaData.getTables(null, null, "NODE%", null).next() && - !it.metaData.getTables(null, null, "DATABASECHANGELOG%", null).next()) + val isFinanceAppWithLiquibase = schemas.any { schema -> + (schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" + || schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1") + && schema.migrationResource != null + } + val noLiquibaseEntryLogForFinanceApp: (Statement) -> Boolean = { + it.execute("SELECT COUNT(*) FROM DATABASECHANGELOG WHERE FILENAME IN ('migration/cash.changelog-init.xml','migration/commercial-paper.changelog-init.xml')") + if (it.resultSet.next()) + it.resultSet.getInt(1) == 0 + else + true } - when { - isExistingDBWithoutLiquibase && existingCheckpoints -> throw CheckpointsException() - isExistingDBWithoutLiquibase -> { - // Virtual file name of the changelog that includes all schemas. - val dynamicInclude = "master.changelog.json" - dataSource.connection.use { connection -> - // Schema migrations pre release 4.0 - val preV4Baseline = mutableListOf("migration/common.changelog-init.xml", - "migration/node-info.changelog-init.xml", - "migration/node-info.changelog-v1.xml", - "migration/node-info.changelog-v2.xml", - "migration/node-core.changelog-init.xml", - "migration/node-core.changelog-v3.xml", - "migration/node-core.changelog-v4.xml", - "migration/node-core.changelog-v5.xml", - "migration/node-core.changelog-pkey.xml", - "migration/vault-schema.changelog-init.xml", - "migration/vault-schema.changelog-v3.xml", - "migration/vault-schema.changelog-v4.xml", - "migration/vault-schema.changelog-pkey.xml") + val (isExistingDBWithoutLiquibase, isFinanceAppWithLiquibaseNotMigrated) = dataSource.connection.use { - if (schemas.any { schema -> schema.migrationResource == "cash.changelog-master" }) - preV4Baseline.addAll(listOf("migration/cash.changelog-init.xml", - "migration/cash.changelog-v1.xml")) + val existingDatabase = it.metaData.getTables(null, null, "NODE%", null).next() - if (schemas.any { schema -> schema.migrationResource == "commercial-paper.changelog-master" }) - preV4Baseline.addAll(listOf("migration/commercial-paper.changelog-init.xml", - "migration/commercial-paper.changelog-v1.xml")) + val hasLiquibase = it.metaData.getTables(null, null, "DATABASECHANGELOG%", null).next() - if (schemas.any { schema -> schema.migrationResource == "node-notary.changelog-master" }) - preV4Baseline.addAll(listOf("migration/node-notary.changelog-init.xml", - "migration/node-notary.changelog-v1.xml")) + val isFinanceAppWithLiquibaseNotMigrated = isFinanceAppWithLiquibase // If Finance App is pre v4.0 then no need to migrate it so no need to check. + && existingDatabase + && (!hasLiquibase // Migrate as other tables. + || (hasLiquibase && it.createStatement().use { noLiquibaseEntryLogForFinanceApp(it) })) // If Liquibase is already in the database check if Finance App schema log is missing. - val customResourceAccessor = CustomResourceAccessor(dynamicInclude, preV4Baseline, classLoader) - val liquibase = Liquibase(dynamicInclude, customResourceAccessor, getLiquibaseDatabase(JdbcConnection(connection))) - liquibase.changeLogSync(Contexts(), LabelExpression()) - } + Pair(existingDatabase && !hasLiquibase, isFinanceAppWithLiquibaseNotMigrated) + } + + if (isExistingDBWithoutLiquibase && existingCheckpoints) + throw CheckpointsException() + + // Schema migrations pre release 4.0 + val preV4Baseline = mutableListOf<String>() + if (isExistingDBWithoutLiquibase) { + preV4Baseline.addAll(listOf("migration/common.changelog-init.xml", + "migration/node-info.changelog-init.xml", + "migration/node-info.changelog-v1.xml", + "migration/node-info.changelog-v2.xml", + "migration/node-core.changelog-init.xml", + "migration/node-core.changelog-v3.xml", + "migration/node-core.changelog-v4.xml", + "migration/node-core.changelog-v5.xml", + "migration/node-core.changelog-pkey.xml", + "migration/vault-schema.changelog-init.xml", + "migration/vault-schema.changelog-v3.xml", + "migration/vault-schema.changelog-v4.xml", + "migration/vault-schema.changelog-pkey.xml")) + + if (schemas.any { schema -> schema.migrationResource == "node-notary.changelog-master" }) + preV4Baseline.addAll(listOf("migration/node-notary.changelog-init.xml", + "migration/node-notary.changelog-v1.xml")) + } + if (isFinanceAppWithLiquibaseNotMigrated) { + preV4Baseline.addAll(listOf("migration/cash.changelog-init.xml", + "migration/cash.changelog-v1.xml", + "migration/commercial-paper.changelog-init.xml", + "migration/commercial-paper.changelog-v1.xml")) + } + + if (preV4Baseline.isNotEmpty()) { + val dynamicInclude = "master.changelog.json" // Virtual file name of the changelog that includes all schemas. + dataSource.connection.use { connection -> + val customResourceAccessor = CustomResourceAccessor(dynamicInclude, preV4Baseline, classLoader) + val liquibase = Liquibase(dynamicInclude, customResourceAccessor, getLiquibaseDatabase(JdbcConnection(connection))) + liquibase.changeLogSync(Contexts(), LabelExpression()) } } - return isExistingDBWithoutLiquibase + return isExistingDBWithoutLiquibase || isFinanceAppWithLiquibaseNotMigrated } }