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 );
}
private boolean doneDialect;
@Override
public ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
if ( !doneDialect ) {
getDatabase().getDialect().registerAttributeConverters( this );
doneDialect = true;
}
return attributeConverterManager;
}

View File

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

View File

@ -18,7 +18,6 @@ import java.util.function.Function;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
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.ConverterAutoApplyHandler;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;

View File

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

View File

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

View File

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

View File

@ -47,6 +47,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cfg.Environment;
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.VARBINARY;
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.isIntegral;
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.isVarcharType;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
@ -349,6 +348,8 @@ public abstract class Dialect implements ConversionContext {
Boolean.toString( getDefaultUseGetGeneratedKeys() ) );
}
public void registerAttributeConverters(InFlightMetadataCollector metadataCollector) {}
/**
* Register ANSI-standard column types using the length limits defined
* by {@link #getMaxVarcharLength()}, {@link #getMaxNVarcharLength()},
@ -653,50 +654,29 @@ public abstract class Dialect implements ConversionContext {
}
}
public String getBooleanTypeDeclaration(int sqlType, char falseChar, char trueChar) {
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) {
public String getEnumTypeDeclaration(String[] values) {
return null;
}
/**
* Render a SQL check condition for a column that represents an enumerated value.
*/
public String getEnumCheckCondition(String columnName, int sqlType, Class<? extends Enum<?>> enumClass) {
if ( isCharacterType(sqlType) ) {
StringBuilder check = new StringBuilder();
check.append( columnName ).append( " in (" );
String separator = "";
for ( Enum<?> value : enumClass.getEnumConstants() ) {
check.append( separator ).append('\'').append( value.name() ).append('\'');
separator = ",";
}
return check.append( ')' ).toString();
}
else if ( isNumericType(sqlType) ) {
int last = enumClass.getEnumConstants().length - 1;
return columnName + " between 0 and " + last;
}
else {
return null;
public String getCheckCondition(String columnName, String[] values) {
StringBuilder check = new StringBuilder();
check.append( columnName ).append( " in (" );
String separator = "";
for ( String value : values ) {
check.append( separator ).append('\'').append( value ).append('\'');
separator = ",";
}
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 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.BINARY;
import static org.hibernate.type.SqlTypes.BIT;
@ -755,39 +753,23 @@ public class MySQLDialect extends Dialect {
}
@Override
public String getEnumTypeDeclaration(int sqlType, Class<? extends Enum<?>> enumClass) {
if ( isCharacterType(sqlType) || isIntegral(sqlType) ) {
StringBuilder type = new StringBuilder();
type.append( "enum (" );
String separator = "";
for ( Enum<?> value : enumClass.getEnumConstants() ) {
type.append( separator ).append('\'').append( value.name() ).append('\'');
separator = ",";
}
return type.append( ')' ).toString();
}
else {
return null;
public String getEnumTypeDeclaration(String[] values) {
StringBuilder type = new StringBuilder();
type.append( "enum (" );
String separator = "";
for ( String value : values ) {
type.append( separator ).append('\'').append( value ).append('\'');
separator = ",";
}
return type.append( ')' ).toString();
}
@Override
public String getBooleanTypeDeclaration(int sqlType, char falseChar, char trueChar) {
return isCharacterType( sqlType ) ? "enum ('" + falseChar + "','" + trueChar + "')" : null;
}
@Override
public String getEnumCheckCondition(String columnName, int sqlType, Class<? extends Enum<?>> enumClass) {
// don't need it, since we're using the 'enum' type
public String getCheckCondition(String columnName, String[] values) {
//not needed, because we use an 'enum' type
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
public String getQueryHintString(String query, String 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.Pattern;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CommonFunctionFactory;
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.StringHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
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.NullType;
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.jdbc.BlobJdbcType;
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
public TimeZoneSupport getTimeZoneSupport() {
return TimeZoneSupport.NATIVE;

View File

@ -318,34 +318,35 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
}
final Selectable selectable = getColumn();
if ( selectable instanceof Column && resolution.getValueConverter() == null ) {
final Column column = (Column) selectable;
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
)
);
}
if ( selectable instanceof Column ) {
resolveColumn( (Column) selectable, getServiceRegistry().getService( JdbcServices.class ).getDialect() );
}
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() {
Properties typeParameters = getTypeParameters();
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 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.resource.beans.spi.ManagedBean;
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.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.java.spi.RegistryHelper;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* 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
public JavaType<? extends AttributeConverter<O, R>> getConverterJavaType() {
return converterJtd;

View File

@ -13,14 +13,16 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Locale;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.convert.spi.EnumValueConverter;
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.JavaType;
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
* 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 JavaType<String> relationalTypeDescriptor;
private transient ValueExtractor<String> valueExtractor;
// private transient ValueExtractor<String> valueExtractor;
private transient ValueBinder<String> valueBinder;
public NamedEnumValueConverter(
@ -43,8 +45,8 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
this.jdbcType = jdbcType;
this.relationalTypeDescriptor = relationalTypeDescriptor;
this.valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor );
this.valueBinder = jdbcType.getBinder( relationalTypeDescriptor );
// valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor );
valueBinder = jdbcType.getBinder( relationalTypeDescriptor );
}
@Override
@ -85,8 +87,8 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
stream.defaultReadObject();
this.valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor );
this.valueBinder = jdbcType.getBinder( relationalTypeDescriptor );
// valueExtractor = jdbcType.getExtractor( relationalTypeDescriptor );
valueBinder = jdbcType.getBinder( relationalTypeDescriptor );
}
@Override
@ -98,4 +100,14 @@ public class NamedEnumValueConverter<E extends Enum<E>> implements EnumValueConv
final String jdbcValue = value == null ? null : value.name();
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.SQLException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.convert.spi.EnumValueConverter;
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.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -26,25 +26,25 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
*
* @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 JdbcType jdbcType;
private final JavaType<Number> relationalJavaType;
private final JavaType<N> relationalJavaType;
private transient ValueExtractor<Number> valueExtractor;
private transient ValueBinder<Number> valueBinder;
// private transient ValueExtractor<N> valueExtractor;
private transient ValueBinder<N> valueBinder;
public OrdinalEnumValueConverter(
EnumJavaType<E> enumJavaType,
JdbcType jdbcType,
JavaType<Number> relationalJavaType) {
JavaType<N> relationalJavaType) {
this.enumJavaType = enumJavaType;
this.jdbcType = jdbcType;
this.relationalJavaType = relationalJavaType;
this.valueExtractor = jdbcType.getExtractor( relationalJavaType );
this.valueBinder = jdbcType.getBinder( relationalJavaType );
// valueExtractor = jdbcType.getExtractor( relationalJavaType );
valueBinder = jdbcType.getBinder( relationalJavaType );
}
@Override
@ -52,9 +52,9 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
return enumJavaType.fromOrdinal( relationalForm == null ? null : relationalForm.intValue() );
}
@Override
public Number toRelationalValue(E domainForm) {
return enumJavaType.toOrdinal( domainForm );
@Override @SuppressWarnings("unchecked")
public N toRelationalValue(E domainForm) {
return (N) enumJavaType.toOrdinal( domainForm );
}
@Override
@ -68,7 +68,7 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
}
@Override
public JavaType<Number> getRelationalJavaType() {
public JavaType<N> getRelationalJavaType() {
return relationalJavaType;
}
@ -81,8 +81,8 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
stream.defaultReadObject();
this.valueExtractor = jdbcType.getExtractor( relationalJavaType );
this.valueBinder = jdbcType.getBinder( relationalJavaType );
// valueExtractor = jdbcType.getExtractor( relationalJavaType );
valueBinder = jdbcType.getBinder( relationalJavaType );
}
@Override
@ -90,4 +90,10 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
throws SQLException {
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;
import org.hibernate.Incubating;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* 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
*/
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 org.hibernate.Remove;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcOperationQuery;
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

View File

@ -213,7 +213,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
private void checkType(int count, String functionName, FunctionParameterType type, int code, Type javaType) {
switch (type) {
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 ) ) ) {
// We also consider UUID to be comparable when it's a character or binary type
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 JavaType<Number> relationalJtd = typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class );
return new QueryLiteral<>(
sqmEnumLiteral.getEnumValue().ordinal(),
new CustomType<>(
new EnumType<>(
enumJtd.getJavaTypeClass(),
new OrdinalEnumValueConverter<>( enumJtd, jdbcType, relationalJtd ),
typeConfiguration
),
typeConfiguration
)
);
return queryLiteral( sqmEnumLiteral, enumJtd, typeConfiguration, jdbcType, relationalJtd );
}
}
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
public Object visitFieldLiteral(SqmFieldLiteral<?> sqmFieldLiteral) {
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 {
this.userType = userType;
this.name = userType.getClass().getName();
name = userType.getClass().getName();
if ( userType instanceof JavaType<?> ) {
//noinspection unchecked
this.mappedJavaType = (JavaType<J>) userType;
mappedJavaType = (JavaType<J>) userType;
}
else if ( userType instanceof JavaTypedExpressible) {
//noinspection unchecked
this.mappedJavaType = ( (JavaTypedExpressible<J>) userType ).getExpressibleJavaType();
mappedJavaType = ( (JavaTypedExpressible<J>) userType ).getExpressibleJavaType();
}
else if ( userType instanceof UserVersionType ) {
this.mappedJavaType = new UserTypeVersionJavaTypeWrapper<>( (UserVersionType<J>) userType );
mappedJavaType = new UserTypeVersionJavaTypeWrapper<>( (UserVersionType<J>) userType );
}
else {
this.mappedJavaType = new UserTypeJavaTypeWrapper<>( userType );
mappedJavaType = new UserTypeJavaTypeWrapper<>( userType );
}
final BasicValueConverter<J, Object> valueConverter = userType.getValueConverter();
@ -91,22 +91,22 @@ public class CustomType<J>
// When an explicit value converter is given,
// 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.jdbcType = userType.getJdbcType( typeConfiguration );
this.jdbcJavaType = valueConverter.getRelationalJavaType();
jdbcType = userType.getJdbcType( typeConfiguration );
jdbcJavaType = valueConverter.getRelationalJavaType();
//noinspection unchecked
this.valueExtractor = (ValueExtractor<J>) jdbcType.getExtractor( jdbcJavaType );
valueExtractor = (ValueExtractor<J>) jdbcType.getExtractor( jdbcJavaType );
//noinspection unchecked
this.valueBinder = (ValueBinder<J>) jdbcType.getBinder( jdbcJavaType );
valueBinder = (ValueBinder<J>) jdbcType.getBinder( jdbcJavaType );
//noinspection unchecked
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<J>) jdbcType.getJdbcLiteralFormatter( jdbcJavaType );
jdbcLiteralFormatter = (JdbcLiteralFormatter<J>) jdbcType.getJdbcLiteralFormatter( jdbcJavaType );
}
else {
// create a JdbcType adapter that uses the UserType binder/extract handling
this.jdbcType = new UserTypeSqlTypeAdapter<>( userType, mappedJavaType, typeConfiguration );
this.jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
this.valueExtractor = jdbcType.getExtractor( mappedJavaType );
this.valueBinder = jdbcType.getBinder( mappedJavaType );
this.jdbcLiteralFormatter = userType instanceof EnhancedUserType
jdbcType = new UserTypeSqlTypeAdapter<>( userType, mappedJavaType, typeConfiguration );
jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
valueExtractor = jdbcType.getExtractor( mappedJavaType );
valueBinder = jdbcType.getBinder( mappedJavaType );
jdbcLiteralFormatter = userType instanceof EnhancedUserType
? jdbcType.getJdbcLiteralFormatter( mappedJavaType )
: null;
}

View File

@ -12,7 +12,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Locale;
import java.util.Properties;
import jakarta.persistence.Enumerated;
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.java.BasicJavaType;
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.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
@ -46,30 +46,10 @@ import org.jboss.logging.Logger;
/**
* 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 Hardy Ferentschik
* @author Steve Ebersole
*/
@SuppressWarnings("unchecked")
public class EnumType<T extends Enum<T>>
implements EnhancedUserType<T>, DynamicParameterizedType, LoggableUserType, TypeConfigurationAware, Serializable {
private static final Logger LOG = CoreLogging.logger( EnumType.class );
@ -90,20 +70,21 @@ public class EnumType<T extends Enum<T>>
public EnumType() {
}
@SuppressWarnings("unchecked")
public EnumType(
Class<T> enumClass,
EnumValueConverter enumValueConverter,
EnumValueConverter<T,?> enumValueConverter,
TypeConfiguration typeConfiguration) {
this.enumClass = enumClass;
this.typeConfiguration = typeConfiguration;
this.enumValueConverter = enumValueConverter;
this.enumValueConverter = (EnumValueConverter<T,Object>) enumValueConverter;
this.jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( enumValueConverter.getJdbcTypeCode() );
this.jdbcValueExtractor = jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() );
this.jdbcValueBinder = jdbcType.getBinder( enumValueConverter.getRelationalJavaType() );
this.jdbcValueExtractor = (ValueExtractor<Object>) jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() );
this.jdbcValueBinder = (ValueBinder<Object>) jdbcType.getBinder( enumValueConverter.getRelationalJavaType() );
}
public EnumValueConverter getEnumValueConverter() {
public EnumValueConverter<T, ?> getEnumValueConverter() {
return enumValueConverter;
}
@ -117,6 +98,32 @@ public class EnumType<T extends Enum<T>>
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
public void setParameterValues(Properties parameters) {
// 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.
final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
// the `reader != null` block handles annotations, while the `else` block
// handles hbm.xml
// the `reader != null` block handles annotations, while the `else` block handles hbm.xml
if ( reader != null ) {
enumClass = (Class<T>) reader.getReturnedClass().asSubclass( Enum.class );
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
);
}
configureUsingReader( reader );
}
else {
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 );
}
this.enumValueConverter = interpretParameters( parameters );
this.jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( enumValueConverter.getJdbcTypeCode() );
configureUsingParameters( parameters );
}
this.jdbcValueExtractor = jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() );
this.jdbcValueBinder = jdbcType.getBinder( enumValueConverter.getRelationalJavaType() );
jdbcValueExtractor = jdbcType.getExtractor( enumValueConverter.getRelationalJavaType() );
jdbcValueBinder = jdbcType.getBinder( enumValueConverter.getRelationalJavaType() );
if ( LOG.isDebugEnabled() ) {
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(
LocalJdbcTypeIndicators indicators,
EnumJavaType<?> enumJavaType) {
return enumJavaType.getRecommendedJdbcType( indicators ).getJdbcRecommendedJavaTypeMapping(
null,
null,
typeConfiguration
);
return enumJavaType.getRecommendedJdbcType( indicators )
.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
}
private jakarta.persistence.EnumType getEnumType(ParameterType reader) {
@ -234,20 +222,19 @@ public class EnumType<T extends Enum<T>>
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 ) {
if ( anClass.isInstance( annotation ) ) {
if ( annotationType.isInstance( annotation ) ) {
return (A) annotation;
}
}
return null;
}
private EnumValueConverter<T,Object> interpretParameters(Properties parameters) {
//noinspection rawtypes
final EnumJavaType enumJavaType = (EnumJavaType) typeConfiguration
.getJavaTypeRegistry()
.getDescriptor( enumClass );
private EnumValueConverter<T,?> interpretParameters(Properties parameters) {
JavaType<T> javaType = typeConfiguration.getJavaTypeRegistry().getDescriptor( enumClass );
final EnumJavaType<T> enumJavaType = (EnumJavaType<T>) javaType;
// this method should only be called for hbm.xml handling
assert parameters.get( PARAMETER_TYPE ) == null;
@ -263,66 +250,78 @@ public class EnumType<T extends Enum<T>>
-1L,
null
);
final BasicJavaType<?> stringJavaType = (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( String.class );
final BasicJavaType<?> integerJavaType = (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class );
if ( parameters.containsKey( NAMED ) ) {
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
if ( 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 )
);
}
return getConverter( enumJavaType, localIndicators, useNamed );
}
if ( parameters.containsKey( TYPE ) ) {
final int type = Integer.decode( (String) parameters.get( TYPE ) );
if ( isNumericType( 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
)
);
}
return getConverterForType( enumJavaType, localIndicators, type );
}
// the fallback
return new OrdinalEnumValueConverter(
return new OrdinalEnumValueConverter<>(
enumJavaType,
integerJavaType.getRecommendedJdbcType( localIndicators ),
typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class )
getIntegerType().getRecommendedJdbcType( localIndicators ),
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) {
switch ( jdbcTypeCode ) {
case Types.CHAR:
@ -377,8 +376,7 @@ public class EnumType<T extends Enum<T>>
@Override
public T nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
verifyConfigured();
final Object relational = jdbcValueExtractor.extract( rs, position, session );
return enumValueConverter.toDomainValue( relational );
return enumValueConverter.toDomainValue( jdbcValueExtractor.extract( rs, position, session ) );
}
private void verifyConfigured() {
@ -446,7 +444,7 @@ public class EnumType<T extends Enum<T>>
return enumValueConverter.toDomainValue( enumValueConverter.getRelationalJavaType().fromString( sequence ) );
}
@Override
@Override @SuppressWarnings("unchecked")
public String toLoggableString(Object value, SessionFactoryImplementor factory) {
verifyConfigured();
return enumValueConverter.getDomainJavaType().toString( (T) value );
@ -462,7 +460,11 @@ public class EnumType<T extends Enum<T>>
private final Long columnLength;
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.columnLength = columnLength;
this.reader = reader;

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.type;
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.IntegerJavaType;
@ -13,6 +14,7 @@ import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* 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() {
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;
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;
@ -13,28 +14,24 @@ import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.AttributeConverter;
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
*/
@Converter
public class TrueFalseConverter implements AttributeConverter<Boolean, Character>,
BasicValueConverter<Boolean, Character> {
public class TrueFalseConverter extends CharBooleanConverter {
/**
* Singleton access
*/
public static final TrueFalseConverter INSTANCE = new TrueFalseConverter();
private static final String[] VALUES = {"F", "T"};
@Override
public Character convertToDatabaseColumn(Boolean attribute) {
return toRelationalValue( attribute );
}
@Override
public Boolean convertToEntityAttribute(Character dbData) {
return toDomainValue( dbData );
protected String[] getValues() {
return VALUES;
}
@Override
@ -60,14 +57,4 @@ public class TrueFalseConverter implements AttributeConverter<Boolean, Character
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;
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;
@ -13,27 +14,24 @@ import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.AttributeConverter;
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
*/
@Converter
public class YesNoConverter implements AttributeConverter<Boolean, Character>, BasicValueConverter<Boolean, Character> {
public class YesNoConverter extends CharBooleanConverter {
/**
* Singleton access
*/
public static final YesNoConverter INSTANCE = new YesNoConverter();
private static final String[] VALUES = {"N", "Y"};
@Override
public Character convertToDatabaseColumn(Boolean attribute) {
return toRelationalValue( attribute );
}
@Override
public Boolean convertToEntityAttribute(Character dbData) {
return toDomainValue( dbData );
protected String[] getValues() {
return VALUES;
}
@Override
@ -60,14 +58,4 @@ public class YesNoConverter implements AttributeConverter<Boolean, Character>, B
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) {
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 jakarta.persistence.EnumType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
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() );
}
@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;
}
@FunctionalInterface
interface CoercionContext {
TypeConfiguration getTypeConfiguration();
}
@ -276,23 +277,6 @@ public interface JavaType<T> extends Serializable {
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}
* based on this {@link JavaType} registered for the raw type.

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.schematools;
import jakarta.persistence.Basic;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
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.SourceDescriptor;
import org.hibernate.tool.schema.spi.TargetDescriptor;
import org.hibernate.type.NumericBooleanConverter;
import org.hibernate.type.YesNoConverter;
import org.junit.jupiter.api.Test;
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;
@Enumerated(EnumType.STRING)
RetentionPolicy retentionPolicy;
@Convert(converter=YesNoConverter.class)
private boolean yesNo;
@Convert(converter= NumericBooleanConverter.class)
private boolean oneZero;
private SimpleEntity() {
// for use by Hibernate