HHH-17837 HHH-18202 Backport fk rendering-side logic for associations in order/group by

This commit is contained in:
Marco Belladelli 2024-03-22 11:15:10 +01:00 committed by Christian Beikov
parent 4772073a54
commit d8c9f9dba9
17 changed files with 303 additions and 56 deletions

View File

@ -59,4 +59,9 @@ public interface BasicValuedModelPart extends BasicValuedMapping, ValuedModelPar
default boolean hasPartitionedSelectionMapping() {
return isPartitioned();
}
@Override
default BasicValuedModelPart asBasicValuedModelPart() {
return this;
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.metamodel.mapping;
import java.util.Set;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
/**
@ -21,6 +23,8 @@ public interface EntityAssociationMapping extends ModelPart, Association, TableG
EntityMappingType getAssociatedEntityMappingType();
Set<String> getTargetKeyPropertyNames();
/**
* The model sub-part relative to the associated entity type that is the target
* of this association's foreign-key

View File

@ -148,6 +148,11 @@ public interface ModelPart extends MappingModelExpressible {
return null;
}
@Nullable
default BasicValuedModelPart asBasicValuedModelPart() {
return null;
}
/**
* A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)},
* that passes 0 as offset and null for the two values {@code X} and {@code Y}.

View File

@ -60,7 +60,7 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
private final EntityMappingType associatedEntityTypeDescriptor;
private final NotFoundAction notFoundAction;
private final Set<String> targetKeyPropertyNames;
protected final Set<String> targetKeyPropertyNames;
public AbstractEntityCollectionPart(
Nature nature,
@ -110,10 +110,6 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
return getAssociatedEntityMappingType();
}
protected Set<String> getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}
@Override
public NavigableRole getNavigableRole() {
return navigableRole;

View File

@ -7,6 +7,7 @@
package org.hibernate.metamodel.mapping.internal;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.annotations.NotFoundAction;
@ -135,6 +136,11 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
return super.findSubPart( name, targetType );
}
@Override
public Set<String> getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}
@Override
public <X, Y> int breakDownJdbcValues(
Object domainValue,

View File

@ -891,6 +891,7 @@ public class ToOneAttributeMapping
return targetKeyPropertyName;
}
@Override
public Set<String> getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}

View File

@ -0,0 +1,90 @@
/*
* 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.query.sqm.internal;
import java.util.function.Consumer;
import org.hibernate.metamodel.model.domain.DiscriminatorSqmPath;
import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPath;
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
/**
* Generic {@link org.hibernate.query.sqm.SemanticQueryWalker} that applies the provided
* {@link Consumer} to all {@link SqmPath paths} encountered during visitation.
*
* @author Marco Belladelli
*/
public class SqmPathVisitor extends BaseSemanticQueryWalker {
private final Consumer<SqmPath<?>> pathConsumer;
public SqmPathVisitor(Consumer<SqmPath<?>> pathConsumer) {
this.pathConsumer = pathConsumer;
}
@Override
public Object visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath<?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitEntityValuedPath(SqmEntityValuedSimplePath<?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitAnyValuedValuedPath(SqmAnyValuedSimplePath<?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitTreatedPath(SqmTreatedPath<?, ?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitDiscriminatorPath(EntityDiscriminatorSqmPath path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitPluralValuedPath(SqmPluralValuedSimplePath<?> path) {
pathConsumer.accept( path );
return path;
}
@Override
public Object visitNonAggregatedCompositeValuedPath(NonAggregatedCompositeSimplePath<?> path) {
pathConsumer.accept( path );
return path;
}
}

View File

@ -24,13 +24,14 @@ import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.IllegalQueryOperationException;
@ -44,22 +45,28 @@ import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.TableGroup;
@ -73,6 +80,7 @@ import org.hibernate.type.internal.BasicTypeImpl;
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
/**
@ -126,13 +134,57 @@ public class SqmUtil {
}
/**
* Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
* dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
* Utility that returns the entity association target's mapping type if the specified {@code sqmPath} should
* be dereferenced using the target table, i.e. when the path's lhs is an explicit join that is used in the
* group by clause, or defaults to the provided {@code modelPartContainer} otherwise.
*/
public static boolean needsTargetTableMapping(SqmPath<?> sqmPath, ModelPartContainer modelPartContainer) {
return modelPartContainer.getPartMappingType() != modelPartContainer
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
public static ModelPartContainer getTargetMappingIfNeeded(
SqmPath<?> sqmPath,
ModelPartContainer modelPartContainer,
SqmToSqlAstConverter sqlAstCreationState) {
final SqmQueryPart<?> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
if ( queryPart != null ) {
// We only need to do this for queries
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
final ModelPart modelPart;
if ( modelPartContainer instanceof PluralAttributeMapping ) {
modelPart = getCollectionPart(
(PluralAttributeMapping) modelPartContainer,
castNonNull( sqmPath.getNavigablePath().getParent() )
);
}
else {
modelPart = modelPartContainer;
}
if ( modelPart instanceof EntityAssociationMapping ) {
final EntityAssociationMapping association = (EntityAssociationMapping) modelPart;
// If the path is one of the association's target key properties,
// we need to render the target side if in group/order by
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
&& ( clause == Clause.GROUP || clause == Clause.ORDER
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
return association.getAssociatedEntityMappingType();
}
}
}
}
return modelPartContainer;
}
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
if ( nature != null ) {
switch ( nature ) {
case ELEMENT:
return attribute.getElementDescriptor();
case INDEX:
return attribute.getIndexDescriptor();
}
}
return null;
}
/**
@ -156,6 +208,35 @@ public class SqmUtil {
return false;
}
public static List<NavigablePath> getGroupByNavigablePaths(SqmQuerySpec<?> querySpec) {
final List<SqmExpression<?>> expressions = querySpec.getGroupByClauseExpressions();
if ( expressions.isEmpty() ) {
return Collections.emptyList();
}
final List<NavigablePath> navigablePaths = new ArrayList<>( expressions.size() );
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
for ( SqmExpression<?> expression : expressions ) {
expression.accept( pathVisitor );
}
return navigablePaths;
}
public static List<NavigablePath> getOrderByNavigablePaths(SqmQuerySpec<?> querySpec) {
final SqmOrderByClause order = querySpec.getOrderByClause();
if ( order == null || order.getSortSpecifications().isEmpty() ) {
return Collections.emptyList();
}
final List<SqmSortSpecification> sortSpecifications = order.getSortSpecifications();
final List<NavigablePath> navigablePaths = new ArrayList<>( sortSpecifications.size() );
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
for ( SqmSortSpecification sortSpec : sortSpecifications ) {
sortSpec.getSortExpression().accept( pathVisitor );
}
return navigablePaths;
}
public static Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> generateJdbcParamsXref(
DomainParameterXref domainParameterXref,
JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {

View File

@ -514,6 +514,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private boolean negativeAdjustment;
private final Set<AssociationKey> visitedAssociationKeys = new HashSet<>();
private final HashMap<MetadataKey<?, ?>, Object> metadata = new HashMap<>();
private final MappingMetamodel domainModel;
public BaseSqmToSqlAstConverter(
@ -8389,6 +8390,42 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
orderByFragments.add( new AbstractMap.SimpleEntry<>( orderByFragment, tableGroup ) );
}
@Override
public <S, M> M resolveMetadata(S source, Function<S, M> producer ) {
//noinspection unchecked
return (M) metadata.computeIfAbsent( new MetadataKey<>( source, producer ), k -> producer.apply( source ) );
}
static class MetadataKey<S, M> {
private final S source;
private final Function<S, M> producer;
public MetadataKey(S source, Function<S, M> producer) {
this.source = source;
this.producer = producer;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
final MetadataKey<?, ?> that = (MetadataKey<?, ?>) o;
return source.equals( that.source ) && producer.equals( that.producer );
}
@Override
public int hashCode() {
int result = source.hashCode();
result = 31 * result + producer.hashCode();
return result;
}
}
@Override
public boolean isResolvingCircularFetch() {
return resolvingCircularFetch;

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.sql;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.LockMode;

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.sql;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.internal.util.collections.Stack;
@ -23,6 +24,8 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import jakarta.annotation.Nullable;
/**
* Specialized SemanticQueryWalker (SQM visitor) for producing SQL AST.
*
@ -52,4 +55,10 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
Predicate visitNestedTopLevelPredicate(SqmPredicate predicate);
/**
* Resolve a generic metadata object from the provided source, using the specified producer.
*/
default <S, M> M resolveMetadata(S source, Function<S, M> producer) {
return producer.apply( source );
}
}

View File

@ -13,7 +13,6 @@ import java.util.function.Consumer;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
@ -34,7 +33,8 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.update.Assignable;
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
/**
* @author Steve Ebersole
@ -81,22 +81,14 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
}
}
final BasicValuedModelPart mapping;
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
// We have to make sure we render the column of the target table
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
sqmPath.getReferencedPathSource().getPathName(),
treatTarget
);
}
else {
mapping = (BasicValuedModelPart) modelPartContainer.findSubPart(
sqmPath.getReferencedPathSource().getPathName(),
treatTarget
);
}
// Use the target type to find the sub part if needed, otherwise just use the container
final ModelPart modelPart = getTargetMappingIfNeeded(
sqmPath,
modelPartContainer,
sqlAstCreationState
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
if ( mapping == null ) {
if ( modelPart == null ) {
if ( jpaQueryComplianceEnabled ) {
// to get the better error, see if we got nothing because of treat handling
final ModelPart subPart = tableGroup.getModelPart().findSubPart(
@ -111,6 +103,7 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
throw new UnknownPathException( "Path '" + sqmPath.getNavigablePath() + "' did not reference a known model part" );
}
final BasicValuedModelPart mapping = castNonNull( modelPart.asBasicValuedModelPart() );
final TableReference tableReference = tableGroup.resolveTableReference(
sqmPath.getNavigablePath(),
mapping,

View File

@ -13,7 +13,6 @@ import java.util.function.Consumer;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
@ -29,7 +28,7 @@ import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.update.Assignable;
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
/**
* @author Steve Ebersole
@ -65,20 +64,12 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
}
final ModelPartContainer modelPartContainer = tableGroup.getModelPart();
final EmbeddableValuedModelPart mapping;
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
// We have to make sure we render the column of the target table
mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
sqmPath.getReferencedPathSource().getPathName(),
treatTarget
);
}
else {
mapping = (EmbeddableValuedModelPart) modelPartContainer.findSubPart(
sqmPath.getReferencedPathSource().getPathName(),
treatTarget
);
}
// Use the target type to find the sub part if needed, otherwise just use the container
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) getTargetMappingIfNeeded(
sqmPath,
modelPartContainer,
sqlAstCreationState
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
return new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression(

View File

@ -162,7 +162,9 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
// we try to make use of it and the FK model part if possible based on the inferred mapping
if ( mapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
final ModelPart keyTargetMatchPart = associationMapping.getForeignKeyDescriptor().getPart(
associationMapping.getSideNature()
);
if ( associationMapping.isFkOptimizationAllowed() ) {
final boolean forceUsingForeignKeyAssociationSidePart;
@ -265,7 +267,7 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
if ( currentClause == Clause.GROUP || currentClause == Clause.ORDER ) {
assert sqlAstCreationState.getCurrentSqmQueryPart().isSimpleQueryPart();
final SqmQuerySpec<?> querySpec = sqlAstCreationState.getCurrentSqmQueryPart().getFirstQuerySpec();
if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath ) ) {
if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath, sqlAstCreationState ) ) {
// 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
expandToAllColumns = false;

View File

@ -12,6 +12,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.Internal;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
@ -25,11 +26,12 @@ import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmNode;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
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.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
@ -729,9 +731,32 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
}
}
public boolean groupByClauseContains(NavigablePath path) {
for ( SqmExpression<?> expression : groupByClauseExpressions ) {
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath() == path ) {
@Internal
public boolean groupByClauseContains(NavigablePath navigablePath, SqmToSqlAstConverter sqlAstConverter) {
if ( groupByClauseExpressions.isEmpty() ) {
return false;
}
return navigablePathsContain( sqlAstConverter.resolveMetadata(
this,
SqmUtil::getGroupByNavigablePaths
), navigablePath );
}
@Internal
public boolean orderByClauseContains(NavigablePath navigablePath, SqmToSqlAstConverter sqlAstConverter) {
final SqmOrderByClause orderByClause = getOrderByClause();
if ( orderByClause == null || orderByClause.getSortSpecifications().isEmpty() ) {
return false;
}
return navigablePathsContain( sqlAstConverter.resolveMetadata(
this,
SqmUtil::getOrderByNavigablePaths
), navigablePath );
}
private boolean navigablePathsContain(List<NavigablePath> navigablePaths, NavigablePath navigablePath) {
for ( NavigablePath path : navigablePaths ) {
if ( path.isParentOrEqual( navigablePath ) ) {
return true;
}
}

View File

@ -52,15 +52,15 @@ public class MapIssueTest {
}
@Test
public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) {
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction(
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 and collection element. No need to join the relationship because it is not nullable
statementInspector.assertNumberOfJoins( 0, 2 );
}
);
}

View File

@ -152,8 +152,9 @@ public class EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest {
.getSingleResult();
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 );
// The join to the target table PARENT for Male#parent is avoided,
// because the FK in the collection table is not-null and data from the target table is not needed
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
assertThat( son.getParent(), CoreMatchers.notNullValue() );