HHH-15898 Allow explicit configuration of the UDT column order
This commit is contained in:
parent
7250449aaa
commit
4e03f320fb
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue