Skip to content
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

HHH-17706 Optimize FK comparison to eliminate unnecessary left join #7782

Closed
wants to merge 1 commit into from
Closed
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 @@ -25,6 +25,9 @@
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
Expand All @@ -35,10 +38,12 @@
import org.hibernate.sql.ast.tree.update.Assignable;

import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.query.sqm.internal.SqmUtil.isFkOptimizationAllowed;
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;

/**
* @author Steve Ebersole
* @author Yanming Zhou
*/
public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretation<T> implements Assignable, DomainResultProducer<T> {
/**
Expand Down Expand Up @@ -83,7 +88,7 @@ public static <T> BasicValuedPathInterpretation<T> from(
}

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

private static boolean isFkOptimizationAllowedForState(SqmPath<?> sqmPath, SqmToSqlAstConverter sqlAstCreationState) {
boolean isFkOptimizationAllowed = isFkOptimizationAllowed( sqmPath );
if ( isFkOptimizationAllowed ) {
if ( sqlAstCreationState.getCurrentSqmQueryPart() instanceof SqmQuerySpec<?> ) {
final SqmQuerySpec<?> spec = (SqmQuerySpec<?>) sqlAstCreationState.getCurrentSqmQueryPart();
final SqmOrderByClause orderByClause = spec.getOrderByClause();
if ( orderByClause != null && !orderByClause.getSortSpecifications().isEmpty() ) {
final SqmSelectClause selectClause = spec.getSelectClause();
if ( selectClause != null && selectClause.isDistinct() ) {
// DISTINCT query requires sorted column in SELECT list
isFkOptimizationAllowed = false;
}
if ( !spec.getGroupByClauseExpressions().isEmpty() ) {
// PostgreSQL requires sorted column appear in the GROUP BY clause or be used in an aggregate function
isFkOptimizationAllowed = false;
}
}

}
}
return isFkOptimizationAllowed;
}

private final ColumnReference columnReference;

public BasicValuedPathInterpretation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) {
s -> {
s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list();
statementInspector.assertExecutedCount( 1 );
// Assert 3 joins, collection table, collection element and relationship
statementInspector.assertNumberOfJoins( 0, 3 );
// Assert 2 joins, collection table, collection element
statementInspector.assertNumberOfJoins( 0, 2 );
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Root;

import org.hibernate.Hibernate;
import org.hibernate.stat.spi.StatisticsImplementor;

import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
Expand All @@ -34,6 +36,7 @@

/**
* @author Andrea Boriero
* @author Yanming Zhou
*/
@DomainModel(
annotatedClasses = {
Expand Down Expand Up @@ -304,6 +307,23 @@ public void testDelete(SessionFactoryScope scope) {
);
}

@Test
public void testFkOptimizationWithLeftJoin(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<OtherEntity> cq = cb.createQuery( OtherEntity.class );
Root<OtherEntity> root = cq.from( OtherEntity.class );
cq.select(root).where( cb.equal( root.join("simpleEntity", JoinType.LEFT ).get( "id" ), 1 ) );
assertThat( session.createQuery( cq ).getResultList().size(), is(1) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}

@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void testHqlSelectSon(SessionFactoryScope scope) {

statementInspector.assertExecutedCount( 2 );
// The join to the target table PARENT for Male#parent is added since it's explicitly joined in HQL
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
assertThat( son.getParent(), CoreMatchers.notNullValue() );

Expand Down