diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java index 03b25ea348..a6e48651d0 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java @@ -11,6 +11,13 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; import java.util.Calendar; import java.util.Date; @@ -21,6 +28,7 @@ import org.hibernate.LockOptions; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.DB2GetObjectExtractor; import org.hibernate.dialect.DB2StructJdbcType; import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; @@ -82,15 +90,24 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNo import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.JavaObjectType; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.CharJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.DecimalJdbcType; +import org.hibernate.type.descriptor.jdbc.InstantJdbcType; +import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType; +import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType; +import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullResolvingJdbcType; +import org.hibernate.type.descriptor.jdbc.OffsetDateTimeJdbcType; +import org.hibernate.type.descriptor.jdbc.OffsetTimeJdbcType; import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType; import org.hibernate.type.descriptor.jdbc.VarcharJdbcType; import org.hibernate.type.descriptor.jdbc.XmlJdbcType; +import org.hibernate.type.descriptor.jdbc.ZonedDateTimeJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; @@ -805,6 +822,12 @@ public class DB2LegacyDialect extends Dialect { return "values current timestamp"; } + @Override + public boolean doesRoundTemporalOnOverflow() { + // DB2 does truncation + return false; + } + @Override public boolean isCurrentTimestampSelectStringCallable() { return false; @@ -877,6 +900,49 @@ public class DB2LegacyDialect extends Dialect { .getDescriptor( Object.class ) ) ); + + typeContributions.contributeJdbcType( new InstantJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, Instant.class ); + } + } ); + typeContributions.contributeJdbcType( new LocalDateTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, LocalDateTime.class ); + } + } ); + typeContributions.contributeJdbcType( new LocalDateJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, LocalDate.class ); + } + } ); + typeContributions.contributeJdbcType( new LocalTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, LocalTime.class ); + } + } ); + typeContributions.contributeJdbcType( new OffsetDateTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, OffsetDateTime.class ); + } + } ); + typeContributions.contributeJdbcType( new OffsetTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, OffsetTime.class ); + } + } ); + typeContributions.contributeJdbcType( new ZonedDateTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, ZonedDateTime.class ); + } + } ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java index c858b759fd..3bd40a7d0d 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java @@ -318,6 +318,11 @@ public class DerbyLegacyDialect extends Dialect { return 52; } + @Override + public int getDefaultTimestampPrecision() { + return 9; + } + @Override public void initializeFunctionRegistry(FunctionContributions functionContributions) { super.initializeFunctionRegistry(functionContributions); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index d3ddb745a3..fd27d7b409 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -723,6 +723,12 @@ public class HSQLLegacyDialect extends Dialect { return "call current_timestamp"; } + @Override + public boolean doesRoundTemporalOnOverflow() { + // HSQLDB does truncation + return false; + } + /** * For HSQLDB 2.0, this is a copy of the base class implementation. * For HSQLDB 1.8, only READ_UNCOMMITTED is supported. diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java index 5d48714e11..d56e791050 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java @@ -179,6 +179,12 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect { return getVersion().isSameOrAfter( 10, 2 ); } + @Override + public boolean doesRoundTemporalOnOverflow() { + // See https://jira.mariadb.org/browse/MDEV-16991 + return false; + } + @Override protected MySQLStorageEngine getDefaultMySQLStorageEngine() { return InnoDBStorageEngine.INSTANCE; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 7ff305cb68..5e2bb454e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -376,6 +376,11 @@ public abstract class AbstractHANADialect extends Dialect { return super.castPattern( from, to ); } + @Override + public int getDefaultTimestampPrecision() { + return 7; + } + public int getDefaultDecimalPrecision() { //the maximum on HANA return 34; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index f4503d2c8c..4588749fc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -11,6 +11,13 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; import java.util.Calendar; import java.util.Date; @@ -74,15 +81,24 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNo import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.JavaObjectType; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.CharJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.DecimalJdbcType; +import org.hibernate.type.descriptor.jdbc.InstantJdbcType; +import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType; +import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType; +import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullResolvingJdbcType; +import org.hibernate.type.descriptor.jdbc.OffsetDateTimeJdbcType; +import org.hibernate.type.descriptor.jdbc.OffsetTimeJdbcType; import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType; import org.hibernate.type.descriptor.jdbc.VarcharJdbcType; import org.hibernate.type.descriptor.jdbc.XmlJdbcType; +import org.hibernate.type.descriptor.jdbc.ZonedDateTimeJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; @@ -888,6 +904,12 @@ public class DB2Dialect extends Dialect { return "values current timestamp"; } + @Override + public boolean doesRoundTemporalOnOverflow() { + // DB2 does truncation + return false; + } + @Override public boolean isCurrentTimestampSelectStringCallable() { return false; @@ -960,6 +982,49 @@ public class DB2Dialect extends Dialect { .getDescriptor( Object.class ) ) ); + + typeContributions.contributeJdbcType( new InstantJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, Instant.class ); + } + } ); + typeContributions.contributeJdbcType( new LocalDateTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, LocalDateTime.class ); + } + } ); + typeContributions.contributeJdbcType( new LocalDateJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, LocalDate.class ); + } + } ); + typeContributions.contributeJdbcType( new LocalTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, LocalTime.class ); + } + } ); + typeContributions.contributeJdbcType( new OffsetDateTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, OffsetDateTime.class ); + } + } ); + typeContributions.contributeJdbcType( new OffsetTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, OffsetTime.class ); + } + } ); + typeContributions.contributeJdbcType( new ZonedDateTimeJdbcType() { + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new DB2GetObjectExtractor<>( javaType, this, ZonedDateTime.class ); + } + } ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2GetObjectExtractor.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2GetObjectExtractor.java new file mode 100644 index 0000000000..189b53352d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2GetObjectExtractor.java @@ -0,0 +1,74 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.JavaTimeJdbcType; +import org.hibernate.type.descriptor.jdbc.internal.GetObjectExtractor; + +/** + * Variant of the {@link GetObjectExtractor} that catches a {@link NullPointerException}, + * because the DB2 JDBC driver runs into that exception when trying to access a {@code null} value + * with the {@code getObject(int, Class)} and {@code getObject(String, Class)} methods. + * + * @author Christian Beikov + */ +public class DB2GetObjectExtractor extends GetObjectExtractor { + public DB2GetObjectExtractor( + JavaType javaType, + JavaTimeJdbcType jdbcType, + Class baseClass) { + super( javaType, jdbcType, baseClass ); + } + + @Override + protected T doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + try { + return super.doExtract( rs, paramIndex, options ); + } + catch (NullPointerException ex) { + final Object object = rs.getObject( paramIndex ); + if ( object == null ) { + return null; + } + throw ex; + } + } + + @Override + protected T doExtract(CallableStatement statement, int paramIndex, WrapperOptions options) throws SQLException { + try { + return super.doExtract( statement, paramIndex, options ); + } + catch (NullPointerException ex) { + final Object object = statement.getObject( paramIndex ); + if ( object == null ) { + return null; + } + throw ex; + } + } + + @Override + protected T doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + try { + return super.doExtract( statement, name, options ); + } + catch (NullPointerException ex) { + final Object object = statement.getObject( name ); + if ( object == null ) { + return null; + } + throw ex; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index 618d1665ab..78395be210 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -307,6 +307,11 @@ public class DerbyDialect extends Dialect { return 52; } + @Override + public int getDefaultTimestampPrecision() { + return 9; + } + @Override public void initializeFunctionRegistry(FunctionContributions functionContributions) { super.initializeFunctionRegistry(functionContributions); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 41606b5db3..0fae27fdcb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -4779,6 +4779,15 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun return 6; //microseconds! } + /** + * Does this dialect round a temporal when converting from a precision higher to a lower one? + * + * @return true if rounding is applied, false if truncation is applied + */ + public boolean doesRoundTemporalOnOverflow() { + return true; + } + /** * This is the default precision for a generated * column mapped to a Java {@link Float} or diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index f14dfac708..3eb934cbbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -583,6 +583,12 @@ public class HSQLDialect extends Dialect { return "values current_timestamp"; } + @Override + public boolean doesRoundTemporalOnOverflow() { + // HSQLDB does truncation + return false; + } + @Override public boolean supportsCommentOn() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java index b1aad9e41b..f9675b37ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java @@ -179,6 +179,12 @@ public class MariaDBDialect extends MySQLDialect { return true; } + @Override + public boolean doesRoundTemporalOnOverflow() { + // See https://jira.mariadb.org/browse/MDEV-16991 + return false; + } + @Override protected MySQLStorageEngine getDefaultMySQLStorageEngine() { return InnoDBStorageEngine.INSTANCE; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java index c0ce637735..330b69aebe 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java @@ -14,6 +14,7 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.util.Calendar; @@ -447,6 +448,32 @@ public final class DateTimeUtils { appender.appendSql( LOCAL_TIME_FORMAT.get().format( calendar.getTime() ) ); } + /** + * Do the same conversion that databases do when they encounter a timestamp with a higher precision + * than what is supported by a column, which is to round the excess fractions. + */ + public static T adjustToDefaultPrecision(T temporal, Dialect d) { + return adjustToPrecision( temporal, d.getDefaultTimestampPrecision(), d ); + } + + public static T adjustToPrecision(T temporal, int precision, Dialect d) { + return d.doesRoundTemporalOnOverflow() + ? roundToSecondPrecision( temporal, precision ) + : truncateToPrecision( temporal, precision ); + } + + public static T truncateToPrecision(T temporal, int precision) { + if ( precision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) { + return temporal; + } + final long factor = pow10( 9 - precision ); + //noinspection unchecked + return (T) temporal.with( + ChronoField.NANO_OF_SECOND, + temporal.get( ChronoField.NANO_OF_SECOND ) / factor * factor + ); + } + /** * Do the same conversion that databases do when they encounter a timestamp with a higher precision * than what is supported by a column, which is to round the excess fractions. @@ -464,6 +491,12 @@ public final class DateTimeUtils { } public static T roundToSecondPrecision(T temporal, int precision) { + if ( precision == 0 ) { + //noinspection unchecked + return temporal.get( ChronoField.NANO_OF_SECOND ) >= 500_000_000L + ? (T) temporal.plus( 1, ChronoUnit.SECONDS ).with( ChronoField.NANO_OF_SECOND, 0L ) + : (T) temporal.with( ChronoField.NANO_OF_SECOND, 0L ); + } //noinspection unchecked return (T) temporal.with( ChronoField.NANO_OF_SECOND, @@ -472,9 +505,9 @@ public final class DateTimeUtils { } public static long roundToPrecision(int nano, int precision) { - assert precision < 9 : "Precision (scale) should be less-than 9 - " + precision; - if ( precision == 0 ) { - return 0; + assert precision > 0 : "Can't round fractional seconds to less-than 0"; + if ( precision >= 9 ) { + return nano; } final int precisionMask = pow10( 9 - precision ); final int nanosToRound = nano % precisionMask; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateIdentityTest.java index c68eb816bc..eea6c646a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateIdentityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateIdentityTest.java @@ -19,6 +19,8 @@ import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.generator.EventType; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.insert.AbstractReturningDelegate; +import org.hibernate.id.insert.AbstractSelectingDelegate; import org.hibernate.id.insert.UniqueKeySelectingDelegate; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.model.MutationType; @@ -73,7 +75,7 @@ public class MutationDelegateIdentityTest { assertThat( entity.getName() ).isNull(); assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); - inspector.assertExecutedCount( delegate != null ? 1 : 2 ); + inspector.assertExecutedCount( delegate instanceof AbstractReturningDelegate ? 1 : 2 ); } ); } @@ -97,7 +99,9 @@ public class MutationDelegateIdentityTest { assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); inspector.assertExecutedCount( - delegate != null && delegate.supportsArbitraryValues() ? 1 : 2 + delegate instanceof AbstractSelectingDelegate + ? 3 + : delegate != null && delegate.supportsArbitraryValues() ? 1 : 2 ); } ); } @@ -110,7 +114,7 @@ public class MutationDelegateIdentityTest { MutationType.UPDATE ); final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); - final Integer id = scope.fromTransaction( session -> { + final Long id = scope.fromTransaction( session -> { final IdentityAndValues entity = new IdentityAndValues(); session.persist( entity ); session.flush(); @@ -154,7 +158,9 @@ public class MutationDelegateIdentityTest { assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); inspector.assertExecutedCount( - delegate != null && delegate.supportsArbitraryValues() ? 1 : 2 + delegate instanceof AbstractSelectingDelegate + ? 3 + : delegate != null && delegate.supportsArbitraryValues() ? 1 : 2 ); final boolean shouldHaveRowId = delegate != null && delegate.supportsRowId() @@ -210,7 +216,7 @@ public class MutationDelegateIdentityTest { ); if ( isUniqueKeyDelegate ) { inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "data", 1 ); - inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "id_column", 0 ); + inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "id_column", 1 ); } final boolean shouldHaveRowId = delegate != null && delegate.supportsRowId() @@ -238,11 +244,11 @@ public class MutationDelegateIdentityTest { public static class IdentityOnly { @Id @GeneratedValue( strategy = GenerationType.IDENTITY ) - private Integer id; + private Long id; private String name; - public Integer getId() { + public Long getId() { return id; } @@ -256,7 +262,7 @@ public class MutationDelegateIdentityTest { public static class IdentityAndValues { @Id @GeneratedValue( strategy = GenerationType.IDENTITY ) - private Integer id; + private Long id; @Generated( event = EventType.INSERT ) @ColumnDefault( "'default_name'" ) @@ -267,7 +273,7 @@ public class MutationDelegateIdentityTest { private String data; - public Integer getId() { + public Long getId() { return id; } @@ -291,7 +297,7 @@ public class MutationDelegateIdentityTest { @Id @Column( name = "id_column" ) @GeneratedValue( strategy = GenerationType.IDENTITY ) - private Integer id; + private Long id; @Generated( event = EventType.INSERT ) @ColumnDefault( "'default_name'" ) @@ -302,7 +308,7 @@ public class MutationDelegateIdentityTest { private String data; - public Integer getId() { + public Long getId() { return id; } @@ -326,7 +332,7 @@ public class MutationDelegateIdentityTest { @Id @Column( name = "id_column" ) @GeneratedValue( strategy = GenerationType.IDENTITY ) - private Integer id; + private Long id; @Generated( event = EventType.INSERT ) @ColumnDefault( "'default_name'" ) @@ -342,7 +348,7 @@ public class MutationDelegateIdentityTest { this.data = data; } - public Integer getId() { + public Long getId() { return id; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateJoinedInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateJoinedInheritanceTest.java index 8563f550f7..9bba590417 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateJoinedInheritanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/delegate/MutationDelegateJoinedInheritanceTest.java @@ -14,6 +14,7 @@ import org.hibernate.annotations.SourceType; import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.generator.EventType; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.insert.AbstractSelectingDelegate; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.model.MutationType; @@ -70,7 +71,9 @@ public class MutationDelegateJoinedInheritanceTest { assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); inspector.assertExecutedCount( - delegate != null && delegate.supportsArbitraryValues() ? 1 : 2 + delegate instanceof AbstractSelectingDelegate + ? 3 + : delegate != null && delegate.supportsArbitraryValues() ? 1 : 2 ); } ); } @@ -89,12 +92,28 @@ public class MutationDelegateJoinedInheritanceTest { assertThat( entity.getName() ).isEqualTo( "default_name" ); assertThat( entity.getChildName() ).isEqualTo( "default_child_name" ); - assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); - assertThat( inspector.getSqlQueries().get( 1 ) ).contains( "insert" ); - // Note: this is a current restriction, mutation delegates only retrieve generated values - // on the "root" table, and we expect other values to be read through a subsequent select - inspector.assertIsSelect( 2 ); - inspector.assertExecutedCount( 3 ); + final GeneratedValuesMutationDelegate delegate = getDelegate( + scope, + ChildEntity.class, + MutationType.INSERT + ); + + if ( delegate instanceof AbstractSelectingDelegate ) { + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); + assertThat( inspector.getSqlQueries().get( 2 ) ).contains( "insert" ); + // Note: this is a current restriction, mutation delegates only retrieve generated values + // on the "root" table, and we expect other values to be read through a subsequent select + inspector.assertIsSelect( 3 ); + inspector.assertExecutedCount( 4 ); + } + else { + assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" ); + assertThat( inspector.getSqlQueries().get( 1 ) ).contains( "insert" ); + // Note: this is a current restriction, mutation delegates only retrieve generated values + // on the "root" table, and we expect other values to be read through a subsequent select + inspector.assertIsSelect( 2 ); + inspector.assertExecutedCount( 3 ); + } } ); } @@ -106,7 +125,7 @@ public class MutationDelegateJoinedInheritanceTest { MutationType.UPDATE ); final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); - final Integer id = scope.fromTransaction( session -> { + final Long id = scope.fromTransaction( session -> { final BaseEntity entity = new BaseEntity(); session.persist( entity ); session.flush(); @@ -133,7 +152,7 @@ public class MutationDelegateJoinedInheritanceTest { @Test public void testUpdateChildEntity(SessionFactoryScope scope) { final SQLStatementInspector inspector = scope.getCollectingStatementInspector(); - final Integer id = scope.fromTransaction( session -> { + final Long id = scope.fromTransaction( session -> { final ChildEntity entity = new ChildEntity(); session.persist( entity ); session.flush(); @@ -175,7 +194,7 @@ public class MutationDelegateJoinedInheritanceTest { public static class BaseEntity { @Id @GeneratedValue( strategy = GenerationType.IDENTITY ) - private Integer id; + private Long id; @Generated( event = EventType.INSERT ) @ColumnDefault( "'default_name'" ) @@ -187,7 +206,7 @@ public class MutationDelegateJoinedInheritanceTest { @SuppressWarnings( "FieldCanBeLocal" ) private String data; - public Integer getId() { + public Long getId() { return id; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java index 6d1f6315d4..449fde20f2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/GlobalJavaTimeJdbcTypeTests.java @@ -17,6 +17,7 @@ import org.hibernate.cfg.MappingSettings; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.mapping.BasicValue; @@ -175,7 +176,8 @@ public class GlobalJavaTimeJdbcTypeTests { } @Test - @SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle drivers truncate fractional seconds from the LocalTime", matchSubTypes = true) + @SkipForDialect(dialectClass = OracleDialect.class, reason = "Oracle drivers truncate fractional seconds from the LocalTime", matchSubTypes = true) + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA time type does not support fractional seconds", matchSubTypes = true) void testLocalTime(SessionFactoryScope scope) { final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java index 3a4a56dbbd..fcd08f09ce 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/javatime/JavaTimeJdbcTypeTests.java @@ -17,6 +17,7 @@ import org.hibernate.cfg.MappingSettings; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.mapping.BasicValue; @@ -175,7 +176,8 @@ public class JavaTimeJdbcTypeTests { } @Test - @SkipForDialect( dialectClass = OracleDialect.class, reason = "Oracle drivers truncate fractional seconds from the LocalTime", matchSubTypes = true) + @SkipForDialect(dialectClass = OracleDialect.class, reason = "Oracle drivers truncate fractional seconds from the LocalTime", matchSubTypes = true) + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA time type does not support fractional seconds", matchSubTypes = true) void testLocalTime(SessionFactoryScope scope) { final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); final LocalTime startTime = roundToDefaultPrecision( LocalTime.now(), dialect ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/FractionalSecondsTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/FractionalSecondsTests.java index 9c15d81e77..db91fee171 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/FractionalSecondsTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/FractionalSecondsTests.java @@ -15,14 +15,12 @@ import java.time.ZonedDateTime; import org.hibernate.annotations.FractionalSeconds; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseDialect; @@ -97,7 +95,6 @@ public class FractionalSecondsTests { @Test @DomainModel(annotatedClasses = TestEntity.class) @SessionFactory - @SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" ) @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) void testUsage(SessionFactoryScope scope) { final Instant start = Instant.now(); @@ -111,28 +108,16 @@ public class FractionalSecondsTests { scope.inTransaction( (session) -> { final TestEntity testEntity = session.find( TestEntity.class, 1 ); - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( dialect instanceof DerbyDialect - || dialect instanceof MariaDBDialect ) { - assertThat( testEntity.theInstant ).isEqualTo( start ); - } - else { - assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 6 ) ); - } + assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.adjustToDefaultPrecision( start, dialect ) ); } ); } @Test @DomainModel(annotatedClasses = TestEntity0.class) @SessionFactory - @SkipForDialect( dialectClass = H2Dialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = MySQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true ) - @SkipForDialect( dialectClass = OracleDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = SQLServerDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = PostgreSQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true ) - @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" ) + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp") + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA does not support specifying a precision on timestamps") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) void testUsage0(SessionFactoryScope scope) { final Instant start = Instant.now(); @@ -146,17 +131,16 @@ public class FractionalSecondsTests { scope.inTransaction( (session) -> { final TestEntity0 testEntity = session.find( TestEntity0.class, 1 ); - assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 0 ) ); + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.adjustToPrecision( start, 0, dialect ) ); } ); } @Test @DomainModel(annotatedClasses = TestEntity3.class) @SessionFactory - @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = HSQLDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" ) + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp") + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA does not support specifying a precision on timestamps") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) void testUsage3(SessionFactoryScope scope) { final Instant start = Instant.now(); @@ -170,17 +154,21 @@ public class FractionalSecondsTests { scope.inTransaction( (session) -> { final TestEntity3 testEntity = session.find( TestEntity3.class, 1 ); - assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 3 ) ); + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.adjustToPrecision( start, 3, dialect ) ); } ); } @Test @DomainModel(annotatedClasses = TestEntity9.class) @SessionFactory - @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "MariaDB only supports precision <= 6" ) - @SkipForDialect( dialectClass = MySQLDialect.class, reason = "MySQL only supports precision <= 6", matchSubTypes = true ) - @SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQL Server only supports precision <= 6" ) + @SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB only supports precision <= 6") + @SkipForDialect(dialectClass = MySQLDialect.class, reason = "MySQL only supports precision <= 6", matchSubTypes = true) + @SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server only supports precision <= 6") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) + @SkipForDialect(dialectClass = PostgreSQLDialect.class, reason = "PostgreSQL only supports precision <= 6", matchSubTypes = true) + @SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB only supports precision <= 6") + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA does not support specifying a precision on timestamps") void testUsage9(SessionFactoryScope scope) { final Instant start = Instant.now(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/JavaTimeFractionalSecondsTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/JavaTimeFractionalSecondsTests.java index 74add55f61..271b590227 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/JavaTimeFractionalSecondsTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/temporal/JavaTimeFractionalSecondsTests.java @@ -16,14 +16,12 @@ import java.time.ZonedDateTime; import org.hibernate.annotations.FractionalSeconds; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.boot.spi.MetadataImplementor; -import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseDialect; @@ -99,7 +97,6 @@ public class JavaTimeFractionalSecondsTests { @Test @DomainModel(annotatedClasses = TestEntity.class) @SessionFactory - @SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" ) @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) void testUsage(SessionFactoryScope scope) { final Instant start = Instant.now(); @@ -113,28 +110,16 @@ public class JavaTimeFractionalSecondsTests { scope.inTransaction( (session) -> { final TestEntity testEntity = session.find( TestEntity.class, 1 ); - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( dialect instanceof DerbyDialect - || dialect instanceof MariaDBDialect ) { - assertThat( testEntity.theInstant ).isEqualTo( start ); - } - else { - assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 6 ) ); - } + assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.adjustToDefaultPrecision( start, dialect ) ); } ); } @Test @DomainModel(annotatedClasses = TestEntity0.class) @SessionFactory - @SkipForDialect( dialectClass = H2Dialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = MySQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true ) - @SkipForDialect( dialectClass = OracleDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = SQLServerDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = PostgreSQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true ) - @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" ) + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp") + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA does not support specifying a precision on timestamps") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) void testUsage0(SessionFactoryScope scope) { final Instant start = Instant.now(); @@ -148,17 +133,16 @@ public class JavaTimeFractionalSecondsTests { scope.inTransaction( (session) -> { final TestEntity0 testEntity = session.find( TestEntity0.class, 1 ); - assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 0 ) ); + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.adjustToPrecision( start, 0, dialect ) ); } ); } @Test @DomainModel(annotatedClasses = TestEntity3.class) @SessionFactory - @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = HSQLDialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" ) - @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" ) + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp") + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA does not support specifying a precision on timestamps") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) void testUsage3(SessionFactoryScope scope) { final Instant start = Instant.now(); @@ -172,17 +156,21 @@ public class JavaTimeFractionalSecondsTests { scope.inTransaction( (session) -> { final TestEntity3 testEntity = session.find( TestEntity3.class, 1 ); - assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 3 ) ); + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.adjustToPrecision( start, 3, dialect ) ); } ); } @Test @DomainModel(annotatedClasses = TestEntity9.class) @SessionFactory - @SkipForDialect( dialectClass = MariaDBDialect.class, reason = "MariaDB only supports precision <= 6" ) - @SkipForDialect( dialectClass = MySQLDialect.class, reason = "MySQL only supports precision <= 6", matchSubTypes = true ) - @SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQL Server only supports precision <= 6" ) + @SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB only supports precision <= 6") + @SkipForDialect(dialectClass = MySQLDialect.class, reason = "MySQL only supports precision <= 6", matchSubTypes = true) + @SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server only supports precision <= 6") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true) + @SkipForDialect(dialectClass = PostgreSQLDialect.class, reason = "PostgreSQL only supports precision <= 6", matchSubTypes = true) + @SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB only supports precision <= 6") + @SkipForDialect(dialectClass = HANADialect.class, reason = "HANA does not support specifying a precision on timestamps") void testUsage9(SessionFactoryScope scope) { final Instant start = Instant.now();