Skip to content

Commit 0942883

Browse files
committed
HHH-17706 Optimize FK comparison to eliminate unnecessary left join
Fix regression introduced by ef155c2
1 parent dc9a997 commit 0942883

File tree

4 files changed

+55
-7
lines changed

4 files changed

+55
-7
lines changed

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
2626
import org.hibernate.query.sqm.tree.domain.SqmPath;
2727
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
28+
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
29+
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
30+
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
2831
import org.hibernate.spi.NavigablePath;
2932
import org.hibernate.sql.ast.SqlAstWalker;
3033
import org.hibernate.sql.ast.tree.expression.ColumnReference;
@@ -35,10 +38,12 @@
3538
import org.hibernate.sql.ast.tree.update.Assignable;
3639

3740
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
41+
import static org.hibernate.query.sqm.internal.SqmUtil.isFkOptimizationAllowed;
3842
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
3943

4044
/**
4145
* @author Steve Ebersole
46+
* @author Yanming Zhou
4247
*/
4348
public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretation<T> implements Assignable, DomainResultProducer<T> {
4449
/**
@@ -83,7 +88,7 @@ public static <T> BasicValuedPathInterpretation<T> from(
8388
}
8489

8590
final ModelPart modelPart;
86-
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
91+
if ( !isFkOptimizationAllowedForState( sqmPath.getLhs(), sqlAstCreationState ) && needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
8792
// We have to make sure we render the column of the target table
8893
modelPart = ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
8994
sqmPath.getReferencedPathSource().getPathName(),
@@ -140,6 +145,29 @@ else if ( expression instanceof SqlSelectionExpression ) {
140145
return new BasicValuedPathInterpretation<>( columnReference, sqmPath.getNavigablePath(), mapping, tableGroup );
141146
}
142147

148+
private static boolean isFkOptimizationAllowedForState(SqmPath<?> sqmPath, SqmToSqlAstConverter sqlAstCreationState) {
149+
boolean isFkOptimizationAllowed = isFkOptimizationAllowed( sqmPath );
150+
if ( isFkOptimizationAllowed ) {
151+
if ( sqlAstCreationState.getCurrentSqmQueryPart() instanceof SqmQuerySpec<?> ) {
152+
final SqmQuerySpec<?> spec = (SqmQuerySpec<?>) sqlAstCreationState.getCurrentSqmQueryPart();
153+
final SqmOrderByClause orderByClause = spec.getOrderByClause();
154+
if ( orderByClause != null && !orderByClause.getSortSpecifications().isEmpty() ) {
155+
final SqmSelectClause selectClause = spec.getSelectClause();
156+
if ( selectClause != null && selectClause.isDistinct() ) {
157+
// DISTINCT query requires sorted column in SELECT list
158+
isFkOptimizationAllowed = false;
159+
}
160+
if ( !spec.getGroupByClauseExpressions().isEmpty() ) {
161+
// PostgreSQL requires sorted column appear in the GROUP BY clause or be used in an aggregate function
162+
isFkOptimizationAllowed = false;
163+
}
164+
}
165+
166+
}
167+
}
168+
return isFkOptimizationAllowed;
169+
}
170+
143171
private final ColumnReference columnReference;
144172

145173
public BasicValuedPathInterpretation(

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) {
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 3 joins, collection table, collection element and relationship
63-
statementInspector.assertNumberOfJoins( 0, 3 );
62+
// Assert 2 joins, collection table, collection element
63+
statementInspector.assertNumberOfJoins( 0, 2 );
6464
}
6565
);
6666
}

hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/ManyToOneTest.java

+23-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
import jakarta.persistence.Entity;
1010
import jakarta.persistence.FetchType;
1111
import jakarta.persistence.Id;
12-
import jakarta.persistence.JoinColumn;
13-
import jakarta.persistence.JoinTable;
1412
import jakarta.persistence.ManyToOne;
1513
import jakarta.persistence.Table;
14+
import jakarta.persistence.criteria.CriteriaBuilder;
15+
import jakarta.persistence.criteria.CriteriaQuery;
16+
import jakarta.persistence.criteria.JoinType;
17+
import jakarta.persistence.criteria.Root;
1618

1719
import org.hibernate.Hibernate;
1820
import org.hibernate.stat.spi.StatisticsImplementor;
19-
21+
import org.hibernate.testing.jdbc.SQLStatementInspector;
2022
import org.hibernate.testing.orm.junit.DomainModel;
2123
import org.hibernate.testing.orm.junit.ServiceRegistry;
2224
import org.hibernate.testing.orm.junit.SessionFactory;
@@ -34,6 +36,7 @@
3436

3537
/**
3638
* @author Andrea Boriero
39+
* @author Yanming Zhou
3740
*/
3841
@DomainModel(
3942
annotatedClasses = {
@@ -304,6 +307,23 @@ public void testDelete(SessionFactoryScope scope) {
304307
);
305308
}
306309

310+
@Test
311+
public void testFkOptimizationWithLeftJoin(SessionFactoryScope scope) {
312+
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
313+
statementInspector.clear();
314+
scope.inTransaction(
315+
session -> {
316+
CriteriaBuilder cb = session.getCriteriaBuilder();
317+
CriteriaQuery<OtherEntity> cq = cb.createQuery( OtherEntity.class );
318+
Root<OtherEntity> root = cq.from( OtherEntity.class );
319+
cq.select(root).where( cb.equal( root.join("simpleEntity", JoinType.LEFT ).get( "id" ), 1 ) );
320+
assertThat( session.createQuery( cq ).getResultList().size(), is(1) );
321+
statementInspector.assertExecutedCount( 1 );
322+
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
323+
}
324+
);
325+
}
326+
307327
@BeforeEach
308328
public void setUp(SessionFactoryScope scope) {
309329
scope.inTransaction(

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public void testHqlSelectSon(SessionFactoryScope scope) {
153153

154154
statementInspector.assertExecutedCount( 2 );
155155
// 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 );
156+
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
157157
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
158158
assertThat( son.getParent(), CoreMatchers.notNullValue() );
159159

0 commit comments

Comments
 (0)