HHH-15898 Allow explicit configuration of the UDT column order

This commit is contained in:
Christian Beikov 2022-12-21 18:59:46 +01:00
parent 7250449aaa
commit 4e03f320fb
7 changed files with 111 additions and 33 deletions

View File

@ -47,4 +47,12 @@ public @interface Struct {
* The name of the UDT (user defined type).
*/
String name();
/**
* The ordered set of attributes of the UDT, as they appear physically in the DDL.
* It is important to specify the attributes in the same order for JDBC interactions to work correctly.
* If the annotated type is a record, the order of record components is used as the default order.
* If no default order can be inferred, attributes are assumed to be in alphabetical order.
*/
String[] attributes() default {};
}

View File

@ -55,6 +55,7 @@ public final class AggregateComponentBinder {
() -> new EmbeddableAggregateJavaType<>( component.getComponentClass(), structName )
);
component.setStructName( structName );
component.setStructColumnNames( determineStructAttributeNames( inferredData, returnedClassOrElement ) );
// Determine the aggregate column
BasicValueBinder basicValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, component, context );
@ -135,6 +136,21 @@ public final class AggregateComponentBinder {
return null;
}
private static String[] determineStructAttributeNames(PropertyData inferredData, XClass returnedClassOrElement) {
final XProperty property = inferredData.getProperty();
if ( property != null ) {
final Struct struct = property.getAnnotation( Struct.class );
if ( struct != null ) {
return struct.attributes();
}
}
final Struct struct = returnedClassOrElement.getAnnotation( Struct.class );
if ( struct != null ) {
return struct.attributes();
}
return null;
}
private static boolean isAggregate(XProperty property, XClass returnedClass) {
if ( property != null ) {
final Struct struct = property.getAnnotation( Struct.class );

View File

@ -31,6 +31,7 @@ import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.UserDefinedType;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.internal.EmbeddableHelper;
@ -189,41 +190,57 @@ public class AggregateComponentSecondPass implements SecondPass {
private void orderColumns(UserDefinedType userDefinedType) {
final Class<?> componentClass = component.getComponentClass();
final int[] originalOrder = component.sortProperties();
final int[] propertyMappingIndex;
if ( ReflectHelper.isRecord( componentClass ) ) {
if ( originalOrder == null ) {
propertyMappingIndex = null;
final String[] structColumnNames = component.getStructColumnNames();
if ( structColumnNames == null || structColumnNames.length == 0 ) {
final int[] propertyMappingIndex;
if ( ReflectHelper.isRecord( componentClass ) ) {
if ( originalOrder == null ) {
propertyMappingIndex = null;
}
else {
final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass );
propertyMappingIndex = EmbeddableHelper.determineMappingIndex(
component.getPropertyNames(),
componentNames
);
}
}
else {
final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass );
propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex(
else if ( component.getInstantiatorPropertyNames() != null ) {
propertyMappingIndex = EmbeddableHelper.determineMappingIndex(
component.getPropertyNames(),
componentNames
component.getInstantiatorPropertyNames()
);
}
}
else {
if ( component.getInstantiatorPropertyNames() == null ) {
return;
else {
propertyMappingIndex = null;
}
propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex(
component.getPropertyNames(),
component.getInstantiatorPropertyNames()
);
}
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
final List<Property> properties = component.getProperties();
if ( propertyMappingIndex == null ) {
for ( Property property : properties ) {
addColumns( orderedColumns, property.getValue() );
if ( propertyMappingIndex == null ) {
// If there is default ordering possible, assume alphabetical ordering
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
final List<Property> properties = component.getProperties();
for ( Property property : properties ) {
addColumns( orderedColumns, property.getValue() );
}
userDefinedType.reorderColumns( orderedColumns );
}
else {
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
final List<Property> properties = component.getProperties();
for ( final int propertyIndex : propertyMappingIndex ) {
addColumns( orderedColumns, properties.get( propertyIndex ).getValue() );
}
userDefinedType.reorderColumns( orderedColumns );
}
}
else {
for ( final int propertyIndex : propertyMappingIndex ) {
addColumns( orderedColumns, properties.get( propertyIndex ).getValue() );
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
for ( String structColumnName : structColumnNames ) {
if ( !addColumns( orderedColumns, component, structColumnName ) ) {
throw new MappingException( "Couldn't find column [" + structColumnName + "] that was defined in @Struct(attributes) in the component [" + component.getComponentClassName() + "]" );
}
}
userDefinedType.reorderColumns( orderedColumns );
}
userDefinedType.reorderColumns( orderedColumns );
}
private static void addColumns(ArrayList<Column> orderedColumns, Value value) {
@ -243,6 +260,33 @@ public class AggregateComponentSecondPass implements SecondPass {
}
}
private static boolean addColumns(ArrayList<Column> orderedColumns, Component component, String structColumnName) {
for ( Property property : component.getProperties() ) {
final Value value = property.getValue();
if ( value instanceof Component ) {
final Component subComponent = (Component) value;
if ( subComponent.getAggregateColumn() == null ) {
if ( addColumns( orderedColumns, subComponent, structColumnName ) ) {
return true;
}
}
else if ( structColumnName.equals( subComponent.getAggregateColumn().getName() ) ) {
orderedColumns.add( subComponent.getAggregateColumn() );
return true;
}
}
else {
for ( Selectable selectable : value.getSelectables() ) {
if ( selectable instanceof Column && structColumnName.equals( ( (Column) selectable ).getName() ) ) {
orderedColumns.add( (Column) selectable );
return true;
}
}
}
}
return false;
}
private void validateSupportedColumnTypes(String basePath, Component component) {
for ( Property property : component.getProperties() ) {
final Value value = property.getValue();

View File

@ -178,7 +178,7 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
}
assert end == string.length();
if ( returnEmbeddable ) {
final Object[] attributeValues = getAttributeValues( embeddableMappingType, array, options );
final Object[] attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options );
//noinspection unchecked
return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate(
() -> attributeValues,
@ -420,8 +420,9 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
options
);
if ( returnEmbeddable ) {
final Object[] attributeValues = getAttributeValues(
final Object[] attributeValues = structJdbcType.getAttributeValues(
structJdbcType.embeddableMappingType,
structJdbcType.orderMapping,
subValues,
options
);
@ -827,6 +828,7 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
private Object[] getAttributeValues(
EmbeddableMappingType embeddableMappingType,
int[] orderMapping,
Object[] rawJdbcValues,
WrapperOptions options) throws SQLException {
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings();
@ -879,7 +881,7 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
jdbcValueCount = embeddableMappingType.getJdbcValueCount();
final Object[] subJdbcValues = new Object[jdbcValueCount];
System.arraycopy( rawJdbcValues, jdbcIndex, subJdbcValues, 0, subJdbcValues.length );
final Object[] subValues = getAttributeValues( embeddableMappingType, subJdbcValues, options );
final Object[] subValues = getAttributeValues( embeddableMappingType, null, subJdbcValues, options );
attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy()
.getInstantiator()
.instantiate(

View File

@ -82,6 +82,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
private AggregateColumn aggregateColumn;
private AggregateColumn parentAggregateColumn;
private String structName;
private String[] structColumnNames;
// lazily computed based on 'properties' field: invalidate by setting to null when properties are modified
private transient List<Selectable> cachedSelectables;
// lazily computed based on 'properties' field: invalidate by setting to null when properties are modified
@ -807,4 +808,11 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
this.instantiatorPropertyNames = instantiatorPropertyNames;
}
public String[] getStructColumnNames() {
return structColumnNames;
}
public void setStructColumnNames(String[] structColumnNames) {
this.structColumnNames = structColumnNames;
}
}

View File

@ -9,11 +9,11 @@ package org.hibernate.metamodel.internal;
import java.util.Arrays;
public class EmbeddableHelper {
public static int[] determinePropertyMappingIndex(String[] propertyNames, String[] componentNames) {
final int[] index = new int[propertyNames.length];
public static int[] determineMappingIndex(String[] sortedNames, String[] names) {
final int[] index = new int[sortedNames.length];
int i = 0;
for ( String componentName : componentNames ) {
final int mappingIndex = Arrays.binarySearch( propertyNames, componentName );
for ( String name : names ) {
final int mappingIndex = Arrays.binarySearch( sortedNames, name );
if ( mappingIndex != -1 ) {
index[i++] = mappingIndex;
}

View File

@ -238,7 +238,6 @@ public class StructComponentInstantiatorTest extends BaseSessionFactoryFunctiona
@Id
private Long id;
@Struct(name = "my_point_type")
private Point thePoint;
public RecordStructHolder() {
@ -268,6 +267,7 @@ public class StructComponentInstantiatorTest extends BaseSessionFactoryFunctiona
}
@Embeddable
@Struct(name = "my_point_type", attributes = { "y", "x", "z" })
public static class Point {
private final String y;