Skip to content

Commit ef155c2

Browse files
mbladelbeikov
authored andcommitted
HHH-17379 HHH-17397 Improve check for non-optimizable path expressions
1 parent 29da2c0 commit ef155c2

File tree

6 files changed

+36
-38
lines changed

6 files changed

+36
-38
lines changed

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

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.hibernate.query.sqm.tree.expression.SqmParameter;
5656
import org.hibernate.query.sqm.tree.from.SqmFrom;
5757
import org.hibernate.query.sqm.tree.from.SqmJoin;
58+
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
5859
import org.hibernate.query.sqm.tree.from.SqmRoot;
5960
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
6061
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@@ -127,27 +128,33 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
127128
);
128129
}
129130

130-
public static boolean needsTargetTableMapping(
131-
SqmPath<?> sqmPath,
132-
ModelPartContainer modelPartContainer,
133-
SqmToSqlAstConverter sqlAstCreationState) {
134-
final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
135-
return ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
136-
&& modelPartContainer.getPartMappingType() != modelPartContainer
131+
/**
132+
* Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
133+
* dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
134+
*/
135+
public static boolean needsTargetTableMapping(SqmPath<?> sqmPath, ModelPartContainer modelPartContainer) {
136+
return modelPartContainer.getPartMappingType() != modelPartContainer
137137
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
138-
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType
139-
&& ( groupByClauseContains( sqlAstCreationState.getCurrentSqmQueryPart(), sqmPath.getNavigablePath() )
140-
|| isNonOptimizableJoin( sqmPath.getLhs() ) );
141-
}
142-
143-
private static boolean groupByClauseContains(SqmQueryPart<?> sqmQueryPart, NavigablePath path) {
144-
return sqmQueryPart.isSimpleQueryPart() && sqmQueryPart.getFirstQuerySpec().groupByClauseContains( path );
138+
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
145139
}
146140

147-
private static boolean isNonOptimizableJoin(SqmPath<?> sqmPath) {
141+
/**
142+
* Utility that returns {@code false} when the provided {@link SqmPath sqmPath} is
143+
* a join that cannot be dereferenced through the foreign key on the associated table,
144+
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
145+
* or one that has an explicit on clause predicate.
146+
*/
147+
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
148148
if ( sqmPath instanceof SqmJoin<?, ?> ) {
149-
final SqmJoinType sqmJoinType = ( (SqmJoin<?, ?>) sqmPath ).getSqmJoinType();
150-
return sqmJoinType != SqmJoinType.INNER && sqmJoinType != SqmJoinType.LEFT;
149+
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
150+
switch ( sqmJoin.getSqmJoinType() ) {
151+
case INNER:
152+
case LEFT:
153+
return !( sqmJoin instanceof SqmQualifiedJoin<?, ?>)
154+
|| ( (SqmQualifiedJoin<?, ?>) sqmJoin ).getJoinPredicate() == null;
155+
default:
156+
return false;
157+
}
151158
}
152159
return false;
153160
}

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@
223223
import org.hibernate.query.sqm.tree.from.SqmFrom;
224224
import org.hibernate.query.sqm.tree.from.SqmFromClause;
225225
import org.hibernate.query.sqm.tree.from.SqmJoin;
226-
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
227226
import org.hibernate.query.sqm.tree.from.SqmRoot;
228227
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
229228
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
@@ -431,6 +430,7 @@
431430
import static org.hibernate.query.sqm.TemporalUnit.NATIVE;
432431
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
433432
import static org.hibernate.query.sqm.UnaryArithmeticOperator.UNARY_MINUS;
433+
import static org.hibernate.query.sqm.internal.SqmUtil.isFkOptimizationAllowed;
434434
import static org.hibernate.sql.ast.spi.SqlAstTreeHelper.combinePredicates;
435435
import static org.hibernate.type.spi.TypeConfiguration.isDuration;
436436

@@ -3996,10 +3996,6 @@ public Expression visitQualifiedEntityJoin(SqmEntityJoin<?> sqmJoin) {
39963996
throw new InterpretationException( "SqmEntityJoin not yet resolved to TableGroup" );
39973997
}
39983998

3999-
private boolean isJoinWithPredicate(SqmFrom<?, ?> path) {
4000-
return path instanceof SqmQualifiedJoin && ( (SqmQualifiedJoin<?, ?>) path ).getJoinPredicate() != null;
4001-
}
4002-
40033999
private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
40044000
final ModelPartContainer tableGroupModelPart = tableGroup.getModelPart();
40054001

@@ -4026,9 +4022,9 @@ private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
40264022
// expansion to all target columns for select and group by clauses is handled in EntityValuedPathInterpretation
40274023
if ( entityValuedModelPart instanceof EntityAssociationMapping
40284024
&& ( (EntityAssociationMapping) entityValuedModelPart ).isFkOptimizationAllowed()
4029-
&& !isJoinWithPredicate( path ) ) {
4025+
&& isFkOptimizationAllowed( path ) ) {
40304026
// If the table group uses an association mapping that is not a one-to-many,
4031-
// we make use of the FK model part - unless the path is a join with an explicit predicate,
4027+
// we make use of the FK model part - unless the path is a non-optimizable join,
40324028
// for which we should always use the target's identifier to preserve semantics
40334029
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) entityValuedModelPart;
40344030
final ModelPart targetPart = associationMapping.getForeignKeyDescriptor().getPart(

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,8 @@ public static <T> BasicValuedPathInterpretation<T> from(
8282
}
8383

8484
final BasicValuedModelPart mapping;
85-
if ( needsTargetTableMapping( sqmPath, modelPartContainer, sqlAstCreationState ) ) {
86-
// In the select, group by, order by and having clause we have to make sure we render
87-
// the column of the target table, never the FK column, if the lhs is a join type that
88-
// requires it (right, full) or if this path is contained in group by clause
85+
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
86+
// We have to make sure we render the column of the target table
8987
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
9088
sqmPath.getReferencedPathSource().getPathName(),
9189
treatTarget

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,8 @@ else if ( lhs.getNodeType() instanceof EntityDomainType ) {
6666

6767
final ModelPartContainer modelPartContainer = tableGroup.getModelPart();
6868
final EmbeddableValuedModelPart mapping;
69-
if ( needsTargetTableMapping( sqmPath, modelPartContainer, sqlAstCreationState ) ) {
70-
// In the select, group by, order by and having clause we have to make sure we render
71-
// the column of the target table, never the FK column, if the lhs is a join type that
72-
// requires it (right, full) or if this path is contained in group by clause
69+
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
70+
// We have to make sure we render the column of the target table
7371
mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
7472
sqmPath.getReferencedPathSource().getPathName(),
7573
treatTarget

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
5252
}
5353

5454
@Test
55-
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
55+
public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) {
5656
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
5757
statementInspector.clear();
5858
scope.inTransaction(
5959
s -> {
6060
s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list();
6161
statementInspector.assertExecutedCount( 1 );
62-
// Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable
63-
statementInspector.assertNumberOfJoins( 0, 2 );
62+
// Assert 3 joins, collection table, collection element and relationship
63+
statementInspector.assertNumberOfJoins( 0, 3 );
6464
}
6565
);
6666
}

hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/onetoone/bidirectional/EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,8 @@ public void testHqlSelectSon(SessionFactoryScope scope) {
152152
.getSingleResult();
153153

154154
statementInspector.assertExecutedCount( 2 );
155-
// The join to the target table PARENT for Male#parent is avoided,
156-
// because the FK in the collection table is not-null and data from the target table is not needed
157-
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
155+
// The join to the target table PARENT for Male#parent is added since it's explicitly joined in HQL
156+
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 );
158157
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
159158
assertThat( son.getParent(), CoreMatchers.notNullValue() );
160159

0 commit comments

Comments
 (0)