diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java index 507d072196..efa83ac68f 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java @@ -407,6 +407,8 @@ public class CockroachLegacyDialect extends Dialect { .getDescriptor( Object.class ) ) ); + + jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 6693b3b6fe..65c309e95c 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -1441,6 +1441,8 @@ public class PostgreSQLLegacyDialect extends Dialect { .getDescriptor( Object.class ) ) ); + + jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java b/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java index d4f45cbc66..de3fab5efe 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java @@ -47,6 +47,16 @@ public @interface Struct { */ String name(); + /** (Optional) The catalog of the UDT. + *

Defaults to the default catalog. + */ + String catalog() default ""; + + /** (Optional) The schema of the UDT. + *

Defaults to the default schema for user. + */ + String schema() default ""; + /** * 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. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 07fac7e806..eacd5272be 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -1778,7 +1778,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, processSecondPasses( idGeneratorResolverSecondPassList ); processSecondPasses( implicitColumnNamingSecondPassList ); processSecondPasses( setBasicValueTypeSecondPassList ); - processSecondPasses( aggregateComponentSecondPassList ); processSecondPasses( toOneJoinTableSecondPassList ); composites.forEach( Component::sortProperties ); @@ -1794,6 +1793,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, processPropertyReferences(); + processSecondPasses( aggregateComponentSecondPassList ); secondPassCompileForeignKeys( buildingContext ); processNaturalIdUniqueKeyBinders(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java index b12064c4a2..1e0c388cdb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java @@ -8,20 +8,20 @@ package org.hibernate.boot.model.internal; import java.util.List; -import org.hibernate.AnnotationException; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.Struct; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.QualifiedName; +import org.hibernate.boot.model.relational.QualifiedNameImpl; +import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; -import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; -import org.hibernate.mapping.Property; -import org.hibernate.mapping.Value; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.java.spi.EmbeddableAggregateJavaType; import org.hibernate.type.spi.TypeConfiguration; @@ -42,19 +42,18 @@ public final class AggregateComponentBinder { AnnotatedColumns columns, MetadataBuildingContext context) { if ( isAggregate( inferredData.getProperty(), componentXClass ) ) { - validateComponent( component, BinderHelper.getPath( propertyHolder, inferredData ) ); - final InFlightMetadataCollector metadataCollector = context.getMetadataCollector(); final TypeConfiguration typeConfiguration = metadataCollector.getTypeConfiguration(); // Determine a struct name if this is a struct through some means - final String structName = determineStructName( columns, inferredData, componentXClass ); + final QualifiedName structQualifiedName = determineStructName( columns, inferredData, componentXClass, context ); + final String structName = structQualifiedName == null ? null : structQualifiedName.render(); // We must register a special JavaType for the embeddable which can provide a recommended JdbcType typeConfiguration.getJavaTypeRegistry().resolveDescriptor( component.getComponentClass(), () -> new EmbeddableAggregateJavaType<>( component.getComponentClass(), structName ) ); - component.setStructName( structName ); + component.setStructName( structQualifiedName ); component.setStructColumnNames( determineStructAttributeNames( inferredData, componentXClass ) ); // Determine the aggregate column @@ -97,6 +96,7 @@ public final class AggregateComponentBinder { propertyHolder, component, componentXClass, + inferredData.getPropertyName(), context ) ); @@ -111,60 +111,59 @@ public final class AggregateComponentBinder { case SqlTypes.TABLE: return SqlTypes.STRUCT_TABLE; default: - throw new IllegalArgumentException( "Unsupported array type code: " + arrayTypeCode ); + throw new UnsupportedOperationException( "Dialect does not support structured array types: " + context.getMetadataCollector() + .getDatabase() + .getDialect() + .getClass() + .getName() ); } } - private static void validateComponent(Component component, String basePath) { - for ( Property property : component.getProperties() ) { - final Value value = property.getValue(); - if ( !( value instanceof BasicValue ) && !( value instanceof Component ) ) { - // todo: see HHH-15831 - throw new AnnotationException( - "Property '" + StringHelper.qualify( basePath, property.getName() ) - + "' uses not yet supported mapping type '" - + value.getClass().getName() - + "' in component class '" - + component.getComponentClassName() - + "'. Aggregate components currently may only contain basic values and components of basic values." - ); - } - if ( value instanceof Component ) { - final Component c = (Component) value; - if ( c.getAggregateColumn() == null ) { - validateComponent( c, StringHelper.qualify( basePath, property.getName() ) ); - } - } - } - } - - private static String determineStructName( + private static QualifiedName determineStructName( AnnotatedColumns columns, PropertyData inferredData, - XClass returnedClassOrElement) { + XClass returnedClassOrElement, + MetadataBuildingContext context) { final XProperty property = inferredData.getProperty(); if ( property != null ) { final Struct struct = property.getAnnotation( Struct.class ); if ( struct != null ) { - return struct.name(); + return toQualifiedName( struct, context ); } final JdbcTypeCode jdbcTypeCode = property.getAnnotation( JdbcTypeCode.class ); if ( jdbcTypeCode != null && ( jdbcTypeCode.value() == SqlTypes.STRUCT || jdbcTypeCode.value() == SqlTypes.STRUCT_ARRAY || jdbcTypeCode.value() == SqlTypes.STRUCT_TABLE ) && columns != null ) { final List columnList = columns.getColumns(); - if ( columnList.size() == 1 && columnList.get( 0 ).getSqlType() != null ) { - return columnList.get( 0 ).getSqlType(); + final String sqlType; + if ( columnList.size() == 1 && ( sqlType = columnList.get( 0 ).getSqlType() ) != null ) { + if ( sqlType.contains( "." ) ) { + return QualifiedNameParser.INSTANCE.parse( sqlType ); + } + return new QualifiedNameParser.NameParts( + null, + null, + context.getMetadataCollector().getDatabase().toIdentifier( sqlType ) + ); } } } final Struct struct = returnedClassOrElement.getAnnotation( Struct.class ); if ( struct != null ) { - return struct.name(); + return toQualifiedName( struct, context ); } return null; } + private static QualifiedName toQualifiedName(Struct struct, MetadataBuildingContext context) { + final Database database = context.getMetadataCollector().getDatabase(); + return new QualifiedNameImpl( + database.toIdentifier( struct.catalog() ), + database.toIdentifier( struct.schema() ), + database.toIdentifier( struct.name() ) + ); + } + private static String[] determineStructAttributeNames(PropertyData inferredData, XClass returnedClassOrElement) { final XProperty property = inferredData.getProperty(); if ( property != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java index f85646d6c3..8872cfb80a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java @@ -11,25 +11,27 @@ import java.util.List; import java.util.Map; import java.util.TreeSet; +import org.hibernate.AnnotationException; import org.hibernate.MappingException; import org.hibernate.annotations.Comment; import org.hibernate.annotations.common.reflection.XClass; -import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.SecondPass; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.internal.util.ReflectHelper; -import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.AggregateColumn; +import org.hibernate.mapping.Collection; 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.ToOne; import org.hibernate.mapping.UserDefinedObjectType; import org.hibernate.mapping.Value; import org.hibernate.metamodel.internal.EmbeddableHelper; @@ -37,6 +39,8 @@ import org.hibernate.sql.Template; import org.hibernate.type.SqlTypes; import org.hibernate.type.spi.TypeConfiguration; +import static org.hibernate.internal.util.StringHelper.qualify; + /** * @author Christian Beikov */ @@ -45,20 +49,26 @@ public class AggregateComponentSecondPass implements SecondPass { private final PropertyHolder propertyHolder; private final Component component; private final XClass componentXClass; + private final String propertyName; private final MetadataBuildingContext context; public AggregateComponentSecondPass( PropertyHolder propertyHolder, Component component, XClass componentXClass, + String propertyName, MetadataBuildingContext context) { this.propertyHolder = propertyHolder; this.component = component; this.componentXClass = componentXClass; + this.propertyName = propertyName; this.context = context; } + @Override public void doSecondPass(Map persistentClasses) throws MappingException { + validateComponent( component, qualify( propertyHolder.getPath(), propertyName ), isAggregateArray() ); + final InFlightMetadataCollector metadataCollector = context.getMetadataCollector(); final TypeConfiguration typeConfiguration = metadataCollector.getTypeConfiguration(); final Database database = metadataCollector.getDatabase(); @@ -72,21 +82,17 @@ public class AggregateComponentSecondPass implements SecondPass { final List aggregatedColumns = component.getAggregatedColumns(); final AggregateColumn aggregateColumn = component.getAggregateColumn(); - ensureInitialized( metadataCollector, typeConfiguration, dialect, aggregateColumn ); + ensureInitialized( metadataCollector, aggregateColumn ); validateSupportedColumnTypes( propertyHolder.getPath(), component ); - for ( org.hibernate.mapping.Column aggregatedColumn : aggregatedColumns ) { - // Make sure this state is initialized - aggregatedColumn.getSqlTypeCode( metadataCollector ); - aggregatedColumn.getSqlType( metadataCollector ); - } - - final String structName = component.getStructName(); + final QualifiedName structName = component.getStructName(); final boolean addAuxiliaryObjects; if ( structName != null ) { - final Namespace defaultNamespace = database.getDefaultNamespace(); - final Identifier udtName = Identifier.toIdentifier( structName ); - final UserDefinedObjectType udt = new UserDefinedObjectType( "orm", defaultNamespace, udtName ); + final Namespace namespace = database.locateNamespace( + structName.getCatalogName(), + structName.getSchemaName() + ); + final UserDefinedObjectType udt = new UserDefinedObjectType( "orm", namespace, structName.getObjectName() ); final Comment comment = componentXClass.getAnnotation( Comment.class ); if ( comment != null ) { udt.setComment( comment.value() ); @@ -94,8 +100,8 @@ public class AggregateComponentSecondPass implements SecondPass { for ( org.hibernate.mapping.Column aggregatedColumn : aggregatedColumns ) { udt.addColumn( aggregatedColumn ); } - final UserDefinedObjectType registeredUdt = defaultNamespace.createUserDefinedType( - udtName, + final UserDefinedObjectType registeredUdt = namespace.createUserDefinedType( + structName.getObjectName(), name -> udt ); if ( registeredUdt == udt ) { @@ -187,6 +193,66 @@ public class AggregateComponentSecondPass implements SecondPass { propertyHolder.getTable().getColumns().removeAll( aggregatedColumns ); } + private static void validateComponent(Component component, String basePath, boolean inArray) { + for ( Property property : component.getProperties() ) { + final Value value = property.getValue(); + if ( value instanceof Component ) { + final Component c = (Component) value; + validateComponent( c, qualify( basePath, property.getName() ), inArray ); + } + else if ( value instanceof ToOne ) { + final ToOne toOne = (ToOne) value; + if ( inArray && toOne.getReferencedPropertyName() != null ) { + throw new AnnotationException( + "Property '" + qualify( basePath, property.getName() ) + + "' uses one-to-one mapping with mappedBy '" + + toOne.getReferencedPropertyName() + + "' in the aggregate component class '" + + component.getComponentClassName() + + "' within an array property, which is not allowed." + ); + } + } + else if ( value instanceof Collection ) { + final Collection collection = (Collection) value; + if ( inArray && collection.getMappedByProperty() != null ) { + throw new AnnotationException( + "Property '" + qualify( basePath, property.getName() ) + + "' uses *-to-many mapping with mappedBy '" + + collection.getMappedByProperty() + + "' in the aggregate component class '" + + component.getComponentClassName() + + "' within an array property, which is not allowed." + ); + } + if ( inArray && collection.getCollectionTable() != null ) { + throw new AnnotationException( + "Property '" + qualify( basePath, property.getName() ) + + "' defines a collection table '" + + collection.getCollectionTable() + + "' in the aggregate component class '" + + component.getComponentClassName() + + "' within an array property, which is not allowed." + ); + } + } + } + } + + private boolean isAggregateArray() { + switch ( component.getAggregateColumn().getSqlTypeCode( context.getMetadataCollector() ) ) { + case SqlTypes.STRUCT_ARRAY: + case SqlTypes.STRUCT_TABLE: + case SqlTypes.JSON_ARRAY: + case SqlTypes.XML_ARRAY: + case SqlTypes.ARRAY: + case SqlTypes.TABLE: + return true; + default: + return false; + } + } + private void orderColumns(UserDefinedObjectType userDefinedType, int[] originalOrder) { final Class componentClass = component.getComponentClass(); final String[] structColumnNames = component.getStructColumnNames(); @@ -303,7 +369,7 @@ public class AggregateComponentSecondPass implements SecondPass { if ( value instanceof Component ) { final Component subComponent = (Component) value; if ( subComponent.getAggregateColumn() == null ) { - validateSupportedColumnTypes( StringHelper.qualify( basePath, property.getName() ), subComponent ); + validateSupportedColumnTypes( qualify( basePath, property.getName() ), subComponent ); } } } @@ -311,8 +377,27 @@ public class AggregateComponentSecondPass implements SecondPass { private static void ensureInitialized( InFlightMetadataCollector metadataCollector, - TypeConfiguration typeConfiguration, - Dialect dialect, + AggregateColumn aggregateColumn) { + ensureParentInitialized( metadataCollector, aggregateColumn ); + ensureChildrenInitialized( metadataCollector, aggregateColumn ); + } + + private static void ensureChildrenInitialized( + InFlightMetadataCollector metadataCollector, + AggregateColumn aggregateColumn) { + for ( Column aggregatedColumn : aggregateColumn.getComponent().getAggregatedColumns() ) { + // Make sure this state is initialized + aggregatedColumn.getSqlTypeCode( metadataCollector ); + aggregatedColumn.getSqlType( metadataCollector ); + if ( aggregatedColumn instanceof AggregateColumn ) { + ensureChildrenInitialized( metadataCollector, (AggregateColumn) aggregatedColumn ); + } + } + + } + + private static void ensureParentInitialized( + InFlightMetadataCollector metadataCollector, AggregateColumn aggregateColumn) { do { // Trigger resolving of the value so that the column gets properly filled diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java index b54af8adf9..48558dd2cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java @@ -1224,6 +1224,11 @@ public class BasicValueBinder implements JdbcTypeIndicators { basicValue = new BasicValue( buildingContext, table ); + if ( columns.getPropertyHolder().isComponent() ) { + final ComponentPropertyHolder propertyHolder = (ComponentPropertyHolder) columns.getPropertyHolder(); + basicValue.setAggregateColumn( propertyHolder.getAggregateColumn() ); + } + if ( isNationalized() ) { basicValue.makeNationalized(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java index b493faf796..d557343243 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java @@ -14,6 +14,7 @@ import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; +import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; @@ -265,7 +266,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder { // if a property is set already the core cannot support that if ( columns != null ) { final Table table = columns.getTable(); - if ( !table.equals( component.getTable() ) ) { + if ( !table.equals( getTable() ) ) { if ( component.getPropertySpan() == 0 ) { component.setTable( table ); } @@ -301,6 +302,11 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder { return component.getOwner().getClassName(); } + public AggregateColumn getAggregateColumn() { + final AggregateColumn aggregateColumn = component.getAggregateColumn(); + return aggregateColumn != null ? aggregateColumn : component.getParentAggregateColumn(); + } + @Override public Table getTable() { return component.getTable(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java index 1463ec488a..17dbbef4e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java @@ -28,6 +28,7 @@ import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; @@ -371,6 +372,14 @@ public class EmbeddableBinder { returnedClassOrElement = context.getBootstrapContext().getReflectionManager() .toXClass( compositeUserType.embeddable() ); } + AggregateComponentBinder.processAggregate( + component, + propertyHolder, + inferredData, + returnedClassOrElement, + columns, + context + ); final XClass annotatedClass = inferredData.getPropertyClass(); final List classElements = @@ -447,14 +456,6 @@ public class EmbeddableBinder { processCompositeUserType( component, compositeUserType ); } - AggregateComponentBinder.processAggregate( - component, - propertyHolder, - inferredData, - returnedClassOrElement, - columns, - context - ); return component; } @@ -563,6 +564,7 @@ public class EmbeddableBinder { columns.setBuildingContext( context ); discriminatorColumn.setParent( columns ); final BasicValue discriminatorColumnBinding = new BasicValue( context, component.getTable() ); + discriminatorColumnBinding.setAggregateColumn( component.getAggregateColumn() ); component.setDiscriminator( discriminatorColumnBinding ); discriminatorColumn.linkWithValue( discriminatorColumnBinding ); discriminatorColumnBinding.setTypeName( discriminatorColumn.getDiscriminatorTypeName() ); @@ -858,6 +860,10 @@ public class EmbeddableBinder { if ( constructor != null ) { component.setInstantiator( constructor, constructor.getAnnotation( Instantiator.class ).value() ); } + if ( propertyHolder.isComponent() ) { + final ComponentPropertyHolder componentPropertyHolder = (ComponentPropertyHolder) propertyHolder; + component.setParentAggregateColumn( componentPropertyHolder.getAggregateColumn() ); + } return component; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java index 1c6a4cb28a..84b8fd0b4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Namespace.java @@ -34,6 +34,7 @@ import org.hibernate.mapping.UserDefinedArrayType; import org.hibernate.mapping.UserDefinedObjectType; import org.hibernate.mapping.UserDefinedType; import org.hibernate.type.BasicType; +import org.hibernate.type.Type; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType; @@ -183,14 +184,17 @@ public class Namespace { final UserDefinedType udt = entry.getValue(); if ( udt instanceof UserDefinedObjectType ) { for ( Column udtColumn : ( (UserDefinedObjectType) udt ).getColumns() ) { - final JdbcType jdbcType = ( (BasicType) udtColumn.getValue().getType() ).getJdbcType(); - if ( jdbcType instanceof SqlTypedJdbcType ) { - dependencies.add( Identifier.toIdentifier( ( (SqlTypedJdbcType) jdbcType ).getSqlTypeName() ) ); - } - else if ( jdbcType instanceof ArrayJdbcType ) { - final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType(); - if ( elementJdbcType instanceof SqlTypedJdbcType ) { - dependencies.add( Identifier.toIdentifier( ( (SqlTypedJdbcType) elementJdbcType ).getSqlTypeName() ) ); + final Type udtColumnType = udtColumn.getValue().getType(); + if ( udtColumnType instanceof BasicType ) { + final JdbcType jdbcType = ( (BasicType) udtColumnType ).getJdbcType(); + if ( jdbcType instanceof SqlTypedJdbcType ) { + dependencies.add( Identifier.toIdentifier( ( (SqlTypedJdbcType) jdbcType ).getSqlTypeName() ) ); + } + else if ( jdbcType instanceof ArrayJdbcType ) { + final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType(); + if ( elementJdbcType instanceof SqlTypedJdbcType ) { + dependencies.add( Identifier.toIdentifier( ( (SqlTypedJdbcType) elementJdbcType ).getSqlTypeName() ) ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java index 5d9e1eceab..76b67da320 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java @@ -24,13 +24,11 @@ import java.util.ArrayList; import java.util.TimeZone; import org.hibernate.internal.util.CharSequenceHelper; -import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.StringBuilderSqlAppender; import org.hibernate.type.BasicPluralType; @@ -1008,7 +1006,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); int count = 0; for ( int i = 0; i < size; i++ ) { - final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, orderMapping[i] ); + final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, orderMapping[i] ); final MappingType mappedType = modelPart.getMappedType(); if ( mappedType instanceof EmbeddableMappingType ) { final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType; @@ -1178,7 +1176,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType return rawJdbcValue.toString(); } - protected String toString(X value, JavaType javaType, WrapperOptions options) { + protected String toString(X value, JavaType javaType, WrapperOptions options) throws SQLException { if ( value == null ) { return null; } @@ -1187,86 +1185,62 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType return sb.toString(); } - private void serializeStructTo(PostgreSQLAppender appender, Object value, WrapperOptions options) { - serializeValuesTo( appender, options, embeddableMappingType, value, '(' ); + private void serializeStructTo(PostgreSQLAppender appender, Object value, WrapperOptions options) throws SQLException { + serializeDomainValueTo( appender, options, value, '(' ); appender.append( ')' ); } - private void serializeValuesTo( + private void serializeDomainValueTo( PostgreSQLAppender appender, WrapperOptions options, - EmbeddableMappingType embeddableMappingType, Object domainValue, - char separator) { - final Object[] array = embeddableMappingType.getValues( domainValue ); - final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); - for ( int i = 0; i < array.length; i++ ) { - final ValuedModelPart attributeMapping; - final Object attributeValue; - if ( orderMapping == null ) { - attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); - attributeValue = array[i]; - } - else { - attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, orderMapping[i] ); - attributeValue = array[orderMapping[i]]; - } - if ( attributeMapping instanceof BasicValuedMapping ) { - appender.append( separator ); - separator = ','; - if ( attributeValue == null ) { - continue; - } - final JdbcMapping jdbcMapping = ( (BasicValuedMapping) attributeMapping ).getJdbcMapping(); - serializeBasicTo( appender, options, jdbcMapping, attributeValue ); - } - else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); - final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); - if ( aggregateMapping == null ) { - serializeValuesTo( - appender, - options, - mappingType, - attributeValue, - separator - ); - separator = ','; - } - else { - appender.append( separator ); - separator = ','; - if ( attributeValue == null ) { - continue; - } - appender.quoteStart(); - ( (AbstractPostgreSQLStructJdbcType) aggregateMapping.getJdbcMapping().getJdbcType() ).serializeStructTo( - appender, - attributeValue, - options - ); - appender.quoteEnd(); - } - } - else { - throw new UnsupportedOperationException( "Unsupported attribute mapping: " + attributeMapping ); - } - } + char separator) throws SQLException { + serializeJdbcValuesTo( + appender, + options, + StructHelper.getJdbcValues( embeddableMappingType, orderMapping, domainValue, options ), + separator + ); } - private void serializeBasicTo( + private void serializeJdbcValuesTo( PostgreSQLAppender appender, WrapperOptions options, - JdbcMapping jdbcMapping, - Object value) { - serializeConvertedBasicTo( appender, options, jdbcMapping, jdbcMapping.convertToRelationalValue( value ) ); + Object[] jdbcValues, + char separator) throws SQLException { + for ( int i = 0; i < jdbcValues.length; i++ ) { + appender.append( separator ); + separator = ','; + final Object jdbcValue = jdbcValues[i]; + if ( jdbcValue == null ) { + continue; + } + final SelectableMapping selectableMapping = orderMapping == null ? + embeddableMappingType.getJdbcValueSelectable( i ) : + embeddableMappingType.getJdbcValueSelectable( orderMapping[i] ); + final JdbcMapping jdbcMapping = selectableMapping.getJdbcMapping(); + if ( jdbcMapping.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType ) { + appender.quoteStart(); + ( (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType() ).serializeJdbcValuesTo( + appender, + options, + (Object[]) jdbcValue, + '(' + ); + appender.append( ')' ); + appender.quoteEnd(); + } + else { + serializeConvertedBasicTo( appender, options, jdbcMapping, jdbcValue ); + } + } } private void serializeConvertedBasicTo( PostgreSQLAppender appender, WrapperOptions options, JdbcMapping jdbcMapping, - Object subValue) { + Object subValue) throws SQLException { //noinspection unchecked final JavaType jdbcJavaType = (JavaType) jdbcMapping.getJdbcJavaType(); switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { @@ -1291,14 +1265,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType case SqlTypes.DECIMAL: case SqlTypes.NUMERIC: case SqlTypes.DURATION: - jdbcJavaType.appendEncodedString( - appender, - jdbcJavaType.unwrap( - subValue, - jdbcJavaType.getJavaTypeClass(), - options - ) - ); + appender.append( subValue.toString() ); break; case SqlTypes.CHAR: case SqlTypes.NCHAR: @@ -1316,14 +1283,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType case SqlTypes.ENUM: case SqlTypes.NAMED_ENUM: appender.quoteStart(); - jdbcJavaType.appendEncodedString( - appender, - jdbcJavaType.unwrap( - subValue, - jdbcJavaType.getJavaTypeClass(), - options - ) - ); + appender.append( (String) subValue ); appender.quoteEnd(); break; case SqlTypes.DATE: @@ -1393,9 +1353,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType case SqlTypes.STRUCT: if ( subValue != null ) { final AbstractPostgreSQLStructJdbcType structJdbcType = (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType(); - final EmbeddableMappingType subEmbeddableMappingType = structJdbcType.getEmbeddableMappingType(); appender.quoteStart(); - structJdbcType.serializeValuesTo( appender, options, subEmbeddableMappingType, subValue, '(' ); + structJdbcType.serializeJdbcValuesTo( appender, options, (Object[]) subValue, '(' ); appender.append( ')' ); appender.quoteEnd(); } @@ -1427,9 +1386,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType else { attributeIndex = orderMapping[i]; } - final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, attributeIndex ); jdbcIndex += injectAttributeValue( - modelPart, + getEmbeddedPart( embeddableMappingType, attributeIndex ), attributeValues, attributeIndex, rawJdbcValues, @@ -1567,6 +1525,10 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType : options.getJdbcTimeZone(); } + protected Object getBindValue(X value, WrapperOptions options) throws SQLException { + return StructHelper.getJdbcValues( embeddableMappingType, orderMapping, value, options ); + } + private static class PostgreSQLAppender extends StringBuilderSqlAppender { private int quote = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index ae363e60d7..dfce4648f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -410,6 +410,8 @@ public class CockroachDialect extends Dialect { .getDescriptor( Object.class ) ) ); + + jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java index 840d309d8e..4e377ccde7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java @@ -73,7 +73,7 @@ public class JsonHelper { final Object[] values = embeddableMappingType.getValues( domainValue ); final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); for ( int i = 0; i < values.length; i++ ) { - final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); if ( attributeMapping instanceof SelectableMapping ) { final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); appender.append( separator ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java index 6811328600..87537cc7db 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java @@ -69,6 +69,8 @@ public class OracleArrayJdbcType extends ArrayJdbcType implements SqlTypedJdbcTy @Override public ValueBinder getBinder(final JavaType javaTypeDescriptor) { + //noinspection unchecked + final ValueBinder elementBinder = getElementJdbcType().getBinder( ( (BasicPluralJavaType) javaTypeDescriptor ).getElementJavaType() ); return new BasicBinder<>( javaTypeDescriptor, this ) { private String typeName(WrapperOptions options) { return ( upperTypeName == null @@ -105,7 +107,7 @@ public class OracleArrayJdbcType extends ArrayJdbcType implements SqlTypedJdbcTy @Override public java.sql.Array getBindValue(X value, WrapperOptions options) throws SQLException { - final Object[] objects = OracleArrayJdbcType.this.getArray( this, value, options ); + final Object[] objects = ( (OracleArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options ); final String arrayTypeName = typeName( options ); final OracleConnection oracleConnection = options.getSession() diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleBaseStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleBaseStructJdbcType.java index 3d434ac168..e8db81222e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleBaseStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleBaseStructJdbcType.java @@ -6,6 +6,9 @@ */ package org.hibernate.dialect; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Locale; import org.hibernate.HibernateException; @@ -17,9 +20,12 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.type.Type; +import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; import oracle.sql.TIMESTAMPTZ; @@ -41,6 +47,28 @@ public class OracleBaseStructJdbcType extends StructJdbcType { ); } + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setObject( index, createJdbcValue( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setObject( name, createJdbcValue( value, options ) ); + } + + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return createJdbcValue( value, options ); + } + }; + } + @Override public String getExtraCreateTableInfo( JavaType javaType, @@ -51,21 +79,24 @@ public class OracleBaseStructJdbcType extends StructJdbcType { .locateUserDefinedType( Identifier.toIdentifier( getSqlTypeName() ) ); StringBuilder sb = null; for ( Column column : udt.getColumns() ) { - final JdbcMapping jdbcMapping = (JdbcMapping) column.getValue().getType(); - final String extraCreateTableInfo = jdbcMapping.getJdbcType().getExtraCreateTableInfo( - jdbcMapping.getJavaTypeDescriptor(), - columnName + "." + column.getName(), - tableName, - database - ); - if ( !extraCreateTableInfo.isEmpty() ) { - if ( sb == null ) { - sb = new StringBuilder(); + final Type columnType = column.getValue().getType(); + if ( columnType instanceof JdbcMapping ) { + final JdbcMapping jdbcMapping = (JdbcMapping) columnType; + final String extraCreateTableInfo = jdbcMapping.getJdbcType().getExtraCreateTableInfo( + jdbcMapping.getJavaTypeDescriptor(), + columnName + "." + column.getName(), + tableName, + database + ); + if ( !extraCreateTableInfo.isEmpty() ) { + if ( sb == null ) { + sb = new StringBuilder(); + } + else { + sb.append( ',' ); + } + sb.append( extraCreateTableInfo ); } - else { - sb.append( ',' ); - } - sb.append( extraCreateTableInfo ); } } return sb != null ? sb.toString() : ""; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLArrayJdbcType.java new file mode 100644 index 0000000000..f16fe6a041 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLArrayJdbcType.java @@ -0,0 +1,97 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * Descriptor for {@link Types#ARRAY ARRAY} handling. + */ +public class PostgreSQLArrayJdbcType extends ArrayJdbcType { + + public PostgreSQLArrayJdbcType(JdbcType elementJdbcType) { + super( elementJdbcType ); + } + + @Override + public ValueBinder getBinder(final JavaType javaTypeDescriptor) { + //noinspection unchecked + final ValueBinder elementBinder = getElementJdbcType().getBinder( ( (BasicPluralJavaType) javaTypeDescriptor ).getElementJavaType() ); + return new BasicBinder<>( javaTypeDescriptor, this ) { + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setArray( index, getArray( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final java.sql.Array arr = getArray( value, options ); + try { + st.setObject( name, arr, java.sql.Types.ARRAY ); + } + catch (SQLException ex) { + throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); + } + } + + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return ( (PostgreSQLArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options ); + } + + private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { + final PostgreSQLArrayJdbcType arrayJdbcType = (PostgreSQLArrayJdbcType) getJdbcType(); + final Object[] objects; + + final JdbcType elementJdbcType = arrayJdbcType.getElementJdbcType(); + if ( elementJdbcType instanceof AggregateJdbcType ) { + // The PostgreSQL JDBC driver does not support arrays of structs, which contain byte[] + final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; + final Object[] domainObjects = getJavaType().unwrap( + value, + Object[].class, + options + ); + objects = new Object[domainObjects.length]; + for ( int i = 0; i < domainObjects.length; i++ ) { + objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); + } + } + else { + objects = arrayJdbcType.getArray( this, elementBinder, value, options ); + } + + final SharedSessionContractImplementor session = options.getSession(); + final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session ); + return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() + .createArrayOf( typeName, objects ); + } + }; + } + + @Override + public String toString() { + return "PostgreSQLArrayTypeDescriptor(" + getElementJdbcType().toString() + ")"; + } +} + diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLArrayJdbcTypeConstructor.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLArrayJdbcTypeConstructor.java new file mode 100644 index 0000000000..4888294bd6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLArrayJdbcTypeConstructor.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect; + +import java.sql.Types; + +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Factory for {@link PostgreSQLArrayJdbcType}. + */ +public class PostgreSQLArrayJdbcTypeConstructor implements JdbcTypeConstructor { + + public static final PostgreSQLArrayJdbcTypeConstructor INSTANCE = new PostgreSQLArrayJdbcTypeConstructor(); + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + BasicType elementType, + ColumnTypeInformation columnTypeInformation) { + return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation ); + } + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + JdbcType elementType, + ColumnTypeInformation columnTypeInformation) { + return new PostgreSQLArrayJdbcType( elementType ); + } + + @Override + public int getDefaultSqlTypeCode() { + return Types.ARRAY; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 965996c41e..4ea90b6f68 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -1474,6 +1474,8 @@ public class PostgreSQLDialect extends Dialect { jdbcTypeRegistry.addDescriptor( PostgreSQLEnumJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( PostgreSQLOrdinalEnumJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( PostgreSQLUUIDJdbcType.INSTANCE ); + + jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructCastingJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructCastingJdbcType.java index 438f184f1a..defe72f811 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructCastingJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructCastingJdbcType.java @@ -89,6 +89,11 @@ public class PostgreSQLStructCastingJdbcType extends AbstractPostgreSQLStructJdb ); st.setString( name, stringValue ); } + + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return ( (PostgreSQLStructCastingJdbcType) getJdbcType() ).getBindValue( value, options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructPGObjectJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructPGObjectJdbcType.java index 24771d5915..df78b6c949 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructPGObjectJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructPGObjectJdbcType.java @@ -97,6 +97,11 @@ public class PostgreSQLStructPGObjectJdbcType extends AbstractPostgreSQLStructJd holder.setValue( stringValue ); st.setObject( name, holder ); } + + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return ( (PostgreSQLStructPGObjectJdbcType) getJdbcType() ).getBindValue( value, options ); + } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java index 45ff62fbcb..1b9ab7c636 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java @@ -14,12 +14,18 @@ import java.sql.SQLException; import org.hibernate.Internal; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping; import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; @@ -39,12 +45,14 @@ public class StructHelper { final StructAttributeValues attributeValues = new StructAttributeValues( numberOfAttributeMappings, rawJdbcValues ); int jdbcIndex = 0; for ( int i = 0; i < size; i++ ) { - final ValuedModelPart valuedModelPart = getEmbeddedPart( - embeddableMappingType, - numberOfAttributeMappings, - i + jdbcIndex += injectAttributeValue( + getEmbeddedPart( embeddableMappingType, i ), + attributeValues, + i, + rawJdbcValues, + jdbcIndex, + options ); - jdbcIndex += injectAttributeValue( valuedModelPart, attributeValues, i, rawJdbcValues, jdbcIndex, options ); } return attributeValues; } @@ -104,26 +112,58 @@ public class StructHelper { else { jdbcValues = values; } - int jdbcIndex = 0; + injectJdbcValues( + embeddableMappingType, + values, + jdbcValues, + 0, + options + ); + if ( orderMapping != null ) { + final Object[] originalJdbcValues = jdbcValues.clone(); + for ( int i = 0; i < orderMapping.length; i++ ) { + jdbcValues[i] = originalJdbcValues[orderMapping[i]]; + } + } + return jdbcValues; + } + + private static int injectJdbcValues( + EmbeddableMappingType embeddableMappingType, + Object domainValue, + Object[] jdbcValues, + int jdbcIndex, + WrapperOptions options) throws SQLException { + return injectJdbcValues( + embeddableMappingType, + embeddableMappingType.getValues( domainValue ), + jdbcValues, + jdbcIndex, + options + ); + } + + private static int injectJdbcValues( + EmbeddableMappingType embeddableMappingType, + Object[] values, + Object[] jdbcValues, + int jdbcIndex, + WrapperOptions options) throws SQLException { + final int jdbcValueCount = embeddableMappingType.getJdbcValueCount(); + final int valueCount = jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); + int offset = 0; for ( int i = 0; i < values.length; i++ ) { - final int attributeIndex; - if ( orderMapping == null ) { - attributeIndex = i; - } - else { - attributeIndex = orderMapping[i]; - } - jdbcIndex += injectJdbcValue( - getEmbeddedPart( embeddableMappingType, jdbcValueCount, attributeIndex ), + offset += injectJdbcValue( + getEmbeddedPart( embeddableMappingType, i ), values, - attributeIndex, + i, jdbcValues, - jdbcIndex, + jdbcIndex + offset, options ); } - assert jdbcIndex == valueCount; - return jdbcValues; + assert offset == valueCount; + return offset; } public static Object instantiate( @@ -142,13 +182,10 @@ public class StructHelper { return instantiator.instantiate( attributeValues, sessionFactory ); } - public static ValuedModelPart getEmbeddedPart( - EmbeddableMappingType embeddableMappingType, - int numberOfAttributes, - int position) { - return position == numberOfAttributes ? - embeddableMappingType.getDiscriminatorMapping() : - embeddableMappingType.getAttributeMapping( position ); + public static ValuedModelPart getEmbeddedPart(EmbeddableMappingType embeddableMappingType, int position) { + return position == embeddableMappingType.getNumberOfAttributeMappings() + ? embeddableMappingType.getDiscriminatorMapping() + : embeddableMappingType.getAttributeMapping( position ); } private static int injectJdbcValue( @@ -158,20 +195,65 @@ public class StructHelper { Object[] jdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { - final MappingType mappedType = attributeMapping.getMappedType(); final int jdbcValueCount; - if ( mappedType instanceof EmbeddableMappingType ) { - final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType; - if ( embeddableMappingType.getAggregateMapping() != null ) { - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) embeddableMappingType.getAggregateMapping() - .getJdbcMapping() - .getJdbcType(); + if ( attributeMapping instanceof ToOneAttributeMapping ) { + final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; + if ( toOneAttributeMapping.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) { + return 0; + } + final ForeignKeyDescriptor foreignKeyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor(); + final ValuedModelPart keyPart = foreignKeyDescriptor.getKeyPart(); + final Object foreignKeyValue = foreignKeyDescriptor.getAssociationKeyFromSide( + attributeValues[attributeIndex], + ForeignKeyDescriptor.Nature.TARGET, + options.getSession() + ); + if ( keyPart instanceof BasicValuedMapping ) { jdbcValueCount = 1; - jdbcValues[jdbcIndex] = aggregateJdbcType.createJdbcValue( - attributeValues[attributeIndex], + jdbcValues[jdbcIndex] = foreignKeyValue; + } + else if ( keyPart instanceof EmbeddableValuedModelPart ) { + final EmbeddableMappingType mappingType = ( (EmbeddableValuedModelPart) keyPart ).getEmbeddableTypeDescriptor(); + jdbcValueCount = injectJdbcValues( + mappingType, + foreignKeyValue, + jdbcValues, + jdbcIndex, options ); } + else { + throw new UnsupportedOperationException( "Unsupported foreign key part: " + keyPart ); + } + } + else if ( attributeMapping instanceof PluralAttributeMapping ) { + return 0; + } + else if ( attributeMapping instanceof DiscriminatedAssociationAttributeMapping ) { + jdbcValueCount = attributeMapping.decompose( + attributeValues[attributeIndex], + jdbcIndex, + jdbcValues, + options, + (valueIndex, objects, wrapperOptions, value, jdbcValueMapping) -> { + objects[valueIndex] = value; + }, + options.getSession() + ); + } + else if ( attributeMapping instanceof EmbeddableValuedModelPart ) { + final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) attributeMapping; + final EmbeddableMappingType embeddableMappingType = embeddableValuedModelPart.getMappedType(); + if ( embeddableMappingType.getAggregateMapping() != null ) { + jdbcValueCount = 1; + jdbcValues[jdbcIndex] = embeddableMappingType.getAggregateMapping() + .getJdbcMapping() + .getJdbcValueBinder() + .getBindValue( + attributeValues[attributeIndex], + options + ); + } else { jdbcValueCount = embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); @@ -180,7 +262,7 @@ public class StructHelper { int offset = 0; for ( int i = 0; i < numberOfValues; i++ ) { offset += injectJdbcValue( - getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, i ), + getEmbeddedPart( embeddableMappingType, i ), subValues, i, jdbcValues, @@ -251,23 +333,27 @@ public class StructHelper { int[] inverseMapping, Object[] sourceJdbcValues, Object[] targetJdbcValues) { - final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); - int targetJdbcOffset = 0; - for ( int i = 0; i < numberOfAttributes + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) { - final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); - final MappingType mappedType = attributeMapping.getMappedType(); - final int jdbcValueCount = getJdbcValueCount( mappedType ); - - final int attributeIndex = inverseMapping[i]; - int sourceJdbcIndex = 0; - for ( int j = 0; j < attributeIndex; j++ ) { - sourceJdbcIndex += getJdbcValueCount( embeddableMappingType.getAttributeMapping( j ).getMappedType() ); - } - - for ( int j = 0; j < jdbcValueCount; j++ ) { - targetJdbcValues[targetJdbcOffset++] = sourceJdbcValues[sourceJdbcIndex + j]; - } + for ( int i = 0; i < inverseMapping.length; i++ ) { + targetJdbcValues[i] = sourceJdbcValues[inverseMapping[i]]; } + +// final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings(); +// int targetJdbcOffset = 0; +// for ( int i = 0; i < numberOfAttributes + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) { +// final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); +// final MappingType mappedType = attributeMapping.getMappedType(); +// final int jdbcValueCount = getJdbcValueCount( mappedType ); +// +// final int attributeIndex = inverseMapping[i]; +// int sourceJdbcIndex = 0; +// for ( int j = 0; j < attributeIndex; j++ ) { +// sourceJdbcIndex += getJdbcValueCount( embeddableMappingType.getAttributeMapping( j ).getMappedType() ); +// } +// +// for ( int j = 0; j < jdbcValueCount; j++ ) { +// targetJdbcValues[targetJdbcOffset++] = sourceJdbcValues[sourceJdbcIndex + j]; +// } +// } } public static int getJdbcValueCount(MappingType mappedType) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java index 14a46f50cb..2ae5ed01f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/StructJdbcType.java @@ -13,10 +13,16 @@ import java.sql.SQLException; import java.sql.Struct; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; @@ -144,9 +150,12 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct @Override public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException { - final Object[] attributes = ( (Struct) rawJdbcValue ).getAttributes(); - wrapRawJdbcValues( embeddableMappingType, orderMapping, inverseOrderMapping, attributes, 0, options ); - return attributes; + final Object[] jdbcValues = ( (Struct) rawJdbcValue ).getAttributes(); + if ( orderMapping != null ) { + StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, jdbcValues.clone(), jdbcValues ); + } + wrapRawJdbcValues( embeddableMappingType, jdbcValues, 0, options ); + return jdbcValues; } @Override @@ -198,18 +207,21 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct return null; } final Struct struct = (Struct) object; - final Object[] values = struct.getAttributes(); + final Object[] jdbcValues = struct.getAttributes(); final boolean jdbcRepresentation = getJavaType().getJavaTypeClass() == Object[].class; if ( jdbcRepresentation ) { - wrapRawJdbcValues( embeddableMappingType, orderMapping, inverseOrderMapping, values, 0, options ); + if ( orderMapping != null ) { + StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, jdbcValues.clone(), jdbcValues ); + } + wrapRawJdbcValues( embeddableMappingType, jdbcValues, 0, options ); //noinspection unchecked - return (X) values; + return (X) jdbcValues; } assert embeddableMappingType != null && embeddableMappingType.getJavaType() == getJavaType(); final StructAttributeValues attributeValues = getAttributeValues( embeddableMappingType, orderMapping, - values, + jdbcValues, options ); //noinspection unchecked @@ -240,9 +252,8 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct else { attributeIndex = orderMapping[i]; } - final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, attributeIndex ); jdbcIndex += injectAttributeValue( - modelPart, + getEmbeddedPart( embeddableMappingType, attributeIndex ), attributeValues, attributeIndex, rawJdbcValues, @@ -381,117 +392,150 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct private int wrapRawJdbcValues( EmbeddableMappingType embeddableMappingType, - int[] orderMapping, - int[] inverseOrderMapping, Object[] jdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { - final Object[] targetJdbcValues; - if ( orderMapping == null ) { - targetJdbcValues = jdbcValues; - } - else { - targetJdbcValues = jdbcValues.clone(); - } final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); for ( int i = 0; i < numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) { - final ValuedModelPart attributeMapping; - if ( orderMapping == null ) { - attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, i ); + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); + if ( attributeMapping instanceof ToOneAttributeMapping ) { + final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; + if ( toOneAttributeMapping.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) { + continue; + } + final ForeignKeyDescriptor foreignKeyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor(); + final ValuedModelPart keyPart = foreignKeyDescriptor.getKeyPart(); + if ( keyPart instanceof BasicValuedMapping ) { + wrapRawJdbcValue( keyPart.getSingleJdbcMapping(), jdbcValues, jdbcIndex, options ); + jdbcIndex++; + } + else if ( keyPart instanceof EmbeddableValuedModelPart ) { + final EmbeddableMappingType mappingType = ( (EmbeddableValuedModelPart) keyPart ).getEmbeddableTypeDescriptor(); + jdbcIndex = wrapRawJdbcValues( + mappingType, + jdbcValues, + jdbcIndex, + options + ); + } + else { + throw new UnsupportedOperationException( "Unsupported foreign key part: " + keyPart ); + } } - else { - attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, orderMapping[i] ); + else if ( attributeMapping instanceof PluralAttributeMapping ) { + continue; } - final MappingType mappedType = attributeMapping.getMappedType(); - - if ( mappedType instanceof EmbeddableMappingType ) { - final EmbeddableMappingType embeddableType = (EmbeddableMappingType) mappedType; + else if ( attributeMapping instanceof DiscriminatedAssociationAttributeMapping ) { + final DiscriminatedAssociationAttributeMapping discriminatedAssociationAttributeMapping = (DiscriminatedAssociationAttributeMapping) attributeMapping; + wrapRawJdbcValue( + discriminatedAssociationAttributeMapping.getDiscriminatorPart().getSingleJdbcMapping(), + jdbcValues, + jdbcIndex, + options + ); + jdbcIndex++; + wrapRawJdbcValue( + discriminatedAssociationAttributeMapping.getKeyPart().getSingleJdbcMapping(), + jdbcValues, + jdbcIndex, + options + ); + jdbcIndex++; + } + else if ( attributeMapping instanceof EmbeddableValuedModelPart ) { + final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) attributeMapping; + final EmbeddableMappingType embeddableType = embeddableValuedModelPart.getMappedType(); if ( embeddableType.getAggregateMapping() != null ) { final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) embeddableType.getAggregateMapping() .getJdbcMapping() .getJdbcType(); - final Object rawJdbcValue = targetJdbcValues[jdbcIndex]; - targetJdbcValues[jdbcIndex] = aggregateJdbcType.extractJdbcValues( rawJdbcValue, options ); + final Object rawJdbcValue = jdbcValues[jdbcIndex]; + jdbcValues[jdbcIndex] = aggregateJdbcType.extractJdbcValues( rawJdbcValue, options ); jdbcIndex++; } else { - jdbcIndex = wrapRawJdbcValues( embeddableType, null, null, targetJdbcValues, jdbcIndex, options ); + jdbcIndex = wrapRawJdbcValues( embeddableType, jdbcValues, jdbcIndex, options ); } } else { assert attributeMapping.getJdbcTypeCount() == 1; - final Object rawJdbcValue = targetJdbcValues[jdbcIndex]; - if ( rawJdbcValue != null ) { - final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping(); - switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { - case SqlTypes.TIME_WITH_TIMEZONE: - case SqlTypes.TIME_UTC: - case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - case SqlTypes.TIMESTAMP_UTC: - // Only transform the raw jdbc value if it could be a TIMESTAMPTZ - targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType() - .wrap( transformRawJdbcValue( rawJdbcValue, options ), options ); - break; - case SqlTypes.ARRAY: - final BasicType elementType = ( (BasicPluralType) jdbcMapping ).getElementType(); - final JdbcType elementJdbcType = elementType.getJdbcType(); - final Object[] array; - final Object[] newArray; - switch ( elementJdbcType.getDefaultSqlTypeCode() ) { - case SqlTypes.TIME_WITH_TIMEZONE: - case SqlTypes.TIME_UTC: - case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - case SqlTypes.TIMESTAMP_UTC: - // Only transform the raw jdbc value if it could be a TIMESTAMPTZ - array = (Object[]) ((java.sql.Array) rawJdbcValue).getArray(); - newArray = new Object[array.length]; - for ( int j = 0; j < array.length; j++ ) { - newArray[j] = elementType.getJdbcJavaType().wrap( - transformRawJdbcValue( array[j], options ), - options - ); - } - targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options ); - break; - case SqlTypes.STRUCT: - case SqlTypes.JSON: - case SqlTypes.SQLXML: - array = (Object[]) ( (java.sql.Array) rawJdbcValue ).getArray(); - newArray = new Object[array.length]; - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; - final EmbeddableMappingType subEmbeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); - for ( int j = 0; j < array.length; j++ ) { - final StructAttributeValues subValues = StructHelper.getAttributeValues( - subEmbeddableMappingType, - aggregateJdbcType.extractJdbcValues( - array[j], - options - ), - options - ); - newArray[j] = instantiate( subEmbeddableMappingType, subValues, options.getSessionFactory() ); - } - targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options ); - break; - default: - targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options ); - break; - } - break; - default: - targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options ); - break; - } - } + wrapRawJdbcValue( attributeMapping.getSingleJdbcMapping(), jdbcValues, jdbcIndex, options ); jdbcIndex++; } } - if ( orderMapping != null ) { - StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, targetJdbcValues, jdbcValues ); - } return jdbcIndex; } + private void wrapRawJdbcValue( + JdbcMapping jdbcMapping, + Object[] jdbcValues, + int jdbcIndex, + WrapperOptions options) throws SQLException { + final Object rawJdbcValue = jdbcValues[jdbcIndex]; + if ( rawJdbcValue == null ) { + return; + } + switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) { + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + case SqlTypes.TIMESTAMP_UTC: + // Only transform the raw jdbc value if it could be a TIMESTAMPTZ + jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType() + .wrap( transformRawJdbcValue( rawJdbcValue, options ), options ); + break; + case SqlTypes.ARRAY: + final BasicType elementType = ( (BasicPluralType) jdbcMapping ).getElementType(); + final JdbcType elementJdbcType = elementType.getJdbcType(); + final Object[] array; + final Object[] newArray; + switch ( elementJdbcType.getDefaultSqlTypeCode() ) { + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + case SqlTypes.TIMESTAMP_UTC: + // Only transform the raw jdbc value if it could be a TIMESTAMPTZ + array = (Object[]) ((java.sql.Array) rawJdbcValue ).getArray(); + newArray = new Object[array.length]; + for ( int j = 0; j < array.length; j++ ) { + newArray[j] = elementType.getJdbcJavaType().wrap( + transformRawJdbcValue( array[j], options ), + options + ); + } + jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options ); + break; + case SqlTypes.STRUCT: + case SqlTypes.JSON: + case SqlTypes.SQLXML: + array = (Object[]) ( (java.sql.Array) rawJdbcValue ).getArray(); + newArray = new Object[array.length]; + final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; + final EmbeddableMappingType subEmbeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); + for ( int j = 0; j < array.length; j++ ) { + final StructAttributeValues subValues = StructHelper.getAttributeValues( + subEmbeddableMappingType, + aggregateJdbcType.extractJdbcValues( + array[j], + options + ), + options + ); + newArray[j] = instantiate( subEmbeddableMappingType, subValues, options.getSessionFactory() ); + } + jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options ); + break; + default: + jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options ); + break; + } + break; + default: + jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options ); + break; + } + } + protected Object transformRawJdbcValue(Object rawJdbcValue, WrapperOptions options) { return rawJdbcValue; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java index 2a148be6b0..f363feeb2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java @@ -493,7 +493,7 @@ public class XmlHelper { if ( array[i] == null ) { continue; } - final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i ); + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); if ( attributeMapping instanceof SelectableMapping ) { final SelectableMapping selectable = (SelectableMapping) attributeMapping; final String tagName = selectable.getSelectableName(); diff --git a/hibernate-core/src/main/java/org/hibernate/generator/values/GeneratedValueBasicResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/generator/values/GeneratedValueBasicResultBuilder.java index d573c506dc..5d87eef4a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/values/GeneratedValueBasicResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/values/GeneratedValueBasicResultBuilder.java @@ -94,7 +94,10 @@ public class GeneratedValueBasicResultBuilder implements ResultBuilder { return new BasicResult<>( sqlSelection.getValuesArrayPosition(), null, - modelPart.getJdbcMapping() + modelPart.getJdbcMapping(), + navigablePath, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java b/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java index de7e72f2bb..6391c978ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/AggregateColumn.java @@ -13,6 +13,7 @@ import org.hibernate.sql.Template; import static org.hibernate.type.SqlTypes.JSON_ARRAY; import static org.hibernate.type.SqlTypes.STRUCT_ARRAY; import static org.hibernate.type.SqlTypes.STRUCT_TABLE; +import static org.hibernate.type.SqlTypes.XML_ARRAY; /** * An aggregate column is a column of type {@link org.hibernate.type.SqlTypes#STRUCT}, @@ -79,7 +80,9 @@ public class AggregateColumn extends Column { final AggregateColumn parentAggregateColumn = component.getParentAggregateColumn(); final String simpleAggregateName = aggregateColumn.getQuotedName( dialect ); final String aggregateSelectableExpression; - if ( parentAggregateColumn == null ) { + // If the aggregate column is an array, drop the parent read expression, because this is a NestedColumnReference + // and will require special rendering + if ( parentAggregateColumn == null || isArray( aggregateColumn ) ) { aggregateSelectableExpression = getRootAggregateSelectableExpression( aggregateColumn, simpleAggregateName ); } else { @@ -99,13 +102,23 @@ public class AggregateColumn extends Column { } private static String getRootAggregateSelectableExpression(AggregateColumn aggregateColumn, String simpleAggregateName) { + if ( isArray( aggregateColumn ) ) { + return Template.TEMPLATE; + } + else { + return Template.TEMPLATE + "." + simpleAggregateName; + } + } + + private static boolean isArray(AggregateColumn aggregateColumn) { switch ( aggregateColumn.getTypeCode() ) { case JSON_ARRAY: + case XML_ARRAY: case STRUCT_ARRAY: case STRUCT_TABLE: - return Template.TEMPLATE; + return true; default: - return Template.TEMPLATE + "." + simpleAggregateName; + return false; } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 5f05330eb5..b506e49816 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -967,7 +967,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol return aggregateColumn == null ? jdbcTypeCode : getDialect().getAggregateSupport() - .aggregateComponentSqlTypeCode( aggregateColumn.getSqlTypeCode(), jdbcTypeCode ); + .aggregateComponentSqlTypeCode( aggregateColumn.getSqlTypeCode( getMetadata() ), jdbcTypeCode ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index 8813839186..97c3965204 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -24,6 +24,7 @@ import org.hibernate.Remove; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.ExportableProducer; +import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.model.source.internal.hbm.MappingDocument; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; @@ -93,7 +94,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable private AggregateColumn aggregateColumn; private AggregateColumn parentAggregateColumn; - private String structName; + private QualifiedName structName; private String[] structColumnNames; private transient Class componentClass; // lazily computed based on 'properties' field: invalidate by setting to null when properties are modified @@ -307,11 +308,11 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable this.parentAggregateColumn = parentAggregateColumn; } - public String getStructName() { + public QualifiedName getStructName() { return structName; } - public void setStructName(String structName) { + public void setStructName(QualifiedName structName) { this.structName = structName; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java index 802c0451ff..00f911d7b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java @@ -87,6 +87,8 @@ public interface CollectionPart extends ValuedModelPart, Fetchable, JavaTypedExp Nature getNature(); + PluralAttributeMapping getCollectionAttribute(); + @Override default String getPartName() { return getNature().getName(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java index a453df3e5d..9d6e3478b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java @@ -12,9 +12,11 @@ import java.util.function.BiConsumer; import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.internal.util.MutableInteger; +import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; import org.hibernate.property.access.spi.Getter; import org.hibernate.spi.NavigablePath; @@ -156,9 +158,43 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap int count = 0; for ( int i = 0; i < numberOfAttributeMappings; i++ ) { final AttributeMapping attributeMapping = getAttributeMapping( i ); - final MappingType mappedType = attributeMapping.getMappedType(); - if ( mappedType instanceof EmbeddableMappingType ) { - final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType; + if ( attributeMapping instanceof DiscriminatedAssociationAttributeMapping ) { + final DiscriminatedAssociationAttributeMapping discriminatedAssociationAttributeMapping = (DiscriminatedAssociationAttributeMapping) attributeMapping; + if ( count == columnIndex ) { + return discriminatedAssociationAttributeMapping.getDiscriminatorMapping(); + } + count++; + if ( count == columnIndex ) { + return discriminatedAssociationAttributeMapping.getKeyPart(); + } + count++; + } + else if ( attributeMapping instanceof ToOneAttributeMapping ) { + final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; + if ( toOneAttributeMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY ) { + final ValuedModelPart keyPart = toOneAttributeMapping.getForeignKeyDescriptor().getKeyPart(); + if ( keyPart instanceof BasicValuedMapping ) { + if ( count == columnIndex ) { + return (SelectableMapping) keyPart; + } + count++; + } + else if ( keyPart instanceof EmbeddableValuedModelPart ) { + final EmbeddableMappingType mappingType = ( (EmbeddableValuedModelPart) keyPart ).getEmbeddableTypeDescriptor(); + final SelectableMapping selectable = mappingType.getJdbcValueSelectable( columnIndex - count ); + if ( selectable != null ) { + return selectable; + } + count += mappingType.getJdbcValueCount(); + } + else { + throw new UnsupportedOperationException( "Unsupported foreign key part: " + keyPart ); + } + } + } + else if ( attributeMapping instanceof EmbeddableValuedModelPart ) { + final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) attributeMapping; + final EmbeddableMappingType embeddableMappingType = embeddableValuedModelPart.getMappedType(); final SelectableMapping aggregateMapping = embeddableMappingType.getAggregateMapping(); if ( aggregateMapping == null ) { final SelectableMapping subSelectable = embeddableMappingType.getJdbcValueSelectable( columnIndex - count ); @@ -176,7 +212,10 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap } else { if ( count == columnIndex ) { - return (SelectableMapping) attributeMapping; + if ( attributeMapping instanceof SelectableMapping ) { + return (SelectableMapping) attributeMapping; + } + assert attributeMapping.getJdbcTypeCount() == 0; } count += attributeMapping.getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java index 78f6e6d1a2..44ac352eb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java @@ -138,7 +138,7 @@ public interface PluralAttributeMapping TableGroup parentTableGroup, String resultVariable, DomainResultCreationState creationState) { - return new BasicResult( 0, null, getJavaType() ); + return new BasicResult( 0, null, getJavaType(), null, null, false, false ); } String getSeparateCollectionTable(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java index 7fc0678423..368800ca17 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java @@ -13,7 +13,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.metamodel.mapping.DiscriminatorConverter; import org.hibernate.metamodel.mapping.DiscriminatorType; -import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; @@ -124,7 +123,9 @@ public abstract class AbstractDiscriminatorMapping implements EntityDiscriminato resultVariable, discriminatorType.getJavaTypeDescriptor(), discriminatorType.getValueConverter(), - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } @@ -175,7 +176,10 @@ public abstract class AbstractDiscriminatorMapping implements EntityDiscriminato this, discriminatorType.getValueConverter(), fetchTiming, - creationState + true, + creationState, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java index 37728b47a7..5aebdd6d34 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java @@ -120,6 +120,11 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa return nature; } + @Override + public PluralAttributeMapping getCollectionAttribute() { + return collectionDescriptor.getAttributeMapping(); + } + @Override public String getFetchableName() { return nature.getName(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java index 3da235f4b5..85f3ea113c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java @@ -37,7 +37,6 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; -import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchOptions; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.basic.BasicFetch; @@ -59,6 +58,8 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions private final String table; private final String column; + private final String customReadExpression; + private final String customWriteExpression; private final String columnDefinition; private final Long length; private final Integer precision; @@ -75,7 +76,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions NavigableRole partRole, DiscriminatedAssociationModelPart declaringType, String table, - String column, + String column, String customReadExpression, String customWriteExpression, String columnDefinition, Long length, Integer precision, @@ -90,6 +91,8 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions this.declaringType = declaringType; this.table = table; this.column = column; + this.customReadExpression = customReadExpression; + this.customWriteExpression = customWriteExpression; this.columnDefinition = columnDefinition; this.length = length; this.precision = precision; @@ -160,12 +163,12 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions @Override public String getCustomReadExpression() { - return null; + return customReadExpression; } @Override public String getCustomWriteExpression() { - return null; + return customWriteExpression; } @Override @@ -326,7 +329,8 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions fetchablePath, this, fetchTiming, - creationState + creationState, + !sqlSelection.isVirtual() ); } @@ -351,7 +355,9 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping(), - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java index a2f7a976c0..4c20f90e60 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java @@ -48,6 +48,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions { private final String table; private final String column; private final DiscriminatedAssociationModelPart anyPart; + private final String customReadExpression; + private final String customWriteExpression; private final String columnDefinition; private final Long length; private final Integer precision; @@ -63,6 +65,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions { DiscriminatedAssociationModelPart anyPart, String table, String column, + String customReadExpression, + String customWriteExpression, String columnDefinition, Long length, Integer precision, @@ -76,6 +80,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions { this.table = table; this.column = column; this.anyPart = anyPart; + this.customReadExpression = customReadExpression; + this.customWriteExpression = customWriteExpression; this.columnDefinition = columnDefinition; this.length = length; this.precision = precision; @@ -124,12 +130,12 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions { @Override public String getCustomReadExpression() { - return null; + return customReadExpression; } @Override public String getCustomWriteExpression() { - return null; + return customWriteExpression; } @Override @@ -242,7 +248,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions { fetchablePath, this, fetchTiming, - creationState + creationState, + !sqlSelection.isVirtual() ); } @@ -333,7 +340,9 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions { sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping, - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index 8f0160ba89..1e7d61dc9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -337,7 +337,9 @@ public class BasicAttributeMapping sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping, - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } @@ -391,10 +393,12 @@ public class BasicAttributeMapping DomainResultCreationState creationState) { final int valuesArrayPosition; boolean coerceResultType = false; + final SqlSelection sqlSelection; if ( fetchTiming == FetchTiming.DELAYED && isLazy ) { // Lazy property. A valuesArrayPosition of -1 will lead to // returning a domain result assembler that returns LazyPropertyInitializer.UNFETCHED_PROPERTY valuesArrayPosition = -1; + sqlSelection = null; } else { final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); @@ -404,7 +408,7 @@ public class BasicAttributeMapping assert tableGroup != null; - final SqlSelection sqlSelection = resolveSqlSelection( + sqlSelection = resolveSqlSelection( fetchablePath, tableGroup, fetchParent, @@ -422,9 +426,12 @@ public class BasicAttributeMapping fetchParent, fetchablePath, this, + getJdbcMapping().getValueConverter(), fetchTiming, + true, creationState, - coerceResultType + coerceResultType, + sqlSelection != null && !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java index a77d0c1c84..5b518b6834 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java @@ -231,7 +231,9 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa sqlSelection.getValuesArrayPosition(), resultVariable, entityPersister.getIdentifierMapping().getSingleJdbcMapping(), - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } @@ -425,10 +427,13 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa fetchParent, fetchablePath, this, + getJdbcMapping().getValueConverter(), FetchTiming.IMMEDIATE, + true, creationState, // if the expression type is different that the expected type coerce the value - selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != getJdbcMapping().getJdbcJavaType() + selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != getJdbcMapping().getJdbcJavaType(), + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java index 9e1d0349f3..ead1f605f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java @@ -17,6 +17,7 @@ import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.model.domain.NavigableRole; @@ -66,6 +67,11 @@ public class BasicValuedCollectionPart return nature; } + @Override + public PluralAttributeMapping getCollectionAttribute() { + return collectionDescriptor.getAttributeMapping(); + } + @Override public MappingType getPartMappingType() { return selectableMapping.getJdbcMapping()::getJavaTypeDescriptor; @@ -168,7 +174,9 @@ public class BasicValuedCollectionPart sqlSelection.getValuesArrayPosition(), resultVariable, selectableMapping.getJdbcMapping(), - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } @@ -280,7 +288,8 @@ public class BasicValuedCollectionPart fetchablePath, this, FetchTiming.IMMEDIATE, - creationState + creationState, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java index 29df053d46..a803297b3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java @@ -18,6 +18,7 @@ import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; @@ -65,6 +66,11 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD return Nature.ID; } + @Override + public PluralAttributeMapping getCollectionAttribute() { + return collectionDescriptor.getAttributeMapping(); + } + @Override public String getContainingTableExpression() { return containingTableName; @@ -278,7 +284,8 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD fetchablePath, this, FetchTiming.IMMEDIATE, - creationState + creationState, + !sqlSelection.isVirtual() ); } @@ -305,7 +312,9 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD sqlSelection.getValuesArrayPosition(), null, type, - collectionPath + collectionPath, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java index bee024f0b0..43dbd65b91 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java @@ -80,6 +80,8 @@ public class DiscriminatedAssociationMapping implements MappingType, FetchOption declaringModelPart, tableName, metaColumn.getText( dialect ), + metaColumn.getCustomReadExpression(), + metaColumn.getCustomWriteExpression(), metaColumn.getSqlType(), metaColumn.getLength(), metaColumn.getPrecision(), @@ -99,6 +101,8 @@ public class DiscriminatedAssociationMapping implements MappingType, FetchOption declaringModelPart, tableName, keyColumn.getText( dialect ), + keyColumn.getCustomReadExpression(), + keyColumn.getCustomWriteExpression(), keyColumn.getSqlType(), keyColumn.getLength(), keyColumn.getPrecision(), diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java index a055fb12e3..280c782c9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java @@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.model.domain.NavigableRole; @@ -80,6 +81,11 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode return nature; } + @Override + public PluralAttributeMapping getCollectionAttribute() { + return collectionDescriptor.getAttributeMapping(); + } + @Override public DiscriminatorMapping getDiscriminatorMapping() { return associationMapping.getDiscriminatorPart(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java index 0dfa4e526b..a5ac378070 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java @@ -292,7 +292,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme case STRUCT_TABLE: isArray = true; aggregateSqlTypeCode = STRUCT; - structTypeName = bootDescriptor.getStructName(); + structTypeName = bootDescriptor.getStructName().render(); if ( structTypeName == null ) { final String arrayTypeName = aggregateColumn.getSqlType( creationContext.getMetadata() ); if ( arrayTypeName.endsWith( " array" ) ) { @@ -322,7 +322,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme ); // Register the resolved type under its struct name and java class name if ( bootDescriptor.getStructName() != null ) { - basicTypeRegistry.register( basicType, bootDescriptor.getStructName() ); + basicTypeRegistry.register( basicType, bootDescriptor.getStructName().render() ); basicTypeRegistry.register( basicType, getMappedJavaType().getJavaTypeClass().getName() ); } final BasicValue basicValue = (BasicValue) aggregateColumn.getValue(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java index 61ebf724eb..190b5fc138 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java @@ -94,6 +94,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF this.sqlAliasStem = sqlAliasStem; } + @Override + public PluralAttributeMapping getCollectionAttribute() { + return collectionDescriptor.getAttributeMapping(); + } + @Override public DomainResult createDomainResult( NavigablePath navigablePath, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java index 9b1d52f518..5e9a8a3912 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java @@ -108,7 +108,9 @@ public class EntityRowIdMappingImpl implements EntityRowIdMapping { sqlSelection.getValuesArrayPosition(), resultVariable, rowIdType, - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java index c505976e65..53075cd16e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java @@ -263,7 +263,8 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti fetchablePath, this, fetchTiming, - creationState + creationState, + !sqlSelection.isVirtual() ); } @@ -279,7 +280,9 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti sqlSelection.getValuesArrayPosition(), resultVariable, versionBasicType, - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 3c162bc550..4d6714dcee 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -384,8 +384,10 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa sqlSelection.getValuesArrayPosition(), null, selectableMapping.getJdbcMapping(), + navigablePath, // if the expression type is different that the expected type coerce the value - selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != javaType + selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != javaType, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SoftDeleteMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SoftDeleteMappingImpl.java index e19bb38ee6..044bfd401b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SoftDeleteMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SoftDeleteMappingImpl.java @@ -163,7 +163,14 @@ public class SoftDeleteMappingImpl implements SoftDeleteMapping { String resultVariable, DomainResultCreationState creationState) { final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState ); - return new BasicResult<>( sqlSelection.getValuesArrayPosition(), resultVariable, getJdbcMapping() ); + return new BasicResult<>( + sqlSelection.getValuesArrayPosition(), + resultVariable, + getJdbcMapping(), + navigablePath, + false, + !sqlSelection.isVirtual() + ); } private SqlSelection resolveSqlSelection( diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java index 62139e2bd2..b8ab1118e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java @@ -18,6 +18,7 @@ import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.graph.basic.BasicResultAssembler; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -69,7 +70,10 @@ public class ScalarDomainResultBuilder implements ResultBuilder { return new BasicResult<>( sqlSelection.getValuesArrayPosition(), null, - ( (BasicType) sqlSelection.getExpressionType() ) + (BasicType) sqlSelection.getExpressionType(), + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java index c2b576056b..0181fa9c29 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java @@ -218,7 +218,9 @@ public class AnonymousTupleBasicValuedModelPart implements OwnedValuedModelPart, sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping, - navigablePath + navigablePath, + false, + !sqlSelection.isVirtual() ); } @@ -270,7 +272,8 @@ public class AnonymousTupleBasicValuedModelPart implements OwnedValuedModelPart, fetchablePath, this, fetchTiming, - creationState + creationState, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java index 949a3956d5..132d5ad2df 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java @@ -31,6 +31,7 @@ import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.graph.basic.BasicResultAssembler; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; @@ -328,7 +329,10 @@ public class ResultSetMappingImpl implements ResultSetMapping { return new BasicResult( valuesArrayPosition, name, - jdbcMapping + jdbcMapping, + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java index db5a419731..7295dd609a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java @@ -13,9 +13,7 @@ import org.hibernate.query.results.ResultsHelper; import org.hibernate.spi.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.ResultBuilder; -import org.hibernate.query.results.ResultSetMappingSqlSelection; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; -import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; @@ -24,7 +22,6 @@ import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import static org.hibernate.query.results.ResultsHelper.impl; -import static org.hibernate.query.results.ResultsHelper.jdbcPositionToValuesArrayPosition; /** * CompleteResultBuilder for basic-valued ModelParts @@ -93,7 +90,10 @@ public class CompleteResultBuilderBasicModelPart return new BasicResult<>( sqlSelection.getValuesArrayPosition(), columnAlias, - modelPart.getJdbcMapping() + modelPart.getJdbcMapping(), + navigablePath, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java index 643e935d05..e4d5a99dbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java @@ -111,7 +111,10 @@ public class CompleteResultBuilderBasicValuedConverted implements CompleteR sqlSelection.getValuesArrayPosition(), columnName, valueConverter.getDomainJavaType(), - valueConverter + valueConverter, + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java index 98feec0e5b..1191163f97 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java @@ -112,7 +112,10 @@ public class CompleteResultBuilderBasicValuedStandard implements CompleteResultB return new BasicResult<>( sqlSelection.getValuesArrayPosition(), columnName, - jdbcMapping + jdbcMapping, + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java index 62dfaa8624..94b1df45c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java @@ -19,6 +19,7 @@ import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.basic.BasicResultAssembler; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; /** @@ -69,7 +70,9 @@ public class DelayedFetchBuilderBasicPart null, FetchTiming.DELAYED, isEnhancedForLazyLoading, - domainResultCreationState + domainResultCreationState, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java index 6c5750859e..dbcd00651e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java @@ -14,7 +14,6 @@ import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.ResultSetMappingSqlSelection; -import org.hibernate.query.results.ResultsHelper; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; @@ -103,7 +102,10 @@ public class DynamicResultBuilderAttribute implements DynamicResultBuilder, Nati return new BasicResult<>( sqlSelection.getValuesArrayPosition(), columnAlias, - attributeMapping.getJdbcMapping() + attributeMapping.getJdbcMapping(), + null, + false, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java index 8f0d0aa9c3..1b617fe59e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java @@ -141,7 +141,10 @@ public class DynamicResultBuilderBasicConverted implements DynamicResultBui sqlSelection.getValuesArrayPosition(), columnAlias, basicValueConverter.getDomainJavaType(), - basicValueConverter + basicValueConverter, + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java index d970a2d5b4..cc17a34425 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java @@ -180,7 +180,15 @@ public class DynamicResultBuilderBasicStandard implements DynamicResultBuilderBa // StandardRowReader expects there to be a JavaType as part of the ResultAssembler. assert javaType != null; - return new BasicResult( sqlSelection.getValuesArrayPosition(), resultAlias, javaType, converter ); + return new BasicResult( + sqlSelection.getValuesArrayPosition(), + resultAlias, + javaType, + converter, + null, + false, + false + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java index 3860ec5ee2..ee79de00c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java @@ -17,7 +17,6 @@ import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.ResultsHelper; -import org.hibernate.query.results.ResultSetMappingSqlSelection; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.Expression; @@ -28,7 +27,6 @@ import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import static org.hibernate.query.results.ResultsHelper.impl; -import static org.hibernate.query.results.ResultsHelper.jdbcPositionToValuesArrayPosition; import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; /** @@ -118,7 +116,8 @@ public class ImplicitFetchBuilderBasic implements ImplicitFetchBuilder, BasicVal fetchPath, fetchable, FetchTiming.IMMEDIATE, - domainResultCreationState + domainResultCreationState, + !sqlSelection.isVirtual() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingFunctionSqlAstExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingFunctionSqlAstExpression.java index 60e3f89380..cbb4784557 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingFunctionSqlAstExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingFunctionSqlAstExpression.java @@ -174,7 +174,10 @@ public class SelfRenderingFunctionSqlAstExpression .getValuesArrayPosition(), resultVariable, type == null ? null : type.getExpressibleJavaType(), - converter + converter, + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/ExpressionDomainResultProducer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/ExpressionDomainResultProducer.java index cd18d6a1ce..048f37106f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/ExpressionDomainResultProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/ExpressionDomainResultProducer.java @@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.graph.basic.BasicResultAssembler; /** * A wrapper around a basic {@link Expression} that produces a {@link BasicResult} as domain result. @@ -33,7 +34,9 @@ public class ExpressionDomainResultProducer implements DomainResultProducer selectionMap; if ( deduplicateSelectionItems ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java index 65444717cf..c4fb775d72 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmParameterInterpretation.java @@ -108,7 +108,10 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu sqlSelection.getValuesArrayPosition(), resultVariable, jdbcMapping.getMappedJavaType(), - converter + converter, + null, + false, + false ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java index 91ca555eb6..d11cccfed8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java @@ -38,26 +38,6 @@ public class BasicFetch implements Fetch, BasicResultGraphNode { private final FetchTiming fetchTiming; - public BasicFetch( - int valuesArrayPosition, - FetchParent fetchParent, - NavigablePath fetchablePath, - BasicValuedModelPart valuedMapping, - FetchTiming fetchTiming, - DomainResultCreationState creationState) { - //noinspection unchecked - this( - valuesArrayPosition, - fetchParent, - fetchablePath, - valuedMapping, - (BasicValueConverter) valuedMapping.getJdbcMapping().getValueConverter(), - fetchTiming, - true, - creationState - ); - } - public BasicFetch( int valuesArrayPosition, FetchParent fetchParent, @@ -65,7 +45,7 @@ public class BasicFetch implements Fetch, BasicResultGraphNode { BasicValuedModelPart valuedMapping, FetchTiming fetchTiming, DomainResultCreationState creationState, - boolean coerceResultType) { + boolean unwrapRowProcessingState) { //noinspection unchecked this( valuesArrayPosition, @@ -76,49 +56,8 @@ public class BasicFetch implements Fetch, BasicResultGraphNode { fetchTiming, true, creationState, - coerceResultType - ); - } - - public BasicFetch( - int valuesArrayPosition, - FetchParent fetchParent, - NavigablePath fetchablePath, - BasicValuedModelPart valuedMapping, - BasicValueConverter valueConverter, - FetchTiming fetchTiming, - DomainResultCreationState creationState) { - this( - valuesArrayPosition, - fetchParent, - fetchablePath, - valuedMapping, - valueConverter, - fetchTiming, - true, - creationState - ); - } - - public BasicFetch( - int valuesArrayPosition, - FetchParent fetchParent, - NavigablePath fetchablePath, - BasicValuedModelPart valuedMapping, - BasicValueConverter valueConverter, - FetchTiming fetchTiming, - boolean canBasicPartFetchBeDelayed, - DomainResultCreationState creationState) { - this( - valuesArrayPosition, - fetchParent, - fetchablePath, - valuedMapping, - valueConverter, - fetchTiming, - canBasicPartFetchBeDelayed, - creationState, - false + false, + unwrapRowProcessingState ); } @@ -131,7 +70,8 @@ public class BasicFetch implements Fetch, BasicResultGraphNode { FetchTiming fetchTiming, boolean canBasicPartFetchBeDelayed, DomainResultCreationState creationState, - boolean coerceResultType) { + boolean coerceResultType, + boolean unwrapRowProcessingState) { this.navigablePath = fetchablePath; this.fetchParent = fetchParent; @@ -149,10 +89,10 @@ public class BasicFetch implements Fetch, BasicResultGraphNode { } else { if (coerceResultType) { - this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState ); } else { - this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java index bd1923b845..e0c3124069 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java @@ -40,7 +40,9 @@ public class BasicResult implements DomainResult, BasicResultGraphNode jdbcValuesArrayPosition, resultVariable, jdbcMapping, - null + null, + false, + false ); } @@ -48,81 +50,38 @@ public class BasicResult implements DomainResult, BasicResultGraphNode int jdbcValuesArrayPosition, String resultVariable, JdbcMapping jdbcMapping, - boolean coerceResultType) { + NavigablePath navigablePath, + boolean coerceResultType, + boolean unwrapRowProcessingState) { //noinspection unchecked this( jdbcValuesArrayPosition, resultVariable, jdbcMapping.getJavaTypeDescriptor(), jdbcMapping.getValueConverter(), - null, - coerceResultType + navigablePath, + coerceResultType, + unwrapRowProcessingState ); } - public BasicResult( - int jdbcValuesArrayPosition, - String resultVariable, - JdbcMapping jdbcMapping, - NavigablePath navigablePath) { - //noinspection unchecked - this( - jdbcValuesArrayPosition, - resultVariable, - (JavaType) jdbcMapping.getJavaTypeDescriptor(), - (BasicValueConverter) jdbcMapping.getValueConverter(), - navigablePath - ); - } - - public BasicResult( - int jdbcValuesArrayPosition, - String resultVariable, - JavaType javaType) { - this( jdbcValuesArrayPosition, resultVariable, javaType, null, null ); - } - - public BasicResult( - int jdbcValuesArrayPosition, - String resultVariable, - JavaType javaType, - NavigablePath navigablePath) { - this( jdbcValuesArrayPosition, resultVariable, javaType, null, navigablePath ); - } - - public BasicResult( - int valuesArrayPosition, - String resultVariable, - JavaType javaType, - BasicValueConverter valueConverter) { - this( valuesArrayPosition, resultVariable, javaType, valueConverter, null ); - } - - public BasicResult( - int valuesArrayPosition, - String resultVariable, - JavaType javaType, - BasicValueConverter valueConverter, - NavigablePath navigablePath) { - this( valuesArrayPosition, resultVariable, javaType, valueConverter, navigablePath, false ); - } - public BasicResult( int valuesArrayPosition, String resultVariable, JavaType javaType, BasicValueConverter valueConverter, NavigablePath navigablePath, - boolean coerceResultType) { + boolean coerceResultType, + boolean unwrapRowProcessingState) { this.resultVariable = resultVariable; this.javaType = javaType; this.navigablePath = navigablePath; if ( coerceResultType ) { - this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState ); } else { - this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java index bda3134d26..2274c845ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java @@ -29,26 +29,30 @@ public class BasicResultAssembler implements DomainResultAssembler { protected final int valuesArrayPosition; protected final JavaType assembledJavaType; private final BasicValueConverter valueConverter; + private final boolean unwrapRowProcessingState; - public BasicResultAssembler( - int valuesArrayPosition, - JavaType assembledJavaType) { - this( valuesArrayPosition, assembledJavaType, null ); + public BasicResultAssembler(int valuesArrayPosition, JavaType assembledJavaType) { + this( valuesArrayPosition, assembledJavaType, null, false ); } public BasicResultAssembler( int valuesArrayPosition, JavaType assembledJavaType, - BasicValueConverter valueConverter) { + BasicValueConverter valueConverter, + boolean unwrapRowProcessingState) { this.valuesArrayPosition = valuesArrayPosition; this.assembledJavaType = assembledJavaType; this.valueConverter = valueConverter; + this.unwrapRowProcessingState = unwrapRowProcessingState; } /** * Access to the raw value (unconverted, if a converter applied) */ public Object extractRawValue(RowProcessingState rowProcessingState) { + if ( unwrapRowProcessingState ) { + rowProcessingState = rowProcessingState.unwrap(); + } return rowProcessingState.getJdbcValue( valuesArrayPosition ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java index 810355cd66..9e164ec1ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java @@ -21,8 +21,9 @@ public class CoercingResultAssembler extends BasicResultAssembler { public CoercingResultAssembler( int valuesArrayPosition, JavaType assembledJavaType, - BasicValueConverter valueConverter) { - super( valuesArrayPosition, assembledJavaType, valueConverter ); + BasicValueConverter valueConverter, + boolean nestedInAggregateComponent) { + super( valuesArrayPosition, assembledJavaType, valueConverter, nestedInAggregateComponent ); } /** @@ -31,7 +32,7 @@ public class CoercingResultAssembler extends BasicResultAssembler { @Override public Object extractRawValue(RowProcessingState rowProcessingState) { return assembledJavaType.coerce( - rowProcessingState.getJdbcValue( valuesArrayPosition ), + super.extractRawValue( rowProcessingState ), rowProcessingState.getSession() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java index f2e3a8b60b..d7f10cb6c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableExpressionResultImpl.java @@ -74,7 +74,8 @@ public class EmbeddableExpressionResultImpl extends AbstractFetchParent imple resolveNavigablePath( attribute ), attribute, FetchTiming.IMMEDIATE, - creationState + creationState, + !sqlSelection.isVirtual() ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/NestedRowProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/NestedRowProcessingState.java index 3c2dde2c91..9a0a42a627 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/NestedRowProcessingState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/NestedRowProcessingState.java @@ -49,6 +49,11 @@ public class NestedRowProcessingState extends BaseExecutionContext implements Ro return jdbcValue == null ? null : jdbcValue[position]; } + @Override + public RowProcessingState unwrap() { + return processingState; + } + // -- delegate the rest @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/RowProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/RowProcessingState.java index 3ca39c789a..ef03f9ef09 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/RowProcessingState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/RowProcessingState.java @@ -68,4 +68,12 @@ public interface RowProcessingState extends ExecutionContext { finishRowProcessing(); } + /** + * If this is a row processing state for aggregate components, + * this will return the underlying row processing state. + */ + default RowProcessingState unwrap() { + return this; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java index e8487b35e7..20e2f404dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java @@ -76,7 +76,7 @@ public class BooleanJavaType extends AbstractClassJavaType implements if ( value == null ) { return null; } - if ( Boolean.class.isAssignableFrom( type ) ) { + if ( Boolean.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Byte.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java index 58eb36910e..ab5c962dd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java @@ -47,7 +47,7 @@ public class ByteJavaType extends AbstractClassJavaType if ( value == null ) { return null; } - if ( Byte.class.isAssignableFrom( type ) ) { + if ( Byte.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Short.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java index a523a877e0..d2e5395f26 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java @@ -44,7 +44,7 @@ public class CharacterJavaType extends AbstractClassJavaType implemen if ( value == null ) { return null; } - if ( Character.class.isAssignableFrom( type ) ) { + if ( Character.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( String.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java index 6171dba2f7..be7a830dff 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java @@ -51,7 +51,7 @@ public class DoubleJavaType extends AbstractClassJavaType implements if ( value == null ) { return null; } - if ( Double.class.isAssignableFrom( type ) ) { + if ( Double.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Float.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java index 6078aa5e55..2de98d30bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatJavaType.java @@ -50,7 +50,7 @@ public class FloatJavaType extends AbstractClassJavaType implements Primi if ( value == null ) { return null; } - if ( Float.class.isAssignableFrom( type ) ) { + if ( Float.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Double.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java index ce239f2632..c20c660857 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaType.java @@ -47,7 +47,7 @@ public class IntegerJavaType extends AbstractClassJavaType if ( value == null ) { return null; } - if ( Integer.class.isAssignableFrom( type ) ) { + if ( Integer.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Byte.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaType.java index 09375859c9..c6c4409391 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaType.java @@ -47,7 +47,7 @@ public class LongJavaType extends AbstractClassJavaType if ( value == null ) { return null; } - if ( Long.class.isAssignableFrom( type ) ) { + if ( Long.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Byte.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaType.java index 0357ad73c6..f611c9dc9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaType.java @@ -57,7 +57,7 @@ public class ShortJavaType extends AbstractClassJavaType if ( value == null ) { return null; } - if ( Short.class.isAssignableFrom( type ) ) { + if ( Short.class.isAssignableFrom( type ) || type == Object.class ) { return (X) value; } if ( Byte.class.isAssignableFrom( type ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java index 63ccde770a..d4c005df98 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java @@ -20,7 +20,6 @@ import org.hibernate.dialect.StructHelper; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.type.BasicPluralType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -107,23 +106,58 @@ public class ArrayJdbcType implements JdbcType { @Override public Class getPreferredJavaTypeClass(WrapperOptions options) { - return java.sql.Array.class; + return Object[].class; } - protected Object[] getArray(BasicBinder binder, Object value, WrapperOptions options) throws SQLException { + protected String getElementTypeName(JavaType javaType, SharedSessionContractImplementor session) { + // TODO: ideally, we would have the actual size or the actual type/column accessible + // this is something that we would need for supporting composite types anyway + if ( elementJdbcType instanceof StructJdbcType ) { + return ( (StructJdbcType) elementJdbcType ).getStructTypeName(); + } + final JavaType elementJavaType; + if ( javaType instanceof ByteArrayJavaType ) { + // Special handling needed for Byte[], because that would conflict with the VARBINARY mapping + elementJavaType = ByteJavaType.INSTANCE; + } + else { + elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); + } + final Size size = session.getJdbcServices() + .getDialect() + .getSizeStrategy() + .resolveSize( elementJdbcType, elementJavaType, null, null, null ); + final DdlTypeRegistry ddlTypeRegistry = session.getTypeConfiguration().getDdlTypeRegistry(); + final String typeName = ddlTypeRegistry.getDescriptor( elementJdbcType.getDdlTypeCode() ) + .getTypeName( size, new BasicTypeImpl<>( elementJavaType, elementJdbcType), ddlTypeRegistry ); + int cutIndex = typeName.indexOf( '(' ); + if ( cutIndex > 0 ) { + // getTypeName for this case required length, etc, parameters. + // Cut them out and use database defaults. + return typeName.substring( 0, cutIndex ); + } + else { + return typeName; + } + } + + protected Object[] getArray( + BasicBinder binder, + ValueBinder elementBinder, + Object value, + WrapperOptions options) throws SQLException { final JdbcType elementJdbcType = ( (ArrayJdbcType) binder.getJdbcType() ).getElementJdbcType(); //noinspection unchecked final JavaType javaType = (JavaType) binder.getJavaType(); if ( elementJdbcType instanceof AggregateJdbcType ) { - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; - final Object[] domainObjects = ( javaType ).unwrap( + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); final Object[] objects = new Object[domainObjects.length]; for ( int i = 0; i < domainObjects.length; i++ ) { - objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); + objects[i] = elementBinder.getBindValue( domainObjects[i], options ); } return objects; } @@ -174,6 +208,8 @@ public class ArrayJdbcType implements JdbcType { @Override public ValueBinder getBinder(final JavaType javaTypeDescriptor) { + //noinspection unchecked + final ValueBinder elementBinder = elementJdbcType.getBinder( ( (BasicPluralJavaType) javaTypeDescriptor ).getElementJavaType() ); return new BasicBinder<>( javaTypeDescriptor, this ) { @Override @@ -193,48 +229,19 @@ public class ArrayJdbcType implements JdbcType { } } - private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { - final JdbcType elementJdbcType = ( (ArrayJdbcType) getJdbcType() ).getElementJdbcType(); - final Object[] objects = ArrayJdbcType.this.getArray( this, value, options ); - - final SharedSessionContractImplementor session = options.getSession(); - final String typeName = getElementTypeName( elementJdbcType, session ); - return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() - .createArrayOf( typeName, objects ); + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return ( (ArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options ); } - private String getElementTypeName(JdbcType elementJdbcType, SharedSessionContractImplementor session) { - // TODO: ideally, we would have the actual size or the actual type/column accessible - // this is something that we would need for supporting composite types anyway - if ( elementJdbcType instanceof StructJdbcType ) { - return ( (StructJdbcType) elementJdbcType ).getStructTypeName(); - } - final JavaType elementJavaType; - if ( getJavaType() instanceof ByteArrayJavaType ) { - // Special handling needed for Byte[], because that would conflict with the VARBINARY mapping - //noinspection unchecked - elementJavaType = (JavaType) ByteJavaType.INSTANCE; - } - else { - //noinspection unchecked - elementJavaType = ( (BasicPluralJavaType) getJavaType() ).getElementJavaType(); - } - final Size size = session.getJdbcServices() - .getDialect() - .getSizeStrategy() - .resolveSize( elementJdbcType, elementJavaType, null, null, null ); - final DdlTypeRegistry ddlTypeRegistry = session.getTypeConfiguration().getDdlTypeRegistry(); - final String typeName = ddlTypeRegistry.getDescriptor( elementJdbcType.getDdlTypeCode() ) - .getTypeName( size, new BasicTypeImpl<>( elementJavaType, elementJdbcType), ddlTypeRegistry ); - int cutIndex = typeName.indexOf( '(' ); - if ( cutIndex > 0 ) { - // getTypeName for this case required length, etc, parameters. - // Cut them out and use database defaults. - return typeName.substring( 0, cutIndex ); - } - else { - return typeName; - } + private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { + final ArrayJdbcType arrayJdbcType = (ArrayJdbcType) getJdbcType(); + final Object[] objects = arrayJdbcType.getArray( this, elementBinder, value, options ); + + final SharedSessionContractImplementor session = options.getSession(); + final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session ); + return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() + .createArrayOf( typeName, objects ); } }; } @@ -266,7 +273,7 @@ public class ArrayJdbcType implements JdbcType { @Override public String toString() { - return "ArrayTypeDescriptor"; + return "ArrayTypeDescriptor(" + getElementJdbcType().toString() + ")"; } /** diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentCollectionErrorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentAssociationErrorTest.java similarity index 61% rename from hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentCollectionErrorTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentAssociationErrorTest.java index 2d4595b88b..7347be6785 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentCollectionErrorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentAssociationErrorTest.java @@ -20,18 +20,21 @@ import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; +@JiraKey( "HHH-15831" ) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) -public class StructComponentCollectionErrorTest { +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructuralArrays.class) +public class StructComponentAssociationErrorTest { @Test - @JiraKey( "HHH-15831" ) - public void testError1() { + public void testOneToOneMappedBy() { final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry(); try { new MetadataSources( ssr ) @@ -41,7 +44,7 @@ public class StructComponentCollectionErrorTest { Assertions.fail( "Expected a failure" ); } catch (MappingException ex) { - assertThat( ex.getMessage(), containsString( "author.favoriteBook" ) ); + assertThat( ex.getMessage(), containsString( "authors.favoriteBook" ) ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); @@ -56,19 +59,21 @@ public class StructComponentCollectionErrorTest { @GeneratedValue private Long id; private String title; - private Person1 author; + private Person1[] authors; + @OneToOne(fetch = FetchType.LAZY) + private Book1 favoredBook; } @Embeddable @Struct(name = "person_type") public static class Person1 { private String name; - @ManyToOne(fetch = FetchType.LAZY) + @OneToOne(mappedBy = "favoredBook", fetch = FetchType.LAZY) private Book1 favoriteBook; } + @Test - @JiraKey( "HHH-15831" ) - public void testError2() { + public void testOneToManyMappedBy() { final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry(); try { new MetadataSources( ssr ) @@ -78,7 +83,7 @@ public class StructComponentCollectionErrorTest { Assertions.fail( "Expected a failure" ); } catch (MappingException ex) { - assertThat( ex.getMessage(), containsString( "author.bookCollection" ) ); + assertThat( ex.getMessage(), containsString( "authors.bookCollection" ) ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); @@ -93,15 +98,54 @@ public class StructComponentCollectionErrorTest { @GeneratedValue private Long id; private String title; - private Person2 author; + private Person2[] authors; + @ManyToOne(fetch = FetchType.LAZY) + private Book2 mainBook; } @Embeddable @Struct(name = "person_type") public static class Person2 { private String name; - @OneToMany + @OneToMany(mappedBy = "mainBook") private List bookCollection; } + @Test + public void testOneToMany() { + final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry(); + try { + new MetadataSources( ssr ) + .addAnnotatedClass( Book3.class ) + .getMetadataBuilder() + .build(); + Assertions.fail( "Expected a failure" ); + } + catch (MappingException ex) { + assertThat( ex.getMessage(), containsString( "authors.bookCollection" ) ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + + @Entity(name = "Book") + public static class Book3 { + + @Id + @GeneratedValue + private Long id; + private String title; + private Person3[] authors; + } + + @Embeddable + @Struct(name = "person_type") + public static class Person3 { + private String name; + @OneToMany + private List bookCollection; + } + } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToAnyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToAnyTest.java new file mode 100644 index 0000000000..d13c2f5645 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToAnyTest.java @@ -0,0 +1,153 @@ +package org.hibernate.orm.test.component; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentManyToAnyTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentManyToAnyTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Hibernate 3"; + book1.author = new Author( "Gavin", null ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Hibernate 6"; + book2.author = new Author( "Steve", book1 ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @Any(fetch = FetchType.LAZY) + @AnyKeyJavaClass(Long.class) + @AnyDiscriminatorValue(entity = Book.class, discriminator = "B") + @JoinColumn(name = "favorite_book_id") + @Column(name = "favorite_book_type") + private Object favoriteBook; + + public Author() { + } + + public Author(String name, Object favoriteBook) { + this.name = name; + this.favoriteBook = favoriteBook; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Book getFavoriteBook() { + return (Book) favoriteBook; + } + + public void setFavoriteBook(Book favoriteBook) { + this.favoriteBook = favoriteBook; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToManyMappedByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToManyMappedByTest.java new file mode 100644 index 0000000000..da4e3f3118 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToManyMappedByTest.java @@ -0,0 +1,173 @@ +package org.hibernate.orm.test.component; + +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentManyToManyMappedByTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentManyToManyMappedByTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Main book"; + book1.author = new Author( "Abc" ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Second book"; + book2.booksInSeries = Set.of( book1 ); + book2.author = new Author( "Abc" ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 1", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.books where b.id = 1", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + @ManyToMany + private Set booksInSeries; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Set getBooksInSeries() { + return booksInSeries; + } + + public void setBooksInSeries(Set booksInSeries) { + this.booksInSeries = booksInSeries; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @ManyToMany(mappedBy = "booksInSeries") + private Set books; + + public Author() { + } + + public Author(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToManyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToManyTest.java new file mode 100644 index 0000000000..0a23dada1f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToManyTest.java @@ -0,0 +1,161 @@ +package org.hibernate.orm.test.component; + +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentManyToManyTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentManyToManyTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Main book"; + book1.author = new Author( "Abc", null ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Second book"; + book2.author = new Author( "Abc", book1 ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Main book", book.author.getBooks().iterator().next().getTitle() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.books where b.id = 2", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Main book", book.author.getBooks().iterator().next().getTitle() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @ManyToMany + private Set books; + + public Author() { + } + + public Author(String name, Book book) { + this.name = name; + this.books = book == null ? null : Set.of( book ); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToOneTest.java new file mode 100644 index 0000000000..c267d31fde --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToOneTest.java @@ -0,0 +1,159 @@ +package org.hibernate.orm.test.component; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentManyToOneTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentManyToOneTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Hibernate 3"; + book1.author = new Author( "Gavin", null ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Hibernate 6"; + book2.author = new Author( "Steve", book1 ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.favoriteBook where b.id = 2", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @ManyToOne(fetch = FetchType.LAZY) + private Book favoriteBook; + + public Author() { + } + + public Author(String name, Book favoriteBook) { + this.name = name; + this.favoriteBook = favoriteBook; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Book getFavoriteBook() { + return favoriteBook; + } + + public void setFavoriteBook(Book favoriteBook) { + this.favoriteBook = favoriteBook; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToManyMappedByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToManyMappedByTest.java new file mode 100644 index 0000000000..782d43d8a1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToManyMappedByTest.java @@ -0,0 +1,173 @@ +package org.hibernate.orm.test.component; + +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentOneToManyMappedByTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentOneToManyMappedByTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Main book"; + book1.author = new Author( "Abc" ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Second book"; + book2.mainBook = book1; + book2.author = new Author( "Abc" ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 1", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.books where b.id = 1", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + @ManyToOne(fetch = FetchType.LAZY) + private Book mainBook; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Book getMainBook() { + return mainBook; + } + + public void setMainBook(Book mainBook) { + this.mainBook = mainBook; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @OneToMany(mappedBy = "mainBook") + private Set books; + + public Author() { + } + + public Author(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToManyTest.java new file mode 100644 index 0000000000..3255736d9b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToManyTest.java @@ -0,0 +1,161 @@ +package org.hibernate.orm.test.component; + +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentOneToManyTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentOneToManyTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Hibernate 3"; + book1.author = new Author( "Gavin", null ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Hibernate 6"; + book2.author = new Author( "Steve", book1 ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Gavin", book.author.getBooks().iterator().next().getAuthor().getName() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.books where b.id = 2", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getBooks() ) ); + assertEquals( "Gavin", book.author.getBooks().iterator().next().getAuthor().getName() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @OneToMany + private Set books; + + public Author() { + } + + public Author(String name, Book book) { + this.name = name; + this.books = book == null ? null : Set.of( book ); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToOneMappedByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToOneMappedByTest.java new file mode 100644 index 0000000000..ae0c3ef8b0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToOneMappedByTest.java @@ -0,0 +1,190 @@ +package org.hibernate.orm.test.component; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentOneToOneMappedByTest.Book.class, + StructComponentOneToOneMappedByTest.BookDetails.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentOneToOneMappedByTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = new Book(); + BookDetails bookDetails = new BookDetails(book, "A nice book"); + book.id = 1L; + book.title = "Hibernate 6"; + book.author = new Author( "Steve", bookDetails ); + + session.save( book ); + session.save( bookDetails ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + session.createQuery( "delete from BookDetails" ).executeUpdate(); + session.createQuery( "delete from Book" ).executeUpdate(); + } + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b", Book.class ).getSingleResult(); + // One-to-one mappedBy is eager by default + assertTrue( Hibernate.isInitialized( book.author.getDetails() ) ); + assertEquals( "A nice book", book.author.getDetails().getSummary() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.details", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getDetails() ) ); + assertEquals( "A nice book", book.author.getDetails().getSummary() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @OneToOne(mappedBy = "book", fetch = FetchType.LAZY) + private BookDetails details; + + public Author() { + } + + public Author(String name, BookDetails details) { + this.name = name; + this.details = details; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BookDetails getDetails() { + return details; + } + + public void setDetails(BookDetails details) { + this.details = details; + } + } + + @Entity(name = "BookDetails") + public static class BookDetails { + @Id + @OneToOne + private Book book; + private String summary; + + public BookDetails() { + } + + public BookDetails(Book book, String summary) { + this.book = book; + this.summary = summary; + } + + public Book getBook() { + return book; + } + + public void setBook(Book book) { + this.book = book; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToOneTest.java new file mode 100644 index 0000000000..816bfbcf42 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentOneToOneTest.java @@ -0,0 +1,161 @@ +package org.hibernate.orm.test.component; + +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; + +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentOneToOneTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentOneToOneTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.title = "Hibernate 3"; + book1.author = new Author( "Gavin", null ); + + session.save( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.title = "Hibernate 6"; + book2.author = new Author( "Steve", book1 ); + + session.save( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.favoriteBook where b.id = 2", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @OneToOne(fetch = FetchType.LAZY) + private Book favoriteBook; + + public Author() { + } + + public Author(String name, Book favoriteBook) { + this.name = name; + this.favoriteBook = favoriteBook; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Book getFavoriteBook() { + return favoriteBook; + } + + public void setFavoriteBook(Book favoriteBook) { + this.favoriteBook = favoriteBook; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructNestedComponentAssociationErrorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructNestedComponentAssociationErrorTest.java new file mode 100644 index 0000000000..df4c5b3977 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructNestedComponentAssociationErrorTest.java @@ -0,0 +1,169 @@ +package org.hibernate.orm.test.component; + +import java.util.List; + +import org.hibernate.MappingException; +import org.hibernate.annotations.Struct; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; + +@JiraKey( "HHH-15831" ) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructuralArrays.class) +public class StructNestedComponentAssociationErrorTest { + + @Test + public void testOneToOneMappedByNested() { + final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry(); + try { + new MetadataSources( ssr ) + .addAnnotatedClass( Book1.class ) + .getMetadataBuilder() + .build(); + Assertions.fail( "Expected a failure" ); + } + catch (MappingException ex) { + assertThat( ex.getMessage(), containsString( "authorInfos.person.favoriteBook" ) ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + + @Entity(name = "Book") + public static class Book1 { + + @Id + @GeneratedValue + private Long id; + private String title; + private AuthorInfo[] authorInfos; + @OneToOne(fetch = FetchType.LAZY) + private Book1 favoredBook; + } + + @Embeddable + @Struct(name = "author_info_type") + public static class AuthorInfo { + Person1 person; + } + + @Embeddable + @Struct(name = "person_type") + public static class Person1 { + private String name; + @OneToOne(mappedBy = "favoredBook", fetch = FetchType.LAZY) + private Book1 favoriteBook; + } + + @Test + public void testOneToManyMappedByNested() { + final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry(); + try { + new MetadataSources( ssr ) + .addAnnotatedClass( Book2.class ) + .getMetadataBuilder() + .build(); + Assertions.fail( "Expected a failure" ); + } + catch (MappingException ex) { + assertThat( ex.getMessage(), containsString( "authorInfos.person.bookCollection" ) ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + + @Entity(name = "Book") + public static class Book2 { + + @Id + @GeneratedValue + private Long id; + private String title; + private AuthorInfo2[] authorInfos; + @ManyToOne(fetch = FetchType.LAZY) + private Book2 mainBook; + } + + @Embeddable + @Struct(name = "author_info_type") + public static class AuthorInfo2 { + Person2 person; + } + + @Embeddable + @Struct(name = "person_type") + public static class Person2 { + private String name; + @OneToMany(mappedBy = "mainBook") + private List bookCollection; + } + + + @Test + public void testOneToMany() { + final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry(); + try { + new MetadataSources( ssr ) + .addAnnotatedClass( Book3.class ) + .getMetadataBuilder() + .build(); + Assertions.fail( "Expected a failure" ); + } + catch (MappingException ex) { + assertThat( ex.getMessage(), containsString( "authorInfos.person.bookCollection" ) ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + + @Entity(name = "Book") + public static class Book3 { + + @Id + @GeneratedValue + private Long id; + private String title; + private AuthorInfo3[] authorInfos; + } + + @Embeddable + @Struct(name = "author_info_type") + public static class AuthorInfo3 { + Person3 person; + } + + @Embeddable + @Struct(name = "person_type") + public static class Person3 { + private String name; + @OneToMany + private List bookCollection; + } + +}