Skip to content

Commit 271121b

Browse files
committed
wip
1 parent 90609c8 commit 271121b

File tree

9 files changed

+232
-34
lines changed

9 files changed

+232
-34
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,9 +1491,9 @@ public void appendDateTimeLiteral(
14911491
appender.appendSql( '\'' );
14921492
break;
14931493
case TIME:
1494-
appender.appendSql( "time '" );
1495-
appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
1496-
appender.appendSql( '\'' );
1494+
appender.appendSql( "to_date('1970-01-01 " );
1495+
appendAsTime( appender, temporalAccessor, false, jdbcTimeZone );
1496+
appender.appendSql( "','YYYY-MM-DD HH24:MI:SS')" );
14971497
break;
14981498
case TIMESTAMP:
14991499
appender.appendSql( "timestamp '" );
@@ -1514,9 +1514,9 @@ public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType
15141514
appender.appendSql( '\'' );
15151515
break;
15161516
case TIME:
1517-
appender.appendSql( "time '" );
1517+
appender.appendSql( "to_date('1970-01-01 " );
15181518
appendAsLocalTime( appender, date );
1519-
appender.appendSql( '\'' );
1519+
appender.appendSql( "','YYYY-MM-DD HH24:MI:SS')" );
15201520
break;
15211521
case TIMESTAMP:
15221522
appender.appendSql( "timestamp '" );
@@ -1537,9 +1537,9 @@ public void appendDateTimeLiteral(SqlAppender appender, Calendar calendar, Tempo
15371537
appender.appendSql( '\'' );
15381538
break;
15391539
case TIME:
1540-
appender.appendSql( "time '" );
1540+
appender.appendSql( "to_date('1970-01-01 " );
15411541
appendAsLocalTime( appender, calendar );
1542-
appender.appendSql( '\'' );
1542+
appender.appendSql( "','YYYY-MM-DD HH24:MI:SS')" );
15431543
break;
15441544
case TIMESTAMP:
15451545
appender.appendSql( "timestamp '" );

hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@
166166
import static org.hibernate.type.SqlTypes.TINYINT;
167167
import static org.hibernate.type.SqlTypes.VARBINARY;
168168
import static org.hibernate.type.SqlTypes.VARCHAR;
169-
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
170-
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_TIME;
171169
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDateWithoutYear0;
172170
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime;
173171
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
@@ -1572,9 +1570,9 @@ public void appendDateTimeLiteral(
15721570
appender.appendSql( '\'' );
15731571
break;
15741572
case TIME:
1575-
appender.appendSql( "time '" );
1576-
appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
1577-
appender.appendSql( '\'' );
1573+
appender.appendSql( "to_date('1970-01-01 " );
1574+
appendAsTime( appender, temporalAccessor, false, jdbcTimeZone );
1575+
appender.appendSql( "','YYYY-MM-DD HH24:MI:SS')" );
15781576
break;
15791577
case TIMESTAMP:
15801578
appender.appendSql( "timestamp '" );
@@ -1595,9 +1593,9 @@ public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType
15951593
appender.appendSql( '\'' );
15961594
break;
15971595
case TIME:
1598-
appender.appendSql( "time '" );
1596+
appender.appendSql( "to_date('1970-01-01 " );
15991597
appendAsLocalTime( appender, date );
1600-
appender.appendSql( '\'' );
1598+
appender.appendSql( "','YYYY-MM-DD HH24:MI:SS')" );
16011599
break;
16021600
case TIMESTAMP:
16031601
appender.appendSql( "timestamp '" );
@@ -1618,9 +1616,9 @@ public void appendDateTimeLiteral(SqlAppender appender, Calendar calendar, Tempo
16181616
appender.appendSql( '\'' );
16191617
break;
16201618
case TIME:
1621-
appender.appendSql( JDBC_ESCAPE_START_TIME );
1619+
appender.appendSql( "to_date('1970-01-01 " );
16221620
appendAsLocalTime( appender, calendar );
1623-
appender.appendSql( JDBC_ESCAPE_END );
1621+
appender.appendSql( "','YYYY-MM-DD HH24:MI:SS')" );
16241622
break;
16251623
case TIMESTAMP:
16261624
appender.appendSql( "timestamp '" );

hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import org.hibernate.type.BasicTypeRegistry;
8282
import org.hibernate.type.StandardBasicTypes;
8383
import org.hibernate.type.descriptor.java.JavaType;
84+
import org.hibernate.type.descriptor.jdbc.GregorianEpochBasedTimestampUtcAsJdbcTimestampJdbcType;
8485
import org.hibernate.type.descriptor.jdbc.JdbcType;
8586
import org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType;
8687
import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType;
@@ -349,7 +350,9 @@ public int getMaxIdentifierLength() {
349350
@Override
350351
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
351352
super.contributeTypes( typeContributions, serviceRegistry );
353+
// typeContributions.contributeJdbcType( GregorianEpochBasedTimestampWithTimeZoneJdbcType.INSTANCE );
352354
// Need to bind as java.sql.Timestamp because reading OffsetDateTime from a "datetime2" column fails
355+
typeContributions.contributeJdbcType( GregorianEpochBasedTimestampUtcAsJdbcTimestampJdbcType.INSTANCE );
353356
typeContributions.contributeJdbcType( TimestampUtcAsJdbcTimestampJdbcType.INSTANCE );
354357
typeContributions.getTypeConfiguration().getJdbcTypeRegistry()
355358
.addDescriptor( Types.TINYINT, TinyIntAsSmallIntJdbcType.INSTANCE );

hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.time.LocalDateTime;
1212
import java.time.LocalTime;
1313
import java.time.ZoneId;
14+
import java.time.ZoneOffset;
1415
import java.time.ZonedDateTime;
1516
import java.time.chrono.IsoEra;
1617
import java.time.format.DateTimeFormatter;
@@ -988,6 +989,29 @@ public static Timestamp toTimestamp(Instant instant) {
988989
}
989990
}
990991

992+
public static Timestamp toUtcTimestamp(Instant instant) {
993+
/*
994+
* This works around two bugs:
995+
* - HHH-13266 (JDK-8061577): around and before 1900,
996+
* the number of milliseconds since the epoch does not mean the same thing
997+
* for java.util and java.time, so conversion must be done using the year, month, day, hour, etc.
998+
* - HHH-13379 (JDK-4312621): after 1908 (approximately),
999+
* Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year
1000+
* (on DST end), so conversion must be done using the number of milliseconds since the epoch.
1001+
* - around 1905, both methods are equally valid, so we don't really care which one is used.
1002+
*/
1003+
final ZonedDateTime zonedDateTime = instant.atZone( ZoneOffset.UTC );
1004+
if ( zonedDateTime.getYear() < 1905 ) {
1005+
return new Timestamp(
1006+
Timestamp.valueOf( zonedDateTime.toLocalDateTime() ).getTime()
1007+
+ TimeZone.getDefault().getOffset( instant.toEpochMilli() )
1008+
);
1009+
}
1010+
else {
1011+
return Timestamp.from( instant );
1012+
}
1013+
}
1014+
9911015
public static java.sql.Date toSqlDate(Instant instant) {
9921016
/*
9931017
* This works around two bugs:
@@ -1027,6 +1051,26 @@ public static Instant toInstant(Timestamp timestamp) {
10271051
}
10281052
}
10291053

1054+
public static Instant toUtcInstant(Timestamp timestamp) {
1055+
/*
1056+
* This works around two bugs:
1057+
* - HHH-13266 (JDK-8061577): around and before 1900,
1058+
* the number of milliseconds since the epoch does not mean the same thing
1059+
* for java.util and java.time, so conversion must be done using the year, month, day, hour, etc.
1060+
* - HHH-13379 (JDK-4312621): after 1908 (approximately),
1061+
* Daylight Saving Time introduces ambiguity in the year/month/day/hour/etc representation once a year
1062+
* (on DST end), so conversion must be done using the number of milliseconds since the epoch.
1063+
* - around 1905, both methods are equally valid, so we don't really care which one is used.
1064+
*/
1065+
if ( timestamp.getYear() < 5 ) { // Timestamp year 0 is 1900
1066+
return toLocalDateTime( timestamp ).minusSeconds( TimeZone.getDefault().getOffset( timestamp.getTime() ) / 1000L )
1067+
.toInstant( ZoneOffset.UTC );
1068+
}
1069+
else {
1070+
return timestamp.toInstant();
1071+
}
1072+
}
1073+
10301074
public static Instant toInstant(Date date) {
10311075
if ( date instanceof Timestamp timestamp ) {
10321076
return toInstant( timestamp );

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateJavaType.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ public boolean areEqual(Date one, Date another) {
8989

9090
return calendar1.get( Calendar.MONTH ) == calendar2.get( Calendar.MONTH )
9191
&& calendar1.get( Calendar.DAY_OF_MONTH ) == calendar2.get( Calendar.DAY_OF_MONTH )
92-
&& calendar1.get( Calendar.YEAR ) == calendar2.get( Calendar.YEAR );
92+
&& calendar1.get( Calendar.YEAR ) == calendar2.get( Calendar.YEAR )
93+
&& calendar1.get( Calendar.ERA ) == calendar2.get( Calendar.ERA );
9394
}
9495

9596
@Override
@@ -100,6 +101,7 @@ public int extractHashCode(Date value) {
100101
hashCode = 31 * hashCode + calendar.get( Calendar.MONTH );
101102
hashCode = 31 * hashCode + calendar.get( Calendar.DAY_OF_MONTH );
102103
hashCode = 31 * hashCode + calendar.get( Calendar.YEAR );
104+
hashCode = 31 * hashCode + calendar.get( Calendar.ERA );
103105
return hashCode;
104106
}
105107

hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/GregorianEpochBasedDateJdbcType.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions
6262
@Override
6363
public Date getBindValue(X value, WrapperOptions options) {
6464
final Date date = javaType.unwrap( value, Date.class, options );
65-
if ( value instanceof Calendar ) {
66-
return date;
67-
}
68-
else if ( date.getTime() < DateTimeUtils.GREGORIAN_START_EPOCH_MILLIS ) {
65+
if ( date.getTime() < DateTimeUtils.GREGORIAN_START_EPOCH_MILLIS ) {
6966
final long epochSecond =
7067
DateTimeUtils.toLocalDate( date ).toEpochSecond( LocalTime.MIN, ZoneOffset.UTC );
7168
return new java.sql.Date( epochSecond * 1000 );

hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/GregorianEpochBasedTimestampJdbcType.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,7 @@ else if ( options.getJdbcTimeZone() != null ) {
6767
@Override
6868
public Timestamp getBindValue(X value, WrapperOptions options) {
6969
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
70-
if ( value instanceof Calendar ) {
71-
return timestamp;
72-
}
73-
else if ( timestamp.getTime() < DateTimeUtils.GREGORIAN_START_EPOCH_MILLIS ) {
70+
if ( timestamp.getTime() < DateTimeUtils.GREGORIAN_START_EPOCH_MILLIS ) {
7471
final long epochSecond =
7572
DateTimeUtils.toLocalDateTime( timestamp ).toEpochSecond( ZoneOffset.UTC );
7673
return new Timestamp( epochSecond * 1000 );
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.type.descriptor.jdbc;
6+
7+
import jakarta.persistence.TemporalType;
8+
import org.hibernate.type.SqlTypes;
9+
import org.hibernate.type.descriptor.DateTimeUtils;
10+
import org.hibernate.type.descriptor.ValueBinder;
11+
import org.hibernate.type.descriptor.ValueExtractor;
12+
import org.hibernate.type.descriptor.WrapperOptions;
13+
import org.hibernate.type.descriptor.java.JavaType;
14+
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterTemporal;
15+
import org.hibernate.type.spi.TypeConfiguration;
16+
17+
import java.sql.CallableStatement;
18+
import java.sql.PreparedStatement;
19+
import java.sql.ResultSet;
20+
import java.sql.SQLException;
21+
import java.sql.Timestamp;
22+
import java.sql.Types;
23+
import java.time.Instant;
24+
import java.time.ZoneOffset;
25+
import java.util.Calendar;
26+
import java.util.TimeZone;
27+
28+
/**
29+
* Descriptor for {@link SqlTypes#TIMESTAMP_UTC TIMESTAMP_UTC} handling.
30+
*
31+
* @author Christian Beikov
32+
*/
33+
public class GregorianEpochBasedTimestampUtcAsJdbcTimestampJdbcType implements JdbcType {
34+
35+
public static final GregorianEpochBasedTimestampUtcAsJdbcTimestampJdbcType INSTANCE = new GregorianEpochBasedTimestampUtcAsJdbcTimestampJdbcType();
36+
private static final Calendar UTC_CALENDAR = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
37+
38+
public GregorianEpochBasedTimestampUtcAsJdbcTimestampJdbcType() {
39+
}
40+
41+
@Override
42+
public int getJdbcTypeCode() {
43+
return Types.TIMESTAMP;
44+
}
45+
46+
@Override
47+
public int getDefaultSqlTypeCode() {
48+
return SqlTypes.TIMESTAMP_UTC;
49+
}
50+
51+
@Override
52+
public String getFriendlyName() {
53+
return "TIMESTAMP_UTC";
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return "TimestampUtcDescriptor";
59+
}
60+
61+
@Override
62+
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
63+
Integer length,
64+
Integer scale,
65+
TypeConfiguration typeConfiguration) {
66+
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Instant.class );
67+
}
68+
69+
@Override
70+
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
71+
return Instant.class;
72+
}
73+
74+
@Override
75+
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
76+
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
77+
}
78+
79+
@Override
80+
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
81+
return new BasicBinder<>( javaType, this ) {
82+
@Override
83+
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
84+
// final Instant instant = javaType.unwrap( value, Instant.class, options );
85+
st.setTimestamp( index, getBindValue( value, options ), UTC_CALENDAR );
86+
}
87+
88+
@Override
89+
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
90+
throws SQLException {
91+
// final Instant instant = javaType.unwrap( value, Instant.class, options );
92+
st.setTimestamp( name, getBindValue( value, options ), UTC_CALENDAR );
93+
}
94+
95+
@Override
96+
public Timestamp getBindValue(X value, WrapperOptions options) {
97+
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
98+
if ( timestamp.getTime() < DateTimeUtils.GREGORIAN_START_EPOCH_MILLIS ) {
99+
final long epochSecond =
100+
DateTimeUtils.toLocalDateTime( timestamp ).toEpochSecond( ZoneOffset.UTC );
101+
return new Timestamp( epochSecond * 1000 );
102+
}
103+
else {
104+
return timestamp;
105+
}
106+
}
107+
};
108+
}
109+
110+
@Override
111+
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
112+
return new BasicExtractor<>( javaType, this ) {
113+
@Override
114+
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
115+
return getExtractValue( rs.getTimestamp( paramIndex, UTC_CALENDAR ), options );
116+
}
117+
118+
@Override
119+
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
120+
return getExtractValue( statement.getTimestamp( index, UTC_CALENDAR ), options );
121+
}
122+
123+
@Override
124+
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
125+
return getExtractValue( statement.getTimestamp( name, UTC_CALENDAR ), options );
126+
}
127+
128+
private X getExtractValue(Timestamp value, WrapperOptions options) {
129+
if ( value != null && value.getTime() < DateTimeUtils.GREGORIAN_START_EPOCH_MILLIS ) {
130+
final Timestamp julianTimestamp = Timestamp.valueOf(
131+
Instant.ofEpochMilli( value.getTime() ).atOffset( ZoneOffset.UTC ).toLocalDateTime()
132+
);
133+
return javaType.wrap( julianTimestamp, options );
134+
}
135+
else {
136+
return javaType.wrap( value, options );
137+
}
138+
}
139+
};
140+
}
141+
}

0 commit comments

Comments
 (0)