Skip to content

Commit d8c9f9d

Browse files
mbelladebeikov
authored andcommitted
HHH-17837 HHH-18202 Backport fk rendering-side logic for associations in order/group by
1 parent 4772073 commit d8c9f9d

17 files changed

+303
-56
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,9 @@ default int forEachSelectable(SelectableConsumer consumer) {
5959
default boolean hasPartitionedSelectionMapping() {
6060
return isPartitioned();
6161
}
62+
63+
@Override
64+
default BasicValuedModelPart asBasicValuedModelPart() {
65+
return this;
66+
}
6267
}

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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ default EntityMappingType asEntityMappingType(){
148148
return null;
149149
}
150150

151+
@Nullable
152+
default BasicValuedModelPart asBasicValuedModelPart() {
153+
return null;
154+
}
155+
151156
/**
152157
* A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)},
153158
* 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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
6060
private final EntityMappingType associatedEntityTypeDescriptor;
6161
private final NotFoundAction notFoundAction;
6262

63-
private final Set<String> targetKeyPropertyNames;
63+
protected final Set<String> targetKeyPropertyNames;
6464

6565
public AbstractEntityCollectionPart(
6666
Nature nature,
@@ -110,10 +110,6 @@ public EntityMappingType getMappedType() {
110110
return getAssociatedEntityMappingType();
111111
}
112112

113-
protected Set<String> getTargetKeyPropertyNames() {
114-
return targetKeyPropertyNames;
115-
}
116-
117113
@Override
118114
public NavigableRole getNavigableRole() {
119115
return navigableRole;

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

Lines changed: 6 additions & 0 deletions
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;
@@ -135,6 +136,11 @@ public ModelPart findSubPart(String name, EntityMappingType targetType) {
135136
return super.findSubPart( name, targetType );
136137
}
137138

139+
@Override
140+
public Set<String> getTargetKeyPropertyNames() {
141+
return targetKeyPropertyNames;
142+
}
143+
138144
@Override
139145
public <X, Y> int breakDownJdbcValues(
140146
Object domainValue,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,7 @@ public String getTargetKeyPropertyName() {
891891
return targetKeyPropertyName;
892892
}
893893

894+
@Override
894895
public Set<String> getTargetKeyPropertyNames() {
895896
return targetKeyPropertyNames;
896897
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
this.pathConsumer = pathConsumer;
35+
}
36+
37+
@Override
38+
public Object visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
39+
pathConsumer.accept( path );
40+
return path;
41+
}
42+
43+
@Override
44+
public Object visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath<?> path) {
45+
pathConsumer.accept( path );
46+
return path;
47+
}
48+
49+
@Override
50+
public Object visitEntityValuedPath(SqmEntityValuedSimplePath<?> path) {
51+
pathConsumer.accept( path );
52+
return path;
53+
}
54+
55+
@Override
56+
public Object visitAnyValuedValuedPath(SqmAnyValuedSimplePath<?> path) {
57+
pathConsumer.accept( path );
58+
return path;
59+
}
60+
61+
@Override
62+
public Object visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> path) {
63+
pathConsumer.accept( path );
64+
return path;
65+
}
66+
67+
@Override
68+
public Object visitTreatedPath(SqmTreatedPath<?, ?> path) {
69+
pathConsumer.accept( path );
70+
return path;
71+
}
72+
73+
@Override
74+
public Object visitDiscriminatorPath(EntityDiscriminatorSqmPath path) {
75+
pathConsumer.accept( path );
76+
return path;
77+
}
78+
79+
@Override
80+
public Object visitPluralValuedPath(SqmPluralValuedSimplePath<?> path) {
81+
pathConsumer.accept( path );
82+
return path;
83+
}
84+
85+
@Override
86+
public Object visitNonAggregatedCompositeValuedPath(NonAggregatedCompositeSimplePath<?> path) {
87+
pathConsumer.accept( path );
88+
return path;
89+
}
90+
}

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

Lines changed: 88 additions & 7 deletions
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;
@@ -44,22 +45,28 @@
4445
import org.hibernate.query.sqm.SqmQuerySource;
4546
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
4647
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
48+
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
4749
import org.hibernate.query.sqm.tree.SqmDmlStatement;
4850
import org.hibernate.query.sqm.tree.SqmJoinType;
4951
import org.hibernate.query.sqm.tree.SqmStatement;
5052
import org.hibernate.query.sqm.tree.domain.SqmPath;
5153
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
5254
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
55+
import org.hibernate.query.sqm.tree.expression.SqmExpression;
5356
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
5457
import org.hibernate.query.sqm.tree.expression.SqmParameter;
5558
import org.hibernate.query.sqm.tree.from.SqmFrom;
5659
import org.hibernate.query.sqm.tree.from.SqmJoin;
5760
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
5861
import org.hibernate.query.sqm.tree.from.SqmRoot;
62+
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
63+
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
64+
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
5965
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
6066
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
6167
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
6268
import org.hibernate.spi.NavigablePath;
69+
import org.hibernate.sql.ast.Clause;
6370
import org.hibernate.sql.ast.SqlTreeCreationException;
6471
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
6572
import org.hibernate.sql.ast.tree.from.TableGroup;
@@ -73,6 +80,7 @@
7380
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
7481
import org.hibernate.type.spi.TypeConfiguration;
7582

83+
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
7684
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
7785

7886
/**
@@ -126,13 +134,57 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
126134
}
127135

128136
/**
129-
* Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
130-
* dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
137+
* Utility that returns the entity association target's mapping type if the specified {@code sqmPath} should
138+
* be dereferenced using the target table, i.e. when the path's lhs is an explicit join that is used in the
139+
* group by clause, or defaults to the provided {@code modelPartContainer} otherwise.
131140
*/
132-
public static boolean needsTargetTableMapping(SqmPath<?> sqmPath, ModelPartContainer modelPartContainer) {
133-
return modelPartContainer.getPartMappingType() != modelPartContainer
134-
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
135-
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
141+
public static ModelPartContainer getTargetMappingIfNeeded(
142+
SqmPath<?> sqmPath,
143+
ModelPartContainer modelPartContainer,
144+
SqmToSqlAstConverter sqlAstCreationState) {
145+
final SqmQueryPart<?> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
146+
if ( queryPart != null ) {
147+
// We only need to do this for queries
148+
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
149+
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
150+
final ModelPart modelPart;
151+
if ( modelPartContainer instanceof PluralAttributeMapping ) {
152+
modelPart = getCollectionPart(
153+
(PluralAttributeMapping) modelPartContainer,
154+
castNonNull( sqmPath.getNavigablePath().getParent() )
155+
);
156+
}
157+
else {
158+
modelPart = modelPartContainer;
159+
}
160+
if ( modelPart instanceof EntityAssociationMapping ) {
161+
final EntityAssociationMapping association = (EntityAssociationMapping) modelPart;
162+
// If the path is one of the association's target key properties,
163+
// we need to render the target side if in group/order by
164+
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
165+
&& ( clause == Clause.GROUP || clause == Clause.ORDER
166+
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
167+
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
168+
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
169+
return association.getAssociatedEntityMappingType();
170+
}
171+
}
172+
}
173+
}
174+
return modelPartContainer;
175+
}
176+
177+
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
178+
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
179+
if ( nature != null ) {
180+
switch ( nature ) {
181+
case ELEMENT:
182+
return attribute.getElementDescriptor();
183+
case INDEX:
184+
return attribute.getIndexDescriptor();
185+
}
186+
}
187+
return null;
136188
}
137189

138190
/**
@@ -156,6 +208,35 @@ public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
156208
return false;
157209
}
158210

211+
public static List<NavigablePath> getGroupByNavigablePaths(SqmQuerySpec<?> querySpec) {
212+
final List<SqmExpression<?>> expressions = querySpec.getGroupByClauseExpressions();
213+
if ( expressions.isEmpty() ) {
214+
return Collections.emptyList();
215+
}
216+
217+
final List<NavigablePath> navigablePaths = new ArrayList<>( expressions.size() );
218+
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
219+
for ( SqmExpression<?> expression : expressions ) {
220+
expression.accept( pathVisitor );
221+
}
222+
return navigablePaths;
223+
}
224+
225+
public static List<NavigablePath> getOrderByNavigablePaths(SqmQuerySpec<?> querySpec) {
226+
final SqmOrderByClause order = querySpec.getOrderByClause();
227+
if ( order == null || order.getSortSpecifications().isEmpty() ) {
228+
return Collections.emptyList();
229+
}
230+
231+
final List<SqmSortSpecification> sortSpecifications = order.getSortSpecifications();
232+
final List<NavigablePath> navigablePaths = new ArrayList<>( sortSpecifications.size() );
233+
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
234+
for ( SqmSortSpecification sortSpec : sortSpecifications ) {
235+
sortSpec.getSortExpression().accept( pathVisitor );
236+
}
237+
return navigablePaths;
238+
}
239+
159240
public static Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> generateJdbcParamsXref(
160241
DomainParameterXref domainParameterXref,
161242
JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
514514
private boolean negativeAdjustment;
515515

516516
private final Set<AssociationKey> visitedAssociationKeys = new HashSet<>();
517+
private final HashMap<MetadataKey<?, ?>, Object> metadata = new HashMap<>();
517518
private final MappingMetamodel domainModel;
518519

519520
public BaseSqmToSqlAstConverter(
@@ -8389,6 +8390,42 @@ private void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragmen
83898390
orderByFragments.add( new AbstractMap.SimpleEntry<>( orderByFragment, tableGroup ) );
83908391
}
83918392

8393+
@Override
8394+
public <S, M> M resolveMetadata(S source, Function<S, M> producer ) {
8395+
//noinspection unchecked
8396+
return (M) metadata.computeIfAbsent( new MetadataKey<>( source, producer ), k -> producer.apply( source ) );
8397+
}
8398+
8399+
static class MetadataKey<S, M> {
8400+
private final S source;
8401+
private final Function<S, M> producer;
8402+
8403+
public MetadataKey(S source, Function<S, M> producer) {
8404+
this.source = source;
8405+
this.producer = producer;
8406+
}
8407+
8408+
@Override
8409+
public boolean equals(Object o) {
8410+
if ( this == o ) {
8411+
return true;
8412+
}
8413+
if ( o == null || getClass() != o.getClass() ) {
8414+
return false;
8415+
}
8416+
8417+
final MetadataKey<?, ?> that = (MetadataKey<?, ?>) o;
8418+
return source.equals( that.source ) && producer.equals( that.producer );
8419+
}
8420+
8421+
@Override
8422+
public int hashCode() {
8423+
int result = source.hashCode();
8424+
result = 31 * result + producer.hashCode();
8425+
return result;
8426+
}
8427+
}
8428+
83928429
@Override
83938430
public boolean isResolvingCircularFetch() {
83948431
return resolvingCircularFetch;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.query.sqm.sql;
88

99
import java.util.List;
10+
import java.util.function.Function;
1011
import java.util.function.Supplier;
1112

1213
import org.hibernate.LockMode;

0 commit comments

Comments
 (0)