Add SqlTypes.TIMESTAMP_UTC to as jdbc type for java.time.Instant
This commit is contained in:
parent
964e72f536
commit
af9edd50d6
|
@ -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) -> {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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( '\'' );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,26 +50,10 @@ 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(");
|
||||
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 ( isTime ) {
|
||||
if ( TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME ) {
|
||||
sqlAppender.appendSql( "timestamp(current_date," );
|
||||
datetime.accept( walker );
|
||||
sqlAppender.appendSql( ")" );
|
||||
|
@ -68,20 +61,8 @@ public class DB2FormatEmulation
|
|||
else {
|
||||
datetime.accept( walker );
|
||||
}
|
||||
sqlAppender.appendSql(",'");
|
||||
sqlAppender.appendSql( bit );
|
||||
sqlAppender.appendSql("')");
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql("'");
|
||||
sqlAppender.appendSql( bit );
|
||||
sqlAppender.appendSql("'");
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( first ) {
|
||||
sqlAppender.appendSql("''");
|
||||
}
|
||||
sqlAppender.appendSql(")");
|
||||
sqlAppender.appendSql( "," );
|
||||
arguments.get( 1 ).accept( walker );
|
||||
sqlAppender.appendSql( ")" );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
|
@ -24,6 +25,8 @@ import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
|
|||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
|
||||
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
|
@ -59,12 +62,17 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
|
||||
private final String nativeFunctionName;
|
||||
private final boolean reversedArguments;
|
||||
private final boolean concatPattern;
|
||||
|
||||
public FormatFunction(String nativeFunctionName, TypeConfiguration typeConfiguration) {
|
||||
this( nativeFunctionName, false, typeConfiguration );
|
||||
this( nativeFunctionName, false, true, typeConfiguration );
|
||||
}
|
||||
|
||||
public FormatFunction(String nativeFunctionName, boolean reversedArguments, TypeConfiguration typeConfiguration) {
|
||||
public FormatFunction(
|
||||
String nativeFunctionName,
|
||||
boolean reversedArguments,
|
||||
boolean concatPattern,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"format",
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING ),
|
||||
|
@ -74,6 +82,7 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
);
|
||||
this.nativeFunctionName = nativeFunctionName;
|
||||
this.reversedArguments = reversedArguments;
|
||||
this.concatPattern = concatPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -104,21 +113,60 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
return new SelfRenderingSqmFunction<>(
|
||||
return new FormatSqmFunction<>(
|
||||
this,
|
||||
this,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
concatPattern,
|
||||
queryEngine
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentListSignature() {
|
||||
return "(TEMPORAL datetime as STRING pattern)";
|
||||
}
|
||||
|
||||
protected static class FormatSqmFunction<T> extends SelfRenderingSqmFunction<T> {
|
||||
|
||||
private final boolean supportsPatternLiterals;
|
||||
private final TypeConfiguration typeConfiguration;
|
||||
|
||||
public FormatSqmFunction(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderingSupport renderingSupport,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
boolean supportsPatternLiterals,
|
||||
QueryEngine queryEngine) {
|
||||
super(
|
||||
descriptor,
|
||||
renderingSupport,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
queryEngine.getCriteriaBuilder(),
|
||||
"format"
|
||||
) {
|
||||
);
|
||||
this.supportsPatternLiterals = supportsPatternLiterals;
|
||||
this.typeConfiguration = queryEngine.getTypeConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
|
||||
final ReturnableType<?> resultType = resolveResultType(
|
||||
walker.getCreationContext().getMappingMetamodel().getTypeConfiguration()
|
||||
);
|
||||
final MappingModelExpressible<?> mappingModelExpressible = resultType == null ? null : getMappingModelExpressible(
|
||||
walker,
|
||||
resultType
|
||||
);
|
||||
|
||||
final List<SqlAstNode> arguments = resolveSqlAstArguments( getArguments(), walker );
|
||||
final SqlAstNode expression = arguments.get( 0 );
|
||||
|
@ -134,9 +182,12 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
final BasicType<Integer> integerType = typeConfiguration.getBasicTypeRegistry()
|
||||
.resolve( StandardBasicTypes.INTEGER );
|
||||
arguments.set( 0, getOffsetAdjusted( sqlTuple, timestampaddFunction, integerType ) );
|
||||
if ( getArgumentsValidator() != null ) {
|
||||
getArgumentsValidator().validateSqlTypes( arguments, getFunctionName() );
|
||||
}
|
||||
final Format format = (Format) arguments.get( 1 );
|
||||
// If the format contains a time zone or offset, we must replace that with the offset column
|
||||
if ( format.getFormat().contains( "x" ) ) {
|
||||
if ( format.getFormat().contains( "x" ) || !supportsPatternLiterals ) {
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor concatFunction = getFunction(
|
||||
walker,
|
||||
"concat"
|
||||
|
@ -156,8 +207,14 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
.getDialect();
|
||||
Expression formatExpression = null;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final String delimiter;
|
||||
if ( supportsPatternLiterals ) {
|
||||
dialect.appendDatetimeFormat( sb::append, "'a'" );
|
||||
final String delimiter = sb.substring( 0, sb.indexOf( "a" ) ).replace( "''", "'" );
|
||||
delimiter = sb.substring( 0, sb.indexOf( "a" ) ).replace( "''", "'" );
|
||||
}
|
||||
else {
|
||||
delimiter = "";
|
||||
}
|
||||
final String[] chunks = StringHelper.splitFull( "'", format.getFormat() );
|
||||
final Expression offsetExpression = sqlTuple.getExpressions().get( 1 );
|
||||
// Splitting by `'` will put actual format pattern parts to even indices and literal pattern parts
|
||||
|
@ -186,12 +243,31 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
sb.setLength( 0 );
|
||||
dialect.appendDatetimeFormat( sb::append, smallParts[l] );
|
||||
final String formatPart = sb.toString();
|
||||
if ( supportsPatternLiterals ) {
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
formatExpression,
|
||||
new QueryLiteral<>( formatPart, stringType )
|
||||
);
|
||||
}
|
||||
else {
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
formatExpression,
|
||||
new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
getRenderingSupport(),
|
||||
List.of(
|
||||
arguments.get( 0 ),
|
||||
new QueryLiteral<>( formatPart, stringType )
|
||||
),
|
||||
resultType,
|
||||
mappingModelExpressible
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( l + 1 < smallParts.length ) {
|
||||
// This is for `x` patterns, which require `+01`
|
||||
// so we concat `substring(offset, 1, 4)`
|
||||
|
@ -220,7 +296,8 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
formatExpression = concatAsLiteral(
|
||||
concatFunction,
|
||||
stringType,
|
||||
delimiter, formatExpression,
|
||||
delimiter,
|
||||
formatExpression,
|
||||
createMediumOffset(
|
||||
concatFunction,
|
||||
substringFunction,
|
||||
|
@ -237,7 +314,8 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
formatExpression = concatAsLiteral(
|
||||
concatFunction,
|
||||
stringType,
|
||||
delimiter, formatExpression,
|
||||
delimiter,
|
||||
formatExpression,
|
||||
createFullOffset(
|
||||
concatFunction,
|
||||
floorFunction,
|
||||
|
@ -252,9 +330,15 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
|
||||
if ( i + 1 < chunks.length ) {
|
||||
// Handle the pattern literal content
|
||||
final String formatLiteralPart;
|
||||
if ( supportsPatternLiterals ) {
|
||||
sb.setLength( 0 );
|
||||
dialect.appendDatetimeFormat( sb::append, "'" + chunks[i + 1] + "'" );
|
||||
final String formatLiteralPart = sb.toString().replace( "''", "'" );
|
||||
formatLiteralPart = sb.toString().replace( "''", "'" );
|
||||
}
|
||||
else {
|
||||
formatLiteralPart = chunks[i + 1];
|
||||
}
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
|
@ -267,18 +351,64 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
}
|
||||
}
|
||||
|
||||
if ( supportsPatternLiterals ) {
|
||||
arguments.set( 1, formatExpression );
|
||||
}
|
||||
else {
|
||||
return formatExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( getArgumentsValidator() != null ) {
|
||||
getArgumentsValidator().validateSqlTypes( arguments, getFunctionName() );
|
||||
}
|
||||
|
||||
if ( !supportsPatternLiterals ) {
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor concatFunction = getFunction(
|
||||
walker,
|
||||
"concat"
|
||||
);
|
||||
final BasicType<String> stringType = typeConfiguration.getBasicTypeRegistry()
|
||||
.resolve( StandardBasicTypes.STRING );
|
||||
Expression formatExpression = null;
|
||||
final Format format = (Format) arguments.get( 1 );
|
||||
final String[] chunks = StringHelper.splitFull( "'", format.getFormat() );
|
||||
// Splitting by `'` will put actual format pattern parts to even indices and literal pattern parts
|
||||
// to uneven indices. We need to apply the format parts and then concatenate because the pattern
|
||||
// doesn't support literals
|
||||
for ( int i = 0; i < chunks.length; i += 2 ) {
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
formatExpression,
|
||||
new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
getRenderingSupport(),
|
||||
List.of( arguments.get( 0 ), new Format( chunks[i] ) ),
|
||||
resultType,
|
||||
mappingModelExpressible
|
||||
)
|
||||
);
|
||||
if ( i + 1 < chunks.length ) {
|
||||
// Handle the pattern literal content
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
formatExpression,
|
||||
new QueryLiteral<>( chunks[i + 1], stringType )
|
||||
);
|
||||
}
|
||||
}
|
||||
return formatExpression;
|
||||
}
|
||||
}
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
getRenderingSupport(),
|
||||
arguments,
|
||||
resultType,
|
||||
resultType == null ? null : getMappingModelExpressible( walker, resultType )
|
||||
mappingModelExpressible
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -752,12 +882,5 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
|
|||
integerType
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentListSignature() {
|
||||
return "(TEMPORAL datetime as STRING pattern)";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -458,7 +458,7 @@ public final class StandardBasicTypes {
|
|||
public static final BasicTypeReference<Instant> INSTANT = new BasicTypeReference<>(
|
||||
"instant",
|
||||
Instant.class,
|
||||
SqlTypes.TIMESTAMP
|
||||
SqlTypes.TIMESTAMP_UTC
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue