Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add quarkus.hibernate-orm.database.version-check.enabled #43762

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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-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
Loading