HHH-16287 Consider hibernate.timezone.default_storage for OffsetTime typing and storage

This commit is contained in:
Christian Beikov 2023-03-17 10:08:20 +01:00
parent c54f71473e
commit e8a098ef1d
65 changed files with 2163 additions and 864 deletions

View File

@ -8,7 +8,9 @@ package org.hibernate.userguide.mapping.basic;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -18,6 +20,7 @@ import org.hibernate.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage; import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.TimeZoneStorageType; import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
@ -47,6 +50,15 @@ import static org.hamcrest.MatcherAssert.assertThat;
@ServiceRegistry(settings = @Setting( name = AvailableSettings.TIMEZONE_DEFAULT_STORAGE, value = "AUTO")) @ServiceRegistry(settings = @Setting( name = AvailableSettings.TIMEZONE_DEFAULT_STORAGE, value = "AUTO"))
public class TimeZoneStorageMappingTests { public class TimeZoneStorageMappingTests {
private static final ZoneOffset JVM_TIMEZONE_OFFSET = OffsetDateTime.now().getOffset();
private static final OffsetTime OFFSET_TIME = OffsetTime.of(
LocalTime.of(
12,
0,
0
),
ZoneOffset.ofHoursMinutes( 5, 45 )
);
private static final OffsetDateTime OFFSET_DATE_TIME = OffsetDateTime.of( private static final OffsetDateTime OFFSET_DATE_TIME = OffsetDateTime.of(
LocalDateTime.of( LocalDateTime.of(
2022, 2022,
@ -69,11 +81,12 @@ public class TimeZoneStorageMappingTests {
), ),
ZoneOffset.ofHoursMinutes( 5, 45 ) ZoneOffset.ofHoursMinutes( 5, 45 )
); );
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern( "HH:mm:ssxxx" );
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern( "dd/MM/yyyy 'at' HH:mm:ssxxx" ); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern( "dd/MM/yyyy 'at' HH:mm:ssxxx" );
@BeforeEach @BeforeEach
public void setup(SessionFactoryScope scope) { public void setup(SessionFactoryScope scope) {
scope.inTransaction( s -> s.persist( new TimeZoneStorageEntity( 1, OFFSET_DATE_TIME, ZONED_DATE_TIME ) ) ); scope.inTransaction( s -> s.persist( new TimeZoneStorageEntity( 1, OFFSET_TIME, OFFSET_DATE_TIME, ZONED_DATE_TIME ) ) );
} }
@AfterEach @AfterEach
@ -109,26 +122,40 @@ public class TimeZoneStorageMappingTests {
session -> { session -> {
List<Tuple> resultList = session.createQuery( List<Tuple> resultList = session.createQuery(
"select " + "select " +
"e.offsetTime" + suffix + ", " +
"e.offsetDateTime" + suffix + ", " + "e.offsetDateTime" + suffix + ", " +
"e.zonedDateTime" + suffix + ", " + "e.zonedDateTime" + suffix + ", " +
"extract(offset from e.offsetTime" + suffix + "), " +
"extract(offset from e.offsetDateTime" + suffix + "), " + "extract(offset from e.offsetDateTime" + suffix + "), " +
"extract(offset from e.zonedDateTime" + suffix + "), " + "extract(offset from e.zonedDateTime" + suffix + "), " +
"e.offsetTime" + suffix + " + 1 hour, " +
"e.offsetDateTime" + suffix + " + 1 hour, " + "e.offsetDateTime" + suffix + " + 1 hour, " +
"e.zonedDateTime" + suffix + " + 1 hour, " + "e.zonedDateTime" + suffix + " + 1 hour, " +
"e.offsetTime" + suffix + " + 1 hour - e.offsetTime" + suffix + ", " +
"e.offsetDateTime" + suffix + " + 1 hour - e.offsetDateTime" + suffix + ", " + "e.offsetDateTime" + suffix + " + 1 hour - e.offsetDateTime" + suffix + ", " +
"e.zonedDateTime" + suffix + " + 1 hour - e.zonedDateTime" + suffix + ", " + "e.zonedDateTime" + suffix + " + 1 hour - e.zonedDateTime" + suffix + ", " +
"1 from TimeZoneStorageEntity e " + "1 from TimeZoneStorageEntity e " +
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix, "where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
Tuple.class Tuple.class
).getResultList(); ).getResultList();
assertThat( resultList.get( 0 ).get( 0, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME ) ); assertThat( resultList.get( 0 ).get( 0, OffsetTime.class ), Matchers.is( OFFSET_TIME ) );
assertThat( resultList.get( 0 ).get( 1, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME ) ); assertThat( resultList.get( 0 ).get( 1, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME ) );
assertThat( resultList.get( 0 ).get( 2, ZoneOffset.class ), Matchers.is( OFFSET_DATE_TIME.getOffset() ) ); assertThat( resultList.get( 0 ).get( 2, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME ) );
assertThat( resultList.get( 0 ).get( 3, ZoneOffset.class ), Matchers.is( ZONED_DATE_TIME.getOffset() ) ); if ( !( scope.getSessionFactory().getJdbcServices().getDialect() instanceof H2Dialect) ) {
assertThat( resultList.get( 0 ).get( 4, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME.plusHours( 1L ) ) ); // H2 bug: https://github.com/h2database/h2database/issues/3757
assertThat( resultList.get( 0 ).get( 5, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME.plusHours( 1L ) ) ); assertThat(
assertThat( resultList.get( 0 ).get( 6, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) ); resultList.get( 0 ).get( 3, ZoneOffset.class ),
assertThat( resultList.get( 0 ).get( 7, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) ); Matchers.is( OFFSET_TIME.getOffset() )
);
}
assertThat( resultList.get( 0 ).get( 4, ZoneOffset.class ), Matchers.is( OFFSET_DATE_TIME.getOffset() ) );
assertThat( resultList.get( 0 ).get( 5, ZoneOffset.class ), Matchers.is( ZONED_DATE_TIME.getOffset() ) );
assertThat( resultList.get( 0 ).get( 6, OffsetTime.class ), Matchers.is( OFFSET_TIME.plusHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 7, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME.plusHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 8, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME.plusHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 9, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 10, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 11, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
} }
); );
} }
@ -138,14 +165,22 @@ public class TimeZoneStorageMappingTests {
session -> { session -> {
List<Tuple> resultList = session.createQuery( List<Tuple> resultList = session.createQuery(
"select " + "select " +
"format(e.offsetTime" + suffix + " as 'HH:mm:ssxxx'), " +
"format(e.offsetDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " + "format(e.offsetDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
"format(e.zonedDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " + "format(e.zonedDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
"1 from TimeZoneStorageEntity e " + "1 from TimeZoneStorageEntity e " +
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix, "where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
Tuple.class Tuple.class
).getResultList(); ).getResultList();
assertThat( resultList.get( 0 ).get( 0, String.class ), Matchers.is( FORMATTER.format( OFFSET_DATE_TIME ) ) ); if ( !( scope.getSessionFactory().getJdbcServices().getDialect() instanceof H2Dialect) ) {
assertThat( resultList.get( 0 ).get( 1, String.class ), Matchers.is( FORMATTER.format( ZONED_DATE_TIME ) ) ); // H2 bug: https://github.com/h2database/h2database/issues/3757
assertThat(
resultList.get( 0 ).get( 0, String.class ),
Matchers.is( TIME_FORMATTER.format( OFFSET_TIME ) )
);
}
assertThat( resultList.get( 0 ).get( 1, String.class ), Matchers.is( FORMATTER.format( OFFSET_DATE_TIME ) ) );
assertThat( resultList.get( 0 ).get( 2, String.class ), Matchers.is( FORMATTER.format( ZONED_DATE_TIME ) ) );
} }
); );
} }
@ -155,29 +190,48 @@ public class TimeZoneStorageMappingTests {
scope.inSession( scope.inSession(
session -> { session -> {
List<Tuple> resultList = session.createQuery( List<Tuple> resultList = session.createQuery(
"select e.offsetDateTimeNormalized, e.zonedDateTimeNormalized, e.offsetDateTimeNormalizedUtc, e.zonedDateTimeNormalizedUtc from TimeZoneStorageEntity e", "select " +
"e.offsetTimeNormalized, " +
"e.offsetDateTimeNormalized, " +
"e.zonedDateTimeNormalized, " +
"e.offsetTimeNormalizedUtc, " +
"e.offsetDateTimeNormalizedUtc, " +
"e.zonedDateTimeNormalizedUtc " +
"from TimeZoneStorageEntity e",
Tuple.class Tuple.class
).getResultList(); ).getResultList();
assertThat( resultList.get( 0 ).get( 0, OffsetDateTime.class ).toInstant(), Matchers.is( OFFSET_DATE_TIME.toInstant() ) ); assertThat( resultList.get( 0 ).get( 0, OffsetTime.class ).toLocalTime(), Matchers.is( OFFSET_TIME.withOffsetSameInstant( JVM_TIMEZONE_OFFSET ).toLocalTime() ) );
assertThat( resultList.get( 0 ).get( 1, ZonedDateTime.class ).toInstant(), Matchers.is( ZONED_DATE_TIME.toInstant() ) ); assertThat( resultList.get( 0 ).get( 0, OffsetTime.class ).getOffset(), Matchers.is( JVM_TIMEZONE_OFFSET ) );
assertThat( resultList.get( 0 ).get( 2, OffsetDateTime.class ).toInstant(), Matchers.is( OFFSET_DATE_TIME.toInstant() ) ); assertThat( resultList.get( 0 ).get( 1, OffsetDateTime.class ).toInstant(), Matchers.is( OFFSET_DATE_TIME.toInstant() ) );
assertThat( resultList.get( 0 ).get( 3, ZonedDateTime.class ).toInstant(), Matchers.is( ZONED_DATE_TIME.toInstant() ) ); assertThat( resultList.get( 0 ).get( 2, ZonedDateTime.class ).toInstant(), Matchers.is( ZONED_DATE_TIME.toInstant() ) );
assertThat( resultList.get( 0 ).get( 3, OffsetTime.class ).toLocalTime(), Matchers.is( OFFSET_TIME.withOffsetSameInstant( ZoneOffset.UTC ).toLocalTime() ) );
assertThat( resultList.get( 0 ).get( 3, OffsetTime.class ).getOffset(), Matchers.is( ZoneOffset.UTC ) );
assertThat( resultList.get( 0 ).get( 4, OffsetDateTime.class ).toInstant(), Matchers.is( OFFSET_DATE_TIME.toInstant() ) );
assertThat( resultList.get( 0 ).get( 5, ZonedDateTime.class ).toInstant(), Matchers.is( ZONED_DATE_TIME.toInstant() ) );
} }
); );
} }
@Test @Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTimezoneTypes.class) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTimezoneTypes.class, comment = "Extracting the offset usually only makes sense if the temporal retains the offset. On DBs that have native TZ support we test this anyway to make sure it's not broken'")
public void testNormalizeOffset(SessionFactoryScope scope) { public void testNormalizeOffset(SessionFactoryScope scope) {
scope.inSession( scope.inSession(
session -> { session -> {
List<Tuple> resultList = session.createQuery( List<Tuple> resultList = session.createQuery(
"select extract(offset from e.offsetDateTimeNormalizedUtc), extract(offset from e.zonedDateTimeNormalizedUtc) from TimeZoneStorageEntity e", "select " +
"extract(offset from e.offsetTimeNormalizedUtc), " +
"extract(offset from e.offsetDateTimeNormalizedUtc), " +
"extract(offset from e.zonedDateTimeNormalizedUtc) " +
"from TimeZoneStorageEntity e",
Tuple.class Tuple.class
).getResultList(); ).getResultList();
assertThat( resultList.get( 0 ).get( 0, ZoneOffset.class ), Matchers.is( ZoneOffset.systemDefault().getRules().getOffset( OFFSET_DATE_TIME.toInstant() ) ) ); if ( !( scope.getSessionFactory().getJdbcServices().getDialect() instanceof H2Dialect) ) {
// H2 bug: https://github.com/h2database/h2database/issues/3757
assertThat( resultList.get( 0 ).get( 0, ZoneOffset.class ), Matchers.is( ZoneOffset.UTC ) );
}
assertThat( resultList.get( 0 ).get( 1, ZoneOffset.class ), Matchers.is( ZoneOffset.UTC ) ); assertThat( resultList.get( 0 ).get( 1, ZoneOffset.class ), Matchers.is( ZoneOffset.UTC ) );
assertThat( resultList.get( 0 ).get( 2, ZoneOffset.class ), Matchers.is( ZoneOffset.UTC ) );
} }
); );
} }
@ -189,6 +243,11 @@ public class TimeZoneStorageMappingTests {
private Integer id; private Integer id;
//tag::time-zone-column-examples-mapping-example[] //tag::time-zone-column-examples-mapping-example[]
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthtime_offset_offset")
@Column(name = "birthtime_offset")
private OffsetTime offsetTimeColumn;
@TimeZoneStorage(TimeZoneStorageType.COLUMN) @TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthday_offset_offset") @TimeZoneColumn(name = "birthday_offset_offset")
@Column(name = "birthday_offset") @Column(name = "birthday_offset")
@ -200,6 +259,10 @@ public class TimeZoneStorageMappingTests {
private ZonedDateTime zonedDateTimeColumn; private ZonedDateTime zonedDateTimeColumn;
//end::time-zone-column-examples-mapping-example[] //end::time-zone-column-examples-mapping-example[]
@TimeZoneStorage
@Column(name = "birthtime_offset_auto")
private OffsetTime offsetTimeAuto;
@TimeZoneStorage @TimeZoneStorage
@Column(name = "birthday_offset_auto") @Column(name = "birthday_offset_auto")
private OffsetDateTime offsetDateTimeAuto; private OffsetDateTime offsetDateTimeAuto;
@ -208,6 +271,10 @@ public class TimeZoneStorageMappingTests {
@Column(name = "birthday_zoned_auto") @Column(name = "birthday_zoned_auto")
private ZonedDateTime zonedDateTimeAuto; private ZonedDateTime zonedDateTimeAuto;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthtime_offset_normalized")
private OffsetTime offsetTimeNormalized;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE) @TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthday_offset_normalized") @Column(name = "birthday_offset_normalized")
private OffsetDateTime offsetDateTimeNormalized; private OffsetDateTime offsetDateTimeNormalized;
@ -216,6 +283,10 @@ public class TimeZoneStorageMappingTests {
@Column(name = "birthday_zoned_normalized") @Column(name = "birthday_zoned_normalized")
private ZonedDateTime zonedDateTimeNormalized; private ZonedDateTime zonedDateTimeNormalized;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthtime_offset_normalized_utc")
private OffsetTime offsetTimeNormalizedUtc;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC) @TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthday_offset_normalized_utc") @Column(name = "birthday_offset_normalized_utc")
private OffsetDateTime offsetDateTimeNormalizedUtc; private OffsetDateTime offsetDateTimeNormalizedUtc;
@ -227,14 +298,18 @@ public class TimeZoneStorageMappingTests {
public TimeZoneStorageEntity() { public TimeZoneStorageEntity() {
} }
public TimeZoneStorageEntity(Integer id, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) { public TimeZoneStorageEntity(Integer id, OffsetTime offsetTime, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) {
this.id = id; this.id = id;
this.offsetTimeColumn = offsetTime;
this.offsetDateTimeColumn = offsetDateTime; this.offsetDateTimeColumn = offsetDateTime;
this.zonedDateTimeColumn = zonedDateTime; this.zonedDateTimeColumn = zonedDateTime;
this.offsetTimeAuto = offsetTime;
this.offsetDateTimeAuto = offsetDateTime; this.offsetDateTimeAuto = offsetDateTime;
this.zonedDateTimeAuto = zonedDateTime; this.zonedDateTimeAuto = zonedDateTime;
this.offsetTimeNormalized = offsetTime;
this.offsetDateTimeNormalized = offsetDateTime; this.offsetDateTimeNormalized = offsetDateTime;
this.zonedDateTimeNormalized = zonedDateTime; this.zonedDateTimeNormalized = zonedDateTime;
this.offsetTimeNormalizedUtc = offsetTime;
this.offsetDateTimeNormalizedUtc = offsetDateTime; this.offsetDateTimeNormalizedUtc = offsetDateTime;
this.zonedDateTimeNormalizedUtc = zonedDateTime; this.zonedDateTimeNormalizedUtc = zonedDateTime;
} }

View File

@ -54,6 +54,7 @@ import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN; import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
@ -83,6 +84,7 @@ public class CUBRIDDialect extends Dialect {
//(always 3, millisecond precision) //(always 3, millisecond precision)
case TIMESTAMP: case TIMESTAMP:
return "datetime"; return "datetime";
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "datetimetz"; return "datetimetz";
default: default:

View File

@ -72,7 +72,7 @@ import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
@ -195,6 +195,10 @@ public class CockroachLegacyDialect extends Dialect {
case BLOB: case BLOB:
return "bytes"; return "bytes";
// We do not use the time with timezone type because PG deprecated it and it lacks certain operations like subtraction
// case TIME_UTC:
// return columnType( TIME_WITH_TIMEZONE );
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return columnType( TIMESTAMP_WITH_TIMEZONE ); return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -269,6 +273,12 @@ public class CockroachLegacyDialect extends Dialect {
break; break;
} }
break; break;
case TIME:
// The PostgreSQL JDBC driver reports TIME for timetz, but we use it only for mapping OffsetTime to UTC
if ( "timetz".equals( columnTypeName ) ) {
jdbcTypeCode = TIME_UTC;
}
break;
case TIMESTAMP: case TIMESTAMP:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant // The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) { if ( "timestamptz".equals( columnTypeName ) ) {
@ -324,7 +334,7 @@ public class CockroachLegacyDialect extends Dialect {
protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry(); .getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) { if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
if ( PgJdbcHelper.isUsable( serviceRegistry ) ) { if ( PgJdbcHelper.isUsable( serviceRegistry ) ) {
@ -423,7 +433,13 @@ public class CockroachLegacyDialect extends Dialect {
functionContributions.getFunctionRegistry().register( functionContributions.getFunctionRegistry().register(
"format", "format",
new FormatFunction( "experimental_strftime", functionContributions.getTypeConfiguration() ) new FormatFunction(
"experimental_strftime",
false,
true,
false,
functionContributions.getTypeConfiguration()
)
); );
functionFactory.windowFunctions(); functionFactory.windowFunctions();
functionFactory.listagg_stringAgg( "string" ); functionFactory.listagg_stringAgg( "string" );
@ -757,22 +773,30 @@ public class CockroachLegacyDialect extends Dialect {
if ( unit == null ) { if ( unit == null ) {
return "(?3-?2)"; return "(?3-?2)";
} }
switch (unit) { if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
case YEAR:
return "(extract(year from ?3)-extract(year from ?2))";
case QUARTER:
return "(extract(year from ?3)*4-extract(year from ?2)*4+extract(month from ?3)//3-extract(month from ?2)//3)";
case MONTH:
return "(extract(year from ?3)*12-extract(year from ?2)*12+extract(month from ?3)-extract(month from ?2))";
}
if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP ) {
// special case: subtraction of two dates // special case: subtraction of two dates
// results in an integer number of days // results in an integer number of days
// instead of an INTERVAL // instead of an INTERVAL
return "(?3-?2)" + DAY.conversionFactor( unit, this ); switch ( unit ) {
case YEAR:
case MONTH:
case QUARTER:
// age only supports timestamptz, so we have to cast the date expressions
return "extract(" + translateDurationField( unit ) + " from age(cast(?3 as timestamptz),cast(?2 as timestamptz)))";
default:
return "(?3-?2)" + DAY.conversionFactor( unit, this );
}
} }
else { else {
switch (unit) { switch (unit) {
case YEAR:
return "extract(year from ?3-?2)";
case QUARTER:
return "(extract(year from ?3-?2)*4+extract(month from ?3-?2)//3)";
case MONTH:
return "(extract(year from ?3-?2)*12+extract(month from ?3-?2))";
// Prior to v20, Cockroach didn't support extracting from an interval/duration,
// so we use the extract_duration function
case WEEK: case WEEK:
return "extract_duration(hour from ?3-?2)/168"; return "extract_duration(hour from ?3-?2)/168";
case DAY: case DAY:

View File

@ -11,7 +11,11 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.FunctionContributions;
@ -96,11 +100,16 @@ import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DECIMAL; import static org.hibernate.type.SqlTypes.DECIMAL;
import static org.hibernate.type.SqlTypes.NUMERIC; import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.SQLXML; import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos;
/** /**
* A {@linkplain Dialect SQL dialect} for DB2. * A {@linkplain Dialect SQL dialect} for DB2.
@ -187,6 +196,7 @@ public class DB2LegacyDialect extends Dialect {
return "clob"; return "clob";
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p)"; return "timestamp($p)";
case TIME:
case TIME_WITH_TIMEZONE: case TIME_WITH_TIMEZONE:
return "time"; return "time";
case BINARY: case BINARY:
@ -416,9 +426,37 @@ public class DB2LegacyDialect extends Dialect {
if ( getDB2Version().isBefore( 11 ) ) { if ( getDB2Version().isBefore( 11 ) ) {
return DB2Dialect.timestampdiffPatternV10( unit, fromTemporalType, toTemporalType ); return DB2Dialect.timestampdiffPatternV10( unit, fromTemporalType, toTemporalType );
} }
StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit(); final String fromExpression;
boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit(); final String toExpression;
if ( unit.isDateUnit() ) {
fromExpression = "?2";
toExpression = "?3";
}
else {
switch ( fromTemporalType ) {
case DATE:
fromExpression = "cast(?2 as timestamp)";
break;
case TIME:
fromExpression = "timestamp('1970-01-01',?2)";
break;
default:
fromExpression = "?2";
break;
}
switch ( toTemporalType ) {
case DATE:
toExpression = "cast(?3 as timestamp)";
break;
case TIME:
toExpression = "timestamp('1970-01-01',?3)";
break;
default:
toExpression = "?3";
break;
}
}
switch ( unit ) { switch ( unit ) {
case NATIVE: case NATIVE:
case NANOSECOND: case NANOSECOND:
@ -434,26 +472,24 @@ public class DB2LegacyDialect extends Dialect {
default: default:
pattern.append( "?1s_between(" ); pattern.append( "?1s_between(" );
} }
if ( castTo ) { pattern.append( toExpression );
pattern.append( "cast(?3 as timestamp)" );
}
else {
pattern.append( "?3" );
}
pattern.append( ',' ); pattern.append( ',' );
if ( castFrom ) { pattern.append( fromExpression );
pattern.append( "cast(?2 as timestamp)" );
}
else {
pattern.append( "?2" );
}
pattern.append( ')' ); pattern.append( ')' );
switch ( unit ) { switch ( unit ) {
case NATIVE: case NATIVE:
pattern.append( "+(microsecond(?3)-microsecond(?2))/1e6)" ); pattern.append( "+(microsecond(");
pattern.append( toExpression );
pattern.append(")-microsecond(");
pattern.append( fromExpression );
pattern.append("))/1e6)" );
break; break;
case NANOSECOND: case NANOSECOND:
pattern.append( "*1e9+(microsecond(?3)-microsecond(?2))*1e3)" ); pattern.append( "*1e9+(microsecond(");
pattern.append( toExpression );
pattern.append(")-microsecond(");
pattern.append( fromExpression );
pattern.append("))*1e3)" );
break; break;
case MONTH: case MONTH:
pattern.append( ')' ); pattern.append( ')' );
@ -468,19 +504,24 @@ public class DB2LegacyDialect extends Dialect {
@Override @Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
final StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
final boolean castTo; final String timestampExpression;
if ( unit.isDateUnit() ) { if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME; if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
} }
else { else {
castTo = temporalType == TemporalType.DATE; if ( temporalType == TemporalType.DATE ) {
} timestampExpression = "cast(?3 as timestamp)";
if (castTo) { }
pattern.append("cast(?3 as timestamp)"); else {
} timestampExpression = "?3";
else { }
pattern.append("?3");
} }
pattern.append(timestampExpression);
pattern.append("+("); pattern.append("+(");
// DB2 supports temporal arithmetic. See https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.sql.ref.doc/doc/r0023457.html // DB2 supports temporal arithmetic. See https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.sql.ref.doc/doc/r0023457.html
switch (unit) { switch (unit) {
@ -503,6 +544,83 @@ public class DB2LegacyDialect extends Dialect {
return pattern.toString(); return pattern.toString();
} }
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
TemporalAccessor temporalAccessor,
TemporalType precision,
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "date '" );
appendAsDate( appender, temporalAccessor );
appender.appendSql( '\'' );
break;
case TIME:
appender.appendSql( "time '" );
appendAsLocalTime( appender, temporalAccessor );
appender.appendSql( '\'' );
break;
case TIMESTAMP:
appender.appendSql( "timestamp '" );
appendAsTimestampWithNanos( appender, temporalAccessor, false, jdbcTimeZone );
appender.appendSql( '\'' );
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType precision, TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "date '" );
appendAsDate( appender, date );
appender.appendSql( '\'' );
break;
case TIME:
appender.appendSql( "time '" );
appendAsLocalTime( appender, date );
appender.appendSql( '\'' );
break;
case TIMESTAMP:
appender.appendSql( "timestamp '" );
appendAsTimestampWithNanos( appender, date, jdbcTimeZone );
appender.appendSql( '\'' );
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
Calendar calendar,
TemporalType precision,
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "date '" );
appendAsDate( appender, calendar );
appender.appendSql( '\'' );
break;
case TIME:
appender.appendSql( "time '" );
appendAsLocalTime( appender, calendar );
appender.appendSql( '\'' );
break;
case TIMESTAMP:
appender.appendSql( "timestamp '" );
appendAsTimestampWithMillis( appender, calendar, jdbcTimeZone );
appender.appendSql( '\'' );
break;
default:
throw new IllegalArgumentException();
}
}
@Override @Override
public String getLowercaseFunction() { public String getLowercaseFunction() {
return getDB2Version().isBefore( 9, 7 ) ? "lcase" : super.getLowercaseFunction(); return getDB2Version().isBefore( 9, 7 ) ? "lcase" : super.getLowercaseFunction();
@ -894,6 +1012,12 @@ public class DB2LegacyDialect extends Dialect {
return "dayofweek(?2)"; return "dayofweek(?2)";
case QUARTER: case QUARTER:
return "quarter(?2)"; return "quarter(?2)";
case EPOCH:
if ( getDB2Version().isBefore( 11 ) ) {
return timestampdiffPattern( TemporalUnit.SECOND, TemporalType.TIMESTAMP, TemporalType.TIMESTAMP )
.replace( "?2", "'1970-01-01 00:00:00'" )
.replace( "?3", "?2" );
}
} }
return super.extractPattern( unit ); return super.extractPattern( unit );
} }
@ -939,4 +1063,20 @@ public class DB2LegacyDialect extends Dialect {
public String getCreateUserDefinedTypeExtensionsString() { public String getCreateUserDefinedTypeExtensionsString() {
return " instantiable mode db2sql"; return " instantiable mode db2sql";
} }
/**
* The more "standard" syntax is {@code rid_bit(alias)} but here we use {@code alias.rowid}.
* <p>
* There is also an alternative {@code rid()} of type {@code bigint}, but it cannot be used
* with partitioning.
*/
@Override
public String rowId(String rowId) {
return "rowid";
}
@Override
public int rowIdSqlType() {
return VARBINARY;
}
} }

View File

@ -37,7 +37,9 @@ import jakarta.persistence.TemporalType;
import java.util.List; import java.util.List;
import static org.hibernate.type.SqlTypes.ROWID;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
/** /**
* An SQL dialect for DB2 for z/OS, previously known as known as Db2 UDB for z/OS and Db2 UDB for z/OS and OS/390. * An SQL dialect for DB2 for z/OS, previously known as known as Db2 UDB for z/OS and Db2 UDB for z/OS and OS/390.
@ -74,9 +76,13 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
@Override @Override
protected String columnType(int sqlTypeCode) { protected String columnType(int sqlTypeCode) {
if ( sqlTypeCode == TIMESTAMP_WITH_TIMEZONE && getVersion().isAfter( 10 ) ) { if ( getVersion().isAfter( 10 ) ) {
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html switch ( sqlTypeCode ) {
return "timestamp with time zone"; case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html
return "timestamp with time zone";
}
} }
return super.columnType( sqlTypeCode ); return super.columnType( sqlTypeCode );
} }
@ -160,14 +166,7 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
@Override @Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
final boolean castTo;
if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME;
}
else {
castTo = temporalType == TemporalType.DATE;
}
pattern.append("add_"); pattern.append("add_");
switch (unit) { switch (unit) {
case NATIVE: case NATIVE:
@ -185,12 +184,24 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
pattern.append("?1"); pattern.append("?1");
} }
pattern.append("s("); pattern.append("s(");
if (castTo) { final String timestampExpression;
pattern.append("cast(?3 as timestamp)"); if ( unit.isDateUnit() ) {
if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
} }
else { else {
pattern.append("?3"); if ( temporalType == TemporalType.DATE ) {
timestampExpression = "cast(?3 as timestamp)";
}
else {
timestampExpression = "?3";
}
} }
pattern.append(timestampExpression);
pattern.append(","); pattern.append(",");
switch (unit) { switch (unit) {
case NANOSECOND: case NANOSECOND:
@ -219,4 +230,23 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
} }
}; };
} }
// I speculate that this is a correct implementation of rowids for DB2 for z/OS,
// just on the basis of the DB2 docs, but I currently have no way to test it
// Note that the implementation inherited from DB2Dialect for LUW will not work!
@Override
public String rowId(String rowId) {
return rowId.isEmpty() ? "rowid_" : rowId;
}
@Override
public int rowIdSqlType() {
return ROWID;
}
@Override
public String getRowIdColumnString(String rowId) {
return rowId( rowId ) + " rowid not null generated always";
}
} }

View File

@ -92,8 +92,10 @@ import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NCLOB; import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NUMERIC; import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
@ -162,6 +164,10 @@ public class DerbyLegacyDialect extends Dialect {
case NCLOB: case NCLOB:
return "clob"; return "clob";
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case TIMESTAMP: case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp"; return "timestamp";
@ -383,6 +389,8 @@ public class DerbyLegacyDialect extends Dialect {
return "(({fn timestampdiff(sql_tsi_day,date(char(year(?2),4)||'-01-01'),{fn timestampadd(sql_tsi_day,{fn timestampdiff(sql_tsi_day,{d '1753-01-01'},?2)}/7*7,{d '1753-01-04'})})}+7)/7)"; return "(({fn timestampdiff(sql_tsi_day,date(char(year(?2),4)||'-01-01'),{fn timestampadd(sql_tsi_day,{fn timestampdiff(sql_tsi_day,{d '1753-01-01'},?2)}/7*7,{d '1753-01-04'})})}+7)/7)";
case QUARTER: case QUARTER:
return "((month(?2)+2)/3)"; return "((month(?2)+2)/3)";
case EPOCH:
return "{fn timestampdiff(sql_tsi_second,{ts '1970-01-01 00:00:00'},?2)}";
default: default:
return "?1(?2)"; return "?1(?2)";
} }

View File

@ -150,7 +150,7 @@ public class FirebirdDialect extends Dialect {
case TIMESTAMP: case TIMESTAMP:
return "timestamp"; return "timestamp";
case TIME_WITH_TIMEZONE: case TIME_WITH_TIMEZONE:
return getVersion().isBefore( 4, 0 ) ? "time" : super.columnType( sqlTypeCode ); return getVersion().isBefore( 4, 0 ) ? "time" : "time with time zone";
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return getVersion().isBefore( 4, 0 ) ? "timestamp" : "timestamp with time zone"; return getVersion().isBefore( 4, 0 ) ? "timestamp" : "timestamp with time zone";
case BINARY: case BINARY:

View File

@ -76,8 +76,11 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLe
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType; import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.TimeUtcAsJdbcTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampUtcAsInstantJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
@ -88,6 +91,7 @@ import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.TemporalUnit.SECOND; import static org.hibernate.query.sqm.TemporalUnit.SECOND;
import static org.hibernate.type.SqlTypes.ARRAY; import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY; import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.CHAR; import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.DECIMAL; import static org.hibernate.type.SqlTypes.DECIMAL;
@ -104,6 +108,8 @@ import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.OTHER; import static org.hibernate.type.SqlTypes.OTHER;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
@ -112,7 +118,6 @@ import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
/** /**
* A legacy {@linkplain Dialect SQL dialect} for H2. * A legacy {@linkplain Dialect SQL dialect} for H2.
@ -208,6 +213,9 @@ public class H2LegacyDialect extends Dialect {
// which caused problems for schema update tool // which caused problems for schema update tool
case NUMERIC: case NUMERIC:
return getVersion().isBefore( 2 ) ? columnType( DECIMAL ) : super.columnType( sqlTypeCode ); return getVersion().isBefore( 2 ) ? columnType( DECIMAL ) : super.columnType( sqlTypeCode );
// Support was only added in 2.0
case TIME_WITH_TIMEZONE:
return getVersion().isBefore( 2 ) ? columnType( TIMESTAMP_WITH_TIMEZONE ) : super.columnType( sqlTypeCode );
case NCHAR: case NCHAR:
return columnType( CHAR ); return columnType( CHAR );
case NVARCHAR: case NVARCHAR:
@ -263,7 +271,15 @@ public class H2LegacyDialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry(); .getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantJdbcType.INSTANCE ); if ( getVersion().isBefore( 2 ) ) {
// Support for TIME_WITH_TIMEZONE was only added in 2.0
jdbcTypeRegistry.addDescriptor( TIME_WITH_TIMEZONE, TimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimeUtcAsJdbcTimeJdbcType.INSTANCE );
}
else {
jdbcTypeRegistry.addDescriptor( TimeUtcAsOffsetTimeJdbcType.INSTANCE );
}
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsInstantJdbcType.INSTANCE );
if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) { if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
} }
@ -878,4 +894,14 @@ public class H2LegacyDialect extends Dialect {
public UniqueDelegate getUniqueDelegate() { public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate; return uniqueDelegate;
} }
@Override
public String rowId(String rowId) {
return "_rowid_";
}
@Override
public int rowIdSqlType() {
return BIGINT;
}
} }

View File

@ -11,7 +11,10 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -135,6 +138,7 @@ import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos;
/** /**
* A {@linkplain Dialect SQL dialect} for Oracle 8i and above. * A {@linkplain Dialect SQL dialect} for Oracle 8i and above.
@ -153,6 +157,13 @@ public class OracleLegacyDialect extends Dialect {
public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw"; public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw";
private static final String yqmSelect =
"( TRUNC(%2$s, 'MONTH') + NUMTOYMINTERVAL(%1$s, 'MONTH') + ( LEAST( EXTRACT( DAY FROM %2$s ), EXTRACT( DAY FROM LAST_DAY( TRUNC(%2$s, 'MONTH') + NUMTOYMINTERVAL(%1$s, 'MONTH') ) ) ) - 1 ) )";
private static final String ADD_YEAR_EXPRESSION = String.format( yqmSelect, "?2*12", "?3" );
private static final String ADD_QUARTER_EXPRESSION = String.format( yqmSelect, "?2*3", "?3" );
private static final String ADD_MONTH_EXPRESSION = String.format( yqmSelect, "?2", "?3" );
private final LimitHandler limitHandler = supportsFetchClause( FetchClauseType.ROWS_ONLY ) private final LimitHandler limitHandler = supportsFetchClause( FetchClauseType.ROWS_ONLY )
? Oracle12LimitHandler.INSTANCE ? Oracle12LimitHandler.INSTANCE
: new LegacyOracleLimitHandler( getVersion() ); : new LegacyOracleLimitHandler( getVersion() );
@ -316,6 +327,11 @@ public class OracleLegacyDialect extends Dialect {
return getVersion().isBefore( 9 ) ? currentTimestamp() : "current_timestamp"; return getVersion().isBefore( 9 ) ? currentTimestamp() : "current_timestamp";
} }
@Override
public boolean supportsInsertReturningGeneratedKeys() {
return getVersion().isSameOrAfter( 12 );
}
/** /**
* Oracle doesn't have any sort of {@link Types#BOOLEAN} * Oracle doesn't have any sort of {@link Types#BOOLEAN}
@ -448,6 +464,8 @@ public class OracleLegacyDialect extends Dialect {
return "to_number(to_char(?2,'MI'))"; return "to_number(to_char(?2,'MI'))";
case SECOND: case SECOND:
return "to_number(to_char(?2,'SS'))"; return "to_number(to_char(?2,'SS'))";
case EPOCH:
return "trunc((cast(?2 at time zone 'UTC' as date) - date '1970-1-1')*86400)";
default: default:
return super.extractPattern(unit); return super.extractPattern(unit);
} }
@ -455,150 +473,119 @@ public class OracleLegacyDialect extends Dialect {
@Override @Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder(); StringBuilder pattern = new StringBuilder();
pattern.append("(?3+");
switch ( unit ) { switch ( unit ) {
case YEAR: case YEAR:
pattern.append( ADD_YEAR_EXPRESSION );
break;
case QUARTER: case QUARTER:
pattern.append( ADD_QUARTER_EXPRESSION );
break;
case MONTH: case MONTH:
pattern.append("numtoyminterval"); pattern.append( ADD_MONTH_EXPRESSION );
break; break;
case WEEK: case WEEK:
pattern.append("(?3+numtodsinterval((?2)*7,'day'))");
break;
case DAY: case DAY:
case HOUR: case HOUR:
case MINUTE: case MINUTE:
case SECOND: case SECOND:
pattern.append("(?3+numtodsinterval(?2,'?1'))");
break;
case NANOSECOND: case NANOSECOND:
pattern.append("(?3+numtodsinterval((?2)/1e9,'second'))");
break;
case NATIVE: case NATIVE:
pattern.append("numtodsinterval"); pattern.append("(?3+numtodsinterval(?2,'second'))");
break; break;
default: default:
throw new SemanticException(unit + " is not a legal field"); throw new SemanticException(unit + " is not a legal field");
} }
pattern.append("(");
switch ( unit ) {
case NANOSECOND:
case QUARTER:
case WEEK:
pattern.append("(");
break;
}
pattern.append("?2");
switch ( unit ) {
case QUARTER:
pattern.append(")*3");
break;
case WEEK:
pattern.append(")*7");
break;
case NANOSECOND:
pattern.append(")/1e9");
break;
}
pattern.append(",'");
switch ( unit ) {
case QUARTER:
pattern.append("month");
break;
case WEEK:
pattern.append("day");
break;
case NANOSECOND:
case NATIVE:
pattern.append("second");
break;
default:
pattern.append("?1");
}
pattern.append("')");
pattern.append(")");
return pattern.toString(); return pattern.toString();
} }
@Override @Override
public String timestampdiffPattern( public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
TemporalUnit unit, final StringBuilder pattern = new StringBuilder();
TemporalType fromTemporalType, TemporalType toTemporalType) { final boolean hasTimePart = toTemporalType != TemporalType.DATE || fromTemporalType != TemporalType.DATE;
StringBuilder pattern = new StringBuilder(); switch ( unit ) {
boolean timestamp = toTemporalType == TemporalType.TIMESTAMP || fromTemporalType == TemporalType.TIMESTAMP;
switch (unit) {
case YEAR: case YEAR:
extractField(pattern, YEAR, unit); extractField( pattern, YEAR, unit );
break; break;
case QUARTER: case QUARTER:
case MONTH: case MONTH:
pattern.append("("); pattern.append( "(" );
extractField(pattern, YEAR, unit); extractField( pattern, YEAR, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, MONTH, unit); extractField( pattern, MONTH, unit );
pattern.append(")"); pattern.append( ")" );
break; break;
case WEEK: case WEEK:
case DAY: case DAY:
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
break; break;
case HOUR: case HOUR:
pattern.append("("); pattern.append( "(" );
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
if (timestamp) { if ( hasTimePart ) {
pattern.append("+"); pattern.append( "+" );
extractField(pattern, HOUR, unit); extractField( pattern, HOUR, unit );
} }
pattern.append(")"); pattern.append( ")" );
break; break;
case MINUTE: case MINUTE:
pattern.append("("); pattern.append( "(" );
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
if (timestamp) { if ( hasTimePart ) {
pattern.append("+"); pattern.append( "+" );
extractField(pattern, HOUR, unit); extractField( pattern, HOUR, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, MINUTE, unit); extractField( pattern, MINUTE, unit );
} }
pattern.append(")"); pattern.append( ")" );
break; break;
case NATIVE: case NATIVE:
case NANOSECOND: case NANOSECOND:
case SECOND: case SECOND:
pattern.append("("); pattern.append( "(" );
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
if (timestamp) { if ( hasTimePart ) {
pattern.append("+"); pattern.append( "+" );
extractField(pattern, HOUR, unit); extractField( pattern, HOUR, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, MINUTE, unit); extractField( pattern, MINUTE, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, SECOND, unit); extractField( pattern, SECOND, unit );
} }
pattern.append(")"); pattern.append( ")" );
break; break;
default: default:
throw new SemanticException("unrecognized field: " + unit); throw new SemanticException( "unrecognized field: " + unit );
} }
return pattern.toString(); return pattern.toString();
} }
private void extractField( private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit toUnit) {
StringBuilder pattern, pattern.append( "extract(" );
TemporalUnit unit, TemporalUnit toUnit) { pattern.append( translateExtractField( unit ) );
pattern.append("extract("); pattern.append( " from (?3-?2) " );
pattern.append( translateExtractField(unit) ); switch ( unit ) {
pattern.append(" from (?3-?2) ");
switch (unit) {
case YEAR: case YEAR:
case MONTH: case MONTH:
pattern.append("year to month"); pattern.append( "year to month" );
break; break;
case DAY: case DAY:
case HOUR: case HOUR:
case MINUTE: case MINUTE:
case SECOND: case SECOND:
pattern.append("day to second"); pattern.append( "day to second" );
break; break;
default: default:
throw new SemanticException(unit + " is not a legal field"); throw new SemanticException( unit + " is not a legal field" );
} }
pattern.append(")"); pattern.append( ")" );
pattern.append( unit.conversionFactor( toUnit, this ) ); pattern.append( unit.conversionFactor( toUnit, this ) );
} }
@ -627,8 +614,9 @@ public class OracleLegacyDialect extends Dialect {
return "number($p,$s)"; return "number($p,$s)";
case DATE: case DATE:
case TIME:
return "date"; return "date";
case TIME:
return getVersion().isBefore( 9 ) ? "date" : super.columnType( sqlTypeCode );
case TIMESTAMP: case TIMESTAMP:
// the only difference between date and timestamp // the only difference between date and timestamp
// on Oracle is that date has no fractional seconds // on Oracle is that date has no fractional seconds
@ -811,7 +799,7 @@ public class OracleLegacyDialect extends Dialect {
typeContributions.contributeJdbcType( OracleJdbcHelper.getArrayJdbcType( serviceRegistry ) ); typeContributions.contributeJdbcType( OracleJdbcHelper.getArrayJdbcType( serviceRegistry ) );
} }
else { else {
typeContributions.contributeJdbcType( ArrayJdbcType.INSTANCE ); typeContributions.contributeJdbcType( OracleReflectionStructJdbcType.INSTANCE );
} }
// Oracle requires a custom binder for binding untyped nulls with the NULL type // Oracle requires a custom binder for binding untyped nulls with the NULL type
typeContributions.contributeJdbcType( NullJdbcType.INSTANCE ); typeContributions.contributeJdbcType( NullJdbcType.INSTANCE );
@ -1107,24 +1095,15 @@ public class OracleLegacyDialect extends Dialect {
@Override @Override
public String getQueryHintString(String sql, String hints) { public String getQueryHintString(String sql, String hints) {
String statementType = statementType(sql); final String statementType = statementType( sql );
final int start = sql.indexOf( statementType );
final int pos = sql.indexOf( statementType ); if ( start < 0 ) {
if ( pos > -1 ) { return sql;
final StringBuilder buffer = new StringBuilder( sql.length() + hints.length() + 8 ); }
if ( pos > 0 ) { else {
buffer.append( sql, 0, pos ); int end = start + statementType.length();
} return sql.substring( 0, end ) + " /*+ " + hints + " */" + sql.substring( end );
buffer
.append( statementType )
.append( " /*+ " )
.append( hints )
.append( " */" )
.append( sql.substring( pos + statementType.length() ) );
sql = buffer.toString();
} }
return sql;
} }
@Override @Override
@ -1161,14 +1140,15 @@ public class OracleLegacyDialect extends Dialect {
return true; return true;
} }
private String statementType(String sql) {
Matcher matcher = SQL_STATEMENT_TYPE_PATTERN.matcher( sql );
private String statementType(String sql) {
final Matcher matcher = SQL_STATEMENT_TYPE_PATTERN.matcher( sql );
if ( matcher.matches() && matcher.groupCount() == 1 ) { if ( matcher.matches() && matcher.groupCount() == 1 ) {
return matcher.group(1); return matcher.group(1);
} }
else {
throw new IllegalArgumentException( "Can't determine SQL statement type for statement: " + sql ); throw new IllegalArgumentException( "Can't determine SQL statement type for statement: " + sql );
}
} }
@Override @Override
@ -1276,6 +1256,32 @@ public class OracleLegacyDialect extends Dialect {
return getWriteLockString( aliases, timeout ); return getWriteLockString( aliases, timeout );
} }
@Override
public boolean supportsTemporalLiteralOffset() {
// Oracle *does* support offsets, but only
// in the ANSI syntax, not in the JDBC
// escape-based syntax, which we use in
// almost all circumstances (see below)
return false;
}
@Override
public void appendDateTimeLiteral(SqlAppender appender, TemporalAccessor temporalAccessor, TemporalType precision, TimeZone jdbcTimeZone) {
// we usually use the JDBC escape-based syntax
// because we want to let the JDBC driver handle
// TIME (a concept which does not exist in Oracle)
// but for the special case of timestamps with an
// offset we need to use the ANSI syntax
if ( precision == TemporalType.TIMESTAMP && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) {
appender.appendSql( "timestamp '" );
appendAsTimestampWithNanos( appender, temporalAccessor, true, jdbcTimeZone, false );
appender.appendSql( '\'' );
}
else {
super.appendDateTimeLiteral( appender, temporalAccessor, precision, jdbcTimeZone );
}
}
@Override @Override
public void appendDatetimeFormat(SqlAppender appender, String format) { public void appendDatetimeFormat(SqlAppender appender, String format) {
// Unlike other databases, Oracle requires an explicit reset for the fm modifier, // Unlike other databases, Oracle requires an explicit reset for the fm modifier,
@ -1432,4 +1438,9 @@ public class OracleLegacyDialect extends Dialect {
public String getCreateUserDefinedTypeKindString() { public String getCreateUserDefinedTypeKindString() {
return "object"; return "object";
} }
@Override
public String rowId(String rowId) {
return "rowid";
}
} }

View File

@ -91,7 +91,7 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
@ -130,9 +130,11 @@ import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.OTHER; import static org.hibernate.type.SqlTypes.OTHER;
import static org.hibernate.type.SqlTypes.SQLXML; import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.STRUCT; import static org.hibernate.type.SqlTypes.STRUCT;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_UTC;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
@ -209,6 +211,10 @@ public class PostgreSQLLegacyDialect extends Dialect {
case LONG32VARBINARY: case LONG32VARBINARY:
return "bytea"; return "bytea";
// We do not use the time with timezone type because PG deprecated it and it lacks certain operations like subtraction
// case TIME_UTC:
// return columnType( TIME_WITH_TIMEZONE );
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return columnType( TIMESTAMP_WITH_TIMEZONE ); return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -323,6 +329,12 @@ public class PostgreSQLLegacyDialect extends Dialect {
break; break;
} }
break; break;
case TIME:
// The PostgreSQL JDBC driver reports TIME for timetz, but we use it only for mapping OffsetTime to UTC
if ( "timetz".equals( columnTypeName ) ) {
jdbcTypeCode = TIME_UTC;
}
break;
case TIMESTAMP: case TIMESTAMP:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant // The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) { if ( "timestamptz".equals( columnTypeName ) ) {
@ -443,36 +455,31 @@ public class PostgreSQLLegacyDialect extends Dialect {
if ( unit == null ) { if ( unit == null ) {
return "(?3-?2)"; return "(?3-?2)";
} }
if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP && unit == DAY ) { if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
// special case: subtraction of two dates // special case: subtraction of two dates
// results in an integer number of days // results in an integer number of days
// instead of an INTERVAL // instead of an INTERVAL
return "(?3-?2)";
}
else {
StringBuilder pattern = new StringBuilder();
switch ( unit ) { switch ( unit ) {
case YEAR: case YEAR:
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit );
break;
case QUARTER:
pattern.append( "(" );
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit );
pattern.append( "+" );
extractField( pattern, QUARTER, fromTemporalType, toTemporalType, unit );
pattern.append( ")" );
break;
case MONTH: case MONTH:
pattern.append( "(" ); case QUARTER:
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit ); return "extract(" + translateDurationField( unit ) + " from age(?3,?2))";
pattern.append( "+" ); default:
extractField( pattern, MONTH, fromTemporalType, toTemporalType, unit ); return "(?3-?2)" + DAY.conversionFactor( unit, this );
pattern.append( ")" ); }
break; }
else {
switch ( unit ) {
case YEAR:
return "extract(year from ?3-?2)";
case QUARTER:
return "(extract(year from ?3-?2)*4+extract(month from ?3-?2)/3)";
case MONTH:
return "(extract(year from ?3-?2)*12+extract(month from ?3-?2))";
case WEEK: //week is not supported by extract() when the argument is a duration case WEEK: //week is not supported by extract() when the argument is a duration
return "(extract(day from ?3-?2)/7)";
case DAY: case DAY:
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit ); return "extract(day from ?3-?2)";
break;
//in order to avoid multiple calls to extract(), //in order to avoid multiple calls to extract(),
//we use extract(epoch from x - y) * factor for //we use extract(epoch from x - y) * factor for
//all the following units: //all the following units:
@ -481,15 +488,14 @@ public class PostgreSQLLegacyDialect extends Dialect {
case SECOND: case SECOND:
case NANOSECOND: case NANOSECOND:
case NATIVE: case NATIVE:
extractField( pattern, EPOCH, fromTemporalType, toTemporalType, unit ); return "extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this );
break;
default: default:
throw new SemanticException( "unrecognized field: " + unit ); throw new SemanticException( "unrecognized field: " + unit );
} }
return pattern.toString();
} }
} }
@Deprecated
protected void extractField( protected void extractField(
StringBuilder pattern, StringBuilder pattern,
TemporalUnit unit, TemporalUnit unit,
@ -499,7 +505,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
pattern.append( "extract(" ); pattern.append( "extract(" );
pattern.append( translateDurationField( unit ) ); pattern.append( translateDurationField( unit ) );
pattern.append( " from " ); pattern.append( " from " );
if ( toTimestamp != TemporalType.TIMESTAMP && fromTimestamp != TemporalType.TIMESTAMP ) { if ( toTimestamp == TemporalType.DATE && fromTimestamp == TemporalType.DATE ) {
// special case subtraction of two // special case subtraction of two
// dates results in an integer not // dates results in an integer not
// an Interval // an Interval
@ -1325,7 +1331,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
// dialect uses oid for Blobs, byte arrays cannot be used. // dialect uses oid for Blobs, byte arrays cannot be used.
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING ); jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING ); jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) { if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
@ -1411,23 +1417,13 @@ public class PostgreSQLLegacyDialect extends Dialect {
// disabled foreign key constraints still prevent 'truncate table' // disabled foreign key constraints still prevent 'truncate table'
// (these would help if we used 'delete' instead of 'truncate') // (these would help if we used 'delete' instead of 'truncate')
// @Override @Override
// public String getDisableConstraintsStatement() { public String rowId(String rowId) {
// return "set constraints all deferred"; return "ctid";
// } }
//
// @Override @Override
// public String getEnableConstraintsStatement() { public int rowIdSqlType() {
// return "set constraints all immediate"; return OTHER;
// } }
//
// @Override
// public String getDisableConstraintStatement(String tableName, String name) {
// return "alter table " + tableName + " alter constraint " + name + " deferrable";
// }
//
// @Override
// public String getEnableConstraintStatement(String tableName, String name) {
// return "alter table " + tableName + " alter constraint " + name + " deferrable";
// }
} }

View File

@ -20,8 +20,6 @@ import org.hibernate.query.sqm.TemporalUnit;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.TemporalUnit.DAY;
/** /**
* An SQL dialect for Postgres Plus * An SQL dialect for Postgres Plus
* *
@ -84,12 +82,10 @@ public class PostgresPlusLegacyDialect extends PostgreSQLLegacyDialect {
@Override @Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP && unit == DAY ) { if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
// special case: subtraction of two dates results in an INTERVAL on Postgres Plus // special case: subtraction of two dates results in an INTERVAL on Postgres Plus
// because there is no date type i.e. without time for Oracle compatibility // because there is no date type i.e. without time for Oracle compatibility
final StringBuilder pattern = new StringBuilder(); return super.timestampdiffPattern( unit, TemporalType.TIMESTAMP, TemporalType.TIMESTAMP );
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit );
return pattern.toString();
} }
return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType ); return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
} }

View File

@ -169,6 +169,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
return getVersion().isSameOrAfter( 10 ) ? "time" : super.columnType( sqlTypeCode ); return getVersion().isSameOrAfter( 10 ) ? "time" : super.columnType( sqlTypeCode );
case TIMESTAMP: case TIMESTAMP:
return getVersion().isSameOrAfter( 10 ) ? "datetime2($p)" : super.columnType( sqlTypeCode ); return getVersion().isSameOrAfter( 10 ) ? "datetime2($p)" : super.columnType( sqlTypeCode );
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return getVersion().isSameOrAfter( 10 ) ? "datetimeoffset($p)" : super.columnType( sqlTypeCode ); return getVersion().isSameOrAfter( 10 ) ? "datetimeoffset($p)" : super.columnType( sqlTypeCode );
} }
@ -780,6 +781,8 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
case SECOND: case SECOND:
//this should evaluate to a floating point type //this should evaluate to a floating point type
return "(datepart(second,?2)+datepart(nanosecond,?2)/1e9)"; return "(datepart(second,?2)+datepart(nanosecond,?2)/1e9)";
case EPOCH:
return "datediff_big(second, '1970-01-01', ?2)";
case WEEK: case WEEK:
// Thanks https://www.sqlservercentral.com/articles/a-simple-formula-to-calculate-the-iso-week-number // Thanks https://www.sqlservercentral.com/articles/a-simple-formula-to-calculate-the-iso-week-number
if ( getVersion().isBefore( 10 ) ) { if ( getVersion().isBefore( 10 ) ) {

View File

@ -37,6 +37,7 @@ import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.TIME; import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
/** /**
* SQL Dialect for Sybase/SQL Anywhere * SQL Dialect for Sybase/SQL Anywhere
@ -65,6 +66,7 @@ public class SybaseAnywhereDialect extends SybaseDialect {
return "time"; return "time";
case TIMESTAMP: case TIMESTAMP:
return "timestamp"; return "timestamp";
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp with time zone"; return "timestamp with time zone";

View File

@ -30,6 +30,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType; import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.hibernate.usertype.internal.OffsetTimeCompositeUserType;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -45,6 +46,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable; import jakarta.persistence.JoinTable;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.isOffsetTimeClass;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.useColumnForTimeZoneStorage; import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.useColumnForTimeZoneStorage;
/** /**
@ -484,11 +486,19 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
} }
} }
else if ( useColumnForTimeZoneStorage( element, context ) ) { else if ( useColumnForTimeZoneStorage( element, context ) ) {
final Column column = createTimestampColumn( element, path, context ); final Column column = createTemporalColumn( element, path, context );
columnOverride.put( if ( isOffsetTimeClass( element ) ) {
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME, columnOverride.put(
new Column[]{ column } path + "." + OffsetTimeCompositeUserType.LOCAL_TIME_NAME,
); new Column[] { column }
);
}
else {
columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
new Column[] { column }
);
}
final Column offsetColumn = createTimeZoneColumn( element, column ); final Column offsetColumn = createTimeZoneColumn( element, column );
columnOverride.put( columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME, path + "." + AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME,
@ -527,7 +537,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
} }
} }
private static Column createTimestampColumn(XAnnotatedElement element, String path, MetadataBuildingContext context) { private static Column createTemporalColumn(XAnnotatedElement element, String path, MetadataBuildingContext context) {
int precision; int precision;
final Column annotatedColumn = element.getAnnotation( Column.class ); final Column annotatedColumn = element.getAnnotation( Column.class );
if ( annotatedColumn != null ) { if ( annotatedColumn != null ) {

View File

@ -13,9 +13,11 @@ import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.internal.OffsetDateTimeCompositeUserType; import org.hibernate.usertype.internal.OffsetDateTimeCompositeUserType;
import org.hibernate.usertype.internal.OffsetTimeCompositeUserType;
import org.hibernate.usertype.internal.ZonedDateTimeCompositeUserType; import org.hibernate.usertype.internal.ZonedDateTimeCompositeUserType;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import static org.hibernate.TimeZoneStorageStrategy.COLUMN; import static org.hibernate.TimeZoneStorageStrategy.COLUMN;
@ -23,6 +25,7 @@ import static org.hibernate.dialect.TimeZoneSupport.NATIVE;
public class TimeZoneStorageHelper { public class TimeZoneStorageHelper {
private static final String OFFSET_TIME_CLASS = OffsetTime.class.getName();
private static final String OFFSET_DATETIME_CLASS = OffsetDateTime.class.getName(); private static final String OFFSET_DATETIME_CLASS = OffsetDateTime.class.getName();
private static final String ZONED_DATETIME_CLASS = ZonedDateTime.class.getName(); private static final String ZONED_DATETIME_CLASS = ZonedDateTime.class.getName();
@ -38,13 +41,29 @@ public class TimeZoneStorageHelper {
else if ( ZONED_DATETIME_CLASS.equals( returnedClassName ) ) { else if ( ZONED_DATETIME_CLASS.equals( returnedClassName ) ) {
return ZonedDateTimeCompositeUserType.class; return ZonedDateTimeCompositeUserType.class;
} }
else if ( OFFSET_TIME_CLASS.equals( returnedClassName ) ) {
return OffsetTimeCompositeUserType.class;
}
} }
return null; return null;
} }
private static boolean isZonedDateTimeClass(String returnedClassName) { private static boolean isTemporalWithTimeZoneClass(String returnedClassName) {
return OFFSET_DATETIME_CLASS.equals( returnedClassName ) return OFFSET_DATETIME_CLASS.equals( returnedClassName )
|| ZONED_DATETIME_CLASS.equals( returnedClassName ); || ZONED_DATETIME_CLASS.equals( returnedClassName )
|| isOffsetTimeClass( returnedClassName );
}
public static boolean isOffsetTimeClass(XAnnotatedElement element) {
if ( element instanceof XProperty ) {
XProperty property = (XProperty) element;
return isOffsetTimeClass( property.getType().getName() );
}
return false;
}
private static boolean isOffsetTimeClass(String returnedClassName) {
return OFFSET_TIME_CLASS.equals( returnedClassName );
} }
static boolean useColumnForTimeZoneStorage(XAnnotatedElement element, MetadataBuildingContext context) { static boolean useColumnForTimeZoneStorage(XAnnotatedElement element, MetadataBuildingContext context) {
@ -52,7 +71,7 @@ public class TimeZoneStorageHelper {
if ( timeZoneStorage == null ) { if ( timeZoneStorage == null ) {
if ( element instanceof XProperty ) { if ( element instanceof XProperty ) {
XProperty property = (XProperty) element; XProperty property = (XProperty) element;
return isZonedDateTimeClass( property.getType().getName() ) return isTemporalWithTimeZoneClass( property.getType().getName() )
//no @TimeZoneStorage annotation, so we need to use the default storage strategy //no @TimeZoneStorage annotation, so we need to use the default storage strategy
&& context.getBuildingOptions().getDefaultTimeZoneStorage() == COLUMN; && context.getBuildingOptions().getDefaultTimeZoneStorage() == COLUMN;
} }

View File

@ -10,6 +10,7 @@ import java.io.InputStream;
import java.sql.Types; import java.sql.Types;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -65,7 +66,6 @@ import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
import org.hibernate.type.descriptor.jdbc.XmlAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.XmlAsStringJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.DdlType; import org.hibernate.type.descriptor.sql.DdlType;
@ -687,7 +687,11 @@ public class MetadataBuildingProcess {
final JdbcType timestampWithTimeZoneOverride = getTimestampWithTimeZoneOverride( options, jdbcTypeRegistry ); final JdbcType timestampWithTimeZoneOverride = getTimestampWithTimeZoneOverride( options, jdbcTypeRegistry );
if ( timestampWithTimeZoneOverride != null ) { if ( timestampWithTimeZoneOverride != null ) {
adaptToDefaultTimeZoneStorage( typeConfiguration, timestampWithTimeZoneOverride ); adaptTimestampTypesToDefaultTimeZoneStorage( typeConfiguration, timestampWithTimeZoneOverride );
}
final JdbcType timeWithTimeZoneOverride = getTimeWithTimeZoneOverride( options, jdbcTypeRegistry );
if ( timeWithTimeZoneOverride != null ) {
adaptTimeTypesToDefaultTimeZoneStorage( typeConfiguration, timeWithTimeZoneOverride );
} }
final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant( serviceRegistry ); final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant( serviceRegistry );
if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) { if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) {
@ -728,7 +732,25 @@ public class MetadataBuildingProcess {
); );
} }
private static void adaptToDefaultTimeZoneStorage( private static void adaptTimeTypesToDefaultTimeZoneStorage(
TypeConfiguration typeConfiguration,
JdbcType timestampWithTimeZoneOverride) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
final BasicType<?> offsetDateTimeType = new NamedBasicTypeImpl<>(
javaTypeRegistry.getDescriptor( OffsetTime.class ),
timestampWithTimeZoneOverride,
"OffsetTime"
);
basicTypeRegistry.register(
offsetDateTimeType,
"org.hibernate.type.OffsetTimeType",
OffsetTime.class.getSimpleName(),
OffsetTime.class.getName()
);
}
private static void adaptTimestampTypesToDefaultTimeZoneStorage(
TypeConfiguration typeConfiguration, TypeConfiguration typeConfiguration,
JdbcType timestampWithTimeZoneOverride) { JdbcType timestampWithTimeZoneOverride) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
@ -757,6 +779,19 @@ public class MetadataBuildingProcess {
); );
} }
private static JdbcType getTimeWithTimeZoneOverride(MetadataBuildingOptions options, JdbcTypeRegistry jdbcTypeRegistry) {
switch ( options.getDefaultTimeZoneStorage() ) {
case NORMALIZE:
// For NORMALIZE, we replace the standard types that use TIME_WITH_TIMEZONE to use TIME
return jdbcTypeRegistry.getDescriptor( Types.TIME );
case NORMALIZE_UTC:
// For NORMALIZE_UTC, we replace the standard types that use TIME_WITH_TIMEZONE to use TIME_UTC
return jdbcTypeRegistry.getDescriptor( SqlTypes.TIME_UTC );
default:
return null;
}
}
private static JdbcType getTimestampWithTimeZoneOverride(MetadataBuildingOptions options, JdbcTypeRegistry jdbcTypeRegistry) { private static JdbcType getTimestampWithTimeZoneOverride(MetadataBuildingOptions options, JdbcTypeRegistry jdbcTypeRegistry) {
switch ( options.getDefaultTimeZoneStorage() ) { switch ( options.getDefaultTimeZoneStorage() ) {
case NORMALIZE: case NORMALIZE:

View File

@ -195,6 +195,7 @@ public class ColumnOrderingStrategyStandard implements ColumnOrderingStrategy {
return (int) length; return (int) length;
case DATE: case DATE:
case TIME: case TIME:
case TIME_UTC:
case TIME_WITH_TIMEZONE: case TIME_WITH_TIMEZONE:
return 4; return 4;
case TIMESTAMP: case TIMESTAMP:

View File

@ -137,8 +137,10 @@ import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NUMERIC; import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.POINT; import static org.hibernate.type.SqlTypes.POINT;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END; import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
@ -254,6 +256,9 @@ public abstract class AbstractHANADialect extends Dialect {
case DOUBLE: case DOUBLE:
return "double"; return "double";
//no explicit precision //no explicit precision
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case TIMESTAMP: case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp"; return "timestamp";
@ -1196,19 +1201,19 @@ public abstract class AbstractHANADialect extends Dialect {
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
switch (unit) { switch (unit) {
case NANOSECOND: case NANOSECOND:
// if ( temporalType == TemporalType.TIME ) { if ( fromTemporalType == TemporalType.TIME && toTemporalType == TemporalType.TIME ) {
// return "nano100_between(cast(?3 as timestamp), cast(?2 as timestamp))*100"; return "seconds_between(?2,?3)*1000000000";
// } }
// else { else {
return "nano100_between(?2,?3)*100"; return "nano100_between(?2,?3)*100";
// } }
case NATIVE: case NATIVE:
// if ( temporalType == TemporalType.TIME ) { if ( fromTemporalType == TemporalType.TIME && toTemporalType == TemporalType.TIME ) {
// return "nano100_between(cast(?3 as timestamp), cast(?2 as timestamp))"; return "seconds_between(?2,?3)*10000000";
// } }
// else { else {
return "nano100_between(?2,?3)"; return "nano100_between(?2,?3)";
// } }
case QUARTER: case QUARTER:
return "months_between(?2,?3)/3"; return "months_between(?2,?3)/3";
case WEEK: case WEEK:

View File

@ -322,6 +322,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
break; break;
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
values[column] = fromRawObject( values[column] = fromRawObject(
jdbcMapping, jdbcMapping,
parseTime( parseTime(
@ -804,6 +805,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
case SqlTypes.DATE: case SqlTypes.DATE:
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP: case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE: case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC: case SqlTypes.TIMESTAMP_UTC:
@ -940,6 +942,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
} }
break; break;
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
if ( value instanceof java.util.Date ) { if ( value instanceof java.util.Date ) {
appendAsTime( appender, (java.util.Date) value, jdbcTimeZone ); appendAsTime( appender, (java.util.Date) value, jdbcTimeZone );
} }

View File

@ -46,6 +46,7 @@ import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.IntervalType; import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.NullOrdering; import org.hibernate.query.sqm.NullOrdering;
import org.hibernate.query.sqm.TemporalUnit; import org.hibernate.query.sqm.TemporalUnit;
@ -58,7 +59,7 @@ import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
@ -76,7 +77,9 @@ import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.query.sqm.TemporalUnit.DAY; import static org.hibernate.query.sqm.TemporalUnit.DAY;
import static org.hibernate.query.sqm.TemporalUnit.EPOCH;
import static org.hibernate.query.sqm.TemporalUnit.NATIVE; import static org.hibernate.query.sqm.TemporalUnit.NATIVE;
import static org.hibernate.query.sqm.TemporalUnit.YEAR;
import static org.hibernate.type.SqlTypes.ARRAY; import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BINARY; import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB; import static org.hibernate.type.SqlTypes.BLOB;
@ -94,9 +97,11 @@ import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NCLOB; import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.OTHER; import static org.hibernate.type.SqlTypes.OTHER;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_UTC;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
@ -212,6 +217,10 @@ public class CockroachDialect extends Dialect {
case BLOB: case BLOB:
return "bytes"; return "bytes";
// We do not use the time with timezone type because PG deprecated it and it lacks certain operations like subtraction
// case TIME_UTC:
// return columnType( TIME_WITH_TIMEZONE );
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return columnType( TIMESTAMP_WITH_TIMEZONE ); return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -284,6 +293,12 @@ public class CockroachDialect extends Dialect {
break; break;
} }
break; break;
case TIME:
// The PostgreSQL JDBC driver reports TIME for timetz, but we use it only for mapping OffsetTime to UTC
if ( "timetz".equals( columnTypeName ) ) {
jdbcTypeCode = TIME_UTC;
}
break;
case TIMESTAMP: case TIMESTAMP:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant // The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) { if ( "timestamptz".equals( columnTypeName ) ) {
@ -339,7 +354,7 @@ public class CockroachDialect extends Dialect {
protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry(); .getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) { if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
if ( PgJdbcHelper.isUsable( serviceRegistry ) ) { if ( PgJdbcHelper.isUsable( serviceRegistry ) ) {
@ -422,7 +437,13 @@ public class CockroachDialect extends Dialect {
functionContributions.getFunctionRegistry().register( functionContributions.getFunctionRegistry().register(
"format", "format",
new FormatFunction( "experimental_strftime", functionContributions.getTypeConfiguration() ) new FormatFunction(
"experimental_strftime",
false,
true,
false,
functionContributions.getTypeConfiguration()
)
); );
functionFactory.windowFunctions(); functionFactory.windowFunctions();
functionFactory.listagg_stringAgg( "string" ); functionFactory.listagg_stringAgg( "string" );
@ -756,30 +777,46 @@ public class CockroachDialect extends Dialect {
if ( unit == null ) { if ( unit == null ) {
return "(?3-?2)"; return "(?3-?2)";
} }
switch (unit) { if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
case YEAR:
return "(extract(year from ?3)-extract(year from ?2))";
case QUARTER:
return "(extract(year from ?3)*4-extract(year from ?2)*4+extract(month from ?3)//3-extract(month from ?2)//3)";
case MONTH:
return "(extract(year from ?3)*12-extract(year from ?2)*12+extract(month from ?3)-extract(month from ?2))";
}
if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP ) {
// special case: subtraction of two dates // special case: subtraction of two dates
// results in an integer number of days // results in an integer number of days
// instead of an INTERVAL // instead of an INTERVAL
return "(?3-?2)" + DAY.conversionFactor( unit, this ); switch ( unit ) {
case YEAR:
case MONTH:
case QUARTER:
// age only supports timestamptz, so we have to cast the date expressions
return "extract(" + translateDurationField( unit ) + " from age(cast(?3 as timestamptz),cast(?2 as timestamptz)))";
default:
return "(?3-?2)" + DAY.conversionFactor( unit, this );
}
} }
else { else {
switch (unit) { switch (unit) {
case WEEK: case YEAR:
return "extract_duration(hour from ?3-?2)/168"; return "extract(year from ?3-?2)";
case QUARTER:
return "(extract(year from ?3-?2)*4+extract(month from ?3-?2)//3)";
case MONTH:
return "(extract(year from ?3-?2)*12+extract(month from ?3-?2))";
case WEEK: //week is not supported by extract() when the argument is a duration
return "(extract(day from ?3-?2)/7)";
case DAY: case DAY:
return "extract_duration(hour from ?3-?2)/24"; return "extract(day from ?3-?2)";
//in order to avoid multiple calls to extract(),
//we use extract(epoch from x - y) * factor for
//all the following units:
// Note that CockroachDB also has an extract_duration function which returns an int,
// but we don't use that here because it is deprecated since v20
case HOUR:
case MINUTE:
case SECOND:
case NANOSECOND: case NANOSECOND:
return "extract_duration(microsecond from ?3-?2)*1e3"; case NATIVE:
return "cast(extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this ) + " as int)";
default: default:
return "extract_duration(?1 from ?3-?2)"; throw new SemanticException( "unrecognized field: " + unit );
} }
} }
} }

View File

@ -93,6 +93,7 @@ import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DECIMAL; import static org.hibernate.type.SqlTypes.DECIMAL;
import static org.hibernate.type.SqlTypes.NUMERIC; import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.SQLXML; import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
@ -189,6 +190,7 @@ public class DB2Dialect extends Dialect {
return "clob"; return "clob";
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p)"; return "timestamp($p)";
case TIME:
case TIME_WITH_TIMEZONE: case TIME_WITH_TIMEZONE:
return "time"; return "time";
case BINARY: case BINARY:
@ -408,9 +410,37 @@ public class DB2Dialect extends Dialect {
if ( getDB2Version().isBefore( 11 ) ) { if ( getDB2Version().isBefore( 11 ) ) {
return timestampdiffPatternV10( unit, fromTemporalType, toTemporalType ); return timestampdiffPatternV10( unit, fromTemporalType, toTemporalType );
} }
StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit(); final String fromExpression;
boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit(); final String toExpression;
if ( unit.isDateUnit() ) {
fromExpression = "?2";
toExpression = "?3";
}
else {
switch ( fromTemporalType ) {
case DATE:
fromExpression = "cast(?2 as timestamp)";
break;
case TIME:
fromExpression = "timestamp('1970-01-01',?2)";
break;
default:
fromExpression = "?2";
break;
}
switch ( toTemporalType ) {
case DATE:
toExpression = "cast(?3 as timestamp)";
break;
case TIME:
toExpression = "timestamp('1970-01-01',?3)";
break;
default:
toExpression = "?3";
break;
}
}
switch ( unit ) { switch ( unit ) {
case NATIVE: case NATIVE:
case NANOSECOND: case NANOSECOND:
@ -426,26 +456,24 @@ public class DB2Dialect extends Dialect {
default: default:
pattern.append( "?1s_between(" ); pattern.append( "?1s_between(" );
} }
if ( castTo ) { pattern.append( toExpression );
pattern.append( "cast(?3 as timestamp)" );
}
else {
pattern.append( "?3" );
}
pattern.append( ',' ); pattern.append( ',' );
if ( castFrom ) { pattern.append( fromExpression );
pattern.append( "cast(?2 as timestamp)" );
}
else {
pattern.append( "?2" );
}
pattern.append( ')' ); pattern.append( ')' );
switch ( unit ) { switch ( unit ) {
case NATIVE: case NATIVE:
pattern.append( "+(microsecond(?3)-microsecond(?2))/1e6)" ); pattern.append( "+(microsecond(");
pattern.append( toExpression );
pattern.append(")-microsecond(");
pattern.append( fromExpression );
pattern.append("))/1e6)" );
break; break;
case NANOSECOND: case NANOSECOND:
pattern.append( "*1e9+(microsecond(?3)-microsecond(?2))*1e3)" ); pattern.append( "*1e9+(microsecond(");
pattern.append( toExpression );
pattern.append(")-microsecond(");
pattern.append( fromExpression );
pattern.append("))*1e3)" );
break; break;
case MONTH: case MONTH:
pattern.append( ')' ); pattern.append( ')' );
@ -458,10 +486,36 @@ public class DB2Dialect extends Dialect {
} }
public static String timestampdiffPatternV10(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { public static String timestampdiffPatternV10(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
final boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit(); final String fromExpression;
final boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit(); final String toExpression;
final String fromExpression = castFrom ? "cast(?2 as timestamp)" : "?2"; if ( unit.isDateUnit() ) {
final String toExpression = castTo ? "cast(?3 as timestamp)" : "?3"; if ( fromTemporalType == TemporalType.TIME ) {
fromExpression = "timestamp('1970-01-01',?2)";
}
else {
fromExpression = "?2";
}
if ( toTemporalType == TemporalType.TIME ) {
toExpression = "timestamp('1970-01-01',?3)";
}
else {
toExpression = "?3";
}
}
else {
if ( fromTemporalType == TemporalType.DATE ) {
fromExpression = "cast(?2 as timestamp)";
}
else {
fromExpression = "?2";
}
if ( toTemporalType == TemporalType.DATE ) {
toExpression = "cast(?3 as timestamp)";
}
else {
toExpression = "?3";
}
}
switch ( unit ) { switch ( unit ) {
case NATIVE: case NATIVE:
return "(select (days(t2)-days(t1))*86400+(midnight_seconds(t2)-midnight_seconds(t1))+(microsecond(t2)-microsecond(t1))/1e6 " + return "(select (days(t2)-days(t1))*86400+(midnight_seconds(t2)-midnight_seconds(t1))+(microsecond(t2)-microsecond(t1))/1e6 " +
@ -498,19 +552,24 @@ public class DB2Dialect extends Dialect {
@Override @Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
final StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
final boolean castTo; final String timestampExpression;
if ( unit.isDateUnit() ) { if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME; if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
} }
else { else {
castTo = temporalType == TemporalType.DATE; if ( temporalType == TemporalType.DATE ) {
} timestampExpression = "cast(?3 as timestamp)";
if (castTo) { }
pattern.append("cast(?3 as timestamp)"); else {
} timestampExpression = "?3";
else { }
pattern.append("?3");
} }
pattern.append(timestampExpression);
pattern.append("+("); pattern.append("+(");
// DB2 supports temporal arithmetic. See https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.sql.ref.doc/doc/r0023457.html // DB2 supports temporal arithmetic. See https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.sql.ref.doc/doc/r0023457.html
switch (unit) { switch (unit) {

View File

@ -32,6 +32,7 @@ import java.util.List;
import static org.hibernate.type.SqlTypes.ROWID; import static org.hibernate.type.SqlTypes.ROWID;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
/** /**
* A SQL dialect for DB2 for z/OS version 12.1 and above, previously known as: * A SQL dialect for DB2 for z/OS version 12.1 and above, previously known as:
@ -78,9 +79,13 @@ public class DB2zDialect extends DB2Dialect {
@Override @Override
protected String columnType(int sqlTypeCode) { protected String columnType(int sqlTypeCode) {
if ( sqlTypeCode == TIMESTAMP_WITH_TIMEZONE && getVersion().isAfter( 10 ) ) { if ( getVersion().isAfter( 10 ) ) {
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html switch ( sqlTypeCode ) {
return "timestamp with time zone"; case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html
return "timestamp with time zone";
}
} }
return super.columnType( sqlTypeCode ); return super.columnType( sqlTypeCode );
} }
@ -150,14 +155,7 @@ public class DB2zDialect extends DB2Dialect {
@Override @Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
final boolean castTo;
if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME;
}
else {
castTo = temporalType == TemporalType.DATE;
}
pattern.append("add_"); pattern.append("add_");
switch (unit) { switch (unit) {
case NATIVE: case NATIVE:
@ -175,12 +173,24 @@ public class DB2zDialect extends DB2Dialect {
pattern.append("?1"); pattern.append("?1");
} }
pattern.append("s("); pattern.append("s(");
if (castTo) { final String timestampExpression;
pattern.append("cast(?3 as timestamp)"); if ( unit.isDateUnit() ) {
if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
} }
else { else {
pattern.append("?3"); if ( temporalType == TemporalType.DATE ) {
timestampExpression = "cast(?3 as timestamp)";
}
else {
timestampExpression = "?3";
}
} }
pattern.append(timestampExpression);
pattern.append(","); pattern.append(",");
switch (unit) { switch (unit) {
case NANOSECOND: case NANOSECOND:

View File

@ -85,8 +85,10 @@ import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NCLOB; import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NUMERIC; import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
@ -154,6 +156,10 @@ public class DerbyDialect extends Dialect {
case NCLOB: case NCLOB:
return "clob"; return "clob";
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case TIMESTAMP: case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp"; return "timestamp";

View File

@ -178,14 +178,16 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampJdbcType; import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.LongNVarcharJdbcType; import org.hibernate.type.descriptor.jdbc.LongNVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.NCharJdbcType; import org.hibernate.type.descriptor.jdbc.NCharJdbcType;
import org.hibernate.type.descriptor.jdbc.NClobJdbcType; import org.hibernate.type.descriptor.jdbc.NClobJdbcType;
import org.hibernate.type.descriptor.jdbc.NVarcharJdbcType; import org.hibernate.type.descriptor.jdbc.NVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeUtcAsJdbcTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
@ -229,6 +231,7 @@ import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_UTC;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
@ -404,6 +407,7 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
ddlTypeRegistry.addDescriptor( simpleSqlType( DATE ) ); ddlTypeRegistry.addDescriptor( simpleSqlType( DATE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIME ) ); ddlTypeRegistry.addDescriptor( simpleSqlType( TIME ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIME_WITH_TIMEZONE ) ); ddlTypeRegistry.addDescriptor( simpleSqlType( TIME_WITH_TIMEZONE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIME_UTC ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP ) ); ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_WITH_TIMEZONE ) ); ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_WITH_TIMEZONE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_UTC ) ); ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_UTC ) );
@ -534,17 +538,21 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
case DATE: case DATE:
return "date"; return "date";
case TIME: case TIME:
return "time"; return "time($p)";
case TIME_WITH_TIMEZONE: case TIME_WITH_TIMEZONE:
// type included here for completeness but note that // type included here for completeness but note that
// very few databases support it, and the general // very few databases support it, and the general
// advice is to caution against its use (for reasons, // advice is to caution against its use (for reasons,
// check the comments in the Postgres documentation). // check the comments in the Postgres documentation).
return "time with time zone"; return "time($p) with time zone";
case TIMESTAMP: case TIMESTAMP:
return "timestamp($p)"; return "timestamp($p)";
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p) with time zone"; return "timestamp($p) with time zone";
case TIME_UTC:
return getTimeZoneSupport() == TimeZoneSupport.NATIVE
? columnType( TIME_WITH_TIMEZONE )
: columnType( TIME );
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return getTimeZoneSupport() == TimeZoneSupport.NATIVE return getTimeZoneSupport() == TimeZoneSupport.NATIVE
? columnType( TIMESTAMP_WITH_TIMEZONE ) ? columnType( TIMESTAMP_WITH_TIMEZONE )
@ -1555,10 +1563,12 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
} }
if ( getTimeZoneSupport() == TimeZoneSupport.NATIVE ) { if ( getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
jdbcTypeRegistry.addDescriptor( InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimeUtcAsOffsetTimeJdbcType.INSTANCE );
} }
else { else {
jdbcTypeRegistry.addDescriptor( InstantAsTimestampJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TimestampUtcAsJdbcTimestampJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimeUtcAsJdbcTimeJdbcType.INSTANCE );
} }
if ( supportsStandardArrays() ) { if ( supportsStandardArrays() ) {
@ -4899,6 +4909,9 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
precision = (int) ceil( precision * LOG_BASE2OF10 ); precision = (int) ceil( precision * LOG_BASE2OF10 );
} }
break; break;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP: case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE: case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC: case SqlTypes.TIMESTAMP_UTC:

View File

@ -69,8 +69,11 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType; import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantJdbcType; import org.hibernate.type.descriptor.jdbc.TimeUtcAsJdbcTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampUtcAsInstantJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
@ -98,6 +101,9 @@ import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.OTHER; import static org.hibernate.type.SqlTypes.OTHER;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_UTC;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
@ -190,6 +196,9 @@ public class H2Dialect extends Dialect {
// which caused problems for schema update tool // which caused problems for schema update tool
case NUMERIC: case NUMERIC:
return getVersion().isBefore( 2 ) ? columnType( DECIMAL ) : super.columnType( sqlTypeCode ); return getVersion().isBefore( 2 ) ? columnType( DECIMAL ) : super.columnType( sqlTypeCode );
// Support was only added in 2.0
case TIME_WITH_TIMEZONE:
return getVersion().isBefore( 2 ) ? columnType( TIMESTAMP_WITH_TIMEZONE ) : super.columnType( sqlTypeCode );
case NCHAR: case NCHAR:
return columnType( CHAR ); return columnType( CHAR );
case NVARCHAR: case NVARCHAR:
@ -243,7 +252,15 @@ public class H2Dialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry(); .getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantJdbcType.INSTANCE ); if ( getVersion().isBefore( 2 ) ) {
// Support for TIME_WITH_TIMEZONE was only added in 2.0
jdbcTypeRegistry.addDescriptor( TIME_WITH_TIMEZONE, TimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimeUtcAsJdbcTimeJdbcType.INSTANCE );
}
else {
jdbcTypeRegistry.addDescriptor( TimeUtcAsOffsetTimeJdbcType.INSTANCE );
}
jdbcTypeRegistry.addDescriptor( TimestampUtcAsInstantJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) { if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );

View File

@ -164,6 +164,7 @@ public class JsonHelper {
break; break;
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
appender.append( '"' ); appender.append( '"' );
JdbcTimeJavaType.INSTANCE.appendEncodedString( JdbcTimeJavaType.INSTANCE.appendEncodedString(
appender, appender,
@ -798,6 +799,7 @@ public class JsonHelper {
); );
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return jdbcMapping.getJdbcJavaType().wrap( return jdbcMapping.getJdbcJavaType().wrap(
JdbcTimeJavaType.INSTANCE.fromEncodedString( JdbcTimeJavaType.INSTANCE.fromEncodedString(
string, string,

View File

@ -529,86 +529,84 @@ public class OracleDialect extends Dialect {
@Override @Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
StringBuilder pattern = new StringBuilder(); final StringBuilder pattern = new StringBuilder();
boolean timestamp = toTemporalType == TemporalType.TIMESTAMP || fromTemporalType == TemporalType.TIMESTAMP; final boolean hasTimePart = toTemporalType != TemporalType.DATE || fromTemporalType != TemporalType.DATE;
switch (unit) { switch ( unit ) {
case YEAR: case YEAR:
extractField(pattern, YEAR, unit); extractField( pattern, YEAR, unit );
break; break;
case QUARTER: case QUARTER:
case MONTH: case MONTH:
pattern.append("("); pattern.append( "(" );
extractField(pattern, YEAR, unit); extractField( pattern, YEAR, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, MONTH, unit); extractField( pattern, MONTH, unit );
pattern.append(")"); pattern.append( ")" );
break; break;
case WEEK: case WEEK:
case DAY: case DAY:
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
break; break;
case HOUR: case HOUR:
pattern.append("("); pattern.append( "(" );
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
if (timestamp) { if ( hasTimePart ) {
pattern.append("+"); pattern.append( "+" );
extractField(pattern, HOUR, unit); extractField( pattern, HOUR, unit );
} }
pattern.append(")"); pattern.append( ")" );
break; break;
case MINUTE: case MINUTE:
pattern.append("("); pattern.append( "(" );
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
if (timestamp) { if ( hasTimePart ) {
pattern.append("+"); pattern.append( "+" );
extractField(pattern, HOUR, unit); extractField( pattern, HOUR, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, MINUTE, unit); extractField( pattern, MINUTE, unit );
} }
pattern.append(")"); pattern.append( ")" );
break; break;
case NATIVE: case NATIVE:
case NANOSECOND: case NANOSECOND:
case SECOND: case SECOND:
pattern.append("("); pattern.append( "(" );
extractField(pattern, DAY, unit); extractField( pattern, DAY, unit );
if (timestamp) { if ( hasTimePart ) {
pattern.append("+"); pattern.append( "+" );
extractField(pattern, HOUR, unit); extractField( pattern, HOUR, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, MINUTE, unit); extractField( pattern, MINUTE, unit );
pattern.append("+"); pattern.append( "+" );
extractField(pattern, SECOND, unit); extractField( pattern, SECOND, unit );
} }
pattern.append(")"); pattern.append( ")" );
break; break;
default: default:
throw new SemanticException("unrecognized field: " + unit); throw new SemanticException( "unrecognized field: " + unit );
} }
return pattern.toString(); return pattern.toString();
} }
private void extractField( private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit toUnit) {
StringBuilder pattern, pattern.append( "extract(" );
TemporalUnit unit, TemporalUnit toUnit) { pattern.append( translateExtractField( unit ) );
pattern.append("extract("); pattern.append( " from (?3-?2) " );
pattern.append( translateExtractField(unit) ); switch ( unit ) {
pattern.append(" from (?3-?2) ");
switch (unit) {
case YEAR: case YEAR:
case MONTH: case MONTH:
pattern.append("year to month"); pattern.append( "year to month" );
break; break;
case DAY: case DAY:
case HOUR: case HOUR:
case MINUTE: case MINUTE:
case SECOND: case SECOND:
pattern.append("day to second"); pattern.append( "day to second" );
break; break;
default: default:
throw new SemanticException(unit + " is not a legal field"); throw new SemanticException( unit + " is not a legal field" );
} }
pattern.append(")"); pattern.append( ")" );
pattern.append( unit.conversionFactor( toUnit, this ) ); pattern.append( unit.conversionFactor( toUnit, this ) );
} }
@ -637,8 +635,9 @@ public class OracleDialect extends Dialect {
return "number($p,$s)"; return "number($p,$s)";
case DATE: case DATE:
case TIME:
return "date"; return "date";
case TIME:
return "timestamp($p)";
// the only difference between date and timestamp // the only difference between date and timestamp
// on Oracle is that date has no fractional seconds // on Oracle is that date has no fractional seconds
case TIME_WITH_TIMEZONE: case TIME_WITH_TIMEZONE:

View File

@ -80,7 +80,7 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
@ -119,9 +119,11 @@ import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.OTHER; import static org.hibernate.type.SqlTypes.OTHER;
import static org.hibernate.type.SqlTypes.SQLXML; import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.STRUCT; import static org.hibernate.type.SqlTypes.STRUCT;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_UTC;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
@ -220,6 +222,10 @@ public class PostgreSQLDialect extends Dialect {
case LONG32VARBINARY: case LONG32VARBINARY:
return "bytea"; return "bytea";
// We do not use the time with timezone type because PG deprecated it and it lacks certain operations like subtraction
// case TIME_UTC:
// return columnType( TIME_WITH_TIMEZONE );
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return columnType( TIMESTAMP_WITH_TIMEZONE ); return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -325,6 +331,12 @@ public class PostgreSQLDialect extends Dialect {
break; break;
} }
break; break;
case TIME:
// The PostgreSQL JDBC driver reports TIME for timetz, but we use it only for mapping OffsetTime to UTC
if ( "timetz".equals( columnTypeName ) ) {
jdbcTypeCode = TIME_UTC;
}
break;
case TIMESTAMP: case TIMESTAMP:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant // The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) { if ( "timestamptz".equals( columnTypeName ) ) {
@ -445,36 +457,31 @@ public class PostgreSQLDialect extends Dialect {
if ( unit == null ) { if ( unit == null ) {
return "(?3-?2)"; return "(?3-?2)";
} }
if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP && unit == DAY ) { if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
// special case: subtraction of two dates // special case: subtraction of two dates
// results in an integer number of days // results in an integer number of days
// instead of an INTERVAL // instead of an INTERVAL
return "(?3-?2)";
}
else {
StringBuilder pattern = new StringBuilder();
switch ( unit ) { switch ( unit ) {
case YEAR: case YEAR:
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit );
break;
case QUARTER:
pattern.append( "(" );
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit );
pattern.append( "+" );
extractField( pattern, QUARTER, fromTemporalType, toTemporalType, unit );
pattern.append( ")" );
break;
case MONTH: case MONTH:
pattern.append( "(" ); case QUARTER:
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit ); return "extract(" + translateDurationField( unit ) + " from age(?3,?2))";
pattern.append( "+" ); default:
extractField( pattern, MONTH, fromTemporalType, toTemporalType, unit ); return "(?3-?2)" + DAY.conversionFactor( unit, this );
pattern.append( ")" ); }
break; }
else {
switch ( unit ) {
case YEAR:
return "extract(year from ?3-?2)";
case QUARTER:
return "(extract(year from ?3-?2)*4+extract(month from ?3-?2)/3)";
case MONTH:
return "(extract(year from ?3-?2)*12+extract(month from ?3-?2))";
case WEEK: //week is not supported by extract() when the argument is a duration case WEEK: //week is not supported by extract() when the argument is a duration
return "(extract(day from ?3-?2)/7)";
case DAY: case DAY:
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit ); return "extract(day from ?3-?2)";
break;
//in order to avoid multiple calls to extract(), //in order to avoid multiple calls to extract(),
//we use extract(epoch from x - y) * factor for //we use extract(epoch from x - y) * factor for
//all the following units: //all the following units:
@ -483,15 +490,14 @@ public class PostgreSQLDialect extends Dialect {
case SECOND: case SECOND:
case NANOSECOND: case NANOSECOND:
case NATIVE: case NATIVE:
extractField( pattern, EPOCH, fromTemporalType, toTemporalType, unit ); return "extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this );
break;
default: default:
throw new SemanticException( "unrecognized field: " + unit ); throw new SemanticException( "unrecognized field: " + unit );
} }
return pattern.toString();
} }
} }
@Deprecated
protected void extractField( protected void extractField(
StringBuilder pattern, StringBuilder pattern,
TemporalUnit unit, TemporalUnit unit,
@ -501,7 +507,7 @@ public class PostgreSQLDialect extends Dialect {
pattern.append( "extract(" ); pattern.append( "extract(" );
pattern.append( translateDurationField( unit ) ); pattern.append( translateDurationField( unit ) );
pattern.append( " from " ); pattern.append( " from " );
if ( toTimestamp != TemporalType.TIMESTAMP && fromTimestamp != TemporalType.TIMESTAMP ) { if ( toTimestamp == TemporalType.DATE && fromTimestamp == TemporalType.DATE ) {
// special case subtraction of two // special case subtraction of two
// dates results in an integer not // dates results in an integer not
// an Interval // an Interval
@ -1333,7 +1339,7 @@ public class PostgreSQLDialect extends Dialect {
// dialect uses oid for Blobs, byte arrays cannot be used. // dialect uses oid for Blobs, byte arrays cannot be used.
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING ); jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING ); jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) { if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {

View File

@ -24,8 +24,6 @@ import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.TemporalUnit.DAY;
/** /**
* An SQL dialect for Postgres Plus * An SQL dialect for Postgres Plus
* *
@ -88,12 +86,10 @@ public class PostgresPlusDialect extends PostgreSQLDialect {
@Override @Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP && unit == DAY ) { if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
// special case: subtraction of two dates results in an INTERVAL on Postgres Plus // special case: subtraction of two dates results in an INTERVAL on Postgres Plus
// because there is no date type i.e. without time for Oracle compatibility // because there is no date type i.e. without time for Oracle compatibility
final StringBuilder pattern = new StringBuilder(); return super.timestampdiffPattern( unit, TemporalType.TIMESTAMP, TemporalType.TIMESTAMP );
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit );
return pattern.toString();
} }
return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType ); return super.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
} }

View File

@ -100,6 +100,7 @@ import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.TIME; import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
@ -179,6 +180,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
return "time"; return "time";
case TIMESTAMP: case TIMESTAMP:
return "datetime2($p)"; return "datetime2($p)";
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return "datetimeoffset($p)"; return "datetimeoffset($p)";
default: default:

View File

@ -374,6 +374,8 @@ public class StructJdbcType implements AggregateJdbcType {
if ( rawJdbcValue != null ) { if ( rawJdbcValue != null ) {
final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping(); final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping();
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE: case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC: case SqlTypes.TIMESTAMP_UTC:
// Only transform the raw jdbc value if it could be a TIMESTAMPTZ // Only transform the raw jdbc value if it could be a TIMESTAMPTZ

View File

@ -160,6 +160,7 @@ public class XmlHelper {
); );
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return fromRawObject( return fromRawObject(
jdbcMapping, jdbcMapping,
JdbcTimeJavaType.INSTANCE.fromEncodedString( JdbcTimeJavaType.INSTANCE.fromEncodedString(
@ -598,6 +599,7 @@ public class XmlHelper {
break; break;
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
JdbcTimeJavaType.INSTANCE.appendEncodedString( JdbcTimeJavaType.INSTANCE.appendEncodedString(
appender, appender,
jdbcJavaType.unwrap( value, java.sql.Time.class, options ) jdbcJavaType.unwrap( value, java.sql.Time.class, options )

View File

@ -19,6 +19,7 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
@ -38,6 +39,8 @@ import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_UTC;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
@ -117,6 +120,8 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
aggregateParentReadExpression + "." + column + ".date()" aggregateParentReadExpression + "." + column + ".date()"
); );
case TIME: case TIME:
case TIME_WITH_TIMEZONE:
case TIME_UTC:
return template.replace( return template.replace(
placeholder, placeholder,
"to_timestamp(" + aggregateParentReadExpression + "." + column + ".string(),'hh24:mi:ss')" "to_timestamp(" + aggregateParentReadExpression + "." + column + ".string(),'hh24:mi:ss')"

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryEngine;
@ -48,6 +49,7 @@ import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
@ -64,6 +66,7 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
private final String nativeFunctionName; private final String nativeFunctionName;
private final boolean reversedArguments; private final boolean reversedArguments;
private final boolean concatPattern; private final boolean concatPattern;
private final boolean supportsTime;
public FormatFunction(String nativeFunctionName, TypeConfiguration typeConfiguration) { public FormatFunction(String nativeFunctionName, TypeConfiguration typeConfiguration) {
this( nativeFunctionName, false, true, typeConfiguration ); this( nativeFunctionName, false, true, typeConfiguration );
@ -74,6 +77,15 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
boolean reversedArguments, boolean reversedArguments,
boolean concatPattern, boolean concatPattern,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
this( nativeFunctionName, reversedArguments, concatPattern, true, typeConfiguration );
}
public FormatFunction(
String nativeFunctionName,
boolean reversedArguments,
boolean concatPattern,
boolean supportsTime,
TypeConfiguration typeConfiguration) {
super( super(
"format", "format",
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING ), new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING ),
@ -84,6 +96,7 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
this.nativeFunctionName = nativeFunctionName; this.nativeFunctionName = nativeFunctionName;
this.reversedArguments = reversedArguments; this.reversedArguments = reversedArguments;
this.concatPattern = concatPattern; this.concatPattern = concatPattern;
this.supportsTime = supportsTime;
} }
@Override @Override
@ -98,9 +111,15 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
if ( reversedArguments ) { if ( reversedArguments ) {
format.accept( walker ); format.accept( walker );
sqlAppender.append( ',' ); sqlAppender.append( ',' );
if ( !supportsTime && isTimeTemporal( expression ) ) {
sqlAppender.append( "date'1970-01-01'+" );
}
expression.accept( walker ); expression.accept( walker );
} }
else { else {
if ( !supportsTime && isTimeTemporal( expression ) ) {
sqlAppender.append( "date'1970-01-01'+" );
}
expression.accept( walker ); expression.accept( walker );
sqlAppender.append( ',' ); sqlAppender.append( ',' );
format.accept( walker ); format.accept( walker );
@ -108,6 +127,23 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
sqlAppender.append( ')' ); sqlAppender.append( ')' );
} }
private boolean isTimeTemporal(SqlAstNode expression) {
if ( expression instanceof Expression ) {
final JdbcMappingContainer expressionType = ( (Expression) expression ).getExpressionType();
if ( expressionType.getJdbcTypeCount() == 1 ) {
switch ( expressionType.getSingleJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) {
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return true;
default:
break;
}
}
}
return false;
}
@Override @Override
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression( protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
List<? extends SqmTypedNode<?>> arguments, List<? extends SqmTypedNode<?>> arguments,

View File

@ -13,6 +13,7 @@ import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
/** /**
@ -32,10 +33,9 @@ public class SQLServerFormatEmulation extends FormatFunction {
List<? extends SqlAstNode> arguments, List<? extends SqlAstNode> arguments,
SqlAstTranslator<?> walker) { SqlAstTranslator<?> walker) {
final Expression datetime = (Expression) arguments.get(0); final Expression datetime = (Expression) arguments.get(0);
final boolean isTime = TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME;
sqlAppender.appendSql("format("); sqlAppender.appendSql("format(");
if ( isTime ) { if ( needsDateTimeCast( datetime ) ) {
sqlAppender.appendSql("cast("); sqlAppender.appendSql("cast(");
datetime.accept( walker ); datetime.accept( walker );
sqlAppender.appendSql(" as datetime)"); sqlAppender.appendSql(" as datetime)");
@ -47,4 +47,16 @@ public class SQLServerFormatEmulation extends FormatFunction {
arguments.get( 1 ).accept( walker ); arguments.get( 1 ).accept( walker );
sqlAppender.appendSql(')'); sqlAppender.appendSql(')');
} }
private boolean needsDateTimeCast(Expression datetime) {
final boolean isTime = TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME;
if ( isTime ) {
// Since SQL Server has no dedicated type for time with time zone, we use the offsetdatetime which has a date part
return datetime.getExpressionType()
.getSingleJdbcMapping()
.getJdbcType()
.getDefaultSqlTypeCode() != SqlTypes.TIME_WITH_TIMEZONE;
}
return false;
}
} }

View File

@ -13,6 +13,7 @@ import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.OffsetTime;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
@ -31,6 +32,7 @@ import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.descriptor.java.TemporalJavaType;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType; import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.hibernate.usertype.internal.OffsetTimeCompositeUserType;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -117,7 +119,12 @@ public class SqmExpressionHelper {
public static SqmExpression<?> getActualExpression(SqmExpression<?> expression) { public static SqmExpression<?> getActualExpression(SqmExpression<?> expression) {
if ( isCompositeTemporal( expression ) ) { if ( isCompositeTemporal( expression ) ) {
return ( (SqmPath<?>) expression ).get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME ); if ( expression.getJavaTypeDescriptor().getJavaTypeClass() == OffsetTime.class ) {
return ( (SqmPath<?>) expression ).get( OffsetTimeCompositeUserType.LOCAL_TIME_NAME );
}
else {
return ( (SqmPath<?>) expression ).get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME );
}
} }
else { else {
return expression; return expression;
@ -127,18 +134,24 @@ public class SqmExpressionHelper {
public static SqmExpression<?> getOffsetAdjustedExpression(SqmExpression<?> expression) { public static SqmExpression<?> getOffsetAdjustedExpression(SqmExpression<?> expression) {
if ( isCompositeTemporal( expression ) ) { if ( isCompositeTemporal( expression ) ) {
final SqmPath<?> compositePath = (SqmPath<?>) expression; final SqmPath<?> compositePath = (SqmPath<?>) expression;
final SqmPath<Object> instantPath = compositePath.get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME ); final SqmPath<Object> temporalPath;
final NodeBuilder nodeBuilder = instantPath.nodeBuilder(); if ( expression.getJavaTypeDescriptor().getJavaTypeClass() == OffsetTime.class ) {
temporalPath = compositePath.get( OffsetTimeCompositeUserType.LOCAL_TIME_NAME );
}
else {
temporalPath = compositePath.get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME );
}
final NodeBuilder nodeBuilder = temporalPath.nodeBuilder();
return new SqmBinaryArithmetic<>( return new SqmBinaryArithmetic<>(
BinaryArithmeticOperator.ADD, BinaryArithmeticOperator.ADD,
instantPath, temporalPath,
new SqmToDuration<>( new SqmToDuration<>(
compositePath.get( AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME ), compositePath.get( AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME ),
new SqmDurationUnit<>( TemporalUnit.SECOND, nodeBuilder.getIntegerType(), nodeBuilder ), new SqmDurationUnit<>( TemporalUnit.SECOND, nodeBuilder.getIntegerType(), nodeBuilder ),
nodeBuilder.getTypeConfiguration().getBasicTypeForJavaType( Duration.class ), nodeBuilder.getTypeConfiguration().getBasicTypeForJavaType( Duration.class ),
nodeBuilder nodeBuilder
), ),
instantPath.getNodeType(), temporalPath.getNodeType(),
nodeBuilder nodeBuilder
); );
} }

View File

@ -440,9 +440,9 @@ public class SqlTypes {
* JDBC} timezone. * JDBC} timezone.
* *
* @see org.hibernate.cfg.AvailableSettings#PREFERRED_INSTANT_JDBC_TYPE * @see org.hibernate.cfg.AvailableSettings#PREFERRED_INSTANT_JDBC_TYPE
* @see org.hibernate.type.descriptor.jdbc.InstantJdbcType * @see org.hibernate.type.descriptor.jdbc.TimestampUtcAsInstantJdbcType
* @see org.hibernate.type.descriptor.jdbc.InstantAsTimestampJdbcType * @see org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType
* @see org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType * @see org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType
*/ */
public static final int TIMESTAMP_UTC = 3003; public static final int TIMESTAMP_UTC = 3003;
@ -479,6 +479,18 @@ public class SqlTypes {
@Internal @Internal
public static final int MATERIALIZED_NCLOB = 3006; public static final int MATERIALIZED_NCLOB = 3006;
/**
* A type code representing the generic SQL type {@code TIME},
* where the value is given in UTC, instead of in the system or
* {@linkplain org.hibernate.cfg.AvailableSettings#JDBC_TIME_ZONE
* JDBC} timezone.
*
* @see org.hibernate.annotations.TimeZoneStorageType#NORMALIZE_UTC
* @see org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType
* @see org.hibernate.type.descriptor.jdbc.TimeUtcAsJdbcTimeJdbcType
*/
public static final int TIME_UTC = 3007;
// Interval types // Interval types
/** /**
@ -689,6 +701,8 @@ public class SqlTypes {
switch ( typeCode ) { switch ( typeCode ) {
case DATE: case DATE:
case TIME: case TIME:
case TIME_WITH_TIMEZONE:
case TIME_UTC:
case TIMESTAMP: case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
@ -728,6 +742,8 @@ public class SqlTypes {
public static boolean hasTimePart(int typeCode) { public static boolean hasTimePart(int typeCode) {
switch ( typeCode ) { switch ( typeCode ) {
case TIME: case TIME:
case TIME_WITH_TIMEZONE:
case TIME_UTC:
case TIMESTAMP: case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_UTC: case TIMESTAMP_UTC:

View File

@ -413,12 +413,40 @@ public final class StandardBasicTypes {
); );
/** /**
* The standard Hibernate type for mapping {@link OffsetTime} to JDBC {@link org.hibernate.type.SqlTypes#TIME TIME}. * The standard Hibernate type for mapping {@link OffsetTime} to JDBC {@link org.hibernate.type.SqlTypes#TIME_WITH_TIMEZONE TIME_WITH_TIMEZONE}.
*/ */
public static final BasicTypeReference<OffsetTime> OFFSET_TIME = new BasicTypeReference<>( public static final BasicTypeReference<OffsetTime> OFFSET_TIME = new BasicTypeReference<>(
"OffsetTime", "OffsetTime",
OffsetTime.class, OffsetTime.class,
// todo (6.0): why not TIME_WITH_TIMEZONE ? SqlTypes.TIME_WITH_TIMEZONE
);
/**
* The standard Hibernate type for mapping {@link OffsetTime} to JDBC {@link org.hibernate.type.SqlTypes#TIME_UTC TIME_UTC}.
* This maps to {@link org.hibernate.TimeZoneStorageStrategy#NORMALIZE_UTC}.
*/
public static final BasicTypeReference<OffsetTime> OFFSET_TIME_UTC = new BasicTypeReference<>(
"OffsetTimeUtc",
OffsetTime.class,
SqlTypes.TIME_UTC
);
/**
* The standard Hibernate type for mapping {@link OffsetTime} to JDBC {@link org.hibernate.type.SqlTypes#TIME_WITH_TIMEZONE TIME_WITH_TIMEZONE}.
* This maps to {@link org.hibernate.TimeZoneStorageStrategy#NATIVE}.
*/
public static final BasicTypeReference<OffsetTime> OFFSET_TIME_WITH_TIMEZONE = new BasicTypeReference<>(
"OffsetTimeWithTimezone",
OffsetTime.class,
SqlTypes.TIME_WITH_TIMEZONE
);
/**
* The standard Hibernate type for mapping {@link OffsetTime} to JDBC {@link org.hibernate.type.SqlTypes#TIME TIME}.
*/
public static final BasicTypeReference<OffsetTime> OFFSET_TIME_WITHOUT_TIMEZONE = new BasicTypeReference<>(
"OffsetTimeWithoutTimezone",
OffsetTime.class,
SqlTypes.TIME SqlTypes.TIME
); );
@ -1038,6 +1066,27 @@ public final class StandardBasicTypes {
OffsetTime.class.getSimpleName(), OffsetTime.class.getName() OffsetTime.class.getSimpleName(), OffsetTime.class.getName()
); );
handle(
OFFSET_TIME_UTC,
null,
basicTypeRegistry,
OFFSET_TIME_UTC.getName()
);
handle(
OFFSET_TIME_WITH_TIMEZONE,
null,
basicTypeRegistry,
OFFSET_TIME_WITH_TIMEZONE.getName()
);
handle(
OFFSET_TIME_WITHOUT_TIMEZONE,
null,
basicTypeRegistry,
OFFSET_TIME_WITHOUT_TIMEZONE.getName()
);
handle( handle(
ZONED_DATE_TIME, ZONED_DATE_TIME,
"org.hibernate.type.ZonedDateTimeType", "org.hibernate.type.ZonedDateTimeType",

View File

@ -456,12 +456,17 @@ public final class DateTimeUtils {
if ( defaultTimestampPrecision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) { if ( defaultTimestampPrecision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) {
return temporal; return temporal;
} }
final int precisionMask = pow10( 9 - defaultTimestampPrecision );
final int nano = temporal.get( ChronoField.NANO_OF_SECOND );
final int nanosToRound = nano % precisionMask;
final int finalNano = nano - nanosToRound + ( nanosToRound >= ( precisionMask >> 1 ) ? precisionMask : 0 );
//noinspection unchecked //noinspection unchecked
return (T) temporal.with( ChronoField.NANO_OF_SECOND, finalNano ); return (T) temporal.with(
ChronoField.NANO_OF_SECOND,
roundToPrecision( temporal.get( ChronoField.NANO_OF_SECOND ), defaultTimestampPrecision )
);
}
public static long roundToPrecision(int nano, int precision) {
final int precisionMask = pow10( 9 - precision );
final int nanosToRound = nano % precisionMask;
return nano - nanosToRound + ( nanosToRound >= ( precisionMask >> 1 ) ? precisionMask : 0 );
} }
private static int pow10(int exponent) { private static int pow10(int exponent) {

View File

@ -147,6 +147,6 @@ public class CalendarTimeJavaType extends AbstractTemporalJavaType<Calendar> {
@Override @Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return 0; //seconds (currently ignored since Dialects don't parameterize time type by precision) return dialect.getDefaultTimestampPrecision();
} }
} }

View File

@ -13,6 +13,7 @@ import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -22,6 +23,7 @@ import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.CharSequenceHelper; import org.hibernate.internal.util.CharSequenceHelper;
import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -127,9 +129,15 @@ public class JdbcTimeJavaType extends AbstractTemporalJavaType<Date> {
} }
if ( LocalTime.class.isAssignableFrom( type ) ) { if ( LocalTime.class.isAssignableFrom( type ) ) {
return value instanceof java.sql.Time final Time time = value instanceof java.sql.Time
? ( (java.sql.Time) value ).toLocalTime() ? ( (java.sql.Time) value )
: new java.sql.Time( value.getTime() ).toLocalTime(); : new java.sql.Time( value.getTime() );
final LocalTime localTime = time.toLocalTime();
final long millis = time.getTime() % 1000;
if ( millis == 0 ) {
return localTime;
}
return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L );
} }
if ( Time.class.isAssignableFrom( type ) ) { if ( Time.class.isAssignableFrom( type ) ) {
@ -174,7 +182,13 @@ public class JdbcTimeJavaType extends AbstractTemporalJavaType<Date> {
} }
if ( value instanceof LocalTime ) { if ( value instanceof LocalTime ) {
return Time.valueOf( (LocalTime) value ); final LocalTime localTime = (LocalTime) value;
final Time time = Time.valueOf( localTime );
if ( localTime.getNano() == 0 ) {
return time;
}
// Preserve milliseconds, which java.sql.Time supports
return new Time( time.getTime() + DateTimeUtils.roundToPrecision( localTime.getNano(), 3 ) );
} }
if ( value instanceof Date ) { if ( value instanceof Date ) {
@ -250,8 +264,7 @@ public class JdbcTimeJavaType extends AbstractTemporalJavaType<Date> {
@Override @Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
//seconds (currently ignored since Dialects don't parameterize time type by precision) return dialect.getDefaultTimestampPrecision();
return 0;
} }
@Override @Override

View File

@ -16,6 +16,7 @@ import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -23,6 +24,7 @@ import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -81,7 +83,12 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
} }
if ( Time.class.isAssignableFrom( type ) ) { if ( Time.class.isAssignableFrom( type ) ) {
return (X) Time.valueOf( value ); final Time time = Time.valueOf( value );
if ( value.getNano() == 0 ) {
return (X) time;
}
// Preserve milliseconds, which java.sql.Time supports
return (X) new Time( time.getTime() + DateTimeUtils.roundToPrecision( value.getNano(), 3 ) );
} }
// Oracle documentation says to set the Date to January 1, 1970 when convert from // Oracle documentation says to set the Date to January 1, 1970 when convert from
@ -122,6 +129,16 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
return (LocalTime) value; return (LocalTime) value;
} }
if (value instanceof Time) {
final Time time = (Time) value;
final LocalTime localTime = time.toLocalTime();
final long millis = time.getTime() % 1000;
if ( millis == 0 ) {
return localTime;
}
return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L );
}
if (value instanceof Timestamp) { if (value instanceof Timestamp) {
final Timestamp ts = (Timestamp) value; final Timestamp ts = (Timestamp) value;
return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalTime(); return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalTime();
@ -158,7 +175,7 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
@Override @Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return 0; return dialect.getDefaultTimestampPrecision();
} }
} }

View File

@ -16,6 +16,7 @@ import java.time.OffsetDateTime;
import java.time.OffsetTime; import java.time.OffsetTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -23,6 +24,7 @@ import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -49,8 +51,9 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
} }
@Override @Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
return context.getJdbcType( Types.TIME ); return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
.getDescriptor( stdIndicators.getDefaultZonedTimeSqlType() );
} }
@Override @Override
@ -87,13 +90,22 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
return (X) offsetTime.withOffsetSameInstant( getCurrentSystemOffset() ).toLocalTime(); return (X) offsetTime.withOffsetSameInstant( getCurrentSystemOffset() ).toLocalTime();
} }
if ( OffsetDateTime.class.isAssignableFrom( type ) ) {
return (X) offsetTime.atDate( LocalDate.EPOCH );
}
// for legacy types, we assume that the JDBC timezone is passed to JDBC // for legacy types, we assume that the JDBC timezone is passed to JDBC
// (since PS.setTime() and friends do accept a timezone passed as a Calendar) // (since PS.setTime() and friends do accept a timezone passed as a Calendar)
final OffsetTime jdbcOffsetTime = offsetTime.withOffsetSameInstant( getCurrentJdbcOffset(options) ); final OffsetTime jdbcOffsetTime = offsetTime.withOffsetSameInstant( getCurrentJdbcOffset(options) );
if ( Time.class.isAssignableFrom( type ) ) { if ( Time.class.isAssignableFrom( type ) ) {
return (X) Time.valueOf( jdbcOffsetTime.toLocalTime() ); final Time time = Time.valueOf( jdbcOffsetTime.toLocalTime() );
if ( jdbcOffsetTime.getNano() == 0 ) {
return (X) time;
}
// Preserve milliseconds, which java.sql.Time supports
return (X) new Time( time.getTime() + DateTimeUtils.roundToPrecision( jdbcOffsetTime.getNano(), 3 ) );
} }
final OffsetDateTime jdbcOffsetDateTime = jdbcOffsetTime.atDate( LocalDate.EPOCH ); final OffsetDateTime jdbcOffsetDateTime = jdbcOffsetTime.atDate( LocalDate.EPOCH );
@ -145,6 +157,10 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
return ((LocalTime) value).atOffset( getCurrentSystemOffset() ); return ((LocalTime) value).atOffset( getCurrentSystemOffset() );
} }
if ( value instanceof OffsetDateTime ) {
return ( (OffsetDateTime) value ).toOffsetTime();
}
/* /*
* Also, in order to fix HHH-13357, and to be consistent with the conversion to Time (see above), * Also, in order to fix HHH-13357, and to be consistent with the conversion to Time (see above),
* we set the offset to the current offset of the JVM (OffsetDateTime.now().getOffset()). * we set the offset to the current offset of the JVM (OffsetDateTime.now().getOffset()).
@ -165,8 +181,14 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
if (value instanceof Time) { if (value instanceof Time) {
final Time time = (Time) value; final Time time = (Time) value;
return time.toLocalTime().atOffset( getCurrentJdbcOffset(options) ) final OffsetTime offsetTime = time.toLocalTime()
.atOffset( getCurrentJdbcOffset( options) )
.withOffsetSameInstant( getCurrentSystemOffset() ); .withOffsetSameInstant( getCurrentSystemOffset() );
final long millis = time.getTime() % 1000;
if ( millis == 0 ) {
return offsetTime;
}
return offsetTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L );
} }
if (value instanceof Timestamp) { if (value instanceof Timestamp) {
@ -217,7 +239,7 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
@Override @Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return 0; return dialect.getDefaultTimestampPrecision();
} }
} }

View File

@ -6,116 +6,15 @@
*/ */
package org.hibernate.type.descriptor.jdbc; package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.util.Calendar;
import java.util.TimeZone;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/** /**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling. * Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
* *
* @deprecated Use {@link TimestampUtcAsJdbcTimestampJdbcType}
* @author Christian Beikov * @author Christian Beikov
*/ */
public class InstantAsTimestampJdbcType implements JdbcType { @Deprecated(forRemoval = true)
public class InstantAsTimestampJdbcType extends TimestampUtcAsJdbcTimestampJdbcType {
public static final InstantAsTimestampJdbcType INSTANCE = new InstantAsTimestampJdbcType(); public static final InstantAsTimestampJdbcType INSTANCE = new InstantAsTimestampJdbcType();
private static final Calendar UTC_CALENDAR = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
public InstantAsTimestampJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIMESTAMP;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIMESTAMP_UTC;
}
@Override
public String getFriendlyName() {
return "TIMESTAMP_UTC";
}
@Override
public String toString() {
return "TimestampUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Instant.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final Instant instant = javaType.unwrap( value, Instant.class, options );
st.setTimestamp( index, Timestamp.from( instant ), UTC_CALENDAR );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final Instant instant = javaType.unwrap( value, Instant.class, options );
st.setTimestamp( name, Timestamp.from( instant ), UTC_CALENDAR );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
final Timestamp timestamp = rs.getTimestamp( paramIndex, UTC_CALENDAR );
return javaType.wrap( timestamp == null ? null : timestamp.toInstant(), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
final Timestamp timestamp = statement.getTimestamp( index, UTC_CALENDAR );
return javaType.wrap( timestamp == null ? null : timestamp.toInstant(), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
final Timestamp timestamp = statement.getTimestamp( name, UTC_CALENDAR );
return javaType.wrap( timestamp == null ? null : timestamp.toInstant(), options );
}
};
}
} }

View File

@ -6,121 +6,15 @@
*/ */
package org.hibernate.type.descriptor.jdbc; package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/** /**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling. * Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
* *
* @deprecated Use {@link TimestampUtcAsOffsetDateTimeJdbcType}
* @author Christian Beikov * @author Christian Beikov
*/ */
public class InstantAsTimestampWithTimeZoneJdbcType implements JdbcType { @Deprecated(forRemoval = true)
public class InstantAsTimestampWithTimeZoneJdbcType extends TimestampUtcAsOffsetDateTimeJdbcType {
public static final InstantAsTimestampWithTimeZoneJdbcType INSTANCE = new InstantAsTimestampWithTimeZoneJdbcType(); public static final InstantAsTimestampWithTimeZoneJdbcType INSTANCE = new InstantAsTimestampWithTimeZoneJdbcType();
public InstantAsTimestampWithTimeZoneJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIMESTAMP_WITH_TIMEZONE;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIMESTAMP_UTC;
}
@Override
public String getFriendlyName() {
return "TIMESTAMP_UTC";
}
@Override
public String toString() {
return "TimestampUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return OffsetDateTime.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(
PreparedStatement st,
X value,
int index,
WrapperOptions wrapperOptions) throws SQLException {
final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( index, dateTime.withOffsetSameInstant( ZoneOffset.UTC ), Types.TIMESTAMP_WITH_TIMEZONE );
}
@Override
protected void doBind(
CallableStatement st,
X value,
String name,
WrapperOptions wrapperOptions)
throws SQLException {
final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( name, dateTime.withOffsetSameInstant( ZoneOffset.UTC ), Types.TIMESTAMP_WITH_TIMEZONE );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int position, WrapperOptions wrapperOptions) throws SQLException {
return javaType.wrap( rs.getObject( position, OffsetDateTime.class ), wrapperOptions );
}
@Override
protected X doExtract(CallableStatement statement, int position, WrapperOptions wrapperOptions) throws SQLException {
return javaType.wrap( statement.getObject( position, OffsetDateTime.class ), wrapperOptions );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions wrapperOptions) throws SQLException {
return javaType.wrap( statement.getObject( name, OffsetDateTime.class ), wrapperOptions );
}
};
}
} }

View File

@ -6,154 +6,15 @@
*/ */
package org.hibernate.type.descriptor.jdbc; package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/** /**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling. * Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
* *
* @deprecated Use {@link TimestampUtcAsInstantJdbcType}
* @author Christian Beikov * @author Christian Beikov
*/ */
public class InstantJdbcType implements JdbcType { @Deprecated(forRemoval = true)
public class InstantJdbcType extends TimestampUtcAsInstantJdbcType {
public static final InstantJdbcType INSTANCE = new InstantJdbcType(); public static final InstantJdbcType INSTANCE = new InstantJdbcType();
public InstantJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIMESTAMP_WITH_TIMEZONE;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIMESTAMP_UTC;
}
@Override
public String getFriendlyName() {
return "TIMESTAMP_UTC";
}
@Override
public String toString() {
return "TimestampUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Instant.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(
PreparedStatement st,
X value,
int index,
WrapperOptions wrapperOptions) throws SQLException {
try {
final Instant dateTime = javaType.unwrap( value, Instant.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( index, dateTime, Types.TIMESTAMP_WITH_TIMEZONE );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, wrapperOptions );
st.setTimestamp( index, timestamp );
}
}
@Override
protected void doBind(
CallableStatement st,
X value,
String name,
WrapperOptions wrapperOptions)
throws SQLException {
try {
final Instant dateTime = javaType.unwrap( value, Instant.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( name, dateTime, Types.TIMESTAMP_WITH_TIMEZONE );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, wrapperOptions );
st.setTimestamp( name, timestamp );
}
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int position, WrapperOptions wrapperOptions) throws SQLException {
try {
// supposed to be supported in JDBC 4.2
return javaType.wrap( rs.getObject( position, Instant.class ), wrapperOptions );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
return javaType.wrap( rs.getTimestamp( position ), wrapperOptions );
}
}
@Override
protected X doExtract(CallableStatement statement, int position, WrapperOptions wrapperOptions) throws SQLException {
try {
// supposed to be supported in JDBC 4.2
return javaType.wrap( statement.getObject( position, Instant.class ), wrapperOptions );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
return javaType.wrap( statement.getTimestamp( position ), wrapperOptions );
}
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions wrapperOptions) throws SQLException {
try {
// supposed to be supported in JDBC 4.2
return javaType.wrap( statement.getObject( name, Instant.class ), wrapperOptions );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
return javaType.wrap( statement.getTimestamp( name ), wrapperOptions );
}
}
};
}
} }

View File

@ -26,6 +26,8 @@ import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.type.SqlTypes.*;
/** /**
* Descriptor for the SQL/JDBC side of a value mapping. A {@code JdbcType} is * Descriptor for the SQL/JDBC side of a value mapping. A {@code JdbcType} is
* always coupled with a {@link JavaType} to describe the typing aspects of an * always coupled with a {@link JavaType} to describe the typing aspects of an
@ -193,32 +195,32 @@ public interface JdbcType extends Serializable {
default boolean isInteger() { default boolean isInteger() {
int typeCode = getDdlTypeCode(); int typeCode = getDdlTypeCode();
return SqlTypes.isIntegral(typeCode) return isIntegral(typeCode)
|| typeCode == Types.BIT; //HIGHLY DUBIOUS! || typeCode == BIT; //HIGHLY DUBIOUS!
} }
default boolean isFloat() { default boolean isFloat() {
return SqlTypes.isFloatOrRealOrDouble( getDdlTypeCode() ); return isFloatOrRealOrDouble( getDdlTypeCode() );
} }
default boolean isDecimal() { default boolean isDecimal() {
return SqlTypes.isNumericOrDecimal( getDdlTypeCode() ); return isNumericOrDecimal( getDdlTypeCode() );
} }
default boolean isNumber() { default boolean isNumber() {
return SqlTypes.isNumericType( getDdlTypeCode() ); return isNumericType( getDdlTypeCode() );
} }
default boolean isBinary() { default boolean isBinary() {
return SqlTypes.isBinaryType( getDdlTypeCode() ); return isBinaryType( getDdlTypeCode() );
} }
default boolean isString() { default boolean isString() {
return SqlTypes.isCharacterOrClobType( getDdlTypeCode() ); return isCharacterOrClobType( getDdlTypeCode() );
} }
default boolean isTemporal() { default boolean isTemporal() {
return SqlTypes.isTemporalType( getDdlTypeCode() ); return isTemporalType( getDdlTypeCode() );
} }
default boolean isLob() { default boolean isLob() {
@ -227,9 +229,9 @@ public interface JdbcType extends Serializable {
static boolean isLob(int jdbcTypeCode) { static boolean isLob(int jdbcTypeCode) {
switch ( jdbcTypeCode ) { switch ( jdbcTypeCode ) {
case SqlTypes.BLOB: case BLOB:
case SqlTypes.CLOB: case CLOB:
case SqlTypes.NCLOB: { case NCLOB: {
return true; return true;
} }
} }
@ -242,11 +244,11 @@ public interface JdbcType extends Serializable {
static boolean isNationalized(int jdbcTypeCode) { static boolean isNationalized(int jdbcTypeCode) {
switch ( jdbcTypeCode ) { switch ( jdbcTypeCode ) {
case SqlTypes.NCHAR: case NCHAR:
case SqlTypes.NVARCHAR: case NVARCHAR:
case SqlTypes.LONGNVARCHAR: case LONGNVARCHAR:
case SqlTypes.LONG32NVARCHAR: case LONG32NVARCHAR:
case SqlTypes.NCLOB: { case NCLOB: {
return true; return true;
} }
} }
@ -254,7 +256,7 @@ public interface JdbcType extends Serializable {
} }
default boolean isInterval() { default boolean isInterval() {
return SqlTypes.isIntervalType( getDdlTypeCode() ); return isIntervalType( getDdlTypeCode() );
} }
default CastType getCastType() { default CastType getCastType() {
@ -263,40 +265,42 @@ public interface JdbcType extends Serializable {
static CastType getCastType(int typeCode) { static CastType getCastType(int typeCode) {
switch ( typeCode ) { switch ( typeCode ) {
case Types.INTEGER: case INTEGER:
case Types.TINYINT: case TINYINT:
case Types.SMALLINT: case SMALLINT:
return CastType.INTEGER; return CastType.INTEGER;
case Types.BIGINT: case BIGINT:
return CastType.LONG; return CastType.LONG;
case Types.FLOAT: case FLOAT:
case Types.REAL: case REAL:
return CastType.FLOAT; return CastType.FLOAT;
case Types.DOUBLE: case DOUBLE:
return CastType.DOUBLE; return CastType.DOUBLE;
case Types.CHAR: case CHAR:
case Types.NCHAR: case NCHAR:
case Types.VARCHAR: case VARCHAR:
case Types.NVARCHAR: case NVARCHAR:
case Types.LONGVARCHAR: case LONGVARCHAR:
case Types.LONGNVARCHAR: case LONGNVARCHAR:
return CastType.STRING; return CastType.STRING;
case Types.CLOB: case CLOB:
return CastType.CLOB; return CastType.CLOB;
case Types.BOOLEAN: case BOOLEAN:
return CastType.BOOLEAN; return CastType.BOOLEAN;
case Types.DECIMAL: case DECIMAL:
case Types.NUMERIC: case NUMERIC:
return CastType.FIXED; return CastType.FIXED;
case Types.DATE: case DATE:
return CastType.DATE; return CastType.DATE;
case Types.TIME: case TIME:
case TIME_UTC:
case TIME_WITH_TIMEZONE:
return CastType.TIME; return CastType.TIME;
case Types.TIMESTAMP: case TIMESTAMP:
return CastType.TIMESTAMP; return CastType.TIMESTAMP;
case Types.TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return CastType.OFFSET_TIMESTAMP; return CastType.OFFSET_TIMESTAMP;
case Types.NULL: case NULL:
return CastType.NULL; return CastType.NULL;
default: default:
return CastType.OTHER; return CastType.OTHER;

View File

@ -25,7 +25,7 @@ public class JdbcTypeFamilyInformation {
BINARY( Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY/*, SqlTypes.LONG32VARBINARY*/ ), BINARY( Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY/*, SqlTypes.LONG32VARBINARY*/ ),
NUMERIC( Types.BIGINT, Types.DECIMAL, Types.DOUBLE, Types.FLOAT, Types.INTEGER, Types.NUMERIC, Types.REAL, Types.SMALLINT, Types.TINYINT ), NUMERIC( Types.BIGINT, Types.DECIMAL, Types.DOUBLE, Types.FLOAT, Types.INTEGER, Types.NUMERIC, Types.REAL, Types.SMALLINT, Types.TINYINT ),
CHARACTER( Types.CHAR, Types.LONGNVARCHAR, Types.LONGVARCHAR, /*SqlTypes.LONG32NVARCHAR ,SqlTypes.LONG32VARCHAR,*/ Types.NCHAR, Types.NVARCHAR, Types.VARCHAR ), CHARACTER( Types.CHAR, Types.LONGNVARCHAR, Types.LONGVARCHAR, /*SqlTypes.LONG32NVARCHAR ,SqlTypes.LONG32VARCHAR,*/ Types.NCHAR, Types.NVARCHAR, Types.VARCHAR ),
DATETIME( Types.DATE, Types.TIME, Types.TIMESTAMP ), DATETIME( Types.DATE, Types.TIME, Types.TIME_WITH_TIMEZONE, Types.TIMESTAMP, Types.TIMESTAMP_WITH_TIMEZONE ),
CLOB( Types.CLOB, Types.NCLOB ); CLOB( Types.CLOB, Types.NCLOB );
private final int[] typeCodes; private final int[] typeCodes;

View File

@ -193,6 +193,28 @@ public interface JdbcTypeIndicators {
return getTypeConfiguration().getCurrentBaseSqlTypeIndicators(); return getTypeConfiguration().getCurrentBaseSqlTypeIndicators();
} }
/**
* @return the SQL column type used for storing times under the
* given {@linkplain TimeZoneStorageStrategy storage strategy}
*
* @see SqlTypes#TIME_WITH_TIMEZONE
* @see SqlTypes#TIME
* @see SqlTypes#TIME_UTC
*/
static int getZonedTimeSqlType(TimeZoneStorageStrategy storageStrategy) {
switch ( storageStrategy ) {
case NATIVE:
return SqlTypes.TIME_WITH_TIMEZONE;
case COLUMN:
case NORMALIZE:
return SqlTypes.TIME;
case NORMALIZE_UTC:
return SqlTypes.TIME_UTC;
default:
throw new AssertionFailure( "unknown time zone storage strategy" );
}
}
/** /**
* @return the SQL column type used for storing datetimes under the * @return the SQL column type used for storing datetimes under the
* given {@linkplain TimeZoneStorageStrategy storage strategy} * given {@linkplain TimeZoneStorageStrategy storage strategy}
@ -216,6 +238,18 @@ public interface JdbcTypeIndicators {
} }
} }
/**
* @return the SQL column type used for storing datetimes under the
* default {@linkplain TimeZoneStorageStrategy storage strategy}
*
* @see SqlTypes#TIME_WITH_TIMEZONE
* @see SqlTypes#TIME
* @see SqlTypes#TIME_UTC
*/
default int getDefaultZonedTimeSqlType() {
return getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
}
/** /**
* @return the SQL column type used for storing datetimes under the * @return the SQL column type used for storing datetimes under the
* default {@linkplain TimeZoneStorageStrategy storage strategy} * default {@linkplain TimeZoneStorageStrategy storage strategy}
@ -228,7 +262,7 @@ public interface JdbcTypeIndicators {
final TemporalType temporalPrecision = getTemporalPrecision(); final TemporalType temporalPrecision = getTemporalPrecision();
switch ( temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision ) { switch ( temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision ) {
case TIME: case TIME:
return Types.TIME; return getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
case DATE: case DATE:
return Types.DATE; return Types.DATE;
case TIMESTAMP: case TIMESTAMP:

View File

@ -26,6 +26,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Calendar; import java.util.Calendar;
import java.util.UUID; import java.util.UUID;
@ -124,6 +125,7 @@ public class JdbcTypeJavaClassMappings {
workMap.put( Time.class, SqlTypes.TIME ); workMap.put( Time.class, SqlTypes.TIME );
workMap.put( Timestamp.class, SqlTypes.TIMESTAMP ); workMap.put( Timestamp.class, SqlTypes.TIMESTAMP );
workMap.put( LocalTime.class, SqlTypes.TIME ); workMap.put( LocalTime.class, SqlTypes.TIME );
workMap.put( OffsetTime.class, SqlTypes.TIME_WITH_TIMEZONE );
workMap.put( LocalDate.class, SqlTypes.DATE ); workMap.put( LocalDate.class, SqlTypes.DATE );
workMap.put( LocalDateTime.class, SqlTypes.TIMESTAMP ); workMap.put( LocalDateTime.class, SqlTypes.TIMESTAMP );
workMap.put( OffsetDateTime.class, SqlTypes.TIMESTAMP_WITH_TIMEZONE ); workMap.put( OffsetDateTime.class, SqlTypes.TIMESTAMP_WITH_TIMEZONE );
@ -181,6 +183,8 @@ public class JdbcTypeJavaClassMappings {
workMap.put( SqlTypes.DATE, java.sql.Date.class ); workMap.put( SqlTypes.DATE, java.sql.Date.class );
workMap.put( SqlTypes.TIME, Time.class ); workMap.put( SqlTypes.TIME, Time.class );
workMap.put( SqlTypes.TIMESTAMP, Timestamp.class ); workMap.put( SqlTypes.TIMESTAMP, Timestamp.class );
workMap.put( SqlTypes.TIME_WITH_TIMEZONE, OffsetTime.class );
workMap.put( SqlTypes.TIMESTAMP_WITH_TIMEZONE, OffsetDateTime.class );
workMap.put( SqlTypes.BLOB, Blob.class ); workMap.put( SqlTypes.BLOB, Blob.class );
workMap.put( SqlTypes.CLOB, Clob.class ); workMap.put( SqlTypes.CLOB, Clob.class );
workMap.put( SqlTypes.NCLOB, NClob.class ); workMap.put( SqlTypes.NCLOB, NClob.class );

View File

@ -0,0 +1,121 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Types;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.util.Calendar;
import java.util.TimeZone;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIME_UTC TIME_UTC} handling.
*
* @author Christian Beikov
*/
public class TimeUtcAsJdbcTimeJdbcType implements JdbcType {
public static final TimeUtcAsJdbcTimeJdbcType INSTANCE = new TimeUtcAsJdbcTimeJdbcType();
private static final Calendar UTC_CALENDAR = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
public TimeUtcAsJdbcTimeJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIME;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIME_UTC;
}
@Override
public String getFriendlyName() {
return "TIME_UTC";
}
@Override
public String toString() {
return "TimeUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( OffsetTime.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return OffsetTime.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIME );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final OffsetTime offsetTime = javaType.unwrap( value, OffsetTime.class, options );
st.setTime( index, Time.valueOf( offsetTime.withOffsetSameInstant( ZoneOffset.UTC ).toLocalTime() ), UTC_CALENDAR );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final OffsetTime offsetTime = javaType.unwrap( value, OffsetTime.class, options );
st.setTime( name, Time.valueOf( offsetTime.withOffsetSameInstant( ZoneOffset.UTC ).toLocalTime() ), UTC_CALENDAR );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
final Time time = rs.getTime( paramIndex, UTC_CALENDAR );
return javaType.wrap( time == null ? null : time.toLocalTime().atOffset( ZoneOffset.UTC ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
final Time time = statement.getTime( index, UTC_CALENDAR );
return javaType.wrap( time == null ? null : time.toLocalTime().atOffset( ZoneOffset.UTC ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
final Time time = statement.getTime( name, UTC_CALENDAR );
return javaType.wrap( time == null ? null : time.toLocalTime().atOffset( ZoneOffset.UTC ), options );
}
};
}
}

View File

@ -0,0 +1,114 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIME_UTC TIME_UTC} handling.
*
* @author Christian Beikov
*/
public class TimeUtcAsOffsetTimeJdbcType implements JdbcType {
public static final TimeUtcAsOffsetTimeJdbcType INSTANCE = new TimeUtcAsOffsetTimeJdbcType();
public TimeUtcAsOffsetTimeJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIME_WITH_TIMEZONE;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIME_UTC;
}
@Override
public String getFriendlyName() {
return "TIME_UTC";
}
@Override
public String toString() {
return "TimeUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( OffsetTime.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return OffsetTime.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIME );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final OffsetTime offsetTime = javaType.unwrap( value, OffsetTime.class, options );
st.setObject( index, offsetTime.withOffsetSameInstant( ZoneOffset.UTC ), Types.TIME_WITH_TIMEZONE );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final OffsetTime offsetTime = javaType.unwrap( value, OffsetTime.class, options );
st.setObject( name, offsetTime.withOffsetSameInstant( ZoneOffset.UTC ), Types.TIME_WITH_TIMEZONE );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaType.wrap( rs.getObject( paramIndex, OffsetTime.class ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getObject( index, OffsetTime.class ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getObject( name, OffsetTime.class ), options );
}
};
}
}

View File

@ -0,0 +1,108 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.OffsetTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIME_WITH_TIMEZONE TIME_WITH_TIMEZONE} handling.
*
* @author Christian Beikov
*/
public class TimeWithTimeZoneJdbcType implements JdbcType {
public static final TimeWithTimeZoneJdbcType INSTANCE = new TimeWithTimeZoneJdbcType();
public TimeWithTimeZoneJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIME_WITH_TIMEZONE;
}
@Override
public String getFriendlyName() {
return "TIME_WITH_TIMEZONE";
}
@Override
public String toString() {
return "TimeWithTimezoneDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( OffsetTime.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return OffsetTime.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIME );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final OffsetTime offsetTime = javaType.unwrap( value, OffsetTime.class, options );
st.setObject( index, offsetTime, Types.TIME_WITH_TIMEZONE );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final OffsetTime offsetTime = javaType.unwrap( value, OffsetTime.class, options );
st.setObject( name, offsetTime, Types.TIME_WITH_TIMEZONE );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaType.wrap( rs.getObject( paramIndex, OffsetTime.class ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getObject( index, OffsetTime.class ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getObject( name, OffsetTime.class ), options );
}
};
}
}

View File

@ -0,0 +1,159 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.util.Calendar;
import java.util.TimeZone;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
*
* @author Christian Beikov
*/
public class TimestampUtcAsInstantJdbcType implements JdbcType {
public static final TimestampUtcAsInstantJdbcType INSTANCE = new TimestampUtcAsInstantJdbcType();
private static final Calendar UTC_CALENDAR = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
public TimestampUtcAsInstantJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIMESTAMP_WITH_TIMEZONE;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIMESTAMP_UTC;
}
@Override
public String getFriendlyName() {
return "TIMESTAMP_UTC";
}
@Override
public String toString() {
return "TimestampUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Instant.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(
PreparedStatement st,
X value,
int index,
WrapperOptions wrapperOptions) throws SQLException {
final Instant instant = javaType.unwrap( value, Instant.class, wrapperOptions );
try {
// supposed to be supported in JDBC 4.2
st.setObject( index, instant, Types.TIMESTAMP_WITH_TIMEZONE );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
st.setTimestamp( index, Timestamp.from( instant ), UTC_CALENDAR );
}
}
@Override
protected void doBind(
CallableStatement st,
X value,
String name,
WrapperOptions wrapperOptions)
throws SQLException {
final Instant instant = javaType.unwrap( value, Instant.class, wrapperOptions );
try {
// supposed to be supported in JDBC 4.2
st.setObject( name, instant, Types.TIMESTAMP_WITH_TIMEZONE );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
st.setTimestamp( name, Timestamp.from( instant ), UTC_CALENDAR );
}
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int position, WrapperOptions wrapperOptions) throws SQLException {
try {
// supposed to be supported in JDBC 4.2
return javaType.wrap( rs.getObject( position, Instant.class ), wrapperOptions );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
return javaType.wrap( rs.getTimestamp( position, UTC_CALENDAR ), wrapperOptions );
}
}
@Override
protected X doExtract(CallableStatement statement, int position, WrapperOptions wrapperOptions) throws SQLException {
try {
// supposed to be supported in JDBC 4.2
return javaType.wrap( statement.getObject( position, Instant.class ), wrapperOptions );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
return javaType.wrap( statement.getTimestamp( position, UTC_CALENDAR ), wrapperOptions );
}
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions wrapperOptions) throws SQLException {
try {
// supposed to be supported in JDBC 4.2
return javaType.wrap( statement.getObject( name, Instant.class ), wrapperOptions );
}
catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp
return javaType.wrap( statement.getTimestamp( name, UTC_CALENDAR ), wrapperOptions );
}
}
};
}
}

View File

@ -0,0 +1,120 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.util.Calendar;
import java.util.TimeZone;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
*
* @author Christian Beikov
*/
public class TimestampUtcAsJdbcTimestampJdbcType implements JdbcType {
public static final TimestampUtcAsJdbcTimestampJdbcType INSTANCE = new TimestampUtcAsJdbcTimestampJdbcType();
private static final Calendar UTC_CALENDAR = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
public TimestampUtcAsJdbcTimestampJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIMESTAMP;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIMESTAMP_UTC;
}
@Override
public String getFriendlyName() {
return "TIMESTAMP_UTC";
}
@Override
public String toString() {
return "TimestampUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Instant.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final Instant instant = javaType.unwrap( value, Instant.class, options );
st.setTimestamp( index, Timestamp.from( instant ), UTC_CALENDAR );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final Instant instant = javaType.unwrap( value, Instant.class, options );
st.setTimestamp( name, Timestamp.from( instant ), UTC_CALENDAR );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
final Timestamp timestamp = rs.getTimestamp( paramIndex, UTC_CALENDAR );
return javaType.wrap( timestamp == null ? null : timestamp.toInstant(), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
final Timestamp timestamp = statement.getTimestamp( index, UTC_CALENDAR );
return javaType.wrap( timestamp == null ? null : timestamp.toInstant(), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
final Timestamp timestamp = statement.getTimestamp( name, UTC_CALENDAR );
return javaType.wrap( timestamp == null ? null : timestamp.toInstant(), options );
}
};
}
}

View File

@ -0,0 +1,125 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
/**
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
*
* @author Christian Beikov
*/
public class TimestampUtcAsOffsetDateTimeJdbcType implements JdbcType {
public static final TimestampUtcAsOffsetDateTimeJdbcType INSTANCE = new TimestampUtcAsOffsetDateTimeJdbcType();
public TimestampUtcAsOffsetDateTimeJdbcType() {
}
@Override
public int getJdbcTypeCode() {
return Types.TIMESTAMP_WITH_TIMEZONE;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TIMESTAMP_UTC;
}
@Override
public String getFriendlyName() {
return "TIMESTAMP_UTC";
}
@Override
public String toString() {
return "TimestampUtcDescriptor";
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return OffsetDateTime.class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(
PreparedStatement st,
X value,
int index,
WrapperOptions wrapperOptions) throws SQLException {
final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( index, dateTime.withOffsetSameInstant( ZoneOffset.UTC ), Types.TIMESTAMP_WITH_TIMEZONE );
}
@Override
protected void doBind(
CallableStatement st,
X value,
String name,
WrapperOptions wrapperOptions)
throws SQLException {
final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( name, dateTime.withOffsetSameInstant( ZoneOffset.UTC ), Types.TIMESTAMP_WITH_TIMEZONE );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int position, WrapperOptions wrapperOptions) throws SQLException {
return javaType.wrap( rs.getObject( position, OffsetDateTime.class ), wrapperOptions );
}
@Override
protected X doExtract(CallableStatement statement, int position, WrapperOptions wrapperOptions) throws SQLException {
return javaType.wrap( statement.getObject( position, OffsetDateTime.class ), wrapperOptions );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions wrapperOptions) throws SQLException {
return javaType.wrap( statement.getObject( name, OffsetDateTime.class ), wrapperOptions );
}
};
}
}

View File

@ -22,6 +22,7 @@ import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Calendar;
/** /**
* Descriptor for {@link Types#TIMESTAMP_WITH_TIMEZONE TIMESTAMP_WITH_TIMEZONE} handling. * Descriptor for {@link Types#TIMESTAMP_WITH_TIMEZONE TIMESTAMP_WITH_TIMEZONE} handling.
@ -75,16 +76,24 @@ public class TimestampWithTimeZoneJdbcType implements JdbcType {
PreparedStatement st, PreparedStatement st,
X value, X value,
int index, int index,
WrapperOptions wrapperOptions) throws SQLException { WrapperOptions options) throws SQLException {
try { try {
final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions ); final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options );
// supposed to be supported in JDBC 4.2 // supposed to be supported in JDBC 4.2
st.setObject( index, dateTime, Types.TIMESTAMP_WITH_TIMEZONE ); st.setObject( index, dateTime, Types.TIMESTAMP_WITH_TIMEZONE );
} }
catch (SQLException|AbstractMethodError e) { catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp // fall back to treating it as a JDBC Timestamp
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, wrapperOptions ); final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
st.setTimestamp( index, timestamp ); if ( value instanceof Calendar ) {
st.setTimestamp( index, timestamp, (Calendar) value );
}
else if ( options.getJdbcTimeZone() != null ) {
st.setTimestamp( index, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ) );
}
else {
st.setTimestamp( index, timestamp );
}
} }
} }
@ -93,17 +102,25 @@ public class TimestampWithTimeZoneJdbcType implements JdbcType {
CallableStatement st, CallableStatement st,
X value, X value,
String name, String name,
WrapperOptions wrapperOptions) WrapperOptions options)
throws SQLException { throws SQLException {
try { try {
final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions ); final OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options );
// supposed to be supported in JDBC 4.2 // supposed to be supported in JDBC 4.2
st.setObject( name, dateTime, Types.TIMESTAMP_WITH_TIMEZONE ); st.setObject( name, dateTime, Types.TIMESTAMP_WITH_TIMEZONE );
} }
catch (SQLException|AbstractMethodError e) { catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp // fall back to treating it as a JDBC Timestamp
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, wrapperOptions ); final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
st.setTimestamp( name, timestamp ); if ( value instanceof Calendar ) {
st.setTimestamp( name, timestamp, (Calendar) value );
}
else if ( options.getJdbcTimeZone() != null ) {
st.setTimestamp( name, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ) );
}
else {
st.setTimestamp( name, timestamp );
}
} }
} }
}; };
@ -113,38 +130,44 @@ public class TimestampWithTimeZoneJdbcType implements JdbcType {
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) { public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) { return new BasicExtractor<>( javaType, this ) {
@Override @Override
protected X doExtract(ResultSet rs, int position, WrapperOptions wrapperOptions) throws SQLException { protected X doExtract(ResultSet rs, int position, WrapperOptions options) throws SQLException {
try { try {
// supposed to be supported in JDBC 4.2 // supposed to be supported in JDBC 4.2
return javaType.wrap( rs.getObject( position, OffsetDateTime.class ), wrapperOptions ); return javaType.wrap( rs.getObject( position, OffsetDateTime.class ), options );
} }
catch (SQLException|AbstractMethodError e) { catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp // fall back to treating it as a JDBC Timestamp
return javaType.wrap( rs.getTimestamp( position ), wrapperOptions ); return options.getJdbcTimeZone() != null ?
javaType.wrap( rs.getTimestamp( position, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaType.wrap( rs.getTimestamp( position ), options );
} }
} }
@Override @Override
protected X doExtract(CallableStatement statement, int position, WrapperOptions wrapperOptions) throws SQLException { protected X doExtract(CallableStatement statement, int position, WrapperOptions options) throws SQLException {
try { try {
// supposed to be supported in JDBC 4.2 // supposed to be supported in JDBC 4.2
return javaType.wrap( statement.getObject( position, OffsetDateTime.class ), wrapperOptions ); return javaType.wrap( statement.getObject( position, OffsetDateTime.class ), options );
} }
catch (SQLException|AbstractMethodError e) { catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp // fall back to treating it as a JDBC Timestamp
return javaType.wrap( statement.getTimestamp( position ), wrapperOptions ); return options.getJdbcTimeZone() != null ?
javaType.wrap( statement.getTimestamp( position, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaType.wrap( statement.getTimestamp( position ), options );
} }
} }
@Override @Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions wrapperOptions) throws SQLException { protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
try { try {
// supposed to be supported in JDBC 4.2 // supposed to be supported in JDBC 4.2
return javaType.wrap( statement.getObject( name, OffsetDateTime.class ), wrapperOptions ); return javaType.wrap( statement.getObject( name, OffsetDateTime.class ), options );
} }
catch (SQLException|AbstractMethodError e) { catch (SQLException|AbstractMethodError e) {
// fall back to treating it as a JDBC Timestamp // fall back to treating it as a JDBC Timestamp
return javaType.wrap( statement.getTimestamp( name ), wrapperOptions ); return options.getJdbcTimeZone() != null ?
javaType.wrap( statement.getTimestamp( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaType.wrap( statement.getTimestamp( name ), options );
} }
} }
}; };

View File

@ -28,6 +28,7 @@ import org.hibernate.type.descriptor.jdbc.RealJdbcType;
import org.hibernate.type.descriptor.jdbc.RowIdJdbcType; import org.hibernate.type.descriptor.jdbc.RowIdJdbcType;
import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeJdbcType; import org.hibernate.type.descriptor.jdbc.TimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType; import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType;
@ -64,6 +65,7 @@ public class JdbcTypeBaseline {
target.addDescriptor( TimestampJdbcType.INSTANCE ); target.addDescriptor( TimestampJdbcType.INSTANCE );
target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE ); target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( TimeJdbcType.INSTANCE ); target.addDescriptor( TimeJdbcType.INSTANCE );
target.addDescriptor( TimeWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( BinaryJdbcType.INSTANCE ); target.addDescriptor( BinaryJdbcType.INSTANCE );
target.addDescriptor( VarbinaryJdbcType.INSTANCE ); target.addDescriptor( VarbinaryJdbcType.INSTANCE );

View File

@ -56,7 +56,7 @@ public class DdlTypeImpl implements DdlType {
final int paren = typeNamePattern.indexOf( '(' ); final int paren = typeNamePattern.indexOf( '(' );
if ( paren > 0 ) { if ( paren > 0 ) {
final int parenEnd = typeNamePattern.lastIndexOf( ')' ); final int parenEnd = typeNamePattern.lastIndexOf( ')' );
return parenEnd == typeNamePattern.length() return parenEnd + 1 == typeNamePattern.length()
? typeNamePattern.substring( 0, paren ) ? typeNamePattern.substring( 0, paren )
: ( typeNamePattern.substring( 0, paren ) + typeNamePattern.substring( parenEnd + 1 ) ); : ( typeNamePattern.substring( 0, paren ) + typeNamePattern.substring( parenEnd + 1 ) );
} }

View File

@ -155,6 +155,9 @@ public class DdlTypeRegistry implements Serializable {
return getTypeName( typeCode, Size.precision( dialect.getFloatPrecision() ) ); return getTypeName( typeCode, Size.precision( dialect.getFloatPrecision() ) );
case SqlTypes.DOUBLE: case SqlTypes.DOUBLE:
return getTypeName( typeCode, Size.precision( dialect.getDoublePrecision() ) ); return getTypeName( typeCode, Size.precision( dialect.getDoublePrecision() ) );
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP: case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE: case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC: case SqlTypes.TIMESTAMP_UTC:

View File

@ -776,6 +776,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return TemporalType.TIMESTAMP; return TemporalType.TIMESTAMP;
case SqlTypes.TIME: case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return TemporalType.TIME; return TemporalType.TIME;
case SqlTypes.DATE: case SqlTypes.DATE:
return TemporalType.DATE; return TemporalType.DATE;

View File

@ -0,0 +1,62 @@
/*
* 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 http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.usertype.internal;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import org.hibernate.HibernateException;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.ValueAccess;
import org.hibernate.type.SqlTypes;
/**
* @author Christian Beikov
*/
public class OffsetTimeCompositeUserType extends AbstractTimeZoneStorageCompositeUserType<OffsetTime> {
public static final String LOCAL_TIME_NAME = "utcTime";
@Override
public Object getPropertyValue(OffsetTime component, int property) throws HibernateException {
switch ( property ) {
case 0:
return component.withOffsetSameInstant( ZoneOffset.UTC );
case 1:
return component.getOffset();
}
return null;
}
@Override
public OffsetTime instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) {
final OffsetTime utcTime = values.getValue( 0, OffsetTime.class );
final ZoneOffset zoneOffset = values.getValue( 1, ZoneOffset.class );
return utcTime == null || zoneOffset == null ? null : utcTime.withOffsetSameInstant( zoneOffset );
}
@Override
public Class<?> embeddable() {
return OffsetTimeEmbeddable.class;
}
@Override
public Class<OffsetTime> returnedClass() {
return OffsetTime.class;
}
public static class OffsetTimeEmbeddable {
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
private OffsetTime utcTime;
@JdbcTypeCode( SqlTypes.INTEGER )
private ZoneOffset zoneOffset;
}
}

View File

@ -11,8 +11,13 @@ import java.time.OffsetTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.List; import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.testing.orm.junit.DialectContext;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -25,11 +30,15 @@ import jakarta.persistence.TypedQuery;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@Jpa( @Jpa(
annotatedClasses = LocalTimeTest.TestEntity.class annotatedClasses = LocalTimeTest.TestEntity.class,
properties = @Setting(name = AvailableSettings.TIMEZONE_DEFAULT_STORAGE, value = "NORMALIZE")
) )
public class LocalTimeTest { public class LocalTimeTest {
private static final LocalTime LOCAL_TIME = LocalTime.now(); private static final LocalTime LOCAL_TIME = DateTimeUtils.roundToDefaultPrecision(
LocalTime.now(),
DialectContext.getDialect()
);
private static final OffsetTime OFFSET_TIME = OffsetTime.of( LOCAL_TIME, ZoneOffset.ofHours( 2 ) ); private static final OffsetTime OFFSET_TIME = OffsetTime.of( LOCAL_TIME, ZoneOffset.ofHours( 2 ) );

View File

@ -26,6 +26,9 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.HSQLDialect;
@ -41,6 +44,12 @@ import org.junit.runners.Parameterized;
*/ */
public class OffsetTimeTest extends AbstractJavaTimeTypeTest<OffsetTime, OffsetTimeTest.EntityWithOffsetTime> { public class OffsetTimeTest extends AbstractJavaTimeTypeTest<OffsetTime, OffsetTimeTest.EntityWithOffsetTime> {
@Override
protected void configure(Configuration configuration) {
super.configure(configuration);
configuration.setProperty( AvailableSettings.TIMEZONE_DEFAULT_STORAGE, TimeZoneStorageType.NORMALIZE.toString() );
}
private static class ParametersBuilder extends AbstractParametersBuilder<ParametersBuilder> { private static class ParametersBuilder extends AbstractParametersBuilder<ParametersBuilder> {
public ParametersBuilder add(int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) { public ParametersBuilder add(int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) {
if ( !isNanosecondPrecisionSupported() ) { if ( !isNanosecondPrecisionSupported() ) {

View File

@ -14,6 +14,27 @@ earlier versions, see any other pertinent migration guides as well.
* link:{docsBase}/6.1/migration-guide/migration-guide.html[6.1 Migration guide] * link:{docsBase}/6.1/migration-guide/migration-guide.html[6.1 Migration guide]
* link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide] * link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide]
[[ddl-changes]]
== DDL type changes
[[ddl-offset-time]]
=== OffsetTime mapping changes
`OffsetTime` now depends on `@TimeZoneStorage` and the `hibernate.timezone.default_storage` setting.
Since the default for this setting is now `TimeZoneStorageType.DEFAULT`, this means that the DDL expectations for such columns changed.
If the target database supports time zone types natively like H2, Oracle, SQL Server and DB2 z/OS,
the type code `SqlTypes.TIME_WITH_TIMEZONE` is now used, which maps to the DDL type `time with time zone`.
Due to this change, schema validation errors could occur on existing databases.
The migration to `time with time zone` requires a migration expression like `cast(old as time with time zone)`
which will interpret the previous time as local time and compute the offset for the `time with time zone` based on the current date
and time zone settings of your database session.
If the target database does not support time zone types natively, Hibernate behaves just like before.
To retain backwards compatibility, configure the setting `hibernate.timezone.default_storage` to `NORMALIZE`.
[[ddl-changes]] [[ddl-changes]]
== DDL type changes == DDL type changes