diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index efced54d9c..1282a75c59 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,11 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* New configuration property `database.initialiseAppSchema` with values `UPDATE`, `VALIDATE` and `NONE`. + The property controls the behavior of the Hibernate DDL generation. `UPDATE` performs an update of CorDapp schemas, while + `VALID` only verifies their integrity. The property does not affect the node-specific DDL handling and + complements `database.initialiseSchema` to disable DDL handling altogether. + * ``JacksonSupport.createInMemoryMapper`` was incorrectly marked as deprecated and is no longer so. * Transaction building and verification enforces new contract attachment version non-downgrade rule. diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 4d5aaf4626..cb717c9462 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -79,9 +79,18 @@ The available config fields are listed below. :database: Database configuration: :transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in - ``java.sql.Connection``, but without the ``TRANSACTION_`` prefix. Defaults to REPEATABLE_READ. + ``java.sql.Connection``, but without the ``TRANSACTION_`` prefix. Defaults to ``REPEATABLE_READ``. :exportHibernateJMXStatistics: Whether to export Hibernate JMX statistics (caution: expensive run-time overhead) + :initialiseSchema: Boolean on whether to update database schema at startup (or create when node starts for the first time). + Defaults to ``true``. If set to ``false`` on startup, the node will validate if it's running against the compatible database schema. + + :initialiseAppSchema: The property allows to override (downgrade) ``database.initialiseSchema`` for the Hibernate + DDL generation for CorDapp schemas. ``UPDATE`` performs an update of CorDapp schemas, while ``VALID`` only verifies + their integrity and ``NONE`` performs no check. By default (if the property is not specified) CorDapp schemas + creation is controlled by ``initialiseSchema``. When ``initialiseSchema`` is set to false then ``initialiseAppSchema`` + may be set as ``VALID`` or ``NONE`` only. + :dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence. Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently the only configuration that has been tested, although in the future full support for other storage layers will be validated. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 0ca39f664c..ffe4959552 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -20,15 +20,23 @@ import javax.sql.DataSource */ const val NODE_DATABASE_PREFIX = "node_" +enum class SchemaInitializationType{ + NONE, + VALIDATE, + UPDATE +} + // This class forms part of the node config and so any changes to it must be handled with care data class DatabaseConfig( val initialiseSchema: Boolean = Defaults.initialiseSchema, + val initialiseAppSchema: SchemaInitializationType = Defaults.initialiseAppSchema, val transactionIsolationLevel: TransactionIsolationLevel = Defaults.transactionIsolationLevel, val exportHibernateJMXStatistics: Boolean = Defaults.exportHibernateJMXStatistics, val mappedSchemaCacheSize: Long = Defaults.mappedSchemaCacheSize ) { object Defaults { val initialiseSchema = true + val initialiseAppSchema = SchemaInitializationType.UPDATE val transactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ val exportHibernateJMXStatistics = false val mappedSchemaCacheSize = 100L diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt index fea52e35f5..637848d155 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt @@ -73,12 +73,22 @@ class HibernateConfiguration( logger.info("Creating session factory for schemas: $schemas") val serviceRegistry = BootstrapServiceRegistryBuilder().build() val metadataSources = MetadataSources(serviceRegistry) + + val hbm2dll: String = + if(databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) { + "update" + } else if((!databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) + || databaseConfig.initialiseAppSchema == SchemaInitializationType.VALIDATE) { + "validate" + } else { + "none" + } + // We set a connection provider as the auto schema generation requires it. The auto schema generation will not // necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though. - // TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs. val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name) - .setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initialiseSchema) "update" else "validate") .setProperty("hibernate.format_sql", "true") + .setProperty("hibernate.hbm2ddl.auto", hbm2dll) .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) schemas.forEach { schema -> diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt index e9b2f3a065..474c666026 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -14,6 +14,7 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.SslConfiguration import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.nodeapi.internal.persistence.SchemaInitializationType import net.corda.tools.shell.SSHDConfiguration import java.net.URL import java.nio.file.Path @@ -112,7 +113,11 @@ data class NodeConfigurationImpl( fun messagingServerExternal(messagingServerAddress: NetworkHostAndPort?) = messagingServerAddress != null - fun database(devMode: Boolean) = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode) + fun database(devMode: Boolean) = DatabaseConfig( + initialiseSchema = devMode, + initialiseAppSchema = if(devMode) SchemaInitializationType.UPDATE else SchemaInitializationType.VALIDATE, + exportHibernateJMXStatistics = devMode + ) } companion object { diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt index ce90d9560f..c81c0390c4 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -42,6 +42,7 @@ import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel +import net.corda.nodeapi.internal.persistence.SchemaInitializationType import net.corda.tools.shell.SSHDConfiguration internal object UserSpec : Configuration.Specification("User") { @@ -203,12 +204,13 @@ internal object SSHDConfigurationSpec : Configuration.Specification("DatabaseConfig") { private val initialiseSchema by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.initialiseSchema) + private val initialiseAppSchema by enum(SchemaInitializationType::class).optional().withDefaultValue(DatabaseConfig.Defaults.initialiseAppSchema) private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel) private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics) private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize) override fun parseValid(configuration: Config): Valid { - return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize])) + return valid(DatabaseConfig(configuration[initialiseSchema], configuration[initialiseAppSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize])) } }