HHH-17779 support for key-based pagination

fixes
This commit is contained in:
Gavin King 2024-02-26 21:15:05 +01:00
parent 8421d1c8d1
commit 842a6d02f6
2 changed files with 46 additions and 29 deletions

View File

@ -7,6 +7,7 @@
package org.hibernate.query;
import org.hibernate.Incubating;
import org.hibernate.Internal;
import java.util.List;
@ -75,7 +76,8 @@ public class KeyedPage<R> {
return key;
}
public static <R> KeyedPage<R> forKey(List<Order<? super R>> keyDefinition, Page page, List<Comparable<?>> key) {
return new KeyedPage<>( keyDefinition, page, key );
@Internal
public KeyedPage<R> nextPage(List<Comparable<?>> keyOfLastResult) {
return new KeyedPage<>( keyDefinition, page, keyOfLastResult );
}
}

View File

@ -160,37 +160,45 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
}
}
private SqmSelectStatement<KeyedResult<R>> keyed(List<Order<? super R>> keyDefinition, List<Comparable<?>> keyValues) {
private SqmSelectStatement<KeyedResult<R>> keyedQuery(
List<Order<? super R>> keyDefinition, List<Comparable<?>> keyValues) {
@SuppressWarnings("unchecked")
final SqmSelectStatement<KeyedResult<R>> sqm =
(SqmSelectStatement<KeyedResult<R>>)
getSqmSelectStatement().copy( noParamCopyContext() );
final NodeBuilder builder = sqm.nodeBuilder();
final SqmQuerySpec<?> querySpec = sqm.getQuerySpec();
final SqmWhereClause whereClause = querySpec.getWhereClause();
final List<? extends JpaSelection<?>> items = querySpec.getSelectClause().getSelectionItems();
final List<SqmPath<?>> newItems = new ArrayList<>();
if ( items.size() == 1 ) {
final JpaSelection<?> selected = items.get(0);
for ( int i = 0; i < keyDefinition.size(); i++ ) {
// ordering by an attribute of the returned entity
if ( selected instanceof SqmRoot ) {
if ( selected instanceof SqmRoot ) {
final List<SqmPath<?>> newItems = new ArrayList<>();
final SqmFrom<?,?> root = (SqmFrom<?,?>) selected;
SqmPredicate restriction = querySpec.getRestriction();
if ( restriction==null && keyValues != null ) {
restriction = builder.disjunction();
}
for ( int i = 0; i < keyDefinition.size(); i++ ) {
// ordering by an attribute of the returned entity
final Order<? super R> key = keyDefinition.get(i);
final SqmFrom<?,?> root = (SqmFrom<?,?>) selected;
if ( keyValues != null ) {
@SuppressWarnings("rawtypes")
final Comparable keyValue = keyValues.get(i);
whereClause.applyPredicate( keyPredicate( root, key, keyValue, builder ) );
restriction = builder.or( restriction,
keyPredicate( root, key, keyValue, newItems, keyValues, builder ) );
}
newItems.add( root.get( key.getAttributeName() ) );
}
else {
throw new IllegalQueryOperationException("Select item was not an entity type");
sqm.select( builder.construct((Class) KeyedResult.class,
asList(selected, builder.construct(List.class, newItems)) ) );
if ( keyValues != null ) {
sqm.where( restriction );
}
return sqm;
}
else {
throw new IllegalQueryOperationException("Select item was not an entity type");
}
sqm.select( builder.construct((Class) KeyedResult.class,
asList(selected, builder.construct(List.class, newItems)) ) );
return sqm;
}
else {
throw new IllegalQueryOperationException("Query has multiple items in the select list");
@ -198,7 +206,9 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
}
private <C extends Comparable<? super C>> SqmPredicate keyPredicate(
SqmFrom<?, ?> selected, Order<? super R> key, C keyValue, NodeBuilder builder) {
SqmFrom<?, ?> selected, Order<? super R> key, C keyValue,
List<SqmPath<?>> previousKeys, List<Comparable<?>> keyValues,
NodeBuilder builder) {
if ( !key.getEntityClass().isAssignableFrom( selected.getJavaType() ) ) {
throw new IllegalQueryOperationException("Select item was of wrong entity type");
}
@ -208,14 +218,22 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
// final Class<C> valueClass = (Class<C>) keyValue.getClass();
// final JpaParameterExpression<C> parameter = builder.parameter(valueClass);
// setParameter( parameter, keyValue );
SqmPredicate predicate;
switch ( key.getDirection() ) {
case ASCENDING:
return builder.greaterThan( path, keyValue );
predicate = builder.greaterThan( path, keyValue );
break;
case DESCENDING:
return builder.lessThan( path, keyValue );
predicate = builder.lessThan( path, keyValue );
break;
default:
throw new AssertionFailure("Unrecognized key direction");
}
for ( int i = 0; i < previousKeys.size(); i++ ) {
final SqmPath keyPath = previousKeys.get(i);
predicate = builder.and( predicate, keyPath.equalTo( keyValues.get(i) ) );
}
return predicate;
}
@Override
@ -232,18 +250,11 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
if ( key == null ) {
setFirstResult( page.getFirstResult() );
}
else {
keyed( keyDefinition, key );
}
final ConcreteSqmSelectQueryPlan<KeyedResult<R>> plan =
buildConcreteQueryPlan( keyed( keyDefinition, key ),
null, null,
getQueryOptions() );
final List<KeyedResult<R>> executed = plan.performList(this);
final KeyedPage<R> nextPage =
KeyedPage.forKey(keyDefinition, page.next(),
getKeyOfLastResult(executed, key));
final List<KeyedResult<R>> executed =
buildConcreteQueryPlan( keyedQuery( keyDefinition, key ), getQueryOptions() )
.performList(this);
final KeyedPage<R> nextPage = keyedPage.nextPage( getKeyOfLastResult( executed, key ) );
return new KeyedResultList<>( collectResultList( executed ), keyedPage, nextPage );
}
@ -301,4 +312,8 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
queryOptions
);
}
private <T> SelectQueryPlan<T> buildConcreteQueryPlan(SqmSelectStatement<T> sqmStatement, QueryOptions options) {
return buildConcreteQueryPlan( sqmStatement, null, null, options );
}
}