Switch back to numeric(21) with nanosecond resolution as fallback for mapping Duration to retain backwards compatibility
This commit is contained in:
parent
6801ff0f26
commit
f2aa533dfc
|
@ -383,8 +383,8 @@ This setting applies to Oracle Dialect only, and it specifies whether `byte[]` o
|
||||||
`*hibernate.type.preferred_boolean_jdbc_type_code*` (e.g. `-7` for `java.sql.Types.BIT`)::
|
`*hibernate.type.preferred_boolean_jdbc_type_code*` (e.g. `-7` for `java.sql.Types.BIT`)::
|
||||||
Global setting identifying the preferred JDBC type code for storing boolean values. The fallback is to ask the Dialect.
|
Global setting identifying the preferred JDBC type code for storing boolean values. The fallback is to ask the Dialect.
|
||||||
|
|
||||||
`*hibernate.type.preferred_duration_jdbc_type_code*` (e.g. `2` for `java.sql.Types.NUMERIC` or `3` for `java.sql.Types.DECIMAL`)::
|
`*hibernate.type.preferred_duration_jdbc_type_code*` (e.g. `2` for `java.sql.Types.NUMERIC` or `3100` for `org.hibernate.types.SqlTypes.INTERVAL_SECOND` (default value))::
|
||||||
Global setting identifying the preferred JDBC type code for storing duration values. The fallback is `3100` for `org.hibernate.types.SqlTypes.INTERVAL_SECOND`.
|
Global setting identifying the preferred JDBC type code for storing duration values.
|
||||||
|
|
||||||
==== Bean Validation options
|
==== Bean Validation options
|
||||||
`*jakarta.persistence.validation.factory*` (e.g. `jakarta.validation.ValidationFactory` implementation)::
|
`*jakarta.persistence.validation.factory*` (e.g. `jakarta.validation.ValidationFactory` implementation)::
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* 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.userguide.mapping.basic;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
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.type.descriptor.jdbc.AdjustableJdbcType;
|
||||||
|
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||||
|
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = DurationMappingLegacyTests.EntityWithDuration.class)
|
||||||
|
@SessionFactory
|
||||||
|
// 2 stands for the type code Types.NUMERIC
|
||||||
|
@ServiceRegistry(settings = @Setting(name = AvailableSettings.PREFERRED_DURATION_JDBC_TYPE_CODE, value = "2"))
|
||||||
|
public class DurationMappingLegacyTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyMappings(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
|
||||||
|
.getRuntimeMetamodels()
|
||||||
|
.getMappingMetamodel();
|
||||||
|
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor(EntityWithDuration.class);
|
||||||
|
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
|
||||||
|
|
||||||
|
final BasicAttributeMapping duration = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("duration");
|
||||||
|
final JdbcMapping jdbcMapping = duration.getJdbcMapping();
|
||||||
|
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Duration.class));
|
||||||
|
final JdbcType intervalType = jdbcTypeRegistry.getDescriptor(SqlTypes.NUMERIC);
|
||||||
|
final JdbcType realType;
|
||||||
|
if (intervalType instanceof AdjustableJdbcType) {
|
||||||
|
realType = ((AdjustableJdbcType) intervalType).resolveIndicatedType(
|
||||||
|
() -> mappingMetamodel.getTypeConfiguration(),
|
||||||
|
jdbcMapping.getJavaTypeDescriptor()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
realType = intervalType;
|
||||||
|
}
|
||||||
|
assertThat( jdbcMapping.getJdbcType(), is( realType));
|
||||||
|
|
||||||
|
scope.inTransaction(
|
||||||
|
(session) -> {
|
||||||
|
session.persist(new EntityWithDuration(1, Duration.ofHours(3)));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
scope.inTransaction(
|
||||||
|
(session) -> session.find(EntityWithDuration.class, 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "EntityWithDuration")
|
||||||
|
@Table(name = "EntityWithDuration")
|
||||||
|
public static class EntityWithDuration {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
//tag::basic-duration-example[]
|
||||||
|
private Duration duration;
|
||||||
|
//end::basic-duration-example[]
|
||||||
|
|
||||||
|
public EntityWithDuration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityWithDuration(Integer id, Duration duration) {
|
||||||
|
this.id = id;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||||
import org.hibernate.engine.config.spi.StandardConverters;
|
import org.hibernate.engine.config.spi.StandardConverters;
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
|
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||||
import org.hibernate.type.BasicType;
|
import org.hibernate.type.BasicType;
|
||||||
import org.hibernate.type.BasicTypeRegistry;
|
import org.hibernate.type.BasicTypeRegistry;
|
||||||
import org.hibernate.type.SqlTypes;
|
import org.hibernate.type.SqlTypes;
|
||||||
|
@ -379,7 +380,13 @@ public class MetadataBuildingProcess {
|
||||||
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY );
|
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY );
|
||||||
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
|
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
|
||||||
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
|
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
|
||||||
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC );
|
final int preferredSqlTypeCodeForDuration = ConfigurationHelper.getPreferredSqlTypeCodeForDuration( bootstrapContext.getServiceRegistry() );
|
||||||
|
if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) {
|
||||||
|
jdbcTypeRegistry.addDescriptor( SqlTypes.INTERVAL_SECOND, jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForDuration ) );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC );
|
||||||
|
}
|
||||||
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOMETRY, SqlTypes.VARBINARY );
|
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOMETRY, SqlTypes.VARBINARY );
|
||||||
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.POINT, SqlTypes.VARBINARY );
|
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.POINT, SqlTypes.VARBINARY );
|
||||||
|
|
||||||
|
|
|
@ -1099,6 +1099,11 @@ public abstract class AbstractHANADialect extends Dialect {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFractionalSecondPrecisionInNanos() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
|
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
|
|
|
@ -271,13 +271,32 @@ public class SybaseASEDialect extends SybaseDialect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFractionalSecondPrecisionInNanos() {
|
||||||
|
// If the database does not support bigdatetime and bigtime types,
|
||||||
|
// we try to operate on milliseconds instead
|
||||||
|
if ( getVersion().isBefore( 15, 5 ) ) {
|
||||||
|
return 1_000_000;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 1_000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
|
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
|
||||||
//TODO!!
|
//TODO!!
|
||||||
switch ( unit ) {
|
switch ( unit ) {
|
||||||
case NANOSECOND:
|
case NANOSECOND:
|
||||||
case NATIVE:
|
case NATIVE:
|
||||||
return "(datediff(mcs,?2,?3)*1000)";
|
// If the database does not support bigdatetime and bigtime types,
|
||||||
|
// we try to operate on milliseconds instead
|
||||||
|
if ( getVersion().isBefore( 15, 5 ) ) {
|
||||||
|
return "cast(datediff(ms,?2,?3) as numeric(21))";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "cast(datediff(mcs,cast(?2 as bigdatetime),cast(?3 as bigdatetime)) as numeric(21))";
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return "datediff(?1,?2,?3)";
|
return "datediff(?1,?2,?3)";
|
||||||
}
|
}
|
||||||
|
|
|
@ -387,8 +387,8 @@ import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues;
|
||||||
import static org.hibernate.query.sqm.BinaryArithmeticOperator.ADD;
|
import static org.hibernate.query.sqm.BinaryArithmeticOperator.ADD;
|
||||||
import static org.hibernate.query.sqm.BinaryArithmeticOperator.MULTIPLY;
|
import static org.hibernate.query.sqm.BinaryArithmeticOperator.MULTIPLY;
|
||||||
import static org.hibernate.query.sqm.BinaryArithmeticOperator.SUBTRACT;
|
import static org.hibernate.query.sqm.BinaryArithmeticOperator.SUBTRACT;
|
||||||
import static org.hibernate.query.sqm.TemporalUnit.DAY;
|
|
||||||
import static org.hibernate.query.sqm.TemporalUnit.EPOCH;
|
import static org.hibernate.query.sqm.TemporalUnit.EPOCH;
|
||||||
|
import static org.hibernate.query.sqm.TemporalUnit.NATIVE;
|
||||||
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
||||||
import static org.hibernate.query.sqm.UnaryArithmeticOperator.UNARY_MINUS;
|
import static org.hibernate.query.sqm.UnaryArithmeticOperator.UNARY_MINUS;
|
||||||
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
|
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
|
||||||
|
@ -5438,7 +5438,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
|
|
||||||
// The result of timestamp subtraction is always a `Duration`, unless a unit is applied
|
// The result of timestamp subtraction is always a `Duration`, unless a unit is applied
|
||||||
// So use SECOND granularity with fractions as that is what the `DurationJavaType` expects
|
// So use SECOND granularity with fractions as that is what the `DurationJavaType` expects
|
||||||
final TemporalUnit baseUnit = SECOND; // todo: alternatively repurpose NATIVE to mean "INTERVAL SECOND"
|
final TemporalUnit baseUnit = NATIVE;
|
||||||
|
final BasicType<Long> diffResultType = basicType( Long.class );
|
||||||
|
|
||||||
if ( adjustedTimestamp != null ) {
|
if ( adjustedTimestamp != null ) {
|
||||||
if ( appliedByUnit != null ) {
|
if ( appliedByUnit != null ) {
|
||||||
|
@ -5451,7 +5452,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
// temporal type, so we must use it for both
|
// temporal type, so we must use it for both
|
||||||
// the diff, and then the subsequent add
|
// the diff, and then the subsequent add
|
||||||
|
|
||||||
DurationUnit unit = new DurationUnit( baseUnit, basicType( Integer.class ) );
|
DurationUnit unit = new DurationUnit( baseUnit, diffResultType );
|
||||||
Expression magnitude = applyScale( timestampdiff().expression( null, unit, right, left ) );
|
Expression magnitude = applyScale( timestampdiff().expression( null, unit, right, left ) );
|
||||||
return timestampadd().expression(
|
return timestampadd().expression(
|
||||||
(ReturnableType<?>) adjustedTimestampType, //TODO should be adjustedTimestamp.getType()
|
(ReturnableType<?>) adjustedTimestampType, //TODO should be adjustedTimestamp.getType()
|
||||||
|
@ -5467,7 +5468,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// a plain "bare" Duration
|
// a plain "bare" Duration
|
||||||
DurationUnit unit = new DurationUnit( baseUnit, basicType( Integer.class ) );
|
DurationUnit unit = new DurationUnit( baseUnit, diffResultType );
|
||||||
BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType();
|
BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType();
|
||||||
Expression scaledMagnitude = applyScale( timestampdiff().expression(
|
Expression scaledMagnitude = applyScale( timestampdiff().expression(
|
||||||
(ReturnableType<?>) expression.getNodeType(),
|
(ReturnableType<?>) expression.getNodeType(),
|
||||||
|
|
|
@ -177,7 +177,7 @@ import org.hibernate.type.StandardBasicTypes;
|
||||||
import org.hibernate.type.descriptor.WrapperOptions;
|
import org.hibernate.type.descriptor.WrapperOptions;
|
||||||
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
|
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
|
||||||
|
|
||||||
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
|
||||||
import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
|
import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
|
||||||
import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph;
|
import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph;
|
||||||
|
|
||||||
|
@ -4439,8 +4439,9 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
||||||
@Override
|
@Override
|
||||||
public void visitDuration(Duration duration) {
|
public void visitDuration(Duration duration) {
|
||||||
duration.getMagnitude().accept( this );
|
duration.getMagnitude().accept( this );
|
||||||
|
// Convert to NANOSECOND because DurationJavaType requires values in that unit
|
||||||
appendSql(
|
appendSql(
|
||||||
duration.getUnit().conversionFactor( SECOND, getDialect() )
|
duration.getUnit().conversionFactor( NANOSECOND, getDialect() )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -347,7 +347,8 @@ public final class StandardBasicTypes {
|
||||||
// Date / time data
|
// Date / time data
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The standard Hibernate type for mapping {@link Duration} to JDBC {@link org.hibernate.type.SqlTypes#NUMERIC NUMERIC}.
|
* The standard Hibernate type for mapping {@link Duration} to JDBC {@link org.hibernate.type.SqlTypes#INTERVAL_SECOND INTERVAL_SECOND}
|
||||||
|
* or {@link org.hibernate.type.SqlTypes#NUMERIC NUMERIC} as a fallback.
|
||||||
*/
|
*/
|
||||||
public static final BasicTypeReference<Duration> DURATION = new BasicTypeReference<>(
|
public static final BasicTypeReference<Duration> DURATION = new BasicTypeReference<>(
|
||||||
"Duration",
|
"Duration",
|
||||||
|
|
|
@ -7,10 +7,7 @@
|
||||||
package org.hibernate.type.descriptor.java;
|
package org.hibernate.type.descriptor.java;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.DecimalFormatSymbols;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
@ -23,13 +20,13 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
|
||||||
* Descriptor for {@link Duration}, which is represented internally
|
* Descriptor for {@link Duration}, which is represented internally
|
||||||
* as ({@code long seconds}, {@code int nanoseconds}), approximately
|
* as ({@code long seconds}, {@code int nanoseconds}), approximately
|
||||||
* 28 decimal digits of precision. This quantity must be stored in
|
* 28 decimal digits of precision. This quantity must be stored in
|
||||||
* the database as a single integer with units of nanoseconds, since
|
* the database as a single integer with units of nanoseconds, unless
|
||||||
* the ANSI SQL {@code interval} type is not well-supported.
|
* the ANSI SQL {@code interval} type is supported.
|
||||||
*
|
*
|
||||||
* In practice, the 19 decimal digits of a SQL {@code bigint} are
|
* In practice, the 19 decimal digits of a SQL {@code bigint} are
|
||||||
* capable of representing six centuries in nanoseconds and are
|
* capable of representing six centuries in nanoseconds and are
|
||||||
* sufficient for many applications. However, by default, we map
|
* sufficient for many applications. However, by default, we map
|
||||||
* Java {@link Duration} to SQL {@code numeric(21,6)} here, which
|
* Java {@link Duration} to SQL {@code numeric(21)} here, which
|
||||||
* can comfortably represent 60 millenia of nanos.
|
* can comfortably represent 60 millenia of nanos.
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
@ -40,13 +37,7 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
||||||
* Singleton access
|
* Singleton access
|
||||||
*/
|
*/
|
||||||
public static final DurationJavaType INSTANCE = new DurationJavaType();
|
public static final DurationJavaType INSTANCE = new DurationJavaType();
|
||||||
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance( Locale.ENGLISH );
|
private static final BigDecimal BILLION = BigDecimal.valueOf( 1_000_000_000 );
|
||||||
private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<>() {
|
|
||||||
@Override
|
|
||||||
protected DecimalFormat initialValue() {
|
|
||||||
return new DecimalFormat( "0.000000000", DECIMAL_FORMAT_SYMBOLS );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public DurationJavaType() {
|
public DurationJavaType() {
|
||||||
super( Duration.class, ImmutableMutabilityPlan.instance() );
|
super( Duration.class, ImmutableMutabilityPlan.instance() );
|
||||||
|
@ -54,7 +45,9 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
|
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
|
||||||
return context.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( context.getPreferredSqlTypeCodeForDuration() );
|
return context.getTypeConfiguration()
|
||||||
|
.getJdbcTypeRegistry()
|
||||||
|
.getDescriptor( context.getPreferredSqlTypeCodeForDuration() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -94,8 +87,7 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
||||||
if ( BigDecimal.class.isAssignableFrom( type ) ) {
|
if ( BigDecimal.class.isAssignableFrom( type ) ) {
|
||||||
return (X) new BigDecimal( duration.getSeconds() )
|
return (X) new BigDecimal( duration.getSeconds() )
|
||||||
.movePointRight( 9 )
|
.movePointRight( 9 )
|
||||||
.add( new BigDecimal( duration.getNano() ) )
|
.add( new BigDecimal( duration.getNano() ) );
|
||||||
.movePointLeft( 9 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( String.class.isAssignableFrom( type ) ) {
|
if ( String.class.isAssignableFrom( type ) ) {
|
||||||
|
@ -120,11 +112,17 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value instanceof BigDecimal) {
|
if (value instanceof BigDecimal) {
|
||||||
return fromDecimal( value );
|
final BigDecimal decimal = (BigDecimal) value;
|
||||||
|
final BigDecimal[] bigDecimals = decimal.divideAndRemainder( BILLION );
|
||||||
|
return Duration.ofSeconds(
|
||||||
|
bigDecimals[0].longValueExact(),
|
||||||
|
bigDecimals[1].longValueExact()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value instanceof Double) {
|
if (value instanceof Double) {
|
||||||
return fromDecimal( value );
|
// PostgreSQL returns a Double for datediff(epoch)
|
||||||
|
return Duration.ofNanos( ( (Double) value ).longValue() );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value instanceof Long) {
|
if (value instanceof Long) {
|
||||||
|
@ -138,18 +136,6 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
||||||
throw unknownWrap( value.getClass() );
|
throw unknownWrap( value.getClass() );
|
||||||
}
|
}
|
||||||
|
|
||||||
private Duration fromDecimal(Object number) {
|
|
||||||
final String formatted = DECIMAL_FORMAT.get().format( number );
|
|
||||||
final int dotIndex = formatted.indexOf( '.' );
|
|
||||||
if (dotIndex == -1) {
|
|
||||||
return Duration.ofSeconds( Long.parseLong( formatted ) );
|
|
||||||
}
|
|
||||||
return Duration.ofSeconds(
|
|
||||||
Long.parseLong( formatted.substring( 0, dotIndex ) ),
|
|
||||||
Long.parseLong( formatted.substring( dotIndex + 1 ) )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
|
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
|
||||||
if ( jdbcType.getDefaultSqlTypeCode() == SqlTypes.INTERVAL_SECOND ) {
|
if ( jdbcType.getDefaultSqlTypeCode() == SqlTypes.INTERVAL_SECOND ) {
|
||||||
|
@ -168,6 +154,13 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) {
|
public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) {
|
||||||
return 9;
|
if ( jdbcType.getDefaultSqlTypeCode() == SqlTypes.INTERVAL_SECOND ) {
|
||||||
|
// The default scale necessary is 9 i.e. nanosecond resolution
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For non-interval types, we use the type numeric(21)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,10 +256,17 @@ plural attribute classification
|
||||||
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#collection-type-reg-ann
|
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#collection-type-reg-ann
|
||||||
for details of using `@CollectionTypeRegistration`
|
for details of using `@CollectionTypeRegistration`
|
||||||
|
|
||||||
=== Misc
|
=== Duration mapping changes
|
||||||
|
|
||||||
* The default type for `Duration` was changed to `NUMERIC` which could lead to schema validation errors
|
Duration now maps to the type code `SqlType.INTERVAL_SECOND` by default, which maps to the SQL type `interval second`
|
||||||
|
if possible, and falls back to `numeric(21)`.
|
||||||
|
In either case, schema validation errors could occur as 5.x used the type code `Types.BIGINT`.
|
||||||
|
|
||||||
|
Migration to `numeric(21)` should be easy. The migration to `interval second` might require a migration expression like
|
||||||
|
`cast(cast(old as numeric(21,9) / 1000000000) as interval second(9))`.
|
||||||
|
|
||||||
|
To retain backwards compatibility, configure the setting `hibernate.type.preferred_duration_jdbc_type_code` to `2`
|
||||||
|
which stands for `Types.NUMERIC`.
|
||||||
|
|
||||||
[[query]]
|
[[query]]
|
||||||
== Query
|
== Query
|
||||||
|
|
Loading…
Reference in New Issue