Skip to content

Commit

Permalink
Merge pull request #43762 from yrodiere/i43703-workaround
Browse files Browse the repository at this point in the history
Add quarkus.hibernate-orm.database.version-check.enabled
  • Loading branch information
gsmet authored Oct 8, 2024
2 parents 5e2e298 + 6daa2ea commit 04a571f
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.
// 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-enabled,`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 check 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 04a571f

Please sign in to comment.