Skip to content

Commit

Permalink
Add quarkus.hibernate-orm.database.version-check.enabled
Browse files Browse the repository at this point in the history
This allows disabling the check on startup if one knows the database
won't be reachable.

It also currently defaults to being disabled when a dialect is set
explicitly (`quarkus.hibernate-orm.dialect=something`),
in order to work around problems we have with correctly
detecting the version on some databases that we don't have tests for
(unsupported ones).
  • Loading branch information
yrodiere committed Oct 8, 2024
1 parent 63a1fea commit 426f839
Show file tree
Hide file tree
Showing 16 changed files with 341 additions and 32 deletions.
15 changes: 12 additions & 3 deletions docs/src/main/asciidoc/hibernate-orm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,20 @@ or implicitly set by the Quarkus build process to a minimum supported version of
Quarkus will try to check this preconfigured version against the actual database version on startup,
leading to a startup failure when the actual version is lower.
This is because Hibernate ORM may generate SQL that is invalid
for versions of the database older than what is configured,
which would lead to runtime exceptions.
This is a safeguard: for versions of the database older than what is configured,
Hibernate ORM may generate SQL that is invalid which would lead to runtime exceptions.

Check warning on line 209 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possibility)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possibility)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 209, "column": 4}}}, "severity": "WARNING"}

Check warning on line 209 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 209, "column": 36}}}, "severity": "INFO"}
// TODO disable the check by default when offline startup is opted in
// See https://github.com/quarkusio/quarkus/issues/13522
If the database cannot be reached, a warning will be logged but startup will proceed.
You can optionally disable the version check if you know the database won't be reachable on startup
using <<quarkus-hibernate-orm_quarkus-hibernate-orm-database-version-check,`quarkus.hibernate-orm.database.version-check.enabled=false`>>.
// TODO change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
The version is disabled by default when a dialect is set explicitly,
as a workaround for https://github.com/quarkusio/quarkus/issues/42255[#42255]/link:https://github.com/quarkusio/quarkus/issues/43703[#43703].
====

[[hibernate-dialect-other-databases]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ public void configurationDescriptorBuilding(
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion),
Optional.ofNullable(xmlDescriptor.getProperties().getProperty(AvailableSettings.DIALECT)),
getMultiTenancyStrategy(
Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
.getProperties().getProperty("hibernate.multiTenancy"))), //FIXME this property is meaningless in Hibernate ORM 6
Expand Down Expand Up @@ -1103,6 +1104,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
jdbcDataSource.flatMap(JdbcDataSourceBuildItem::getDbVersion),
persistenceUnitConfig.dialect().dialect(),
multiTenancyStrategy,
hibernateOrmConfig.database().ormCompatibilityVersion(),
persistenceUnitConfig.unsupportedProperties()),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.quarkus.hibernate.orm.config.dialect;

import static io.quarkus.hibernate.orm.ResourceUtil.loadResourceAndReplacePlaceholders;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Map;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.MyEntity;
import io.quarkus.hibernate.orm.SmokeTestUtils;
import io.quarkus.hibernate.orm.runtime.config.DialectVersions;
import io.quarkus.test.QuarkusUnitTest;

/**
* Tests that the workaround for https://github.com/quarkusio/quarkus/issues/43703 /
* https://github.com/quarkusio/quarkus/issues/42255
* is effective.
*/
// TODO remove this test when change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
public class DbVersionCheckDisabledAutomaticallyPersistenceXmlTest {

private static final String ACTUAL_H2_VERSION = DialectVersions.Defaults.H2;
// We will set the DB version to something higher than the actual version: this is invalid.
private static final String CONFIGURED_DB_VERSION = "999.999.0";
static {
assertThat(ACTUAL_H2_VERSION)
.as("Test setup - we need the required version to be different from the actual one")
.doesNotStartWith(CONFIGURED_DB_VERSION);
}

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(SmokeTestUtils.class)
.addClass(MyEntity.class)
.addAsManifestResource(new StringAsset(loadResourceAndReplacePlaceholders(
"META-INF/some-persistence-with-h2-version-placeholder-and-explicit-dialect.xml",
Map.of("H2_VERSION", "999.999"))),
"persistence.xml"))
.withConfigurationResource("application-datasource-only.properties");

@Inject
SessionFactory sessionFactory;

@Inject
Session session;

@Test
public void dialectVersion() {
var dialectVersion = sessionFactory.unwrap(SessionFactoryImplementor.class).getJdbcServices().getDialect().getVersion();
assertThat(DialectVersions.toString(dialectVersion)).isEqualTo(CONFIGURED_DB_VERSION);
}

@Test
@Transactional
public void smokeTest() {
SmokeTestUtils.testSimplePersistRetrieveUpdateDelete(session,
MyEntity.class, MyEntity::new,
MyEntity::getId,
MyEntity::setName, MyEntity::getName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.quarkus.hibernate.orm.config.dialect;

import static org.assertj.core.api.Assertions.assertThat;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.MyEntity;
import io.quarkus.hibernate.orm.SmokeTestUtils;
import io.quarkus.hibernate.orm.runtime.config.DialectVersions;
import io.quarkus.test.QuarkusUnitTest;

/**
* Tests that the workaround for https://github.com/quarkusio/quarkus/issues/43703 /
* https://github.com/quarkusio/quarkus/issues/42255
* is effective.
*/
// TODO remove this test when change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
public class DbVersionCheckDisabledAutomaticallyTest {

private static final String ACTUAL_H2_VERSION = DialectVersions.Defaults.H2;
// We will set the DB version to something higher than the actual version: this is invalid.
private static final String CONFIGURED_DB_VERSION = "999.999.0";
static {
assertThat(ACTUAL_H2_VERSION)
.as("Test setup - we need the required version to be different from the actual one")
.doesNotStartWith(CONFIGURED_DB_VERSION);
}

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(SmokeTestUtils.class)
.addClass(MyEntity.class))
.withConfigurationResource("application.properties")
.overrideConfigKey("quarkus.datasource.db-version", "999.999")
// Setting a dialect should disable the version check, so Quarkus should boot just fine
.overrideConfigKey("quarkus.hibernate-orm.dialect", H2Dialect.class.getName());

@Inject
SessionFactory sessionFactory;

@Inject
Session session;

@Test
public void dialectVersion() {
var dialectVersion = sessionFactory.unwrap(SessionFactoryImplementor.class).getJdbcServices().getDialect().getVersion();
assertThat(DialectVersions.toString(dialectVersion)).isEqualTo(CONFIGURED_DB_VERSION);
}

@Test
@Transactional
public void smokeTest() {
SmokeTestUtils.testSimplePersistRetrieveUpdateDelete(session,
MyEntity.class, MyEntity::new,
MyEntity::getId,
MyEntity::setName, MyEntity::getName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.quarkus.hibernate.orm.config.dialect;

import static org.assertj.core.api.Assertions.assertThat;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.MyEntity;
import io.quarkus.hibernate.orm.SmokeTestUtils;
import io.quarkus.hibernate.orm.runtime.config.DialectVersions;
import io.quarkus.test.QuarkusUnitTest;

/**
* Tests that the workaround for https://github.com/quarkusio/quarkus/issues/43703 /
* https://github.com/quarkusio/quarkus/issues/42255
* is effective.
*/
// TODO remove this test when change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
public class DbVersionCheckDisabledExplicitlyTest {

private static final String ACTUAL_H2_VERSION = DialectVersions.Defaults.H2;
// We will set the DB version to something higher than the actual version: this is invalid.
private static final String CONFIGURED_DB_VERSION = "999.999.0";
static {
assertThat(ACTUAL_H2_VERSION)
.as("Test setup - we need the required version to be different from the actual one")
.doesNotStartWith(CONFIGURED_DB_VERSION);
}

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(SmokeTestUtils.class)
.addClass(MyEntity.class))
.withConfigurationResource("application.properties")
.overrideConfigKey("quarkus.datasource.db-version", "999.999")
// We disable the version check explicitly, so Quarkus should boot just fine
.overrideConfigKey("quarkus.hibernate-orm.database.version-check.enabled", "false");

@Inject
SessionFactory sessionFactory;

@Inject
Session session;

@Test
public void dialectVersion() {
var dialectVersion = sessionFactory.unwrap(SessionFactoryImplementor.class).getJdbcServices().getDialect().getVersion();
assertThat(DialectVersions.toString(dialectVersion)).isEqualTo(CONFIGURED_DB_VERSION);
}

@Test
@Transactional
public void smokeTest() {
SmokeTestUtils.testSimplePersistRetrieveUpdateDelete(session,
MyEntity.class, MyEntity::new,
MyEntity::getId,
MyEntity::setName, MyEntity::getName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public class DbVersionInvalidTest {
"Consider upgrading your database",
"Alternatively, rebuild your application with 'quarkus.datasource.db-version="
+ ACTUAL_H2_VERSION_REPORTED + "'",
"this may disable some features and/or impact performance negatively"));
"this may disable some features and/or impact performance negatively",
"disable the check with 'quarkus.hibernate-orm.database.version-check.enabled=false'"));

@Inject
SessionFactory sessionFactory;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">

<persistence-unit name="templatePU" transaction-type="JTA">

<description>Hibernate test case template Persistence Unit</description>

<class>io.quarkus.hibernate.orm.MyEntity</class>

<properties>
<property name="jakarta.persistence.database-product-name" value="H2"/>
<!-- This placeholder is replaced programmatically in tests -->
<property name="jakarta.persistence.database-product-version" value="${H2_VERSION}"/>

<!--
Optimistically create the tables;
will cause background errors being logged if they already exist,
but is practical to retain existing data across runs (or create as needed) -->
<property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>

<property name="jakarta.persistence.validation.mode" value="NONE"/>

<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
</properties>

</persistence-unit>
</persistence>
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String
}
RuntimeSettings runtimeSettings = buildRuntimeSettings(persistenceUnitName, recordedState, puConfig);

StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(runtimeSettings,
recordedState, persistenceUnitName);
StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(persistenceUnitName,
recordedState, puConfig, runtimeSettings);

final Object cdiBeanManager = Arc.container().beanManager();
final Object validatorFactory = Arc.container().instance("quarkus-hibernate-validator-factory").get();
Expand Down Expand Up @@ -283,10 +283,10 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde
return runtimeSettingsBuilder.build();
}

private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(RuntimeSettings runtimeSettings, RecordedState rs,
String persistenceUnitName) {
private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(String persistenceUnitName, RecordedState rs,
HibernateOrmRuntimeConfigPersistenceUnit puConfig, RuntimeSettings runtimeSettings) {
PreconfiguredServiceRegistryBuilder serviceRegistryBuilder = new PreconfiguredServiceRegistryBuilder(
persistenceUnitName, rs);
persistenceUnitName, rs, puConfig);

runtimeSettings.getSettings().forEach((key, value) -> {
serviceRegistryBuilder.applySetting(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithName;
import io.smallrye.config.WithParentName;

@ConfigGroup
Expand Down Expand Up @@ -101,6 +102,23 @@ interface HibernateOrmConfigPersistenceUnitDatabase {
@WithConverter(TrimmedStringConverter.class)
Optional<String> defaultSchema();

/**
* Whether Hibernate ORM should check on startup
* that the version of the database matches the version configured on the dialect
* (either the default version, or the one set through `quarkus.datasource.db-version`).
*
* This should be set to `false` if the database is not available on startup.
*
* @asciidoclet
*/
// TODO change the default to "always enabled" when we solve version detection problems
// See https://github.com/quarkusio/quarkus/issues/43703
// See https://github.com/quarkusio/quarkus/issues/42255
// TODO disable the check by default when offline startup is opted in
// See https://github.com/quarkusio/quarkus/issues/13522
@WithName("version-check.enabled")
@ConfigDocDefault("`true` if the dialect was set automatically by Quarkus, `false` if it was set explicitly")
Optional<Boolean> versionCheckEnabled();
}

@ConfigGroup
Expand Down
Loading

0 comments on commit 426f839

Please sign in to comment.