Skip to content

Commit

Permalink
Warn on startup when setting a database/orm compatibility version
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere committed Mar 2, 2023
1 parent d4ebbe9 commit a1051b1
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import java.util.Optional;
import java.util.TreeMap;

import io.quarkus.hibernate.orm.deployment.config.DatabaseOrmCompatibilityVersion;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
Expand Down Expand Up @@ -40,11 +40,14 @@ public class HibernateOrmConfig {
* but it may still expect a different (but runtime-compatible) schema.
* * robust test suites are still useful and recommended:
* you should still check that your application behaves as intended with your legacy schema.
* * older versions will be dropped as Hibernate ORM changes pile up
* and support for those older versions becomes too unreliable:
* you should still plan a migration of your schema to a newer version of Hibernate ORM.
* To help with migration, have a look at the source code of {@link DatabaseOrmCompatibilityVersion}:
* it lists relevant settings and includes links to the relevant sections of migration guides.
* * this feature is inherently unstable:
* some aspects of it may stop working in future versions of Quarkus,
* and older versions will be dropped as Hibernate ORM changes pile up
* and support for those older versions becomes too unreliable.
* * you should still plan a migration of your schema to a newer version of Hibernate ORM.
* For help with migration, refer to
* link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration[the Quarkus 3
* migration guide from Hibernate ORM 5 to 6].
*
* @asciidoclet
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,13 +389,13 @@ public void configurationDescriptorBuilding(
.produce(new PersistenceUnitDescriptorBuildItem(xmlDescriptor,
xmlDescriptor.getName(),
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME),
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
getMultiTenancyStrategy(Optional.ofNullable(persistenceXmlDescriptorBuildItem.getDescriptor()
.getProperties().getProperty("hibernate.multiTenancy"))), //FIXME this property is meaningless in Hibernate ORM 6
null,
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
Collections.emptyMap(),
hibernateOrmConfig.databaseOrmCompatibilityVersion
.settings(jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind).orElse(null)),
hibernateOrmConfig.databaseOrmCompatibilityVersion,
false, true));
}

Expand Down Expand Up @@ -1200,12 +1200,12 @@ private static void producePersistenceUnitDescriptorFromConfig(
persistenceUnitDescriptors.produce(
new PersistenceUnitDescriptorBuildItem(descriptor, descriptor.getName(),
jdbcDataSource.map(JdbcDataSourceBuildItem::getName),
jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind),
multiTenancyStrategy,
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
xmlMappings,
persistenceUnitConfig.unsupportedProperties,
hibernateOrmConfig.databaseOrmCompatibilityVersion
.settings(jdbcDataSource.map(JdbcDataSourceBuildItem::getDbKind).orElse(null)),
hibernateOrmConfig.databaseOrmCompatibilityVersion,
false, false));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition;
import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping;
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor;
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;

Expand All @@ -29,40 +30,43 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {
// use the name "<default>", so we need to convert between those.
private final String configurationName;
private final Optional<String> dataSource;
private final Optional<String> dbKind;
private final MultiTenancyStrategy multiTenancyStrategy;
private final String multiTenancySchemaDataSource;
private final List<RecordableXmlMapping> xmlMappings;
private final Map<String, String> quarkusConfigUnsupportedProperties;
private final Map<String, String> databaseOrmCompatibilitySettings;
private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
private final boolean isReactive;
private final boolean fromPersistenceXml;

public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
Optional<String> dbKind,
List<RecordableXmlMapping> xmlMappings,
Map<String, String> quarkusConfigUnsupportedProperties,
Map<String, String> databaseOrmCompatibilitySettings,
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
boolean isReactive, boolean fromPersistenceXml) {
this(descriptor, configurationName,
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), MultiTenancyStrategy.NONE, null,
xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilitySettings,
Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), dbKind, MultiTenancyStrategy.NONE, null,
xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion,
isReactive, fromPersistenceXml);
}

public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
Optional<String> dataSource,
Optional<String> dataSource, Optional<String> dbKind,
MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource,
List<RecordableXmlMapping> xmlMappings,
Map<String, String> quarkusConfigUnsupportedProperties,
Map<String, String> databaseOrmCompatibilitySettings,
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
boolean isReactive, boolean fromPersistenceXml) {
this.descriptor = descriptor;
this.configurationName = configurationName;
this.dataSource = dataSource;
this.dbKind = dbKind;
this.multiTenancyStrategy = multiTenancyStrategy;
this.multiTenancySchemaDataSource = multiTenancySchemaDataSource;
this.xmlMappings = xmlMappings;
this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties;
this.databaseOrmCompatibilitySettings = databaseOrmCompatibilitySettings;
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
this.isReactive = isReactive;
this.fromPersistenceXml = fromPersistenceXml;
}
Expand Down Expand Up @@ -105,9 +109,9 @@ public boolean isFromPersistenceXml() {

public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition(
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, multiTenancyStrategy,
xmlMappings,
quarkusConfigUnsupportedProperties, databaseOrmCompatibilitySettings,
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, dbKind,
multiTenancyStrategy, xmlMappings,
quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion,
isReactive, fromPersistenceXml, integrationStaticDescriptors);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.hibernate.orm.config.unsupportedproperties;
package io.quarkus.hibernate.orm.config;

import java.io.Serializable;
import java.util.ArrayList;
Expand All @@ -20,7 +20,7 @@
* Feel free to use some other solution if you find one.
*/
public class SettingsSpyingIdentifierGenerator implements IdentifierGenerator {
static final List<Map<String, Object>> collectedSettings = new ArrayList<>();
public static final List<Map<String, Object>> collectedSettings = new ArrayList<>();

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.quarkus.hibernate.orm.config.databaseormcompatibility;

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

import java.util.Map;
import java.util.logging.Formatter;
import java.util.logging.Level;

import jakarta.inject.Inject;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.cfg.AvailableSettings;
import org.jboss.logmanager.formatters.PatternFormatter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator;
import io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider;
import io.quarkus.test.QuarkusUnitTest;

public class DatabaseOrmCompatibilityVersionTest {

private static final Formatter LOG_FORMATTER = new PatternFormatter("%s");

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(SpyingIdentifierGeneratorEntity.class)
.addClass(SettingsSpyingIdentifierGenerator.class))
.withConfigurationResource("application.properties")
.overrideConfigKey("quarkus.hibernate-orm.database.orm-compatibility.version", "5.6")
// We allow overriding database/orm compatibility settings with .unsupported-properties,
// to enable step-by-step migration
.overrideConfigKey(
"quarkus.hibernate-orm.unsupported-properties.\"" + AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE + "\"",
"TIMESTAMP_UTC")
// Expect warnings on startup
.setLogRecordPredicate(record -> FastBootHibernatePersistenceProvider.class.getName().equals(record.getLoggerName())
&& record.getLevel().intValue() >= Level.WARNING.intValue())
.assertLogRecords(records -> {
var assertion = assertThat(records)
.as("Warnings on startup")
.hasSizeGreaterThanOrEqualTo(3);
assertion.element(0).satisfies(record -> assertThat(LOG_FORMATTER.formatMessage(record))
.contains("Persistence-unit [<default>] sets unsupported properties")
// We should not log property values, that could be a security breach for some properties.
.doesNotContain("some-value"));
assertion.element(1).satisfies(record -> assertThat(LOG_FORMATTER.formatMessage(record))
.contains("Persistence-unit [<default>]:"
+ " enabling best-effort backwards compatibility with 'quarkus.hibernate-orm.database.orm-compatibility.version=5.6'.",
"Quarkus will attempt to change the behavior and expected schema of Hibernate ORM"
+ " to match those of Hibernate ORM 5.6.",
"This is an inherently best-effort feature",
"may stop working in future versions of Quarkus",
"Consider migrating your application",
"https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration"));
assertion.anySatisfy(record -> assertThat(LOG_FORMATTER.formatMessage(record))
.contains(
"Persistence-unit [<default>] - 5.6 compatibility: setting 'hibernate.timezone.default_storage=NORMALIZE'.",
"affects Hibernate ORM's behavior and schema compatibility",
"may stop working in future versions of Quarkus"));
});

@Inject
EntityManagerFactory emf;

@Inject
EntityManager em;

@Test
public void testPropertiesPropagatedToStaticInit() {
assertThat(SettingsSpyingIdentifierGenerator.collectedSettings).hasSize(1);
Map<String, Object> settings = SettingsSpyingIdentifierGenerator.collectedSettings.get(0);
assertThat(settings).containsAllEntriesOf(Map.of(
AvailableSettings.TIMEZONE_DEFAULT_STORAGE, "NORMALIZE",
// We allow overriding database/orm compatibility settings with .unsupported-properties,
// to enable step-by-step migration
AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP_UTC"));
}

@Test
public void testPropertiesPropagatedToRuntimeInit() {
assertThat(emf.getProperties()).containsAllEntriesOf(Map.of(
AvailableSettings.TIMEZONE_DEFAULT_STORAGE, "NORMALIZE",
// We allow overriding database/orm compatibility settings with .unsupported-properties,
// to enable step-by-step migration
AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP_UTC"));
}

@Entity
public static class SpyingIdentifierGeneratorEntity {
@Id
@GeneratedValue(generator = "spying-generator")
@GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator")
private Long id;

public SpyingIdentifierGeneratorEntity() {
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator;
import io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.test.QuarkusUnitTest;
Expand Down Expand Up @@ -211,7 +212,7 @@ public void setParent(ParentEntity parent) {
public static class SpyingIdentifierGeneratorEntity {
@Id
@GeneratedValue(generator = "spying-generator")
@GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.orm.config.unsupportedproperties.SettingsSpyingIdentifierGenerator")
@GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.orm.config.SettingsSpyingIdentifierGenerator")
private Long id;

public SpyingIdentifierGeneratorEntity() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@

import java.util.Map;

import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;

public class BuildTimeSettings {

private Map<String, Object> quarkusConfigSettings;
private DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion;
private Map<String, String> databaseOrmCompatibilitySettings;
private Map<String, Object> allSettings;

public BuildTimeSettings(Map<String, Object> quarkusConfigSettings,
DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion,
Map<String, String> databaseOrmCompatibilitySettings,
Map<String, Object> allSettings) {
this.quarkusConfigSettings = Map.copyOf(quarkusConfigSettings);
this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion;
this.databaseOrmCompatibilitySettings = Map.copyOf(databaseOrmCompatibilitySettings);
this.allSettings = Map.copyOf(allSettings);
}
Expand All @@ -33,6 +38,10 @@ public Map<String, Object> getQuarkusConfigSettings() {
return quarkusConfigSettings;
}

public DatabaseOrmCompatibilityVersion getDatabaseOrmCompatibilityVersion() {
return databaseOrmCompatibilityVersion;
}

public Map<String, String> getDatabaseOrmCompatibilitySettings() {
return databaseOrmCompatibilitySettings;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

Expand Down Expand Up @@ -34,6 +33,7 @@
import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder;
import io.quarkus.hibernate.orm.runtime.boot.RuntimePersistenceUnitDescriptor;
import io.quarkus.hibernate.orm.runtime.boot.registry.PreconfiguredServiceRegistryBuilder;
import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeDescriptor;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata;
Expand Down Expand Up @@ -254,7 +254,35 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde
runtimeSettingsBuilder.put(entry.getKey(), entry.getValue());
}

buildTimeSettings.getDatabaseOrmCompatibilitySettings().forEach(runtimeSettingsBuilder::putIfAbsent);
var databaseOrmCompatibilityVersion = buildTimeSettings.getDatabaseOrmCompatibilityVersion();
var databaseOrmCompatibilitySettings = buildTimeSettings.getDatabaseOrmCompatibilitySettings();
if (databaseOrmCompatibilityVersion != DatabaseOrmCompatibilityVersion.LATEST) {
log.warnf("Persistence-unit [%1$s]: enabling best-effort backwards compatibility with '%2$s=%3$s'."
+ " Quarkus will attempt to change the behavior and expected schema of Hibernate ORM"
+ " to match those of Hibernate ORM %3$s."
+ " This is an inherently best-effort feature that cannot address all "
+ " backwards-incompatible changes of Hibernate ORM 6."
+ " It is also inherently unstable and may stop working in future versions of Quarkus."
+ " Consider migrating your application to native Hibernate ORM 6 behavior;"
+ " see https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration for more information.",
persistenceUnitName,
HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnitName, "database.orm-compatibility.version"),
databaseOrmCompatibilityVersion.externalRepresentation,
persistenceUnitConfig.unsupportedProperties.keySet());
}
for (Map.Entry<String, String> entry : databaseOrmCompatibilitySettings.entrySet()) {
var key = entry.getKey();
var value = entry.getValue();
if (!runtimeSettingsBuilder.isConfigured(key)) {
log.warnf("Persistence-unit [%1$s] - %2$s compatibility: setting '%3$s=%4$s'."
+ " This affects Hibernate ORM's behavior and schema compatibility"
+ " and may stop working in future versions of Quarkus.",
persistenceUnitName,
databaseOrmCompatibilityVersion.externalRepresentation,
key, value);
runtimeSettingsBuilder.put(key, value);
}
}

return runtimeSettingsBuilder.build();
}
Expand Down
Loading

0 comments on commit a1051b1

Please sign in to comment.