Skip to content

Commit 466fcad

Browse files
mbladelbeikov
authored andcommitted
HHH-17379 HHH-17397 HHH-17837 HHH-18202 Backport fk rendering-side logic for associations in order/group by
1 parent 7066861 commit 466fcad

15 files changed

+323
-70
lines changed

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedModelPart.java

+5
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,9 @@ default int forEachSelectable(SelectableConsumer consumer) {
6767
default boolean hasPartitionedSelectionMapping() {
6868
return isPartitioned();
6969
}
70+
71+
@Override
72+
default BasicValuedModelPart asBasicValuedModelPart() {
73+
return this;
74+
}
7075
}

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77
package org.hibernate.metamodel.mapping;
88

9+
import java.util.Set;
10+
911
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
1012

1113
/**
@@ -21,6 +23,8 @@ default String getFetchableName() {
2123

2224
EntityMappingType getAssociatedEntityMappingType();
2325

26+
Set<String> getTargetKeyPropertyNames();
27+
2428
/**
2529
* The model sub-part relative to the associated entity type that is the target
2630
* of this association's foreign-key

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.hibernate.sql.results.graph.DomainResultCreationState;
2020
import org.hibernate.type.descriptor.java.JavaType;
2121

22+
import org.checkerframework.checker.nullness.qual.Nullable;
23+
2224
/**
2325
* Base descriptor, within the mapping model, for any part of the
2426
* application's domain model: an attribute, an entity identifier,
@@ -146,6 +148,11 @@ default EntityMappingType asEntityMappingType(){
146148
return null;
147149
}
148150

151+
@Nullable
152+
default BasicValuedModelPart asBasicValuedModelPart() {
153+
return null;
154+
}
155+
149156
/**
150157
* A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)},
151158
* that passes 0 as offset and null for the two values {@code X} and {@code Y}.

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java

+1-5
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
6262
private final EntityMappingType associatedEntityTypeDescriptor;
6363
private final NotFoundAction notFoundAction;
6464

65-
private final Set<String> targetKeyPropertyNames;
65+
protected final Set<String> targetKeyPropertyNames;
6666

6767
public AbstractEntityCollectionPart(
6868
Nature nature,
@@ -112,10 +112,6 @@ public EntityMappingType getMappedType() {
112112
return getAssociatedEntityMappingType();
113113
}
114114

115-
protected Set<String> getTargetKeyPropertyNames() {
116-
return targetKeyPropertyNames;
117-
}
118-
119115
@Override
120116
public NavigableRole getNavigableRole() {
121117
return navigableRole;

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.metamodel.mapping.internal;
88

99
import java.util.Locale;
10+
import java.util.Set;
1011
import java.util.function.Consumer;
1112

1213
import org.hibernate.annotations.NotFoundAction;
@@ -137,6 +138,11 @@ public ModelPart findSubPart(String name, EntityMappingType targetType) {
137138
return super.findSubPart( name, targetType );
138139
}
139140

141+
@Override
142+
public Set<String> getTargetKeyPropertyNames() {
143+
return targetKeyPropertyNames;
144+
}
145+
140146
@Override
141147
public <X, Y> int breakDownJdbcValues(
142148
Object domainValue,

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java

+1
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,7 @@ public String getTargetKeyPropertyName() {
873873
return targetKeyPropertyName;
874874
}
875875

876+
@Override
876877
public Set<String> getTargetKeyPropertyNames() {
877878
return targetKeyPropertyNames;
878879
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.query.sqm.internal;
8+
9+
import java.util.function.Consumer;
10+
11+
import org.hibernate.metamodel.model.domain.DiscriminatorSqmPath;
12+
import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPath;
13+
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
14+
import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
15+
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
16+
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
17+
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
18+
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
19+
import org.hibernate.query.sqm.tree.domain.SqmPath;
20+
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
21+
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
22+
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
23+
24+
/**
25+
* Generic {@link org.hibernate.query.sqm.SemanticQueryWalker} that applies the provided
26+
* {@link Consumer} to all {@link SqmPath paths} encountered during visitation.
27+
*
28+
* @author Marco Belladelli
29+
*/
30+
public class SqmPathVisitor extends BaseSemanticQueryWalker {
31+
private final Consumer<SqmPath<?>> pathConsumer;
32+
33+
public SqmPathVisitor(Consumer<SqmPath<?>> pathConsumer) {
34+
super( null );
35+
this.pathConsumer = pathConsumer;
36+
}
37+
38+
@Override
39+
public Object visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
40+
pathConsumer.accept( path );
41+
return path;
42+
}
43+
44+
@Override
45+
public Object visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath<?> path) {
46+
pathConsumer.accept( path );
47+
return path;
48+
}
49+
50+
@Override
51+
public Object visitEntityValuedPath(SqmEntityValuedSimplePath<?> path) {
52+
pathConsumer.accept( path );
53+
return path;
54+
}
55+
56+
@Override
57+
public Object visitAnyValuedValuedPath(SqmAnyValuedSimplePath<?> path) {
58+
pathConsumer.accept( path );
59+
return path;
60+
}
61+
62+
@Override
63+
public Object visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> path) {
64+
pathConsumer.accept( path );
65+
return path;
66+
}
67+
68+
@Override
69+
public Object visitTreatedPath(SqmTreatedPath<?, ?> path) {
70+
pathConsumer.accept( path );
71+
return path;
72+
}
73+
74+
@Override
75+
public Object visitDiscriminatorPath(EntityDiscriminatorSqmPath path) {
76+
pathConsumer.accept( path );
77+
return path;
78+
}
79+
80+
@Override
81+
public Object visitPluralValuedPath(SqmPluralValuedSimplePath<?> path) {
82+
pathConsumer.accept( path );
83+
return path;
84+
}
85+
86+
@Override
87+
public Object visitNonAggregatedCompositeValuedPath(NonAggregatedCompositeSimplePath<?> path) {
88+
pathConsumer.accept( path );
89+
return path;
90+
}
91+
}

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

+103-16
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424
import org.hibernate.metamodel.MappingMetamodel;
2525
import org.hibernate.metamodel.mapping.BasicValuedMapping;
2626
import org.hibernate.metamodel.mapping.Bindable;
27+
import org.hibernate.metamodel.mapping.CollectionPart;
2728
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
2829
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
2930
import org.hibernate.metamodel.mapping.EntityMappingType;
3031
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
3132
import org.hibernate.metamodel.mapping.JdbcMapping;
32-
import org.hibernate.metamodel.mapping.ManagedMappingType;
3333
import org.hibernate.metamodel.mapping.MappingModelExpressible;
34+
import org.hibernate.metamodel.mapping.ModelPart;
3435
import org.hibernate.metamodel.mapping.ModelPartContainer;
3536
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
3637
import org.hibernate.query.IllegalQueryOperationException;
@@ -47,13 +48,17 @@
4748
import org.hibernate.query.sqm.tree.SqmStatement;
4849
import org.hibernate.query.sqm.tree.domain.SqmPath;
4950
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
51+
import org.hibernate.query.sqm.tree.expression.SqmExpression;
5052
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
5153
import org.hibernate.query.sqm.tree.expression.SqmParameter;
5254
import org.hibernate.query.sqm.tree.from.SqmFrom;
5355
import org.hibernate.query.sqm.tree.from.SqmJoin;
56+
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
57+
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
5458
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
55-
import org.hibernate.query.sqm.tree.jpa.ParameterCollector;
59+
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
5660
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
61+
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
5762
import org.hibernate.spi.NavigablePath;
5863
import org.hibernate.sql.ast.Clause;
5964
import org.hibernate.sql.ast.SqlTreeCreationException;
@@ -69,6 +74,9 @@
6974
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
7075
import org.hibernate.type.spi.TypeConfiguration;
7176

77+
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
78+
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
79+
7280
/**
7381
* Helper utilities for dealing with SQM
7482
*
@@ -119,31 +127,110 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
119127
);
120128
}
121129

122-
public static boolean needsTargetTableMapping(
130+
/**
131+
* Utility that returns the entity association target's mapping type if the specified {@code sqmPath} should
132+
* be dereferenced using the target table, i.e. when the path's lhs is an explicit join that is used in the
133+
* group by clause, or defaults to the provided {@code modelPartContainer} otherwise.
134+
*/
135+
public static ModelPartContainer getTargetMappingIfNeeded(
123136
SqmPath<?> sqmPath,
124137
ModelPartContainer modelPartContainer,
125138
SqmToSqlAstConverter sqlAstCreationState) {
126-
final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
127-
return ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
128-
&& modelPartContainer.getPartMappingType() != modelPartContainer
129-
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
130-
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType
131-
&& ( groupByClauseContains( sqlAstCreationState.getCurrentSqmQueryPart(), sqmPath.getNavigablePath() )
132-
|| isNonOptimizableJoin( sqmPath.getLhs() ) );
139+
final SqmQueryPart<?> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
140+
if ( queryPart != null ) {
141+
// We only need to do this for queries
142+
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
143+
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
144+
final ModelPart modelPart;
145+
if ( modelPartContainer instanceof PluralAttributeMapping ) {
146+
modelPart = getCollectionPart(
147+
(PluralAttributeMapping) modelPartContainer,
148+
castNonNull( sqmPath.getNavigablePath().getParent() )
149+
);
150+
}
151+
else {
152+
modelPart = modelPartContainer;
153+
}
154+
if ( modelPart instanceof EntityAssociationMapping ) {
155+
final EntityAssociationMapping association = (EntityAssociationMapping) modelPart;
156+
// If the path is one of the association's target key properties,
157+
// we need to render the target side if in group/order by
158+
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
159+
&& ( clause == Clause.GROUP || clause == Clause.ORDER
160+
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
161+
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
162+
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
163+
return association.getAssociatedEntityMappingType();
164+
}
165+
}
166+
}
167+
}
168+
return modelPartContainer;
133169
}
134170

135-
private static boolean groupByClauseContains(SqmQueryPart<?> sqmQueryPart, NavigablePath path) {
136-
return sqmQueryPart.isSimpleQueryPart() && sqmQueryPart.getFirstQuerySpec().groupByClauseContains( path );
171+
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
172+
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
173+
if ( nature != null ) {
174+
switch ( nature ) {
175+
case ELEMENT:
176+
return attribute.getElementDescriptor();
177+
case INDEX:
178+
return attribute.getIndexDescriptor();
179+
}
180+
}
181+
return null;
137182
}
138183

139-
private static boolean isNonOptimizableJoin(SqmPath<?> sqmPath) {
184+
/**
185+
* Utility that returns {@code false} when the provided {@link SqmPath sqmPath} is
186+
* a join that cannot be dereferenced through the foreign key on the associated table,
187+
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
188+
* or one that has an explicit on clause predicate.
189+
*/
190+
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
140191
if ( sqmPath instanceof SqmJoin<?, ?> ) {
141-
final SqmJoinType sqmJoinType = ( (SqmJoin<?, ?>) sqmPath ).getSqmJoinType();
142-
return sqmJoinType != SqmJoinType.INNER && sqmJoinType != SqmJoinType.LEFT;
192+
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
193+
switch ( sqmJoin.getSqmJoinType() ) {
194+
case INNER:
195+
case LEFT:
196+
return !( sqmJoin instanceof SqmQualifiedJoin<?, ?>)
197+
|| ( (SqmQualifiedJoin<?, ?>) sqmJoin ).getJoinPredicate() == null;
198+
default:
199+
return false;
200+
}
143201
}
144202
return false;
145203
}
146204

205+
public static List<NavigablePath> getGroupByNavigablePaths(SqmQuerySpec<?> querySpec) {
206+
final List<SqmExpression<?>> expressions = querySpec.getGroupByClauseExpressions();
207+
if ( expressions.isEmpty() ) {
208+
return Collections.emptyList();
209+
}
210+
211+
final List<NavigablePath> navigablePaths = new ArrayList<>( expressions.size() );
212+
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
213+
for ( SqmExpression<?> expression : expressions ) {
214+
expression.accept( pathVisitor );
215+
}
216+
return navigablePaths;
217+
}
218+
219+
public static List<NavigablePath> getOrderByNavigablePaths(SqmQuerySpec<?> querySpec) {
220+
final SqmOrderByClause order = querySpec.getOrderByClause();
221+
if ( order == null || order.getSortSpecifications().isEmpty() ) {
222+
return Collections.emptyList();
223+
}
224+
225+
final List<SqmSortSpecification> sortSpecifications = order.getSortSpecifications();
226+
final List<NavigablePath> navigablePaths = new ArrayList<>( sortSpecifications.size() );
227+
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
228+
for ( SqmSortSpecification sortSpec : sortSpecifications ) {
229+
sortSpec.getSortExpression().accept( pathVisitor );
230+
}
231+
return navigablePaths;
232+
}
233+
147234
public static Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> generateJdbcParamsXref(
148235
DomainParameterXref domainParameterXref,
149236
JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {
@@ -523,7 +610,7 @@ public static SqmStatement.ParameterResolutions resolveParameters(SqmStatement<?
523610
if ( statement.getQuerySource() == SqmQuerySource.CRITERIA ) {
524611
final CriteriaParameterCollector parameterCollector = new CriteriaParameterCollector();
525612

526-
ParameterCollector.collectParameters(
613+
collectParameters(
527614
statement,
528615
parameterCollector::process,
529616
statement.nodeBuilder().getServiceRegistry()

0 commit comments

Comments
 (0)