Implement handling of top level limit/offset/fetch clause when collection fetches are involved
This commit is contained in:
parent
afe9b6744e
commit
6e8f344a9f
|
@ -2954,6 +2954,21 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_hql_bad_fetch_first_example() {
|
||||||
|
|
||||||
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
|
List<Phone> wrongCalls = entityManager.createQuery(
|
||||||
|
"select p " +
|
||||||
|
"from Phone p " +
|
||||||
|
"join fetch p.calls " +
|
||||||
|
"order by p " +
|
||||||
|
"fetch first 50 percent rows only",
|
||||||
|
Phone.class)
|
||||||
|
.getResultList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_hql_read_only_entities_example() {
|
public void test_hql_read_only_entities_example() {
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
|
||||||
import org.hibernate.query.Query;
|
import org.hibernate.query.Query;
|
||||||
import org.hibernate.query.QueryTypeMismatchException;
|
import org.hibernate.query.QueryTypeMismatchException;
|
||||||
import org.hibernate.query.SemanticException;
|
import org.hibernate.query.SemanticException;
|
||||||
|
import org.hibernate.query.criteria.JpaExpression;
|
||||||
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
|
||||||
import org.hibernate.query.hql.internal.QuerySplitter;
|
import org.hibernate.query.hql.internal.QuerySplitter;
|
||||||
import org.hibernate.query.hql.spi.HqlQueryImplementor;
|
import org.hibernate.query.hql.spi.HqlQueryImplementor;
|
||||||
|
@ -69,6 +70,7 @@ import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||||
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
||||||
|
@ -678,7 +680,7 @@ public class QuerySqmImpl<R>
|
||||||
|
|
||||||
getSession().prepareForQueryExecution( requiresTxn( getLockOptions().findGreatestLockMode() ) );
|
getSession().prepareForQueryExecution( requiresTxn( getLockOptions().findGreatestLockMode() ) );
|
||||||
final boolean containsCollectionFetches = selectStatement.containsCollectionFetches();
|
final boolean containsCollectionFetches = selectStatement.containsCollectionFetches();
|
||||||
final boolean hasLimit = queryOptions.hasLimit();
|
final boolean hasLimit = queryOptions.hasLimit() || selectStatement.getFetch() != null || selectStatement.getOffset() != null;
|
||||||
final boolean needsDistincting = containsCollectionFetches && (
|
final boolean needsDistincting = containsCollectionFetches && (
|
||||||
selectStatement.usesDistinct() ||
|
selectStatement.usesDistinct() ||
|
||||||
queryOptions.getGraph() != null ||
|
queryOptions.getGraph() != null ||
|
||||||
|
@ -723,10 +725,10 @@ public class QuerySqmImpl<R>
|
||||||
int includedCount = -1;
|
int includedCount = -1;
|
||||||
// NOTE : firstRow is zero-based
|
// NOTE : firstRow is zero-based
|
||||||
final int first = !hasLimit || queryOptions.getLimit().getFirstRow() == null
|
final int first = !hasLimit || queryOptions.getLimit().getFirstRow() == null
|
||||||
? 0
|
? getIntegerLiteral( selectStatement.getOffset(), 0 )
|
||||||
: queryOptions.getLimit().getFirstRow();
|
: queryOptions.getLimit().getFirstRow();
|
||||||
final int max = !hasLimit || queryOptions.getLimit().getMaxRows() == null
|
final int max = !hasLimit || queryOptions.getLimit().getMaxRows() == null
|
||||||
? -1
|
? getMaxRows( selectStatement, list.size() )
|
||||||
: queryOptions.getLimit().getMaxRows();
|
: queryOptions.getLimit().getMaxRows();
|
||||||
final List<R> tmp = new ArrayList<>( list.size() );
|
final List<R> tmp = new ArrayList<>( list.size() );
|
||||||
final IdentitySet<R> distinction = new IdentitySet<>( list.size() );
|
final IdentitySet<R> distinction = new IdentitySet<>( list.size() );
|
||||||
|
@ -749,6 +751,52 @@ public class QuerySqmImpl<R>
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getMaxRows(SqmSelectStatement<?> selectStatement, int size) {
|
||||||
|
final JpaExpression<Number> expression = selectStatement.getFetch();
|
||||||
|
if ( expression == null ) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Number fetchValue;
|
||||||
|
if ( expression instanceof SqmLiteral<?> ) {
|
||||||
|
fetchValue = ( (SqmLiteral<Number>) expression ).getLiteralValue();
|
||||||
|
}
|
||||||
|
else if ( expression instanceof SqmParameter<?> ) {
|
||||||
|
fetchValue = getParameterValue( (Parameter<Number>) expression );
|
||||||
|
if ( fetchValue == null ) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException( "Can't get max rows value from: " + expression );
|
||||||
|
}
|
||||||
|
// Note that we can never have ties because this is only used when we de-duplicate results
|
||||||
|
switch ( selectStatement.getFetchClauseType() ) {
|
||||||
|
case ROWS_ONLY:
|
||||||
|
case ROWS_WITH_TIES:
|
||||||
|
return fetchValue.intValue();
|
||||||
|
case PERCENT_ONLY:
|
||||||
|
case PERCENT_WITH_TIES:
|
||||||
|
return (int) Math.ceil( ( ( (double) size ) * fetchValue.doubleValue() ) / 100d );
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException( "Unsupported fetch clause type: " + selectStatement.getFetchClauseType() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getIntegerLiteral(JpaExpression<Number> expression, int defaultValue) {
|
||||||
|
if ( expression == null ) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( expression instanceof SqmLiteral<?> ) {
|
||||||
|
return ( (SqmLiteral<Number>) expression ).getLiteralValue().intValue();
|
||||||
|
}
|
||||||
|
else if ( expression instanceof SqmParameter<?> ) {
|
||||||
|
final Number parameterValue = getParameterValue( (Parameter<Number>) expression );
|
||||||
|
return parameterValue == null ? defaultValue : parameterValue.intValue();
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException( "Can't get integer literal value from: " + expression );
|
||||||
|
}
|
||||||
|
|
||||||
private boolean requiresTxn(LockMode lockMode) {
|
private boolean requiresTxn(LockMode lockMode) {
|
||||||
return lockMode != null && lockMode.greaterThan( LockMode.READ );
|
return lockMode != null && lockMode.greaterThan( LockMode.READ );
|
||||||
}
|
}
|
||||||
|
|
|
@ -361,6 +361,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
private boolean resolvingCircularFetch;
|
private boolean resolvingCircularFetch;
|
||||||
private ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide;
|
private ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide;
|
||||||
private SqmQueryPart<?> currentSqmQueryPart;
|
private SqmQueryPart<?> currentSqmQueryPart;
|
||||||
|
private boolean containsCollectionFetches;
|
||||||
|
|
||||||
private final Map<String, PredicateCollector> collectionFilterPredicates = new HashMap<>();
|
private final Map<String, PredicateCollector> collectionFilterPredicates = new HashMap<>();
|
||||||
private List<Map.Entry<OrderByFragment, TableGroup>> orderByFragments;
|
private List<Map.Entry<OrderByFragment, TableGroup>> orderByFragments;
|
||||||
|
@ -1692,11 +1693,18 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlQueryPart.setOffsetClauseExpression( visitOffsetExpression( sqmQueryPart.getOffsetExpression() ) );
|
if ( !containsCollectionFetches || !currentClauseStack.isEmpty() ) {
|
||||||
sqlQueryPart.setFetchClauseExpression(
|
// Strip off the root offset and limit expressions in case the query contains collection fetches to retain
|
||||||
visitFetchExpression( sqmQueryPart.getFetchExpression() ),
|
// the proper cardinality. We could implement pagination for single select statements differently in this
|
||||||
sqmQueryPart.getFetchClauseType()
|
// case by using a subquery e.g. `... where alias in (select subAlias from ... limit ...)`
|
||||||
);
|
// or use window functions e.g. `select ... from (select ..., dense_rank() over(order by ..., id) rn from ...) tmp where tmp.rn between ...`
|
||||||
|
// but these transformations/translations are non-trivial and can be done later
|
||||||
|
sqlQueryPart.setOffsetClauseExpression( visitOffsetExpression( sqmQueryPart.getOffsetExpression() ) );
|
||||||
|
sqlQueryPart.setFetchClauseExpression(
|
||||||
|
visitFetchExpression( sqmQueryPart.getFetchExpression() ),
|
||||||
|
sqmQueryPart.getFetchClauseType()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableGroup findTableGroupByPath(NavigablePath navigablePath) {
|
private TableGroup findTableGroupByPath(NavigablePath navigablePath) {
|
||||||
|
@ -2464,6 +2472,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
|
|
||||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
|
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
|
||||||
|
|
||||||
|
if ( sqmJoin.isFetched() ) {
|
||||||
|
containsCollectionFetches = true;
|
||||||
|
}
|
||||||
|
|
||||||
joinedTableGroupJoin = pluralAttributeMapping.createTableGroupJoin(
|
joinedTableGroupJoin = pluralAttributeMapping.createTableGroupJoin(
|
||||||
sqmJoinNavigablePath,
|
sqmJoinNavigablePath,
|
||||||
ownerTableGroup,
|
ownerTableGroup,
|
||||||
|
|
|
@ -179,10 +179,10 @@ public abstract class SqmQueryPart<T> implements SqmVisitableNode, JpaQueryPart<
|
||||||
sb.append( " rows with ties" );
|
sb.append( " rows with ties" );
|
||||||
break;
|
break;
|
||||||
case PERCENT_ONLY:
|
case PERCENT_ONLY:
|
||||||
sb.append( " percent only" );
|
sb.append( " percent rows only" );
|
||||||
break;
|
break;
|
||||||
case PERCENT_WITH_TIES:
|
case PERCENT_WITH_TIES:
|
||||||
sb.append( " percent with ties" );
|
sb.append( " percent rows with ties" );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue