Prevent overflow when persisting Durations

Durations should be converted to BigDecimals for storage in NUMERIC columns.

(I missed this change when I merged my work from the previous branch.)
This commit is contained in:
gavinking 2020-01-31 08:48:35 +01:00
parent 5401f4fcfd
commit ae291bf04f
1 changed files with 42 additions and 2 deletions

View File

@ -6,13 +6,28 @@
*/
package org.hibernate.type.descriptor.java;
import java.math.BigDecimal;
import java.time.Duration;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@link Duration}, which is represented internally
* as ({@code long seconds}, {@code int nanoseconds}), approximately
* 28 decimal digits of precision. This quantity must be stored in
* the database as a single integer with units of nanoseconds, since
* the ANSI SQL {@code interval} type is not well-supported.
*
* In practice, the 19 decimal digits of a SQL {@code bigint} are
* capable of representing six centuries in nanoseconds and are
* sufficient for many applications. However, by default, we map
* Java {@link Duration} to SQL {@code numeric(21)} here, which
* can comfortably represent 60 millenia of nanos.
*
* @author Steve Ebersole
* @author Gavin King
*/
public class DurationJavaDescriptor extends AbstractTypeDescriptor<Duration> {
/**
@ -30,7 +45,10 @@ public class DurationJavaDescriptor extends AbstractTypeDescriptor<Duration> {
if ( value == null ) {
return null;
}
return String.valueOf( value.toNanos() );
String seconds = String.valueOf( value.getSeconds() );
String nanos = String.valueOf( value.getNano() );
String zeros = StringHelper.repeat( '0', 9-nanos.length() );
return seconds + zeros + nanos;
}
@Override
@ -38,7 +56,11 @@ public class DurationJavaDescriptor extends AbstractTypeDescriptor<Duration> {
if ( string == null ) {
return null;
}
return Duration.ofNanos( Long.parseLong( string ) );
int cutoff = string.length() - 9;
return Duration.ofSeconds(
Long.parseLong( string.substring( 0, cutoff ) ),
Long.parseLong( string.substring( cutoff ) )
);
}
@Override
@ -52,6 +74,11 @@ public class DurationJavaDescriptor extends AbstractTypeDescriptor<Duration> {
return (X) duration;
}
if ( BigDecimal.class.isAssignableFrom( type ) ) {
return (X) new BigDecimal( duration.getSeconds() ).movePointRight(9)
.add( new BigDecimal( duration.getNano() ) );
}
if ( String.class.isAssignableFrom( type ) ) {
return (X) duration.toString();
}
@ -73,6 +100,19 @@ public class DurationJavaDescriptor extends AbstractTypeDescriptor<Duration> {
return (Duration) value;
}
if ( BigDecimal.class.isInstance( value ) ) {
BigDecimal[] secondsAndNanos =
((BigDecimal) value).divideAndRemainder( BigDecimal.ONE.movePointRight(9) );
return Duration.ofSeconds(
secondsAndNanos[0].longValueExact(),
// use intValue() not intValueExact() here, because
// the database will sometimes produce garbage digits
// in a floating point multiplication, and we would
// get an unwanted ArithmeticException
secondsAndNanos[1].intValue()
);
}
if ( Long.class.isInstance( value ) ) {
return Duration.ofNanos( (Long) value );
}