Support for type coercion for values passed as ids and as query parameter bindings
- widening coercions - valid (no over/under flow) narrowing coercions
This commit is contained in:
parent
27662f91a9
commit
d95806b516
|
@ -35,6 +35,8 @@ import org.hibernate.resource.jdbc.spi.JdbcSessionOwner;
|
|||
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
|
||||
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder.Options;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Defines the internal contract shared between {@link org.hibernate.Session} and
|
||||
|
@ -66,7 +68,7 @@ import org.hibernate.type.descriptor.WrapperOptions;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SharedSessionContractImplementor
|
||||
extends SharedSessionContract, JdbcSessionOwner, Options, LobCreationContext, WrapperOptions, QueryProducerImplementor {
|
||||
extends SharedSessionContract, JdbcSessionOwner, Options, LobCreationContext, WrapperOptions, QueryProducerImplementor, JavaTypeDescriptor.CoercionContext {
|
||||
|
||||
// todo : this is the shared contract between Session and StatelessSession, but it defines methods that StatelessSession does not implement
|
||||
// (it just throws UnsupportedOperationException). To me it seems like it is better to properly isolate those methods
|
||||
|
@ -82,6 +84,11 @@ public interface SharedSessionContractImplementor
|
|||
*/
|
||||
SessionFactoryImplementor getFactory();
|
||||
|
||||
@Override
|
||||
default TypeConfiguration getTypeConfiguration() {
|
||||
return getFactory().getTypeConfiguration();
|
||||
}
|
||||
|
||||
SessionEventListenerManager getEventListenerManager();
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,11 +27,13 @@ import org.hibernate.graph.spi.RootGraphImplementor;
|
|||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
|
||||
public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T>, JavaTypeDescriptor.CoercionContext {
|
||||
private final LoadAccessContext context;
|
||||
private final EntityPersister entityPersister;
|
||||
|
||||
|
@ -114,6 +116,8 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
|
|||
final EventSource eventSource = (EventSource) session;
|
||||
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
|
||||
|
||||
id = entityPersister.getIdentifierMapping().getJavaTypeDescriptor().coerce( id, this );
|
||||
|
||||
if ( this.lockOptions != null ) {
|
||||
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, eventSource, loadQueryInfluencers.getReadOnly() );
|
||||
context.fireLoad( event, LoadEventListener.LOAD );
|
||||
|
@ -154,6 +158,8 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
|
|||
final EventSource eventSource = (EventSource) session;
|
||||
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
|
||||
|
||||
id = entityPersister.getIdentifierMapping().getJavaTypeDescriptor().coerce( id, this );
|
||||
|
||||
if ( this.lockOptions != null ) {
|
||||
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, eventSource, loadQueryInfluencers.getReadOnly() );
|
||||
context.fireLoad( event, LoadEventListener.GET );
|
||||
|
@ -206,4 +212,9 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
|
|||
( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( result, null );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeConfiguration getTypeConfiguration() {
|
||||
return context.getSession().getSessionFactory().getTypeConfiguration();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public class MultiIdLoaderStandard<T> implements MultiIdEntityLoader<T> {
|
|||
final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>();
|
||||
|
||||
for ( int i = 0; i < ids.length; i++ ) {
|
||||
final Object id = ids[i];
|
||||
final Object id = entityDescriptor.getIdentifierMapping().getJavaTypeDescriptor().coerce( ids[i], session );
|
||||
final EntityKey entityKey = new EntityKey( id, entityDescriptor );
|
||||
|
||||
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
||||
|
@ -175,7 +175,7 @@ public class MultiIdLoaderStandard<T> implements MultiIdEntityLoader<T> {
|
|||
|
||||
// if we did not hit any of the continues above, then we need to batch
|
||||
// load the entity state.
|
||||
idsInBatch.add( ids[i] );
|
||||
idsInBatch.add( id );
|
||||
|
||||
if ( idsInBatch.size() >= maxBatchSize ) {
|
||||
// we've hit the allotted max-batch-size, perform an "intermediate load"
|
||||
|
@ -352,7 +352,7 @@ public class MultiIdLoaderStandard<T> implements MultiIdEntityLoader<T> {
|
|||
final List<Object> nonManagedIds = new ArrayList<>();
|
||||
|
||||
for ( int i = 0; i < ids.length; i++ ) {
|
||||
final Object id = ids[ i ];
|
||||
final Object id = entityDescriptor.getIdentifierMapping().getJavaTypeDescriptor().coerce( ids[ i ], session );
|
||||
final EntityKey entityKey = new EntityKey( id, entityDescriptor );
|
||||
|
||||
LoadEvent loadEvent = new LoadEvent(
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
@ -32,12 +33,14 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
|
|||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Single-attribute NaturalIdMapping implementation
|
||||
*/
|
||||
public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
||||
public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements JavaTypeDescriptor.CoercionContext {
|
||||
private final SingularAttributeMapping attribute;
|
||||
private final TypeConfiguration typeConfiguration;
|
||||
|
||||
public SimpleNaturalIdMapping(
|
||||
SingularAttributeMapping attribute,
|
||||
|
@ -48,6 +51,11 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
|||
attribute.getAttributeMetadataAccess().resolveAttributeMetadata( declaringType ).isUpdatable()
|
||||
);
|
||||
this.attribute = attribute;
|
||||
|
||||
typeConfiguration = creationProcess.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getTypeConfiguration();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,15 +120,21 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
|||
|
||||
if ( ! getJavaTypeDescriptor().getJavaTypeClass().isInstance( naturalIdValue ) ) {
|
||||
throw new IllegalArgumentException(
|
||||
"Incoming natural-id value [" + naturalIdValue + "] is not of expected type ["
|
||||
+ getJavaTypeDescriptor().getJavaType().getTypeName() + "]"
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Incoming natural-id value [%s (`%s`)] is not of expected type [`%s`] and could not be coerced",
|
||||
naturalIdValue,
|
||||
naturalIdValue.getClass().getName(),
|
||||
getJavaTypeDescriptor().getJavaType().getTypeName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateHashCode(Object value, SharedSessionContractImplementor session) {
|
||||
return 0;
|
||||
//noinspection unchecked
|
||||
return value == null ? 0 : ( (JavaTypeDescriptor<Object>) getJavaTypeDescriptor() ).extractHashCode( value );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -134,16 +148,16 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
|||
final Map valueMap = (Map) naturalIdToLoad;
|
||||
assert valueMap.size() == 1;
|
||||
assert valueMap.containsKey( getAttribute().getAttributeName() );
|
||||
return valueMap.get( getAttribute().getAttributeName() );
|
||||
return getJavaTypeDescriptor().coerce( valueMap.get( getAttribute().getAttributeName() ), this );
|
||||
}
|
||||
|
||||
if ( naturalIdToLoad instanceof Object[] ) {
|
||||
final Object[] values = (Object[]) naturalIdToLoad;
|
||||
assert values.length == 1;
|
||||
return values[0];
|
||||
return getJavaTypeDescriptor().coerce( values[0], this );
|
||||
}
|
||||
|
||||
return naturalIdToLoad;
|
||||
return getJavaTypeDescriptor().coerce( naturalIdToLoad, this );
|
||||
}
|
||||
|
||||
public SingularAttributeMapping getAttribute() {
|
||||
|
@ -247,4 +261,9 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
|||
public MultiNaturalIdLoader<?> makeMultiLoader(EntityMappingType entityDescriptor) {
|
||||
return new MultiNaturalIdLoaderStandard<>( entityDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeConfiguration getTypeConfiguration() {
|
||||
return typeConfiguration;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import org.hibernate.query.QueryParameter;
|
|||
import org.hibernate.query.spi.QueryParameterBinding;
|
||||
import org.hibernate.query.spi.QueryParameterBindingTypeResolver;
|
||||
import org.hibernate.query.spi.QueryParameterBindingValidator;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
|
@ -26,7 +28,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
||||
public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, JavaTypeDescriptor.CoercionContext {
|
||||
private final QueryParameter<T> queryParameter;
|
||||
private final QueryParameterBindingTypeResolver typeResolver;
|
||||
private final boolean isBindingValidationRequired;
|
||||
|
@ -103,6 +105,13 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( bindType != null ) {
|
||||
value = bindType.getExpressableJavaTypeDescriptor().coerce( value, this );
|
||||
}
|
||||
else if ( queryParameter.getHibernateType() != null ) {
|
||||
value = queryParameter.getHibernateType().getExpressableJavaTypeDescriptor().coerce( value, this );
|
||||
}
|
||||
|
||||
if ( isBindingValidationRequired ) {
|
||||
validate( value );
|
||||
}
|
||||
|
@ -148,15 +157,22 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( clarifiedType != null ) {
|
||||
this.bindType = clarifiedType;
|
||||
}
|
||||
|
||||
if ( bindType != null ) {
|
||||
value = bindType.getExpressableJavaTypeDescriptor().coerce( value, this );
|
||||
}
|
||||
else if ( queryParameter.getHibernateType() != null ) {
|
||||
value = queryParameter.getHibernateType().getExpressableJavaTypeDescriptor().coerce( value, this );
|
||||
}
|
||||
|
||||
if ( isBindingValidationRequired ) {
|
||||
validate( value, clarifiedType );
|
||||
}
|
||||
|
||||
bindValue( value );
|
||||
|
||||
if ( clarifiedType != null ) {
|
||||
this.bindType = clarifiedType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -165,16 +181,23 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( bindType == null ) {
|
||||
bindType = queryParameter.getHibernateType();
|
||||
}
|
||||
|
||||
if ( bindType != null ) {
|
||||
value = bindType.getExpressableJavaTypeDescriptor().coerce( value, this );
|
||||
}
|
||||
else if ( queryParameter.getHibernateType() != null ) {
|
||||
value = queryParameter.getHibernateType().getExpressableJavaTypeDescriptor().coerce( value, this );
|
||||
}
|
||||
|
||||
if ( isBindingValidationRequired ) {
|
||||
validate( value, temporalTypePrecision );
|
||||
}
|
||||
|
||||
bindValue( value );
|
||||
|
||||
if ( bindType == null ) {
|
||||
bindType = queryParameter.getHibernateType();
|
||||
}
|
||||
|
||||
if ( bindType != null ) {
|
||||
bindType = (AllowableParameterType) BindingTypeHelper.INSTANCE.resolveDateTemporalTypeVariant(
|
||||
bindType.getExpressableJavaTypeDescriptor().getJavaTypeClass(),
|
||||
|
@ -221,10 +244,10 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
|||
|
||||
@Override
|
||||
public void setBindValues(Collection<T> values, AllowableParameterType<T> clarifiedType) {
|
||||
setBindValues( values );
|
||||
if ( clarifiedType != null ) {
|
||||
this.bindType = clarifiedType;
|
||||
}
|
||||
setBindValues( values );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -237,7 +260,7 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
|||
this.bindType = BindingTypeHelper.INSTANCE.resolveTemporalPrecision(
|
||||
temporalTypePrecision,
|
||||
bindType,
|
||||
typeConfiguration
|
||||
getTypeConfiguration()
|
||||
);
|
||||
|
||||
this.explicitTemporalPrecision = temporalTypePrecision;
|
||||
|
@ -273,4 +296,9 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
|
|||
private void validate(T value, TemporalType clarifiedTemporalType) {
|
||||
QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, clarifiedTemporalType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeConfiguration getTypeConfiguration() {
|
||||
return typeResolver.getTypeConfiguration();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies;
|
|||
import org.hibernate.property.access.spi.Getter;
|
||||
import org.hibernate.property.access.spi.PropertyAccess;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.ParameterMetadata;
|
||||
import org.hibernate.query.QueryLogging;
|
||||
import org.hibernate.query.QueryParameter;
|
||||
import org.hibernate.query.ResultListTransformer;
|
||||
|
|
|
@ -54,7 +54,6 @@ public interface QueryParameterBinding<T> {
|
|||
|
||||
/**
|
||||
* Sets the parameter binding value using the explicit Type.
|
||||
*
|
||||
* @param value The bind value
|
||||
* @param clarifiedType The explicit Type to use
|
||||
*/
|
||||
|
@ -62,7 +61,6 @@ public interface QueryParameterBinding<T> {
|
|||
|
||||
/**
|
||||
* Sets the parameter binding value using the explicit TemporalType.
|
||||
*
|
||||
* @param value The bind value
|
||||
* @param temporalTypePrecision The temporal type to use
|
||||
*/
|
||||
|
@ -78,21 +76,19 @@ public interface QueryParameterBinding<T> {
|
|||
/**
|
||||
* Sets the parameter binding values. The inherent parameter type (if known) is assumed in regards to the
|
||||
* individual values.
|
||||
*
|
||||
* @param values The bind values
|
||||
*
|
||||
*/
|
||||
void setBindValues(Collection<T> values);
|
||||
|
||||
/**
|
||||
* Sets the parameter binding values using the explicit Type in regards to the individual values.
|
||||
*
|
||||
* @param values The bind values
|
||||
* @param clarifiedType The explicit Type to use
|
||||
*/
|
||||
void setBindValues(Collection<T> values, AllowableParameterType<T> clarifiedType);
|
||||
|
||||
/**Sets the parameter binding value using the explicit TemporalType in regards to the individual values.
|
||||
*
|
||||
*
|
||||
* @param values The bind values
|
||||
* @param temporalTypePrecision The temporal type to use
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.java;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
@ -78,13 +79,13 @@ public class BigDecimalTypeDescriptor extends AbstractClassTypeDescriptor<BigDec
|
|||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( BigDecimal.class.isInstance( value ) ) {
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return (BigDecimal) value;
|
||||
}
|
||||
if ( BigInteger.class.isInstance( value ) ) {
|
||||
if ( value instanceof BigInteger ) {
|
||||
return new BigDecimal( (BigInteger) value );
|
||||
}
|
||||
if ( Number.class.isInstance( value ) ) {
|
||||
if ( value instanceof Number ) {
|
||||
return BigDecimal.valueOf( ( (Number) value ).doubleValue() );
|
||||
}
|
||||
throw unknownWrap( value.getClass() );
|
||||
|
@ -99,4 +100,30 @@ public class BigDecimalTypeDescriptor extends AbstractClassTypeDescriptor<BigDec
|
|||
public int getDefaultSqlPrecision(Dialect dialect) {
|
||||
return dialect.getDefaultDecimalPrecision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> BigDecimal coerce(X value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Number ) {
|
||||
return BigDecimal.valueOf( ( (Number) value ).doubleValue() );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> BigDecimal.valueOf( Double.parseDouble( (String) value ) )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce value [%s (%s)] to BigDecimal",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.java;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
@ -82,13 +83,13 @@ public class BigIntegerTypeDescriptor extends AbstractClassTypeDescriptor<BigInt
|
|||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( BigInteger.class.isInstance( value ) ) {
|
||||
if ( value instanceof BigInteger ) {
|
||||
return (BigInteger) value;
|
||||
}
|
||||
if ( BigDecimal.class.isInstance( value ) ) {
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return ( (BigDecimal) value ).toBigIntegerExact();
|
||||
}
|
||||
if ( Number.class.isInstance( value ) ) {
|
||||
if ( value instanceof Number ) {
|
||||
return BigInteger.valueOf( ( (Number) value ).longValue() );
|
||||
}
|
||||
throw unknownWrap( value.getClass() );
|
||||
|
@ -108,4 +109,58 @@ public class BigIntegerTypeDescriptor extends AbstractClassTypeDescriptor<BigInt
|
|||
public int getDefaultSqlScale() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> BigInteger coerce(X value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return (BigInteger) value;
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return BigInteger.valueOf( ( (byte) value ) );
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return BigInteger.valueOf( ( (short) value ) );
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return BigInteger.valueOf( ( (int) value ) );
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return BigInteger.valueOf( ( (long) value ) );
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return CoercionHelper.toBigInteger( (Double) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return CoercionHelper.toBigInteger( (Float) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return CoercionHelper.toBigInteger( (BigDecimal) value );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> BigInteger.valueOf( Long.parseLong( (String) value ) )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce value [%s (%s)] to BigInteger",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.type.descriptor.java;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.spi.Primitive;
|
||||
|
@ -66,20 +70,20 @@ public class ByteTypeDescriptor extends AbstractClassTypeDescriptor<Byte> implem
|
|||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( Byte.class.isInstance( value ) ) {
|
||||
if ( value instanceof Byte ) {
|
||||
return (Byte) value;
|
||||
}
|
||||
if ( Number.class.isInstance( value ) ) {
|
||||
if ( value instanceof Number ) {
|
||||
return ( (Number) value ).byteValue();
|
||||
}
|
||||
if ( String.class.isInstance( value ) ) {
|
||||
if ( value instanceof String ) {
|
||||
return Byte.valueOf( ( (String) value ) );
|
||||
}
|
||||
throw unknownWrap( value.getClass() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getPrimitiveClass() {
|
||||
public Class<Byte> getPrimitiveClass() {
|
||||
return byte.class;
|
||||
}
|
||||
|
||||
|
@ -102,4 +106,58 @@ public class ByteTypeDescriptor extends AbstractClassTypeDescriptor<Byte> implem
|
|||
public int getDefaultSqlScale() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> Byte coerce(X value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return (byte) value;
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return CoercionHelper.toByte( (short) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return CoercionHelper.toByte( (Integer) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return CoercionHelper.toByte( (Long) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return CoercionHelper.toByte( (Double) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return CoercionHelper.toByte( (Float) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return CoercionHelper.toByte( (BigInteger) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return CoercionHelper.toByte( (BigDecimal) value );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> Byte.parseByte( (String) value )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce value `%s` [%s] as Byte",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.java;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CoercionException extends HibernateException {
|
||||
public CoercionException(String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public CoercionException(String message, Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* 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.java;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Helper for type coercions. Mainly used for narrowing coercions which
|
||||
* might lead to under/over-flow problems
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CoercionHelper {
|
||||
private CoercionHelper() {
|
||||
// disallow direct instantiation
|
||||
}
|
||||
|
||||
public static Byte toByte(Short value) {
|
||||
if ( value > Byte.MAX_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Short value `%s` to Byte : overflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value < Byte.MIN_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Short value `%s` to Byte : underflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return value.byteValue();
|
||||
}
|
||||
|
||||
public static Byte toByte(Integer value) {
|
||||
if ( value > Byte.MAX_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Integer value `%s` to Byte : overflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value < Byte.MIN_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Integer value `%s` to Byte : underflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return value.byteValue();
|
||||
}
|
||||
|
||||
public static Byte toByte(Long value) {
|
||||
if ( value > Byte.MAX_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Long value `%s` to Byte : overflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value < Byte.MIN_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Long value `%s` to Byte : underflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return value.byteValue();
|
||||
}
|
||||
|
||||
public static Byte toByte(Double value) {
|
||||
if ( ! isWholeNumber( value ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Double value `%s` to Byte : not a whole number",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value > Byte.MAX_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Double value `%s` to Byte : overflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value < Byte.MIN_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Double value `%s` to Byte : underflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return value.byteValue();
|
||||
}
|
||||
|
||||
public static Byte toByte(Float value) {
|
||||
if ( ! isWholeNumber( value ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Float value `%s` to Byte : not a whole number",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value > Byte.MAX_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Float value `%s` to Byte : overflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( value < Byte.MIN_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Float value `%s` to Byte : underflow",
|
||||
value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return value.byteValue();
|
||||
}
|
||||
|
||||
public static Byte toByte(BigInteger value) {
|
||||
return coerceWrappingError( value::byteValueExact );
|
||||
}
|
||||
|
||||
public static Byte toByte(BigDecimal value) {
|
||||
return coerceWrappingError( value::byteValueExact );
|
||||
}
|
||||
|
||||
public static Short toShort(Byte value) {
|
||||
return value.shortValue();
|
||||
}
|
||||
|
||||
public static Short toShort(Integer value) {
|
||||
if ( value > Short.MAX_VALUE ) {
|
||||
throw new CoercionException( "Cannot coerce Integer value `" + value + "` as Short : overflow" );
|
||||
}
|
||||
|
||||
if ( value < Short.MIN_VALUE ) {
|
||||
throw new CoercionException( "Cannot coerce Integer value `" + value + "` as Short : underflow" );
|
||||
}
|
||||
|
||||
return value.shortValue();
|
||||
}
|
||||
|
||||
public static Short toShort(Long value) {
|
||||
if ( value > Short.MAX_VALUE ) {
|
||||
throw new CoercionException( "Cannot coerce Long value `" + value + "` as Short : overflow" );
|
||||
}
|
||||
|
||||
if ( value < Short.MIN_VALUE ) {
|
||||
throw new CoercionException( "Cannot coerce Long value `" + value + "` as Short : underflow" );
|
||||
}
|
||||
|
||||
return value.shortValue();
|
||||
}
|
||||
|
||||
public static Short toShort(Double doubleValue) {
|
||||
if ( ! isWholeNumber( doubleValue ) ) {
|
||||
throw new CoercionException( "Cannot coerce Double value `" + doubleValue + "` as Short : not a whole number" );
|
||||
}
|
||||
return toShort( doubleValue.longValue() );
|
||||
}
|
||||
|
||||
public static Short toShort(Float floatValue) {
|
||||
if ( ! isWholeNumber( floatValue ) ) {
|
||||
throw new CoercionException( "Cannot coerce Float value `" + floatValue + "` as Short : not a whole number" );
|
||||
}
|
||||
return toShort( floatValue.longValue() );
|
||||
}
|
||||
|
||||
public static Short toShort(BigInteger value) {
|
||||
return coerceWrappingError( value::shortValueExact );
|
||||
}
|
||||
|
||||
public static Short toShort(BigDecimal value) {
|
||||
return coerceWrappingError( value::shortValueExact );
|
||||
}
|
||||
|
||||
public static Integer toInteger(Byte value) {
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
public static Integer toInteger(Short value) {
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
public static Integer toInteger(Long value) {
|
||||
return coerceWrappingError( () -> Math.toIntExact( value ) );
|
||||
}
|
||||
|
||||
public static Integer toInteger(Double doubleValue) {
|
||||
if ( ! isWholeNumber( doubleValue ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce Double value `%s` to Integer: not a whole number",
|
||||
doubleValue
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return toInteger( doubleValue.longValue() );
|
||||
}
|
||||
|
||||
public static Integer toInteger(Float floatValue) {
|
||||
if ( ! isWholeNumber( floatValue ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce Float value `%s` to Integer: not a whole number",
|
||||
floatValue
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return toInteger( floatValue.longValue() );
|
||||
}
|
||||
|
||||
public static Integer toInteger(BigInteger value) {
|
||||
return coerceWrappingError( value::intValueExact );
|
||||
}
|
||||
|
||||
public static Integer toInteger(BigDecimal value) {
|
||||
return coerceWrappingError( value::intValueExact );
|
||||
}
|
||||
|
||||
public static Long toLong(Byte value) {
|
||||
return value.longValue();
|
||||
}
|
||||
|
||||
public static Long toLong(Short value) {
|
||||
return value.longValue();
|
||||
}
|
||||
|
||||
public static Long toLong(Integer value) {
|
||||
return value.longValue();
|
||||
}
|
||||
|
||||
public static Long toLong(Double doubleValue) {
|
||||
if ( ! isWholeNumber( doubleValue ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce Double value `%s` as Integer: not a whole number",
|
||||
doubleValue
|
||||
)
|
||||
);
|
||||
}
|
||||
return doubleValue.longValue();
|
||||
}
|
||||
|
||||
public static Long toLong(Float floatValue) {
|
||||
if ( ! isWholeNumber( floatValue ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce Float value `%s` as Integer: not a whole number",
|
||||
floatValue
|
||||
)
|
||||
);
|
||||
}
|
||||
return floatValue.longValue();
|
||||
}
|
||||
|
||||
public static Long toLong(BigInteger value) {
|
||||
return coerceWrappingError( value::longValueExact );
|
||||
}
|
||||
|
||||
public static Long toLong(BigDecimal value) {
|
||||
return coerceWrappingError( value::longValueExact );
|
||||
}
|
||||
|
||||
public static BigInteger toBigInteger(Double doubleValue) {
|
||||
if ( ! isWholeNumber( doubleValue ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce Double value `%s` as BigInteger: not a whole number",
|
||||
doubleValue
|
||||
)
|
||||
);
|
||||
}
|
||||
return BigInteger.valueOf( doubleValue.longValue() );
|
||||
}
|
||||
|
||||
public static BigInteger toBigInteger(Float floatValue) {
|
||||
if ( ! isWholeNumber( floatValue ) ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Unable to coerce Double Float `%s` as BigInteger: not a whole number",
|
||||
floatValue
|
||||
)
|
||||
);
|
||||
}
|
||||
return BigInteger.valueOf( floatValue.longValue() );
|
||||
}
|
||||
|
||||
public static BigInteger toBigInteger(BigDecimal value) {
|
||||
return coerceWrappingError( value::toBigIntegerExact );
|
||||
}
|
||||
|
||||
public static Double toDouble(Float floatValue) {
|
||||
if ( floatValue > (float) Double.MAX_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Float value `%s` to Double : overflow",
|
||||
floatValue
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( floatValue < (float) Double.MIN_VALUE ) {
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce Float value `%s` to Double : underflow",
|
||||
floatValue
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (double) floatValue;
|
||||
}
|
||||
|
||||
public static Double toDouble(BigInteger value) {
|
||||
return coerceWrappingError( value::doubleValue );
|
||||
}
|
||||
|
||||
public static Double toDouble(BigDecimal value) {
|
||||
return coerceWrappingError( value::doubleValue );
|
||||
}
|
||||
|
||||
public static boolean isWholeNumber(double doubleValue) {
|
||||
return doubleValue % 1 == 0;
|
||||
}
|
||||
|
||||
public static boolean isWholeNumber(float floatValue) {
|
||||
return floatValue == ( (float) (long) floatValue );
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Coercer<T> {
|
||||
T doCoercion();
|
||||
}
|
||||
|
||||
public static <T> T coerceWrappingError(Coercer<T> coercer) {
|
||||
try {
|
||||
return coercer.doCoercion();
|
||||
}
|
||||
catch (ArithmeticException | NumberFormatException e) {
|
||||
throw new CoercionException( "Error coercing value", e );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ package org.hibernate.type.descriptor.java;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Types;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
@ -121,4 +122,58 @@ public class DoubleTypeDescriptor extends AbstractClassTypeDescriptor<Double> im
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <X> Double coerce(X value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return ( (Double) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return ( (Byte) value ).doubleValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return ( (Short) value ).doubleValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return ( (Integer) value ).doubleValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return ( (Long) value ).doubleValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return CoercionHelper.toDouble( (float) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return CoercionHelper.toDouble( (BigInteger) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return CoercionHelper.toDouble( (BigDecimal) value );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> Double.parseDouble( (String) value )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce value `%s` [%s] as Double",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.java;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
@ -109,4 +110,58 @@ public class FloatTypeDescriptor extends AbstractClassTypeDescriptor<Float> impl
|
|||
//in a single-precision FP number
|
||||
return dialect.getFloatPrecision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> Float coerce(X value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return (Float) value;
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return ( (Double) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return ( (Byte) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return ( (Short) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return ( (Integer) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return ( (Long) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return ( (BigInteger) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return ( (BigDecimal) value ).floatValue();
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> Float.parseFloat( (String) value )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce value `%s` [%s] as Float",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.java;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
@ -110,4 +111,58 @@ public class IntegerTypeDescriptor extends AbstractClassTypeDescriptor<Integer>
|
|||
public int getDefaultSqlScale() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer coerce(Object value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return CoercionHelper.toInteger( (short) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return CoercionHelper.toInteger( (byte) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return CoercionHelper.toInteger( (long) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return CoercionHelper.toInteger( (double) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return CoercionHelper.toInteger( (float) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return CoercionHelper.toInteger( (BigInteger) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return CoercionHelper.toInteger( (BigDecimal) value );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> Integer.parseInt( (String) value )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce vale `%s` [%s] as Integer",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.hibernate.internal.util.compare.ComparableComparator;
|
|||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptorIndicators;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Descriptor for the Java side of a value mapping.
|
||||
|
@ -179,6 +180,15 @@ public interface JavaTypeDescriptor<T> extends Serializable {
|
|||
*/
|
||||
<X> T wrap(X value, WrapperOptions options);
|
||||
|
||||
interface CoercionContext {
|
||||
TypeConfiguration getTypeConfiguration();
|
||||
}
|
||||
|
||||
default <X> T coerce(X value, CoercionContext coercionContext) {
|
||||
//noinspection unchecked
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Java type handled here.
|
||||
*
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.java;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
@ -74,18 +75,72 @@ public class LongTypeDescriptor extends AbstractClassTypeDescriptor<Long> implem
|
|||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( Long.class.isInstance( value ) ) {
|
||||
if ( value instanceof Long ) {
|
||||
return (Long) value;
|
||||
}
|
||||
if ( Number.class.isInstance( value ) ) {
|
||||
if ( value instanceof Number ) {
|
||||
return ( (Number) value ).longValue();
|
||||
}
|
||||
else if ( String.class.isInstance( value ) ) {
|
||||
else if ( value instanceof String ) {
|
||||
return Long.valueOf( ( (String) value ) );
|
||||
}
|
||||
throw unknownWrap( value.getClass() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> Long coerce(X value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return ( (Long) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return CoercionHelper.toLong( (Byte) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return CoercionHelper.toLong( (Short) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return CoercionHelper.toLong( (Integer) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return CoercionHelper.toLong( (Double) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return CoercionHelper.toLong( (Float) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return CoercionHelper.toLong( (BigInteger) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return CoercionHelper.toLong( (BigDecimal) value );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> Long.parseLong( (String) value )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce value `%s` [%s] as Long",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getPrimitiveClass() {
|
||||
return long.class;
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.type.descriptor.java;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.spi.Primitive;
|
||||
|
@ -76,7 +80,7 @@ public class ShortTypeDescriptor extends AbstractClassTypeDescriptor<Short> impl
|
|||
}
|
||||
|
||||
@Override
|
||||
public Class getPrimitiveClass() {
|
||||
public Class<Short> getPrimitiveClass() {
|
||||
return short.class;
|
||||
}
|
||||
|
||||
|
@ -99,4 +103,58 @@ public class ShortTypeDescriptor extends AbstractClassTypeDescriptor<Short> impl
|
|||
public int getDefaultSqlScale() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short coerce(Object value, CoercionContext coercionContext) {
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( value instanceof Short ) {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
if ( value instanceof Byte ) {
|
||||
return CoercionHelper.toShort( (Byte) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Integer ) {
|
||||
return CoercionHelper.toShort( (Integer) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Long ) {
|
||||
return CoercionHelper.toShort( (Long) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Double ) {
|
||||
return CoercionHelper.toShort( (Double) value );
|
||||
}
|
||||
|
||||
if ( value instanceof Float ) {
|
||||
return CoercionHelper.toShort( (Float) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigInteger ) {
|
||||
return CoercionHelper.toShort( (BigInteger) value );
|
||||
}
|
||||
|
||||
if ( value instanceof BigDecimal ) {
|
||||
return CoercionHelper.toShort( (BigDecimal) value );
|
||||
}
|
||||
|
||||
if ( value instanceof String ) {
|
||||
return CoercionHelper.coerceWrappingError(
|
||||
() -> Short.parseShort( (String) value )
|
||||
);
|
||||
}
|
||||
|
||||
throw new CoercionException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot coerce value `%s` [%s] as Short",
|
||||
value,
|
||||
value.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.type.java;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.query.spi.QueryImplementor;
|
||||
import org.hibernate.type.descriptor.java.CoercionException;
|
||||
import org.hibernate.type.descriptor.java.CoercionHelper;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for implicit widening coercions
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = CoercionTests.TheEntity.class )
|
||||
@SessionFactory
|
||||
public class CoercionTests {
|
||||
final short shortValue = 1;
|
||||
final byte byteValue = 1;
|
||||
final int intValue = 1;
|
||||
final long longValue = 1L;
|
||||
final double doubleValue = 0.5;
|
||||
final float floatValue = 0.5F;
|
||||
|
||||
final long largeLongValue = Integer.MAX_VALUE + 1L;
|
||||
final float largeFloatValue = (float) Double.MAX_VALUE + 1.5F;
|
||||
|
||||
@Test
|
||||
public void testCoercibleDetection(SessionFactoryScope scope) {
|
||||
final TypeConfiguration typeConfiguration = scope.getSessionFactory().getTypeConfiguration();
|
||||
final JavaTypeDescriptorRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry();
|
||||
|
||||
final JavaTypeDescriptor<Integer> integerType = jtdRegistry.resolveDescriptor( Integer.class );
|
||||
final JavaTypeDescriptor<Long> longType = jtdRegistry.resolveDescriptor( Long.class );
|
||||
final JavaTypeDescriptor<Double> doubleType = jtdRegistry.resolveDescriptor( Double.class );
|
||||
final JavaTypeDescriptor<Float> floatType = jtdRegistry.resolveDescriptor( Float.class );
|
||||
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
checkIntegerConversions( integerType, session );
|
||||
checkLongConversions( longType, session );
|
||||
|
||||
checkDoubleConversions( doubleType, session );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void checkDoubleConversions(JavaTypeDescriptor<Double> doubleType, SessionImplementor session) {
|
||||
assertThat( doubleType.coerce( (double) 1, session ), Matchers.is( 1.0 ) );
|
||||
assertThat( doubleType.coerce( 1F, session ), Matchers.is( 1.0 ) );
|
||||
assertThat( doubleType.coerce( doubleValue, session ), Matchers.is( doubleValue ) );
|
||||
assertThat( doubleType.coerce( floatValue, session ), Matchers.is( doubleValue ) );
|
||||
|
||||
assertThat( doubleType.coerce( largeFloatValue, session ), Matchers.is( (double) largeFloatValue ) );
|
||||
|
||||
assertThat( doubleType.coerce( shortValue, session ), Matchers.is( 1.0 ) );
|
||||
assertThat( doubleType.coerce( byteValue, session ), Matchers.is( 1.0 ) );
|
||||
assertThat( doubleType.coerce( longValue, session ), Matchers.is( 1.0 ) );
|
||||
|
||||
assertThat( doubleType.coerce( BigInteger.ONE, session ), Matchers.is( 1.0 ) );
|
||||
assertThat( doubleType.coerce( BigDecimal.ONE, session ), Matchers.is( 1.0 ) );
|
||||
|
||||
// negative checks
|
||||
}
|
||||
|
||||
private void checkIntegerConversions(JavaTypeDescriptor<Integer> integerType, SessionImplementor session) {
|
||||
assertThat( integerType.coerce( intValue, session ), Matchers.is( intValue) );
|
||||
|
||||
assertThat( integerType.coerce( shortValue, session ), Matchers.is( intValue) );
|
||||
assertThat( integerType.coerce( byteValue, session ), Matchers.is( intValue) );
|
||||
|
||||
assertThat( integerType.coerce( longValue, session ), Matchers.is( intValue) );
|
||||
|
||||
assertThat( integerType.coerce( (double) 1, session ), Matchers.is( intValue) );
|
||||
assertThat( integerType.coerce( 1F, session ), Matchers.is( intValue) );
|
||||
|
||||
assertThat( integerType.coerce( BigInteger.ONE, session ), Matchers.is( intValue) );
|
||||
assertThat( integerType.coerce( BigDecimal.ONE, session ), Matchers.is( intValue) );
|
||||
|
||||
// negative checks
|
||||
checkDisallowedConversion( () -> integerType.coerce( largeLongValue, session ) );
|
||||
checkDisallowedConversion( () -> integerType.coerce( largeFloatValue, session ) );
|
||||
checkDisallowedConversion( () -> integerType.coerce( doubleValue, session ) );
|
||||
checkDisallowedConversion( () -> integerType.coerce( floatValue, session ) );
|
||||
}
|
||||
|
||||
private void checkLongConversions(JavaTypeDescriptor<Long> longType, SessionImplementor session) {
|
||||
assertThat( longType.coerce( longValue, session ), Matchers.is( longValue ) );
|
||||
assertThat( longType.coerce( largeLongValue, session ), Matchers.is( largeLongValue ) );
|
||||
|
||||
assertThat( longType.coerce( intValue, session ), Matchers.is( longValue ) );
|
||||
assertThat( longType.coerce( shortValue, session ), Matchers.is( longValue ) );
|
||||
assertThat( longType.coerce( byteValue, session ), Matchers.is( longValue ) );
|
||||
|
||||
assertThat( longType.coerce( (double) 1, session ), Matchers.is( longValue ) );
|
||||
assertThat( longType.coerce( 1F, session ), Matchers.is( longValue ) );
|
||||
|
||||
assertThat( longType.coerce( BigInteger.ONE, session ), Matchers.is( longValue ) );
|
||||
assertThat( longType.coerce( BigDecimal.ONE, session ), Matchers.is( longValue ) );
|
||||
|
||||
// negative checks
|
||||
checkDisallowedConversion( () -> longType.coerce( largeFloatValue, session ) );
|
||||
checkDisallowedConversion( () -> longType.coerce( doubleValue, session ) );
|
||||
checkDisallowedConversion( () -> longType.coerce( floatValue, session ) );
|
||||
}
|
||||
|
||||
private void checkDisallowedConversion(CoercionHelper.Coercer callback) {
|
||||
try {
|
||||
callback.doCoercion();
|
||||
fail( "Expecting coercion to fail" );
|
||||
}
|
||||
catch (CoercionException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoading(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byId( TheEntity.class ).load( 1L );
|
||||
|
||||
session.byId( TheEntity.class ).load( (byte) 1 );
|
||||
session.byId( TheEntity.class ).load( (short) 1 );
|
||||
session.byId( TheEntity.class ).load( 1 );
|
||||
|
||||
session.byId( TheEntity.class ).load( 1.0 );
|
||||
session.byId( TheEntity.class ).load( 1.0F );
|
||||
|
||||
session.byId( TheEntity.class ).load( BigInteger.ONE );
|
||||
session.byId( TheEntity.class ).load( BigDecimal.ONE );
|
||||
}
|
||||
);
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byId( TheEntity.class ).getReference( 1L );
|
||||
session.byId( TheEntity.class ).getReference( 1 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiIdLoading(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( 1L );
|
||||
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( (byte) 1 );
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( (short) 1 );
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( 1 );
|
||||
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( 1.0 );
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( 1.0F );
|
||||
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( BigInteger.ONE );
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( BigDecimal.ONE );
|
||||
}
|
||||
);
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( Arrays.asList( 1L ) );
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( Arrays.asList( 1 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNaturalIdLoading(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( 1L );
|
||||
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( (byte) 1 );
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( (short) 1 );
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( 1 );
|
||||
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( 1.0 );
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( 1.0F );
|
||||
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( BigInteger.ONE );
|
||||
session.bySimpleNaturalId( TheEntity.class ).load( BigDecimal.ONE );
|
||||
}
|
||||
);
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( Arrays.asList( 1L ) );
|
||||
session.byMultipleIds( TheEntity.class ).multiLoad( Arrays.asList( 1 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiNaturalIdLoading(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( 1L );
|
||||
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( (byte) 1 );
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( (short) 1 );
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( 1 );
|
||||
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( 1.0 );
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( 1.0F );
|
||||
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( BigInteger.ONE );
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( BigDecimal.ONE );
|
||||
}
|
||||
);
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( Arrays.asList( 1L ) );
|
||||
session.byMultipleNaturalId( TheEntity.class ).enableOrderedReturn( false ).multiLoad( Arrays.asList( 1 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryParameterIntegralWiden(SessionFactoryScope scope) {
|
||||
final String qry = "select e from TheEntity e where e.longId = :id";
|
||||
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final QueryImplementor query = session.createQuery( qry );
|
||||
|
||||
query.setParameter( "id", 1L ).list();
|
||||
|
||||
query.setParameter( "id", 1 ).list();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryParameterIntegralNarrow(SessionFactoryScope scope) {
|
||||
final String qry = "select e from TheEntity e where e.intValue = ?1";
|
||||
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final QueryImplementor query = session.createQuery( qry );
|
||||
|
||||
query.setParameter( 1, 1 ).list();
|
||||
|
||||
query.setParameter( 1, 1L ).list();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryParameterFloatingWiden(SessionFactoryScope scope) {
|
||||
final String qry = "select e from TheEntity e where e.floatValue = :p";
|
||||
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final QueryImplementor query = session.createQuery( qry );
|
||||
|
||||
query.setParameter( "p", 0.5f ).list();
|
||||
|
||||
query.setParameter( "p", 0.5 ).list();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryParameterFloatingNarrow(SessionFactoryScope scope) {
|
||||
final String qry = "select e from TheEntity e where e.doubleValue = :p";
|
||||
|
||||
scope.inTransaction(
|
||||
(session) -> {
|
||||
final QueryImplementor query = session.createQuery( qry );
|
||||
|
||||
query.setParameter( "p", 0.5 ).list();
|
||||
|
||||
query.setParameter( "p", 0.5f ).list();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity( name = "TheEntity" )
|
||||
@Table( name = "the_entity" )
|
||||
public static class TheEntity {
|
||||
@Id
|
||||
private Long longId;
|
||||
@NaturalId
|
||||
private Long longNaturalId;
|
||||
private Integer intValue;
|
||||
private Float floatValue;
|
||||
private Double doubleValue;
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue