HHH-16691 Avoid join table joins for SqmPath in some more scenarios

This commit is contained in:
Christian Beikov 2023-05-25 10:37:18 +02:00
parent 908630a7f2
commit 4dc03a9c39
8 changed files with 120 additions and 43 deletions

View File

@ -762,6 +762,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return currentSqmQueryPart; return currentSqmQueryPart;
} }
@Override
public SqmStatement<?> getCurrentSqmStatement() {
return currentSqmStatement;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Statements // Statements
@ -891,16 +896,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
throw new SemanticException( "user-defined version types not supported for increment option" ); throw new SemanticException( "user-defined version types not supported for increment option" );
} }
currentClauseStack.push( Clause.SET );
final EntityVersionMapping versionMapping = persister.getVersionMapping(); final EntityVersionMapping versionMapping = persister.getVersionMapping();
final List<ColumnReference> targetColumnReferences = BasicValuedPathInterpretation.from( final List<ColumnReference> targetColumnReferences = BasicValuedPathInterpretation.from(
(SqmBasicValuedSimplePath<?>) sqmStatement (SqmBasicValuedSimplePath<?>) sqmStatement
.getRoot() .getRoot()
.get( versionMapping.getPartName() ), .get( versionMapping.getPartName() ),
this, this,
this, jpaQueryComplianceEnabled
jpaQueryComplianceEnabled,
Clause.SET
).getColumnReferences(); ).getColumnReferences();
currentClauseStack.pop();
assert targetColumnReferences.size() == 1; assert targetColumnReferences.size() == 1;
final ColumnReference versionColumn = targetColumnReferences.get( 0 ); final ColumnReference versionColumn = targetColumnReferences.get( 0 );
@ -1344,9 +1349,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
(SqmBasicValuedSimplePath<?>) sqmStatement.getTarget() (SqmBasicValuedSimplePath<?>) sqmStatement.getTarget()
.get( versionAttributeName ), .get( versionAttributeName ),
this, this,
this, jpaQueryComplianceEnabled
jpaQueryComplianceEnabled,
getCurrentClauseStack().getCurrent()
); );
final List<ColumnReference> targetColumnReferences = versionPath.getColumnReferences(); final List<ColumnReference> targetColumnReferences = versionPath.getColumnReferences();
assert targetColumnReferences.size() == 1; assert targetColumnReferences.size() == 1;
@ -4151,9 +4154,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
() -> BasicValuedPathInterpretation.from( () -> BasicValuedPathInterpretation.from(
sqmPath, sqmPath,
this, this,
this, jpaQueryComplianceEnabled
jpaQueryComplianceEnabled,
getCurrentClauseStack().getCurrent()
) )
); );
Expression result = path; Expression result = path;
@ -4254,9 +4255,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
() -> EmbeddableValuedPathInterpretation.from( () -> EmbeddableValuedPathInterpretation.from(
sqmPath, sqmPath,
this, this,
this, jpaQueryComplianceEnabled
jpaQueryComplianceEnabled,
getCurrentClauseStack().getCurrent()
) )
), ),
sqmPath sqmPath

View File

@ -14,6 +14,7 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.SqmVisitableNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter;
@ -92,6 +93,11 @@ public class FakeSqmToSqlAstConverter extends BaseSemanticQueryWalker implements
return null; return null;
} }
@Override
public SqmStatement<?> getCurrentSqmStatement() {
return null;
}
@Override @Override
public void registerQueryTransformer(QueryTransformer transformer) { public void registerQueryTransformer(QueryTransformer transformer) {
} }

View File

@ -12,6 +12,7 @@ import java.util.function.Supplier;
import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.SqmVisitableNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter;
@ -33,6 +34,8 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
SqmQueryPart<?> getCurrentSqmQueryPart(); SqmQueryPart<?> getCurrentSqmQueryPart();
SqmStatement<?> getCurrentSqmStatement();
void registerQueryTransformer(QueryTransformer transformer); void registerQueryTransformer(QueryTransformer transformer);
/** /**

View File

@ -18,19 +18,20 @@ import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.spi.NavigablePath; import org.hibernate.spi.NavigablePath;
import org.hibernate.query.SemanticException; import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.StrictJpaComplianceViolation; import org.hibernate.query.sqm.StrictJpaComplianceViolation;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -47,10 +48,8 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
*/ */
public static <T> BasicValuedPathInterpretation<T> from( public static <T> BasicValuedPathInterpretation<T> from(
SqmBasicValuedSimplePath<T> sqmPath, SqmBasicValuedSimplePath<T> sqmPath,
SqlAstCreationState sqlAstCreationState, SqmToSqlAstConverter sqlAstCreationState,
SemanticQueryWalker<?> sqmWalker, boolean jpaQueryComplianceEnabled) {
boolean jpaQueryComplianceEnabled,
Clause currentClause) {
final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess(); final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess();
final TableGroup tableGroup = fromClauseAccess.getTableGroup( sqmPath.getNavigablePath().getParent() ); final TableGroup tableGroup = fromClauseAccess.getTableGroup( sqmPath.getNavigablePath().getParent() );
@ -89,10 +88,15 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
final BasicValuedModelPart mapping; final BasicValuedModelPart mapping;
// In the select, group by, order by and having clause we have to make sure we render the column of the target table, // In the select, group by, order by and having clause we have to make sure we render the column of the target table,
// never the FK column, if the lhs is a SqmFrom i.e. something explicitly queried/joined. // never the FK column, if the lhs is a SqmFrom i.e. something explicitly queried/joined
// and if this basic path is part of the group by clause
final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
final SqmStatement<?> sqmStatement = sqlAstCreationState.getCurrentSqmStatement();
if ( ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING ) if ( ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
&& sqmPath.getLhs() instanceof SqmFrom<?, ?> && sqmPath.getLhs() instanceof SqmFrom<?, ?>
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType ) { && modelPartContainer.getPartMappingType() instanceof ManagedMappingType
&& sqmStatement instanceof SqmSelectStatement<?>
&& ( (SqmSelectStatement<?>) sqmStatement ).getQuerySpec().groupByClauseContains( sqmPath.getNavigablePath() ) ) {
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart( mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
sqmPath.getReferencedPathSource().getPathName(), sqmPath.getReferencedPathSource().getPathName(),
treatTarget treatTarget

View File

@ -16,9 +16,10 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.spi.NavigablePath; import org.hibernate.spi.NavigablePath;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
@ -41,15 +42,13 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
*/ */
public static <T> Expression from( public static <T> Expression from(
SqmEmbeddedValuedSimplePath<T> sqmPath, SqmEmbeddedValuedSimplePath<T> sqmPath,
SqmToSqlAstConverter converter, SqmToSqlAstConverter sqlAstCreationState,
SemanticQueryWalker<?> sqmWalker, boolean jpaQueryComplianceEnabled) {
boolean jpaQueryComplianceEnabled, TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess().findTableGroup( sqmPath.getLhs().getNavigablePath() );
Clause currentClause) {
TableGroup tableGroup = converter.getFromClauseAccess().findTableGroup( sqmPath.getLhs().getNavigablePath() );
EntityMappingType treatTarget = null; EntityMappingType treatTarget = null;
if ( jpaQueryComplianceEnabled ) { if ( jpaQueryComplianceEnabled ) {
final MappingMetamodel mappingMetamodel = converter.getCreationContext() final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
.getSessionFactory() .getSessionFactory()
.getRuntimeMetamodels() .getRuntimeMetamodels()
.getMappingMetamodel(); .getMappingMetamodel();
@ -69,10 +68,15 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
final ModelPartContainer modelPart = tableGroup.getModelPart(); final ModelPartContainer modelPart = tableGroup.getModelPart();
final EmbeddableValuedModelPart mapping; final EmbeddableValuedModelPart mapping;
// In the select, group by, order by and having clause we have to make sure we render the column of the target table, // In the select, group by, order by and having clause we have to make sure we render the column of the target table,
// never the FK column, if the lhs is a SqmFrom i.e. something explicitly queried/joined. // never the FK column, if the lhs is a SqmFrom i.e. something explicitly queried/joined
// and if this basic path is part of the group by clause
final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
final SqmStatement<?> sqmStatement = sqlAstCreationState.getCurrentSqmStatement();
if ( ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING ) if ( ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
&& sqmPath.getLhs() instanceof SqmFrom<?, ?> && sqmPath.getLhs() instanceof SqmFrom<?, ?>
&& modelPart.getPartMappingType() instanceof ManagedMappingType ) { && modelPart.getPartMappingType() instanceof ManagedMappingType
&& sqmStatement instanceof SqmSelectStatement<?>
&& ( (SqmSelectStatement<?>) sqmStatement ).getQuerySpec().groupByClauseContains( sqmPath.getNavigablePath() ) ) {
mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPart.getPartMappingType() ).findSubPart( mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPart.getPartMappingType() ).findSubPart(
sqmPath.getReferencedPathSource().getPathName(), sqmPath.getReferencedPathSource().getPathName(),
treatTarget treatTarget
@ -88,9 +92,9 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
return new EmbeddableValuedPathInterpretation<>( return new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression( mapping.toSqlExpression(
tableGroup, tableGroup,
converter.getCurrentClauseStack().getCurrent(), currentClause,
converter, sqlAstCreationState,
converter sqlAstCreationState
), ),
sqmPath.getNavigablePath(), sqmPath.getNavigablePath(),
mapping, mapping,

View File

@ -27,7 +27,6 @@ import org.hibernate.query.derived.AnonymousTupleEntityValuedModelPart;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation;
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
@ -259,7 +258,7 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent(); final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
if ( currentClause == Clause.GROUP || currentClause == Clause.ORDER ) { if ( currentClause == Clause.GROUP || currentClause == Clause.ORDER ) {
final SqmQuerySpec<?> querySpec = (SqmQuerySpec<?>) sqlAstCreationState.getCurrentSqmQueryPart(); final SqmQuerySpec<?> querySpec = (SqmQuerySpec<?>) sqlAstCreationState.getCurrentSqmQueryPart();
if ( currentClause == Clause.ORDER && !groupByClauseContains( navigablePath, querySpec ) ) { if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath ) ) {
// We must ensure that the order by expression be expanded but only if the group by // We must ensure that the order by expression be expanded but only if the group by
// contained the same expression, and that was expanded as well // contained the same expression, and that was expanded as well
expandToAllColumns = false; expandToAllColumns = false;
@ -373,15 +372,6 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
return false; return false;
} }
private static boolean groupByClauseContains(NavigablePath path, SqmQuerySpec<?> sqmQuerySpec) {
for ( SqmExpression<?> expression : sqmQuerySpec.getGroupByClauseExpressions() ) {
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath() == path ) {
return true;
}
}
return false;
}
private final Expression sqlExpression; private final Expression sqlExpression;
public EntityValuedPathInterpretation( public EntityValuedPathInterpretation(

View File

@ -28,6 +28,7 @@ import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmNode; import org.hibernate.query.sqm.tree.SqmNode;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
@ -42,6 +43,7 @@ import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClauseContainer; import org.hibernate.query.sqm.tree.predicate.SqmWhereClauseContainer;
import org.hibernate.spi.NavigablePath;
import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
@ -698,4 +700,13 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
appendJoins( sqmTreat, sb ); appendJoins( sqmTreat, sb );
} }
} }
public boolean groupByClauseContains(NavigablePath path) {
for ( SqmExpression<?> expression : groupByClauseExpressions ) {
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath() == path ) {
return true;
}
}
return false;
}
} }

View File

@ -0,0 +1,60 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.jpa.ql;
import java.util.Set;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinTable;
import jakarta.persistence.OneToMany;
@TestForIssue(jiraKey = "HHH-16691")
@DomainModel(
annotatedClasses = {
JoinTableOptimizationTest.Document.class, JoinTableOptimizationTest.Person.class
})
@SessionFactory(useCollectingStatementInspector = true)
public class JoinTableOptimizationTest {
@Test
public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
s -> {
s.createQuery( "select p.id from Document d left join d.people p where p.id is not null" ).list();
statementInspector.assertExecutedCount( 1 );
// Assert only the collection table is joined
statementInspector.assertNumberOfJoins( 0, 1 );
}
);
}
@Entity(name = "Document")
public static class Document {
@Id
Long id;
String name;
@OneToMany
@JoinTable(name = "people")
Set<Person> people;
}
@Entity(name = "Person")
public static class Person {
@Id
Long id;
String name;
}
}