HHH-17884 Respect ordering guarantee of ORDINAL/STRING when using native enums

This commit is contained in:
Christian Beikov 2024-03-27 18:06:16 +01:00
parent 7e29539153
commit 95641b2366
9 changed files with 115 additions and 9 deletions

View File

@ -12,11 +12,13 @@ import org.hibernate.engine.jdbc.Size;
import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.converter.internal.EnumHelper;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import java.sql.CallableStatement; import java.sql.CallableStatement;
@ -24,6 +26,9 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.Arrays;
import jakarta.persistence.EnumType;
import static java.util.Collections.emptySet; import static java.util.Collections.emptySet;
import static org.hibernate.type.SqlTypes.NAMED_ENUM; import static org.hibernate.type.SqlTypes.NAMED_ENUM;
@ -114,17 +119,40 @@ public class PostgreSQLEnumJdbcType implements JdbcType {
}; };
} }
@Override
public void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Size columnSize,
Database database,
JdbcTypeIndicators context) {
addAuxiliaryDatabaseObjects( javaType, database, context.getEnumeratedType() == EnumType.STRING );
}
@Override @Override
public void addAuxiliaryDatabaseObjects( public void addAuxiliaryDatabaseObjects(
JavaType<?> javaType, JavaType<?> javaType,
Size columnSize, Size columnSize,
Database database, Database database,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
addAuxiliaryDatabaseObjects( javaType, database, false );
}
private void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Database database,
boolean sortEnumValues) {
final Dialect dialect = database.getDialect(); final Dialect dialect = database.getDialect();
final Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) javaType.getJavaType(); final Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) javaType.getJavaType();
final String[] create = dialect.getCreateEnumTypeCommand( enumClass ); final String[] enumeratedValues = EnumHelper.getEnumeratedValues( enumClass );
if ( sortEnumValues ) {
Arrays.sort( enumeratedValues );
}
final String[] create = dialect.getCreateEnumTypeCommand(
javaType.getJavaTypeClass().getSimpleName(),
enumeratedValues
);
final String[] drop = dialect.getDropEnumTypeCommand( enumClass ); final String[] drop = dialect.getDropEnumTypeCommand( enumClass );
if ( create != null && create.length>0 ) { if ( create != null && create.length > 0 ) {
database.addAuxiliaryDatabaseObject( database.addAuxiliaryDatabaseObject(
new NamedAuxiliaryDatabaseObject( new NamedAuxiliaryDatabaseObject(
enumClass.getSimpleName(), enumClass.getSimpleName(),

View File

@ -369,7 +369,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
resolution.getRelationalJavaType(), resolution.getRelationalJavaType(),
size, size,
getBuildingContext().getMetadataCollector().getDatabase(), getBuildingContext().getMetadataCollector().getDatabase(),
getTypeConfiguration() this
); );
return resolution; return resolution;

View File

@ -59,6 +59,7 @@ public class EnumType<T extends Enum<T>>
private Class<T> enumClass; private Class<T> enumClass;
private boolean isOrdinal;
private JdbcType jdbcType; private JdbcType jdbcType;
private EnumJavaType<T> enumJavaType; private EnumJavaType<T> enumJavaType;
@ -131,6 +132,10 @@ public class EnumType<T extends Enum<T>>
if ( parameters.containsKey( TYPE ) ) { if ( parameters.containsKey( TYPE ) ) {
int jdbcTypeCode = Integer.parseInt( (String) parameters.get( TYPE ) ); int jdbcTypeCode = Integer.parseInt( (String) parameters.get( TYPE ) );
jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( jdbcTypeCode ); jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( jdbcTypeCode );
isOrdinal = jdbcType.isInteger()
// Both, ENUM and NAMED_ENUM are treated like ordinal with respect to the ordering
|| jdbcType.getDefaultSqlTypeCode() == SqlTypes.ENUM
|| jdbcType.getDefaultSqlTypeCode() == SqlTypes.NAMED_ENUM;
} }
else { else {
final LocalJdbcTypeIndicators indicators; final LocalJdbcTypeIndicators indicators;
@ -151,6 +156,7 @@ public class EnumType<T extends Enum<T>>
); );
} }
jdbcType = descriptor.getRecommendedJdbcType( indicators ); jdbcType = descriptor.getRecommendedJdbcType( indicators );
isOrdinal = indicators.getEnumeratedType() != STRING;
} }
if ( LOG.isDebugEnabled() ) { if ( LOG.isDebugEnabled() ) {
@ -295,7 +301,7 @@ public class EnumType<T extends Enum<T>>
public boolean isOrdinal() { public boolean isOrdinal() {
verifyConfigured(); verifyConfigured();
return jdbcType.isInteger(); return isOrdinal;
} }
private class LocalJdbcTypeIndicators implements JdbcTypeIndicators { private class LocalJdbcTypeIndicators implements JdbcTypeIndicators {

View File

@ -6,10 +6,34 @@
*/ */
package org.hibernate.type.descriptor.converter.internal; package org.hibernate.type.descriptor.converter.internal;
import java.util.Arrays;
import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/** /**
* @author Gavin King * @author Gavin King
*/ */
public class EnumHelper { public class EnumHelper {
public static String[] getEnumeratedValues(Type type) {
return getEnumeratedValues( type.getReturnedClass(), ( (BasicType<?>) type ).getJdbcType() );
}
public static String[] getEnumeratedValues(Class<?> javaType, JdbcType jdbcType) {
//noinspection unchecked
final Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) javaType;
final String[] enumValues;
if ( jdbcType.isString() ) {
enumValues = getSortedEnumeratedValues( enumClass );
}
else {
enumValues = getEnumeratedValues( enumClass );
}
return enumValues;
}
public static String[] getEnumeratedValues(Class<? extends Enum<?>> enumClass) { public static String[] getEnumeratedValues(Class<? extends Enum<?>> enumClass) {
Enum<?>[] values = enumClass.getEnumConstants(); Enum<?>[] values = enumClass.getEnumConstants();
String[] names = new String[values.length]; String[] names = new String[values.length];
@ -18,4 +42,10 @@ public class EnumHelper {
} }
return names; return names;
} }
public static String[] getSortedEnumeratedValues(Class<? extends Enum<?>> enumClass) {
final String[] names = getEnumeratedValues( enumClass );
Arrays.sort( names );
return names;
}
} }

View File

@ -19,6 +19,7 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import static jakarta.persistence.EnumType.ORDINAL; import static jakarta.persistence.EnumType.ORDINAL;
import static org.hibernate.type.SqlTypes.CHAR; import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.ENUM; import static org.hibernate.type.SqlTypes.ENUM;
import static org.hibernate.type.SqlTypes.NAMED_ENUM;
import static org.hibernate.type.SqlTypes.NCHAR; import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.SMALLINT; import static org.hibernate.type.SqlTypes.SMALLINT;
@ -42,12 +43,23 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
int sqlType; int sqlType;
switch ( type == null ? ORDINAL : type ) { switch ( type == null ? ORDINAL : type ) {
case ORDINAL: case ORDINAL:
sqlType = hasManyValues() ? SMALLINT : TINYINT; if ( jdbcTypeRegistry.hasRegisteredDescriptor( ENUM ) ) {
sqlType = ENUM;
}
else if ( jdbcTypeRegistry.hasRegisteredDescriptor( NAMED_ENUM ) ) {
sqlType = NAMED_ENUM;
}
else {
sqlType = hasManyValues() ? SMALLINT : TINYINT;
}
break; break;
case STRING: case STRING:
if ( jdbcTypeRegistry.hasRegisteredDescriptor( ENUM ) ) { if ( jdbcTypeRegistry.hasRegisteredDescriptor( ENUM ) ) {
sqlType = ENUM; sqlType = ENUM;
} }
else if ( jdbcTypeRegistry.hasRegisteredDescriptor( NAMED_ENUM ) ) {
sqlType = NAMED_ENUM;
}
else if ( context.getColumnLength() == 1 ) { else if ( context.getColumnLength() == 1 ) {
sqlType = context.isNationalized() ? NCHAR : CHAR; sqlType = context.isNationalized() ? NCHAR : CHAR;
} }

View File

@ -357,7 +357,11 @@ public interface JdbcType extends Serializable {
callableStatement.registerOutParameter( index, getJdbcTypeCode() ); callableStatement.registerOutParameter( index, getJdbcTypeCode() );
} }
/**
* @deprecated Use {@link #addAuxiliaryDatabaseObjects(JavaType, Size, Database, JdbcTypeIndicators)} instead
*/
@Incubating @Incubating
@Deprecated(forRemoval = true)
default void addAuxiliaryDatabaseObjects( default void addAuxiliaryDatabaseObjects(
JavaType<?> javaType, JavaType<?> javaType,
Size columnSize, Size columnSize,
@ -365,6 +369,20 @@ public interface JdbcType extends Serializable {
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
} }
/**
* Add auxiliary database objects for this {@linkplain JdbcType} to the {@link Database} object.
*
* @since 6.5
*/
@Incubating
default void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Size columnSize,
Database database,
JdbcTypeIndicators context) {
addAuxiliaryDatabaseObjects( javaType, columnSize, database, context.getTypeConfiguration() );
}
@Incubating @Incubating
default String getExtraCreateTableInfo(JavaType<?> javaType, String columnName, String tableName, Database database) { default String getExtraCreateTableInfo(JavaType<?> javaType, String columnName, String tableName, Database database) {
return ""; return "";

View File

@ -9,6 +9,7 @@ package org.hibernate.type.descriptor.sql.internal;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.Size;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.descriptor.converter.internal.EnumHelper;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.sql.DdlType; import org.hibernate.type.descriptor.sql.DdlType;
@ -40,7 +41,10 @@ public class NativeEnumDdlTypeImpl implements DdlType {
@Override @SuppressWarnings("unchecked") @Override @SuppressWarnings("unchecked")
public String getTypeName(Size columnSize, Type type, DdlTypeRegistry ddlTypeRegistry) { public String getTypeName(Size columnSize, Type type, DdlTypeRegistry ddlTypeRegistry) {
return dialect.getEnumTypeDeclaration( (Class<? extends Enum<?>>) type.getReturnedClass() ); return dialect.getEnumTypeDeclaration(
type.getReturnedClass().getSimpleName(),
EnumHelper.getEnumeratedValues( type )
);
} }
@Override @Override

View File

@ -36,6 +36,8 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.EnumType.STRING;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isOneOf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
@ -75,7 +77,10 @@ public class EnumeratedWithMappedSuperclassTest extends BaseUnitTestCase {
final Property natureProperty = addressLevelBinding.getProperty( "nature" ); final Property natureProperty = addressLevelBinding.getProperty( "nature" );
//noinspection unchecked //noinspection unchecked
BasicType<Nature> natureMapping = (BasicType<Nature>) natureProperty.getType(); BasicType<Nature> natureMapping = (BasicType<Nature>) natureProperty.getType();
assertEquals( SqlTypes.VARCHAR, natureMapping.getJdbcType().getJdbcTypeCode() ); assertThat(
natureMapping.getJdbcType().getJdbcTypeCode(),
isOneOf( SqlTypes.VARCHAR, SqlTypes.ENUM, SqlTypes.NAMED_ENUM )
);
try ( SessionFactoryImplementor sf = (SessionFactoryImplementor) metadata.buildSessionFactory() ) { try ( SessionFactoryImplementor sf = (SessionFactoryImplementor) metadata.buildSessionFactory() ) {
EntityPersister p = sf.getRuntimeMetamodels() EntityPersister p = sf.getRuntimeMetamodels()
@ -83,7 +88,10 @@ public class EnumeratedWithMappedSuperclassTest extends BaseUnitTestCase {
.getEntityDescriptor( AddressLevel.class.getName() ); .getEntityDescriptor( AddressLevel.class.getName() );
//noinspection unchecked //noinspection unchecked
BasicType<Nature> runtimeType = (BasicType<Nature>) p.getPropertyType( "nature" ); BasicType<Nature> runtimeType = (BasicType<Nature>) p.getPropertyType( "nature" );
assertEquals( SqlTypes.VARCHAR, runtimeType.getJdbcType().getJdbcTypeCode() ); assertThat(
runtimeType.getJdbcType().getJdbcTypeCode(),
isOneOf( SqlTypes.VARCHAR, SqlTypes.ENUM, SqlTypes.NAMED_ENUM )
);
} }
} }

View File

@ -77,7 +77,7 @@ public class ArrayAggregateTest {
new ArrayJavaType<>( javaTypeRegistry.getDescriptor( String.class ) ), new ArrayJavaType<>( javaTypeRegistry.getDescriptor( String.class ) ),
Size.nil(), Size.nil(),
metadata.getDatabase(), metadata.getDatabase(),
typeConfiguration typeConfiguration.getCurrentBaseSqlTypeIndicators()
); );
} }
} }