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).
|
* The name of the UDT (user defined type).
|
||||||
*/
|
*/
|
||||||
String name();
|
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 )
|
() -> new EmbeddableAggregateJavaType<>( component.getComponentClass(), structName )
|
||||||
);
|
);
|
||||||
component.setStructName( structName );
|
component.setStructName( structName );
|
||||||
|
component.setStructColumnNames( determineStructAttributeNames( inferredData, returnedClassOrElement ) );
|
||||||
|
|
||||||
// Determine the aggregate column
|
// Determine the aggregate column
|
||||||
BasicValueBinder basicValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, component, context );
|
BasicValueBinder basicValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, component, context );
|
||||||
|
@ -135,6 +136,21 @@ public final class AggregateComponentBinder {
|
||||||
return null;
|
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) {
|
private static boolean isAggregate(XProperty property, XClass returnedClass) {
|
||||||
if ( property != null ) {
|
if ( property != null ) {
|
||||||
final Struct struct = property.getAnnotation( Struct.class );
|
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.Component;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
|
import org.hibernate.mapping.Selectable;
|
||||||
import org.hibernate.mapping.UserDefinedType;
|
import org.hibernate.mapping.UserDefinedType;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
import org.hibernate.metamodel.internal.EmbeddableHelper;
|
import org.hibernate.metamodel.internal.EmbeddableHelper;
|
||||||
|
@ -189,6 +190,8 @@ public class AggregateComponentSecondPass implements SecondPass {
|
||||||
private void orderColumns(UserDefinedType userDefinedType) {
|
private void orderColumns(UserDefinedType userDefinedType) {
|
||||||
final Class<?> componentClass = component.getComponentClass();
|
final Class<?> componentClass = component.getComponentClass();
|
||||||
final int[] originalOrder = component.sortProperties();
|
final int[] originalOrder = component.sortProperties();
|
||||||
|
final String[] structColumnNames = component.getStructColumnNames();
|
||||||
|
if ( structColumnNames == null || structColumnNames.length == 0 ) {
|
||||||
final int[] propertyMappingIndex;
|
final int[] propertyMappingIndex;
|
||||||
if ( ReflectHelper.isRecord( componentClass ) ) {
|
if ( ReflectHelper.isRecord( componentClass ) ) {
|
||||||
if ( originalOrder == null ) {
|
if ( originalOrder == null ) {
|
||||||
|
@ -196,35 +199,49 @@ public class AggregateComponentSecondPass implements SecondPass {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass );
|
final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass );
|
||||||
propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex(
|
propertyMappingIndex = EmbeddableHelper.determineMappingIndex(
|
||||||
component.getPropertyNames(),
|
component.getPropertyNames(),
|
||||||
componentNames
|
componentNames
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if ( component.getInstantiatorPropertyNames() != null ) {
|
||||||
if ( component.getInstantiatorPropertyNames() == null ) {
|
propertyMappingIndex = EmbeddableHelper.determineMappingIndex(
|
||||||
return;
|
|
||||||
}
|
|
||||||
propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex(
|
|
||||||
component.getPropertyNames(),
|
component.getPropertyNames(),
|
||||||
component.getInstantiatorPropertyNames()
|
component.getInstantiatorPropertyNames()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
propertyMappingIndex = null;
|
||||||
|
}
|
||||||
|
if ( propertyMappingIndex == null ) {
|
||||||
|
// If there is default ordering possible, assume alphabetical ordering
|
||||||
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
|
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
|
||||||
final List<Property> properties = component.getProperties();
|
final List<Property> properties = component.getProperties();
|
||||||
if ( propertyMappingIndex == null ) {
|
|
||||||
for ( Property property : properties ) {
|
for ( Property property : properties ) {
|
||||||
addColumns( orderedColumns, property.getValue() );
|
addColumns( orderedColumns, property.getValue() );
|
||||||
}
|
}
|
||||||
|
userDefinedType.reorderColumns( orderedColumns );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
|
||||||
|
final List<Property> properties = component.getProperties();
|
||||||
for ( final int propertyIndex : propertyMappingIndex ) {
|
for ( final int propertyIndex : propertyMappingIndex ) {
|
||||||
addColumns( orderedColumns, properties.get( propertyIndex ).getValue() );
|
addColumns( orderedColumns, properties.get( propertyIndex ).getValue() );
|
||||||
}
|
}
|
||||||
|
userDefinedType.reorderColumns( orderedColumns );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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) {
|
private static void addColumns(ArrayList<Column> orderedColumns, Value value) {
|
||||||
if ( value instanceof Component ) {
|
if ( value instanceof Component ) {
|
||||||
|
@ -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) {
|
private void validateSupportedColumnTypes(String basePath, Component component) {
|
||||||
for ( Property property : component.getProperties() ) {
|
for ( Property property : component.getProperties() ) {
|
||||||
final Value value = property.getValue();
|
final Value value = property.getValue();
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
|
||||||
}
|
}
|
||||||
assert end == string.length();
|
assert end == string.length();
|
||||||
if ( returnEmbeddable ) {
|
if ( returnEmbeddable ) {
|
||||||
final Object[] attributeValues = getAttributeValues( embeddableMappingType, array, options );
|
final Object[] attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options );
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate(
|
return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate(
|
||||||
() -> attributeValues,
|
() -> attributeValues,
|
||||||
|
@ -420,8 +420,9 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
if ( returnEmbeddable ) {
|
if ( returnEmbeddable ) {
|
||||||
final Object[] attributeValues = getAttributeValues(
|
final Object[] attributeValues = structJdbcType.getAttributeValues(
|
||||||
structJdbcType.embeddableMappingType,
|
structJdbcType.embeddableMappingType,
|
||||||
|
structJdbcType.orderMapping,
|
||||||
subValues,
|
subValues,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
@ -827,6 +828,7 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
|
||||||
|
|
||||||
private Object[] getAttributeValues(
|
private Object[] getAttributeValues(
|
||||||
EmbeddableMappingType embeddableMappingType,
|
EmbeddableMappingType embeddableMappingType,
|
||||||
|
int[] orderMapping,
|
||||||
Object[] rawJdbcValues,
|
Object[] rawJdbcValues,
|
||||||
WrapperOptions options) throws SQLException {
|
WrapperOptions options) throws SQLException {
|
||||||
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings();
|
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings();
|
||||||
|
@ -879,7 +881,7 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme
|
||||||
jdbcValueCount = embeddableMappingType.getJdbcValueCount();
|
jdbcValueCount = embeddableMappingType.getJdbcValueCount();
|
||||||
final Object[] subJdbcValues = new Object[jdbcValueCount];
|
final Object[] subJdbcValues = new Object[jdbcValueCount];
|
||||||
System.arraycopy( rawJdbcValues, jdbcIndex, subJdbcValues, 0, subJdbcValues.length );
|
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()
|
attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy()
|
||||||
.getInstantiator()
|
.getInstantiator()
|
||||||
.instantiate(
|
.instantiate(
|
||||||
|
|
|
@ -82,6 +82,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
|
||||||
private AggregateColumn aggregateColumn;
|
private AggregateColumn aggregateColumn;
|
||||||
private AggregateColumn parentAggregateColumn;
|
private AggregateColumn parentAggregateColumn;
|
||||||
private String structName;
|
private String structName;
|
||||||
|
private String[] structColumnNames;
|
||||||
// lazily computed based on 'properties' field: invalidate by setting to null when properties are modified
|
// lazily computed based on 'properties' field: invalidate by setting to null when properties are modified
|
||||||
private transient List<Selectable> cachedSelectables;
|
private transient List<Selectable> cachedSelectables;
|
||||||
// lazily computed based on 'properties' field: invalidate by setting to null when properties are modified
|
// 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;
|
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;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class EmbeddableHelper {
|
public class EmbeddableHelper {
|
||||||
public static int[] determinePropertyMappingIndex(String[] propertyNames, String[] componentNames) {
|
public static int[] determineMappingIndex(String[] sortedNames, String[] names) {
|
||||||
final int[] index = new int[propertyNames.length];
|
final int[] index = new int[sortedNames.length];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for ( String componentName : componentNames ) {
|
for ( String name : names ) {
|
||||||
final int mappingIndex = Arrays.binarySearch( propertyNames, componentName );
|
final int mappingIndex = Arrays.binarySearch( sortedNames, name );
|
||||||
if ( mappingIndex != -1 ) {
|
if ( mappingIndex != -1 ) {
|
||||||
index[i++] = mappingIndex;
|
index[i++] = mappingIndex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,7 +238,6 @@ public class StructComponentInstantiatorTest extends BaseSessionFactoryFunctiona
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
private Long id;
|
private Long id;
|
||||||
@Struct(name = "my_point_type")
|
|
||||||
private Point thePoint;
|
private Point thePoint;
|
||||||
|
|
||||||
public RecordStructHolder() {
|
public RecordStructHolder() {
|
||||||
|
@ -268,6 +267,7 @@ public class StructComponentInstantiatorTest extends BaseSessionFactoryFunctiona
|
||||||
}
|
}
|
||||||
|
|
||||||
@Embeddable
|
@Embeddable
|
||||||
|
@Struct(name = "my_point_type", attributes = { "y", "x", "z" })
|
||||||
public static class Point {
|
public static class Point {
|
||||||
|
|
||||||
private final String y;
|
private final String y;
|
||||||
|
|
Loading…
Reference in New Issue