Add SqlTypes.TIMESTAMP_UTC to as jdbc type for java.time.Instant

This commit is contained in:
Christian Beikov 2022-03-03 15:40:34 +01:00
parent 964e72f536
commit af9edd50d6
21 changed files with 1219 additions and 666 deletions

View File

@ -15,6 +15,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -41,7 +42,13 @@ public class InstantMappingTests {
final BasicAttributeMapping duration = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("instant");
final JdbcMapping jdbcMapping = duration.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Instant.class));
assertThat( jdbcMapping.getJdbcType().getJdbcTypeCode(), equalTo( Types.TIMESTAMP));
assertThat(
jdbcMapping.getJdbcType(),
equalTo(
mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry()
.getDescriptor( SqlTypes.TIMESTAMP_UTC )
)
);
scope.inTransaction(
(session) -> {

View File

@ -44,6 +44,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
@ -113,6 +114,9 @@ public class CockroachDialect extends Dialect {
return "bytes($l)";
case BLOB:
return "bytes";
case TIMESTAMP_UTC:
return columnType( TIMESTAMP_WITH_TIMEZONE );
}
return super.columnType( sqlTypeCode );
}
@ -188,6 +192,7 @@ public class CockroachDialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLIntervalSecondJdbcType.INSTANCE );

View File

@ -149,6 +149,8 @@ import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
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.JdbcType;
import org.hibernate.type.descriptor.jdbc.LongNVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.NCharJdbcType;
@ -306,6 +308,7 @@ public abstract class Dialect implements ConversionContext {
ddlTypeRegistry.addDescriptor( simpleSqlType( TIME_WITH_TIMEZONE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_WITH_TIMEZONE ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( TIMESTAMP_UTC ) );
ddlTypeRegistry.addDescriptor( simpleSqlType( CHAR ) );
ddlTypeRegistry.addDescriptor(
@ -410,6 +413,10 @@ public abstract class Dialect implements ConversionContext {
return "timestamp($p)";
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p) with time zone";
case TIMESTAMP_UTC:
return getTimeZoneSupport() == TimeZoneSupport.NATIVE
? columnType( TIMESTAMP_WITH_TIMEZONE )
: columnType( TIMESTAMP );
case CHAR:
return "char($l)";
@ -893,7 +900,7 @@ public abstract class Dialect implements ConversionContext {
"instant",
new CurrentFunction(
"instant",
currentTimestamp(),
currentTimestampWithTimeZone(),
instantType
)
);
@ -1241,6 +1248,13 @@ public abstract class Dialect implements ConversionContext {
ClobJdbcType.STREAM_BINDING
);
}
if ( getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
typeContributions.contributeJdbcType( InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
}
else {
typeContributions.contributeJdbcType( InstantAsTimestampJdbcType.INSTANCE );
}
}
/**
@ -3609,25 +3623,25 @@ public abstract class Dialect implements ConversionContext {
}
switch ( jdbcTypeCode ) {
case Types.BIT:
case Types.CHAR:
case Types.NCHAR:
case Types.VARCHAR:
case Types.NVARCHAR:
case Types.BINARY:
case Types.VARBINARY:
case Types.CLOB:
case Types.BLOB:
case SqlTypes.BIT:
case SqlTypes.CHAR:
case SqlTypes.NCHAR:
case SqlTypes.VARCHAR:
case SqlTypes.NVARCHAR:
case SqlTypes.BINARY:
case SqlTypes.VARBINARY:
case SqlTypes.CLOB:
case SqlTypes.BLOB:
size.setLength( javaType.getDefaultSqlLength( Dialect.this, jdbcType ) );
break;
case Types.LONGVARCHAR:
case Types.LONGNVARCHAR:
case Types.LONGVARBINARY:
case SqlTypes.LONGVARCHAR:
case SqlTypes.LONGNVARCHAR:
case SqlTypes.LONGVARBINARY:
size.setLength( javaType.getLongSqlLength() );
break;
case Types.FLOAT:
case Types.DOUBLE:
case Types.REAL:
case SqlTypes.FLOAT:
case SqlTypes.DOUBLE:
case SqlTypes.REAL:
// this is almost always the thing we use:
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
if ( scale != null && scale != 0 ) {
@ -3640,15 +3654,16 @@ public abstract class Dialect implements ConversionContext {
precision = (int) ceil( precision * LOG_BASE2OF10 );
}
break;
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
if ( scale != null && scale != 0 ) {
throw new IllegalArgumentException("scale has no meaning for timestamps");
}
break;
case Types.NUMERIC:
case Types.DECIMAL:
case SqlTypes.NUMERIC:
case SqlTypes.DECIMAL:
case SqlTypes.INTERVAL_SECOND:
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
break;

View File

@ -58,6 +58,7 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
@ -84,6 +85,7 @@ import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
/**
* A {@linkplain Dialect SQL dialect} for H2.
@ -228,6 +230,7 @@ public class H2Dialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantJdbcType.INSTANCE );
if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
}

View File

@ -1014,12 +1014,6 @@ public class MySQLDialect extends Dialect {
return getMySQLVersion().isBefore( 5, 5 ) ? MyISAMStorageEngine.INSTANCE : InnoDBStorageEngine.INSTANCE;
}
@Override
public TimeZoneSupport getTimeZoneSupport() {
// In MySQL and MariaDB, the TIMESTAMP type normalize to UTC just like PostgreSQL
return TimeZoneSupport.NORMALIZE;
}
@Override
public void appendLiteral(SqlAppender appender, String literal) {
appender.appendSql( '\'' );

View File

@ -67,6 +67,7 @@ import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
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.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
@ -154,6 +155,9 @@ public class PostgreSQLDialect extends Dialect {
case VARBINARY:
case LONG32VARBINARY:
return "bytea";
case TIMESTAMP_UTC:
return columnType( TIMESTAMP_WITH_TIMEZONE );
}
return super.columnType( sqlTypeCode );
}
@ -1065,6 +1069,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 );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLInetJdbcType.INSTANCE );

View File

@ -71,7 +71,7 @@ public class Replacer {
for ( Replacement replacement : replacements ) {
int result = replacement.apply( chunk, position );
if ( result >= 0 ) {
position += result;
position += result - 1;
break;
}
}

View File

@ -437,7 +437,7 @@ public class SpannerDialect extends Dialect {
queryEngine.getSqmFunctionRegistry().register(
"format",
new FormatFunction( "format_timestamp", true, queryEngine.getTypeConfiguration() )
new FormatFunction( "format_timestamp", true, true, queryEngine.getTypeConfiguration() )
);
functionFactory.listagg_stringAgg( "string" );
functionFactory.inverseDistributionOrderedSetAggregates();

View File

@ -9,6 +9,14 @@ package org.hibernate.dialect.function;
import java.util.List;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.FunctionRenderingSupport;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
@ -26,12 +34,13 @@ import jakarta.persistence.TemporalType;
*
* @author Gavin King
*/
public class DB2FormatEmulation
extends FormatFunction {
public class DB2FormatEmulation extends FormatFunction {
public DB2FormatEmulation(TypeConfiguration typeConfiguration) {
super(
"format",
"varchar_format",
false,
false,
typeConfiguration
);
}
@ -41,47 +50,19 @@ public class DB2FormatEmulation
SqlAppender sqlAppender,
List<? extends SqlAstNode> arguments,
SqlAstTranslator<?> walker) {
final Expression datetime = (Expression) arguments.get(0);
final boolean isTime = TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME;
final Format format = (Format) arguments.get(1);
sqlAppender.appendSql("(");
String[] bits = OracleDialect.datetimeFormat( format.getFormat(), false, false ).result().split("\"");
boolean first = true;
for ( int i=0; i<bits.length; i++ ) {
String bit = bits[i];
if ( !bit.isEmpty() ) {
if ( first ) {
first = false;
}
else {
sqlAppender.appendSql("||");
}
if ( i % 2 == 0 ) {
sqlAppender.appendSql("varchar_format(");
// Times need to be wrapped into a timestamp to be able to use formatting
if ( isTime ) {
sqlAppender.appendSql( "timestamp(current_date," );
datetime.accept( walker );
sqlAppender.appendSql( ")" );
}
else {
datetime.accept( walker );
}
sqlAppender.appendSql(",'");
sqlAppender.appendSql( bit );
sqlAppender.appendSql("')");
}
else {
sqlAppender.appendSql("'");
sqlAppender.appendSql( bit );
sqlAppender.appendSql("'");
}
}
final Expression datetime = (Expression) arguments.get( 0 );
sqlAppender.appendSql( "varchar_format(" );
// Times need to be wrapped into a timestamp to be able to use formatting
if ( TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME ) {
sqlAppender.appendSql( "timestamp(current_date," );
datetime.accept( walker );
sqlAppender.appendSql( ")" );
}
if ( first ) {
sqlAppender.appendSql("''");
else {
datetime.accept( walker );
}
sqlAppender.appendSql(")");
sqlAppender.appendSql( "," );
arguments.get( 1 ).accept( walker );
sqlAppender.appendSql( ")" );
}
}

View File

@ -18,7 +18,7 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
* @author Gavin King
*/
public class Format implements SqlExpressible, SqlAstNode {
private String format;
private final String format;
public Format(String format) {
this.format = format;

View File

@ -425,6 +425,13 @@ public class SqlTypes {
*/
public static final int INET = 3002;
/**
* The constant in the Java programming language, sometimes referred to
* as a type code, that identifies the generic SQL type
* {@code TIMESTAMP_UTC}.
*/
public static final int TIMESTAMP_UTC = 3003;
// Interval types
/**

View File

@ -458,7 +458,7 @@ public final class StandardBasicTypes {
public static final BasicTypeReference<Instant> INSTANT = new BasicTypeReference<>(
"instant",
Instant.class,
SqlTypes.TIMESTAMP
SqlTypes.TIMESTAMP_UTC
);
/**

View File

@ -22,6 +22,7 @@ import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -68,7 +69,7 @@ public class InstantJavaType extends AbstractTemporalJavaType<Instant>
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
return context.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( Types.TIMESTAMP );
return context.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( SqlTypes.TIMESTAMP_UTC );
}
@Override

View File

@ -0,0 +1,116 @@
/*
* 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.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.
*
* @author Christian Beikov
*/
public class InstantAsTimestampJdbcType implements JdbcType {
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> BasicJavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return (BasicJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( 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,123 @@
/*
* 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.time.OffsetDateTime;
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.
*
* @author Christian Beikov
*/
public class InstantAsTimestampWithTimeZoneJdbcType implements JdbcType {
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> BasicJavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return (BasicJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( 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 OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, wrapperOptions );
// supposed to be supported in JDBC 4.2
st.setObject( index, dateTime, 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, 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

@ -0,0 +1,155 @@
/*
* 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.time.OffsetDateTime;
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.
*
* @author Christian Beikov
*/
public class InstantJdbcType implements JdbcType {
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> BasicJavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return (BasicJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( 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

@ -21,6 +21,7 @@ import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -127,6 +128,7 @@ public class JdbcTypeJavaClassMappings {
workMap.put( LocalDateTime.class, SqlTypes.TIMESTAMP );
workMap.put( OffsetDateTime.class, SqlTypes.TIMESTAMP_WITH_TIMEZONE );
workMap.put( ZonedDateTime.class, SqlTypes.TIMESTAMP_WITH_TIMEZONE );
workMap.put( Instant.class, SqlTypes.TIMESTAMP_UTC );
workMap.put( Blob.class, SqlTypes.BLOB );
workMap.put( Clob.class, SqlTypes.CLOB );
workMap.put( Array.class, SqlTypes.ARRAY );
@ -190,6 +192,7 @@ public class JdbcTypeJavaClassMappings {
workMap.put( SqlTypes.SQLXML, SQLXML.class );
workMap.put( SqlTypes.UUID, UUID.class );
workMap.put( SqlTypes.INET, InetAddress.class );
workMap.put( SqlTypes.TIMESTAMP_UTC, Instant.class );
workMap.put( SqlTypes.INTERVAL_SECOND, Duration.class );
return workMap;

View File

@ -14,6 +14,7 @@ import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.sql.DdlType;
import org.hibernate.type.spi.TypeConfiguration;
@ -78,12 +79,12 @@ public class DdlTypeRegistry implements Serializable {
// they're just used to indicate that JavaType.getLongSqlLength()
// should be used by default (and that's already handled by the
// time we get to here)
case Types.LONGVARCHAR:
return ddlTypes.get( Types.VARCHAR );
case Types.LONGNVARCHAR:
return ddlTypes.get( Types.NVARCHAR );
case Types.LONGVARBINARY:
return ddlTypes.get( Types.VARBINARY );
case SqlTypes.LONGVARCHAR:
return ddlTypes.get( SqlTypes.VARCHAR );
case SqlTypes.LONGNVARCHAR:
return ddlTypes.get( SqlTypes.NVARCHAR );
case SqlTypes.LONGVARBINARY:
return ddlTypes.get( SqlTypes.VARBINARY );
}
}
return ddlType;
@ -92,16 +93,17 @@ public class DdlTypeRegistry implements Serializable {
public String getTypeName(int typeCode, Dialect dialect) {
// explicitly enforce dialect's default precisions
switch ( typeCode ) {
case Types.DECIMAL:
case Types.NUMERIC:
case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC:
return getTypeName( typeCode, Size.precision( dialect.getDefaultDecimalPrecision() ) );
case Types.FLOAT:
case Types.REAL:
case SqlTypes.FLOAT:
case SqlTypes.REAL:
return getTypeName( typeCode, Size.precision( dialect.getFloatPrecision() ) );
case Types.DOUBLE:
case SqlTypes.DOUBLE:
return getTypeName( typeCode, Size.precision( dialect.getDoublePrecision() ) );
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
return getTypeName( typeCode, Size.precision( dialect.getDefaultTimestampPrecision() ) );
default:
return getTypeName( typeCode, Size.nil() );

View File

@ -354,7 +354,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return Types.BOOLEAN;
return SqlTypes.BOOLEAN;
}
};
@ -635,13 +635,14 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
protected static TemporalType getSqlTemporalType(int jdbcTypeCode) {
switch ( jdbcTypeCode ) {
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
return TemporalType.TIMESTAMP;
case Types.TIME:
case Types.TIME_WITH_TIMEZONE:
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
return TemporalType.TIME;
case Types.DATE:
case SqlTypes.DATE:
return TemporalType.DATE;
}
return null;

View File

@ -16,7 +16,10 @@ import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@ -27,6 +30,7 @@ import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.dialect.TimeZoneSupport;
import org.junit.runners.Parameterized;
@ -149,25 +153,33 @@ public class InstantTest extends AbstractJavaTimeTypeTest<Instant, InstantTest.E
@Override
protected void setJdbcValueForNonHibernateWrite(PreparedStatement statement, int parameterIndex) throws SQLException {
statement.setTimestamp( parameterIndex, getExpectedJdbcValueAfterHibernateWrite() );
if ( sessionFactory().getJdbcServices().getDialect().getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
// Oracle and H2 require reading/writing through OffsetDateTime to avoid TZ related miscalculations
statement.setObject( parameterIndex, getExpectedJdbcValueAfterHibernateWrite().toInstant().atOffset( ZoneOffset.UTC ) );
}
else {
statement.setTimestamp(
parameterIndex,
getExpectedJdbcValueAfterHibernateWrite(),
Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) )
);
}
}
@Override
protected Timestamp getExpectedJdbcValueAfterHibernateWrite() {
LocalDateTime dateTimeInDefaultTimeZone = getExpectedPropertyValueAfterHibernateRead().atZone( ZoneId.systemDefault() )
.toLocalDateTime();
return new Timestamp(
dateTimeInDefaultTimeZone.getYear() - 1900, dateTimeInDefaultTimeZone.getMonthValue() - 1,
dateTimeInDefaultTimeZone.getDayOfMonth(),
dateTimeInDefaultTimeZone.getHour(), dateTimeInDefaultTimeZone.getMinute(),
dateTimeInDefaultTimeZone.getSecond(),
dateTimeInDefaultTimeZone.getNano()
);
return Timestamp.from( getExpectedPropertyValueAfterHibernateRead() );
}
@Override
protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException {
return resultSet.getTimestamp( columnIndex );
if ( sessionFactory().getJdbcServices().getDialect().getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
// Oracle and H2 require reading/writing through OffsetDateTime to avoid TZ related miscalculations
return Timestamp.from( resultSet.getObject( columnIndex, OffsetDateTime.class ).toInstant() );
}
else {
return resultSet.getTimestamp( columnIndex, Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
}
}
@Entity(name = ENTITY_NAME)