Skip to content

Commit 2244832

Browse files
committed
HHH-17837 Always render explicit plural path joins
1 parent 14cce8c commit 2244832

File tree

8 files changed

+71
-47
lines changed

8 files changed

+71
-47
lines changed

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525
import org.hibernate.metamodel.MappingMetamodel;
2626
import org.hibernate.metamodel.mapping.BasicValuedMapping;
2727
import org.hibernate.metamodel.mapping.Bindable;
28+
import org.hibernate.metamodel.mapping.CollectionPart;
2829
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
2930
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
3031
import org.hibernate.metamodel.mapping.EntityMappingType;
3132
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
3233
import org.hibernate.metamodel.mapping.JdbcMapping;
33-
import org.hibernate.metamodel.mapping.ManagedMappingType;
3434
import org.hibernate.metamodel.mapping.MappingModelExpressible;
35+
import org.hibernate.metamodel.mapping.ModelPart;
3536
import org.hibernate.metamodel.mapping.ModelPartContainer;
3637
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
3738
import org.hibernate.query.IllegalQueryOperationException;
@@ -46,6 +47,7 @@
4647
import org.hibernate.query.sqm.SqmQuerySource;
4748
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
4849
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
50+
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
4951
import org.hibernate.query.sqm.tree.SqmDmlStatement;
5052
import org.hibernate.query.sqm.tree.SqmJoinType;
5153
import org.hibernate.query.sqm.tree.SqmStatement;
@@ -65,6 +67,7 @@
6567
import org.hibernate.query.sqm.tree.select.SqmSelection;
6668
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
6769
import org.hibernate.spi.NavigablePath;
70+
import org.hibernate.sql.ast.Clause;
6871
import org.hibernate.sql.ast.SqlTreeCreationException;
6972
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
7073
import org.hibernate.sql.ast.tree.from.TableGroup;
@@ -79,6 +82,7 @@
7982
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
8083
import org.hibernate.type.spi.TypeConfiguration;
8184

85+
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
8286
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
8387

8488
/**
@@ -132,13 +136,45 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
132136
}
133137

134138
/**
135-
* Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
136-
* dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
139+
* Utility that returns the entity association target's mapping type if the specified
140+
* {@code sqmPath} should be dereferenced using the target table, i.e. when the path's
141+
* lhs is an explicit join, or defaults to the provided {@code modelPartContainer} otherwise.
137142
*/
138-
public static boolean needsTargetTableMapping(SqmPath<?> sqmPath, ModelPartContainer modelPartContainer) {
139-
return modelPartContainer.getPartMappingType() != modelPartContainer
140-
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
141-
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
143+
public static ModelPartContainer getTargetMappingIfNeeded(
144+
SqmPath<?> sqmPath,
145+
ModelPartContainer modelPartContainer,
146+
SqmToSqlAstConverter sqlAstCreationState) {
147+
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
148+
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer
149+
&& sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
150+
final ModelPart modelPart;
151+
if ( modelPartContainer instanceof PluralAttributeMapping ) {
152+
modelPart = getCollectionPart(
153+
(PluralAttributeMapping) modelPartContainer,
154+
castNonNull( sqmPath.getNavigablePath().getParent() )
155+
);
156+
}
157+
else {
158+
modelPart = modelPartContainer;
159+
}
160+
if ( modelPart instanceof EntityAssociationMapping ) {
161+
return ( (EntityAssociationMapping) modelPart ).getAssociatedEntityMappingType();
162+
}
163+
}
164+
return modelPartContainer;
165+
}
166+
167+
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
168+
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
169+
if ( nature != null ) {
170+
switch ( nature ) {
171+
case ELEMENT:
172+
return attribute.getElementDescriptor();
173+
case INDEX:
174+
return attribute.getIndexDescriptor();
175+
}
176+
}
177+
return null;
142178
}
143179

144180
/**

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.hibernate.metamodel.MappingMetamodel;
1414
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
1515
import org.hibernate.metamodel.mapping.EntityMappingType;
16-
import org.hibernate.metamodel.mapping.ManagedMappingType;
1716
import org.hibernate.metamodel.mapping.MappingType;
1817
import org.hibernate.metamodel.mapping.ModelPart;
1918
import org.hibernate.metamodel.mapping.ModelPartContainer;
@@ -35,7 +34,7 @@
3534
import org.hibernate.sql.ast.tree.update.Assignable;
3635

3736
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
38-
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
37+
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
3938

4039
/**
4140
* @author Steve Ebersole
@@ -82,20 +81,12 @@ public static <T> BasicValuedPathInterpretation<T> from(
8281
}
8382
}
8483

85-
final ModelPart modelPart;
86-
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
87-
// We have to make sure we render the column of the target table
88-
modelPart = ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
89-
sqmPath.getReferencedPathSource().getPathName(),
90-
treatTarget
91-
);
92-
}
93-
else {
94-
modelPart = modelPartContainer.findSubPart(
95-
sqmPath.getReferencedPathSource().getPathName(),
96-
treatTarget
97-
);
98-
}
84+
// Use the target type to find the sub part if needed, otherwise just use the container
85+
final ModelPart modelPart = getTargetMappingIfNeeded(
86+
sqmPath,
87+
modelPartContainer,
88+
sqlAstCreationState
89+
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
9990

10091
if ( modelPart == null ) {
10192
if ( jpaQueryComplianceEnabled ) {

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.hibernate.metamodel.MappingMetamodel;
1414
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
1515
import org.hibernate.metamodel.mapping.EntityMappingType;
16-
import org.hibernate.metamodel.mapping.ManagedMappingType;
1716
import org.hibernate.metamodel.mapping.ModelPartContainer;
1817
import org.hibernate.metamodel.model.domain.EntityDomainType;
1918
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
@@ -29,7 +28,7 @@
2928
import org.hibernate.sql.ast.tree.from.TableGroup;
3029
import org.hibernate.sql.ast.tree.update.Assignable;
3130

32-
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
31+
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
3332

3433
/**
3534
* @author Steve Ebersole
@@ -65,20 +64,12 @@ else if ( lhs.getNodeType() instanceof EntityDomainType ) {
6564
}
6665

6766
final ModelPartContainer modelPartContainer = tableGroup.getModelPart();
68-
final EmbeddableValuedModelPart mapping;
69-
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
70-
// We have to make sure we render the column of the target table
71-
mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
72-
sqmPath.getReferencedPathSource().getPathName(),
73-
treatTarget
74-
);
75-
}
76-
else {
77-
mapping = (EmbeddableValuedModelPart) modelPartContainer.findSubPart(
78-
sqmPath.getReferencedPathSource().getPathName(),
79-
treatTarget
80-
);
81-
}
67+
// Use the target type to find the sub part if needed, otherwise just use the container
68+
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) getTargetMappingIfNeeded(
69+
sqmPath,
70+
modelPartContainer,
71+
sqlAstCreationState
72+
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
8273

8374
return new EmbeddableValuedPathInterpretation<>(
8475
mapping.toSqlExpression(

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ private static <T> EntityValuedPathInterpretation<T> from(
162162
// we try to make use of it and the FK model part if possible based on the inferred mapping
163163
if ( mapping instanceof EntityAssociationMapping ) {
164164
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
165-
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
165+
final ModelPart keyTargetMatchPart = associationMapping.getForeignKeyDescriptor().getPart(
166+
associationMapping.getSideNature()
167+
);
166168

167169
if ( associationMapping.isFkOptimizationAllowed() ) {
168170
final boolean forceUsingForeignKeyAssociationSidePart;

hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,8 +629,14 @@ public void appendHqlString(StringBuilder sb) {
629629

630630
public boolean groupByClauseContains(NavigablePath path) {
631631
for ( SqmExpression<?> expression : groupByClauseExpressions ) {
632-
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath() == path ) {
633-
return true;
632+
if ( expression instanceof SqmPath ) {
633+
final NavigablePath expressionPath = ( (SqmPath<?>) expression ).getNavigablePath();
634+
if ( expressionPath == path ) {
635+
return true;
636+
}
637+
else if ( CollectionPart.Nature.fromNameExact( path.getLocalName() ) != null && expressionPath == path.getParent() ) {
638+
return true;
639+
}
634640
}
635641
}
636642
return false;

hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/ManyToManyJoinTableAndInheritanceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void testLeftJoinSelectId(SessionFactoryScope scope) {
9393
).getSingleResult();
9494
assertThat( result ).isEqualTo( 1L );
9595
inspector.assertExecutedCount( 1 );
96-
inspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
96+
inspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 );
9797
} );
9898
}
9999

hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/JoinTableOptimizationTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
4141
s -> {
4242
s.createQuery( "select p.id from Document d left join d.people p where p.id is not null" ).list();
4343
statementInspector.assertExecutedCount( 1 );
44-
// Assert only the collection table is joined
45-
statementInspector.assertNumberOfJoins( 0, 1 );
44+
statementInspector.assertNumberOfJoins( 0, 2 );
4645
}
4746
);
4847
}

hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
4545
s -> {
4646
s.createQuery( "select 1 from MapOwner as o left join o.contents c where c.id is not null" ).list();
4747
statementInspector.assertExecutedCount( 1 );
48-
// Assert only the collection table is joined
49-
statementInspector.assertNumberOfJoins( 0, 1 );
48+
statementInspector.assertNumberOfJoins( 0, 2 );
5049
}
5150
);
5251
}

0 commit comments

Comments
 (0)