HHH-15719 Hint UniqueSematics.NONE for entity queries without collection join fetches

This commit is contained in:
Christian Beikov 2022-11-15 14:20:49 +01:00
parent de9b82f994
commit 68324b9297
3 changed files with 81 additions and 1 deletions

View File

@ -18,6 +18,10 @@ import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.graph.spi.AttributeNodeImplementor;
import org.hibernate.graph.spi.GraphImplementor;
import org.hibernate.graph.spi.SubGraphImplementor;
import org.hibernate.internal.EmptyScrollableResults; import org.hibernate.internal.EmptyScrollableResults;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
@ -86,6 +90,13 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
this.rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions ); this.rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions );
final ListResultsConsumer.UniqueSemantic uniqueSemantic;
if ( sqm.producesUniqueResults() && !containsCollectionFetches( queryOptions ) ) {
uniqueSemantic = ListResultsConsumer.UniqueSemantic.NONE;
}
else {
uniqueSemantic = ListResultsConsumer.UniqueSemantic.ALLOW;
}
this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> { this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
final SharedSessionContractImplementor session = executionContext.getSession(); final SharedSessionContractImplementor session = executionContext.getSession();
final JdbcSelect jdbcSelect = sqmInterpretation.getJdbcSelect(); final JdbcSelect jdbcSelect = sqmInterpretation.getJdbcSelect();
@ -122,7 +133,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
} }
}, },
rowTransformer, rowTransformer,
ListResultsConsumer.UniqueSemantic.ALLOW uniqueSemantic
); );
} }
finally { finally {
@ -168,6 +179,25 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
// `#performList` and `#performScroll`. // `#performList` and `#performScroll`.
} }
private static boolean containsCollectionFetches(QueryOptions queryOptions) {
final AppliedGraph appliedGraph = queryOptions.getAppliedGraph();
return appliedGraph != null && appliedGraph.getGraph() != null && containsCollectionFetches( appliedGraph.getGraph() );
}
private static boolean containsCollectionFetches(GraphImplementor<?> graph) {
for ( AttributeNodeImplementor<?> attributeNodeImplementor : graph.getAttributeNodeImplementors() ) {
if ( attributeNodeImplementor.getAttributeDescriptor().isCollection() ) {
return true;
}
for ( SubGraphImplementor<?> subGraph : attributeNodeImplementor.getSubGraphMap().values() ) {
if ( containsCollectionFetches( subGraph ) ) {
return true;
}
}
}
return false;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private RowTransformer<R> determineRowTransformer( private RowTransformer<R> determineRowTransformer(
SqmSelectStatement<?> sqm, SqmSelectStatement<?> sqm,

View File

@ -146,6 +146,41 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
this.fromClause = fromClause; this.fromClause = fromClause;
} }
public boolean producesUniqueResults() {
if ( fromClause.getRoots().size() != 1 ) {
return false;
}
final SqmRoot<?> sqmRoot = fromClause.getRoots().get( 0 );
if ( selectClause != null ) {
final List<SqmSelection<?>> selections = selectClause.getSelections();
if ( selections.size() != 1 || selections.get( 0 ).getSelectableNode() != sqmRoot ) {
// If we select anything but the query root, let's be pessimistic about unique results
return false;
}
}
final List<SqmFrom<?, ?>> fromNodes = new ArrayList<>( sqmRoot.getSqmJoins().size() + 1 );
fromNodes.add( sqmRoot );
while ( !fromNodes.isEmpty() ) {
final SqmFrom<?, ?> fromNode = fromNodes.remove( fromNodes.size() - 1 );
for ( SqmJoin<?, ?> sqmJoin : fromNode.getSqmJoins() ) {
if ( sqmJoin instanceof SqmAttributeJoin<?, ?> ) {
final SqmAttributeJoin<?, ?> join = (SqmAttributeJoin<?, ?>) sqmJoin;
if ( join.getAttribute().isCollection() ) {
// Collections joins always alter cardinality
return false;
}
}
else {
// For now, consider all non-attribute joins as cardinality altering
return false;
}
fromNodes.add( sqmJoin );
}
fromNodes.addAll( fromNode.getSqmTreats() );
}
return true;
}
public boolean containsCollectionFetches() { public boolean containsCollectionFetches() {
final List<SqmFrom<?, ?>> fromNodes = new ArrayList<>( fromClause.getRoots() ); final List<SqmFrom<?, ?>> fromNodes = new ArrayList<>( fromClause.getRoots() );
while ( !fromNodes.isEmpty() ) { while ( !fromNodes.isEmpty() ) {
@ -159,6 +194,7 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
} }
fromNodes.add( sqmJoin ); fromNodes.add( sqmJoin );
} }
fromNodes.addAll( fromNode.getSqmTreats() );
} }
return false; return false;
} }

View File

@ -153,6 +153,20 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
} }
} }
public boolean producesUniqueResults() {
return producesUniqueResults( getQueryPart() );
}
private boolean producesUniqueResults(SqmQueryPart<?> queryPart) {
if ( queryPart instanceof SqmQuerySpec<?> ) {
return ( (SqmQuerySpec<?>) queryPart ).producesUniqueResults();
}
else {
// For query groups we have to assume that duplicates are possible
return true;
}
}
public boolean containsCollectionFetches() { public boolean containsCollectionFetches() {
return containsCollectionFetches( getQueryPart() ); return containsCollectionFetches( getQueryPart() );
} }