HHH-16125 attempt to support PostgreSQL enum types

This commit is contained in:
Gavin 2023-04-24 22:34:32 +02:00 committed by Gavin King
parent 1d7be9512a
commit fb9c007bdd
19 changed files with 480 additions and 121 deletions

View File

@ -77,7 +77,6 @@
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
import org.hibernate.type.descriptor.jdbc.NullJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
@ -796,7 +795,7 @@ public boolean supportsColumnCheck() {
}
@Override
public String getEnumTypeDeclaration(String[] values) {
public String getEnumTypeDeclaration(String name, String[] values) {
StringBuilder type = new StringBuilder();
type.append( "enum (" );
String separator = "";

View File

@ -15,16 +15,20 @@
import org.hibernate.mapping.BasicValue;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.ConvertedBasicType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.converter.internal.NamedEnumValueConverter;
import org.hibernate.type.descriptor.converter.internal.NativeEnumValueConverter;
import org.hibernate.type.descriptor.converter.internal.OrdinalEnumValueConverter;
import org.hibernate.type.descriptor.converter.spi.EnumValueConverter;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.ObjectJavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.NativeEnumJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
@ -32,9 +36,8 @@
import jakarta.persistence.EnumType;
import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.SqlTypes.isCharacterType;
/**
* Resolution for {@linkplain Enum enum} mappings using {@link jakarta.persistence.Enumerated},
@ -50,8 +53,7 @@ public class EnumeratedValueResolution<E extends Enum<E>,R> implements BasicValu
public EnumeratedValueResolution(
JdbcType jdbcType,
EnumValueConverter<E, R> valueConverter,
MetadataBuildingContext context) {
EnumValueConverter<E, R> valueConverter) {
this.valueConverter = valueConverter;
final String externalizableName = createName( valueConverter );
@ -70,7 +72,8 @@ private String createName(EnumValueConverter<E, R> valueConverter) {
}
private static EnumType enumStyle(EnumValueConverter<?,?> valueConverter) {
if ( valueConverter instanceof NamedEnumValueConverter ) {
if ( valueConverter instanceof NamedEnumValueConverter
|| valueConverter instanceof NativeEnumValueConverter ) {
return EnumType.STRING;
}
else if ( valueConverter instanceof OrdinalEnumValueConverter ) {
@ -132,35 +135,48 @@ public static <E extends Enum<E>> EnumeratedValueResolution<E,?> fromName(
final Class<E> enumClass = resolveEnumClass( parts[1], context.getBootstrapContext() );
final jakarta.persistence.EnumType style = jakarta.persistence.EnumType.valueOf( parts[ 2 ] );
//noinspection unchecked,rawtypes
@SuppressWarnings({"unchecked", "rawtypes"})
final EnumJavaType<E> enumJavaType = (EnumJavaType) javaTypeRegistry.getDescriptor( enumClass );
final JdbcType jdbcType;
final JdbcType jdbcType = enumJavaType.getRecommendedJdbcType( jdbcTypeIndicators );
final EnumValueConverter<E,?> converter;
if ( style == EnumType.ORDINAL ) {
jdbcType = jdbcTypeRegistry.getDescriptor( enumJavaType.hasManyValues() ? SMALLINT : TINYINT );
final JavaType<Integer> jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping(
jdbcTypeIndicators.getColumnPrecision(),
jdbcTypeIndicators.getColumnScale(),
typeConfiguration
);
converter = new OrdinalEnumValueConverter<>( enumJavaType, jdbcType, jdbcJavaType );
}
else if ( style == EnumType.STRING ) {
jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeIndicators.getColumnLength() == 1 ? CHAR : VARCHAR );
final JavaType<String> jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping(
jdbcTypeIndicators.getColumnPrecision(),
jdbcTypeIndicators.getColumnScale(),
typeConfiguration
);
converter = new NamedEnumValueConverter<>( enumJavaType, jdbcType, jdbcJavaType );
}
else {
throw new IllegalArgumentException( );
switch ( style ) {
case ORDINAL:
final JavaType<Integer> integerJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping(
jdbcTypeIndicators.getColumnPrecision(),
jdbcTypeIndicators.getColumnScale(),
typeConfiguration
);
converter = new OrdinalEnumValueConverter<>( enumJavaType, jdbcType, integerJavaType );
break;
case STRING:
if (jdbcType instanceof NativeEnumJdbcType) {
converter = new NativeEnumValueConverter<>( enumJavaType, jdbcType, new ObjectJavaType() {
@Override
public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
if ( String.class.equals(type) && value instanceof Enum) {
return (X) ((Enum<?>) value).name();
}
else {
return super.unwrap(value, type, options);
}
}
} );
}
else {
final JavaType<String> stringJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping(
jdbcTypeIndicators.getColumnPrecision(),
jdbcTypeIndicators.getColumnScale(),
typeConfiguration
);
converter = new NamedEnumValueConverter<>( enumJavaType, jdbcType, stringJavaType );
}
break;
default:
throw new IllegalArgumentException( );
}
return new EnumeratedValueResolution<>( jdbcType, converter, context );
return new EnumeratedValueResolution<>( jdbcType, converter );
}
private static <E extends Enum<E>> Class<E> resolveEnumClass(String enumClassName, BootstrapContext bootstrapContext) {

View File

@ -23,18 +23,23 @@
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.SerializableType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.converter.internal.NamedEnumValueConverter;
import org.hibernate.type.descriptor.converter.internal.NativeEnumValueConverter;
import org.hibernate.type.descriptor.converter.internal.OrdinalEnumValueConverter;
import org.hibernate.type.descriptor.converter.spi.EnumValueConverter;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.JavaTypeHelper;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.ObjectJavaType;
import org.hibernate.type.descriptor.java.SerializableJavaType;
import org.hibernate.type.descriptor.java.TemporalJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.NativeEnumJdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectJdbcType;
import org.hibernate.type.spi.TypeConfiguration;
@ -43,6 +48,7 @@
import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.isCharacterType;
/**
* BasicValue.Resolution resolver for cases where no explicit
@ -331,28 +337,6 @@ public static <E extends Enum<E>, R> EnumeratedValueResolution<E,R> fromEnum(
JdbcTypeIndicators stdIndicators,
MetadataBuildingContext context) {
final EnumType enumStyle = stdIndicators.getEnumeratedType();
if ( enumStyle == EnumType.STRING ) {
//noinspection unchecked
return (EnumeratedValueResolution<E, R>) stringEnumValueResolution(
enumJavaType,
explicitJavaType,
explicitJdbcType,
stdIndicators,
context
);
}
if ( enumStyle == EnumType.ORDINAL ) {
//noinspection unchecked
return (EnumeratedValueResolution<E, R>) ordinalEnumValueResolution(
enumJavaType,
(BasicJavaType<? extends Number>)explicitJavaType,
explicitJdbcType,
context
);
}
if ( enumStyle == null ) {
// NOTE : separate from the explicit ORDINAL check to facilitate
// handling native database enum types. In theory anyway - atm
@ -367,8 +351,27 @@ public static <E extends Enum<E>, R> EnumeratedValueResolution<E,R> fromEnum(
context
);
}
throw new MappingException( "Unknown enumeration-style (JPA EnumType) : " + enumStyle );
switch ( enumStyle ) {
case STRING:
//noinspection unchecked
return (EnumeratedValueResolution<E, R>) stringEnumValueResolution(
enumJavaType,
explicitJavaType,
explicitJdbcType,
stdIndicators,
context
);
case ORDINAL:
//noinspection unchecked
return (EnumeratedValueResolution<E, R>) ordinalEnumValueResolution(
enumJavaType,
(BasicJavaType<? extends Number>)explicitJavaType,
explicitJdbcType,
context
);
default:
throw new MappingException( "Unknown enumeration-style (JPA EnumType) : " + enumStyle );
}
}
private static <E extends Enum<E>, N extends Number> EnumeratedValueResolution<E,N> ordinalEnumValueResolution(
@ -378,11 +381,9 @@ private static <E extends Enum<E>, N extends Number> EnumeratedValueResolution<E
MetadataBuildingContext context) {
final JdbcType jdbcType = ordinalJdbcType( explicitJdbcType, enumJavaType, context );
final JavaType<N> relationalJavaType = ordinalJavaType( explicitJavaType, jdbcType, context );
return new EnumeratedValueResolution<>(
jdbcType,
new OrdinalEnumValueConverter<>( enumJavaType, jdbcType, relationalJavaType ),
context
new OrdinalEnumValueConverter<>( enumJavaType, jdbcType, relationalJavaType )
);
}
@ -418,7 +419,7 @@ private static <N extends Number> JavaType<N> ordinalJavaType(
}
}
private static <E extends Enum<E>> EnumeratedValueResolution<E,String> stringEnumValueResolution(
private static <E extends Enum<E>> EnumeratedValueResolution<E,?> stringEnumValueResolution(
EnumJavaType<E> enumJavaType,
BasicJavaType<?> explicitJavaType,
JdbcType explicitJdbcType,
@ -427,19 +428,28 @@ private static <E extends Enum<E>> EnumeratedValueResolution<E,String> stringEnu
final JdbcType jdbcType = explicitJdbcType == null
? enumJavaType.getRecommendedJdbcType( stdIndicators )
: explicitJdbcType;
final JavaType<String> relationalJtd = stringJavaType( explicitJavaType, stdIndicators, context );
return new EnumeratedValueResolution<>(
jdbcType,
new NamedEnumValueConverter<>( enumJavaType, jdbcType, relationalJtd ),
context
);
}
private static JdbcType stringJdbcType(JdbcType explicitJdbcType, JdbcTypeIndicators stdIndicators, JavaType<String> relationalJtd) {
return explicitJdbcType != null
? explicitJdbcType
: relationalJtd.getRecommendedJdbcType( stdIndicators );
final EnumValueConverter<E,?> converter;
if ( jdbcType instanceof NativeEnumJdbcType ) {
converter = new NativeEnumValueConverter<>( enumJavaType, jdbcType, new ObjectJavaType() {
@Override
public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
if ( String.class.equals(type) && value instanceof Enum) {
return (X) ((Enum<?>) value).name();
}
else {
return super.unwrap(value, type, options);
}
}
} );
}
else {
converter = new NamedEnumValueConverter<>(
enumJavaType,
jdbcType,
stringJavaType( explicitJavaType, stdIndicators, context )
);
}
return new EnumeratedValueResolution<>( jdbcType, converter );
}
private static JavaType<String> stringJavaType(

View File

@ -18,6 +18,27 @@ public class NamedAuxiliaryDatabaseObject
implements Exportable {
private final String name;
public NamedAuxiliaryDatabaseObject(
String name,
Namespace namespace,
String[] createStrings,
String[] dropStrings,
Set<String> dialectScopes) {
super( namespace, createStrings, dropStrings, dialectScopes );
this.name = name;
}
public NamedAuxiliaryDatabaseObject(
String name,
Namespace namespace,
String[] createStrings,
String[] dropStrings,
Set<String> dialectScopes,
boolean beforeTables) {
super( namespace, createStrings, dropStrings, dialectScopes, beforeTables );
this.name = name;
}
public NamedAuxiliaryDatabaseObject(
String name,
Namespace namespace,
@ -28,6 +49,17 @@ public NamedAuxiliaryDatabaseObject(
this.name = name;
}
public NamedAuxiliaryDatabaseObject(
String name,
Namespace namespace,
String createString,
String dropString,
Set<String> dialectScopes,
boolean beforeTables) {
super( namespace, createString, dropString, dialectScopes, beforeTables );
this.name = name;
}
@Override
public String getExportIdentifier() {
return new QualifiedNameImpl(

View File

@ -30,6 +30,41 @@ public class SimpleAuxiliaryDatabaseObject extends AbstractAuxiliaryDatabaseObje
private final String[] createStrings;
private final String[] dropStrings;
private static String extractName(Identifier identifier) {
return identifier == null ? null : identifier.getText();
}
public SimpleAuxiliaryDatabaseObject(
Namespace namespace,
String createString,
String dropString,
Set<String> dialectScopes,
boolean beforeTables) {
this(
namespace,
new String[] { createString },
new String[] { dropString },
dialectScopes,
beforeTables
);
}
public SimpleAuxiliaryDatabaseObject(
Namespace namespace,
String[] createStrings,
String[] dropStrings,
Set<String> dialectScopes,
boolean beforeTables) {
this(
dialectScopes,
extractName( namespace.getPhysicalName().getCatalog() ),
extractName( namespace.getPhysicalName().getSchema() ),
createStrings,
dropStrings,
beforeTables
);
}
public SimpleAuxiliaryDatabaseObject(
Namespace namespace,
String createString,
@ -57,8 +92,18 @@ public SimpleAuxiliaryDatabaseObject(
);
}
private static String extractName(Identifier identifier) {
return identifier == null ? null : identifier.getText();
public SimpleAuxiliaryDatabaseObject(
Set<String> dialectScopes,
String catalogName,
String schemaName,
String[] createStrings,
String[] dropStrings,
boolean beforeTables) {
super( beforeTables, dialectScopes );
this.catalogName = catalogName;
this.schemaName = schemaName;
this.createStrings = createStrings;
this.dropStrings = dropStrings;
}
public SimpleAuxiliaryDatabaseObject(

View File

@ -199,6 +199,7 @@
import static java.lang.Math.ceil;
import static java.lang.Math.log;
import static java.util.Arrays.sort;
import static org.hibernate.cfg.AvailableSettings.NON_CONTEXTUAL_LOB_CREATION;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_BATCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS;
@ -247,6 +248,7 @@
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos;
import static org.hibernate.type.descriptor.converter.internal.EnumHelper.getEnumeratedValues;
/**
* Represents a dialect of SQL implemented by a particular RDBMS. Every
@ -736,6 +738,10 @@ public int resolveSqlTypeLength(
}
}
public boolean hasNativeEnums() {
return false;
}
/**
* If this database has a special MySQL-style {@code enum} column type,
* return the type declaration for the given enumeration of values.
@ -745,10 +751,26 @@ public int resolveSqlTypeLength(
* @param values the enumerated values of the type
* @return the DDL column type declaration
*/
public String getEnumTypeDeclaration(String[] values) {
public String getEnumTypeDeclaration(String name, String[] values) {
return null;
}
public String getEnumTypeDeclaration(Class<? extends Enum<?>> enumType) {
String[] values = getEnumeratedValues( enumType );
sort( values ); //sort alphabetically, to guarantee alphabetical ordering in queries with 'order by'
return getEnumTypeDeclaration( enumType.getSimpleName(), values );
}
public String[] getCreateEnumTypeCommand(String name, String[] values) {
return null;
}
public String[] getCreateEnumTypeCommand(Class<? extends Enum<?>> enumType) {
String[] values = getEnumeratedValues( enumType );
sort( values ); //sort alphabetically, to guarantee alphabetical ordering in queries with 'order by'
return getCreateEnumTypeCommand( enumType.getSimpleName(), values );
}
/**
* Render a SQL check condition for a column that represents an enumerated value
* by its {@linkplain jakarta.persistence.EnumType#STRING string representation}.
@ -766,6 +788,10 @@ public String getCheckCondition(String columnName, String[] values) {
return check.append( ')' ).toString();
}
public String getCheckCondition(String columnName, Class<? extends Enum<?>> enumType) {
return getCheckCondition( columnName, getEnumeratedValues( enumType ) );
}
/**
* Render a SQL check condition for a column that represents an enumerated value.
* by its {@linkplain jakarta.persistence.EnumType#ORDINAL ordinal representation}.

View File

@ -217,8 +217,8 @@ public int resolveSqlTypeLength(
}
@Override
public String getEnumTypeDeclaration(String[] values) {
return wrapped.getEnumTypeDeclaration( values );
public String getEnumTypeDeclaration(String name, String[] values) {
return wrapped.getEnumTypeDeclaration( name, values );
}
@Override

View File

@ -870,7 +870,7 @@ public boolean supportsColumnCheck() {
}
@Override
public String getEnumTypeDeclaration(String[] values) {
public String getEnumTypeDeclaration(String name, String[] values) {
StringBuilder type = new StringBuilder();
type.append( "enum (" );
String separator = "";

View File

@ -384,6 +384,43 @@ protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration ty
return super.resolveSqlTypeCode( columnTypeName, typeConfiguration );
}
@Override
public boolean hasNativeEnums() {
return true;
}
@Override
public String getEnumTypeDeclaration(String name, String[] values) {
return name;
}
@Override
public String getCheckCondition(String columnName, Class<? extends Enum<?>> enumType) {
return null;
}
public String[] getCreateEnumTypeCommand(String name, String[] values) {
StringBuilder type = new StringBuilder();
type.append( "create type " )
.append( name )
.append( " as enum (" );
String separator = "";
for ( String value : values ) {
type.append( separator ).append('\'').append( value ).append('\'');
separator = ",";
}
type.append( ')' );
StringBuilder cast1 = new StringBuilder();
cast1.append("create cast (varchar as " )
.append( name )
.append( ") with inout as implicit" );
StringBuilder cast2 = new StringBuilder();
cast2.append("create cast (" )
.append( name )
.append( " as varchar) with inout as implicit" );
return new String[] { type.toString(), cast1.toString(), cast2.toString() };
}
@Override
public String currentTime() {
return "localtime";

View File

@ -27,6 +27,8 @@
import org.hibernate.boot.model.process.internal.NamedConverterResolution;
import org.hibernate.boot.model.process.internal.UserTypeResolution;
import org.hibernate.boot.model.process.internal.VersionResolution;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
@ -318,6 +320,19 @@ public Resolution<?> resolve() {
resolveColumn( (Column) selectable, getDialect() );
}
Database database = getBuildingContext().getMetadataCollector().getDatabase();
BasicValueConverter<?, ?> valueConverter = resolution.getValueConverter();
if ( valueConverter != null ) {
AuxiliaryDatabaseObject udt = valueConverter.getAuxiliaryDatabaseObject(
resolution.getJdbcType(),
database.getDialect(),
database.getDefaultNamespace()
);
if ( udt != null ) {
database.addAuxiliaryDatabaseObject( udt );
}
}
return resolution;
}
@ -333,7 +348,7 @@ private void resolveColumn(Column column, Dialect dialect) {
}
if ( resolution.getValueConverter() != null ) {
final String declaration = resolution.getLegacyResolvedBasicType().getSpecializedTypeDeclaration(dialect);
final String declaration = resolution.getLegacyResolvedBasicType().getSpecializedTypeDeclaration( dialect );
if ( declaration != null ) {
column.setSpecializedTypeDeclaration( declaration );
}

View File

@ -63,6 +63,7 @@
import jakarta.persistence.AttributeConverter;
import static org.hibernate.boot.model.convert.spi.ConverterDescriptor.TYPE_NAME_PREFIX;
import static org.hibernate.id.factory.internal.IdentifierGeneratorUtil.createLegacyIdentifierGenerator;
import static org.hibernate.internal.util.collections.ArrayHelper.toBooleanArray;
@ -208,7 +209,7 @@ protected void justAddColumn(Column column) {
protected void justAddColumn(Column column, boolean insertable, boolean updatable) {
int index = columns.indexOf( column );
if ( index == -1 ) {
columns.add(column);
columns.add( column );
insertability.add( insertable );
updatability.add( updatable );
}
@ -295,8 +296,8 @@ public String getTypeName() {
}
public void setTypeName(String typeName) {
if ( typeName != null && typeName.startsWith( ConverterDescriptor.TYPE_NAME_PREFIX ) ) {
final String converterClassName = typeName.substring( ConverterDescriptor.TYPE_NAME_PREFIX.length() );
if ( typeName != null && typeName.startsWith( TYPE_NAME_PREFIX ) ) {
final String converterClassName = typeName.substring( TYPE_NAME_PREFIX.length() );
final ClassLoaderService cls = getMetadata()
.getMetadataBuildingOptions()
.getServiceRegistry()
@ -771,7 +772,7 @@ public Dialect getDialect() {
// todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times.
return new ConvertedBasicTypeImpl<>(
ConverterDescriptor.TYPE_NAME_PREFIX
TYPE_NAME_PREFIX
+ jpaAttributeConverter.getConverterJavaType().getJavaType().getTypeName(),
String.format(
"BasicType adapter for AttributeConverter<%s,%s>",
@ -822,11 +823,11 @@ protected static boolean isSame(Value v1, Value v2) {
public boolean isSame(SimpleValue other) {
return Objects.equals( columns, other.columns )
&& Objects.equals( typeName, other.typeName )
&& Objects.equals( typeParameters, other.typeParameters )
&& Objects.equals( table, other.table )
&& Objects.equals( foreignKeyName, other.foreignKeyName )
&& Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition );
&& Objects.equals( typeName, other.typeName )
&& Objects.equals( typeParameters, other.typeParameters )
&& Objects.equals( table, other.table )
&& Objects.equals( foreignKeyName, other.foreignKeyName )
&& Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition );
}
@Override

View File

@ -151,23 +151,27 @@ private static void appendColumnDefinition(
Table table,
Metadata metadata,
Dialect dialect) {
final String columnType = column.getSqlType(metadata);
if ( isIdentityColumn(column, table, metadata, dialect) ) {
if ( isIdentityColumn( column, table, metadata, dialect) ) {
// to support dialects that have their own identity data type
if ( dialect.getIdentityColumnSupport().hasDataTypeInIdentityColumn() ) {
definition.append( ' ' ).append( columnType );
definition.append( ' ' ).append( column.getSqlType( metadata ) );
}
final String identityColumnString = dialect.getIdentityColumnSupport()
.getIdentityColumnString( column.getSqlTypeCode(metadata) );
.getIdentityColumnString( column.getSqlTypeCode( metadata ) );
definition.append( ' ' ).append( identityColumnString );
}
else {
final String columnType;
if ( column.hasSpecializedTypeDeclaration() ) {
definition.append( ' ' ).append( column.getSpecializedTypeDeclaration() );
}
else if ( column.getGeneratedAs() == null || dialect.hasDataTypeBeforeGeneratedAs() ) {
columnType = column.getSpecializedTypeDeclaration();
definition.append( ' ' ).append( columnType );
}
else {
columnType = column.getSqlType( metadata );
if ( column.getGeneratedAs() == null || dialect.hasDataTypeBeforeGeneratedAs() ) {
definition.append( ' ' ).append( columnType );
}
}
final String defaultValue = column.getDefaultValue();
if ( defaultValue != null ) {

View File

@ -52,7 +52,7 @@ public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect di
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return dialect.getEnumTypeDeclaration( getValues() );
return dialect.getEnumTypeDeclaration( null, getValues() );
}
protected abstract String[] getValues();

View File

@ -7,16 +7,17 @@
package org.hibernate.type.descriptor.converter.internal;
import java.io.Serializable;
import java.util.Locale;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.converter.spi.EnumValueConverter;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import static java.util.Arrays.sort;
import static org.hibernate.type.descriptor.converter.internal.EnumHelper.getEnumeratedValues;
import static java.util.Collections.emptySet;
/**
* BasicValueConverter handling the conversion of an enum based on
@ -65,19 +66,30 @@ public int getJdbcTypeCode() {
@Override
public String toSqlLiteral(Object value) {
//noinspection rawtypes
return String.format( Locale.ROOT, "'%s'", ( (Enum) value ).name() );
return "'" + ( (Enum<?>) value ).name() + "'";
}
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return dialect.getCheckCondition( columnName, getEnumeratedValues( getDomainJavaType().getJavaTypeClass() ) );
return dialect.getCheckCondition( columnName, getDomainJavaType().getJavaTypeClass() );
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
String[] values = getEnumeratedValues( getDomainJavaType().getJavaTypeClass() );
sort( values ); //sort alphabetically, to guarantee alphabetical ordering in queries with 'order by'
return dialect.getEnumTypeDeclaration( values );
return dialect.getEnumTypeDeclaration( getDomainJavaType().getJavaTypeClass() );
}
@Override
public AuxiliaryDatabaseObject getAuxiliaryDatabaseObject(JdbcType jdbcType, Dialect dialect, Namespace defaultNamespace) {
Class<? extends Enum<?>> enumClass = getDomainJavaType().getJavaTypeClass();
String name = enumClass.getSimpleName();
String[] create = dialect.getCreateEnumTypeCommand( enumClass );
if ( create != null ) {
String[] drop = new String[] { "drop type " + name + " cascade" }; //TODO: move to Dialect
return new NamedAuxiliaryDatabaseObject( name, defaultNamespace, create, drop, emptySet(), true );
}
else {
return null;
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.type.descriptor.converter.internal;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.converter.spi.EnumValueConverter;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import java.io.Serializable;
import static java.util.Collections.emptySet;
/**
* BasicValueConverter handling the conversion of an enum based on
* JPA {@link jakarta.persistence.EnumType#STRING} strategy (storing the name)
*
* @author Steve Ebersole
*/
public class NativeEnumValueConverter<E extends Enum<E>> implements EnumValueConverter<E,Object>, Serializable {
private final EnumJavaType<E> domainTypeDescriptor;
private final JdbcType jdbcType;
private final JavaType<Object> relationalTypeDescriptor;
public NativeEnumValueConverter(
EnumJavaType<E> domainTypeDescriptor,
JdbcType jdbcType,
JavaType<Object> relationalTypeDescriptor) {
this.domainTypeDescriptor = domainTypeDescriptor;
this.jdbcType = jdbcType;
this.relationalTypeDescriptor = relationalTypeDescriptor;
}
@Override
public EnumJavaType<E> getDomainJavaType() {
return domainTypeDescriptor;
}
@Override
public JavaType<Object> getRelationalJavaType() {
return relationalTypeDescriptor;
}
@Override
public E toDomainValue(Object relationalForm) {
return relationalForm instanceof String
? domainTypeDescriptor.fromName( (String) relationalForm )
: (E) relationalForm;
}
@Override
public Object toRelationalValue(E domainForm) {
return domainForm;
}
@Override
public int getJdbcTypeCode() {
return jdbcType.getDefaultSqlTypeCode();
}
@Override
public String toSqlLiteral(Object value) {
return "'" + ( (Enum<?>) value ).name() + "'";
}
@Override
public String getCheckCondition(String columnName, JdbcType jdbcType, Dialect dialect) {
return dialect.getCheckCondition( columnName, getDomainJavaType().getJavaTypeClass() );
}
@Override
public String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return dialect.getEnumTypeDeclaration( getDomainJavaType().getJavaTypeClass() );
}
@Override
public AuxiliaryDatabaseObject getAuxiliaryDatabaseObject(JdbcType jdbcType, Dialect dialect, Namespace defaultNamespace) {
Class<? extends Enum<?>> enumClass = getDomainJavaType().getJavaTypeClass();
String name = enumClass.getSimpleName();
String[] create = dialect.getCreateEnumTypeCommand( enumClass );
if ( create != null ) {
String[] drop = new String[] { "drop type " + name + " cascade" }; //TODO: move to Dialect
return new NamedAuxiliaryDatabaseObject( name, defaultNamespace, create, drop, emptySet(), true );
}
else {
return null;
}
}
}

View File

@ -62,8 +62,7 @@ public JavaType<N> getRelationalJavaType() {
@Override
public String toSqlLiteral(Object value) {
//noinspection rawtypes
return Integer.toString( ( (Enum) value ).ordinal() );
return Integer.toString( ( (Enum<?>) value ).ordinal() );
}
@Override

View File

@ -7,6 +7,8 @@
package org.hibernate.type.descriptor.converter.spi;
import org.hibernate.Incubating;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -66,4 +68,9 @@ default String getCheckCondition(String columnName, JdbcType sqlType, Dialect di
default String getSpecializedTypeDeclaration(JdbcType jdbcType, Dialect dialect) {
return null;
}
@Incubating
default AuxiliaryDatabaseObject getAuxiliaryDatabaseObject(JdbcType jdbcType, Dialect dialect, Namespace defaultNamespace) {
return null;
}
}

View File

@ -12,6 +12,7 @@
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.NativeEnumJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import static jakarta.persistence.EnumType.ORDINAL;
@ -36,24 +37,26 @@ public EnumJavaType(Class<T> type) {
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
final JdbcTypeRegistry registry = context.getTypeConfiguration().getJdbcTypeRegistry();
final EnumType type = context.getEnumeratedType();
int sqlType;
switch ( type == null ? ORDINAL : type ) {
case ORDINAL: {
return registry.getDescriptor( hasManyValues() ? SMALLINT : TINYINT );
}
case STRING: {
if ( context.getColumnLength() == 1 ) {
return context.isNationalized()
? registry.getDescriptor( NCHAR )
: registry.getDescriptor( CHAR );
case ORDINAL:
sqlType = hasManyValues() ? SMALLINT : TINYINT;
break;
case STRING:
if ( context.getDialect().hasNativeEnums() ) {
return NativeEnumJdbcType.INSTANCE;
}
return context.isNationalized()
? registry.getDescriptor( NVARCHAR )
: registry.getDescriptor( VARCHAR );
}
default: {
else if ( context.getColumnLength() == 1 ) {
sqlType = context.isNationalized() ? NCHAR : CHAR;
}
else {
sqlType = context.isNationalized() ? NVARCHAR : VARCHAR;
}
break;
default:
throw new AssertionFailure("unknown EnumType");
}
}
return registry.getDescriptor( sqlType );
}
public boolean hasManyValues() {
@ -250,4 +253,5 @@ public boolean isWider(JavaType<?> javaType) {
return false;
}
}
}

View File

@ -0,0 +1,55 @@
package org.hibernate.type.descriptor.jdbc;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class NativeEnumJdbcType extends VarcharJdbcType {
public static final NativeEnumJdbcType INSTANCE = new NativeEnumJdbcType();
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setObject( index, value, SqlTypes.OTHER );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setObject( name, value, SqlTypes.OTHER );
}
};
}
@Override
@SuppressWarnings("unchecked")
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return (X) rs.getObject( paramIndex );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return (X) statement.getObject( index );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return (X) statement.getObject( name );
}
};
}
}