ENT-5316 split schema migration

* ENT-5273 Split schema migration into separate core and app schema migration, with separate command line flags
This commit is contained in:
Christian Sailer 2020-06-15 15:52:31 +01:00 committed by GitHub
parent 1108ef2a24
commit 836dd559e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 170 additions and 104 deletions

View File

@ -4,7 +4,7 @@
cordaVersion=4.6 cordaVersion=4.6
versionSuffix=SNAPSHOT versionSuffix=SNAPSHOT
gradlePluginsVersion=5.0.9 gradlePluginsVersion=5.0.10
kotlinVersion=1.2.71 kotlinVersion=1.2.71
java8MinUpdateVersion=171 java8MinUpdateVersion=171
# ***************************************************************# # ***************************************************************#

View File

@ -56,7 +56,9 @@ class ReceiveFinalityFlowTest {
bob.assertFlowSentForObservationDueToConstraintError(paymentReceiverId) bob.assertFlowSentForObservationDueToConstraintError(paymentReceiverId)
// Restart Bob with the contracts CorDapp so that it can recover from the error // Restart Bob with the contracts CorDapp so that it can recover from the error
bob = mockNet.restartNode(bob, parameters = InternalMockNodeParameters(additionalCordapps = listOf(FINANCE_CONTRACTS_CORDAPP))) bob = mockNet.restartNode(bob,
parameters = InternalMockNodeParameters(additionalCordapps = listOf(FINANCE_CONTRACTS_CORDAPP)),
nodeFactory = { args -> InternalMockNetwork.MockNode(args, allowAppSchemaUpgradeWithCheckpoints = true) })
mockNet.runNetwork() mockNet.runNetwork()
assertThat(bob.services.getCashBalance(GBP)).isEqualTo(100.POUNDS) assertThat(bob.services.getCashBalance(GBP)).isEqualTo(100.POUNDS)
} }

View File

@ -39,24 +39,21 @@ class MissingSchemaMigrationTest {
dataSource = DataSourceFactory.createDataSource(hikariProperties) dataSource = DataSourceFactory.createDataSource(hikariProperties)
} }
private fun createSchemaMigration(schemasToMigrate: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean): SchemaMigration { private fun schemaMigration() = SchemaMigration(dataSource, null, null,
return SchemaMigration(schemasToMigrate, dataSource, null, null, TestIdentity(ALICE_NAME, 70).name)
TestIdentity(ALICE_NAME, 70).name, forceThrowOnMissingMigration)
}
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test that an error is thrown when forceThrowOnMissingMigration is set and a mapped schema is missing a migration`() { fun `test that an error is thrown when forceThrowOnMissingMigration is set and a mapped schema is missing a migration`() {
assertThatThrownBy { assertThatThrownBy {
createSchemaMigration(setOf(GoodSchema), true) schemaMigration().runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L }, setOf(GoodSchema), true)
.runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L })
}.isInstanceOf(MissingMigrationException::class.java) }.isInstanceOf(MissingMigrationException::class.java)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test that an error is not thrown when forceThrowOnMissingMigration is not set and a mapped schema is missing a migration`() { fun `test that an error is not thrown when forceThrowOnMissingMigration is not set and a mapped schema is missing a migration`() {
assertDoesNotThrow { assertDoesNotThrow {
createSchemaMigration(setOf(GoodSchema), false) schemaMigration().runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L }, setOf(GoodSchema), false)
.runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L })
} }
} }
@ -64,8 +61,7 @@ class MissingSchemaMigrationTest {
fun `test that there are no missing migrations for the node`() { fun `test that there are no missing migrations for the node`() {
assertDoesNotThrow("This test failure indicates " + assertDoesNotThrow("This test failure indicates " +
"a new table has been added to the node without the appropriate migration scripts being present") { "a new table has been added to the node without the appropriate migration scripts being present") {
createSchemaMigration(NodeSchemaService().internalSchemas(), false) schemaMigration().runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L }, NodeSchemaService().internalSchemas, true)
.runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L })
} }
} }

View File

@ -79,7 +79,8 @@ constructor(private val initSerEnv: Boolean,
Paths.get(System.getProperty("java.home"), "bin", "java").toString(), Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-jar", "-jar",
"corda.jar", "corda.jar",
"run-migration-scripts" "run-migration-scripts",
"--core-schemas"
) )
private const val LOGS_DIR_NAME = "logs" private const val LOGS_DIR_NAME = "logs"

View File

@ -16,14 +16,12 @@ import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path import java.nio.file.Path
import java.sql.Connection import java.sql.Connection
import java.sql.Statement
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import javax.sql.DataSource import javax.sql.DataSource
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
// Migrate the database to the current version, using liquibase. // Migrate the database to the current version, using liquibase.
open class SchemaMigration( open class SchemaMigration(
val schemas: Set<MappedSchema>,
val dataSource: DataSource, val dataSource: DataSource,
cordappLoader: CordappLoader? = null, cordappLoader: CordappLoader? = null,
private val currentDirectory: Path?, private val currentDirectory: Path?,
@ -31,9 +29,6 @@ open class SchemaMigration(
// its copy of the identity service. It is passed through using a system property. When multiple identity support is added, this will need // its copy of the identity service. It is passed through using a system property. When multiple identity support is added, this will need
// reworking so that multiple identities can be passed to the migration. // reworking so that multiple identities can be passed to the migration.
private val ourName: CordaX500Name? = null, private val ourName: CordaX500Name? = null,
// This parameter forces an error to be thrown if there are missing migrations. When using H2, Hibernate will automatically create schemas where they are
// missing, so no need to throw unless you're specifically testing whether all the migrations are present.
private val forceThrowOnMissingMigration: Boolean = false,
protected val databaseFactory: LiquibaseDatabaseFactory = LiquibaseDatabaseFactoryImpl()) { protected val databaseFactory: LiquibaseDatabaseFactory = LiquibaseDatabaseFactoryImpl()) {
companion object { companion object {
@ -53,10 +48,14 @@ open class SchemaMigration(
/** /**
* Will run the Liquibase migration on the actual database. * Will run the Liquibase migration on the actual database.
* @param existingCheckpoints Whether checkpoints exist that would prohibit running a migration
* @param schemas The set of MappedSchemas to check
* @param forceThrowOnMissingMigration throws an exception if a mapped schema is missing the migration resource. Can be set to false
* when allowing hibernate to create missing schemas in dev or tests.
*/ */
fun runMigration(existingCheckpoints: Boolean) { fun runMigration(existingCheckpoints: Boolean, schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean) {
migrateOlderDatabaseToUseLiquibase(existingCheckpoints) migrateOlderDatabaseToUseLiquibase(existingCheckpoints, schemas)
val resourcesAndSourceInfo = prepareResources() val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration)
// current version of Liquibase appears to be non-threadsafe // current version of Liquibase appears to be non-threadsafe
// this is apparent when multiple in-process nodes are all running migrations simultaneously // this is apparent when multiple in-process nodes are all running migrations simultaneously
@ -76,9 +75,12 @@ open class SchemaMigration(
/** /**
* Ensures that the database is up to date with the latest migration changes. * Ensures that the database is up to date with the latest migration changes.
* @param schemas The set of MappedSchemas to check
* @param forceThrowOnMissingMigration throws an exception if a mapped schema is missing the migration resource. Can be set to false
* when allowing hibernate to create missing schemas in dev or tests.
*/ */
fun checkState() { fun checkState(schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean) {
val resourcesAndSourceInfo = prepareResources() val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration)
// current version of Liquibase appears to be non-threadsafe // current version of Liquibase appears to be non-threadsafe
// this is apparent when multiple in-process nodes are all running migrations simultaneously // this is apparent when multiple in-process nodes are all running migrations simultaneously
@ -110,7 +112,7 @@ open class SchemaMigration(
} }
} }
private fun logOrThrowMigrationError(mappedSchema: MappedSchema): String? = private fun logOrThrowMigrationError(mappedSchema: MappedSchema, forceThrowOnMissingMigration: Boolean): String? =
if (forceThrowOnMissingMigration) { if (forceThrowOnMissingMigration) {
throw MissingMigrationException(mappedSchema) throw MissingMigrationException(mappedSchema)
} else { } else {
@ -121,15 +123,13 @@ open class SchemaMigration(
// Virtual file name of the changelog that includes all schemas. // Virtual file name of the changelog that includes all schemas.
val dynamicInclude = "master.changelog.json" val dynamicInclude = "master.changelog.json"
protected fun prepareResources(): List<Pair<CustomResourceAccessor, String>> { protected fun prepareResources(schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean): List<Pair<CustomResourceAccessor, String>> {
// Collect all changelog files referenced in the included schemas. // Collect all changelog files referenced in the included schemas.
val changelogList = schemas.mapNotNull { mappedSchema -> val changelogList = schemas.mapNotNull { mappedSchema ->
val resource = getMigrationResource(mappedSchema, classLoader) val resource = getMigrationResource(mappedSchema, classLoader)
when { when {
resource != null -> resource resource != null -> resource
// Corda OS FinanceApp in v3 has no Liquibase script, so no error is raised else -> logOrThrowMigrationError(mappedSchema, forceThrowOnMissingMigration)
(mappedSchema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || mappedSchema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1") && mappedSchema.migrationResource == null -> null
else -> logOrThrowMigrationError(mappedSchema)
} }
} }
@ -155,21 +155,8 @@ open class SchemaMigration(
} }
/** For existing database created before verions 4.0 add Liquibase support - creates DATABASECHANGELOG and DATABASECHANGELOGLOCK tables and marks changesets as 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 { private fun migrateOlderDatabaseToUseLiquibase(existingCheckpoints: Boolean, schemas: Set<MappedSchema>): Boolean {
val isFinanceAppWithLiquibase = schemas.any { schema -> val isExistingDBWithoutLiquibase = dataSource.connection.use {
(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
}
val (isExistingDBWithoutLiquibase, isFinanceAppWithLiquibaseNotMigrated) = dataSource.connection.use {
val existingDatabase = it.metaData.getTables(null, null, "NODE%", null).next() val existingDatabase = it.metaData.getTables(null, null, "NODE%", null).next()
// Lower case names for PostgreSQL // Lower case names for PostgreSQL
@ -179,12 +166,7 @@ open class SchemaMigration(
// Lower case names for PostgreSQL // Lower case names for PostgreSQL
|| it.metaData.getTables(null, null, "databasechangelog%", null).next() || it.metaData.getTables(null, null, "databasechangelog%", null).next()
val isFinanceAppWithLiquibaseNotMigrated = isFinanceAppWithLiquibase // If Finance App is pre v4.0 then no need to migrate it so no need to check. existingDatabase && !hasLiquibase
&& 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.
Pair(existingDatabase && !hasLiquibase, isFinanceAppWithLiquibaseNotMigrated)
} }
if (isExistingDBWithoutLiquibase && existingCheckpoints) if (isExistingDBWithoutLiquibase && existingCheckpoints)
@ -219,12 +201,6 @@ open class SchemaMigration(
preV4Baseline.addAll(listOf("migration/notary-bft-smart.changelog-init.xml", preV4Baseline.addAll(listOf("migration/notary-bft-smart.changelog-init.xml",
"migration/notary-bft-smart.changelog-v1.xml")) "migration/notary-bft-smart.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()) { if (preV4Baseline.isNotEmpty()) {
val dynamicInclude = "master.changelog.json" // Virtual file name of the changelog that includes all schemas. val dynamicInclude = "master.changelog.json" // Virtual file name of the changelog that includes all schemas.
@ -235,7 +211,7 @@ open class SchemaMigration(
liquibase.changeLogSync(Contexts(), LabelExpression()) liquibase.changeLogSync(Contexts(), LabelExpression())
} }
} }
return isExistingDBWithoutLiquibase || isFinanceAppWithLiquibaseNotMigrated return isExistingDBWithoutLiquibase
} }
private fun checkResourcesInClassPath(resources: List<String?>) { private fun checkResourcesInClassPath(resources: List<String?>) {

View File

@ -37,7 +37,8 @@ class FlowCheckpointVersionNodeStartupCheckTest {
startNodesInProcess = false, startNodesInProcess = false,
inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
cordappsForAllNodes = emptyList(), cordappsForAllNodes = emptyList(),
notarySpecs = emptyList() notarySpecs = emptyList(),
allowHibernateToManageAppSchema = false
)) { )) {
createSuspendedFlowInBob() createSuspendedFlowInBob()
val cordappsDir = baseDirectory(BOB_NAME) / "cordapps" val cordappsDir = baseDirectory(BOB_NAME) / "cordapps"

View File

@ -86,7 +86,7 @@ class NodeStatePersistenceTests {
nodeName nodeName
}() }()
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to "false")).getOrThrow() val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { val result = CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
val page = it.proxy.vaultQuery(MessageState::class.java) val page = it.proxy.vaultQuery(MessageState::class.java)
page.states.singleOrNull() page.states.singleOrNull()

View File

@ -86,8 +86,8 @@ abstract class StatemachineErrorHandlingTest {
internal fun getBytemanOutput(nodeHandle: NodeHandle): List<String> { internal fun getBytemanOutput(nodeHandle: NodeHandle): List<String> {
return nodeHandle.baseDirectory return nodeHandle.baseDirectory
.list() .list()
.first { it.toString().contains("net.corda.node.Corda") && it.toString().contains("stdout.log") } .filter { it.toString().contains("net.corda.node.Corda") && it.toString().contains("stdout.log") }
.readAllLines() .flatMap { it.readAllLines() }
} }
@StartableByRPC @StartableByRPC

View File

@ -15,6 +15,7 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.enclosedCordapp
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
@ -23,7 +24,7 @@ class CordappScanningDriverTest {
fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() { fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() {
val user = User("u", "p", setOf(startFlow<ReceiveFlow>())) val user = User("u", "p", setOf(startFlow<ReceiveFlow>()))
// The driver will automatically pick up the annotated flows below // The driver will automatically pick up the annotated flows below
driver(DriverParameters(notarySpecs = emptyList())) { driver(DriverParameters(notarySpecs = emptyList(), cordappsForAllNodes = listOf(enclosedCordapp()))) {
val (alice, bob) = listOf( val (alice, bob) = listOf(
startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)), startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)),
startNode(providedName = BOB_NAME)).transpose().getOrThrow() startNode(providedName = BOB_NAME)).transpose().getOrThrow()

View File

@ -21,7 +21,8 @@ class NodeConfigParsingTests {
driver(DriverParameters( driver(DriverParameters(
environmentVariables = mapOf("corda_sshd_port" to sshPort.toString()), environmentVariables = mapOf("corda_sshd_port" to sshPort.toString()),
startNodesInProcess = false, startNodesInProcess = false,
portAllocation = portAllocator)) { portAllocation = portAllocator,
cordappsForAllNodes = emptyList())) {
val hasSsh = startNode().get() val hasSsh = startNode().get()
.logFile() .logFile()
.readLines() .readLines()
@ -39,7 +40,8 @@ class NodeConfigParsingTests {
driver(DriverParameters( driver(DriverParameters(
environmentVariables = mapOf("CORDA_sshd_port" to sshPort.toString()), environmentVariables = mapOf("CORDA_sshd_port" to sshPort.toString()),
startNodesInProcess = false, startNodesInProcess = false,
portAllocation = portAllocator)) { portAllocation = portAllocator,
cordappsForAllNodes = emptyList())) {
val hasSsh = startNode().get() val hasSsh = startNode().get()
.logFile() .logFile()
.readLines() .readLines()
@ -58,7 +60,8 @@ class NodeConfigParsingTests {
environmentVariables = mapOf("CORDA.sshd.port" to sshPort.toString(), environmentVariables = mapOf("CORDA.sshd.port" to sshPort.toString(),
"corda.devMode" to true.toString()), "corda.devMode" to true.toString()),
startNodesInProcess = false, startNodesInProcess = false,
portAllocation = portAllocator)) { portAllocation = portAllocator,
cordappsForAllNodes = emptyList())) {
val hasSsh = startNode(NodeParameters()).get() val hasSsh = startNode(NodeParameters()).get()
.logFile() .logFile()
.readLines() .readLines()
@ -95,7 +98,8 @@ class NodeConfigParsingTests {
"corda_bad_key" to "2077"), "corda_bad_key" to "2077"),
startNodesInProcess = false, startNodesInProcess = false,
portAllocation = portAllocator, portAllocation = portAllocator,
notarySpecs = emptyList())) { notarySpecs = emptyList(),
cordappsForAllNodes = emptyList())) {
val hasWarning = startNode() val hasWarning = startNode()
.getOrThrow() .getOrThrow()

View File

@ -172,10 +172,10 @@ import org.apache.activemq.artemis.utils.ReusableLatch
import org.jolokia.jvmagent.JolokiaServer import org.jolokia.jvmagent.JolokiaServer
import org.jolokia.jvmagent.JolokiaServerConfig import org.jolokia.jvmagent.JolokiaServerConfig
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory
import rx.Scheduler import rx.Scheduler
import java.io.IOException import java.io.IOException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStoreException import java.security.KeyStoreException
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
@ -184,7 +184,7 @@ import java.sql.Savepoint
import java.time.Clock import java.time.Clock
import java.time.Duration import java.time.Duration
import java.time.format.DateTimeParseException import java.time.format.DateTimeParseException
import java.util.Properties import java.util.*
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
@ -194,6 +194,32 @@ import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import java.util.function.Consumer import java.util.function.Consumer
import javax.persistence.EntityManager import javax.persistence.EntityManager
import javax.sql.DataSource
import kotlin.collections.ArrayList
import kotlin.collections.List
import kotlin.collections.MutableList
import kotlin.collections.MutableSet
import kotlin.collections.Set
import kotlin.collections.drop
import kotlin.collections.emptyList
import kotlin.collections.filterNotNull
import kotlin.collections.first
import kotlin.collections.flatMap
import kotlin.collections.fold
import kotlin.collections.forEach
import kotlin.collections.groupBy
import kotlin.collections.last
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mapOf
import kotlin.collections.mutableListOf
import kotlin.collections.mutableSetOf
import kotlin.collections.plus
import kotlin.collections.plusAssign
import kotlin.collections.reversed
import kotlin.collections.setOf
import kotlin.collections.single
import kotlin.collections.toSet
/** /**
* A base node implementation that can be customised either for production (with real implementations that do real * A base node implementation that can be customised either for production (with real implementations that do real
@ -212,9 +238,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val busyNodeLatch: ReusableLatch = ReusableLatch(), val busyNodeLatch: ReusableLatch = ReusableLatch(),
djvmBootstrapSource: ApiSource = EmptyApi, djvmBootstrapSource: ApiSource = EmptyApi,
djvmCordaSource: UserSource? = null, djvmCordaSource: UserSource? = null,
protected val allowHibernateToManageAppSchema: Boolean = false) : SingletonSerializeAsToken() { protected val allowHibernateToManageAppSchema: Boolean = false,
private val allowAppSchemaUpgradeWithCheckpoints: Boolean = false) : SingletonSerializeAsToken() {
protected abstract val log: Logger protected abstract val log: Logger
@Suppress("LeakingThis") @Suppress("LeakingThis")
private var tokenizableServices: MutableList<SerializeAsToken>? = mutableListOf(platformClock, this) private var tokenizableServices: MutableList<SerializeAsToken>? = mutableListOf(platformClock, this)
@ -472,12 +500,20 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} }
} }
open fun runDatabaseMigrationScripts() { open fun runDatabaseMigrationScripts(
updateCoreSchemas: Boolean,
updateAppSchemas: Boolean,
updateAppSchemasWithCheckpoints: Boolean
) {
check(started == null) { "Node has already been started" } check(started == null) { "Node has already been started" }
Node.printBasicNodeInfo("Running database schema migration scripts ...") Node.printBasicNodeInfo("Running database schema migration scripts ...")
val props = configuration.dataSourceProperties val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.") if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
database.startHikariPool(props, schemaService.internalSchemas(), metricRegistry, this.cordappLoader, configuration.baseDirectory, configuration.myLegalName, runMigrationScripts = true) database.startHikariPool(props, metricRegistry) { dataSource, haveCheckpoints ->
SchemaMigration(dataSource, cordappLoader, configuration.baseDirectory, configuration.myLegalName)
.checkOrUpdate(schemaService.internalSchemas, updateCoreSchemas, haveCheckpoints, true)
.checkOrUpdate(schemaService.appSchemas, updateAppSchemas, !updateAppSchemasWithCheckpoints && haveCheckpoints, false)
}
// Now log the vendor string as this will also cause a connection to be tested eagerly. // Now log the vendor string as this will also cause a connection to be tested eagerly.
logVendorString(database, log) logVendorString(database, log)
if (allowHibernateToManageAppSchema) { if (allowHibernateToManageAppSchema) {
@ -987,7 +1023,12 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
protected open fun startDatabase() { protected open fun startDatabase() {
val props = configuration.dataSourceProperties val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.") if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
database.startHikariPool(props, schemaService.internalSchemas(), metricRegistry, this.cordappLoader, configuration.baseDirectory, configuration.myLegalName, runMigrationScripts = runMigrationScripts) database.startHikariPool(props, metricRegistry) { dataSource, haveCheckpoints ->
SchemaMigration(dataSource, cordappLoader, configuration.baseDirectory, configuration.myLegalName)
.checkOrUpdate(schemaService.internalSchemas, runMigrationScripts, haveCheckpoints, true)
.checkOrUpdate(schemaService.appSchemas, runMigrationScripts, haveCheckpoints && !allowAppSchemaUpgradeWithCheckpoints, false)
}
// Now log the vendor string as this will also cause a connection to be tested eagerly. // Now log the vendor string as this will also cause a connection to be tested eagerly.
logVendorString(database, log) logVendorString(database, log)
} }
@ -1388,23 +1429,16 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig,
allowHibernateToManageAppSchema = allowHibernateToManageAppSchema) allowHibernateToManageAppSchema = allowHibernateToManageAppSchema)
} }
@Suppress("LongParameterList", "ComplexMethod", "ThrowsCount") @Suppress("ThrowsCount")
fun CordaPersistence.startHikariPool( fun CordaPersistence.startHikariPool(
hikariProperties: Properties, hikariProperties: Properties,
schemas: Set<MappedSchema>,
metricRegistry: MetricRegistry? = null, metricRegistry: MetricRegistry? = null,
cordappLoader: CordappLoader? = null, schemaMigration: (DataSource, Boolean) -> Unit) {
currentDir: Path? = null,
ourName: CordaX500Name,
runMigrationScripts: Boolean = false) {
try { try {
val dataSource = DataSourceFactory.createDataSource(hikariProperties, metricRegistry = metricRegistry) val dataSource = DataSourceFactory.createDataSource(hikariProperties, metricRegistry = metricRegistry)
val schemaMigration = SchemaMigration(schemas, dataSource, cordappLoader, currentDir, ourName) val haveCheckpoints = dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L }
if (runMigrationScripts) {
schemaMigration.runMigration(dataSource.connection.use { DBCheckpointStorage.getCheckpointCount(it) != 0L }) schemaMigration(dataSource, haveCheckpoints)
} else {
schemaMigration.checkState()
}
start(dataSource) start(dataSource)
} catch (ex: Exception) { } catch (ex: Exception) {
when { when {
@ -1416,13 +1450,23 @@ fun CordaPersistence.startHikariPool(
"Could not find the database driver class. Please add it to the 'drivers' folder.", "Could not find the database driver class. Please add it to the 'drivers' folder.",
NodeDatabaseErrors.MISSING_DRIVER) NodeDatabaseErrors.MISSING_DRIVER)
ex is OutstandingDatabaseChangesException -> throw (DatabaseIncompatibleException(ex.message)) ex is OutstandingDatabaseChangesException -> throw (DatabaseIncompatibleException(ex.message))
else -> else -> {
LoggerFactory.getLogger("CordaPersistence extension").error("Could not create the DataSource", ex)
throw CouldNotCreateDataSourceException( throw CouldNotCreateDataSourceException(
"Could not create the DataSource: ${ex.message}", "Could not create the DataSource: ${ex.message}",
NodeDatabaseErrors.FAILED_STARTUP, NodeDatabaseErrors.FAILED_STARTUP,
cause = ex) cause = ex)
} }
} }
}
}
fun SchemaMigration.checkOrUpdate(schemas: Set<MappedSchema>, update: Boolean, haveCheckpoints: Boolean, forceThrowOnMissingMigration: Boolean): SchemaMigration {
if (update)
this.runMigration(haveCheckpoints, schemas, forceThrowOnMissingMigration)
else
this.checkState(schemas, forceThrowOnMissingMigration)
return this
} }
fun clientSslOptionsCompatibleWith(nodeRpcOptions: NodeRpcOptions): ClientRpcSslOptions? { fun clientSslOptionsCompatibleWith(nodeRpcOptions: NodeRpcOptions): ClientRpcSslOptions? {

View File

@ -561,11 +561,14 @@ open class Node(configuration: NodeConfiguration,
return super.generateAndSaveNodeInfo() return super.generateAndSaveNodeInfo()
} }
override fun runDatabaseMigrationScripts() { override fun runDatabaseMigrationScripts(
updateCoreSchemas: Boolean,
updateAppSchemas: Boolean,
updateAppSchemasWithCheckpoints: Boolean) {
if (allowHibernateToManageAppSchema) { if (allowHibernateToManageAppSchema) {
initialiseSerialization() initialiseSerialization()
} }
super.runDatabaseMigrationScripts() super.runDatabaseMigrationScripts(updateCoreSchemas, updateAppSchemas, updateAppSchemasWithCheckpoints)
} }
override fun start(): NodeInfo { override fun start(): NodeInfo {

View File

@ -4,12 +4,25 @@ import net.corda.node.internal.Node
import net.corda.node.internal.NodeCliCommand import net.corda.node.internal.NodeCliCommand
import net.corda.node.internal.NodeStartup import net.corda.node.internal.NodeStartup
import net.corda.node.internal.RunAfterNodeInitialisation import net.corda.node.internal.RunAfterNodeInitialisation
import picocli.CommandLine
class RunMigrationScriptsCli(startup: NodeStartup) : NodeCliCommand("run-migration-scripts", "Run the database migration scripts and create or update schemas", startup) { class RunMigrationScriptsCli(startup: NodeStartup) : NodeCliCommand("run-migration-scripts", "Run the database migration scripts and create or update schemas", startup) {
@CommandLine.Option(names = ["--core-schemas"], description = ["Manage the core/node schemas"])
var updateCoreSchemas: Boolean = false
@CommandLine.Option(names = ["--app-schemas"], description = ["Manage the CorDapp schemas"])
var updateAppSchemas: Boolean = false
@CommandLine.Option(names = ["--update-app-schema-with-checkpoints"], description = ["Allow updating app schema even if there are suspended flows"])
var updateAppSchemaWithCheckpoints: Boolean = false
override fun runProgram(): Int { override fun runProgram(): Int {
require(updateAppSchemas || updateCoreSchemas) { "Nothing to do: at least one of --core-schemas or --app-schemas must be set" }
return startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation { return startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
override fun run(node: Node) { override fun run(node: Node) {
node.runDatabaseMigrationScripts() node.runDatabaseMigrationScripts(updateCoreSchemas, updateAppSchemas, updateAppSchemaWithCheckpoints)
} }
}) })
} }

View File

@ -62,14 +62,13 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
NodeInfoSchemaV1, NodeInfoSchemaV1,
NodeCoreV1) NodeCoreV1)
fun internalSchemas() = requiredSchemas + extraSchemas.filter { schema -> val internalSchemas = requiredSchemas + extraSchemas.filter { schema ->
// when mapped schemas from the finance module are present, they are considered as internal ones
schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" ||
schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1" ||
schema::class.qualifiedName == "net.corda.node.services.transactions.NodeNotarySchemaV1" || schema::class.qualifiedName == "net.corda.node.services.transactions.NodeNotarySchemaV1" ||
schema::class.qualifiedName?.startsWith("net.corda.notary.") ?: false schema::class.qualifiedName?.startsWith("net.corda.notary.") ?: false
} }
val appSchemas = extraSchemas - internalSchemas
override val schemas: Set<MappedSchema> = requiredSchemas + extraSchemas override val schemas: Set<MappedSchema> = requiredSchemas + extraSchemas
// Currently returns all schemas supported by the state, with no filtering or enrichment. // Currently returns all schemas supported by the state, with no filtering or enrichment.

View File

@ -2,11 +2,13 @@ package net.corda.node.services.persistence
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.internal.checkOrUpdate
import net.corda.node.internal.createCordaPersistence import net.corda.node.internal.createCordaPersistence
import net.corda.node.internal.startHikariPool import net.corda.node.internal.startHikariPool
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
@ -93,7 +95,10 @@ class DbMapDeadlockTest {
val dbConfig = DatabaseConfig() val dbConfig = DatabaseConfig()
val schemaService = NodeSchemaService(extraSchemas = setOf(LockDbSchemaV2)) val schemaService = NodeSchemaService(extraSchemas = setOf(LockDbSchemaV2))
createCordaPersistence(dbConfig, { null }, { null }, schemaService, hikariProperties, cacheFactory, null).apply { createCordaPersistence(dbConfig, { null }, { null }, schemaService, hikariProperties, cacheFactory, null).apply {
startHikariPool(hikariProperties, schemaService.schemas, ourName = TestIdentity(ALICE_NAME, 70).name, runMigrationScripts = true) startHikariPool(hikariProperties) { dataSource, haveCheckpoints ->
SchemaMigration(dataSource, null, null, TestIdentity(ALICE_NAME, 70).name)
.checkOrUpdate(schemaService.schemas, true, haveCheckpoints, false)
}
}.use { persistence -> }.use { persistence ->
// First clean up any remains from previous test runs // First clean up any remains from previous test runs

View File

@ -90,6 +90,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
} }
cordapp project(':samples:attachment-demo:contracts') cordapp project(':samples:attachment-demo:contracts')
cordapp project(':samples:attachment-demo:workflows') cordapp project(':samples:attachment-demo:workflows')
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -48,6 +48,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
nodeDefaults { nodeDefaults {
cordapp project(':finance:workflows') cordapp project(':finance:workflows')
cordapp project(':finance:contracts') cordapp project(':finance:contracts')
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -25,6 +25,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
} }
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]] rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
cordapp project(':samples:cordapp-configuration:workflows') cordapp project(':samples:cordapp-configuration:workflows')
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -60,6 +60,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
} }
cordapp project(':samples:irs-demo:cordapp:contracts-irs') cordapp project(':samples:irs-demo:cordapp:contracts-irs')
cordapp project(':samples:irs-demo:cordapp:workflows-irs') cordapp project(':samples:irs-demo:cordapp:workflows-irs')
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -36,6 +36,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
} }
cordapp project(':samples:network-verifier:contracts') cordapp project(':samples:network-verifier:contracts')
cordapp project(':samples:network-verifier:workflows') cordapp project(':samples:network-verifier:workflows')
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -44,6 +44,7 @@ task deployNodesSingle(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) {
extraConfig = [h2Settings: [address: "localhost:0"]] extraConfig = [h2Settings: [address: "localhost:0"]]
cordapp project(':samples:notary-demo:contracts') cordapp project(':samples:notary-demo:contracts')
cordapp project(':samples:notary-demo:workflows') cordapp project(':samples:notary-demo:workflows')
runSchemaMigration = true
} }
node { node {
name "O=Alice Corp,L=Madrid,C=ES" name "O=Alice Corp,L=Madrid,C=ES"

View File

@ -91,6 +91,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
cordapp project(':samples:simm-valuation-demo:contracts-states') cordapp project(':samples:simm-valuation-demo:contracts-states')
cordapp project(':samples:simm-valuation-demo:flows') cordapp project(':samples:simm-valuation-demo:flows')
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]] rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -81,6 +81,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
cordapp project(':finance:workflows') cordapp project(':finance:workflows')
cordapp project(':finance:contracts') cordapp project(':finance:contracts')
cordapp project(':samples:trader-demo:workflows-trader') cordapp project(':samples:trader-demo:workflows-trader')
runSchemaMigration = true
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"

View File

@ -127,7 +127,7 @@ open class MockServices private constructor(
val cordappLoader = cordappLoaderForPackages(cordappPackages) val cordappLoader = cordappLoaderForPackages(cordappPackages)
val dataSourceProps = makeTestDataSourceProperties() val dataSourceProps = makeTestDataSourceProperties()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas()) val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas)
val keyManagementService = MockKeyManagementService( val keyManagementService = MockKeyManagementService(
identityService, identityService,
*arrayOf(initialIdentity.keyPair) + moreKeys *arrayOf(initialIdentity.keyPair) + moreKeys
@ -170,7 +170,7 @@ open class MockServices private constructor(
wellKnownPartyFromX500Name = identityService::wellKnownPartyFromX500Name, wellKnownPartyFromX500Name = identityService::wellKnownPartyFromX500Name,
wellKnownPartyFromAnonymous = identityService::wellKnownPartyFromAnonymous, wellKnownPartyFromAnonymous = identityService::wellKnownPartyFromAnonymous,
schemaService = schemaService, schemaService = schemaService,
internalSchemas = schemaService.internalSchemas() internalSchemas = schemaService.internalSchemas
) )
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, cacheFactory) val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, cacheFactory)

View File

@ -337,6 +337,8 @@ class DriverDSLImpl(
return startOutOfProcessMiniNode(config, return startOutOfProcessMiniNode(config,
listOfNotNull( listOfNotNull(
"run-migration-scripts", "run-migration-scripts",
"--core-schemas",
"--app-schemas",
if (hibernateForAppSchema) "--allow-hibernate-to-manage-app-schema" else null if (hibernateForAppSchema) "--allow-hibernate-to-manage-app-schema" else null
).toTypedArray()).map { config } ).toTypedArray()).map { config }
} }

View File

@ -279,7 +279,10 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
} }
} }
open class MockNode(args: MockNodeArgs, private val mockFlowManager: MockNodeFlowManager = args.flowManager) : AbstractNode<TestStartedNode>( open class MockNode(
args: MockNodeArgs,
private val mockFlowManager: MockNodeFlowManager = args.flowManager,
allowAppSchemaUpgradeWithCheckpoints: Boolean = false) : AbstractNode<TestStartedNode>(
args.config, args.config,
TestClock(Clock.systemUTC()), TestClock(Clock.systemUTC()),
DefaultNamedCacheFactory(), DefaultNamedCacheFactory(),
@ -287,7 +290,8 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
mockFlowManager, mockFlowManager,
args.network.getServerThread(args.id), args.network.getServerThread(args.id),
args.network.busyLatch, args.network.busyLatch,
allowHibernateToManageAppSchema = true allowHibernateToManageAppSchema = true,
allowAppSchemaUpgradeWithCheckpoints = allowAppSchemaUpgradeWithCheckpoints
) { ) {
companion object { companion object {
private val staticLog = contextLogger() private val staticLog = contextLogger()

View File

@ -1,7 +1,11 @@
package net.corda.testing.internal package net.corda.testing.internal
import net.corda.core.context.AuthServiceId import net.corda.core.context.AuthServiceId
import net.corda.core.contracts.* import net.corda.core.contracts.Command
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -19,6 +23,8 @@ import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.coretesting.internal.asTestContextEnv import net.corda.coretesting.internal.asTestContextEnv
import net.corda.coretesting.internal.createTestSerializationEnv import net.corda.coretesting.internal.createTestSerializationEnv
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.node.internal.checkOrUpdate
import net.corda.node.internal.createCordaPersistence import net.corda.node.internal.createCordaPersistence
import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.startHikariPool import net.corda.node.internal.startHikariPool
@ -32,19 +38,17 @@ import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.loadDevCaTrustStore
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.nodeapi.internal.registerDevP2pCertificates import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.serialization.internal.amqp.AMQP_ENABLED import net.corda.serialization.internal.amqp.AMQP_ENABLED
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.net.ServerSocket import java.net.ServerSocket
import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.util.* import java.util.*
@ -169,7 +173,7 @@ fun configureDatabase(hikariProperties: Properties,
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?, wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
schemaService: SchemaService = NodeSchemaService(), schemaService: SchemaService = NodeSchemaService(),
internalSchemas: Set<MappedSchema> = NodeSchemaService().internalSchemas(), internalSchemas: Set<MappedSchema> = NodeSchemaService().internalSchemas,
cacheFactory: NamedCacheFactory = TestingNamedCacheFactory(), cacheFactory: NamedCacheFactory = TestingNamedCacheFactory(),
ourName: CordaX500Name = TestIdentity(ALICE_NAME, 70).name, ourName: CordaX500Name = TestIdentity(ALICE_NAME, 70).name,
runMigrationScripts: Boolean = true, runMigrationScripts: Boolean = true,
@ -183,7 +187,10 @@ fun configureDatabase(hikariProperties: Properties,
cacheFactory, cacheFactory,
null, null,
allowHibernateToManageAppSchema) allowHibernateToManageAppSchema)
persistence.startHikariPool(hikariProperties, internalSchemas, ourName = ourName, runMigrationScripts = runMigrationScripts) persistence.startHikariPool(hikariProperties) { dataSource, haveCheckpoints ->
SchemaMigration(dataSource, null, null, ourName)
.checkOrUpdate(internalSchemas, runMigrationScripts, haveCheckpoints, false)
}
return persistence return persistence
} }