Skip to content

HHH-19575 Ensure null struct is fetched as null embeddable #10594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.Comment;
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;
Expand Down Expand Up @@ -111,7 +112,8 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
orderColumns( registeredUdt, originalOrder );
}
else {
addAuxiliaryObjects = false;
addAuxiliaryObjects =
isAggregateArray() && namespace.locateUserDefinedArrayType( Identifier.toIdentifier( aggregateColumn.getSqlType() ) ) == null;
validateEqual( registeredUdt, udt );
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -581,9 +581,9 @@ public List<AuxiliaryDatabaseObject> aggregateAuxiliaryDatabaseObjects(
var serializerSb = new StringBuilder();
var deserializerSb = new StringBuilder();
serializerSb.append( "create function " ).append( columnType ).append( "_serializer(v " ).append( columnType ).append( ") returns xml language sql " )
.append( "return xmlelement(name \"").append( XmlHelper.ROOT_TAG ).append( "\"" );
.append( "return case when v is null then null else xmlelement(name \"").append( XmlHelper.ROOT_TAG ).append( "\"" );
appendSerializer( aggregatedColumns, serializerSb, "v..", legacyXmlFormatEnabled );
serializerSb.append( ')' );
serializerSb.append( ") end" );

deserializerSb.append( "create function " ).append( columnType ).append( "_deserializer(v xml) returns " ).append( columnType ).append( " language sql " )
.append( "return select " ).append( columnType ).append( "()" );
Expand Down Expand Up @@ -633,6 +633,10 @@ private static void appendSerializer(List<Column> aggregatedColumns, StringBuild
}
for ( Column udtColumn : aggregatedColumns ) {
serializerSb.append( sep );
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
serializerSb.append( "case when ").append( prefix ).append( udtColumn.getName() )
.append( " is null then null else " );
}
serializerSb.append( "xmlelement(name \"" ).append( udtColumn.getName() ).append( "\"" );
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
final AggregateColumn aggregateColumn = (AggregateColumn) udtColumn;
Expand Down Expand Up @@ -664,6 +668,9 @@ else if ( needsVarcharForBitDataCast( udtColumn.getSqlType() ) ) {
serializerSb.append( ',' ).append( prefix ).append( udtColumn.getName() );
}
serializerSb.append( ')' );
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
serializerSb.append( " end" );
}
sep = ',';
}
if ( aggregatedColumns.size() > 1 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,6 @@ else if ( string.charAt( i + 1 ) == '{' ) {
quotes + 1,
arrayList,
(BasicType<Object>) pluralType.getElementType(),
returnEmbeddable,
options
);
assert string.charAt( subEnd - 1 ) == '}';
Expand Down Expand Up @@ -620,7 +619,6 @@ else if ( jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass().isEnum()
quotes + 1,
arrayList,
(BasicType<Object>) pluralType.getElementType(),
returnEmbeddable,
options
);
assert string.charAt( i - 1 ) == '}';
Expand Down Expand Up @@ -663,7 +661,6 @@ private int deserializeArray(
int quotes,
ArrayList<Object> values,
BasicType<Object> elementType,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
boolean inQuote = false;
StringBuilder escapingSb = null;
Expand Down Expand Up @@ -854,29 +851,16 @@ private int deserializeArray(
i + 1,
quotes + 1,
subValues,
returnEmbeddable,
true,
options
);
if ( returnEmbeddable ) {
final StructAttributeValues attributeValues = structJdbcType.getAttributeValues(
structJdbcType.embeddableMappingType,
structJdbcType.orderMapping,
subValues,
options
);
values.add( instantiate( structJdbcType.embeddableMappingType, attributeValues ) );
}
else {
if ( structJdbcType.inverseOrderMapping != null ) {
StructHelper.orderJdbcValues(
structJdbcType.embeddableMappingType,
structJdbcType.inverseOrderMapping,
subValues.clone(),
subValues
);
}
values.add( subValues );
}
final StructAttributeValues attributeValues = structJdbcType.getAttributeValues(
structJdbcType.embeddableMappingType,
structJdbcType.orderMapping,
subValues,
options
);
values.add( instantiate( structJdbcType.embeddableMappingType, attributeValues ) );
// The subEnd points to the first character after the '}',
// so move forward the index to point to the next char after quotes
assert isDoubleQuote( string, subEnd, expectedQuotes );
Expand Down Expand Up @@ -994,38 +978,8 @@ else if ( elementType.getJavaTypeDescriptor().getJavaTypeClass().isEnum()
}

private SelectableMapping getJdbcValueSelectable(int jdbcValueSelectableIndex) {
if ( orderMapping != null ) {
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings();
final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 );
int count = 0;
for ( int i = 0; i < size; i++ ) {
final ValuedModelPart modelPart = getSubPart( embeddableMappingType, orderMapping[i] );
if ( modelPart.getMappedType() instanceof EmbeddableMappingType embeddableMappingType ) {
final SelectableMapping aggregateMapping = embeddableMappingType.getAggregateMapping();
if ( aggregateMapping == null ) {
final SelectableMapping subSelectable = embeddableMappingType.getJdbcValueSelectable( jdbcValueSelectableIndex - count );
if ( subSelectable != null ) {
return subSelectable;
}
count += embeddableMappingType.getJdbcValueCount();
}
else {
if ( count == jdbcValueSelectableIndex ) {
return aggregateMapping;
}
count++;
}
}
else {
if ( count == jdbcValueSelectableIndex ) {
return (SelectableMapping) modelPart;
}
count += modelPart.getJdbcTypeCount();
}
}
return null;
}
return embeddableMappingType.getJdbcValueSelectable( jdbcValueSelectableIndex );
return embeddableMappingType.getJdbcValueSelectable(
orderMapping != null ? orderMapping[jdbcValueSelectableIndex] : jdbcValueSelectableIndex );
}

private static boolean repeatsChar(String string, int start, int times, char expectedChar) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectablePath;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions

private final String table;
private final String column;
private final SelectablePath selectablePath;
private final String customReadExpression;
private final String customWriteExpression;
private final String columnDefinition;
Expand All @@ -72,7 +74,10 @@ public AnyDiscriminatorPart(
NavigableRole partRole,
DiscriminatedAssociationModelPart declaringType,
String table,
String column, String customReadExpression, String customWriteExpression,
String column,
SelectablePath selectablePath,
String customReadExpression,
String customWriteExpression,
String columnDefinition,
Long length,
Integer precision,
Expand All @@ -88,6 +93,7 @@ public AnyDiscriminatorPart(
this.declaringType = declaringType;
this.table = table;
this.column = column;
this.selectablePath = selectablePath;
this.customReadExpression = customReadExpression;
this.customWriteExpression = customWriteExpression;
this.columnDefinition = columnDefinition;
Expand Down Expand Up @@ -142,6 +148,16 @@ public String getSelectionExpression() {
return column;
}

@Override
public String getSelectableName() {
return selectablePath.getSelectableName();
}

@Override
public SelectablePath getSelectablePath() {
return selectablePath;
}

@Override
public boolean isFormula() {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SelectablePath;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.spi.FromClauseAccess;
Expand Down Expand Up @@ -43,6 +44,7 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
private final NavigableRole navigableRole;
private final String table;
private final String column;
private final SelectablePath selectablePath;
private final DiscriminatedAssociationModelPart anyPart;
private final String customReadExpression;
private final String customWriteExpression;
Expand All @@ -61,6 +63,7 @@ public AnyKeyPart(
DiscriminatedAssociationModelPart anyPart,
String table,
String column,
SelectablePath selectablePath,
String customReadExpression,
String customWriteExpression,
String columnDefinition,
Expand All @@ -75,6 +78,7 @@ public AnyKeyPart(
this.navigableRole = navigableRole;
this.table = table;
this.column = column;
this.selectablePath = selectablePath;
this.anyPart = anyPart;
this.customReadExpression = customReadExpression;
this.customWriteExpression = customWriteExpression;
Expand All @@ -99,6 +103,16 @@ public String getSelectionExpression() {
return column;
}

@Override
public String getSelectableName() {
return selectablePath.getSelectableName();
}

@Override
public SelectablePath getSelectablePath() {
return selectablePath;
}

@Override
public boolean isFormula() {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectablePath;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
Expand Down Expand Up @@ -71,13 +72,18 @@ public static DiscriminatedAssociationMapping from(
assert !keySelectable.isFormula();
final Column metaColumn = (Column) metaSelectable;
final Column keyColumn = (Column) keySelectable;
final SelectablePath parentSelectablePath = declaringModelPart.asAttributeMapping() != null
? MappingModelCreationHelper.getSelectablePath( declaringModelPart.asAttributeMapping().getDeclaringType() )
: null;

final MetaType metaType = (MetaType) anyType.getDiscriminatorType();
final AnyDiscriminatorPart discriminatorPart = new AnyDiscriminatorPart(
containerRole.append( AnyDiscriminatorPart.ROLE_NAME),
containerRole.append( AnyDiscriminatorPart.ROLE_NAME ),
declaringModelPart,
tableName,
metaColumn.getText( dialect ),
parentSelectablePath != null ? parentSelectablePath.append( metaColumn.getQuotedName( dialect ) )
: new SelectablePath( metaColumn.getQuotedName( dialect ) ),
metaColumn.getCustomReadExpression(),
metaColumn.getCustomWriteExpression(),
metaColumn.getSqlType(),
Expand All @@ -100,6 +106,8 @@ public static DiscriminatedAssociationMapping from(
declaringModelPart,
tableName,
keyColumn.getText( dialect ),
parentSelectablePath != null ? parentSelectablePath.append( keyColumn.getQuotedName( dialect ) )
: new SelectablePath( keyColumn.getQuotedName( dialect ) ),
keyColumn.getCustomReadExpression(),
keyColumn.getCustomWriteExpression(),
keyColumn.getSqlType(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -950,13 +950,15 @@ else if ( modelPart instanceof EmbeddableValuedModelPart embeddableValuedModelPa
( (PropertyBasedMapping) simpleFkTarget ).getPropertyAccess()
);
}
final SelectablePath parentSelectablePath = getSelectablePath( attributeMapping.getDeclaringType() );
final SelectableMapping keySelectableMapping;
int i = 0;
final Value value = bootProperty.getValue();
if ( columnIterator.hasNext() ) {
keySelectableMapping = SelectableMappingImpl.from(
tableExpression,
columnIterator.next(),
parentSelectablePath,
simpleFkTarget.getJdbcMapping(),
creationProcess.getCreationContext().getTypeConfiguration(),
value.isColumnInsertable( i ),
Expand All @@ -973,6 +975,7 @@ else if ( modelPart instanceof EmbeddableValuedModelPart embeddableValuedModelPa
keySelectableMapping = SelectableMappingImpl.from(
tableExpression,
table.getPrimaryKey().getColumn( 0 ),
parentSelectablePath,
simpleFkTarget.getJdbcMapping(),
creationProcess.getCreationContext().getTypeConfiguration(),
value.isColumnInsertable( 0 ),
Expand Down Expand Up @@ -1104,6 +1107,7 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
boolean[] updateable,
Dialect dialect,
MappingModelCreationProcess creationProcess) {
final SelectablePath parentSelectablePath = getSelectablePath( keyDeclaringType );
final boolean hasConstraint;
final SelectableMappings keySelectableMappings;
if ( bootValueMapping instanceof Collection collectionBootValueMapping ) {
Expand All @@ -1115,6 +1119,7 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
keyTableExpression,
collectionBootValueMapping.getKey(),
getPropertyOrder( bootValueMapping, creationProcess ),
parentSelectablePath,
creationProcess.getCreationContext().getMetadata(),
creationProcess.getCreationContext().getTypeConfiguration(),
insertable,
Expand All @@ -1139,6 +1144,7 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
keyTableExpression,
bootValueMapping,
getPropertyOrder( bootValueMapping, creationProcess ),
parentSelectablePath,
creationProcess.getCreationContext().getMetadata(),
creationProcess.getCreationContext().getTypeConfiguration(),
insertable,
Expand Down Expand Up @@ -1186,6 +1192,11 @@ private static EmbeddedForeignKeyDescriptor buildEmbeddableForeignKeyDescriptor(
}
}

public static @Nullable SelectablePath getSelectablePath(ManagedMappingType type) {
return type instanceof EmbeddableMappingType embeddableType && embeddableType.getAggregateMapping() != null
? embeddableType.getAggregateMapping().getSelectablePath() : null;
}

public static int[] getPropertyOrder(Value bootValueMapping, MappingModelCreationProcess creationProcess) {
final RuntimeModelCreationContext creationContext = creationProcess.getCreationContext();
final ComponentType componentType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.Locale;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Selectable;
Expand Down Expand Up @@ -124,7 +125,7 @@ public static SelectableMapping from(
public static SelectableMapping from(
final String containingTableExpression,
final Selectable selectable,
final SelectablePath parentPath,
@Nullable final SelectablePath parentPath,
final JdbcMapping jdbcMapping,
final TypeConfiguration typeConfiguration,
boolean insertable,
Expand Down Expand Up @@ -152,7 +153,7 @@ public static SelectableMapping from(
public static SelectableMapping from(
final String containingTableExpression,
final Selectable selectable,
final SelectablePath parentPath,
@Nullable final SelectablePath parentPath,
final JdbcMapping jdbcMapping,
final TypeConfiguration typeConfiguration,
boolean insertable,
Expand Down
Loading