diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 1727fc8145d31..4f8dd100ea901 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -170,6 +170,7 @@ ${enforcer.skip} + ${surefire.argLine.additional} 1.7.0 @@ -460,7 +461,7 @@ ${project.groupId} - -Djava.io.tmpdir="${project.build.directory}" + -Djava.io.tmpdir="${project.build.directory}" ${failsafe.argLine.additional} MAVEN_OPTS diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java index 8f750396872c5..049ce282705f8 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java @@ -5,11 +5,13 @@ import java.util.TreeMap; 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; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.annotations.ConvertWith; @ConfigRoot public class HibernateOrmConfig { @@ -26,6 +28,33 @@ public class HibernateOrmConfig { @ConfigItem(defaultValue = "true") public boolean enabled; + /** + * When set, attempts to exchange data with the database + * as the given version of Hibernate ORM would have, + * *on a best-effort basis*. + * + * Please note: + * + * * schema validation may still fail in some cases: + * this attempts to make Hibernate ORM 6+ behave correctly at runtime, + * 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. + * * 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 + */ + @ConfigItem(name = "database.orm-compatibility.version", defaultValue = "LATEST") + @ConvertWith(DatabaseOrmCompatibilityVersion.Converter.class) + public DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion; + /** * Configuration for the default persistence unit. */ diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 9fa96ab743751..e0792c9571d8e 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -382,17 +382,21 @@ public void configurationDescriptorBuilding( // First produce the PUs having a persistence.xml: these are not reactive, as we don't allow using a persistence.xml for them. for (PersistenceXmlDescriptorBuildItem persistenceXmlDescriptorBuildItem : persistenceXmlDescriptors) { ParsedPersistenceXmlDescriptor xmlDescriptor = persistenceXmlDescriptorBuildItem.getDescriptor(); + Optional jdbcDataSource = jdbcDataSources.stream() + .filter(i -> i.isDefault()) + .findFirst(); persistenceUnitDescriptors .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(), - false, - true)); + hibernateOrmConfig.databaseOrmCompatibilityVersion, + false, true)); } if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) { @@ -1196,10 +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, false, false)); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java index f9b5b0a7253cd..df35ac0ab26fc 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java @@ -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; @@ -29,35 +30,43 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem { // use the name "", so we need to convert between those. private final String configurationName; private final Optional dataSource; + private final Optional dbKind; private final MultiTenancyStrategy multiTenancyStrategy; private final String multiTenancySchemaDataSource; private final List xmlMappings; private final Map quarkusConfigUnsupportedProperties; + private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion; private final boolean isReactive; private final boolean fromPersistenceXml; public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName, + Optional dbKind, List xmlMappings, Map quarkusConfigUnsupportedProperties, + DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion, boolean isReactive, boolean fromPersistenceXml) { this(descriptor, configurationName, - Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), MultiTenancyStrategy.NONE, null, - xmlMappings, quarkusConfigUnsupportedProperties, isReactive, fromPersistenceXml); + Optional.of(DataSourceUtil.DEFAULT_DATASOURCE_NAME), dbKind, MultiTenancyStrategy.NONE, null, + xmlMappings, quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion, + isReactive, fromPersistenceXml); } public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName, - Optional dataSource, + Optional dataSource, Optional dbKind, MultiTenancyStrategy multiTenancyStrategy, String multiTenancySchemaDataSource, List xmlMappings, Map quarkusConfigUnsupportedProperties, + 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.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; } @@ -100,8 +109,9 @@ public boolean isFromPersistenceXml() { public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition( List integrationStaticDescriptors) { - return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, multiTenancyStrategy, - xmlMappings, - quarkusConfigUnsupportedProperties, isReactive, fromPersistenceXml, integrationStaticDescriptors); + return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, dataSource, dbKind, + multiTenancyStrategy, xmlMappings, + quarkusConfigUnsupportedProperties, databaseOrmCompatibilityVersion, + isReactive, fromPersistenceXml, integrationStaticDescriptors); } } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/SettingsSpyingIdentifierGenerator.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/SettingsSpyingIdentifierGenerator.java similarity index 89% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/SettingsSpyingIdentifierGenerator.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/SettingsSpyingIdentifierGenerator.java index b92afcae634ff..4fbd40b8261d1 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/SettingsSpyingIdentifierGenerator.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/SettingsSpyingIdentifierGenerator.java @@ -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; @@ -20,7 +20,7 @@ * Feel free to use some other solution if you find one. */ public class SettingsSpyingIdentifierGenerator implements IdentifierGenerator { - static final List> collectedSettings = new ArrayList<>(); + public static final List> collectedSettings = new ArrayList<>(); @Override @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/databaseormcompatibility/DatabaseOrmCompatibilityVersionTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/databaseormcompatibility/DatabaseOrmCompatibilityVersionTest.java new file mode 100644 index 0000000000000..021f341bdd22a --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/databaseormcompatibility/DatabaseOrmCompatibilityVersionTest.java @@ -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 [] 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 []:" + + " 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 [] - 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 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; + } + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/UnsupportedPropertiesTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/UnsupportedPropertiesTest.java index 6f1b725efd00d..d5539e8e9a192 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/UnsupportedPropertiesTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/unsupportedproperties/UnsupportedPropertiesTest.java @@ -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; @@ -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() { diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/BuildTimeSettings.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/BuildTimeSettings.java index 1683c59033538..6ae78cd28f235 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/BuildTimeSettings.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/BuildTimeSettings.java @@ -1,36 +1,57 @@ package io.quarkus.hibernate.orm.runtime; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; -public class BuildTimeSettings { +import io.quarkus.hibernate.orm.runtime.config.DatabaseOrmCompatibilityVersion; - private Map settings; +public class BuildTimeSettings { - public BuildTimeSettings(Map settings) { - this.settings = Collections.unmodifiableMap(new HashMap<>(settings)); + private Map quarkusConfigSettings; + private DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion; + private Map databaseOrmCompatibilitySettings; + private Map allSettings; + + public BuildTimeSettings(Map quarkusConfigSettings, + DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion, + Map databaseOrmCompatibilitySettings, + Map allSettings) { + this.quarkusConfigSettings = Map.copyOf(quarkusConfigSettings); + this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion; + this.databaseOrmCompatibilitySettings = Map.copyOf(databaseOrmCompatibilitySettings); + this.allSettings = Map.copyOf(allSettings); } public Object get(String key) { - return settings.get(key); + return allSettings.get(key); } public boolean getBoolean(String key) { - Object propertyValue = settings.get(key); + Object propertyValue = allSettings.get(key); return propertyValue != null && Boolean.parseBoolean(propertyValue.toString()); } public boolean isConfigured(String key) { - return settings.containsKey(key); + return allSettings.containsKey(key); + } + + public Map getQuarkusConfigSettings() { + return quarkusConfigSettings; + } + + public DatabaseOrmCompatibilityVersion getDatabaseOrmCompatibilityVersion() { + return databaseOrmCompatibilityVersion; + } + + public Map getDatabaseOrmCompatibilitySettings() { + return databaseOrmCompatibilitySettings; } - public Map getSettings() { - return settings; + public Map getAllSettings() { + return allSettings; } @Override public String toString() { - return this.getClass().getSimpleName() + " {" + settings.toString() + "}"; + return this.getClass().getSimpleName() + " {" + allSettings.toString() + "}"; } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index 511bb5b4086f6..b1b02d62ac8df 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -33,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; @@ -253,8 +254,37 @@ private RuntimeSettings buildRuntimeSettings(String persistenceUnitName, Recorde runtimeSettingsBuilder.put(entry.getKey(), entry.getValue()); } - RuntimeSettings runtimeSettings = runtimeSettingsBuilder.build(); - return runtimeSettings; + 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 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(); } private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(RuntimeSettings runtimeSettings, RecordedState rs, diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java index 34937dfaad008..28cbdb267a353 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java @@ -39,7 +39,7 @@ public static class Builder { private final Map settings; public Builder(BuildTimeSettings buildTimeSettings, IntegrationSettings integrationSettings) { - this.settings = new HashMap<>(buildTimeSettings.getSettings()); + this.settings = new HashMap<>(buildTimeSettings.getQuarkusConfigSettings()); this.settings.putAll(integrationSettings.getSettings()); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index c45338a255bcf..9a88c0020f5ac 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -19,6 +19,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -137,24 +138,11 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti final RecordableBootstrap ssrBuilder = RecordableBootstrapFactory.createRecordableBootstrapBuilder(puDefinition); final MergedSettings mergedSettings = mergeSettings(puDefinition); - this.buildTimeSettings = new BuildTimeSettings(mergedSettings.getConfigurationValues()); + this.buildTimeSettings = createBuildTimeSettings(puDefinition, mergedSettings.getConfigurationValues()); // Build the "standard" service registry - ssrBuilder.applySettings(buildTimeSettings.getSettings()); - // We don't add unsupported properties to mergedSettings/buildTimeSettings, - // so that we can more easily differentiate between - // properties coming from Quarkus and "unsupported" properties - // on startup (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings) - for (Map.Entry entry : puDefinition.getQuarkusConfigUnsupportedProperties().entrySet()) { - var key = entry.getKey(); - if (buildTimeSettings.get(key) != null) { - // Ignore properties that were already set by Quarkus; - // we'll log a warning about those on startup. - // (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings) - continue; - } - ssrBuilder.applySetting(key, entry.getValue()); - } + ssrBuilder.applySettings(buildTimeSettings.getAllSettings()); + // We need to initialize the multi tenancy strategy before building the service registry as it is used to // create metadata builder. Adding services afterwards would lead to unpredicted behavior. final MultiTenancyStrategy multiTenancyStrategy = puDefinition.getMultitenancyStrategy(); @@ -220,6 +208,34 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti metamodelBuilder.applyTempClassLoader(null); } + private BuildTimeSettings createBuildTimeSettings(QuarkusPersistenceUnitDefinition puDefinition, + Map quarkusConfigSettings) { + Map quarkusConfigUnsupportedProperties = puDefinition.getQuarkusConfigUnsupportedProperties(); + Map allSettings = new HashMap<>(quarkusConfigSettings); + + // Ignore properties that were already set by Quarkus; + // we'll log a warning about those on startup. + // (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings) + quarkusConfigUnsupportedProperties.forEach(allSettings::putIfAbsent); + + var databaseOrmCompatibilityVersion = puDefinition.getDatabaseOrmCompatibilityVersion(); + Map appliedDatabaseOrmCompatibilitySettings = new HashMap<>(); + for (Map.Entry entry : databaseOrmCompatibilityVersion.settings(puDefinition.getDbKind()).entrySet()) { + // Not using putIfAbsent() because that would be ambiguous in case of null values + if (!allSettings.containsKey(entry.getKey())) { + appliedDatabaseOrmCompatibilitySettings.put(entry.getKey(), entry.getValue()); + } + } + allSettings.putAll(appliedDatabaseOrmCompatibilitySettings); + + // We keep a separate copy of settings coming from Quarkus config, + // so that we can more easily differentiate between + // properties coming from Quarkus and "unsupported" properties + // on startup (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings) + return new BuildTimeSettings(quarkusConfigSettings, databaseOrmCompatibilityVersion, + appliedDatabaseOrmCompatibilitySettings, allSettings); + } + /** * Simplified copy of * org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl#mergeSettings(org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor, diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java index 2b6b1c4901216..48ecf9765d51b 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/QuarkusPersistenceUnitDefinition.java @@ -8,6 +8,7 @@ import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; 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; import io.quarkus.runtime.annotations.RecordableConstructor; @@ -20,27 +21,34 @@ public final class QuarkusPersistenceUnitDefinition { private final RuntimePersistenceUnitDescriptor actualHibernateDescriptor; private final Optional dataSource; + private final Optional dbKind; private final MultiTenancyStrategy multitenancyStrategy; private final List xmlMappings; private final boolean isReactive; private final boolean fromPersistenceXml; private final List integrationStaticDescriptors; private final Map quarkusConfigUnsupportedProperties; + private final DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion; public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUnitDescriptor, - String configurationName, Optional dataSource, + String configurationName, Optional dataSource, Optional dbKind, MultiTenancyStrategy multitenancyStrategy, List xmlMappings, Map quarkusConfigUnsupportedProperties, + DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion, boolean isReactive, boolean fromPersistenceXml, List integrationStaticDescriptors) { Objects.requireNonNull(persistenceUnitDescriptor); + Objects.requireNonNull(dataSource); + Objects.requireNonNull(dbKind); Objects.requireNonNull(multitenancyStrategy); this.actualHibernateDescriptor = RuntimePersistenceUnitDescriptor.validateAndReadFrom(persistenceUnitDescriptor, configurationName); this.dataSource = dataSource; + this.dbKind = dbKind; this.multitenancyStrategy = multitenancyStrategy; this.xmlMappings = xmlMappings; this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties; + this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; this.integrationStaticDescriptors = integrationStaticDescriptors; @@ -48,21 +56,25 @@ public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUni @RecordableConstructor public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualHibernateDescriptor, - Optional dataSource, + Optional dataSource, Optional dbKind, MultiTenancyStrategy multitenancyStrategy, List xmlMappings, Map quarkusConfigUnsupportedProperties, + DatabaseOrmCompatibilityVersion databaseOrmCompatibilityVersion, boolean reactive, boolean fromPersistenceXml, List integrationStaticDescriptors) { Objects.requireNonNull(actualHibernateDescriptor); Objects.requireNonNull(dataSource); + Objects.requireNonNull(dbKind); Objects.requireNonNull(multitenancyStrategy); this.actualHibernateDescriptor = actualHibernateDescriptor; this.dataSource = dataSource; + this.dbKind = dbKind; this.multitenancyStrategy = multitenancyStrategy; this.xmlMappings = xmlMappings; this.quarkusConfigUnsupportedProperties = quarkusConfigUnsupportedProperties; + this.databaseOrmCompatibilityVersion = databaseOrmCompatibilityVersion; this.isReactive = reactive; this.fromPersistenceXml = fromPersistenceXml; this.integrationStaticDescriptors = integrationStaticDescriptors; @@ -80,6 +92,10 @@ public Optional getDataSource() { return dataSource; } + public Optional getDbKind() { + return dbKind; + } + public MultiTenancyStrategy getMultitenancyStrategy() { return multitenancyStrategy; } @@ -105,4 +121,7 @@ public Map getQuarkusConfigUnsupportedProperties() { return quarkusConfigUnsupportedProperties; } + public DatabaseOrmCompatibilityVersion getDatabaseOrmCompatibilityVersion() { + return databaseOrmCompatibilityVersion; + } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DatabaseOrmCompatibilityVersion.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DatabaseOrmCompatibilityVersion.java new file mode 100644 index 0000000000000..e1fed5059b927 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DatabaseOrmCompatibilityVersion.java @@ -0,0 +1,85 @@ +package io.quarkus.hibernate.orm.runtime.config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.hibernate.cfg.AvailableSettings; + +import io.quarkus.datasource.common.runtime.DatabaseKind; + +public enum DatabaseOrmCompatibilityVersion { + V5_6("5.6") { + @Override + public Map settings(Optional dbKind) { + Map result = new HashMap<>(Map.of( + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#implicit-identifier-sequence-and-table-name + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#id-sequence-defaults + AvailableSettings.ID_DB_STRUCTURE_NAMING_STRATEGY, "legacy", + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#duration-mapping-changes + AvailableSettings.PREFERRED_DURATION_JDBC_TYPE, "BIGINT", + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#instant-mapping-changes + AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE, "TIMESTAMP", + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // Not changing this for now as there's no setting and affected users should be rare, and they can fix their code rather easily. + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#enum-mapping-changes + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#datatype-for-enums + // Not changing this because we cannot: + // there is no setting for this, so the schema will be incompatible. + // Runtime (queries, persisting) should continue to work, though. + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#timezone-and-offset-storage + AvailableSettings.TIMEZONE_DEFAULT_STORAGE, "NORMALIZE")); + + if (dbKind.isPresent() && !usedToSupportUuid(dbKind.get())) { + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#uuid-mapping-changes + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-mariadb + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-sql-server + result.put(AvailableSettings.PREFERRED_UUID_JDBC_TYPE, "BINARY"); + } + + return result; + } + }, + LATEST("latest") { + @Override + public Map settings(Optional dbKind) { + // Nothing to add + return Map.of(); + } + }; + + private static boolean usedToSupportUuid(String dbKind) { + // As far as I can tell, only the PostgreSQL dialect used to support a native UUID type in ORM 5.x. + return DatabaseKind.isPostgreSQL(dbKind); + } + + public final String externalRepresentation; + + DatabaseOrmCompatibilityVersion(String externalRepresentation) { + this.externalRepresentation = externalRepresentation; + } + + public abstract Map settings(Optional dbKind); + + public static class Converter + implements org.eclipse.microprofile.config.spi.Converter { + @Override + public DatabaseOrmCompatibilityVersion convert(String value) { + final String normalizedValue = value.trim().toLowerCase(Locale.ROOT); + for (DatabaseOrmCompatibilityVersion candidate : values()) { + if (candidate.externalRepresentation.equals(normalizedValue)) { + return candidate; + } + } + throw new IllegalArgumentException(String.format(Locale.ROOT, + "Invalid ORM compatibility version: %1$s. Valid versions are: %2$s.", + value, + Arrays.stream(values()) + .map(v -> v.externalRepresentation) + .collect(Collectors.toList()))); + } + } +} diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index e3e2effa6887f..aabf0d4426ce0 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -163,9 +163,10 @@ public void buildReactivePersistenceUnit( // - we don't support starting Hibernate Reactive from a persistence.xml // - we don't support Hibernate Envers with Hibernate Reactive persistenceUnitDescriptors.produce(new PersistenceUnitDescriptorBuildItem(reactivePU, - PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, + PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, dbKindOptional, jpaModel.getXmlMappings(reactivePU.getName()), persistenceUnitConfig.unsupportedProperties, + hibernateOrmConfig.databaseOrmCompatibilityVersion, true, false)); } diff --git a/integration-tests/hibernate-orm-compatibility-5.6/README.md b/integration-tests/hibernate-orm-compatibility-5.6/README.md new file mode 100644 index 0000000000000..dee5471439527 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/README.md @@ -0,0 +1,34 @@ +# Hibernate ORM 5.6 database compatibility tests + +## What is this? + +These module test that Quarkus can indeed work with a database created by Hibernate ORM 5.6 +when the following property is set: + +```properties +quarkus.hibernate-orm.database.orm-compatibility.version = 5.6 +``` + +## How does it work? + +The tests need to execute on a database whose schema and data +was initialized with Hibernate ORM 5.6. + +Everything is already set up to restore a dump on startup. + +## How to update the tests? + +If you add new tests and those changes require new entity mappings and/or data, +make sure to update the project `database-generator` accordingly +(same entity mapping as in your tests, in particular). +This project depends on Quarkus 2 and is used to generate a database. + +Then, to update the dump, run `./update-dump.sh` from each DB directory (`mariadb`, `postgresql`, ...). +This will start a container, generate the database, and update the dump in `src/test/resources`. + +## Why is `database-generator` not part of the build? + +Because: + +1. It doesn't need to. This project is only meant to be used to update dumps. +2. It depends on Quarkus 2, so adding it to the build would pollute the local Maven repository unnecessarily. diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/pom.xml b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/pom.xml new file mode 100644 index 0000000000000..d6206acb24038 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + io.quarkus + quarkus-integration-test-hibernate-orm-compatibility-5.6-database-generator + 1.0.0-SNAPSHOT + + 3.10.1 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 2.16.3.Final + true + 3.0.0-M7 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-mariadb + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-arc + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + native + + + native + + + + false + native + + + + diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/Main.java b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/Main.java new file mode 100644 index 0000000000000..417babc8a9d43 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/Main.java @@ -0,0 +1,96 @@ +package io.quarkus.it.hibernate.compatibility; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import javax.inject.Inject; +import javax.persistence.EntityManager; + +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.annotations.QuarkusMain; + +@QuarkusMain +public class Main { + public static void main(String... args) { + System.out.println("Initializing schema..."); + Quarkus.run(QuarkusMain.class, args); + } + + static class QuarkusMain implements QuarkusApplication { + @Inject + EntityManager em; + + @Override + public int run(String... args) { + System.out.println("Initializing data..."); + MyEntity createdEntity = QuarkusTransaction.requiringNew().call(() -> { + var entity = new MyEntity(); + entity.duration = Duration.of(59, ChronoUnit.SECONDS); + entity.uuid = UUID.fromString("f49c6ba8-8d7f-417a-a255-d594dddf729f"); + entity.instant = Instant.parse("2018-01-01T10:58:30.00Z"); + entity.offsetDateTime = LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atOffset(ZoneOffset.ofHours(2)); + entity.zonedDateTime = LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atZone(ZoneId.of("Africa/Cairo" /* UTC+2 */)); + entity.intArray = new int[] { 0, 1, 42 }; + entity.stringList = new ArrayList<>(List.of("one", "two")); + entity.myEnum = MyEnum.VALUE2; + em.persist(entity); + + // Create more than one entity of each type, + // so that we avoid the (uninteresting) edge case in sequence optimizers + // where only 1 entity was created and the optimizer is just about to start another pool. + em.persist(new MyEntity()); + em.persist(new MyEntityWithGenericGeneratorAndDefaultAllocationSize()); + em.persist(new MyEntityWithGenericGeneratorAndDefaultAllocationSize()); + em.persist(new MyEntityWithSequenceGeneratorAndDefaultAllocationSize()); + em.persist(new MyEntityWithSequenceGeneratorAndDefaultAllocationSize()); + + return entity; + }); + + System.out.println("Checking data..."); + // Check that Hibernate ORM 5 used to load the values we're going to expect in compatibility tests + QuarkusTransaction.requiringNew().run(() -> { + checkEqual(1L, createdEntity.id); + var loadedEntity = em.find(MyEntity.class, createdEntity.id); + checkEqual(createdEntity.duration, loadedEntity.duration); + checkEqual(createdEntity.uuid, loadedEntity.uuid); + checkEqual(createdEntity.instant, loadedEntity.instant); + checkEqual(createdEntity.offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()).toOffsetDateTime(), + loadedEntity.offsetDateTime); + checkEqual(createdEntity.zonedDateTime.withZoneSameInstant(ZoneId.systemDefault()), loadedEntity.zonedDateTime); + checkEqual(createdEntity.intArray, loadedEntity.intArray); + checkEqual(createdEntity.stringList, loadedEntity.stringList); + checkEqual(createdEntity.myEnum, loadedEntity.myEnum); + }); + + System.out.println("Done."); + return 0; + } + + private void checkEqual(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Not equal; expected: " + expected + ", actual: " + actual); + } + } + + private void checkEqual(int[] expected, int[] actual) { + if (!Arrays.equals(expected, actual)) { + throw new AssertionError("Not equal; expected: " + Arrays.toString(expected) + + ", actual: " + Arrays.toString(actual)); + } + } + } +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java new file mode 100644 index 0000000000000..b4f459421bc1a --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java @@ -0,0 +1,39 @@ +package io.quarkus.it.hibernate.compatibility; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +public class MyEntity { + @Id + @GeneratedValue + public Long id; + + public Duration duration; + + public UUID uuid; + + public Instant instant; + + public OffsetDateTime offsetDateTime; + + public ZonedDateTime zonedDateTime; + + public int[] intArray; + + public ArrayList stringList; + + @Enumerated(EnumType.ORDINAL) + public MyEnum myEnum; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java new file mode 100644 index 0000000000000..80d275fc7d90c --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java @@ -0,0 +1,17 @@ +package io.quarkus.it.hibernate.compatibility; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity(name = "myentity_gengendefallocsize") +public class MyEntityWithGenericGeneratorAndDefaultAllocationSize { + @Id + @GeneratedValue(generator = "gengendefallocsize") + @GenericGenerator(name = "gengendefallocsize", strategy = "sequence") + public Long id; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java new file mode 100644 index 0000000000000..82b47e2ef4675 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java @@ -0,0 +1,15 @@ +package io.quarkus.it.hibernate.compatibility; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.SequenceGenerator; + +@Entity(name = "myentity_seqgendefallocsize") +public class MyEntityWithSequenceGeneratorAndDefaultAllocationSize { + @Id + @GeneratedValue(generator = "seqgendefallocsize") + @SequenceGenerator(name = "seqgendefallocsize") + public Long id; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java new file mode 100644 index 0000000000000..47d99905d0acf --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java @@ -0,0 +1,7 @@ +package io.quarkus.it.hibernate.compatibility; + +public enum MyEnum { + VALUE1, + VALUE2, + VALUE3 +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/resources/application.properties b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/resources/application.properties new file mode 100644 index 0000000000000..e783aed6cdb69 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/database-generator/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# This is set from the commandline when building +quarkus.datasource.db-kind=${db-kind} +quarkus.hibernate-orm.database.generation=create diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/pom.xml b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/pom.xml new file mode 100644 index 0000000000000..a4d95207936f8 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/pom.xml @@ -0,0 +1,297 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../.. + + 4.0.0 + + quarkus-integration-test-hibernate-orm-compatibility-5.6-mariadb + Quarkus - Integration Tests - Hibernate ORM - Compatibility with databases meant for ORM 5.6 - MariaDB + + + jdbc:mariadb://localhost:3306/hibernate_orm_test + + -Duser.timezone=Europe/Paris + + + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-mariadb + + + io.quarkus + quarkus-flyway + + + org.flywaydb + flyway-mysql + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-flyway-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-mariadb-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-mariadb + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + + docker-mariadb + + + start-containers + + + + jdbc:mariadb://localhost:3308/hibernate_orm_test + + + + + io.fabric8 + docker-maven-plugin + + + + ${mariadb.image} + quarkus-test-mariadb + + ${mariadb.image} + + + 5s + 3s + 5s + 5 + + + mysqladmin ping -h localhost -u root -psecret|| exit 1 + + + + + + 3308:3306 + + + hibernate_orm_test + hibernate_orm_test + hibernate_orm_test + true + + + MariaDB: + default + cyan + + + /var/lib/mysql + + + true + + + + + + true + + + + docker-start + compile + + stop + build + start + + + + docker-stop + post-integration-test + + stop + + + + + + org.codehaus.mojo + exec-maven-plugin + + + docker-prune + generate-resources + + exec + + + ${docker-prune.location} + + + + + + + + + + + diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestResource.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestResource.java new file mode 100644 index 0000000000000..6daf149d2c933 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestResource.java @@ -0,0 +1,51 @@ +package io.quarkus.it.hibernate.compatibility; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import org.jboss.resteasy.reactive.RestPath; + +@ApplicationScoped +@Produces("application/json") +@Consumes("application/json") +@Path("/compatibility") +@Transactional +public class CompatibilityTestResource { + @Inject + EntityManager em; + + @GET + @Path("/{id}") + public MyEntity find(@RestPath Long id) { + return em.find(MyEntity.class, id); + } + + @POST + public MyEntity create(MyEntity entity) { + em.persist(entity); + return entity; + } + + @POST + @Path("/genericgenerator") + public MyEntityWithGenericGeneratorAndDefaultAllocationSize create( + MyEntityWithGenericGeneratorAndDefaultAllocationSize entity) { + em.persist(entity); + return entity; + } + + @POST + @Path("/sequencegenerator") + public MyEntityWithSequenceGeneratorAndDefaultAllocationSize create( + MyEntityWithSequenceGeneratorAndDefaultAllocationSize entity) { + em.persist(entity); + return entity; + } +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java new file mode 100644 index 0000000000000..8d1ca185bba5e --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java @@ -0,0 +1,48 @@ +package io.quarkus.it.hibernate.compatibility; + +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.UUID; + +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +public class MyEntity { + @Id + @GeneratedValue + public Long id; + + public Duration duration; + + public UUID uuid; + + public Instant instant; + + public OffsetDateTime offsetDateTime; + + public ZonedDateTime zonedDateTime; + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // This mapping change is required because Quarkus cannot fix this through settings. + @JdbcTypeCode(SqlTypes.VARBINARY) + public int[] intArray; + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // This mapping change is required because Quarkus cannot fix this through settings. + @JdbcTypeCode(SqlTypes.VARBINARY) + public ArrayList stringList; + + @Enumerated(EnumType.ORDINAL) + public MyEnum myEnum; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java new file mode 100644 index 0000000000000..64caf2a628045 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java @@ -0,0 +1,16 @@ +package io.quarkus.it.hibernate.compatibility; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; + +@Entity(name = "myentity_gengendefallocsize") +public class MyEntityWithGenericGeneratorAndDefaultAllocationSize { + @Id + @GeneratedValue(generator = "gengendefallocsize") + @GenericGenerator(name = "gengendefallocsize", strategy = "sequence") + public Long id; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java new file mode 100644 index 0000000000000..e8bf345e8e6f7 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java @@ -0,0 +1,15 @@ +package io.quarkus.it.hibernate.compatibility; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; + +@Entity(name = "myentity_seqgendefallocsize") +public class MyEntityWithSequenceGeneratorAndDefaultAllocationSize { + @Id + @GeneratedValue(generator = "seqgendefallocsize") + @SequenceGenerator(name = "seqgendefallocsize") + public Long id; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java new file mode 100644 index 0000000000000..47d99905d0acf --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java @@ -0,0 +1,7 @@ +package io.quarkus.it.hibernate.compatibility; + +public enum MyEnum { + VALUE1, + VALUE2, + VALUE3 +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/resources/application.properties b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/resources/application.properties new file mode 100644 index 0000000000000..a9a64cd31cebc --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/main/resources/application.properties @@ -0,0 +1,16 @@ +quarkus.datasource.username=hibernate_orm_test +quarkus.datasource.password=hibernate_orm_test +quarkus.datasource.jdbc.url=${mariadb.url} +quarkus.datasource.jdbc.max-size=8 + +# On startup, restore the dump of a database meant for ORM 5.6 +quarkus.flyway.migrate-at-start=true +# https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#enum-mapping-changes +# https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#datatype-for-enums +# The schema changed for enums and there is nothing we can do about it, +# so we're cannot pass schema validation. +quarkus.hibernate-orm.database.generation=none + +# Configure Hibernate ORM for compatibility with ORM 5.6 +quarkus.hibernate-orm.database.orm-compatibility.version=5.6 + diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTest.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTest.java new file mode 100644 index 0000000000000..6bf66a479147b --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTest.java @@ -0,0 +1,163 @@ +package io.quarkus.it.hibernate.compatibility; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import jakarta.ws.rs.core.Response.Status; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CompatibilityTest { + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#implicit-identifier-sequence-and-table-name + @Test + @Order(1) + public void sequence_defaultGenerator() { + var entity = new MyEntity(); + MyEntity createdEntity = given() + .body(entity).contentType("application/json") + .when().post("/compatibility/").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntity.class); + assertThat(createdEntity.id).isEqualTo(3L); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#id-sequence-defaults + @Test + @Order(2) + public void sequence_genericGenerator_defaultAllocation() { + var entity = new MyEntityWithGenericGeneratorAndDefaultAllocationSize(); + MyEntityWithGenericGeneratorAndDefaultAllocationSize createdEntity = given() + .body(entity).contentType("application/json") + .when().post("/compatibility/genericgenerator").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntityWithGenericGeneratorAndDefaultAllocationSize.class); + assertThat(createdEntity.id).isEqualTo(3L); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#id-sequence-defaults + @Test + @Order(2) + public void sequence_sequenceGenerator_defaultAllocation() { + var entity = new MyEntityWithSequenceGeneratorAndDefaultAllocationSize(); + MyEntityWithSequenceGeneratorAndDefaultAllocationSize createdEntity = given() + .body(entity).contentType("application/json") + .when().post("/compatibility/sequencegenerator").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntityWithSequenceGeneratorAndDefaultAllocationSize.class); + // Sequence generators defined through @SequenceGenerator have always defaulted to an allocation size of 50. + // Since we've created 2 entities in Hibernate 5, we should be starting the second pool here, starting at 52. + assertThat(createdEntity.id).isEqualTo(52L); + } + + // Just check that persisting with the old schema and new application does not throw any exception + @Test + @Order(4) // So that we can assert the generated ID in sequence*() + public void persistUsingOldSchema() { + var entity = new MyEntity(); + entity.duration = Duration.of(59, ChronoUnit.SECONDS); + entity.uuid = UUID.fromString("f49c6ba8-8d7f-417a-a255-d594dddf729f"); + entity.instant = Instant.parse("2018-01-01T10:58:30.00Z"); + entity.intArray = new int[] { 0, 1, 42 }; + entity.offsetDateTime = LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atOffset(ZoneOffset.ofHours(2)); + entity.zonedDateTime = LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atZone(ZoneId.of("Africa/Cairo" /* UTC+2 */)); + entity.stringList = new ArrayList<>(List.of("one", "two")); + entity.myEnum = MyEnum.VALUE2; + given() + .body(entity).contentType("application/json") + .when().post("/compatibility/").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#duration-mapping-changes + @Test + public void duration() { + assertThat(findOld().duration).isEqualTo(Duration.of(59, ChronoUnit.SECONDS)); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#uuid-mapping-changes + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-mariadb + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-sql-server + @Test + public void uuid() { + assertThat(findOld().uuid).isEqualTo(UUID.fromString("f49c6ba8-8d7f-417a-a255-d594dddf729f")); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#instant-mapping-changes + @Test + public void instant() { + assertThat(findOld().instant).isEqualTo(Instant.parse("2018-01-01T10:58:30.00Z")); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#timezone-and-offset-storage + @Test + public void offsetDateTime() { + assertThat(findOld().offsetDateTime) + .isEqualTo(LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atOffset(ZoneOffset.ofHours(2)) + // Hibernate ORM 5 used to normalize these values to the JVM TZ + .atZoneSameInstant(ZoneId.systemDefault()).toOffsetDateTime()); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#timezone-and-offset-storage + @Test + public void zonedDateTime() { + assertThat(findOld().zonedDateTime) + .isEqualTo(LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atZone(ZoneId.of("Africa/Cairo" /* UTC+2 */)) + // Hibernate ORM 5 used to normalize these values to the JVM TZ + .withZoneSameInstant(ZoneId.systemDefault())); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // Note this is not fixed automatically by Quarkus and requires new annotations on the entity (see entity class). + @Test + public void array() { + assertThat(findOld().intArray).isEqualTo(new int[] { 0, 1, 42 }); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // Note this is not fixed automatically by Quarkus and requires new annotations on the entity (see entity class). + @Test + public void list() { + assertThat(findOld().stringList).isEqualTo(new ArrayList<>(List.of("one", "two"))); + } + + @Test + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#enum-mapping-changes + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#datatype-for-enums + public void enum_() { + assertThat(findOld().myEnum).isEqualTo(MyEnum.VALUE2); + } + + private static MyEntity findOld() { + return find(1L); + } + + private static MyEntity find(long id) { + return given().when().get("/compatibility/{id}", id).then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntity.class); + } + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestInGraalITCase.java b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestInGraalITCase.java new file mode 100644 index 0000000000000..4564e10519e9a --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestInGraalITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.hibernate.compatibility; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class CompatibilityTestInGraalITCase extends CompatibilityTest { + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/resources/db/migration/V1.0.0__orm5-6.sql b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/resources/db/migration/V1.0.0__orm5-6.sql new file mode 100644 index 0000000000000..af0edfd88353f --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/resources/db/migration/V1.0.0__orm5-6.sql @@ -0,0 +1,38 @@ +CREATE SEQUENCE `gengendefallocsize` start with 1 minvalue 1 maxvalue 9223372036854775806 increment by 1 cache 1000 nocycle ENGINE=InnoDB; +SELECT SETVAL(`gengendefallocsize`, 1001, 0); +CREATE SEQUENCE `hibernate_sequence` start with 1 minvalue 1 maxvalue 9223372036854775806 increment by 1 cache 1000 nocycle ENGINE=InnoDB; +SELECT SETVAL(`hibernate_sequence`, 1001, 0); +CREATE SEQUENCE `seqgendefallocsize` start with 1 minvalue 1 maxvalue 9223372036854775806 increment by 50 cache 1000 nocycle ENGINE=InnoDB; +SELECT SETVAL(`seqgendefallocsize`, 50001, 0); +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `MyEntity` ( + `id` bigint(20) NOT NULL, + `duration` bigint(20) DEFAULT NULL, + `instant` datetime(6) DEFAULT NULL, + `intArray` tinyblob DEFAULT NULL, + `myEnum` int(11) DEFAULT NULL, + `offsetDateTime` datetime(6) DEFAULT NULL, + `stringList` tinyblob DEFAULT NULL, + `uuid` binary(255) DEFAULT NULL, + `zonedDateTime` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `MyEntity` VALUES (1,59000000000,'2018-01-01 11:58:30.000000',0xACED0005757200025B494DBA602676EAB2A502000078700000000300000000000000010000002A,1,'2018-01-01 11:58:30.000000',0xACED0005737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000027704000000027400036F6E6574000374776F78,0xF49C6BA88D7F417AA255D594DDDF729F0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,'2018-01-01 11:58:30.000000'),(2,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `myentity_gengendefallocsize` ( + `id` bigint(20) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `myentity_gengendefallocsize` VALUES (1),(2); +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `myentity_seqgendefallocsize` ( + `id` bigint(20) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +INSERT INTO `myentity_seqgendefallocsize` VALUES (1),(2); diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/resources/db/migration/V1.0.1__reset-sequences.sql b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/resources/db/migration/V1.0.1__reset-sequences.sql new file mode 100644 index 0000000000000..0c80a34f37cad --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/src/test/resources/db/migration/V1.0.1__reset-sequences.sql @@ -0,0 +1,18 @@ +-- MariaDB dumps lead to weird values in sequences because of caching mechanisms. +-- => Reset sequences to the value they would have had, had we not dumped and restored the database. + +ALTER SEQUENCE `hibernate_sequence` restart; +-- We created exactly two entities +SELECT nextval(`hibernate_sequence`); +SELECT nextval(`hibernate_sequence`); + +ALTER SEQUENCE `gengendefallocsize` restart; +-- We created exactly two entities +SELECT nextval(`gengendefallocsize`); +SELECT nextval(`gengendefallocsize`); + +ALTER SEQUENCE `seqgendefallocsize` restart; +-- We created exactly two entities +-- This sequence uses a pooled optimizer, but the first two entities still require two calls to nextval() +SELECT nextval(`seqgendefallocsize`); +SELECT nextval(`seqgendefallocsize`); diff --git a/integration-tests/hibernate-orm-compatibility-5.6/mariadb/update-dump.sh b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/update-dump.sh new file mode 100755 index 0000000000000..442c72059b0b6 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/mariadb/update-dump.sh @@ -0,0 +1,30 @@ +#!/bin/bash -e + +MVN=../../../mvnw +DB_CONTAINER_NAME=mariadb-database-gen +# These should be the same as the username/password used by docker-maven-plugin in ./pom.xml +DB_NAME=hibernate_orm_test +DB_PORT=3308 +DB_USER=hibernate_orm_test +DB_PASSWORD=hibernate_orm_test +DUMP_LOCATION=src/test/resources/db/migration/V1.0.0__orm5-6.sql + +# Start container +$MVN docker:stop docker:build docker:start -Dstart-containers -Ddocker.containerNamePattern=$DB_CONTAINER_NAME +trap '$MVN docker:stop -Dstart-containers -Ddocker.containerNamePattern=$DB_CONTAINER_NAME' EXIT + +# Generate database +$MVN -f ../database-generator clean install -Ddb-kind=mariadb +# We use features in Hibernate ORM that are timezone-sensitive, e.g. ZoneOffsetDateTime normalization +TZ=Europe/Paris +QUARKUS_DATASOURCE_JDBC_URL="jdbc:mariadb://localhost:$DB_PORT/$DB_NAME" \ +QUARKUS_DATASOURCE_USERNAME="$DB_USER" \ +QUARKUS_DATASOURCE_PASSWORD="$DB_PASSWORD" \ + java -jar ../database-generator/target/quarkus-app/quarkus-run.jar + +# Update the dump +# https://stackoverflow.com/a/32611542/6692043 +echo "Updating dump at '$DUMP_LOCATION'" +docker exec "$DB_CONTAINER_NAME" \ + sh -c 'exec mysqldump -hlocalhost -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" --compact --hex-blob' \ + > "$DUMP_LOCATION" \ No newline at end of file diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/pom.xml b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/pom.xml new file mode 100644 index 0000000000000..8d53080e47976 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/pom.xml @@ -0,0 +1,277 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../.. + + 4.0.0 + + quarkus-integration-test-hibernate-orm-compatibility-5.6-postgresql + Quarkus - Integration Tests - Hibernate ORM - Compatibility with databases meant for ORM 5.6 - PostgreSQL + + + + -Duser.timezone=Europe/Paris + + + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-flyway + + + org.flywaydb + flyway-mysql + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-flyway-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-postgresql-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-postgresql + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + + docker-postgresql + + + start-containers + + + + jdbc:postgresql://localhost:5431/hibernate_orm_test + + + + + io.fabric8 + docker-maven-plugin + + + + ${postgres.image} + postgresql + + + hibernate_orm_test + hibernate_orm_test + hibernate_orm_test + + + 5431:5432 + + + + mapped + + 5432 + + + + + + + + + true + + + + docker-start + compile + + stop + start + + + + docker-stop + post-integration-test + + stop + + + + + + org.codehaus.mojo + exec-maven-plugin + + + docker-prune + generate-resources + + exec + + + ${docker-prune.location} + + + + + + + + + + + diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestResource.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestResource.java new file mode 100644 index 0000000000000..6daf149d2c933 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestResource.java @@ -0,0 +1,51 @@ +package io.quarkus.it.hibernate.compatibility; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import org.jboss.resteasy.reactive.RestPath; + +@ApplicationScoped +@Produces("application/json") +@Consumes("application/json") +@Path("/compatibility") +@Transactional +public class CompatibilityTestResource { + @Inject + EntityManager em; + + @GET + @Path("/{id}") + public MyEntity find(@RestPath Long id) { + return em.find(MyEntity.class, id); + } + + @POST + public MyEntity create(MyEntity entity) { + em.persist(entity); + return entity; + } + + @POST + @Path("/genericgenerator") + public MyEntityWithGenericGeneratorAndDefaultAllocationSize create( + MyEntityWithGenericGeneratorAndDefaultAllocationSize entity) { + em.persist(entity); + return entity; + } + + @POST + @Path("/sequencegenerator") + public MyEntityWithSequenceGeneratorAndDefaultAllocationSize create( + MyEntityWithSequenceGeneratorAndDefaultAllocationSize entity) { + em.persist(entity); + return entity; + } +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java new file mode 100644 index 0000000000000..8d1ca185bba5e --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntity.java @@ -0,0 +1,48 @@ +package io.quarkus.it.hibernate.compatibility; + +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.UUID; + +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +public class MyEntity { + @Id + @GeneratedValue + public Long id; + + public Duration duration; + + public UUID uuid; + + public Instant instant; + + public OffsetDateTime offsetDateTime; + + public ZonedDateTime zonedDateTime; + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // This mapping change is required because Quarkus cannot fix this through settings. + @JdbcTypeCode(SqlTypes.VARBINARY) + public int[] intArray; + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // This mapping change is required because Quarkus cannot fix this through settings. + @JdbcTypeCode(SqlTypes.VARBINARY) + public ArrayList stringList; + + @Enumerated(EnumType.ORDINAL) + public MyEnum myEnum; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java new file mode 100644 index 0000000000000..64caf2a628045 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithGenericGeneratorAndDefaultAllocationSize.java @@ -0,0 +1,16 @@ +package io.quarkus.it.hibernate.compatibility; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; + +@Entity(name = "myentity_gengendefallocsize") +public class MyEntityWithGenericGeneratorAndDefaultAllocationSize { + @Id + @GeneratedValue(generator = "gengendefallocsize") + @GenericGenerator(name = "gengendefallocsize", strategy = "sequence") + public Long id; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java new file mode 100644 index 0000000000000..e8bf345e8e6f7 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEntityWithSequenceGeneratorAndDefaultAllocationSize.java @@ -0,0 +1,15 @@ +package io.quarkus.it.hibernate.compatibility; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; + +@Entity(name = "myentity_seqgendefallocsize") +public class MyEntityWithSequenceGeneratorAndDefaultAllocationSize { + @Id + @GeneratedValue(generator = "seqgendefallocsize") + @SequenceGenerator(name = "seqgendefallocsize") + public Long id; + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java new file mode 100644 index 0000000000000..47d99905d0acf --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/java/io/quarkus/it/hibernate/compatibility/MyEnum.java @@ -0,0 +1,7 @@ +package io.quarkus.it.hibernate.compatibility; + +public enum MyEnum { + VALUE1, + VALUE2, + VALUE3 +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/resources/application.properties b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/resources/application.properties new file mode 100644 index 0000000000000..ef7cac5fdc786 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/main/resources/application.properties @@ -0,0 +1,16 @@ +quarkus.datasource.username=hibernate_orm_test +quarkus.datasource.password=hibernate_orm_test +quarkus.datasource.jdbc.url=${postgres.url} +quarkus.datasource.jdbc.max-size=8 + +# On startup, restore the dump of a database meant for ORM 5.6 +quarkus.flyway.migrate-at-start=true +# https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#enum-mapping-changes +# https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#datatype-for-enums +# The schema changed for enums and there is nothing we can do about it, +# so we're cannot pass schema validation. +quarkus.hibernate-orm.database.generation=none + +# Configure Hibernate ORM for compatibility with ORM 5.6 +quarkus.hibernate-orm.database.orm-compatibility.version=5.6 + diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTest.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTest.java new file mode 100644 index 0000000000000..19fa1ec2e5a20 --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTest.java @@ -0,0 +1,163 @@ +package io.quarkus.it.hibernate.compatibility; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import jakarta.ws.rs.core.Response.Status; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CompatibilityTest { + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#implicit-identifier-sequence-and-table-name + @Test + @Order(1) + public void sequence_defaultGenerator() { + var entity = new MyEntity(); + MyEntity createdEntity = given() + .body(entity).contentType("application/json") + .when().post("/compatibility/").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntity.class); + assertThat(createdEntity.id).isEqualTo(3); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#id-sequence-defaults + @Test + @Order(2) + public void sequence_genericGenerator_defaultAllocation() { + var entity = new MyEntityWithGenericGeneratorAndDefaultAllocationSize(); + MyEntityWithGenericGeneratorAndDefaultAllocationSize createdEntity = given() + .body(entity).contentType("application/json") + .when().post("/compatibility/genericgenerator").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntityWithGenericGeneratorAndDefaultAllocationSize.class); + assertThat(createdEntity.id).isEqualTo(3); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#id-sequence-defaults + @Test + @Order(3) + public void sequence_sequenceGenerator_defaultAllocation() { + var entity = new MyEntityWithSequenceGeneratorAndDefaultAllocationSize(); + MyEntityWithSequenceGeneratorAndDefaultAllocationSize createdEntity = given() + .body(entity).contentType("application/json") + .when().post("/compatibility/sequencegenerator").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntityWithSequenceGeneratorAndDefaultAllocationSize.class); + // Sequence generators defined through @SequenceGenerator have always defaulted to an allocation size of 50. + // Since we've created 2 entities in Hibernate 5, we should be starting the second pool here, starting at 52. + assertThat(createdEntity.id).isEqualTo(52L); + } + + // Just check that persisting with the old schema and new application does not throw any exception + @Test + @Order(4) // So that we can assert the generated ID in sequence*() + public void persistUsingOldSchema() { + var entity = new MyEntity(); + entity.duration = Duration.of(59, ChronoUnit.SECONDS); + entity.uuid = UUID.fromString("f49c6ba8-8d7f-417a-a255-d594dddf729f"); + entity.instant = Instant.parse("2018-01-01T10:58:30.00Z"); + entity.intArray = new int[] { 0, 1, 42 }; + entity.offsetDateTime = LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atOffset(ZoneOffset.ofHours(2)); + entity.zonedDateTime = LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atZone(ZoneId.of("Africa/Cairo" /* UTC+2 */)); + entity.stringList = new ArrayList<>(List.of("one", "two")); + entity.myEnum = MyEnum.VALUE2; + given() + .body(entity).contentType("application/json") + .when().post("/compatibility/").then() + .assertThat().statusCode(is(Status.OK.getStatusCode())); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#duration-mapping-changes + @Test + public void duration() { + assertThat(findOld().duration).isEqualTo(Duration.of(59, ChronoUnit.SECONDS)); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#uuid-mapping-changes + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-mariadb + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#uuid-mapping-changes-on-sql-server + @Test + public void uuid() { + assertThat(findOld().uuid).isEqualTo(UUID.fromString("f49c6ba8-8d7f-417a-a255-d594dddf729f")); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#instant-mapping-changes + @Test + public void instant() { + assertThat(findOld().instant).isEqualTo(Instant.parse("2018-01-01T10:58:30.00Z")); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#timezone-and-offset-storage + @Test + public void offsetDateTime() { + assertThat(findOld().offsetDateTime) + .isEqualTo(LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atOffset(ZoneOffset.ofHours(2)) + // Hibernate ORM 5 used to normalize these values to the JVM TZ + .atZoneSameInstant(ZoneId.systemDefault()).toOffsetDateTime()); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#timezone-and-offset-storage + @Test + public void zonedDateTime() { + assertThat(findOld().zonedDateTime) + .isEqualTo(LocalDateTime.of(2018, 1, 1, 12, 58, 30, 0) + .atZone(ZoneId.of("Africa/Cairo" /* UTC+2 */)) + // Hibernate ORM 5 used to normalize these values to the JVM TZ + .withZoneSameInstant(ZoneId.systemDefault())); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // Note this is not fixed automatically by Quarkus and requires new annotations on the entity (see entity class). + @Test + public void array() { + assertThat(findOld().intArray).isEqualTo(new int[] { 0, 1, 42 }); + } + + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#basic-arraycollection-mapping + // Note this is not fixed automatically by Quarkus and requires new annotations on the entity (see entity class). + @Test + public void list() { + assertThat(findOld().stringList).isEqualTo(new ArrayList<>(List.of("one", "two"))); + } + + @Test + // https://github.com/hibernate/hibernate-orm/blob/6.1/migration-guide.adoc#enum-mapping-changes + // https://github.com/hibernate/hibernate-orm/blob/6.2/migration-guide.adoc#datatype-for-enums + public void enum_() { + assertThat(findOld().myEnum).isEqualTo(MyEnum.VALUE2); + } + + private static MyEntity findOld() { + return find(1L); + } + + private static MyEntity find(long id) { + return given().when().get("/compatibility/{id}", id).then() + .assertThat().statusCode(is(Status.OK.getStatusCode())) + .extract().as(MyEntity.class); + } + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestInGraalITCase.java b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestInGraalITCase.java new file mode 100644 index 0000000000000..4564e10519e9a --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/java/io/quarkus/it/hibernate/compatibility/CompatibilityTestInGraalITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.hibernate.compatibility; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class CompatibilityTestInGraalITCase extends CompatibilityTest { + +} diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/resources/db/migration/V1.0.0__orm5-6.sql b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/resources/db/migration/V1.0.0__orm5-6.sql new file mode 100644 index 0000000000000..a84c82b0c182a --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/src/test/resources/db/migration/V1.0.0__orm5-6.sql @@ -0,0 +1,184 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 14.7 (Debian 14.7-1.pgdg110+1) +-- Dumped by pg_dump version 14.7 (Debian 14.7-1.pgdg110+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: gengendefallocsize; Type: SEQUENCE; Schema: public; Owner: hibernate_orm_test +-- + +CREATE SEQUENCE public.gengendefallocsize + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.gengendefallocsize OWNER TO hibernate_orm_test; + +-- +-- Name: hibernate_sequence; Type: SEQUENCE; Schema: public; Owner: hibernate_orm_test +-- + +CREATE SEQUENCE public.hibernate_sequence + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.hibernate_sequence OWNER TO hibernate_orm_test; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: myentity; Type: TABLE; Schema: public; Owner: hibernate_orm_test +-- + +CREATE TABLE public.myentity ( + id bigint NOT NULL, + duration bigint, + instant timestamp without time zone, + intarray bytea, + myenum integer, + offsetdatetime timestamp without time zone, + stringlist bytea, + uuid uuid, + zoneddatetime timestamp without time zone +); + + +ALTER TABLE public.myentity OWNER TO hibernate_orm_test; + +-- +-- Name: myentity_gengendefallocsize; Type: TABLE; Schema: public; Owner: hibernate_orm_test +-- + +CREATE TABLE public.myentity_gengendefallocsize ( + id bigint NOT NULL +); + + +ALTER TABLE public.myentity_gengendefallocsize OWNER TO hibernate_orm_test; + +-- +-- Name: myentity_seqgendefallocsize; Type: TABLE; Schema: public; Owner: hibernate_orm_test +-- + +CREATE TABLE public.myentity_seqgendefallocsize ( + id bigint NOT NULL +); + + +ALTER TABLE public.myentity_seqgendefallocsize OWNER TO hibernate_orm_test; + +-- +-- Name: seqgendefallocsize; Type: SEQUENCE; Schema: public; Owner: hibernate_orm_test +-- + +CREATE SEQUENCE public.seqgendefallocsize + START WITH 1 + INCREMENT BY 50 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.seqgendefallocsize OWNER TO hibernate_orm_test; + +-- +-- Data for Name: myentity; Type: TABLE DATA; Schema: public; Owner: hibernate_orm_test +-- + +COPY public.myentity (id, duration, instant, intarray, myenum, offsetdatetime, stringlist, uuid, zoneddatetime) FROM stdin; +1 59000000000 2018-01-01 11:58:30 \\xaced0005757200025b494dba602676eab2a502000078700000000300000000000000010000002a 1 2018-01-01 11:58:30 \\xaced0005737200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657870000000027704000000027400036f6e6574000374776f78 f49c6ba8-8d7f-417a-a255-d594dddf729f 2018-01-01 11:58:30 +2 \N \N \N \N \N \N \N \N +\. + + +-- +-- Data for Name: myentity_gengendefallocsize; Type: TABLE DATA; Schema: public; Owner: hibernate_orm_test +-- + +COPY public.myentity_gengendefallocsize (id) FROM stdin; +1 +2 +\. + + +-- +-- Data for Name: myentity_seqgendefallocsize; Type: TABLE DATA; Schema: public; Owner: hibernate_orm_test +-- + +COPY public.myentity_seqgendefallocsize (id) FROM stdin; +1 +2 +\. + + +-- +-- Name: gengendefallocsize; Type: SEQUENCE SET; Schema: public; Owner: hibernate_orm_test +-- + +SELECT pg_catalog.setval('public.gengendefallocsize', 2, true); + + +-- +-- Name: hibernate_sequence; Type: SEQUENCE SET; Schema: public; Owner: hibernate_orm_test +-- + +SELECT pg_catalog.setval('public.hibernate_sequence', 2, true); + + +-- +-- Name: seqgendefallocsize; Type: SEQUENCE SET; Schema: public; Owner: hibernate_orm_test +-- + +SELECT pg_catalog.setval('public.seqgendefallocsize', 51, true); + + +-- +-- Name: myentity_gengendefallocsize myentity_gengendefallocsize_pkey; Type: CONSTRAINT; Schema: public; Owner: hibernate_orm_test +-- + +ALTER TABLE ONLY public.myentity_gengendefallocsize + ADD CONSTRAINT myentity_gengendefallocsize_pkey PRIMARY KEY (id); + + +-- +-- Name: myentity myentity_pkey; Type: CONSTRAINT; Schema: public; Owner: hibernate_orm_test +-- + +ALTER TABLE ONLY public.myentity + ADD CONSTRAINT myentity_pkey PRIMARY KEY (id); + + +-- +-- Name: myentity_seqgendefallocsize myentity_seqgendefallocsize_pkey; Type: CONSTRAINT; Schema: public; Owner: hibernate_orm_test +-- + +ALTER TABLE ONLY public.myentity_seqgendefallocsize + ADD CONSTRAINT myentity_seqgendefallocsize_pkey PRIMARY KEY (id); + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/integration-tests/hibernate-orm-compatibility-5.6/postgresql/update-dump.sh b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/update-dump.sh new file mode 100755 index 0000000000000..36bcf6ab371fd --- /dev/null +++ b/integration-tests/hibernate-orm-compatibility-5.6/postgresql/update-dump.sh @@ -0,0 +1,31 @@ +#!/bin/bash -e + +MVN=../../../mvnw +DB_CONTAINER_NAME=postgresql-database-gen +# These should be the same as the username/password used by docker-maven-plugin in ./pom.xml +DB_NAME=hibernate_orm_test +DB_PORT=5431 +DB_USER=hibernate_orm_test +DB_PASSWORD=hibernate_orm_test +DUMP_LOCATION=src/test/resources/db/migration/V1.0.0__orm5-6.sql + +# Start container +$MVN docker:stop docker:build docker:start -Dstart-containers -Ddocker.containerNamePattern=$DB_CONTAINER_NAME +trap '$MVN docker:stop -Dstart-containers -Ddocker.containerNamePattern=$DB_CONTAINER_NAME' EXIT + +# Generate database +$MVN -f ../database-generator clean install -Ddb-kind=postgresql +# We use features in Hibernate ORM that are timezone-sensitive, e.g. ZoneOffsetDateTime normalization +TZ=Europe/Paris +QUARKUS_DATASOURCE_JDBC_URL="jdbc:postgresql://localhost:$DB_PORT/$DB_NAME" \ +QUARKUS_DATASOURCE_USERNAME="$DB_USER" \ +QUARKUS_DATASOURCE_PASSWORD="$DB_PASSWORD" \ + java -jar ../database-generator/target/quarkus-app/quarkus-run.jar + +# Update the dump +# https://stackoverflow.com/a/32611542/6692043 +echo "Updating dump at '$DUMP_LOCATION'" + +docker exec "$DB_CONTAINER_NAME" \ + sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump --username "$POSTGRES_USER" "$POSTGRES_DB"' \ + > "$DUMP_LOCATION" \ No newline at end of file diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 6d41e219f8048..3995a75f2be86 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -180,6 +180,8 @@ jpa-mssql jpa-mysql jpa-oracle + hibernate-orm-compatibility-5.6/mariadb + hibernate-orm-compatibility-5.6/postgresql hibernate-orm-panache hibernate-orm-rest-data-panache hibernate-orm-graphql-panache