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 f4eba432a72ec..ecbc0d26c5a58 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 @@ -281,6 +281,15 @@ private static ParsedPersistenceXmlDescriptor generateReactivePersistenceUnit( namingStrategy -> desc.getProperties() .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy)); + // Mapping + if (persistenceUnitConfig.mapping.timeZoneDefaultStorage.isPresent()) { + desc.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, + persistenceUnitConfig.mapping.timeZoneDefaultStorage.get().name()); + } + desc.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER, + persistenceUnitConfig.mapping.idOptimizerDefault + .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName); + //charset desc.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, persistenceUnitConfig.database.charset.name()); diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/SchemaUtil.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/SchemaUtil.java new file mode 100644 index 0000000000000..ffd8be2dfb0e5 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/SchemaUtil.java @@ -0,0 +1,61 @@ +package io.quarkus.hibernate.reactive; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import jakarta.persistence.EntityManagerFactory; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.generator.Generator; +import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; + +public final class SchemaUtil { + + private SchemaUtil() { + } + + public static Set getColumnNames(EntityManagerFactory entityManagerFactory, Class entityType) { + Set result = new HashSet<>(); + AbstractEntityPersister persister = (AbstractEntityPersister) entityManagerFactory + .unwrap(SessionFactoryImplementor.class) + .getMetamodel().entityPersister(entityType); + if (persister == null) { + return result; + } + for (String propertyName : persister.getPropertyNames()) { + Collections.addAll(result, persister.getPropertyColumnNames(propertyName)); + } + return result; + } + + public static String getColumnTypeName(EntityManagerFactory entityManagerFactory, Class entityType, + String columnName) { + MappingMetamodel domainModel = entityManagerFactory + .unwrap(SessionFactoryImplementor.class).getRuntimeMetamodels().getMappingMetamodel(); + EntityPersister entityDescriptor = domainModel.findEntityDescriptor(entityType); + var columnFinder = new SelectableConsumer() { + private SelectableMapping found; + + @Override + public void accept(int selectionIndex, SelectableMapping selectableMapping) { + if (found == null && selectableMapping.getSelectableName().equals(columnName)) { + found = selectableMapping; + } + } + }; + entityDescriptor.forEachSelectable(columnFinder); + return columnFinder.found.getJdbcMapping().getJdbcType().getFriendlyName(); + } + + public static Generator getGenerator(EntityManagerFactory entityManagerFactory, Class entityType) { + MappingMetamodel domainModel = entityManagerFactory + .unwrap(SessionFactoryImplementor.class).getRuntimeMetamodels().getMappingMetamodel(); + EntityPersister entityDescriptor = domainModel.findEntityDescriptor(entityType); + return entityDescriptor.getGenerator(); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java new file mode 100644 index 0000000000000..f1d1f4fe70293 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java @@ -0,0 +1,35 @@ +package io.quarkus.hibernate.reactive.config; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.HibernateException; +import org.hibernate.MappingException; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.Type; + +/** + * This was the only way I found to get our hands on the settings used during metadata building. + * Feel free to use some other solution if you find one. + */ +public class SettingsSpyingIdentifierGenerator implements IdentifierGenerator { + public static final List> collectedSettings = new ArrayList<>(); + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { + collectedSettings.add(new HashMap<>(serviceRegistry.getService(ConfigurationService.class).getSettings())); + } + + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { + throw new IllegalStateException("This should not be called"); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/unsupportedproperties/UnsupportedPropertiesTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/unsupportedproperties/UnsupportedPropertiesTest.java new file mode 100644 index 0000000000000..28002eb779566 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/unsupportedproperties/UnsupportedPropertiesTest.java @@ -0,0 +1,124 @@ +package io.quarkus.hibernate.reactive.config.unsupportedproperties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +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.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.SessionFactory; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.reactive.mutiny.Mutiny; +import org.jboss.logmanager.formatters.PatternFormatter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.config.SettingsSpyingIdentifierGenerator; +import io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class UnsupportedPropertiesTest { + + 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.jdbc.statement-batch-size", "10") + // This should be taken into account by Hibernate ORM + .overrideConfigKey("quarkus.hibernate-orm.unsupported-properties.\"" + AvailableSettings.ORDER_INSERTS + "\"", + "true") + // This is just to test a property set at build time + .overrideConfigKey("quarkus.hibernate-orm.unsupported-properties.\"hibernate.some.unknown.key.static-and-runtime\"", + "some-value-1") + // This is just to test a property set at runtime, which would not be available during the build + // (or even during static init with native applications). + .overrideRuntimeConfigKey( + "quarkus.hibernate-orm.unsupported-properties.\"hibernate.some.unknown.key.runtime-only\"", + "some-value-2") + // This should be ignored with a warning + .overrideConfigKey( + "quarkus.hibernate-orm.unsupported-properties.\"" + AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION + + "\"", + "drop-and-create") + // Expect warnings on startup + .setLogRecordPredicate( + record -> FastBootHibernateReactivePersistenceProvider.class.getName().equals(record.getLoggerName()) + && record.getLevel().intValue() >= Level.WARNING.intValue()) + .assertLogRecords(records -> { + var assertion = assertThat(records) + .as("Warnings on startup") + .hasSize(2); + assertion.element(0).satisfies(record -> assertThat(LOG_FORMATTER.formatMessage(record)) + .contains("Persistence-unit [default-reactive] sets unsupported properties", + "These properties may not work correctly", + "may change when upgrading to a newer version of Quarkus (even just a micro/patch version)", + "Consider using a supported configuration property", + "make sure to file a feature request so that a supported configuration property can be added to Quarkus") + .contains(AvailableSettings.ORDER_INSERTS, AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, + "hibernate.some.unknown.key.static-and-runtime", "hibernate.some.unknown.key.runtime-only") + // We should not log property values, that could be a security breach for some properties. + .doesNotContain("some-value")); + assertion.element(1).satisfies(record -> assertThat(LOG_FORMATTER.formatMessage(record)) + .contains( + "Persistence-unit [default-reactive] sets property '" + + AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION + + "' to a custom value through 'quarkus.hibernate-orm.unsupported-properties.\"" + + AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION + "\"'", + "Quarkus already set that property independently", + "The custom value will be ignored")); + }); + + @Inject + SessionFactory ormSessionFactory; // This is an ORM SessionFactory, but it's backing Hibernate Reactive. + + @Inject + Mutiny.SessionFactory sessionFactory; + + @Test + public void testPropertiesPropagatedToStaticInit() { + assertThat(SettingsSpyingIdentifierGenerator.collectedSettings).hasSize(1); + Map settings = SettingsSpyingIdentifierGenerator.collectedSettings.get(0); + assertThat(settings) + .containsEntry("hibernate.some.unknown.key.static-and-runtime", "some-value-1") + .doesNotContainKey("hibernate.some.unknown.key.runtime-only"); + } + + @Test + public void testPropertiesPropagatedToRuntimeInit() { + assertThat(ormSessionFactory.getProperties()) + .contains(entry("hibernate.order_inserts", "true"), + // Also test a property that Quarkus cannot possibly know about + entry("hibernate.some.unknown.key.static-and-runtime", "some-value-1"), + entry("hibernate.some.unknown.key.runtime-only", "some-value-2")); + } + + @Entity + public static class SpyingIdentifierGeneratorEntity { + @Id + @GeneratedValue(generator = "spying-generator") + @GenericGenerator(name = "spying-generator", strategy = "io.quarkus.hibernate.reactive.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-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/AbstractIdOptimizerDefaultTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/AbstractIdOptimizerDefaultTest.java new file mode 100644 index 0000000000000..5d7960bd539f1 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/AbstractIdOptimizerDefaultTest.java @@ -0,0 +1,76 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.assertj.core.api.AbstractObjectAssert; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.hibernate.SessionFactory; +import org.hibernate.id.OptimizableGenerator; +import org.hibernate.id.enhanced.Optimizer; +import org.hibernate.id.enhanced.PooledLoOptimizer; +import org.hibernate.id.enhanced.PooledOptimizer; +import org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper; +import org.hibernate.reactive.mutiny.Mutiny; +import org.junit.jupiter.api.Test; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; + +public abstract class AbstractIdOptimizerDefaultTest { + + @Inject + SessionFactory ormSessionFactory; // This is an ORM SessionFactory, but it's backing Hibernate Reactive. + + @Inject + Mutiny.SessionFactory sessionFactory; + + abstract Class defaultOptimizerType(); + + @Test + public void defaults() { + assertThat(List.of( + EntityWithDefaultGenerator.class, + EntityWithGenericGenerator.class, + EntityWithSequenceGenerator.class, + EntityWithTableGenerator.class)) + .allSatisfy(c -> assertOptimizer(c).isInstanceOf(defaultOptimizerType())); + } + + @Test + public void explicitOverrides() { + assertOptimizer(EntityWithGenericGeneratorAndPooledOptimizer.class) + .isInstanceOf(PooledOptimizer.class); + assertOptimizer(EntityWithGenericGeneratorAndPooledLoOptimizer.class) + .isInstanceOf(PooledLoOptimizer.class); + } + + @Test + @RunOnVertxContext + public void ids(UniAsserter asserter) { + for (long i = 1; i <= 51; i++) { + long expectedId = i; + // Apparently, we can rely on assertions being executed in order. + asserter.assertThat(() -> sessionFactory.withTransaction(s -> { + var entity = new EntityWithSequenceGenerator(); + return s.persist(entity).replaceWith(() -> entity.id); + }), + id -> assertThat(id).isEqualTo(expectedId)); + } + } + + AbstractObjectAssert assertOptimizer(Class entityType) { + return assertThat(SchemaUtil.getGenerator(ormSessionFactory, entityType)) + .as("Reactive ID generator wrapper for entity type " + entityType.getSimpleName()) + .asInstanceOf(InstanceOfAssertFactories.type(ReactiveGeneratorWrapper.class)) + .extracting("generator") // Needs reflection, unfortunately the blocking generator is not exposed... + .as("Blocking ID generator for entity type " + entityType.getSimpleName()) + .asInstanceOf(InstanceOfAssertFactories.type(OptimizableGenerator.class)) + .extracting(OptimizableGenerator::getOptimizer) + .as("ID optimizer for entity type " + entityType.getSimpleName()); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithDefaultGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithDefaultGenerator.java new file mode 100644 index 0000000000000..46ddf6ab11fc9 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithDefaultGenerator.java @@ -0,0 +1,17 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class EntityWithDefaultGenerator { + + @Id + @GeneratedValue + Long id; + + public EntityWithDefaultGenerator() { + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGenerator.java new file mode 100644 index 0000000000000..1d12f0c9dc801 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGenerator.java @@ -0,0 +1,21 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; + +@Entity +public class EntityWithGenericGenerator { + + @Id + @GeneratedValue(generator = "gen_gen") + @GenericGenerator(name = "gen_gen", type = SequenceStyleGenerator.class) + Long id; + + public EntityWithGenericGenerator() { + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGeneratorAndPooledLoOptimizer.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGeneratorAndPooledLoOptimizer.java new file mode 100644 index 0000000000000..2bad1453be00a --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGeneratorAndPooledLoOptimizer.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.id.OptimizableGenerator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; + +@Entity +public class EntityWithGenericGeneratorAndPooledLoOptimizer { + + @Id + @GeneratedValue(generator = "gen_gen_pooled_lo") + @GenericGenerator(name = "gen_gen_pooled_lo", type = SequenceStyleGenerator.class, parameters = @Parameter(name = OptimizableGenerator.OPT_PARAM, value = "pooled-lo")) + Long id; + + public EntityWithGenericGeneratorAndPooledLoOptimizer() { + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGeneratorAndPooledOptimizer.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGeneratorAndPooledOptimizer.java new file mode 100644 index 0000000000000..6789ffa305c18 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithGenericGeneratorAndPooledOptimizer.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.id.OptimizableGenerator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; + +@Entity +public class EntityWithGenericGeneratorAndPooledOptimizer { + + @Id + @GeneratedValue(generator = "gen_gen_pooled_lo") + @GenericGenerator(name = "gen_gen_pooled_lo", type = SequenceStyleGenerator.class, parameters = @Parameter(name = OptimizableGenerator.OPT_PARAM, value = "pooled")) + Long id; + + public EntityWithGenericGeneratorAndPooledOptimizer() { + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithSequenceGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithSequenceGenerator.java new file mode 100644 index 0000000000000..ce615b11dece5 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithSequenceGenerator.java @@ -0,0 +1,19 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; + +@Entity +public class EntityWithSequenceGenerator { + + @Id + @GeneratedValue(generator = "seq_gen") + @SequenceGenerator(name = "seq_gen") + Long id; + + public EntityWithSequenceGenerator() { + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithTableGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithTableGenerator.java new file mode 100644 index 0000000000000..1bf5dbc50ab03 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/EntityWithTableGenerator.java @@ -0,0 +1,19 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.TableGenerator; + +@Entity +public class EntityWithTableGenerator { + + @Id + @GeneratedValue(generator = "tab_gen") + @TableGenerator(name = "tab_gen") + Long id; + + public EntityWithTableGenerator() { + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultDefaultTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultDefaultTest.java new file mode 100644 index 0000000000000..c170e3fad1bab --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultDefaultTest.java @@ -0,0 +1,25 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import org.hibernate.id.enhanced.PooledLoOptimizer; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class IdOptimizerDefaultDefaultTest extends AbstractIdOptimizerDefaultTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithDefaultGenerator.class, EntityWithGenericGenerator.class, + EntityWithSequenceGenerator.class, EntityWithTableGenerator.class, + EntityWithGenericGeneratorAndPooledOptimizer.class, + EntityWithGenericGeneratorAndPooledLoOptimizer.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties"); + + @Override + Class defaultOptimizerType() { + return PooledLoOptimizer.class; + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java new file mode 100644 index 0000000000000..2ff9f955a727c --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java @@ -0,0 +1,34 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import org.hibernate.id.enhanced.NoopOptimizer; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.vertx.UniAsserter; + +public class IdOptimizerDefaultNoneTest extends AbstractIdOptimizerDefaultTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithDefaultGenerator.class, EntityWithGenericGenerator.class, + EntityWithSequenceGenerator.class, EntityWithTableGenerator.class, + EntityWithGenericGeneratorAndPooledOptimizer.class, + EntityWithGenericGeneratorAndPooledLoOptimizer.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.id.optimizer.default", "none"); + + @Override + @Disabled("The 'none' optimizer will produce a different stream of IDs (1 then 51 then 101 then ...)") + public void ids(UniAsserter asserter) { + super.ids(asserter); + } + + @Override + Class defaultOptimizerType() { + return NoopOptimizer.class; + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultPooledLoTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultPooledLoTest.java new file mode 100644 index 0000000000000..afaa8e2dcd4e3 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultPooledLoTest.java @@ -0,0 +1,26 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import org.hibernate.id.enhanced.PooledLoOptimizer; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class IdOptimizerDefaultPooledLoTest extends AbstractIdOptimizerDefaultTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithDefaultGenerator.class, EntityWithGenericGenerator.class, + EntityWithSequenceGenerator.class, EntityWithTableGenerator.class, + EntityWithGenericGeneratorAndPooledOptimizer.class, + EntityWithGenericGeneratorAndPooledLoOptimizer.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.id.optimizer.default", "pooled-lo"); + + @Override + Class defaultOptimizerType() { + return PooledLoOptimizer.class; + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultPooledTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultPooledTest.java new file mode 100644 index 0000000000000..3c4a4b621e49e --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultPooledTest.java @@ -0,0 +1,26 @@ +package io.quarkus.hibernate.reactive.mapping.id.optimizer.optimizer; + +import org.hibernate.id.enhanced.PooledOptimizer; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class IdOptimizerDefaultPooledTest extends AbstractIdOptimizerDefaultTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithDefaultGenerator.class, EntityWithGenericGenerator.class, + EntityWithSequenceGenerator.class, EntityWithTableGenerator.class, + EntityWithGenericGeneratorAndPooledOptimizer.class, + EntityWithGenericGeneratorAndPooledLoOptimizer.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.id.optimizer.default", "pooled"); + + @Override + Class defaultOptimizerType() { + return PooledOptimizer.class; + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/AbstractTimezoneDefaultStorageTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/AbstractTimezoneDefaultStorageTest.java new file mode 100644 index 0000000000000..a779f625959a6 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/AbstractTimezoneDefaultStorageTest.java @@ -0,0 +1,45 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import java.time.LocalDateTime; +import java.time.Month; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import jakarta.inject.Inject; + +import org.assertj.core.api.SoftAssertions; +import org.hibernate.SessionFactory; +import org.hibernate.reactive.mutiny.Mutiny; + +import io.quarkus.test.vertx.UniAsserter; + +public class AbstractTimezoneDefaultStorageTest { + + private static final LocalDateTime LOCAL_DATE_TIME_TO_TEST = LocalDateTime.of(2017, Month.NOVEMBER, 6, 19, 19, 0); + public static final ZonedDateTime PERSISTED_ZONED_DATE_TIME = LOCAL_DATE_TIME_TO_TEST.atZone(ZoneId.of("Africa/Cairo")); + public static final OffsetDateTime PERSISTED_OFFSET_DATE_TIME = LOCAL_DATE_TIME_TO_TEST.atOffset(ZoneOffset.ofHours(3)); + + @Inject + SessionFactory ormSessionFactory; // This is an ORM SessionFactory, but it's backing Hibernate Reactive. + + @Inject + Mutiny.SessionFactory sessionFactory; + + protected void assertPersistedThenLoadedValues(UniAsserter asserter, ZonedDateTime expectedZonedDateTime, + OffsetDateTime expectedOffsetDateTime) { + asserter.assertThat( + () -> sessionFactory.withTransaction(session -> { + var entity = new EntityWithTimezones(PERSISTED_ZONED_DATE_TIME, PERSISTED_OFFSET_DATE_TIME); + return session.persist(entity).replaceWith(() -> entity.id); + }) + .chain(id -> sessionFactory.withTransaction(session -> session.find(EntityWithTimezones.class, id))), + entity -> { + SoftAssertions.assertSoftly(assertions -> { + assertions.assertThat(entity).extracting("zonedDateTime").isEqualTo(expectedZonedDateTime); + assertions.assertThat(entity).extracting("offsetDateTime").isEqualTo(expectedOffsetDateTime); + }); + }); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/EntityWithTimezones.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/EntityWithTimezones.java new file mode 100644 index 0000000000000..8f05420d1e582 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/EntityWithTimezones.java @@ -0,0 +1,29 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class EntityWithTimezones { + + @Id + @GeneratedValue + Long id; + + public EntityWithTimezones() { + } + + public EntityWithTimezones(ZonedDateTime zonedDateTime, OffsetDateTime offsetDateTime) { + this.zonedDateTime = zonedDateTime; + this.offsetDateTime = offsetDateTime; + } + + public ZonedDateTime zonedDateTime; + + public OffsetDateTime offsetDateTime; + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageAutoTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageAutoTest.java new file mode 100644 index 0000000000000..245ccf125ec5d --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageAutoTest.java @@ -0,0 +1,44 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; + +public class TimezoneDefaultStorageAutoTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "auto"); + + @Test + public void schema() { + assertThat(SchemaUtil.getColumnNames(ormSessionFactory, EntityWithTimezones.class)) + .contains("zonedDateTime_tz", "offsetDateTime_tz") + // For some reason we don't get a TZ column for OffsetTime + .doesNotContain("offsetTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + } + + @Test + @RunOnVertxContext + public void persistAndLoad(UniAsserter asserter) { + // Native storage is not supported with PostgreSQL, so we'll effectively use COLUMN. + // For some reason column storage preserves the offset, but not the zone ID. + assertPersistedThenLoadedValues(asserter, + PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(PERSISTED_ZONED_DATE_TIME.getOffset()), + PERSISTED_OFFSET_DATE_TIME); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageColumnTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageColumnTest.java new file mode 100644 index 0000000000000..ebccb257cd4df --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageColumnTest.java @@ -0,0 +1,43 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; + +public class TimezoneDefaultStorageColumnTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "column"); + + @Test + public void schema() { + assertThat(SchemaUtil.getColumnNames(ormSessionFactory, EntityWithTimezones.class)) + .contains("zonedDateTime_tz", "offsetDateTime_tz") + // For some reason we don't get a TZ column for OffsetTime + .doesNotContain("offsetTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + } + + @Test + @RunOnVertxContext + public void persistAndLoad(UniAsserter asserter) { + // For some reason column storage preserves the offset, but not the zone ID. + assertPersistedThenLoadedValues(asserter, + PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(PERSISTED_ZONED_DATE_TIME.getOffset()), + PERSISTED_OFFSET_DATE_TIME); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageDefaultTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageDefaultTest.java new file mode 100644 index 0000000000000..fc9fb7b7514b8 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageDefaultTest.java @@ -0,0 +1,42 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.ZoneOffset; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; + +public class TimezoneDefaultStorageDefaultTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties"); + + @Test + public void schema() { + assertThat(SchemaUtil.getColumnNames(ormSessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + } + + @Test + @RunOnVertxContext + public void persistAndLoad(UniAsserter asserter) { + // Native storage is not supported with PostgreSQL, so we'll effectively use NORMALIZED_UTC. + assertPersistedThenLoadedValues(asserter, + PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(ZoneOffset.UTC), + PERSISTED_OFFSET_DATE_TIME.withOffsetSameInstant(ZoneOffset.UTC)); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNativeTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNativeTest.java new file mode 100644 index 0000000000000..b113edf626637 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNativeTest.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class TimezoneDefaultStorageNativeTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "native") + .assertException(t -> assertThat(t) + // NATIVE is not supported with PostgreSQL. + .rootCause().hasMessageContaining("NATIVE is not supported with the configured dialect")); + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNormalizeTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNormalizeTest.java new file mode 100644 index 0000000000000..e52d1cd09204d --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNormalizeTest.java @@ -0,0 +1,46 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.ZoneId; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; + +public class TimezoneDefaultStorageNormalizeTest extends AbstractTimezoneDefaultStorageTest { + + private static final ZoneId JDBC_TIMEZONE = ZoneId.of("America/Los_Angeles"); + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "normalize") + .overrideConfigKey("quarkus.hibernate-orm.jdbc.timezone", JDBC_TIMEZONE.getId()); + + @Test + public void schema() { + assertThat(SchemaUtil.getColumnNames(ormSessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP"); + } + + @Test + @RunOnVertxContext + public void persistAndLoad(UniAsserter asserter) { + assertPersistedThenLoadedValues(asserter, + PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(ZoneId.systemDefault()), + PERSISTED_OFFSET_DATE_TIME.withOffsetSameInstant( + ZoneId.systemDefault().getRules().getOffset(PERSISTED_OFFSET_DATE_TIME.toInstant()))); + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNormalizeUtcTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNormalizeUtcTest.java new file mode 100644 index 0000000000000..fcd3e59a05a9a --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/timezone/TimezoneDefaultStorageNormalizeUtcTest.java @@ -0,0 +1,42 @@ +package io.quarkus.hibernate.reactive.mapping.timezone; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.ZoneOffset; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.SchemaUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; + +public class TimezoneDefaultStorageNormalizeUtcTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "normalize-utc"); + + @Test + public void schema() { + assertThat(SchemaUtil.getColumnNames(ormSessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + assertThat(SchemaUtil.getColumnTypeName(ormSessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + } + + @Test + @RunOnVertxContext + public void persistAndLoad(UniAsserter asserter) { + assertPersistedThenLoadedValues(asserter, + PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(ZoneOffset.UTC), + PERSISTED_OFFSET_DATE_TIME.withOffsetSameInstant(ZoneOffset.UTC)); + } +} diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java index 3b8a808bd8ff1..98a1147dc3f66 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java @@ -169,6 +169,31 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String } } + if (!puConfig.unsupportedProperties.isEmpty()) { + log.warnf("Persistence-unit [%s] sets unsupported properties." + + " These properties may not work correctly, and even if they do," + + " that may change when upgrading to a newer version of Quarkus (even just a micro/patch version)." + + " Consider using a supported configuration property before falling back to unsupported ones." + + " If there is no supported equivalent, make sure to file a feature request so that a supported configuration property can be added to Quarkus," + + " and more importantly so that the configuration property is tested regularly." + + " Unsupported properties being set: %s", + persistenceUnitName, + puConfig.unsupportedProperties.keySet()); + } + for (Map.Entry entry : puConfig.unsupportedProperties.entrySet()) { + var key = entry.getKey(); + if (runtimeSettingsBuilder.get(key) != null) { + log.warnf("Persistence-unit [%s] sets property '%s' to a custom value through '%s'," + + " but Quarkus already set that property independently." + + " The custom value will be ignored.", + persistenceUnitName, key, + HibernateOrmRuntimeConfig.puPropertyKey(persistenceUnit.getConfigurationName(), + "unsupported-properties.\"" + key + "\"")); + continue; + } + runtimeSettingsBuilder.put(entry.getKey(), entry.getValue()); + } + RuntimeSettings runtimeSettings = runtimeSettingsBuilder.build(); StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry(