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.ValueExtractor;
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.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
import java.sql.CallableStatement;
@ -24,6 +26,9 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import jakarta.persistence.EnumType;
import static java.util.Collections.emptySet;
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
public void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Size columnSize,
Database database,
TypeConfiguration typeConfiguration) {
addAuxiliaryDatabaseObjects( javaType, database, false );
}
private void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Database database,
boolean sortEnumValues) {
final Dialect dialect = database.getDialect();
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 );
if ( create != null && create.length>0 ) {
if ( create != null && create.length > 0 ) {
database.addAuxiliaryDatabaseObject(
new NamedAuxiliaryDatabaseObject(
enumClass.getSimpleName(),

View File

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

View File

@ -59,6 +59,7 @@ public class EnumType<T extends Enum<T>>
private Class<T> enumClass;
private boolean isOrdinal;
private JdbcType jdbcType;
private EnumJavaType<T> enumJavaType;
@ -131,6 +132,10 @@ public class EnumType<T extends Enum<T>>
if ( parameters.containsKey( TYPE ) ) {
int jdbcTypeCode = Integer.parseInt( (String) parameters.get( TYPE ) );
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 {
final LocalJdbcTypeIndicators indicators;
@ -151,6 +156,7 @@ public class EnumType<T extends Enum<T>>
);
}
jdbcType = descriptor.getRecommendedJdbcType( indicators );
isOrdinal = indicators.getEnumeratedType() != STRING;
}
if ( LOG.isDebugEnabled() ) {
@ -295,7 +301,7 @@ public class EnumType<T extends Enum<T>>
public boolean isOrdinal() {
verifyConfigured();
return jdbcType.isInteger();
return isOrdinal;
}
private class LocalJdbcTypeIndicators implements JdbcTypeIndicators {

View File

@ -6,10 +6,34 @@
*/
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
*/
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) {
Enum<?>[] values = enumClass.getEnumConstants();
String[] names = new String[values.length];
@ -18,4 +42,10 @@ public class EnumHelper {
}
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 org.hibernate.type.SqlTypes.CHAR;
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.NVARCHAR;
import static org.hibernate.type.SqlTypes.SMALLINT;
@ -42,12 +43,23 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
int sqlType;
switch ( type == null ? ORDINAL : type ) {
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;
case STRING:
if ( jdbcTypeRegistry.hasRegisteredDescriptor( ENUM ) ) {
sqlType = ENUM;
}
else if ( jdbcTypeRegistry.hasRegisteredDescriptor( NAMED_ENUM ) ) {
sqlType = NAMED_ENUM;
}
else if ( context.getColumnLength() == 1 ) {
sqlType = context.isNationalized() ? NCHAR : CHAR;
}

View File

@ -357,7 +357,11 @@ public interface JdbcType extends Serializable {
callableStatement.registerOutParameter( index, getJdbcTypeCode() );
}
/**
* @deprecated Use {@link #addAuxiliaryDatabaseObjects(JavaType, Size, Database, JdbcTypeIndicators)} instead
*/
@Incubating
@Deprecated(forRemoval = true)
default void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Size columnSize,
@ -365,6 +369,20 @@ public interface JdbcType extends Serializable {
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
default String getExtraCreateTableInfo(JavaType<?> javaType, String columnName, String tableName, Database database) {
return "";

View File

@ -9,6 +9,7 @@ package org.hibernate.type.descriptor.sql.internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
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.jdbc.JdbcType;
import org.hibernate.type.descriptor.sql.DdlType;
@ -40,7 +41,10 @@ public class NativeEnumDdlTypeImpl implements DdlType {
@Override @SuppressWarnings("unchecked")
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

View File

@ -36,6 +36,8 @@ import org.junit.Before;
import org.junit.Test;
import static jakarta.persistence.EnumType.STRING;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isOneOf;
import static org.junit.Assert.assertEquals;
/**
@ -75,7 +77,10 @@ public class EnumeratedWithMappedSuperclassTest extends BaseUnitTestCase {
final Property natureProperty = addressLevelBinding.getProperty( "nature" );
//noinspection unchecked
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() ) {
EntityPersister p = sf.getRuntimeMetamodels()
@ -83,7 +88,10 @@ public class EnumeratedWithMappedSuperclassTest extends BaseUnitTestCase {
.getEntityDescriptor( AddressLevel.class.getName() );
//noinspection unchecked
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 ) ),
Size.nil(),
metadata.getDatabase(),
typeConfiguration
typeConfiguration.getCurrentBaseSqlTypeIndicators()
);
}
}