Skip to content

Commit 7279d10

Browse files
committed
HHH-19575 Ensure null struct is fetched as null embeddable
1 parent a947d56 commit 7279d10

17 files changed

+263
-110
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.hibernate.MappingException;
1616
import org.hibernate.annotations.Comment;
1717
import org.hibernate.annotations.common.reflection.XClass;
18+
import org.hibernate.boot.model.naming.Identifier;
1819
import org.hibernate.boot.model.relational.Database;
1920
import org.hibernate.boot.model.relational.Namespace;
2021
import org.hibernate.boot.model.relational.QualifiedName;
@@ -109,7 +110,8 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
109110
orderColumns( registeredUdt, originalOrder );
110111
}
111112
else {
112-
addAuxiliaryObjects = false;
113+
addAuxiliaryObjects =
114+
isAggregateArray() && namespace.locateUserDefinedArrayType( Identifier.toIdentifier( aggregateColumn.getSqlType() ) ) == null;
113115
validateEqual( registeredUdt, udt );
114116
}
115117
}

hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java

Lines changed: 10 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,6 @@ else if ( string.charAt( i + 1 ) == '{' ) {
514514
quotes + 1,
515515
arrayList,
516516
(BasicType<Object>) pluralType.getElementType(),
517-
returnEmbeddable,
518517
options
519518
);
520519
assert string.charAt( subEnd - 1 ) == '}';
@@ -624,7 +623,6 @@ else if ( jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass().isEnum()
624623
quotes + 1,
625624
arrayList,
626625
(BasicType<Object>) pluralType.getElementType(),
627-
returnEmbeddable,
628626
options
629627
);
630628
assert string.charAt( i - 1 ) == '}';
@@ -667,7 +665,6 @@ private int deserializeArray(
667665
int quotes,
668666
ArrayList<Object> values,
669667
BasicType<Object> elementType,
670-
boolean returnEmbeddable,
671668
WrapperOptions options) throws SQLException {
672669
boolean inQuote = false;
673670
StringBuilder escapingSb = null;
@@ -860,30 +857,17 @@ private int deserializeArray(
860857
i + 1,
861858
quotes + 1,
862859
subValues,
863-
returnEmbeddable,
860+
true,
864861
options
865862
);
866-
if ( returnEmbeddable ) {
867-
final StructAttributeValues attributeValues = structJdbcType.getAttributeValues(
868-
structJdbcType.embeddableMappingType,
869-
structJdbcType.orderMapping,
870-
subValues,
871-
options
872-
);
873-
final Object subValue = instantiate( structJdbcType.embeddableMappingType, attributeValues, options.getSessionFactory() );
863+
final StructAttributeValues attributeValues = structJdbcType.getAttributeValues(
864+
structJdbcType.embeddableMappingType,
865+
structJdbcType.orderMapping,
866+
subValues,
867+
options
868+
);
869+
final Object subValue = instantiate( structJdbcType.embeddableMappingType, attributeValues, options.getSessionFactory() );
874870
values.add( subValue );
875-
}
876-
else {
877-
if ( structJdbcType.inverseOrderMapping != null ) {
878-
StructHelper.orderJdbcValues(
879-
structJdbcType.embeddableMappingType,
880-
structJdbcType.inverseOrderMapping,
881-
subValues.clone(),
882-
subValues
883-
);
884-
}
885-
values.add( subValues );
886-
}
887871
// The subEnd points to the first character after the '}',
888872
// so move forward the index to point to the next char after quotes
889873
assert isDoubleQuote( string, subEnd, expectedQuotes );
@@ -1001,40 +985,8 @@ else if ( elementType.getJavaTypeDescriptor().getJavaTypeClass().isEnum()
1001985
}
1002986

1003987
private SelectableMapping getJdbcValueSelectable(int jdbcValueSelectableIndex) {
1004-
if ( orderMapping != null ) {
1005-
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings();
1006-
final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 );
1007-
int count = 0;
1008-
for ( int i = 0; i < size; i++ ) {
1009-
final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, orderMapping[i] );
1010-
final MappingType mappedType = modelPart.getMappedType();
1011-
if ( mappedType instanceof EmbeddableMappingType ) {
1012-
final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType;
1013-
final SelectableMapping aggregateMapping = embeddableMappingType.getAggregateMapping();
1014-
if ( aggregateMapping == null ) {
1015-
final SelectableMapping subSelectable = embeddableMappingType.getJdbcValueSelectable( jdbcValueSelectableIndex - count );
1016-
if ( subSelectable != null ) {
1017-
return subSelectable;
1018-
}
1019-
count += embeddableMappingType.getJdbcValueCount();
1020-
}
1021-
else {
1022-
if ( count == jdbcValueSelectableIndex ) {
1023-
return aggregateMapping;
1024-
}
1025-
count++;
1026-
}
1027-
}
1028-
else {
1029-
if ( count == jdbcValueSelectableIndex ) {
1030-
return (SelectableMapping) modelPart;
1031-
}
1032-
count += modelPart.getJdbcTypeCount();
1033-
}
1034-
}
1035-
return null;
1036-
}
1037-
return embeddableMappingType.getJdbcValueSelectable( jdbcValueSelectableIndex );
988+
return embeddableMappingType.getJdbcValueSelectable(
989+
orderMapping != null ? orderMapping[jdbcValueSelectableIndex] : jdbcValueSelectableIndex );
1038990
}
1039991

1040992
private static boolean repeatsChar(String string, int start, int times, char expectedChar) {

hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,10 @@ else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
521521
final String tagName = aggregateMapping.getSelectableName();
522522
sb.append( '<' );
523523
sb.append( tagName );
524+
if ( array[i] == null ) {
525+
sb.append( "/>" );
526+
continue;
527+
}
524528
sb.append( '>' );
525529
toString(
526530
mappingType,

hibernate-core/src/main/java/org/hibernate/dialect/aggregate/DB2AggregateSupport.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,9 @@ public List<AuxiliaryDatabaseObject> aggregateAuxiliaryDatabaseObjects(
333333
var serializerSb = new StringBuilder();
334334
var deserializerSb = new StringBuilder();
335335
serializerSb.append( "create function " ).append( columnType ).append( "_serializer(v " ).append( columnType ).append( ") returns xml language sql " )
336-
.append( "return xmlelement(name \"").append( XmlHelper.ROOT_TAG ).append( "\"" );
336+
.append( "return case when v is null then null else xmlelement(name \"").append( XmlHelper.ROOT_TAG ).append( "\"" );
337337
appendSerializer( aggregatedColumns, serializerSb, "v.." );
338-
serializerSb.append( ')' );
338+
serializerSb.append( ") end" );
339339

340340
deserializerSb.append( "create function " ).append( columnType ).append( "_deserializer(v xml) returns " ).append( columnType ).append( " language sql " )
341341
.append( "return select " ).append( columnType ).append( "()" );
@@ -385,6 +385,10 @@ private static void appendSerializer(List<Column> aggregatedColumns, StringBuild
385385
}
386386
for ( Column udtColumn : aggregatedColumns ) {
387387
serializerSb.append( sep );
388+
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
389+
serializerSb.append( "case when ").append( prefix ).append( udtColumn.getName() )
390+
.append( " is null then null else " );
391+
}
388392
serializerSb.append( "xmlelement(name \"" ).append( udtColumn.getName() ).append( "\"" );
389393
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
390394
final AggregateColumn aggregateColumn = (AggregateColumn) udtColumn;
@@ -402,6 +406,9 @@ else if ( needsVarcharForBitDataCast( udtColumn.getSqlType() ) ) {
402406
serializerSb.append( ',' ).append( prefix ).append( udtColumn.getName() );
403407
}
404408
serializerSb.append( ')' );
409+
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
410+
serializerSb.append( " end" );
411+
}
405412
sep = ',';
406413
}
407414
if ( aggregatedColumns.size() > 1 ) {

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.hibernate.metamodel.mapping.MappedDiscriminatorConverter;
2626
import org.hibernate.metamodel.mapping.MappingType;
2727
import org.hibernate.metamodel.mapping.SelectableConsumer;
28+
import org.hibernate.metamodel.mapping.SelectablePath;
2829
import org.hibernate.metamodel.model.domain.NavigableRole;
2930
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
3031
import org.hibernate.spi.NavigablePath;
@@ -58,6 +59,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
5859

5960
private final String table;
6061
private final String column;
62+
private final SelectablePath selectablePath;
6163
private final String customReadExpression;
6264
private final String customWriteExpression;
6365
private final String columnDefinition;
@@ -76,7 +78,10 @@ public AnyDiscriminatorPart(
7678
NavigableRole partRole,
7779
DiscriminatedAssociationModelPart declaringType,
7880
String table,
79-
String column, String customReadExpression, String customWriteExpression,
81+
String column,
82+
SelectablePath selectablePath,
83+
String customReadExpression,
84+
String customWriteExpression,
8085
String columnDefinition,
8186
Long length,
8287
Integer precision,
@@ -91,6 +96,7 @@ public AnyDiscriminatorPart(
9196
this.declaringType = declaringType;
9297
this.table = table;
9398
this.column = column;
99+
this.selectablePath = selectablePath;
94100
this.customReadExpression = customReadExpression;
95101
this.customWriteExpression = customWriteExpression;
96102
this.columnDefinition = columnDefinition;
@@ -136,6 +142,16 @@ public String getSelectionExpression() {
136142
return column;
137143
}
138144

145+
@Override
146+
public String getSelectableName() {
147+
return selectablePath.getSelectableName();
148+
}
149+
150+
@Override
151+
public SelectablePath getSelectablePath() {
152+
return selectablePath;
153+
}
154+
139155
@Override
140156
public boolean isFormula() {
141157
return false;

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.hibernate.metamodel.mapping.JdbcMapping;
2020
import org.hibernate.metamodel.mapping.MappingType;
2121
import org.hibernate.metamodel.mapping.SelectableConsumer;
22+
import org.hibernate.metamodel.mapping.SelectablePath;
2223
import org.hibernate.metamodel.model.domain.NavigableRole;
2324
import org.hibernate.spi.NavigablePath;
2425
import org.hibernate.sql.ast.spi.FromClauseAccess;
@@ -47,6 +48,7 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
4748
private final NavigableRole navigableRole;
4849
private final String table;
4950
private final String column;
51+
private final SelectablePath selectablePath;
5052
private final DiscriminatedAssociationModelPart anyPart;
5153
private final String customReadExpression;
5254
private final String customWriteExpression;
@@ -65,6 +67,7 @@ public AnyKeyPart(
6567
DiscriminatedAssociationModelPart anyPart,
6668
String table,
6769
String column,
70+
SelectablePath selectablePath,
6871
String customReadExpression,
6972
String customWriteExpression,
7073
String columnDefinition,
@@ -79,6 +82,7 @@ public AnyKeyPart(
7982
this.navigableRole = navigableRole;
8083
this.table = table;
8184
this.column = column;
85+
this.selectablePath = selectablePath;
8286
this.anyPart = anyPart;
8387
this.customReadExpression = customReadExpression;
8488
this.customWriteExpression = customWriteExpression;
@@ -103,6 +107,16 @@ public String getSelectionExpression() {
103107
return column;
104108
}
105109

110+
@Override
111+
public String getSelectableName() {
112+
return selectablePath.getSelectableName();
113+
}
114+
115+
@Override
116+
public SelectablePath getSelectablePath() {
117+
return selectablePath;
118+
}
119+
106120
@Override
107121
public boolean isFormula() {
108122
return false;

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.hibernate.metamodel.mapping.EntityMappingType;
2626
import org.hibernate.metamodel.mapping.MappingType;
2727
import org.hibernate.metamodel.mapping.ModelPart;
28+
import org.hibernate.metamodel.mapping.SelectablePath;
2829
import org.hibernate.metamodel.model.domain.NavigableRole;
2930
import org.hibernate.spi.NavigablePath;
3031
import org.hibernate.sql.ast.tree.from.TableGroup;
@@ -73,13 +74,18 @@ public static DiscriminatedAssociationMapping from(
7374
assert !keySelectable.isFormula();
7475
final Column metaColumn = (Column) metaSelectable;
7576
final Column keyColumn = (Column) keySelectable;
77+
final SelectablePath parentSelectablePath = declaringModelPart.asAttributeMapping() != null
78+
? MappingModelCreationHelper.getSelectablePath( declaringModelPart.asAttributeMapping().getDeclaringType() )
79+
: null;
7680

7781
final MetaType metaType = (MetaType) anyType.getDiscriminatorType();
7882
final AnyDiscriminatorPart discriminatorPart = new AnyDiscriminatorPart(
79-
containerRole.append( AnyDiscriminatorPart.ROLE_NAME),
83+
containerRole.append( AnyDiscriminatorPart.ROLE_NAME ),
8084
declaringModelPart,
8185
tableName,
8286
metaColumn.getText( dialect ),
87+
parentSelectablePath != null ? parentSelectablePath.append( metaColumn.getQuotedName( dialect ) )
88+
: new SelectablePath( metaColumn.getQuotedName( dialect ) ),
8389
metaColumn.getCustomReadExpression(),
8490
metaColumn.getCustomWriteExpression(),
8591
metaColumn.getSqlType(),
@@ -101,6 +107,8 @@ public static DiscriminatedAssociationMapping from(
101107
declaringModelPart,
102108
tableName,
103109
keyColumn.getText( dialect ),
110+
parentSelectablePath != null ? parentSelectablePath.append( keyColumn.getQuotedName( dialect ) )
111+
: new SelectablePath( keyColumn.getQuotedName( dialect ) ),
104112
keyColumn.getCustomReadExpression(),
105113
keyColumn.getCustomWriteExpression(),
106114
keyColumn.getSqlType(),

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,13 +970,15 @@ else if ( modelPart == null ) {
970970
( (PropertyBasedMapping) simpleFkTarget ).getPropertyAccess()
971971
);
972972
}
973+
final SelectablePath parentSelectablePath = getSelectablePath( attributeMapping.getDeclaringType() );
973974
final SelectableMapping keySelectableMapping;
974975
int i = 0;
975976
final Value value = bootProperty.getValue();
976977
if ( columnIterator.hasNext() ) {
977978
keySelectableMapping = SelectableMappingImpl.from(
978979
tableExpression,
979980
columnIterator.next(),
981+
parentSelectablePath,
980982
simpleFkTarget.getJdbcMapping(),
981983
creationProcess.getCreationContext().getTypeConfiguration(),
982984
value.isColumnInsertable( i ),
@@ -993,6 +995,7 @@ else if ( modelPart == null ) {
993995
keySelectableMapping = SelectableMappingImpl.from(
994996
tableExpression,
995997
table.getPrimaryKey().getColumn( 0 ),
998+
parentSelectablePath,
996999
simpleFkTarget.getJdbcMapping(),
9971000
creationProcess.getCreationContext().getTypeConfiguration(),
9981001
value.isColumnInsertable( 0 ),
@@ -1129,6 +1132,7 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
11291132
boolean[] updateable,
11301133
Dialect dialect,
11311134
MappingModelCreationProcess creationProcess) {
1135+
final SelectablePath parentSelectablePath = getSelectablePath( keyDeclaringType );
11321136
final boolean hasConstraint;
11331137
final SelectableMappings keySelectableMappings;
11341138
if ( bootValueMapping instanceof Collection ) {
@@ -1142,6 +1146,7 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
11421146
keyTableExpression,
11431147
collectionBootValueMapping.getKey(),
11441148
getPropertyOrder( bootValueMapping, creationProcess ),
1149+
parentSelectablePath,
11451150
creationProcess.getCreationContext().getMetadata(),
11461151
creationProcess.getCreationContext().getTypeConfiguration(),
11471152
insertable,
@@ -1167,6 +1172,7 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
11671172
keyTableExpression,
11681173
bootValueMapping,
11691174
getPropertyOrder( bootValueMapping, creationProcess ),
1175+
parentSelectablePath,
11701176
creationProcess.getCreationContext().getMetadata(),
11711177
creationProcess.getCreationContext().getTypeConfiguration(),
11721178
insertable,
@@ -1214,6 +1220,15 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
12141220
}
12151221
}
12161222

1223+
public static @Nullable SelectablePath getSelectablePath(ManagedMappingType type) {
1224+
if ( type instanceof EmbeddableMappingType ) {
1225+
final EmbeddableMappingType embeddableType = (EmbeddableMappingType) type;
1226+
return embeddableType.getAggregateMapping() != null
1227+
? embeddableType.getAggregateMapping().getSelectablePath() : null;
1228+
}
1229+
return null;
1230+
}
1231+
12171232
public static int[] getPropertyOrder(Value bootValueMapping, MappingModelCreationProcess creationProcess) {
12181233
final ComponentType componentType;
12191234
final boolean sorted;

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SelectableMappingImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import java.util.Locale;
1010

11+
import org.checkerframework.checker.nullness.qual.Nullable;
1112
import org.hibernate.dialect.Dialect;
1213
import org.hibernate.mapping.Column;
1314
import org.hibernate.mapping.Selectable;
@@ -126,7 +127,7 @@ public static SelectableMapping from(
126127
public static SelectableMapping from(
127128
final String containingTableExpression,
128129
final Selectable selectable,
129-
final SelectablePath parentPath,
130+
@Nullable final SelectablePath parentPath,
130131
final JdbcMapping jdbcMapping,
131132
final TypeConfiguration typeConfiguration,
132133
boolean insertable,
@@ -154,7 +155,7 @@ public static SelectableMapping from(
154155
public static SelectableMapping from(
155156
final String containingTableExpression,
156157
final Selectable selectable,
157-
final SelectablePath parentPath,
158+
@Nullable final SelectablePath parentPath,
158159
final JdbcMapping jdbcMapping,
159160
final TypeConfiguration typeConfiguration,
160161
boolean insertable,

0 commit comments

Comments
 (0)