Skip to content

Commit 0f80872

Browse files
committed
HHH-18816 FK rendering: check parent query specs for group by / order by
1 parent 3bac078 commit 0f80872

File tree

4 files changed

+83
-51
lines changed

4 files changed

+83
-51
lines changed

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

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.hibernate.engine.spi.SessionFactoryImplementor;
2222
import org.hibernate.engine.spi.SharedSessionContractImplementor;
2323
import org.hibernate.internal.util.collections.CollectionHelper;
24+
import org.hibernate.internal.util.collections.Stack;
2425
import org.hibernate.jpa.spi.JpaCompliance;
2526
import org.hibernate.metamodel.mapping.BasicValuedMapping;
2627
import org.hibernate.metamodel.mapping.Bindable;
@@ -168,48 +169,72 @@ public static ModelPartContainer getTargetMappingIfNeeded(
168169
SqmPath<?> sqmPath,
169170
ModelPartContainer modelPartContainer,
170171
SqmToSqlAstConverter sqlAstCreationState) {
171-
final SqmQueryPart<?> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
172-
if ( queryPart != null ) {
173-
// We only need to do this for queries
174-
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
175-
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
176-
final ModelPart modelPart;
177-
if ( modelPartContainer instanceof PluralAttributeMapping ) {
178-
modelPart = getCollectionPart(
179-
(PluralAttributeMapping) modelPartContainer,
180-
castNonNull( sqmPath.getNavigablePath().getParent() )
181-
);
182-
}
183-
else {
184-
modelPart = modelPartContainer;
185-
}
186-
if ( modelPart instanceof EntityAssociationMapping association ) {
187-
// If the path is one of the association's target key properties,
188-
// we need to render the target side if in group/order by
189-
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
190-
&& ( clause == Clause.GROUP || clause == Clause.ORDER
191-
|| !isFkOptimizationAllowed( sqmPath.getLhs(), association )
192-
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
193-
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
194-
return association.getAssociatedEntityMappingType();
195-
}
172+
// We only need to do this for queries
173+
if ( sqlAstCreationState.getCurrentClauseStack().getCurrent() != Clause.FROM
174+
&& modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
175+
final ModelPart modelPart;
176+
if ( modelPartContainer instanceof PluralAttributeMapping pluralAttributeMapping ) {
177+
modelPart = getCollectionPart(
178+
pluralAttributeMapping,
179+
castNonNull( sqmPath.getNavigablePath().getParent() )
180+
);
181+
}
182+
else {
183+
modelPart = modelPartContainer;
184+
}
185+
if ( modelPart instanceof EntityAssociationMapping association ) {
186+
if ( shouldRenderTargetSide( sqmPath, association, sqlAstCreationState ) ) {
187+
return association.getAssociatedEntityMappingType();
196188
}
197189
}
198190
}
199191
return modelPartContainer;
200192
}
201193

194+
private static boolean shouldRenderTargetSide(
195+
SqmPath<?> sqmPath,
196+
EntityAssociationMapping association,
197+
SqmToSqlAstConverter sqlAstCreationState) {
198+
if ( !association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() ) ) {
199+
return false;
200+
}
201+
// If the path is one of the association's target key properties,
202+
// we need to render the target side if in group/order by
203+
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
204+
return clause == Clause.GROUP || clause == Clause.ORDER
205+
|| !isFkOptimizationAllowed( sqmPath.getLhs(), association )
206+
|| clauseContainsPath( Clause.GROUP, sqmPath, sqlAstCreationState )
207+
|| clauseContainsPath( Clause.ORDER, sqmPath, sqlAstCreationState );
208+
}
209+
210+
private static boolean clauseContainsPath(
211+
Clause clauseToCheck,
212+
SqmPath<?> sqmPath,
213+
SqmToSqlAstConverter sqlAstCreationState) {
214+
final Stack<SqmQueryPart> queryPartStack = sqlAstCreationState.getSqmQueryPartStack();
215+
final NavigablePath navigablePath = sqmPath.getNavigablePath();
216+
final Boolean found = queryPartStack.findCurrentFirst( queryPart -> {
217+
final SqmQuerySpec<?> spec = queryPart.getFirstQuerySpec();
218+
if ( clauseToCheck == Clause.GROUP && spec.groupByClauseContains( navigablePath, sqlAstCreationState ) ) {
219+
return true;
220+
}
221+
else if ( clauseToCheck == Clause.ORDER && spec.orderByClauseContains( navigablePath, sqlAstCreationState ) ) {
222+
return true;
223+
}
224+
else {
225+
return null;
226+
}
227+
} );
228+
return Boolean.TRUE.equals( found );
229+
}
230+
202231
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
203232
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
204-
if ( nature != null ) {
205-
switch ( nature ) {
206-
case ELEMENT:
207-
return attribute.getElementDescriptor();
208-
case INDEX:
209-
return attribute.getIndexDescriptor();
210-
}
211-
}
212-
return null;
233+
return nature != null ? switch ( nature ) {
234+
case ELEMENT -> attribute.getElementDescriptor();
235+
case INDEX -> attribute.getIndexDescriptor();
236+
case ID -> attribute.getIdentifierDescriptor();
237+
} : null;
213238
}
214239

215240
/**

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

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
488488
private boolean deduplicateSelectionItems;
489489
private ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide;
490490
private SqmStatement<?> currentSqmStatement;
491-
private SqmQueryPart<?> currentSqmQueryPart;
491+
private Stack<SqmQueryPart> sqmQueryPartStack = new StandardStack<>( SqmQueryPart.class );
492492
private CteContainer cteContainer;
493493
/**
494494
* A map from {@link SqmCteTable#getCteName()} to the final SQL name.
@@ -792,9 +792,10 @@ public Stack<Clause> getCurrentClauseStack() {
792792
return currentClauseStack;
793793
}
794794

795+
@SuppressWarnings("rawtypes")
795796
@Override
796-
public SqmQueryPart<?> getCurrentSqmQueryPart() {
797-
return currentSqmQueryPart;
797+
public Stack<SqmQueryPart> getSqmQueryPartStack() {
798+
return sqmQueryPartStack;
798799
}
799800

800801
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1726,8 +1727,7 @@ public CteStatement visitCteStatement(SqmCteStatement<?> sqmCteStatement) {
17261727
);
17271728
final DelegatingSqmAliasedNodeCollector collector =
17281729
(DelegatingSqmAliasedNodeCollector) processingState.getSqlExpressionResolver();
1729-
final SqmQueryPart<?> oldSqmQueryPart = currentSqmQueryPart;
1730-
currentSqmQueryPart = queryGroup;
1730+
sqmQueryPartStack.push( queryGroup );
17311731
pushProcessingState( processingState );
17321732

17331733
try {
@@ -1771,7 +1771,7 @@ public CteStatement visitCteStatement(SqmCteStatement<?> sqmCteStatement) {
17711771
}
17721772
finally {
17731773
popProcessingStateStack();
1774-
currentSqmQueryPart = oldSqmQueryPart;
1774+
sqmQueryPartStack.pop();
17751775
}
17761776
}
17771777
finally {
@@ -1985,8 +1985,7 @@ public QueryGroup visitQueryGroup(SqmQueryGroup<?> queryGroup) {
19851985
);
19861986
final DelegatingSqmAliasedNodeCollector collector = (DelegatingSqmAliasedNodeCollector) processingState
19871987
.getSqlExpressionResolver();
1988-
final SqmQueryPart<?> sqmQueryPart = currentSqmQueryPart;
1989-
currentSqmQueryPart = queryGroup;
1988+
sqmQueryPartStack.push( queryGroup );
19901989
pushProcessingState( processingState );
19911990

19921991
try {
@@ -2008,7 +2007,7 @@ public QueryGroup visitQueryGroup(SqmQueryGroup<?> queryGroup) {
20082007
}
20092008
finally {
20102009
popProcessingStateStack();
2011-
currentSqmQueryPart = sqmQueryPart;
2010+
sqmQueryPartStack.pop();
20122011
}
20132012
}
20142013

@@ -2043,9 +2042,8 @@ public QuerySpec visitQuerySpec(SqmQuerySpec<?> sqmQuerySpec) {
20432042
);
20442043
}
20452044

2046-
final SqmQueryPart<?> sqmQueryPart = currentSqmQueryPart;
20472045
final boolean originalDeduplicateSelectionItems = deduplicateSelectionItems;
2048-
currentSqmQueryPart = sqmQuerySpec;
2046+
sqmQueryPartStack.push( sqmQuerySpec );
20492047
// In sub-queries, we can never deduplicate the selection items as that might change semantics
20502048
deduplicateSelectionItems = false;
20512049
pushProcessingState( processingState );
@@ -2062,7 +2060,7 @@ public QuerySpec visitQuerySpec(SqmQuerySpec<?> sqmQuerySpec) {
20622060
inNestedContext = oldInNestedContext;
20632061
popProcessingStateStack();
20642062
queryTransformers.pop();
2065-
currentSqmQueryPart = sqmQueryPart;
2063+
sqmQueryPartStack.pop();
20662064
deduplicateSelectionItems = originalDeduplicateSelectionItems;
20672065
}
20682066
}
@@ -2203,7 +2201,7 @@ public SelectClause visitSelectClause(SqmSelectClause selectClause) {
22032201
try {
22042202
final SelectClause sqlSelectClause = currentQuerySpec().getSelectClause();
22052203
if ( selectClause == null ) {
2206-
final SqmFrom<?, ?> implicitSelection = determineImplicitSelection( (SqmQuerySpec<?>) currentSqmQueryPart );
2204+
final SqmFrom<?, ?> implicitSelection = determineImplicitSelection( (SqmQuerySpec<?>) getCurrentSqmQueryPart() );
22072205
visitSelection( 0, new SqmSelection<>( implicitSelection, implicitSelection.nodeBuilder() ) );
22082206
}
22092207
else {
@@ -2228,7 +2226,7 @@ public SelectClause visitSelectClause(SqmSelectClause selectClause) {
22282226
@Override
22292227
public Void visitSelection(SqmSelection<?> sqmSelection) {
22302228
visitSelection(
2231-
currentSqmQueryPart.getFirstQuerySpec().getSelectClause().getSelections().indexOf( sqmSelection ),
2229+
getCurrentSqmQueryPart().getFirstQuerySpec().getSelectClause().getSelections().indexOf( sqmSelection ),
22322230
sqmSelection
22332231
);
22342232
return null;
@@ -2353,7 +2351,7 @@ else if ( statement.getQuerySource() == SqmQuerySource.CRITERIA && currentClause
23532351
// To avoid this issue, we determine the position and let the SqlAstTranslator handle the rest.
23542352
// Usually it will render `select ?, count(*) from dual group by 1` if supported
23552353
// or force rendering the parameter as literal instead so that the database can see the grouping is fine
2356-
final SqmQuerySpec<?> querySpec = currentSqmQueryPart.getFirstQuerySpec();
2354+
final SqmQuerySpec<?> querySpec = getCurrentSqmQueryPart().getFirstQuerySpec();
23572355
sqmPosition = indexOfExpression( querySpec.getSelectClause().getSelections(), groupByClauseExpression );
23582356
path = null;
23592357
}
@@ -2395,7 +2393,7 @@ private SqlSelectionExpression selectionExpression(List<SqlSelection> selections
23952393
return null;
23962394
}
23972395
}
2398-
return currentSqmQueryPart instanceof SqmQueryGroup<?>
2396+
return getCurrentSqmQueryPart() instanceof SqmQueryGroup<?>
23992397
// Reusing the SqlSelection for query groups would be wrong because the aliases do no exist
24002398
// So we have to use a literal expression in a new SqlSelection instance to refer to the position
24012399
? sqlSelectionExpression( selection )

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ public Stack<Clause> getCurrentClauseStack() {
9292
return null;
9393
}
9494

95+
@Override
96+
public Stack<SqmQueryPart> getSqmQueryPartStack() {
97+
return null;
98+
}
99+
95100
@Override
96101
public SqmQueryPart<?> getCurrentSqmQueryPart() {
97102
return null;

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAstCreationState {
3434
Stack<Clause> getCurrentClauseStack();
3535

36-
SqmQueryPart<?> getCurrentSqmQueryPart();
36+
Stack<SqmQueryPart> getSqmQueryPartStack();
37+
38+
default SqmQueryPart<?> getCurrentSqmQueryPart() {
39+
return getSqmQueryPartStack().getCurrent();
40+
}
3741

3842
void registerQueryTransformer(QueryTransformer transformer);
3943

0 commit comments

Comments
 (0)