HHH-15847 fix check constraint creation

- fix check constraints for built-in Boolean converters
- move getCheckCondition() + getSpecializedTypeDeclaration() from JavaType to BasicValueConverter
- simplify the API of Dialect related to check constraints
- recover check constraint for boolean on Oracle by letting Dialects register converters
- attempt to clean up some generics stuff in enum-related code
This commit is contained in:
Gavin 2022-12-10 20:40:33 +01:00 committed by Gavin King
parent a25e53d1ab
commit 4d2f4988c8
28 changed files with 604 additions and 440 deletions

View File

@ -510,8 +510,14 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
attributeConverterManager.addRegistration( conversion, bootstrapContext ); attributeConverterManager.addRegistration( conversion, bootstrapContext );
} }
private boolean doneDialect;
@Override @Override
public ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() { public ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
if ( !doneDialect ) {
getDatabase().getDialect().registerAttributeConverters( this );
doneDialect = true;
}
return attributeConverterManager; return attributeConverterManager;
} }

View File

@ -75,7 +75,7 @@ public class TypeDefinition implements Serializable {
Map<String,String> parameters) { Map<String,String> parameters) {
this.name = name; this.name = name;
this.typeImplementorClass = typeImplementorClass; this.typeImplementorClass = typeImplementorClass;
this.registrationKeys= registrationKeys; this.registrationKeys = registrationKeys;
this.parameters = parameters; this.parameters = parameters;
} }

View File

@ -18,7 +18,6 @@ import java.util.function.Function;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.internal.ClassmateContext;
import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor; import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler; import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterDescriptor;

View File

@ -7,6 +7,7 @@
package org.hibernate.boot.model.convert.internal; package org.hibernate.boot.model.convert.internal;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -18,6 +19,10 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.members.ResolvedMember; import com.fasterxml.classmate.members.ResolvedMember;
import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveAttributeType;
import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveMember;
import static org.hibernate.boot.model.convert.internal.ConverterHelper.typesMatch;
/** /**
* Standard implementation of AutoApplicableConverterDescriptor * Standard implementation of AutoApplicableConverterDescriptor
* *
@ -34,9 +39,9 @@ public class AutoApplicableConverterDescriptorStandardImpl implements AutoApplic
public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute( public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute(
XProperty xProperty, XProperty xProperty,
MetadataBuildingContext context) { MetadataBuildingContext context) {
final ResolvedType attributeType = ConverterHelper.resolveAttributeType( xProperty, context ); final ResolvedType attributeType = resolveAttributeType( xProperty, context );
return ConverterHelper.typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), attributeType ) return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), attributeType )
? linkedConverterDescriptor ? linkedConverterDescriptor
: null; : null;
} }
@ -45,20 +50,32 @@ public class AutoApplicableConverterDescriptorStandardImpl implements AutoApplic
public ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement( public ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement(
XProperty xProperty, XProperty xProperty,
MetadataBuildingContext context) { MetadataBuildingContext context) {
final ResolvedMember<?> collectionMember = ConverterHelper.resolveMember( xProperty, context ); final ResolvedMember<?> collectionMember = resolveMember( xProperty, context );
final ResolvedType elementType; final ResolvedType elementType;
if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { Class<?> erasedType = collectionMember.getType().getErasedType();
elementType = collectionMember.getType().typeParametersFor( Map.class ).get( 1 ); if ( Map.class.isAssignableFrom( erasedType ) ) {
List<ResolvedType> typeArguments = collectionMember.getType().typeParametersFor(Map.class);
if ( typeArguments.size() < 2 ) {
return null;
}
elementType = typeArguments.get( 1 );
} }
else if ( Collection.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { else if ( Collection.class.isAssignableFrom( erasedType ) ) {
elementType = collectionMember.getType().typeParametersFor( Collection.class ).get( 0 ); List<ResolvedType> typeArguments = collectionMember.getType().typeParametersFor(Collection.class);
if ( typeArguments.isEmpty() ) {
return null;
}
elementType = typeArguments.get( 0 );
}
else if ( erasedType.isArray() ) {
elementType = collectionMember.getType().getArrayElementType();
} }
else { else {
throw new HibernateException( "Attribute was neither a Collection nor a Map : " + collectionMember.getType().getErasedType() ); throw new HibernateException( "Attribute was neither a Collection nor a Map : " + erasedType);
} }
return ConverterHelper.typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), elementType ) return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), elementType )
? linkedConverterDescriptor ? linkedConverterDescriptor
: null; : null;
} }
@ -68,17 +85,21 @@ public class AutoApplicableConverterDescriptorStandardImpl implements AutoApplic
XProperty xProperty, XProperty xProperty,
MetadataBuildingContext context) { MetadataBuildingContext context) {
final ResolvedMember<?> collectionMember = ConverterHelper.resolveMember( xProperty, context ); final ResolvedMember<?> collectionMember = resolveMember( xProperty, context );
final ResolvedType keyType; final ResolvedType keyType;
if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) {
keyType = collectionMember.getType().typeParametersFor( Map.class ).get( 0 ); List<ResolvedType> typeArguments = collectionMember.getType().typeParametersFor(Map.class);
if ( typeArguments.isEmpty() ) {
return null;
}
keyType = typeArguments.get(0);
} }
else { else {
throw new HibernateException( "Attribute was not a Map : " + collectionMember.getType().getErasedType() ); throw new HibernateException( "Attribute was not a Map : " + collectionMember.getType().getErasedType() );
} }
return ConverterHelper.typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), keyType ) return typesMatch( linkedConverterDescriptor.getDomainValueResolvedType(), keyType )
? linkedConverterDescriptor ? linkedConverterDescriptor
: null; : null;
} }

View File

@ -34,7 +34,6 @@ public class ClassBasedConverterDescriptor extends AbstractConverterDescriptor {
@Override @Override
protected ManagedBean<? extends AttributeConverter<?, ?>> createManagedBean(JpaAttributeConverterCreationContext context) { protected ManagedBean<? extends AttributeConverter<?, ?>> createManagedBean(JpaAttributeConverterCreationContext context) {
//noinspection unchecked return context.getManagedBeanRegistry().getBean( getAttributeConverterClass() );
return (ManagedBean<? extends AttributeConverter<?, ?>>) context.getManagedBeanRegistry().getBean( getAttributeConverterClass() );
} }
} }

View File

@ -274,7 +274,8 @@ public class InferredBasicValueResolver {
recommendedJtd, recommendedJtd,
explicitJdbcType explicitJdbcType
), ),
recommendedJtd ); recommendedJtd
);
legacyType = jdbcMapping; legacyType = jdbcMapping;
} }
else { else {
@ -358,9 +359,9 @@ public class InferredBasicValueResolver {
} }
} }
public static <E extends Enum<E>> InferredBasicValueResolution<E,?> fromEnum( public static <E extends Enum<E>, N extends Number> InferredBasicValueResolution<E,?> fromEnum(
EnumJavaType<E> enumJavaType, EnumJavaType<E> enumJavaType,
BasicJavaType<?> explicitJavaType, BasicJavaType<N> explicitJavaType,
JdbcType explicitJdbcType, JdbcType explicitJdbcType,
JdbcTypeIndicators stdIndicators, JdbcTypeIndicators stdIndicators,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
@ -383,7 +384,6 @@ public class InferredBasicValueResolver {
enumJavaType, enumJavaType,
explicitJavaType, explicitJavaType,
explicitJdbcType, explicitJdbcType,
stdIndicators,
typeConfiguration typeConfiguration
); );
} }
@ -393,41 +393,51 @@ public class InferredBasicValueResolver {
} }
} }
private static <E extends Enum<E>> InferredBasicValueResolution<E, Number> ordinalEnumValueResolution( private static <E extends Enum<E>, N extends Number> InferredBasicValueResolution<E,N> ordinalEnumValueResolution(
EnumJavaType<E> enumJavaType, EnumJavaType<E> enumJavaType,
BasicJavaType<?> explicitJavaType, JavaType<N> explicitJavaType,
JdbcType explicitJdbcType, JdbcType explicitJdbcType,
JdbcTypeIndicators stdIndicators,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
final JavaType<Number> relationalJtd; return ordinalResolution(
enumJavaType,
integerJavaType( explicitJavaType, typeConfiguration ),
integerJdbcType( explicitJdbcType, typeConfiguration ),
typeConfiguration
);
}
private static JdbcType integerJdbcType(JdbcType explicitJdbcType, TypeConfiguration typeConfiguration) {
return explicitJdbcType != null
? explicitJdbcType
: typeConfiguration.getJdbcTypeRegistry().getDescriptor( SqlTypes.SMALLINT );
}
private static <N extends Number> JavaType<N> integerJavaType(JavaType<N> explicitJavaType, TypeConfiguration typeConfiguration) {
if ( explicitJavaType != null ) { if ( explicitJavaType != null ) {
if ( ! Integer.class.isAssignableFrom( explicitJavaType.getJavaTypeClass() ) ) { if ( !Integer.class.isAssignableFrom( explicitJavaType.getJavaTypeClass() ) ) {
throw new MappingException( throw new MappingException(
"Explicit JavaType [" + explicitJavaType + "Explicit JavaType [" + explicitJavaType +
"] applied to enumerated value with EnumType#ORDINAL" + "] applied to enumerated value with EnumType#ORDINAL" +
" should handle `java.lang.Integer` as its relational type descriptor" " should handle `java.lang.Integer` as its relational type descriptor"
); );
} }
//noinspection unchecked return explicitJavaType;
relationalJtd = (BasicJavaType<Number>) explicitJavaType;
} }
else { else {
relationalJtd = typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class ); return typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class );
} }
}
final JdbcType jdbcType = explicitJdbcType != null private static <E extends Enum<E>, N extends Number> InferredBasicValueResolution<E, N> ordinalResolution(
? explicitJdbcType EnumJavaType<E> enumJavaType,
: typeConfiguration.getJdbcTypeRegistry().getDescriptor( SqlTypes.SMALLINT ); JavaType<N> relationalJtd,
JdbcType jdbcType,
final OrdinalEnumValueConverter<E> valueConverter = new OrdinalEnumValueConverter<>( TypeConfiguration typeConfiguration
enumJavaType, ) {
jdbcType,
relationalJtd
);
final CustomType<E> customType = new CustomType<>( final CustomType<E> customType = new CustomType<>(
new org.hibernate.type.EnumType<>( new org.hibernate.type.EnumType<>(
enumJavaType.getJavaTypeClass(), enumJavaType.getJavaTypeClass(),
valueConverter, new OrdinalEnumValueConverter<>( enumJavaType, jdbcType, relationalJtd ),
typeConfiguration typeConfiguration
), ),
typeConfiguration typeConfiguration
@ -448,37 +458,28 @@ public class InferredBasicValueResolver {
JdbcType explicitJdbcType, JdbcType explicitJdbcType,
JdbcTypeIndicators stdIndicators, JdbcTypeIndicators stdIndicators,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
final JavaType<String> relationalJtd; final JavaType<String> relationalJtd = stringJavaType( explicitJavaType, stdIndicators, typeConfiguration );
if ( explicitJavaType != null ) { return stringResolution(
if ( ! String.class.isAssignableFrom( explicitJavaType.getJavaTypeClass() ) ) {
throw new MappingException(
"Explicit JavaType [" + explicitJavaType +
"] applied to enumerated value with EnumType#STRING" +
" should handle `java.lang.String` as its relational type descriptor"
);
}
//noinspection unchecked
relationalJtd = (BasicJavaType<String>) explicitJavaType;
}
else {
final boolean useCharacter = stdIndicators.getColumnLength() == 1;
relationalJtd = typeConfiguration.getJavaTypeRegistry()
.getDescriptor( useCharacter ? Character.class : String.class );
}
final JdbcType jdbcType = explicitJdbcType != null
? explicitJdbcType
: relationalJtd.getRecommendedJdbcType(stdIndicators);
final NamedEnumValueConverter<E> valueConverter = new NamedEnumValueConverter<>(
enumJavaType, enumJavaType,
jdbcType, relationalJtd,
relationalJtd stringJdbcType( explicitJdbcType, stdIndicators, relationalJtd ),
typeConfiguration
); );
}
private static <E extends Enum<E>> InferredBasicValueResolution<E, String> stringResolution(
EnumJavaType<E> enumJavaType,
JavaType<String> relationalJtd,
JdbcType jdbcType,
TypeConfiguration typeConfiguration) {
final CustomType<E> customType = new CustomType<>( final CustomType<E> customType = new CustomType<>(
new org.hibernate.type.EnumType<>( new org.hibernate.type.EnumType<>(
enumJavaType.getJavaTypeClass(), enumJavaType.getJavaTypeClass(),
valueConverter, new NamedEnumValueConverter<E>(
enumJavaType,
jdbcType,
relationalJtd
),
typeConfiguration typeConfiguration
), ),
typeConfiguration typeConfiguration
@ -493,6 +494,29 @@ public class InferredBasicValueResolver {
); );
} }
private static JdbcType stringJdbcType(JdbcType explicitJdbcType, JdbcTypeIndicators stdIndicators, JavaType<String> relationalJtd) {
return explicitJdbcType != null
? explicitJdbcType
: relationalJtd.getRecommendedJdbcType( stdIndicators );
}
private static JavaType<String> stringJavaType(BasicJavaType<?> explicitJavaType, JdbcTypeIndicators stdIndicators, TypeConfiguration typeConfiguration) {
if ( explicitJavaType != null ) {
if ( ! String.class.isAssignableFrom( explicitJavaType.getJavaTypeClass() ) ) {
throw new MappingException(
"Explicit JavaType [" + explicitJavaType +
"] applied to enumerated value with EnumType#STRING" +
" should handle `java.lang.String` as its relational type descriptor"
);
}
return (JavaType<String>) explicitJavaType;
}
else {
return typeConfiguration.getJavaTypeRegistry()
.getDescriptor( stdIndicators.getColumnLength() == 1 ? Character.class : String.class );
}
}
public static <T> InferredBasicValueResolution<T,T> fromTemporal( public static <T> InferredBasicValueResolution<T,T> fromTemporal(
TemporalJavaType<T> reflectedJtd, TemporalJavaType<T> reflectedJtd,
BasicJavaType<?> explicitJavaType, BasicJavaType<?> explicitJavaType,

View File

@ -47,6 +47,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Sequence; import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CastFunction; import org.hibernate.dialect.function.CastFunction;
@ -215,11 +216,9 @@ import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.SqlTypes.isCharacterType;
import static org.hibernate.type.SqlTypes.isFloatOrRealOrDouble; import static org.hibernate.type.SqlTypes.isFloatOrRealOrDouble;
import static org.hibernate.type.SqlTypes.isIntegral; import static org.hibernate.type.SqlTypes.isIntegral;
import static org.hibernate.type.SqlTypes.isNumericOrDecimal; import static org.hibernate.type.SqlTypes.isNumericOrDecimal;
import static org.hibernate.type.SqlTypes.isNumericType;
import static org.hibernate.type.SqlTypes.isVarbinaryType; import static org.hibernate.type.SqlTypes.isVarbinaryType;
import static org.hibernate.type.SqlTypes.isVarcharType; import static org.hibernate.type.SqlTypes.isVarcharType;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END; import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
@ -349,6 +348,8 @@ public abstract class Dialect implements ConversionContext {
Boolean.toString( getDefaultUseGetGeneratedKeys() ) ); Boolean.toString( getDefaultUseGetGeneratedKeys() ) );
} }
public void registerAttributeConverters(InFlightMetadataCollector metadataCollector) {}
/** /**
* Register ANSI-standard column types using the length limits defined * Register ANSI-standard column types using the length limits defined
* by {@link #getMaxVarcharLength()}, {@link #getMaxNVarcharLength()}, * by {@link #getMaxVarcharLength()}, {@link #getMaxNVarcharLength()},
@ -653,50 +654,29 @@ public abstract class Dialect implements ConversionContext {
} }
} }
public String getBooleanTypeDeclaration(int sqlType, char falseChar, char trueChar) { public String getEnumTypeDeclaration(String[] values) {
return null;
}
/**
* Render a SQL check condition for a column that represents a boolean value.
*/
public String getBooleanCheckCondition(String columnName, int sqlType, char falseChar, char trueChar) {
if ( isCharacterType(sqlType) ) {
return columnName + " in ('" + falseChar + "','" + trueChar + "')";
}
else if ( isNumericType(sqlType) && !supportsBitType() ) {
return columnName + " in (0,1)";
}
else {
return null;
}
}
public String getEnumTypeDeclaration(int sqlType, Class<? extends Enum<?>> enumClass) {
return null; return null;
} }
/** /**
* Render a SQL check condition for a column that represents an enumerated value. * Render a SQL check condition for a column that represents an enumerated value.
*/ */
public String getEnumCheckCondition(String columnName, int sqlType, Class<? extends Enum<?>> enumClass) { public String getCheckCondition(String columnName, String[] values) {
if ( isCharacterType(sqlType) ) { StringBuilder check = new StringBuilder();
StringBuilder check = new StringBuilder(); check.append( columnName ).append( " in (" );
check.append( columnName ).append( " in (" ); String separator = "";
String separator = ""; for ( String value : values ) {
for ( Enum<?> value : enumClass.getEnumConstants() ) { check.append( separator ).append('\'').append( value ).append('\'');
check.append( separator ).append('\'').append( value.name() ).append('\''); separator = ",";
separator = ",";
}
return check.append( ')' ).toString();
}
else if ( isNumericType(sqlType) ) {
int last = enumClass.getEnumConstants().length - 1;
return columnName + " between 0 and " + last;
}
else {
return null;
} }
return check.append( ')' ).toString();
}
/**
* Render a SQL check condition for a column that represents an enumerated value.
*/
public String getCheckCondition(String columnName, long min, long max) {
return columnName + " between " + min + " and " + max;
} }
/** /**

View File

@ -77,8 +77,6 @@ import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.isCharacterType;
import static org.hibernate.type.SqlTypes.isIntegral;
import static org.hibernate.type.SqlTypes.BIGINT; import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY; import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BIT; import static org.hibernate.type.SqlTypes.BIT;
@ -755,39 +753,23 @@ public class MySQLDialect extends Dialect {
} }
@Override @Override
public String getEnumTypeDeclaration(int sqlType, Class<? extends Enum<?>> enumClass) { public String getEnumTypeDeclaration(String[] values) {
if ( isCharacterType(sqlType) || isIntegral(sqlType) ) { StringBuilder type = new StringBuilder();
StringBuilder type = new StringBuilder(); type.append( "enum (" );
type.append( "enum (" ); String separator = "";
String separator = ""; for ( String value : values ) {
for ( Enum<?> value : enumClass.getEnumConstants() ) { type.append( separator ).append('\'').append( value ).append('\'');
type.append( separator ).append('\'').append( value.name() ).append('\''); separator = ",";
separator = ",";
}
return type.append( ')' ).toString();
}
else {
return null;
} }
return type.append( ')' ).toString();
} }
@Override @Override
public String getBooleanTypeDeclaration(int sqlType, char falseChar, char trueChar) { public String getCheckCondition(String columnName, String[] values) {
return isCharacterType( sqlType ) ? "enum ('" + falseChar + "','" + trueChar + "')" : null; //not needed, because we use an 'enum' type
}
@Override
public String getEnumCheckCondition(String columnName, int sqlType, Class<? extends Enum<?>> enumClass) {
// don't need it, since we're using the 'enum' type
return null; return null;
} }
@Override
public String getBooleanCheckCondition(String columnName, int sqlType, char falseChar, char trueChar) {
return isCharacterType( sqlType ) ? null
: super.getBooleanCheckCondition( columnName, sqlType, falseChar, trueChar );
}
@Override @Override
public String getQueryHintString(String query, String hints) { public String getQueryHintString(String query, String hints) {
return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints ); return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );

View File

@ -15,9 +15,12 @@ import java.util.Locale;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException; import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.ModeStatsModeEmulation; import org.hibernate.dialect.function.ModeStatsModeEmulation;
@ -47,6 +50,7 @@ import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.procedure.internal.StandardCallableStatementSupport; import org.hibernate.procedure.internal.StandardCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.procedure.spi.CallableStatementSupport;
@ -74,6 +78,8 @@ import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
import org.hibernate.type.NullType; import org.hibernate.type.NullType;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -636,6 +642,48 @@ public class OracleDialect extends Dialect {
} }
} }
@Converter(autoApply = true)
static class BooleanBooleanConverter implements AttributeConverter<Boolean,Boolean>, BasicValueConverter<Boolean,Boolean> {
@Override
public Boolean convertToDatabaseColumn(Boolean attribute) {
return attribute;
}
@Override
public Boolean convertToEntityAttribute(Boolean dbData) {
return dbData;
}
@Override
public Boolean toDomainValue(Boolean relationalForm) {
return relationalForm;
}
@Override
public Boolean toRelationalValue(Boolean domainForm) {
return domainForm;
}
@Override
public JavaType<Boolean> getDomainJavaType() {
return BooleanJavaType.INSTANCE;
}
@Override
public JavaType<Boolean> getRelationalJavaType() {
return BooleanJavaType.INSTANCE;
}
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return jdbcType.getDefaultSqlTypeCode() == Types.BOOLEAN ? columnName + " in (0,1)" : null;
}
}
public void registerAttributeConverters(InFlightMetadataCollector metadataCollector) {
metadataCollector.addAttributeConverter( BooleanBooleanConverter.class );
}
@Override @Override
public TimeZoneSupport getTimeZoneSupport() { public TimeZoneSupport getTimeZoneSupport() {
return TimeZoneSupport.NATIVE; return TimeZoneSupport.NATIVE;

View File

@ -318,34 +318,35 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
} }
final Selectable selectable = getColumn(); final Selectable selectable = getColumn();
if ( selectable instanceof Column && resolution.getValueConverter() == null ) { if ( selectable instanceof Column ) {
final Column column = (Column) selectable; resolveColumn( (Column) selectable, getServiceRegistry().getService( JdbcServices.class ).getDialect() );
if ( column.getSqlTypeCode() == null ) {
column.setSqlTypeCode( resolution.getJdbcType().getDefaultSqlTypeCode() );
}
final Dialect dialect = getServiceRegistry().getService( JdbcServices.class ).getDialect();
column.setSpecializedTypeDeclaration(
resolution.getDomainJavaType().getSpecializedTypeDeclaration(
resolution.getJdbcType(),
dialect
)
);
if ( dialect.supportsColumnCheck() && !column.hasCheckConstraint() ) {
column.setCheckConstraint(
resolution.getDomainJavaType().getCheckCondition(
column.getQuotedName(dialect),
resolution.getJdbcType(),
dialect
)
);
}
} }
return resolution; return resolution;
} }
private void resolveColumn(Column column, Dialect dialect) {
if ( column.getSqlTypeCode() == null ) {
column.setSqlTypeCode( resolution.getJdbcType().getDefaultSqlTypeCode() );
}
if ( resolution.getValueConverter() != null ) {
column.setSpecializedTypeDeclaration( resolution.getValueConverter().getSpecializedTypeDeclaration( resolution.getJdbcType(), dialect ) );
if ( dialect.supportsColumnCheck() && !column.hasCheckConstraint() ) {
column.setCheckConstraint(
resolution.getValueConverter()
.getCheckCondition(
column.getQuotedName( dialect ),
resolution.getJdbcType(),
dialect
)
);
}
}
}
protected Resolution<?> buildResolution() { protected Resolution<?> buildResolution() {
Properties typeParameters = getTypeParameters(); Properties typeParameters = getTypeParameters();
if ( typeParameters != null if ( typeParameters != null

View File

@ -0,0 +1,21 @@
/*
* 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.metamodel.model.convert.internal;
/**
* @author Gavin King
*/
public class EnumHelper {
public static final String[] getEnumeratedValues(Class<? extends Enum<?>> enumClass) {
Enum<?>[] values = enumClass.getEnumConstants();
String[] names = new String[values.length];
for ( int i = 0; i < values.length; i++ ) {
names[i] = values[i].name();
}
return names;
}
}

View File

@ -10,6 +10,8 @@ import jakarta.persistence.AttributeConverter;
import jakarta.persistence.PersistenceException; import jakarta.persistence.PersistenceException;
import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter; import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ManagedBean;
import org.hibernate.type.descriptor.converter.AttributeConverterMutabilityPlanImpl; import org.hibernate.type.descriptor.converter.AttributeConverterMutabilityPlanImpl;
@ -17,6 +19,7 @@ import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan; import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.java.spi.RegistryHelper; import org.hibernate.type.descriptor.java.spi.RegistryHelper;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/** /**
* Standard implementation of JpaAttributeConverter * Standard implementation of JpaAttributeConverter
@ -106,6 +109,28 @@ public class JpaAttributeConverterImpl<O,R> implements JpaAttributeConverter<O,R
} }
} }
@Override
public String getCheckCondition(String columnName, JdbcType sqlType, Dialect dialect) {
if ( BasicValueConverter.class.isAssignableFrom( attributeConverterBean.getBeanClass() ) ) {
return ((BasicValueConverter<?, ?>) attributeConverterBean.getBeanInstance())
.getCheckCondition( columnName, sqlType, dialect );
}
else {
return null;
}
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
if ( BasicValueConverter.class.isAssignableFrom( attributeConverterBean.getBeanClass() ) ) {
return ((BasicValueConverter<?, ?>) attributeConverterBean.getBeanInstance())
.getSpecializedTypeDeclaration( jdbcType, dialect );
}
else {
return null;
}
}
@Override @Override
public JavaType<? extends AttributeConverter<O, R>> getConverterJavaType() { public JavaType<? extends AttributeConverter<O, R>> getConverterJavaType() {
return converterJtd; return converterJtd;

View File

@ -13,14 +13,16 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Locale; import java.util.Locale;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; import org.hibernate.metamodel.model.convert.spi.EnumValueConverter;
import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import static org.hibernate.metamodel.model.convert.internal.EnumHelper.getEnumeratedValues;
/** /**
* BasicValueConverter handling the conversion of an enum based on * BasicValueConverter handling the conversion of an enum based on
* JPA {@link jakarta.persistence.EnumType#STRING} strategy (storing the name) * JPA {@link jakarta.persistence.EnumType#STRING} strategy (storing the name)
@ -32,7 +34,7 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
private final JdbcType jdbcType; private final JdbcType jdbcType;
private final JavaType<String> relationalTypeDescriptor; private final JavaType<String> relationalTypeDescriptor;
private transient ValueExtractor<String> valueExtractor; // private transient ValueExtractor<String> valueExtractor;
private transient ValueBinder<String> valueBinder; private transient ValueBinder<String> valueBinder;
public NamedEnumValueConverter( public NamedEnumValueConverter(
@ -43,8 +45,8 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
this.jdbcType = jdbcType; this.jdbcType = jdbcType;
this.relationalTypeDescriptor = relationalTypeDescriptor; this.relationalTypeDescriptor = relationalTypeDescriptor;
this.valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor ); // valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor );
this.valueBinder = jdbcType.getBinder( relationalTypeDescriptor ); valueBinder = jdbcType.getBinder( relationalTypeDescriptor );
} }
@Override @Override
@ -85,8 +87,8 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
stream.defaultReadObject(); stream.defaultReadObject();
this.valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor ); // valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor );
this.valueBinder = jdbcType.getBinder( relationalTypeDescriptor ); valueBinder = jdbcType.getBinder( relationalTypeDescriptor );
} }
@Override @Override
@ -98,4 +100,14 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
final String jdbcValue = value == null ? null : value.name(); final String jdbcValue = value == null ? null : value.name();
valueBinder.bind( statement, jdbcValue, position, session ); valueBinder.bind( statement, jdbcValue, position, session );
} }
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return dialect.getCheckCondition( columnName, getEnumeratedValues( getDomainJavaType().getJavaTypeClass() ) );
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return dialect.getEnumTypeDeclaration( getEnumeratedValues( getDomainJavaType().getJavaTypeClass() ) );
}
} }

View File

@ -12,10 +12,10 @@ import java.io.Serializable;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; import org.hibernate.metamodel.model.convert.spi.EnumValueConverter;
import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -26,25 +26,25 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueConverter<E, Number>, Serializable { public class OrdinalEnumValueConverter<E extends Enum<E>, N extends Number> implements EnumValueConverter<E, N>, Serializable {
private final EnumJavaType<E> enumJavaType; private final EnumJavaType<E> enumJavaType;
private final JdbcType jdbcType; private final JdbcType jdbcType;
private final JavaType<Number> relationalJavaType; private final JavaType<N> relationalJavaType;
private transient ValueExtractor<Number> valueExtractor; // private transient ValueExtractor<N> valueExtractor;
private transient ValueBinder<Number> valueBinder; private transient ValueBinder<N> valueBinder;
public OrdinalEnumValueConverter( public OrdinalEnumValueConverter(
EnumJavaType<E> enumJavaType, EnumJavaType<E> enumJavaType,
JdbcType jdbcType, JdbcType jdbcType,
JavaType<Number> relationalJavaType) { JavaType<N> relationalJavaType) {
this.enumJavaType = enumJavaType; this.enumJavaType = enumJavaType;
this.jdbcType = jdbcType; this.jdbcType = jdbcType;
this.relationalJavaType = relationalJavaType; this.relationalJavaType = relationalJavaType;
this.valueExtractor = jdbcType.getExtractor( relationalJavaType ); // valueExtractor = jdbcType.getExtractor( relationalJavaType );
this.valueBinder = jdbcType.getBinder( relationalJavaType ); valueBinder = jdbcType.getBinder( relationalJavaType );
} }
@Override @Override
@ -52,9 +52,9 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
return enumJavaType.fromOrdinal( relationalForm == null ? null : relationalForm.intValue() ); return enumJavaType.fromOrdinal( relationalForm == null ? null : relationalForm.intValue() );
} }
@Override @Override @SuppressWarnings("unchecked")
public Number toRelationalValue(E domainForm) { public N toRelationalValue(E domainForm) {
return enumJavaType.toOrdinal( domainForm ); return (N) enumJavaType.toOrdinal( domainForm );
} }
@Override @Override
@ -68,7 +68,7 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
} }
@Override @Override
public JavaType<Number> getRelationalJavaType() { public JavaType<N> getRelationalJavaType() {
return relationalJavaType; return relationalJavaType;
} }
@ -81,8 +81,8 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
stream.defaultReadObject(); stream.defaultReadObject();
this.valueExtractor = jdbcType.getExtractor( relationalJavaType ); // valueExtractor = jdbcType.getExtractor( relationalJavaType );
this.valueBinder = jdbcType.getBinder( relationalJavaType ); valueBinder = jdbcType.getBinder( relationalJavaType );
} }
@Override @Override
@ -90,4 +90,10 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
throws SQLException { throws SQLException {
valueBinder.bind( statement, toRelationalValue( value ), position, session ); valueBinder.bind( statement, toRelationalValue( value ), position, session );
} }
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
int max = getDomainJavaType().getJavaTypeClass().getEnumConstants().length - 1;
return dialect.getCheckCondition( columnName, 0, max );
}
} }

View File

@ -7,7 +7,9 @@
package org.hibernate.metamodel.model.convert.spi; package org.hibernate.metamodel.model.convert.spi;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/** /**
* Support for basic-value conversions. * Support for basic-value conversions.
@ -46,4 +48,23 @@ public interface BasicValueConverter<D,R> {
* Descriptor for the Java type for the relational portion of this converter * Descriptor for the Java type for the relational portion of this converter
*/ */
JavaType<R> getRelationalJavaType(); JavaType<R> getRelationalJavaType();
/**
* The check constraint that should be added to the column
* definition in generated DDL.
*
* @param columnName the name of the column
* @param sqlType the {@link JdbcType} of the mapped column
* @param dialect the SQL {@link Dialect}
* @return a check constraint condition or null
*/
@Incubating
default String getCheckCondition(String columnName, JdbcType sqlType, Dialect dialect) {
return null;
}
@Incubating
default String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return null;
}
} }

View File

@ -10,10 +10,14 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.hibernate.Remove; import org.hibernate.Remove;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcOperationQuery; import org.hibernate.sql.exec.spi.JdbcOperationQuery;
import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import static org.hibernate.metamodel.model.convert.internal.EnumHelper.getEnumeratedValues;
/** /**
* BasicValueConverter extension for enum-specific support * BasicValueConverter extension for enum-specific support

View File

@ -213,7 +213,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
private void checkType(int count, String functionName, FunctionParameterType type, int code, Type javaType) { private void checkType(int count, String functionName, FunctionParameterType type, int code, Type javaType) {
switch (type) { switch (type) {
case COMPARABLE: case COMPARABLE:
if ( !isCharacterType(code) && !isTemporalType(code) &&!isNumericType(code) && code != UUID ) { if ( !isCharacterType(code) && !isTemporalType(code) && !isNumericType(code) && code != UUID ) {
if ( javaType == java.util.UUID.class && ( code == Types.BINARY || isCharacterType( code ) ) ) { if ( javaType == java.util.UUID.class && ( code == Types.BINARY || isCharacterType( code ) ) ) {
// We also consider UUID to be comparable when it's a character or binary type // We also consider UUID to be comparable when it's a character or binary type
return; return;

View File

@ -6432,20 +6432,29 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final JdbcType jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( SqlTypes.SMALLINT ); final JdbcType jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( SqlTypes.SMALLINT );
final JavaType<Number> relationalJtd = typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class ); final JavaType<Number> relationalJtd = typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class );
return new QueryLiteral<>( return queryLiteral( sqmEnumLiteral, enumJtd, typeConfiguration, jdbcType, relationalJtd );
sqmEnumLiteral.getEnumValue().ordinal(),
new CustomType<>(
new EnumType<>(
enumJtd.getJavaTypeClass(),
new OrdinalEnumValueConverter<>( enumJtd, jdbcType, relationalJtd ),
typeConfiguration
),
typeConfiguration
)
);
} }
} }
private static <T extends Enum<T>, N extends Number> QueryLiteral<Integer> queryLiteral(
SqmEnumLiteral<?> sqmEnumLiteral,
EnumJavaType<T> enumJtd,
TypeConfiguration typeConfiguration,
JdbcType jdbcType,
JavaType<N> relationalJtd) {
return new QueryLiteral<>(
sqmEnumLiteral.getEnumValue().ordinal(),
new CustomType<>(
new EnumType<>(
enumJtd.getJavaTypeClass(),
new OrdinalEnumValueConverter<>( enumJtd, jdbcType, relationalJtd ),
typeConfiguration
),
typeConfiguration
)
);
}
@Override @Override
public Object visitFieldLiteral(SqmFieldLiteral<?> sqmFieldLiteral) { public Object visitFieldLiteral(SqmFieldLiteral<?> sqmFieldLiteral) {
return new QueryLiteral<>( return new QueryLiteral<>(

View File

@ -0,0 +1,59 @@
/*
* 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;
import jakarta.persistence.AttributeConverter;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.CharacterJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* Abstract supertype of converters which map {@link Boolean} to {@link Character}.
*
* @author Steve Ebersole
* @author Gavin King
*/
public abstract class CharBooleanConverter
implements AttributeConverter<Boolean, Character>, BasicValueConverter<Boolean, Character> {
/**
* Singleton access
*/
@Override
public Character convertToDatabaseColumn(Boolean attribute) {
return toRelationalValue( attribute );
}
@Override
public Boolean convertToEntityAttribute(Character dbData) {
return toDomainValue( dbData );
}
@Override
public JavaType<Boolean> getDomainJavaType() {
return BooleanJavaType.INSTANCE;
}
@Override
public JavaType<Character> getRelationalJavaType() {
return CharacterJavaType.INSTANCE;
}
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return dialect.getCheckCondition( columnName, getValues() );
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return dialect.getEnumTypeDeclaration( getValues() );
}
protected abstract String[] getValues();
}

View File

@ -69,21 +69,21 @@ public class CustomType<J>
public CustomType(UserType<J> userType, String[] registrationKeys, TypeConfiguration typeConfiguration) throws MappingException { public CustomType(UserType<J> userType, String[] registrationKeys, TypeConfiguration typeConfiguration) throws MappingException {
this.userType = userType; this.userType = userType;
this.name = userType.getClass().getName(); name = userType.getClass().getName();
if ( userType instanceof JavaType<?> ) { if ( userType instanceof JavaType<?> ) {
//noinspection unchecked //noinspection unchecked
this.mappedJavaType = (JavaType<J>) userType; mappedJavaType = (JavaType<J>) userType;
} }
else if ( userType instanceof JavaTypedExpressible) { else if ( userType instanceof JavaTypedExpressible) {
//noinspection unchecked //noinspection unchecked
this.mappedJavaType = ( (JavaTypedExpressible<J>) userType ).getExpressibleJavaType(); mappedJavaType = ( (JavaTypedExpressible<J>) userType ).getExpressibleJavaType();
} }
else if ( userType instanceof UserVersionType ) { else if ( userType instanceof UserVersionType ) {
this.mappedJavaType = new UserTypeVersionJavaTypeWrapper<>( (UserVersionType<J>) userType ); mappedJavaType = new UserTypeVersionJavaTypeWrapper<>( (UserVersionType<J>) userType );
} }
else { else {
this.mappedJavaType = new UserTypeJavaTypeWrapper<>( userType ); mappedJavaType = new UserTypeJavaTypeWrapper<>( userType );
} }
final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter(); final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
@ -91,22 +91,22 @@ public class CustomType<J>
// When an explicit value converter is given, // When an explicit value converter is given,
// we configure the custom type to use that instead of adapters that delegate to UserType. // we configure the custom type to use that instead of adapters that delegate to UserType.
// This is necessary to support selecting a column with multiple domain type representations. // This is necessary to support selecting a column with multiple domain type representations.
this.jdbcType = userType.getJdbcType( typeConfiguration ); jdbcType = userType.getJdbcType( typeConfiguration );
this.jdbcJavaType = valueConverter.getRelationalJavaType(); jdbcJavaType = valueConverter.getRelationalJavaType();
//noinspection unchecked //noinspection unchecked
this.valueExtractor = (ValueExtractor<J>) jdbcType.getExtractor( jdbcJavaType ); valueExtractor = (ValueExtractor<J>) jdbcType.getExtractor( jdbcJavaType );
//noinspection unchecked //noinspection unchecked
this.valueBinder = (ValueBinder<J>) jdbcType.getBinder( jdbcJavaType ); valueBinder = (ValueBinder<J>) jdbcType.getBinder( jdbcJavaType );
//noinspection unchecked //noinspection unchecked
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<J>) jdbcType.getJdbcLiteralFormatter( jdbcJavaType ); jdbcLiteralFormatter = (JdbcLiteralFormatter<J>) jdbcType.getJdbcLiteralFormatter( jdbcJavaType );
} }
else { else {
// create a JdbcType adapter that uses the UserType binder/extract handling // create a JdbcType adapter that uses the UserType binder/extract handling
this.jdbcType = new UserTypeSqlTypeAdapter<>( userType, mappedJavaType, typeConfiguration ); jdbcType = new UserTypeSqlTypeAdapter<>( userType, mappedJavaType, typeConfiguration );
this.jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration ); jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
this.valueExtractor = jdbcType.getExtractor( mappedJavaType ); valueExtractor = jdbcType.getExtractor( mappedJavaType );
this.valueBinder = jdbcType.getBinder( mappedJavaType ); valueBinder = jdbcType.getBinder( mappedJavaType );
this.jdbcLiteralFormatter = userType instanceof EnhancedUserType jdbcLiteralFormatter = userType instanceof EnhancedUserType
? jdbcType.getJdbcLiteralFormatter( mappedJavaType ) ? jdbcType.getJdbcLiteralFormatter( mappedJavaType )
: null; : null;
} }

View File

@ -12,7 +12,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
import jakarta.persistence.MapKeyEnumerated; import jakarta.persistence.MapKeyEnumerated;
@ -33,6 +32,7 @@ import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
@ -46,30 +46,10 @@ import org.jboss.logging.Logger;
/** /**
* Value type mapper for enumerations. * Value type mapper for enumerations.
* *
* Provides 2 distinct forms of "configuration" - one for hbm.xml mapping and
* another for annotation/orm.xml mapping triggered within the {@link #setParameterValues}
* method
*
* Annotation based config relies on a {@link ParameterType} reference passed as
* an entry in the parameter values under the key {@link #PARAMETER_TYPE}
*
* hbm.xml based config relies on a number of values from the parameters: <ul>
* <li>
* {@link #ENUM} - Name the enumeration class.
* </li>
* <li>
* {@link #NAMED} - Should enum be mapped via name. Default is to map as ordinal.
* </li>
* <li>
* {@link #TYPE} - JDBC type code (legacy alternative to {@link #NAMED})
* </li>
* </ul>
*
* @author Emmanuel Bernard * @author Emmanuel Bernard
* @author Hardy Ferentschik * @author Hardy Ferentschik
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@SuppressWarnings("unchecked")
public class EnumType<T extends Enum<T>> public class EnumType<T extends Enum<T>>
implements EnhancedUserType<T>, DynamicParameterizedType, LoggableUserType, TypeConfigurationAware, Serializable { implements EnhancedUserType<T>, DynamicParameterizedType, LoggableUserType, TypeConfigurationAware, Serializable {
private static final Logger LOG = CoreLogging.logger( EnumType.class ); private static final Logger LOG = CoreLogging.logger( EnumType.class );
@ -90,20 +70,21 @@ public class EnumType<T extends Enum<T>>
public EnumType() { public EnumType() {
} }
@SuppressWarnings("unchecked")
public EnumType( public EnumType(
Class<T> enumClass, Class<T> enumClass,
EnumValueConverter enumValueConverter, EnumValueConverter<T,?> enumValueConverter,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
this.enumClass = enumClass; this.enumClass = enumClass;
this.typeConfiguration = typeConfiguration; this.typeConfiguration = typeConfiguration;
this.enumValueConverter = enumValueConverter; this.enumValueConverter = (EnumValueConverter<T,Object>) enumValueConverter;
this.jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( enumValueConverter.getJdbcTypeCode() ); this.jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( enumValueConverter.getJdbcTypeCode() );
this.jdbcValueExtractor = jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() ); this.jdbcValueExtractor = (ValueExtractor<Object>) jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() );
this.jdbcValueBinder = jdbcType.getBinder( enumValueConverter.getRelationalJavaType() ); this.jdbcValueBinder = (ValueBinder<Object>) jdbcType.getBinder( enumValueConverter.getRelationalJavaType() );
} }
public EnumValueConverter getEnumValueConverter() { public EnumValueConverter<T, ?> getEnumValueConverter() {
return enumValueConverter; return enumValueConverter;
} }
@ -117,6 +98,32 @@ public class EnumType<T extends Enum<T>>
return enumValueConverter; return enumValueConverter;
} }
/**
* <p>
* An instance of this class is "configured" by a call to {@link #setParameterValues},
* where configuration parameters are given as entries in a {@link Properties} object.
* There are two distinct ways an instance may be configured:
* <ul>
* <li>one for {@code hbm.xml}-based mapping, and
* <li>another for annotation-based or {@code orm.xml}-based mapping.
* </ul>
* In the case of annotations or {@code orm.xml}, a {@link ParameterType} is passed to
* {@link #setParameterValues} under the key {@value #PARAMETER_TYPE}.
* <p>
* But in the case of {@code hbm.xml}, there are multiple parameters:
* <ul>
* <li>
* {@value #ENUM}, the name of the Java enumeration class.
* </li>
* <li>
* {@value #NAMED}, specifies if the enum should be mapped by name.
* Default is to map as ordinal.
* </li>
* <li>
* {@value #TYPE}, a JDBC type code (legacy alternative to {@value #NAMED}).
* </li>
* </ul>
*/
@Override @Override
public void setParameterValues(Properties parameters) { public void setParameterValues(Properties parameters) {
// IMPL NOTE: we handle 2 distinct cases here: // IMPL NOTE: we handle 2 distinct cases here:
@ -126,74 +133,15 @@ public class EnumType<T extends Enum<T>>
// 2) we are not passed a ParameterType - generally this indicates a hbm.xml binding case. // 2) we are not passed a ParameterType - generally this indicates a hbm.xml binding case.
final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE ); final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
// the `reader != null` block handles annotations, while the `else` block // the `reader != null` block handles annotations, while the `else` block handles hbm.xml
// handles hbm.xml
if ( reader != null ) { if ( reader != null ) {
enumClass = (Class<T>) reader.getReturnedClass().asSubclass( Enum.class ); configureUsingReader( reader );
final Long columnLength = reader.getColumnLengths()[0];
final boolean isOrdinal;
final jakarta.persistence.EnumType enumType = getEnumType( reader );
if ( enumType == null ) {
isOrdinal = true;
}
else if ( jakarta.persistence.EnumType.ORDINAL.equals( enumType ) ) {
isOrdinal = true;
}
else if ( jakarta.persistence.EnumType.STRING.equals( enumType ) ) {
isOrdinal = false;
}
else {
throw new AssertionFailure( "Unknown EnumType: " + enumType );
}
final EnumJavaType enumJavaType = (EnumJavaType) typeConfiguration
.getJavaTypeRegistry()
.getDescriptor( enumClass );
final LocalJdbcTypeIndicators indicators = new LocalJdbcTypeIndicators(
enumType,
columnLength,
reader
);
final BasicJavaType<?> relationalJavaType = resolveRelationalJavaType(
indicators,
enumJavaType
);
this.jdbcType = relationalJavaType.getRecommendedJdbcType( indicators );
if ( isOrdinal ) {
this.enumValueConverter = new OrdinalEnumValueConverter(
enumJavaType,
jdbcType,
relationalJavaType
);
}
else {
this.enumValueConverter = new NamedEnumValueConverter(
enumJavaType,
jdbcType,
relationalJavaType
);
}
} }
else { else {
final String enumClassName = (String) parameters.get( ENUM ); configureUsingParameters( parameters );
try {
enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
}
catch ( ClassNotFoundException exception ) {
throw new HibernateException( "Enum class not found: " + enumClassName, exception );
}
this.enumValueConverter = interpretParameters( parameters );
this.jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( enumValueConverter.getJdbcTypeCode() );
} }
this.jdbcValueExtractor = jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() ); jdbcValueExtractor = jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() );
this.jdbcValueBinder = jdbcType.getBinder( enumValueConverter.getRelationalJavaType() ); jdbcValueBinder = jdbcType.getBinder( enumValueConverter.getRelationalJavaType() );
if ( LOG.isDebugEnabled() ) { if ( LOG.isDebugEnabled() ) {
LOG.debugf( LOG.debugf(
@ -204,14 +152,54 @@ public class EnumType<T extends Enum<T>>
} }
} }
@SuppressWarnings("unchecked")
private void configureUsingParameters(Properties parameters) {
final String enumClassName = (String) parameters.get( ENUM );
try {
enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
}
catch ( ClassNotFoundException exception ) {
throw new HibernateException( "Enum class not found: " + enumClassName, exception );
}
enumValueConverter = (EnumValueConverter<T,Object>) interpretParameters( parameters ); //this typecast is rubbish
jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( enumValueConverter.getJdbcTypeCode() );
}
@SuppressWarnings({"rawtypes","unchecked"})
private void configureUsingReader(ParameterType reader) {
enumClass = (Class<T>) reader.getReturnedClass().asSubclass( Enum.class );
final jakarta.persistence.EnumType enumType = getEnumType( reader );
final JavaType<T> descriptor = typeConfiguration.getJavaTypeRegistry().getDescriptor( enumClass );
final EnumJavaType<T> enumJavaType = (EnumJavaType<T>) descriptor;
final LocalJdbcTypeIndicators indicators = new LocalJdbcTypeIndicators( enumType, reader );
final JavaType<?> relationalJavaType = resolveRelationalJavaType( indicators, enumJavaType );
jdbcType = relationalJavaType.getRecommendedJdbcType( indicators );
enumValueConverter = isOrdinal( enumType )
? new OrdinalEnumValueConverter( enumJavaType, jdbcType, relationalJavaType )
: new NamedEnumValueConverter( enumJavaType, jdbcType, relationalJavaType );
}
private static boolean isOrdinal(jakarta.persistence.EnumType enumType) {
if ( enumType == null ) {
return true;
}
else {
switch ( enumType ) {
case ORDINAL:
return true;
case STRING:
return false;
default:
throw new AssertionFailure( "Unknown EnumType: " + enumType);
}
}
}
private BasicJavaType<?> resolveRelationalJavaType( private BasicJavaType<?> resolveRelationalJavaType(
LocalJdbcTypeIndicators indicators, LocalJdbcTypeIndicators indicators,
EnumJavaType<?> enumJavaType) { EnumJavaType<?> enumJavaType) {
return enumJavaType.getRecommendedJdbcType( indicators ).getJdbcRecommendedJavaTypeMapping( return enumJavaType.getRecommendedJdbcType( indicators )
null, .getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
null,
typeConfiguration
);
} }
private jakarta.persistence.EnumType getEnumType(ParameterType reader) { private jakarta.persistence.EnumType getEnumType(ParameterType reader) {
@ -234,20 +222,19 @@ public class EnumType<T extends Enum<T>>
return null; return null;
} }
private <A extends Annotation> A getAnnotation(Annotation[] annotations, Class<A> anClass) { @SuppressWarnings("unchecked")
private <A extends Annotation> A getAnnotation(Annotation[] annotations, Class<A> annotationType) {
for ( Annotation annotation : annotations ) { for ( Annotation annotation : annotations ) {
if ( anClass.isInstance( annotation ) ) { if ( annotationType.isInstance( annotation ) ) {
return (A) annotation; return (A) annotation;
} }
} }
return null; return null;
} }
private EnumValueConverter<T,Object> interpretParameters(Properties parameters) { private EnumValueConverter<T,?> interpretParameters(Properties parameters) {
//noinspection rawtypes JavaType<T> javaType = typeConfiguration.getJavaTypeRegistry().getDescriptor( enumClass );
final EnumJavaType enumJavaType = (EnumJavaType) typeConfiguration final EnumJavaType<T> enumJavaType = (EnumJavaType<T>) javaType;
.getJavaTypeRegistry()
.getDescriptor( enumClass );
// this method should only be called for hbm.xml handling // this method should only be called for hbm.xml handling
assert parameters.get( PARAMETER_TYPE ) == null; assert parameters.get( PARAMETER_TYPE ) == null;
@ -263,66 +250,78 @@ public class EnumType<T extends Enum<T>>
-1L, -1L,
null null
); );
final BasicJavaType<?> stringJavaType = (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( String.class );
final BasicJavaType<?> integerJavaType = (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class );
if ( parameters.containsKey( NAMED ) ) { if ( parameters.containsKey( NAMED ) ) {
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters ); final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
if ( useNamed ) { return getConverter( enumJavaType, localIndicators, useNamed );
//noinspection rawtypes
return new NamedEnumValueConverter(
enumJavaType,
stringJavaType.getRecommendedJdbcType( localIndicators ),
stringJavaType
);
}
else {
//noinspection rawtypes
return new OrdinalEnumValueConverter(
enumJavaType,
integerJavaType.getRecommendedJdbcType( localIndicators ),
typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class )
);
}
} }
if ( parameters.containsKey( TYPE ) ) { if ( parameters.containsKey( TYPE ) ) {
final int type = Integer.decode( (String) parameters.get( TYPE ) ); final int type = Integer.decode( (String) parameters.get( TYPE ) );
if ( isNumericType( type ) ) { return getConverterForType( enumJavaType, localIndicators, type );
//noinspection rawtypes
return new OrdinalEnumValueConverter(
enumJavaType,
integerJavaType.getRecommendedJdbcType( localIndicators ),
typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class )
);
}
else if ( isCharacterType( type ) ) {
//noinspection rawtypes
return new NamedEnumValueConverter(
enumJavaType,
stringJavaType.getRecommendedJdbcType( localIndicators ),
stringJavaType
);
}
else {
throw new HibernateException(
String.format(
Locale.ENGLISH,
"Passed JDBC type code [%s] not recognized as numeric nor character",
type
)
);
}
} }
// the fallback // the fallback
return new OrdinalEnumValueConverter( return new OrdinalEnumValueConverter<>(
enumJavaType, enumJavaType,
integerJavaType.getRecommendedJdbcType( localIndicators ), getIntegerType().getRecommendedJdbcType( localIndicators ),
typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class ) getIntegerType()
); );
} }
private JavaType<Integer> getIntegerType() {
return typeConfiguration.getJavaTypeRegistry().getDescriptor(Integer.class);
}
private JavaType<String> getStringType() {
return typeConfiguration.getJavaTypeRegistry().getDescriptor(String.class);
}
private EnumValueConverter<T,?> getConverter(
EnumJavaType<T> enumJavaType,
EnumType<T>.LocalJdbcTypeIndicators localIndicators,
boolean useNamed) {
if (useNamed) {
return new NamedEnumValueConverter<>(
enumJavaType,
getStringType().getRecommendedJdbcType( localIndicators ),
getStringType()
);
}
else {
return new OrdinalEnumValueConverter<>(
enumJavaType,
getIntegerType().getRecommendedJdbcType( localIndicators ),
getIntegerType()
);
}
}
private EnumValueConverter<T,?> getConverterForType(
EnumJavaType<T> enumJavaType,
LocalJdbcTypeIndicators localIndicators,
int type) {
if ( isNumericType(type) ) {
return new OrdinalEnumValueConverter<>(
enumJavaType,
getIntegerType().getRecommendedJdbcType( localIndicators ),
getIntegerType()
);
}
else if ( isCharacterType(type) ) {
return new NamedEnumValueConverter<>(
enumJavaType,
getStringType().getRecommendedJdbcType( localIndicators ),
getStringType()
);
}
else {
throw new HibernateException(
String.format( "Passed JDBC type code [%s] not recognized as numeric nor character", type )
);
}
}
private boolean isCharacterType(int jdbcTypeCode) { private boolean isCharacterType(int jdbcTypeCode) {
switch ( jdbcTypeCode ) { switch ( jdbcTypeCode ) {
case Types.CHAR: case Types.CHAR:
@ -377,8 +376,7 @@ public class EnumType<T extends Enum<T>>
@Override @Override
public T nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException { public T nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
verifyConfigured(); verifyConfigured();
final Object relational = jdbcValueExtractor.extract( rs, position, session ); return enumValueConverter.toDomainValue( jdbcValueExtractor.extract( rs, position, session ) );
return enumValueConverter.toDomainValue( relational );
} }
private void verifyConfigured() { private void verifyConfigured() {
@ -446,7 +444,7 @@ public class EnumType<T extends Enum<T>>
return enumValueConverter.toDomainValue( enumValueConverter.getRelationalJavaType().fromString( sequence ) ); return enumValueConverter.toDomainValue( enumValueConverter.getRelationalJavaType().fromString( sequence ) );
} }
@Override @Override @SuppressWarnings("unchecked")
public String toLoggableString(Object value, SessionFactoryImplementor factory) { public String toLoggableString(Object value, SessionFactoryImplementor factory) {
verifyConfigured(); verifyConfigured();
return enumValueConverter.getDomainJavaType().toString( (T) value ); return enumValueConverter.getDomainJavaType().toString( (T) value );
@ -462,7 +460,11 @@ public class EnumType<T extends Enum<T>>
private final Long columnLength; private final Long columnLength;
private final ParameterType reader; private final ParameterType reader;
public LocalJdbcTypeIndicators(jakarta.persistence.EnumType enumType, Long columnLength, ParameterType reader) { private LocalJdbcTypeIndicators(jakarta.persistence.EnumType enumType, ParameterType reader) {
this( enumType, reader.getColumnLengths()[0], reader );
}
private LocalJdbcTypeIndicators(jakarta.persistence.EnumType enumType, Long columnLength, ParameterType reader) {
this.enumType = enumType; this.enumType = enumType;
this.columnLength = columnLength; this.columnLength = columnLength;
this.reader = reader; this.reader = reader;

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.type; package org.hibernate.type;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BooleanJavaType; import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.IntegerJavaType; import org.hibernate.type.descriptor.java.IntegerJavaType;
@ -13,6 +14,7 @@ import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.AttributeConverter; import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter; import jakarta.persistence.Converter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/** /**
* Handles conversion to/from Boolean as `0` (false) or `1` (true) * Handles conversion to/from Boolean as `0` (false) or `1` (true)
@ -72,4 +74,9 @@ public class NumericBooleanConverter implements AttributeConverter<Boolean, Inte
public JavaType<Integer> getRelationalJavaType() { public JavaType<Integer> getRelationalJavaType() {
return IntegerJavaType.INSTANCE; return IntegerJavaType.INSTANCE;
} }
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return columnName + " in (0,1)";
}
} }

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.type; package org.hibernate.type;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BooleanJavaType; import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.CharacterJavaType; import org.hibernate.type.descriptor.java.CharacterJavaType;
@ -13,28 +14,24 @@ import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.AttributeConverter; import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter; import jakarta.persistence.Converter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/** /**
* Handles conversion to/from Boolean as `T` or `F` * Handles conversion to/from {@code Boolean} as {@code 'T'} or {@code 'F'}
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@Converter @Converter
public class TrueFalseConverter implements AttributeConverter<Boolean, Character>, public class TrueFalseConverter extends CharBooleanConverter {
BasicValueConverter<Boolean, Character> {
/** /**
* Singleton access * Singleton access
*/ */
public static final TrueFalseConverter INSTANCE = new TrueFalseConverter(); public static final TrueFalseConverter INSTANCE = new TrueFalseConverter();
private static final String[] VALUES = {"F", "T"};
@Override @Override
public Character convertToDatabaseColumn(Boolean attribute) { protected String[] getValues() {
return toRelationalValue( attribute ); return VALUES;
}
@Override
public Boolean convertToEntityAttribute(Character dbData) {
return toDomainValue( dbData );
} }
@Override @Override
@ -60,14 +57,4 @@ public class TrueFalseConverter implements AttributeConverter<Boolean, Character
return domainForm ? 'T' : 'F'; return domainForm ? 'T' : 'F';
} }
@Override
public JavaType<Boolean> getDomainJavaType() {
return BooleanJavaType.INSTANCE;
}
@Override
public JavaType<Character> getRelationalJavaType() {
return CharacterJavaType.INSTANCE;
}
} }

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.type; package org.hibernate.type;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BooleanJavaType; import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.CharacterJavaType; import org.hibernate.type.descriptor.java.CharacterJavaType;
@ -13,27 +14,24 @@ import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.AttributeConverter; import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter; import jakarta.persistence.Converter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/** /**
* Handles conversion to/from Boolean as `Y` or `N` * Handles conversion to/from {@code Boolean} as {@code 'Y'} or {@code 'N'}
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@Converter @Converter
public class YesNoConverter implements AttributeConverter<Boolean, Character>, BasicValueConverter<Boolean, Character> { public class YesNoConverter extends CharBooleanConverter {
/** /**
* Singleton access * Singleton access
*/ */
public static final YesNoConverter INSTANCE = new YesNoConverter(); public static final YesNoConverter INSTANCE = new YesNoConverter();
private static final String[] VALUES = {"N", "Y"};
@Override @Override
public Character convertToDatabaseColumn(Boolean attribute) { protected String[] getValues() {
return toRelationalValue( attribute ); return VALUES;
}
@Override
public Boolean convertToEntityAttribute(Character dbData) {
return toDomainValue( dbData );
} }
@Override @Override
@ -60,14 +58,4 @@ public class YesNoConverter implements AttributeConverter<Boolean, Character>, B
return domainForm ? 'Y' : 'N'; return domainForm ? 'Y' : 'N';
} }
@Override
public JavaType<Boolean> getDomainJavaType() {
return BooleanJavaType.INSTANCE;
}
@Override
public JavaType<Character> getRelationalJavaType() {
return CharacterJavaType.INSTANCE;
}
} }

View File

@ -166,23 +166,4 @@ public class BooleanJavaType extends AbstractClassJavaType<Boolean> implements
public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) { public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) {
return 0; return 0;
} }
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return dialect.getBooleanCheckCondition(
columnName,
jdbcType.getJdbcTypeCode(),
characterValueFalse,
characterValueTrue
);
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return dialect.getBooleanTypeDeclaration(
jdbcType.getJdbcTypeCode(),
characterValueFalse,
characterValueTrue
);
}
} }

View File

@ -9,7 +9,6 @@ package org.hibernate.type.descriptor.java;
import java.sql.Types; import java.sql.Types;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -214,14 +213,4 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
} }
return Enum.valueOf( getJavaTypeClass(), relationalForm.trim() ); return Enum.valueOf( getJavaTypeClass(), relationalForm.trim() );
} }
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return dialect.getEnumCheckCondition( columnName, jdbcType.getJdbcTypeCode(), getJavaTypeClass() );
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return dialect.getEnumTypeDeclaration( jdbcType.getJdbcTypeCode(), getJavaTypeClass() );
}
} }

View File

@ -267,6 +267,7 @@ public interface JavaType<T> extends Serializable {
return false; return false;
} }
@FunctionalInterface
interface CoercionContext { interface CoercionContext {
TypeConfiguration getTypeConfiguration(); TypeConfiguration getTypeConfiguration();
} }
@ -276,23 +277,6 @@ public interface JavaType<T> extends Serializable {
return (T) value; return (T) value;
} }
/**
* The check constraint that should be added to the column
* definition in generated DDL.
*
* @param columnName the name of the column
* @param sqlType the {@link JdbcType} of the mapped column
* @param dialect the SQL {@link Dialect}
* @return a check constraint condition or null
*/
default String getCheckCondition(String columnName, JdbcType sqlType, Dialect dialect) {
return null;
}
default String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return null;
}
/** /**
* Creates the {@link JavaType} for the given {@link ParameterizedType} * Creates the {@link JavaType} for the given {@link ParameterizedType}
* based on this {@link JavaType} registered for the raw type. * based on this {@link JavaType} registered for the raw type.

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.schematools; package org.hibernate.orm.test.schematools;
import jakarta.persistence.Basic; import jakarta.persistence.Basic;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
@ -33,6 +34,8 @@ import org.hibernate.tool.schema.spi.ScriptSourceInput;
import org.hibernate.tool.schema.spi.ScriptTargetOutput; import org.hibernate.tool.schema.spi.ScriptTargetOutput;
import org.hibernate.tool.schema.spi.SourceDescriptor; import org.hibernate.tool.schema.spi.SourceDescriptor;
import org.hibernate.tool.schema.spi.TargetDescriptor; import org.hibernate.tool.schema.spi.TargetDescriptor;
import org.hibernate.type.NumericBooleanConverter;
import org.hibernate.type.YesNoConverter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -94,7 +97,9 @@ public class EnumCheckTests {
} }
); );
assertThat( CollectingGenerationTarget.commands.get(0).contains( "check ('SOURCE','CLASS','RUNTIME')") ); assertThat( CollectingGenerationTarget.commands.get(0) ).contains( "in ('SOURCE','CLASS','RUNTIME')");
assertThat( CollectingGenerationTarget.commands.get(0) ).contains( "in ('N','Y')");
assertThat( CollectingGenerationTarget.commands.get(0) ).contains( "in (0,1)");
} }
); );
} }
@ -143,6 +148,10 @@ public class EnumCheckTests {
private String name; private String name;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
RetentionPolicy retentionPolicy; RetentionPolicy retentionPolicy;
@Convert(converter=YesNoConverter.class)
private boolean yesNo;
@Convert(converter= NumericBooleanConverter.class)
private boolean oneZero;
private SimpleEntity() { private SimpleEntity() {
// for use by Hibernate // for use by Hibernate