From 51981f75107aaf0ea221f415d859c5dd12e9ef83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 14 Mar 2023 13:42:21 +0100 Subject: [PATCH] Introduce quarkus.hibernate-orm.mapping.timezone.default-storage --- .../HibernateOrmConfigPersistenceUnit.java | 64 +++++++++++++++++++ .../orm/deployment/HibernateOrmProcessor.java | 6 ++ .../io/quarkus/hibernate/orm/SchemaUtil.java | 23 +++++++ .../AbstractTimezoneDefaultStorageTest.java | 50 +++++++++++++++ .../orm/mapping/EntityWithTimezones.java | 29 +++++++++ .../TimezoneDefaultStorageAutoTest.java | 39 +++++++++++ .../TimezoneDefaultStorageColumnTest.java | 41 ++++++++++++ .../TimezoneDefaultStorageDefaultTest.java | 38 +++++++++++ .../TimezoneDefaultStorageNativeTest.java | 39 +++++++++++ .../TimezoneDefaultStorageNormalizeTest.java | 44 +++++++++++++ ...imezoneDefaultStorageNormalizeUtcTest.java | 40 ++++++++++++ 11 files changed, 413 insertions(+) create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/AbstractTimezoneDefaultStorageTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/EntityWithTimezones.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageAutoTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageColumnTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageDefaultTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNativeTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeTest.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeUtcTest.java diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java index e002cfe6def2c..8ca4ac692d8ad 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java @@ -10,6 +10,8 @@ import java.util.OptionalLong; import java.util.Set; +import org.hibernate.annotations.TimeZoneStorageType; + import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigGroup; @@ -154,6 +156,13 @@ public class HibernateOrmConfigPersistenceUnit { @ConvertWith(TrimmedStringConverter.class) public Optional> mappingFiles; + /** + * Mapping configuration. + */ + @ConfigItem + @ConfigDocSection + public HibernateOrmConfigPersistenceUnitMapping mapping; + /** * Query related configuration. */ @@ -265,6 +274,7 @@ public boolean isAnyPropertySet() { physicalNamingStrategy.isPresent() || implicitNamingStrategy.isPresent() || metadataBuilderContributor.isPresent() || + mapping.isAnyPropertySet() || query.isAnyPropertySet() || database.isAnyPropertySet() || jdbc.isAnyPropertySet() || @@ -324,6 +334,58 @@ public boolean isAnyPropertySet() { } } + /** + * Mapping-related configuration. + */ + @ConfigGroup + public static class HibernateOrmConfigPersistenceUnitMapping { + /** + * How to store timezones in the database by default + * for properties of type `OffsetDateTime` and `ZonedDateTime`. + * + * This default may be overridden on a per-property basis using `@TimeZoneStorage`. + * + * NOTE: Properties of type `OffsetTime` are https://hibernate.atlassian.net/browse/HHH-16287[not affected by this + * setting]. + * + * `default`:: + * Equivalent to `native` if supported, `normalize-utc` otherwise. + * `auto`:: + * Equivalent to `native` if supported, `column` otherwise. + * `native`:: + * Stores the timestamp and timezone in a column of type `timestamp with time zone`. + * + + * Only available on some databases/dialects; + * if not supported, an exception will be thrown during static initialization. + * `column`:: + * Stores the timezone in a separate column next to the timestamp column. + * + + * Use `@TimeZoneColumn` on the relevant entity property to customize the timezone column. + * `normalize-utc`:: + * Does not store the timezone, and loses timezone information upon persisting. + * + + * Instead, normalizes the value to a timestamp in the UTC timezone. + * `normalize`:: + * Does not store the timezone, and loses timezone information upon persisting. + * + + * Instead, normalizes the value: + * * upon persisting to the database, to a timestamp in the JDBC timezone + * set through `quarkus.hibernate-orm.jdbc.timezone`, + * or the JVM default timezone if not set. + * * upon reading back from the database, to the JVM default timezone. + * + + * Use this to get the legacy behavior of Quarkus 2 / Hibernate ORM 5 or older. + * + * @asciidoclet + */ + @ConfigItem(name = "timezone.default-storage", defaultValueDocumentation = "default") + public Optional timeZoneDefaultStorage; + + public boolean isAnyPropertySet() { + return timeZoneDefaultStorage.isPresent(); + } + } + @ConfigGroup public static class HibernateOrmConfigPersistenceUnitQuery { @@ -398,6 +460,8 @@ public static class HibernateOrmConfigPersistenceUnitJdbc { /** * The time zone pushed to the JDBC driver. + * + * See `quarkus.hibernate-orm.mapping.timezone.default-storage`. */ @ConfigItem @ConvertWith(TrimmedStringConverter.class) 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 00a94bb4644d5..99cfa6bb50474 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 @@ -1017,6 +1017,12 @@ private static void producePersistenceUnitDescriptorFromConfig( className -> descriptor.getProperties() .setProperty(EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR, className)); + // Mapping + if (persistenceUnitConfig.mapping.timeZoneDefaultStorage.isPresent()) { + descriptor.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, + persistenceUnitConfig.mapping.timeZoneDefaultStorage.get().name()); + } + //charset descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME, persistenceUnitConfig.database.charset.name()); diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/SchemaUtil.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/SchemaUtil.java index 9ddd7e2d461d0..d1223244e886a 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/SchemaUtil.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/SchemaUtil.java @@ -7,7 +7,11 @@ import jakarta.persistence.EntityManagerFactory; import org.hibernate.engine.spi.SessionFactoryImplementor; +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 { @@ -27,4 +31,23 @@ public static Set getColumnNames(EntityManagerFactory entityManagerFacto } 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(); + } } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/AbstractTimezoneDefaultStorageTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/AbstractTimezoneDefaultStorageTest.java new file mode 100644 index 0000000000000..f3f09421f0d4c --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/AbstractTimezoneDefaultStorageTest.java @@ -0,0 +1,50 @@ +package io.quarkus.hibernate.orm.mapping; + +import java.time.LocalDateTime; +import java.time.Month; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +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.Session; +import org.hibernate.SessionFactory; + +import io.quarkus.narayana.jta.QuarkusTransaction; + +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)); + public static final OffsetTime PERSISTED_OFFSET_TIME = LOCAL_DATE_TIME_TO_TEST.toLocalTime() + .atOffset(ZoneOffset.ofHours(3)); + + @Inject + SessionFactory sessionFactory; + + @Inject + Session session; + + protected long persistWithValuesToTest() { + return QuarkusTransaction.requiringNew().call(() -> { + var entity = new EntityWithTimezones(PERSISTED_ZONED_DATE_TIME, PERSISTED_OFFSET_DATE_TIME); + session.persist(entity); + return entity.id; + }); + } + + protected void assertLoadedValues(long id, ZonedDateTime expectedZonedDateTime, OffsetDateTime expectedOffsetDateTime) { + QuarkusTransaction.requiringNew().run(() -> { + var entity = session.find(EntityWithTimezones.class, id); + SoftAssertions.assertSoftly(assertions -> { + assertions.assertThat(entity).extracting("zonedDateTime").isEqualTo(expectedZonedDateTime); + assertions.assertThat(entity).extracting("offsetDateTime").isEqualTo(expectedOffsetDateTime); + }); + }); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/EntityWithTimezones.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/EntityWithTimezones.java new file mode 100644 index 0000000000000..189993f569629 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/EntityWithTimezones.java @@ -0,0 +1,29 @@ +package io.quarkus.hibernate.orm.mapping; + +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-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageAutoTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageAutoTest.java new file mode 100644 index 0000000000000..d5f808b72548e --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageAutoTest.java @@ -0,0 +1,39 @@ +package io.quarkus.hibernate.orm.mapping; + +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.orm.SchemaUtil; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class TimezoneDefaultStorageAutoTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class, SmokeTestUtils.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "auto"); + + @Test + public void schema() throws Exception { + assertThat(SchemaUtil.getColumnNames(sessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_WITH_TIMEZONE"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_WITH_TIMEZONE"); + } + + @Test + public void persistAndLoad() { + long id = persistWithValuesToTest(); + // For some reason native storage (with H2 at least) preserves the offset, but not the zone ID. + assertLoadedValues(id, PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(PERSISTED_ZONED_DATE_TIME.getOffset()), + PERSISTED_OFFSET_DATE_TIME); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageColumnTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageColumnTest.java new file mode 100644 index 0000000000000..cd92cc8f94035 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageColumnTest.java @@ -0,0 +1,41 @@ +package io.quarkus.hibernate.orm.mapping; + +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.orm.SchemaUtil; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class TimezoneDefaultStorageColumnTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class, SmokeTestUtils.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "column"); + + @Test + public void schema() throws Exception { + assertThat(SchemaUtil.getColumnNames(sessionFactory, 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(sessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + } + + @Test + public void persistAndLoad() { + long id = persistWithValuesToTest(); + // For some reason column storage preserves the offset, but not the zone ID. + assertLoadedValues(id, PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(PERSISTED_ZONED_DATE_TIME.getOffset()), + PERSISTED_OFFSET_DATE_TIME); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageDefaultTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageDefaultTest.java new file mode 100644 index 0000000000000..cd66ccc2c1301 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageDefaultTest.java @@ -0,0 +1,38 @@ +package io.quarkus.hibernate.orm.mapping; + +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.orm.SchemaUtil; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class TimezoneDefaultStorageDefaultTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class, SmokeTestUtils.class)) + .withConfigurationResource("application.properties"); + + @Test + public void schema() throws Exception { + assertThat(SchemaUtil.getColumnNames(sessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_WITH_TIMEZONE"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_WITH_TIMEZONE"); + } + + @Test + public void persistAndLoad() { + long id = persistWithValuesToTest(); + // For some reason native storage (with H2 at least) preserves the offset, but not the zone ID. + assertLoadedValues(id, PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(PERSISTED_ZONED_DATE_TIME.getOffset()), + PERSISTED_OFFSET_DATE_TIME); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNativeTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNativeTest.java new file mode 100644 index 0000000000000..85641b8063d45 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNativeTest.java @@ -0,0 +1,39 @@ +package io.quarkus.hibernate.orm.mapping; + +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.orm.SchemaUtil; +import io.quarkus.hibernate.orm.SmokeTestUtils; +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, SmokeTestUtils.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "native"); + + @Test + public void schema() throws Exception { + assertThat(SchemaUtil.getColumnNames(sessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_WITH_TIMEZONE"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_WITH_TIMEZONE"); + } + + @Test + public void persistAndLoad() { + long id = persistWithValuesToTest(); + // For some reason native storage (with H2 at least) preserves the offset, but not the zone ID. + assertLoadedValues(id, PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(PERSISTED_ZONED_DATE_TIME.getOffset()), + PERSISTED_OFFSET_DATE_TIME); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeTest.java new file mode 100644 index 0000000000000..b1a6f0c060feb --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeTest.java @@ -0,0 +1,44 @@ +package io.quarkus.hibernate.orm.mapping; + +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.orm.SchemaUtil; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +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, SmokeTestUtils.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() throws Exception { + assertThat(SchemaUtil.getColumnNames(sessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP"); + } + + @Test + public void persistAndLoad() { + long id = persistWithValuesToTest(); + assertLoadedValues(id, 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-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeUtcTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeUtcTest.java new file mode 100644 index 0000000000000..f7c3f36624f47 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/mapping/TimezoneDefaultStorageNormalizeUtcTest.java @@ -0,0 +1,40 @@ +package io.quarkus.hibernate.orm.mapping; + +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.orm.SchemaUtil; +import io.quarkus.hibernate.orm.SmokeTestUtils; +import io.quarkus.test.QuarkusUnitTest; + +public class TimezoneDefaultStorageNormalizeUtcTest extends AbstractTimezoneDefaultStorageTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(EntityWithTimezones.class) + .addClasses(SchemaUtil.class, SmokeTestUtils.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.mapping.timezone.default-storage", "normalize-utc"); + + @Test + public void schema() throws Exception { + assertThat(SchemaUtil.getColumnNames(sessionFactory, EntityWithTimezones.class)) + .doesNotContain("zonedDateTime_tz", "offsetDateTime_tz"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "zonedDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + assertThat(SchemaUtil.getColumnTypeName(sessionFactory, EntityWithTimezones.class, "offsetDateTime")) + .isEqualTo("TIMESTAMP_UTC"); + } + + @Test + public void persistAndLoad() { + long id = persistWithValuesToTest(); + assertLoadedValues(id, PERSISTED_ZONED_DATE_TIME.withZoneSameInstant(ZoneOffset.UTC), + PERSISTED_OFFSET_DATE_TIME.withOffsetSameInstant(ZoneOffset.UTC)); + } +}