Skip to content

Commit 12256c1

Browse files
committed
[#1538] Convert timestamps to OffsetDateTime
Except for MySQL because the Vert.x client doesn't like when we try to save temporal types with a time zone.
1 parent 915a3ec commit 12256c1

File tree

2 files changed

+124
-5
lines changed

2 files changed

+124
-5
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@
3333
import java.sql.SQLXML;
3434
import java.sql.Time;
3535
import java.sql.Timestamp;
36+
import java.time.ZonedDateTime;
37+
import java.time.temporal.Temporal;
3638
import java.util.Arrays;
3739
import java.util.Calendar;
40+
import java.util.function.Function;
3841

3942
/**
4043
* Collects parameter bindings from Hibernate core code
@@ -168,7 +171,16 @@ public void setTimestamp(int parameterIndex, Timestamp x) {
168171

169172
@Override
170173
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) {
171-
put( parameterIndex, x.toInstant().atZone( cal.getTimeZone().toZoneId() ).toLocalDateTime() );
174+
setTimestamp( parameterIndex, x, cal, ZonedDateTime::toOffsetDateTime );
175+
}
176+
177+
// Sometimes we need a different approach depending on the database
178+
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal, Function<ZonedDateTime, Temporal> converter) {
179+
put( parameterIndex, converter.apply( x.toInstant().atZone( cal.getTimeZone().toZoneId() ) ) );
180+
}
181+
182+
public void setTimestamp(String name, Timestamp x, Calendar cal, Function<ZonedDateTime, Temporal> converter) {
183+
throw new UnsupportedOperationException();
172184
}
173185

174186
@Override

hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@
66
package org.hibernate.reactive.provider.impl;
77

88
import java.lang.reflect.Type;
9+
import java.sql.CallableStatement;
10+
import java.sql.PreparedStatement;
11+
import java.sql.SQLException;
12+
import java.sql.Timestamp;
913
import java.sql.Types;
14+
import java.time.Instant;
15+
import java.time.ZonedDateTime;
16+
import java.util.Calendar;
17+
import java.util.TimeZone;
1018

1119
import org.hibernate.boot.model.TypeContributions;
1220
import org.hibernate.boot.model.TypeContributor;
1321
import org.hibernate.dialect.Dialect;
22+
import org.hibernate.dialect.MySQLDialect;
1423
import org.hibernate.dialect.PostgreSQLDialect;
1524
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
25+
import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor;
26+
import org.hibernate.reactive.dialect.ReactiveDialectWrapper;
1627
import org.hibernate.service.ServiceRegistry;
1728
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
1829
import org.hibernate.type.BasicTypeRegistry;
@@ -24,13 +35,18 @@
2435
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
2536
import org.hibernate.type.descriptor.java.StringJavaType;
2637
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
38+
import org.hibernate.type.descriptor.jdbc.BasicBinder;
2739
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
2840
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
2941
import org.hibernate.type.descriptor.jdbc.JdbcType;
3042
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
3143
import org.hibernate.type.descriptor.jdbc.ObjectJdbcType;
44+
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
45+
import org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType;
46+
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
3247
import org.hibernate.type.descriptor.sql.DdlType;
3348
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
49+
import org.hibernate.type.spi.TypeConfiguration;
3450

3551
import io.vertx.core.json.JsonObject;
3652

@@ -52,21 +68,112 @@ public void contribute(TypeContributions typeContributions, ServiceRegistry serv
5268
}
5369

5470
private void registerReactiveChanges(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
55-
BasicTypeRegistry basicTypeRegistry = typeContributions.getTypeConfiguration().getBasicTypeRegistry();
56-
DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
71+
Dialect dialect = dialect( serviceRegistry );
72+
TypeConfiguration typeConfiguration = typeContributions.getTypeConfiguration();
73+
74+
DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
5775
ddlTypeRegistry.addDescriptor( Types.JAVA_OBJECT, new JavaObjectDdlType() );
5876

59-
JavaTypeRegistry javaTypeRegistry = typeContributions.getTypeConfiguration().getJavaTypeRegistry();
77+
JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
6078
javaTypeRegistry.addDescriptor( JsonObjectJavaType.INSTANCE );
6179

62-
Dialect dialect = serviceRegistry.getService( JdbcEnvironment.class ).getDialect();
80+
if ( dialect instanceof MySQLDialect ) {
81+
JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry();
82+
jdbcTypeRegistry.addDescriptor( TimestampAsLocalDateTimeJdbcType.INSTANCE );
83+
jdbcTypeRegistry.addDescriptor( TimestampUtcAsLocalDateTimeJdbcType.INSTANCE );
84+
}
85+
86+
BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
6387
basicTypeRegistry.register( new JsonType( dialect ) );
6488
// FIXME: I think only Postgres, needs this special type because of the way the driver returns the SQL type
6589
// We could only add them for Postgres
6690
basicTypeRegistry.register( new BlobType( dialect ) );
6791
basicTypeRegistry.register( new ClobType( dialect ) );
6892
}
6993

94+
private Dialect dialect(ServiceRegistry serviceRegistry) {
95+
Dialect dialect = serviceRegistry.getService( JdbcEnvironment.class ).getDialect();
96+
return dialect instanceof ReactiveDialectWrapper
97+
? ( (ReactiveDialectWrapper) dialect ).getWrappedDialect()
98+
: dialect;
99+
}
100+
101+
/**
102+
* Some database (MySQL for example) don't like saving temporal types with a timezone.
103+
*
104+
* @see TimestampJdbcType
105+
*/
106+
private static class TimestampAsLocalDateTimeJdbcType extends TimestampJdbcType {
107+
public static final TimestampAsLocalDateTimeJdbcType INSTANCE = new TimestampAsLocalDateTimeJdbcType();
108+
109+
@Override
110+
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
111+
return new BasicBinder<>( javaType, this ) {
112+
@Override
113+
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
114+
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
115+
if ( value instanceof Calendar ) {
116+
( (PreparedStatementAdaptor) st )
117+
.setTimestamp( index, timestamp, (Calendar) value, ZonedDateTime::toLocalDateTime );
118+
}
119+
else if ( options.getJdbcTimeZone() != null ) {
120+
( (PreparedStatementAdaptor) st )
121+
.setTimestamp( index, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ), ZonedDateTime::toLocalDateTime );
122+
}
123+
else {
124+
st.setTimestamp( index, timestamp );
125+
}
126+
}
127+
128+
@Override
129+
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
130+
throws SQLException {
131+
final Timestamp timestamp = javaType.unwrap( value, Timestamp.class, options );
132+
if ( value instanceof Calendar ) {
133+
( (PreparedStatementAdaptor) st )
134+
.setTimestamp( name, timestamp, (Calendar) value, ZonedDateTime::toLocalDateTime );
135+
}
136+
else if ( options.getJdbcTimeZone() != null ) {
137+
( (PreparedStatementAdaptor) st )
138+
.setTimestamp( name, timestamp, Calendar.getInstance( options.getJdbcTimeZone() ), ZonedDateTime::toLocalDateTime );
139+
}
140+
else {
141+
st.setTimestamp( name, timestamp );
142+
}
143+
}
144+
};
145+
}
146+
}
147+
148+
/**
149+
* Some database (MySQL for example) don't like saving temporal types with a timezone.
150+
*
151+
* @see TimestampUtcAsJdbcTimestampJdbcType
152+
*/
153+
private static class TimestampUtcAsLocalDateTimeJdbcType extends TimestampUtcAsJdbcTimestampJdbcType {
154+
private static final Calendar UTC_CALENDAR = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
155+
156+
public static final TimestampUtcAsLocalDateTimeJdbcType INSTANCE = new TimestampUtcAsLocalDateTimeJdbcType();
157+
158+
@Override
159+
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
160+
return new BasicBinder<>( javaType, this ) {
161+
@Override
162+
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
163+
final Instant instant = javaType.unwrap( value, Instant.class, options );
164+
( (PreparedStatementAdaptor) st).setTimestamp( index, Timestamp.from( instant ), UTC_CALENDAR, ZonedDateTime::toLocalDateTime );
165+
}
166+
167+
@Override
168+
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
169+
throws SQLException {
170+
final Instant instant = javaType.unwrap( value, Instant.class, options );
171+
( (PreparedStatementAdaptor) st).setTimestamp( name, Timestamp.from( instant ), UTC_CALENDAR, ZonedDateTime::toLocalDateTime );
172+
}
173+
};
174+
}
175+
}
176+
70177
private static class JavaObjectDdlType implements DdlType {
71178

72179
@Override

0 commit comments

Comments
 (0)