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 30943e1214
commit 41bec6d5f9
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.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@ -18,6 +20,7 @@ import org.hibernate.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
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"))
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(
LocalDateTime.of(
2022,
@ -69,11 +81,12 @@ public class TimeZoneStorageMappingTests {
),
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" );
@BeforeEach
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
@ -109,26 +122,40 @@ public class TimeZoneStorageMappingTests {
session -> {
List<Tuple> resultList = session.createQuery(
"select " +
"e.offsetTime" + suffix + ", " +
"e.offsetDateTime" + suffix + ", " +
"e.zonedDateTime" + suffix + ", " +
"extract(offset from e.offsetTime" + suffix + "), " +
"extract(offset from e.offsetDateTime" + suffix + "), " +
"extract(offset from e.zonedDateTime" + suffix + "), " +
"e.offsetTime" + suffix + " + 1 hour, " +
"e.offsetDateTime" + 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.zonedDateTime" + suffix + " + 1 hour - e.zonedDateTime" + suffix + ", " +
"1 from TimeZoneStorageEntity e " +
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
Tuple.class
).getResultList();
assertThat( resultList.get( 0 ).get( 0, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME ) );
assertThat( resultList.get( 0 ).get( 1, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME ) );
assertThat( resultList.get( 0 ).get( 2, ZoneOffset.class ), Matchers.is( OFFSET_DATE_TIME.getOffset() ) );
assertThat( resultList.get( 0 ).get( 3, ZoneOffset.class ), Matchers.is( ZONED_DATE_TIME.getOffset() ) );
assertThat( resultList.get( 0 ).get( 4, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME.plusHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 5, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME.plusHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 6, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 7, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
assertThat( resultList.get( 0 ).get( 0, OffsetTime.class ), Matchers.is( OFFSET_TIME ) );
assertThat( resultList.get( 0 ).get( 1, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME ) );
assertThat( resultList.get( 0 ).get( 2, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME ) );
if ( !( scope.getSessionFactory().getJdbcServices().getDialect() instanceof H2Dialect) ) {
// H2 bug: https://github.com/h2database/h2database/issues/3757
assertThat(
resultList.get( 0 ).get( 3, ZoneOffset.class ),
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 -> {
List<Tuple> resultList = session.createQuery(
"select " +
"format(e.offsetTime" + suffix + " as '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'), " +
"1 from TimeZoneStorageEntity e " +
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
Tuple.class
).getResultList();
assertThat( resultList.get( 0 ).get( 0, String.class ), Matchers.is( FORMATTER.format( OFFSET_DATE_TIME ) ) );
assertThat( resultList.get( 0 ).get( 1, String.class ), Matchers.is( FORMATTER.format( ZONED_DATE_TIME ) ) );
if ( !( scope.getSessionFactory().getJdbcServices().getDialect() instanceof H2Dialect) ) {
// 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(
session -> {
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
).getResultList();
assertThat( resultList.get( 0 ).get( 0, OffsetDateTime.class ).toInstant(), Matchers.is( OFFSET_DATE_TIME.toInstant() ) );
assertThat( resultList.get( 0 ).get( 1, ZonedDateTime.class ).toInstant(), Matchers.is( ZONED_DATE_TIME.toInstant() ) );
assertThat( resultList.get( 0 ).get( 2, 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( 0, OffsetTime.class ).toLocalTime(), Matchers.is( OFFSET_TIME.withOffsetSameInstant( JVM_TIMEZONE_OFFSET ).toLocalTime() ) );
assertThat( resultList.get( 0 ).get( 0, OffsetTime.class ).getOffset(), Matchers.is( JVM_TIMEZONE_OFFSET ) );
assertThat( resultList.get( 0 ).get( 1, OffsetDateTime.class ).toInstant(), Matchers.is( OFFSET_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
@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) {
scope.inSession(
session -> {
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
).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( 2, ZoneOffset.class ), Matchers.is( ZoneOffset.UTC ) );
}
);
}
@ -189,6 +243,11 @@ public class TimeZoneStorageMappingTests {
private Integer id;
//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)
@TimeZoneColumn(name = "birthday_offset_offset")
@Column(name = "birthday_offset")
@ -200,6 +259,10 @@ public class TimeZoneStorageMappingTests {
private ZonedDateTime zonedDateTimeColumn;
//end::time-zone-column-examples-mapping-example[]
@TimeZoneStorage
@Column(name = "birthtime_offset_auto")
private OffsetTime offsetTimeAuto;
@TimeZoneStorage
@Column(name = "birthday_offset_auto")
private OffsetDateTime offsetDateTimeAuto;
@ -208,6 +271,10 @@ public class TimeZoneStorageMappingTests {
@Column(name = "birthday_zoned_auto")
private ZonedDateTime zonedDateTimeAuto;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthtime_offset_normalized")
private OffsetTime offsetTimeNormalized;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthday_offset_normalized")
private OffsetDateTime offsetDateTimeNormalized;
@ -216,6 +283,10 @@ public class TimeZoneStorageMappingTests {
@Column(name = "birthday_zoned_normalized")
private ZonedDateTime zonedDateTimeNormalized;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthtime_offset_normalized_utc")
private OffsetTime offsetTimeNormalizedUtc;
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthday_offset_normalized_utc")
private OffsetDateTime offsetDateTimeNormalizedUtc;
@ -227,14 +298,18 @@ public class TimeZoneStorageMappingTests {
public TimeZoneStorageEntity() {
}
public TimeZoneStorageEntity(Integer id, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) {
public TimeZoneStorageEntity(Integer id, OffsetTime offsetTime, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) {
this.id = id;
this.offsetTimeColumn = offsetTime;
this.offsetDateTimeColumn = offsetDateTime;
this.zonedDateTimeColumn = zonedDateTime;
this.offsetTimeAuto = offsetTime;
this.offsetDateTimeAuto = offsetDateTime;
this.zonedDateTimeAuto = zonedDateTime;
this.offsetTimeNormalized = offsetTime;
this.offsetDateTimeNormalized = offsetDateTime;
this.zonedDateTimeNormalized = zonedDateTime;
this.offsetTimeNormalizedUtc = offsetTime;
this.offsetDateTimeNormalizedUtc = offsetDateTime;
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.TIMESTAMP;
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.VARBINARY;
@ -83,6 +84,7 @@ public class CUBRIDDialect extends Dialect {
//(always 3, millisecond precision)
case TIMESTAMP:
return "datetime";
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
return "datetimetz";
default:

View File

@ -72,7 +72,7 @@ import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.JavaObjectType;
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.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
@ -195,6 +195,10 @@ public class CockroachLegacyDialect extends Dialect {
case BLOB:
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:
return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -269,6 +273,12 @@ public class CockroachLegacyDialect extends Dialect {
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:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) {
@ -324,7 +334,7 @@ public class CockroachLegacyDialect extends Dialect {
protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
if ( PgJdbcHelper.isUsable( serviceRegistry ) ) {
@ -423,7 +433,13 @@ public class CockroachLegacyDialect extends Dialect {
functionContributions.getFunctionRegistry().register(
"format",
new FormatFunction( "experimental_strftime", functionContributions.getTypeConfiguration() )
new FormatFunction(
"experimental_strftime",
false,
true,
false,
functionContributions.getTypeConfiguration()
)
);
functionFactory.windowFunctions();
functionFactory.listagg_stringAgg( "string" );
@ -757,22 +773,30 @@ public class CockroachLegacyDialect extends Dialect {
if ( unit == null ) {
return "(?3-?2)";
}
switch (unit) {
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 ) {
if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
// special case: subtraction of two dates
// results in an integer number of days
// 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 {
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:
return "extract_duration(hour from ?3-?2)/168";
case DAY:

View File

@ -11,7 +11,11 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import org.hibernate.LockOptions;
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.NUMERIC;
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.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
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.
@ -187,6 +196,7 @@ public class DB2LegacyDialect extends Dialect {
return "clob";
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p)";
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case BINARY:
@ -416,9 +426,37 @@ public class DB2LegacyDialect extends Dialect {
if ( getDB2Version().isBefore( 11 ) ) {
return DB2Dialect.timestampdiffPatternV10( unit, fromTemporalType, toTemporalType );
}
StringBuilder pattern = new StringBuilder();
boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
final StringBuilder pattern = new StringBuilder();
final String fromExpression;
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 ) {
case NATIVE:
case NANOSECOND:
@ -434,26 +472,24 @@ public class DB2LegacyDialect extends Dialect {
default:
pattern.append( "?1s_between(" );
}
if ( castTo ) {
pattern.append( "cast(?3 as timestamp)" );
}
else {
pattern.append( "?3" );
}
pattern.append( toExpression );
pattern.append( ',' );
if ( castFrom ) {
pattern.append( "cast(?2 as timestamp)" );
}
else {
pattern.append( "?2" );
}
pattern.append( fromExpression );
pattern.append( ')' );
switch ( unit ) {
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;
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;
case MONTH:
pattern.append( ')' );
@ -468,19 +504,24 @@ public class DB2LegacyDialect extends Dialect {
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
final StringBuilder pattern = new StringBuilder();
final boolean castTo;
final String timestampExpression;
if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME;
if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
}
else {
castTo = temporalType == TemporalType.DATE;
}
if (castTo) {
pattern.append("cast(?3 as timestamp)");
}
else {
pattern.append("?3");
if ( temporalType == TemporalType.DATE ) {
timestampExpression = "cast(?3 as timestamp)";
}
else {
timestampExpression = "?3";
}
}
pattern.append(timestampExpression);
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
switch (unit) {
@ -503,6 +544,83 @@ public class DB2LegacyDialect extends Dialect {
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
public String getLowercaseFunction() {
return getDB2Version().isBefore( 9, 7 ) ? "lcase" : super.getLowercaseFunction();
@ -894,6 +1012,12 @@ public class DB2LegacyDialect extends Dialect {
return "dayofweek(?2)";
case QUARTER:
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 );
}
@ -939,4 +1063,20 @@ public class DB2LegacyDialect extends Dialect {
public String getCreateUserDefinedTypeExtensionsString() {
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 static org.hibernate.type.SqlTypes.ROWID;
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.
@ -74,9 +76,13 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
@Override
protected String columnType(int sqlTypeCode) {
if ( sqlTypeCode == TIMESTAMP_WITH_TIMEZONE && getVersion().isAfter( 10 ) ) {
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html
return "timestamp with time zone";
if ( getVersion().isAfter( 10 ) ) {
switch ( sqlTypeCode ) {
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 );
}
@ -160,14 +166,7 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder();
final boolean castTo;
if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME;
}
else {
castTo = temporalType == TemporalType.DATE;
}
final StringBuilder pattern = new StringBuilder();
pattern.append("add_");
switch (unit) {
case NATIVE:
@ -185,12 +184,24 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
pattern.append("?1");
}
pattern.append("s(");
if (castTo) {
pattern.append("cast(?3 as timestamp)");
final String timestampExpression;
if ( unit.isDateUnit() ) {
if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
}
else {
pattern.append("?3");
if ( temporalType == TemporalType.DATE ) {
timestampExpression = "cast(?3 as timestamp)";
}
else {
timestampExpression = "?3";
}
}
pattern.append(timestampExpression);
pattern.append(",");
switch (unit) {
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.NUMERIC;
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_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
@ -162,6 +164,10 @@ public class DerbyLegacyDialect extends Dialect {
case NCLOB:
return "clob";
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
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)";
case QUARTER:
return "((month(?2)+2)/3)";
case EPOCH:
return "{fn timestampdiff(sql_tsi_second,{ts '1970-01-01 00:00:00'},?2)}";
default:
return "?1(?2)";
}

View File

@ -150,7 +150,7 @@ public class FirebirdDialect extends Dialect {
case TIMESTAMP:
return "timestamp";
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:
return getVersion().isBefore( 4, 0 ) ? "timestamp" : "timestamp with time zone";
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.spi.SequenceInformationExtractor;
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.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.spi.JdbcTypeRegistry;
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.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.CHAR;
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.OTHER;
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.VARBINARY;
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.appendAsTimestampWithMillis;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
/**
* A legacy {@linkplain Dialect SQL dialect} for H2.
@ -208,6 +213,9 @@ public class H2LegacyDialect extends Dialect {
// which caused problems for schema update tool
case NUMERIC:
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:
return columnType( CHAR );
case NVARCHAR:
@ -263,7 +271,15 @@ public class H2LegacyDialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.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 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
}
@ -878,4 +894,14 @@ public class H2LegacyDialect extends Dialect {
public UniqueDelegate getUniqueDelegate() {
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.SQLException;
import java.sql.Types;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
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.VARBINARY;
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.
@ -153,6 +157,13 @@ public class OracleLegacyDialect extends Dialect {
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 )
? Oracle12LimitHandler.INSTANCE
: new LegacyOracleLimitHandler( getVersion() );
@ -316,6 +327,11 @@ public class OracleLegacyDialect extends Dialect {
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}
@ -448,6 +464,8 @@ public class OracleLegacyDialect extends Dialect {
return "to_number(to_char(?2,'MI'))";
case SECOND:
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:
return super.extractPattern(unit);
}
@ -455,150 +473,119 @@ public class OracleLegacyDialect extends Dialect {
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder();
pattern.append("(?3+");
switch ( unit ) {
case YEAR:
pattern.append( ADD_YEAR_EXPRESSION );
break;
case QUARTER:
pattern.append( ADD_QUARTER_EXPRESSION );
break;
case MONTH:
pattern.append("numtoyminterval");
pattern.append( ADD_MONTH_EXPRESSION );
break;
case WEEK:
pattern.append("(?3+numtodsinterval((?2)*7,'day'))");
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append("(?3+numtodsinterval(?2,'?1'))");
break;
case NANOSECOND:
pattern.append("(?3+numtodsinterval((?2)/1e9,'second'))");
break;
case NATIVE:
pattern.append("numtodsinterval");
pattern.append("(?3+numtodsinterval(?2,'second'))");
break;
default:
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();
}
@Override
public String timestampdiffPattern(
TemporalUnit unit,
TemporalType fromTemporalType, TemporalType toTemporalType) {
StringBuilder pattern = new StringBuilder();
boolean timestamp = toTemporalType == TemporalType.TIMESTAMP || fromTemporalType == TemporalType.TIMESTAMP;
switch (unit) {
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
final StringBuilder pattern = new StringBuilder();
final boolean hasTimePart = toTemporalType != TemporalType.DATE || fromTemporalType != TemporalType.DATE;
switch ( unit ) {
case YEAR:
extractField(pattern, YEAR, unit);
extractField( pattern, YEAR, unit );
break;
case QUARTER:
case MONTH:
pattern.append("(");
extractField(pattern, YEAR, unit);
pattern.append("+");
extractField(pattern, MONTH, unit);
pattern.append(")");
pattern.append( "(" );
extractField( pattern, YEAR, unit );
pattern.append( "+" );
extractField( pattern, MONTH, unit );
pattern.append( ")" );
break;
case WEEK:
case DAY:
extractField(pattern, DAY, unit);
extractField( pattern, DAY, unit );
break;
case HOUR:
pattern.append("(");
extractField(pattern, DAY, unit);
if (timestamp) {
pattern.append("+");
extractField(pattern, HOUR, unit);
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
}
pattern.append(")");
pattern.append( ")" );
break;
case MINUTE:
pattern.append("(");
extractField(pattern, DAY, unit);
if (timestamp) {
pattern.append("+");
extractField(pattern, HOUR, unit);
pattern.append("+");
extractField(pattern, MINUTE, unit);
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
}
pattern.append(")");
pattern.append( ")" );
break;
case NATIVE:
case NANOSECOND:
case SECOND:
pattern.append("(");
extractField(pattern, DAY, unit);
if (timestamp) {
pattern.append("+");
extractField(pattern, HOUR, unit);
pattern.append("+");
extractField(pattern, MINUTE, unit);
pattern.append("+");
extractField(pattern, SECOND, unit);
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "+" );
extractField( pattern, SECOND, unit );
}
pattern.append(")");
pattern.append( ")" );
break;
default:
throw new SemanticException("unrecognized field: " + unit);
throw new SemanticException( "unrecognized field: " + unit );
}
return pattern.toString();
}
private void extractField(
StringBuilder pattern,
TemporalUnit unit, TemporalUnit toUnit) {
pattern.append("extract(");
pattern.append( translateExtractField(unit) );
pattern.append(" from (?3-?2) ");
switch (unit) {
private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit toUnit) {
pattern.append( "extract(" );
pattern.append( translateExtractField( unit ) );
pattern.append( " from (?3-?2) " );
switch ( unit ) {
case YEAR:
case MONTH:
pattern.append("year to month");
pattern.append( "year to month" );
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append("day to second");
pattern.append( "day to second" );
break;
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 ) );
}
@ -627,8 +614,9 @@ public class OracleLegacyDialect extends Dialect {
return "number($p,$s)";
case DATE:
case TIME:
return "date";
case TIME:
return getVersion().isBefore( 9 ) ? "date" : super.columnType( sqlTypeCode );
case TIMESTAMP:
// the only difference between date and timestamp
// on Oracle is that date has no fractional seconds
@ -811,7 +799,7 @@ public class OracleLegacyDialect extends Dialect {
typeContributions.contributeJdbcType( OracleJdbcHelper.getArrayJdbcType( serviceRegistry ) );
}
else {
typeContributions.contributeJdbcType( ArrayJdbcType.INSTANCE );
typeContributions.contributeJdbcType( OracleReflectionStructJdbcType.INSTANCE );
}
// Oracle requires a custom binder for binding untyped nulls with the NULL type
typeContributions.contributeJdbcType( NullJdbcType.INSTANCE );
@ -1107,24 +1095,15 @@ public class OracleLegacyDialect extends Dialect {
@Override
public String getQueryHintString(String sql, String hints) {
String statementType = statementType(sql);
final int pos = sql.indexOf( statementType );
if ( pos > -1 ) {
final StringBuilder buffer = new StringBuilder( sql.length() + hints.length() + 8 );
if ( pos > 0 ) {
buffer.append( sql, 0, pos );
}
buffer
.append( statementType )
.append( " /*+ " )
.append( hints )
.append( " */" )
.append( sql.substring( pos + statementType.length() ) );
sql = buffer.toString();
final String statementType = statementType( sql );
final int start = sql.indexOf( statementType );
if ( start < 0 ) {
return sql;
}
else {
int end = start + statementType.length();
return sql.substring( 0, end ) + " /*+ " + hints + " */" + sql.substring( end );
}
return sql;
}
@Override
@ -1161,14 +1140,15 @@ public class OracleLegacyDialect extends Dialect {
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 ) {
return matcher.group(1);
}
throw new IllegalArgumentException( "Can't determine SQL statement type for statement: " + sql );
else {
throw new IllegalArgumentException( "Can't determine SQL statement type for statement: " + sql );
}
}
@Override
@ -1276,6 +1256,32 @@ public class OracleLegacyDialect extends Dialect {
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
public void appendDatetimeFormat(SqlAppender appender, String format) {
// Unlike other databases, Oracle requires an explicit reset for the fm modifier,
@ -1432,4 +1438,9 @@ public class OracleLegacyDialect extends Dialect {
public String getCreateUserDefinedTypeKindString() {
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.BlobJdbcType;
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.ObjectNullAsBinaryTypeJdbcType;
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.SQLXML;
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_UTC;
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.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
@ -209,6 +211,10 @@ public class PostgreSQLLegacyDialect extends Dialect {
case LONG32VARBINARY:
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:
return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -323,6 +329,12 @@ public class PostgreSQLLegacyDialect extends Dialect {
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:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) {
@ -443,36 +455,31 @@ public class PostgreSQLLegacyDialect extends Dialect {
if ( unit == null ) {
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
// results in an integer number of days
// instead of an INTERVAL
return "(?3-?2)";
}
else {
StringBuilder pattern = new StringBuilder();
switch ( unit ) {
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:
pattern.append( "(" );
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit );
pattern.append( "+" );
extractField( pattern, MONTH, fromTemporalType, toTemporalType, unit );
pattern.append( ")" );
break;
case QUARTER:
return "extract(" + translateDurationField( unit ) + " from age(?3,?2))";
default:
return "(?3-?2)" + DAY.conversionFactor( unit, this );
}
}
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
return "(extract(day from ?3-?2)/7)";
case DAY:
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit );
break;
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:
@ -481,15 +488,14 @@ public class PostgreSQLLegacyDialect extends Dialect {
case SECOND:
case NANOSECOND:
case NATIVE:
extractField( pattern, EPOCH, fromTemporalType, toTemporalType, unit );
break;
return "extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this );
default:
throw new SemanticException( "unrecognized field: " + unit );
}
return pattern.toString();
}
}
@Deprecated
protected void extractField(
StringBuilder pattern,
TemporalUnit unit,
@ -499,7 +505,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
pattern.append( "extract(" );
pattern.append( translateDurationField( unit ) );
pattern.append( " from " );
if ( toTimestamp != TemporalType.TIMESTAMP && fromTimestamp != TemporalType.TIMESTAMP ) {
if ( toTimestamp == TemporalType.DATE && fromTimestamp == TemporalType.DATE ) {
// special case subtraction of two
// dates results in an integer not
// an Interval
@ -1325,7 +1331,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
// dialect uses oid for Blobs, byte arrays cannot be used.
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
@ -1411,23 +1417,13 @@ public class PostgreSQLLegacyDialect extends Dialect {
// disabled foreign key constraints still prevent 'truncate table'
// (these would help if we used 'delete' instead of 'truncate')
// @Override
// public String getDisableConstraintsStatement() {
// return "set constraints all deferred";
// }
//
// @Override
// public String getEnableConstraintsStatement() {
// return "set constraints all immediate";
// }
//
// @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";
// }
@Override
public String rowId(String rowId) {
return "ctid";
}
@Override
public int rowIdSqlType() {
return OTHER;
}
}

View File

@ -20,8 +20,6 @@ import org.hibernate.query.sqm.TemporalUnit;
import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.TemporalUnit.DAY;
/**
* An SQL dialect for Postgres Plus
*
@ -84,12 +82,10 @@ public class PostgresPlusLegacyDialect extends PostgreSQLLegacyDialect {
@Override
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
// because there is no date type i.e. without time for Oracle compatibility
final StringBuilder pattern = new StringBuilder();
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit );
return pattern.toString();
return super.timestampdiffPattern( unit, TemporalType.TIMESTAMP, TemporalType.TIMESTAMP );
}
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 );
case TIMESTAMP:
return getVersion().isSameOrAfter( 10 ) ? "datetime2($p)" : super.columnType( sqlTypeCode );
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
return getVersion().isSameOrAfter( 10 ) ? "datetimeoffset($p)" : super.columnType( sqlTypeCode );
}
@ -780,6 +781,8 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
case SECOND:
//this should evaluate to a floating point type
return "(datepart(second,?2)+datepart(nanosecond,?2)/1e9)";
case EPOCH:
return "datediff_big(second, '1970-01-01', ?2)";
case WEEK:
// Thanks https://www.sqlservercentral.com/articles/a-simple-formula-to-calculate-the-iso-week-number
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.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
/**
* SQL Dialect for Sybase/SQL Anywhere
@ -65,6 +66,7 @@ public class SybaseAnywhereDialect extends SybaseDialect {
return "time";
case TIMESTAMP:
return "timestamp";
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
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.util.StringHelper;
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.hibernate.usertype.internal.OffsetTimeCompositeUserType;
import org.jboss.logging.Logger;
@ -45,6 +46,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.MappedSuperclass;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.isOffsetTimeClass;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.useColumnForTimeZoneStorage;
/**
@ -484,11 +486,19 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
}
}
else if ( useColumnForTimeZoneStorage( element, context ) ) {
final Column column = createTimestampColumn( element, path, context );
columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
new Column[]{ column }
);
final Column column = createTemporalColumn( element, path, context );
if ( isOffsetTimeClass( element ) ) {
columnOverride.put(
path + "." + OffsetTimeCompositeUserType.LOCAL_TIME_NAME,
new Column[] { column }
);
}
else {
columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
new Column[] { column }
);
}
final Column offsetColumn = createTimeZoneColumn( element, column );
columnOverride.put(
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;
final Column annotatedColumn = element.getAnnotation( Column.class );
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.usertype.CompositeUserType;
import org.hibernate.usertype.internal.OffsetDateTimeCompositeUserType;
import org.hibernate.usertype.internal.OffsetTimeCompositeUserType;
import org.hibernate.usertype.internal.ZonedDateTimeCompositeUserType;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import static org.hibernate.TimeZoneStorageStrategy.COLUMN;
@ -23,6 +25,7 @@ import static org.hibernate.dialect.TimeZoneSupport.NATIVE;
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 ZONED_DATETIME_CLASS = ZonedDateTime.class.getName();
@ -38,13 +41,29 @@ public class TimeZoneStorageHelper {
else if ( ZONED_DATETIME_CLASS.equals( returnedClassName ) ) {
return ZonedDateTimeCompositeUserType.class;
}
else if ( OFFSET_TIME_CLASS.equals( returnedClassName ) ) {
return OffsetTimeCompositeUserType.class;
}
}
return null;
}
private static boolean isZonedDateTimeClass(String returnedClassName) {
private static boolean isTemporalWithTimeZoneClass(String 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) {
@ -52,7 +71,7 @@ public class TimeZoneStorageHelper {
if ( timeZoneStorage == null ) {
if ( element instanceof XProperty ) {
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
&& context.getBuildingOptions().getDefaultTimeZoneStorage() == COLUMN;
}

View File

@ -10,6 +10,7 @@ import java.io.InputStream;
import java.sql.Types;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
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.jdbc.JdbcType;
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.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.DdlType;
@ -687,7 +687,11 @@ public class MetadataBuildingProcess {
final JdbcType timestampWithTimeZoneOverride = getTimestampWithTimeZoneOverride( options, jdbcTypeRegistry );
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 );
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,
JdbcType timestampWithTimeZoneOverride) {
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) {
switch ( options.getDefaultTimeZoneStorage() ) {
case NORMALIZE:

View File

@ -195,6 +195,7 @@ public class ColumnOrderingStrategyStandard implements ColumnOrderingStrategy {
return (int) length;
case DATE:
case TIME:
case TIME_UTC:
case TIME_WITH_TIMEZONE:
return 4;
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.NVARCHAR;
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_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
@ -254,6 +256,9 @@ public abstract class AbstractHANADialect extends Dialect {
case DOUBLE:
return "double";
//no explicit precision
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp";
@ -1196,19 +1201,19 @@ public abstract class AbstractHANADialect extends Dialect {
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
switch (unit) {
case NANOSECOND:
// if ( temporalType == TemporalType.TIME ) {
// return "nano100_between(cast(?3 as timestamp), cast(?2 as timestamp))*100";
// }
// else {
return "nano100_between(?2,?3)*100";
// }
if ( fromTemporalType == TemporalType.TIME && toTemporalType == TemporalType.TIME ) {
return "seconds_between(?2,?3)*1000000000";
}
else {
return "nano100_between(?2,?3)*100";
}
case NATIVE:
// if ( temporalType == TemporalType.TIME ) {
// return "nano100_between(cast(?3 as timestamp), cast(?2 as timestamp))";
// }
// else {
return "nano100_between(?2,?3)";
// }
if ( fromTemporalType == TemporalType.TIME && toTemporalType == TemporalType.TIME ) {
return "seconds_between(?2,?3)*10000000";
}
else {
return "nano100_between(?2,?3)";
}
case QUARTER:
return "months_between(?2,?3)/3";
case WEEK:

View File

@ -322,6 +322,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
break;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
values[column] = fromRawObject(
jdbcMapping,
parseTime(
@ -804,6 +805,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
case SqlTypes.DATE:
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
@ -940,6 +942,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
}
break;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
if ( value instanceof java.util.Date ) {
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.internal.CoreMessageLogger;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.NullOrdering;
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.type.JavaObjectType;
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.ObjectNullAsBinaryTypeJdbcType;
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.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.YEAR;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BINARY;
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.NVARCHAR;
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_UTC;
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.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
@ -212,6 +217,10 @@ public class CockroachDialect extends Dialect {
case BLOB:
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:
return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -284,6 +293,12 @@ public class CockroachDialect extends Dialect {
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:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) {
@ -339,7 +354,7 @@ public class CockroachDialect extends Dialect {
protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
if ( PgJdbcHelper.isUsable( serviceRegistry ) ) {
@ -422,7 +437,13 @@ public class CockroachDialect extends Dialect {
functionContributions.getFunctionRegistry().register(
"format",
new FormatFunction( "experimental_strftime", functionContributions.getTypeConfiguration() )
new FormatFunction(
"experimental_strftime",
false,
true,
false,
functionContributions.getTypeConfiguration()
)
);
functionFactory.windowFunctions();
functionFactory.listagg_stringAgg( "string" );
@ -756,30 +777,46 @@ public class CockroachDialect extends Dialect {
if ( unit == null ) {
return "(?3-?2)";
}
switch (unit) {
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 ) {
if ( toTemporalType == TemporalType.DATE && fromTemporalType == TemporalType.DATE ) {
// special case: subtraction of two dates
// results in an integer number of days
// 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 {
switch (unit) {
case WEEK:
return "extract_duration(hour from ?3-?2)/168";
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
return "(extract(day from ?3-?2)/7)";
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:
return "extract_duration(microsecond from ?3-?2)*1e3";
case NATIVE:
return "cast(extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this ) + " as int)";
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.NUMERIC;
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.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
@ -189,6 +190,7 @@ public class DB2Dialect extends Dialect {
return "clob";
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p)";
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case BINARY:
@ -408,9 +410,37 @@ public class DB2Dialect extends Dialect {
if ( getDB2Version().isBefore( 11 ) ) {
return timestampdiffPatternV10( unit, fromTemporalType, toTemporalType );
}
StringBuilder pattern = new StringBuilder();
boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
final StringBuilder pattern = new StringBuilder();
final String fromExpression;
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 ) {
case NATIVE:
case NANOSECOND:
@ -426,26 +456,24 @@ public class DB2Dialect extends Dialect {
default:
pattern.append( "?1s_between(" );
}
if ( castTo ) {
pattern.append( "cast(?3 as timestamp)" );
}
else {
pattern.append( "?3" );
}
pattern.append( toExpression );
pattern.append( ',' );
if ( castFrom ) {
pattern.append( "cast(?2 as timestamp)" );
}
else {
pattern.append( "?2" );
}
pattern.append( fromExpression );
pattern.append( ')' );
switch ( unit ) {
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;
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;
case MONTH:
pattern.append( ')' );
@ -458,10 +486,36 @@ public class DB2Dialect extends Dialect {
}
public static String timestampdiffPatternV10(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
final boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
final boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
final String fromExpression = castFrom ? "cast(?2 as timestamp)" : "?2";
final String toExpression = castTo ? "cast(?3 as timestamp)" : "?3";
final String fromExpression;
final String toExpression;
if ( unit.isDateUnit() ) {
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 ) {
case NATIVE:
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
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
final StringBuilder pattern = new StringBuilder();
final boolean castTo;
final String timestampExpression;
if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME;
if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
}
else {
castTo = temporalType == TemporalType.DATE;
}
if (castTo) {
pattern.append("cast(?3 as timestamp)");
}
else {
pattern.append("?3");
if ( temporalType == TemporalType.DATE ) {
timestampExpression = "cast(?3 as timestamp)";
}
else {
timestampExpression = "?3";
}
}
pattern.append(timestampExpression);
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
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.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:
@ -78,9 +79,13 @@ public class DB2zDialect extends DB2Dialect {
@Override
protected String columnType(int sqlTypeCode) {
if ( sqlTypeCode == TIMESTAMP_WITH_TIMEZONE && getVersion().isAfter( 10 ) ) {
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html
return "timestamp with time zone";
if ( getVersion().isAfter( 10 ) ) {
switch ( sqlTypeCode ) {
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 );
}
@ -150,14 +155,7 @@ public class DB2zDialect extends DB2Dialect {
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder();
final boolean castTo;
if ( unit.isDateUnit() ) {
castTo = temporalType == TemporalType.TIME;
}
else {
castTo = temporalType == TemporalType.DATE;
}
final StringBuilder pattern = new StringBuilder();
pattern.append("add_");
switch (unit) {
case NATIVE:
@ -175,12 +173,24 @@ public class DB2zDialect extends DB2Dialect {
pattern.append("?1");
}
pattern.append("s(");
if (castTo) {
pattern.append("cast(?3 as timestamp)");
final String timestampExpression;
if ( unit.isDateUnit() ) {
if ( temporalType == TemporalType.TIME ) {
timestampExpression = "timestamp('1970-01-01',?3)";
}
else {
timestampExpression = "?3";
}
}
else {
pattern.append("?3");
if ( temporalType == TemporalType.DATE ) {
timestampExpression = "cast(?3 as timestamp)";
}
else {
timestampExpression = "?3";
}
}
pattern.append(timestampExpression);
pattern.append(",");
switch (unit) {
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.NUMERIC;
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_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
@ -154,6 +156,10 @@ public class DerbyDialect extends Dialect {
case NCLOB:
return "clob";
case TIME:
case TIME_WITH_TIMEZONE:
return "time";
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
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.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType;
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.JdbcType;
import org.hibernate.type.descriptor.jdbc.LongNVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.NCharJdbcType;
import org.hibernate.type.descriptor.jdbc.NClobJdbcType;
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.sql.internal.CapacityDependentDdlType;
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_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.TINYINT;
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( TIME ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIME_WITH_TIMEZONE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIME_UTC ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_WITH_TIMEZONE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_UTC ) );
@ -534,17 +538,21 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
case DATE:
return "date";
case TIME:
return "time";
return "time($p)";
case TIME_WITH_TIMEZONE:
// type included here for completeness but note that
// very few databases support it, and the general
// advice is to caution against its use (for reasons,
// check the comments in the Postgres documentation).
return "time with time zone";
return "time($p) with time zone";
case TIMESTAMP:
return "timestamp($p)";
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p) with time zone";
case TIME_UTC:
return getTimeZoneSupport() == TimeZoneSupport.NATIVE
? columnType( TIME_WITH_TIMEZONE )
: columnType( TIME );
case TIMESTAMP_UTC:
return getTimeZoneSupport() == TimeZoneSupport.NATIVE
? columnType( TIMESTAMP_WITH_TIMEZONE )
@ -1555,10 +1563,12 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
}
if ( getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
jdbcTypeRegistry.addDescriptor( InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimeUtcAsOffsetTimeJdbcType.INSTANCE );
}
else {
jdbcTypeRegistry.addDescriptor( InstantAsTimestampJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimestampUtcAsJdbcTimestampJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TimeUtcAsJdbcTimeJdbcType.INSTANCE );
}
if ( supportsStandardArrays() ) {
@ -4899,6 +4909,9 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
precision = (int) ceil( precision * LOG_BASE2OF10 );
}
break;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
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.spi.SequenceInformationExtractor;
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.TimeUtcAsOffsetTimeJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
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.OTHER;
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.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
@ -190,6 +196,9 @@ public class H2Dialect extends Dialect {
// which caused problems for schema update tool
case NUMERIC:
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:
return columnType( CHAR );
case NVARCHAR:
@ -243,7 +252,15 @@ public class H2Dialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.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 );
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );

View File

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

View File

@ -529,86 +529,84 @@ public class OracleDialect extends Dialect {
@Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
StringBuilder pattern = new StringBuilder();
boolean timestamp = toTemporalType == TemporalType.TIMESTAMP || fromTemporalType == TemporalType.TIMESTAMP;
switch (unit) {
final StringBuilder pattern = new StringBuilder();
final boolean hasTimePart = toTemporalType != TemporalType.DATE || fromTemporalType != TemporalType.DATE;
switch ( unit ) {
case YEAR:
extractField(pattern, YEAR, unit);
extractField( pattern, YEAR, unit );
break;
case QUARTER:
case MONTH:
pattern.append("(");
extractField(pattern, YEAR, unit);
pattern.append("+");
extractField(pattern, MONTH, unit);
pattern.append(")");
pattern.append( "(" );
extractField( pattern, YEAR, unit );
pattern.append( "+" );
extractField( pattern, MONTH, unit );
pattern.append( ")" );
break;
case WEEK:
case DAY:
extractField(pattern, DAY, unit);
extractField( pattern, DAY, unit );
break;
case HOUR:
pattern.append("(");
extractField(pattern, DAY, unit);
if (timestamp) {
pattern.append("+");
extractField(pattern, HOUR, unit);
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
}
pattern.append(")");
pattern.append( ")" );
break;
case MINUTE:
pattern.append("(");
extractField(pattern, DAY, unit);
if (timestamp) {
pattern.append("+");
extractField(pattern, HOUR, unit);
pattern.append("+");
extractField(pattern, MINUTE, unit);
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
}
pattern.append(")");
pattern.append( ")" );
break;
case NATIVE:
case NANOSECOND:
case SECOND:
pattern.append("(");
extractField(pattern, DAY, unit);
if (timestamp) {
pattern.append("+");
extractField(pattern, HOUR, unit);
pattern.append("+");
extractField(pattern, MINUTE, unit);
pattern.append("+");
extractField(pattern, SECOND, unit);
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "+" );
extractField( pattern, SECOND, unit );
}
pattern.append(")");
pattern.append( ")" );
break;
default:
throw new SemanticException("unrecognized field: " + unit);
throw new SemanticException( "unrecognized field: " + unit );
}
return pattern.toString();
}
private void extractField(
StringBuilder pattern,
TemporalUnit unit, TemporalUnit toUnit) {
pattern.append("extract(");
pattern.append( translateExtractField(unit) );
pattern.append(" from (?3-?2) ");
switch (unit) {
private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit toUnit) {
pattern.append( "extract(" );
pattern.append( translateExtractField( unit ) );
pattern.append( " from (?3-?2) " );
switch ( unit ) {
case YEAR:
case MONTH:
pattern.append("year to month");
pattern.append( "year to month" );
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append("day to second");
pattern.append( "day to second" );
break;
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 ) );
}
@ -637,8 +635,9 @@ public class OracleDialect extends Dialect {
return "number($p,$s)";
case DATE:
case TIME:
return "date";
case TIME:
return "timestamp($p)";
// the only difference between date and timestamp
// on Oracle is that date has no fractional seconds
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.BlobJdbcType;
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.ObjectNullAsBinaryTypeJdbcType;
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.SQLXML;
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_UTC;
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.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
@ -220,6 +222,10 @@ public class PostgreSQLDialect extends Dialect {
case LONG32VARBINARY:
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:
return columnType( TIMESTAMP_WITH_TIMEZONE );
@ -325,6 +331,12 @@ public class PostgreSQLDialect extends Dialect {
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:
// The PostgreSQL JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant
if ( "timestamptz".equals( columnTypeName ) ) {
@ -445,36 +457,31 @@ public class PostgreSQLDialect extends Dialect {
if ( unit == null ) {
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
// results in an integer number of days
// instead of an INTERVAL
return "(?3-?2)";
}
else {
StringBuilder pattern = new StringBuilder();
switch ( unit ) {
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:
pattern.append( "(" );
extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit );
pattern.append( "+" );
extractField( pattern, MONTH, fromTemporalType, toTemporalType, unit );
pattern.append( ")" );
break;
case QUARTER:
return "extract(" + translateDurationField( unit ) + " from age(?3,?2))";
default:
return "(?3-?2)" + DAY.conversionFactor( unit, this );
}
}
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
return "(extract(day from ?3-?2)/7)";
case DAY:
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit );
break;
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:
@ -483,15 +490,14 @@ public class PostgreSQLDialect extends Dialect {
case SECOND:
case NANOSECOND:
case NATIVE:
extractField( pattern, EPOCH, fromTemporalType, toTemporalType, unit );
break;
return "extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this );
default:
throw new SemanticException( "unrecognized field: " + unit );
}
return pattern.toString();
}
}
@Deprecated
protected void extractField(
StringBuilder pattern,
TemporalUnit unit,
@ -501,7 +507,7 @@ public class PostgreSQLDialect extends Dialect {
pattern.append( "extract(" );
pattern.append( translateDurationField( unit ) );
pattern.append( " from " );
if ( toTimestamp != TemporalType.TIMESTAMP && fromTimestamp != TemporalType.TIMESTAMP ) {
if ( toTimestamp == TemporalType.DATE && fromTimestamp == TemporalType.DATE ) {
// special case subtraction of two
// dates results in an integer not
// an Interval
@ -1333,7 +1339,7 @@ public class PostgreSQLDialect extends Dialect {
// dialect uses oid for Blobs, byte arrays cannot be used.
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, TimestampUtcAsOffsetDateTimeJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {

View File

@ -24,8 +24,6 @@ import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.TemporalUnit.DAY;
/**
* An SQL dialect for Postgres Plus
*
@ -88,12 +86,10 @@ public class PostgresPlusDialect extends PostgreSQLDialect {
@Override
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
// because there is no date type i.e. without time for Oracle compatibility
final StringBuilder pattern = new StringBuilder();
extractField( pattern, DAY, fromTemporalType, toTemporalType, unit );
return pattern.toString();
return super.timestampdiffPattern( unit, TemporalType.TIMESTAMP, TemporalType.TIMESTAMP );
}
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.TIMESTAMP;
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.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
@ -179,6 +180,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
return "time";
case TIMESTAMP:
return "datetime2($p)";
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
return "datetimeoffset($p)";
default:

View File

@ -374,6 +374,8 @@ public class StructJdbcType implements AggregateJdbcType {
if ( rawJdbcValue != null ) {
final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping();
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
// 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_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return fromRawObject(
jdbcMapping,
JdbcTimeJavaType.INSTANCE.fromEncodedString(
@ -598,6 +599,7 @@ public class XmlHelper {
break;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
JdbcTimeJavaType.INSTANCE.appendEncodedString(
appender,
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.spi.SqlAppender;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
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_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.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
@ -117,6 +120,8 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
aggregateParentReadExpression + "." + column + ".date()"
);
case TIME:
case TIME_WITH_TIMEZONE:
case TIME_UTC:
return template.replace(
placeholder,
"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.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.ReturnableType;
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.ComparisonPredicate;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
@ -64,6 +66,7 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
private final String nativeFunctionName;
private final boolean reversedArguments;
private final boolean concatPattern;
private final boolean supportsTime;
public FormatFunction(String nativeFunctionName, TypeConfiguration typeConfiguration) {
this( nativeFunctionName, false, true, typeConfiguration );
@ -74,6 +77,15 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
boolean reversedArguments,
boolean concatPattern,
TypeConfiguration typeConfiguration) {
this( nativeFunctionName, reversedArguments, concatPattern, true, typeConfiguration );
}
public FormatFunction(
String nativeFunctionName,
boolean reversedArguments,
boolean concatPattern,
boolean supportsTime,
TypeConfiguration typeConfiguration) {
super(
"format",
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING ),
@ -84,6 +96,7 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
this.nativeFunctionName = nativeFunctionName;
this.reversedArguments = reversedArguments;
this.concatPattern = concatPattern;
this.supportsTime = supportsTime;
}
@Override
@ -98,9 +111,15 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
if ( reversedArguments ) {
format.accept( walker );
sqlAppender.append( ',' );
if ( !supportsTime && isTimeTemporal( expression ) ) {
sqlAppender.append( "date'1970-01-01'+" );
}
expression.accept( walker );
}
else {
if ( !supportsTime && isTimeTemporal( expression ) ) {
sqlAppender.append( "date'1970-01-01'+" );
}
expression.accept( walker );
sqlAppender.append( ',' );
format.accept( walker );
@ -108,6 +127,23 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
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
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
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.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -32,10 +33,9 @@ public class SQLServerFormatEmulation extends FormatFunction {
List<? extends SqlAstNode> arguments,
SqlAstTranslator<?> walker) {
final Expression datetime = (Expression) arguments.get(0);
final boolean isTime = TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME;
sqlAppender.appendSql("format(");
if ( isTime ) {
if ( needsDateTimeCast( datetime ) ) {
sqlAppender.appendSql("cast(");
datetime.accept( walker );
sqlAppender.appendSql(" as datetime)");
@ -47,4 +47,16 @@ public class SQLServerFormatEmulation extends FormatFunction {
arguments.get( 1 ).accept( walker );
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.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import org.hibernate.engine.spi.SessionFactoryImplementor;
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.spi.TypeConfiguration;
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.hibernate.usertype.internal.OffsetTimeCompositeUserType;
/**
* @author Steve Ebersole
@ -117,7 +119,12 @@ public class SqmExpressionHelper {
public static SqmExpression<?> getActualExpression(SqmExpression<?> 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 {
return expression;
@ -127,18 +134,24 @@ public class SqmExpressionHelper {
public static SqmExpression<?> getOffsetAdjustedExpression(SqmExpression<?> expression) {
if ( isCompositeTemporal( expression ) ) {
final SqmPath<?> compositePath = (SqmPath<?>) expression;
final SqmPath<Object> instantPath = compositePath.get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME );
final NodeBuilder nodeBuilder = instantPath.nodeBuilder();
final SqmPath<Object> temporalPath;
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<>(
BinaryArithmeticOperator.ADD,
instantPath,
temporalPath,
new SqmToDuration<>(
compositePath.get( AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME ),
new SqmDurationUnit<>( TemporalUnit.SECOND, nodeBuilder.getIntegerType(), nodeBuilder ),
nodeBuilder.getTypeConfiguration().getBasicTypeForJavaType( Duration.class ),
nodeBuilder
),
instantPath.getNodeType(),
temporalPath.getNodeType(),
nodeBuilder
);
}

View File

@ -440,9 +440,9 @@ public class SqlTypes {
* JDBC} timezone.
*
* @see org.hibernate.cfg.AvailableSettings#PREFERRED_INSTANT_JDBC_TYPE
* @see org.hibernate.type.descriptor.jdbc.InstantJdbcType
* @see org.hibernate.type.descriptor.jdbc.InstantAsTimestampJdbcType
* @see org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType
* @see org.hibernate.type.descriptor.jdbc.TimestampUtcAsInstantJdbcType
* @see org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType
* @see org.hibernate.type.descriptor.jdbc.TimestampUtcAsOffsetDateTimeJdbcType
*/
public static final int TIMESTAMP_UTC = 3003;
@ -479,6 +479,18 @@ public class SqlTypes {
@Internal
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
/**
@ -689,6 +701,8 @@ public class SqlTypes {
switch ( typeCode ) {
case DATE:
case TIME:
case TIME_WITH_TIMEZONE:
case TIME_UTC:
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_UTC:
@ -728,6 +742,8 @@ public class SqlTypes {
public static boolean hasTimePart(int typeCode) {
switch ( typeCode ) {
case TIME:
case TIME_WITH_TIMEZONE:
case TIME_UTC:
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
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<>(
"OffsetTime",
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
);
@ -1038,6 +1066,27 @@ public final class StandardBasicTypes {
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(
ZONED_DATE_TIME,
"org.hibernate.type.ZonedDateTimeType",

View File

@ -456,12 +456,17 @@ public final class DateTimeUtils {
if ( defaultTimestampPrecision >= 9 || !temporal.isSupported( ChronoField.NANO_OF_SECOND ) ) {
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
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) {

View File

@ -147,6 +147,6 @@ public class CalendarTimeJavaType extends AbstractTemporalJavaType<Calendar> {
@Override
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.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
@ -22,6 +23,7 @@ import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.CharSequenceHelper;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -127,9 +129,15 @@ public class JdbcTimeJavaType extends AbstractTemporalJavaType<Date> {
}
if ( LocalTime.class.isAssignableFrom( type ) ) {
return value instanceof java.sql.Time
? ( (java.sql.Time) value ).toLocalTime()
: new java.sql.Time( value.getTime() ).toLocalTime();
final Time time = value instanceof java.sql.Time
? ( (java.sql.Time) value )
: 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 ) ) {
@ -174,7 +182,13 @@ public class JdbcTimeJavaType extends AbstractTemporalJavaType<Date> {
}
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 ) {
@ -250,8 +264,7 @@ public class JdbcTimeJavaType extends AbstractTemporalJavaType<Date> {
@Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
//seconds (currently ignored since Dialects don't parameterize time type by precision)
return 0;
return dialect.getDefaultTimestampPrecision();
}
@Override

View File

@ -16,6 +16,7 @@ import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@ -23,6 +24,7 @@ import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -81,7 +83,12 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
}
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
@ -122,6 +129,16 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
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) {
final Timestamp ts = (Timestamp) value;
return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() ).toLocalTime();
@ -158,7 +175,7 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
@Override
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.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@ -23,6 +24,7 @@ import java.util.GregorianCalendar;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -49,8 +51,9 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
return context.getJdbcType( Types.TIME );
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
.getDescriptor( stdIndicators.getDefaultZonedTimeSqlType() );
}
@Override
@ -87,13 +90,22 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
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
// (since PS.setTime() and friends do accept a timezone passed as a Calendar)
final OffsetTime jdbcOffsetTime = offsetTime.withOffsetSameInstant( getCurrentJdbcOffset(options) );
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 );
@ -145,6 +157,10 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
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),
* 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) {
final Time time = (Time) value;
return time.toLocalTime().atOffset( getCurrentJdbcOffset(options) )
final OffsetTime offsetTime = time.toLocalTime()
.atOffset( getCurrentJdbcOffset( options) )
.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) {
@ -217,7 +239,7 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
@Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return 0;
return dialect.getDefaultTimestampPrecision();
}
}

View File

@ -6,116 +6,15 @@
*/
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.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.
*
* @deprecated Use {@link TimestampUtcAsJdbcTimestampJdbcType}
* @author Christian Beikov
*/
public class InstantAsTimestampJdbcType implements JdbcType {
@Deprecated(forRemoval = true)
public class InstantAsTimestampJdbcType extends TimestampUtcAsJdbcTimestampJdbcType {
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;
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.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.
*
* @deprecated Use {@link TimestampUtcAsOffsetDateTimeJdbcType}
* @author Christian Beikov
*/
public class InstantAsTimestampWithTimeZoneJdbcType implements JdbcType {
@Deprecated(forRemoval = true)
public class InstantAsTimestampWithTimeZoneJdbcType extends TimestampUtcAsOffsetDateTimeJdbcType {
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;
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.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.
*
* @deprecated Use {@link TimestampUtcAsInstantJdbcType}
* @author Christian Beikov
*/
public class InstantJdbcType implements JdbcType {
@Deprecated(forRemoval = true)
public class InstantJdbcType extends TimestampUtcAsInstantJdbcType {
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.spi.TypeConfiguration;
import static org.hibernate.type.SqlTypes.*;
/**
* 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
@ -193,32 +195,32 @@ public interface JdbcType extends Serializable {
default boolean isInteger() {
int typeCode = getDdlTypeCode();
return SqlTypes.isIntegral(typeCode)
|| typeCode == Types.BIT; //HIGHLY DUBIOUS!
return isIntegral(typeCode)
|| typeCode == BIT; //HIGHLY DUBIOUS!
}
default boolean isFloat() {
return SqlTypes.isFloatOrRealOrDouble( getDdlTypeCode() );
return isFloatOrRealOrDouble( getDdlTypeCode() );
}
default boolean isDecimal() {
return SqlTypes.isNumericOrDecimal( getDdlTypeCode() );
return isNumericOrDecimal( getDdlTypeCode() );
}
default boolean isNumber() {
return SqlTypes.isNumericType( getDdlTypeCode() );
return isNumericType( getDdlTypeCode() );
}
default boolean isBinary() {
return SqlTypes.isBinaryType( getDdlTypeCode() );
return isBinaryType( getDdlTypeCode() );
}
default boolean isString() {
return SqlTypes.isCharacterOrClobType( getDdlTypeCode() );
return isCharacterOrClobType( getDdlTypeCode() );
}
default boolean isTemporal() {
return SqlTypes.isTemporalType( getDdlTypeCode() );
return isTemporalType( getDdlTypeCode() );
}
default boolean isLob() {
@ -227,9 +229,9 @@ public interface JdbcType extends Serializable {
static boolean isLob(int jdbcTypeCode) {
switch ( jdbcTypeCode ) {
case SqlTypes.BLOB:
case SqlTypes.CLOB:
case SqlTypes.NCLOB: {
case BLOB:
case CLOB:
case NCLOB: {
return true;
}
}
@ -242,11 +244,11 @@ public interface JdbcType extends Serializable {
static boolean isNationalized(int jdbcTypeCode) {
switch ( jdbcTypeCode ) {
case SqlTypes.NCHAR:
case SqlTypes.NVARCHAR:
case SqlTypes.LONGNVARCHAR:
case SqlTypes.LONG32NVARCHAR:
case SqlTypes.NCLOB: {
case NCHAR:
case NVARCHAR:
case LONGNVARCHAR:
case LONG32NVARCHAR:
case NCLOB: {
return true;
}
}
@ -254,7 +256,7 @@ public interface JdbcType extends Serializable {
}
default boolean isInterval() {
return SqlTypes.isIntervalType( getDdlTypeCode() );
return isIntervalType( getDdlTypeCode() );
}
default CastType getCastType() {
@ -263,40 +265,42 @@ public interface JdbcType extends Serializable {
static CastType getCastType(int typeCode) {
switch ( typeCode ) {
case Types.INTEGER:
case Types.TINYINT:
case Types.SMALLINT:
case INTEGER:
case TINYINT:
case SMALLINT:
return CastType.INTEGER;
case Types.BIGINT:
case BIGINT:
return CastType.LONG;
case Types.FLOAT:
case Types.REAL:
case FLOAT:
case REAL:
return CastType.FLOAT;
case Types.DOUBLE:
case DOUBLE:
return CastType.DOUBLE;
case Types.CHAR:
case Types.NCHAR:
case Types.VARCHAR:
case Types.NVARCHAR:
case Types.LONGVARCHAR:
case Types.LONGNVARCHAR:
case CHAR:
case NCHAR:
case VARCHAR:
case NVARCHAR:
case LONGVARCHAR:
case LONGNVARCHAR:
return CastType.STRING;
case Types.CLOB:
case CLOB:
return CastType.CLOB;
case Types.BOOLEAN:
case BOOLEAN:
return CastType.BOOLEAN;
case Types.DECIMAL:
case Types.NUMERIC:
case DECIMAL:
case NUMERIC:
return CastType.FIXED;
case Types.DATE:
case DATE:
return CastType.DATE;
case Types.TIME:
case TIME:
case TIME_UTC:
case TIME_WITH_TIMEZONE:
return CastType.TIME;
case Types.TIMESTAMP:
case TIMESTAMP:
return CastType.TIMESTAMP;
case Types.TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
return CastType.OFFSET_TIMESTAMP;
case Types.NULL:
case NULL:
return CastType.NULL;
default:
return CastType.OTHER;

View File

@ -25,7 +25,7 @@ public class JdbcTypeFamilyInformation {
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 ),
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 );
private final int[] typeCodes;

View File

@ -193,6 +193,28 @@ public interface JdbcTypeIndicators {
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
* 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
* default {@linkplain TimeZoneStorageStrategy storage strategy}
@ -228,7 +262,7 @@ public interface JdbcTypeIndicators {
final TemporalType temporalPrecision = getTemporalPrecision();
switch ( temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision ) {
case TIME:
return Types.TIME;
return getZonedTimeSqlType( getDefaultTimeZoneStorageStrategy() );
case DATE:
return Types.DATE;
case TIMESTAMP:

View File

@ -26,6 +26,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.UUID;
@ -124,6 +125,7 @@ public class JdbcTypeJavaClassMappings {
workMap.put( Time.class, SqlTypes.TIME );
workMap.put( Timestamp.class, SqlTypes.TIMESTAMP );
workMap.put( LocalTime.class, SqlTypes.TIME );
workMap.put( OffsetTime.class, SqlTypes.TIME_WITH_TIMEZONE );
workMap.put( LocalDate.class, SqlTypes.DATE );
workMap.put( LocalDateTime.class, SqlTypes.TIMESTAMP );
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.TIME, Time.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.CLOB, Clob.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.Types;
import java.time.OffsetDateTime;
import java.util.Calendar;
/**
* Descriptor for {@link Types#TIMESTAMP_WITH_TIMEZONE TIMESTAMP_WITH_TIMEZONE} handling.
@ -75,16 +76,24 @@ public class TimestampWithTimeZoneJdbcType implements JdbcType {
PreparedStatement st,
X value,
int index,
WrapperOptions wrapperOptions) throws SQLException {
WrapperOptions options) throws SQLException {
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
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 );
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
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,
X value,
String name,
WrapperOptions wrapperOptions)
WrapperOptions options)
throws SQLException {
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
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 );
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
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) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int position, WrapperOptions wrapperOptions) throws SQLException {
protected X doExtract(ResultSet rs, int position, WrapperOptions options) throws SQLException {
try {
// 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) {
// 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
protected X doExtract(CallableStatement statement, int position, WrapperOptions wrapperOptions) throws SQLException {
protected X doExtract(CallableStatement statement, int position, WrapperOptions options) throws SQLException {
try {
// 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) {
// 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
protected X doExtract(CallableStatement statement, String name, WrapperOptions wrapperOptions) throws SQLException {
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
try {
// 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) {
// 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.SmallIntJdbcType;
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.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType;
@ -64,6 +65,7 @@ public class JdbcTypeBaseline {
target.addDescriptor( TimestampJdbcType.INSTANCE );
target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( TimeJdbcType.INSTANCE );
target.addDescriptor( TimeWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( BinaryJdbcType.INSTANCE );
target.addDescriptor( VarbinaryJdbcType.INSTANCE );

View File

@ -56,7 +56,7 @@ public class DdlTypeImpl implements DdlType {
final int paren = typeNamePattern.indexOf( '(' );
if ( paren > 0 ) {
final int parenEnd = typeNamePattern.lastIndexOf( ')' );
return parenEnd == typeNamePattern.length()
return parenEnd + 1 == typeNamePattern.length()
? typeNamePattern.substring( 0, paren )
: ( 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() ) );
case SqlTypes.DOUBLE:
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_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:

View File

@ -776,6 +776,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return TemporalType.TIMESTAMP;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return TemporalType.TIME;
case SqlTypes.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.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.Jpa;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -25,11 +30,15 @@ import jakarta.persistence.TypedQuery;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Jpa(
annotatedClasses = LocalTimeTest.TestEntity.class
annotatedClasses = LocalTimeTest.TestEntity.class,
properties = @Setting(name = AvailableSettings.TIMEZONE_DEFAULT_STORAGE, value = "NORMALIZE")
)
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 ) );

View File

@ -26,6 +26,9 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity;
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.H2Dialect;
import org.hibernate.dialect.HSQLDialect;
@ -41,6 +44,12 @@ import org.junit.runners.Parameterized;
*/
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> {
public ParametersBuilder add(int hour, int minute, int second, int nanosecond, String offset, ZoneId defaultTimeZone) {
if ( !isNanosecondPrecisionSupported() ) {

View File

@ -14,4 +14,25 @@ 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.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`.