Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,11 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
throw new UnsupportedOperationException("getKeyedResultList() not implemented for ProcedureCall/StoredProcedureQuery");
}

@Override
public KeyedResultList<R> getKeyedResultList() {
throw new UnsupportedOperationException("getKeyedResultList() not implemented for ProcedureCall/StoredProcedureQuery");
}

@Override
public ScrollableResultsImplementor<R> scroll(ScrollMode scrollMode) {
throw new UnsupportedOperationException( "scroll() is not implemented for ProcedureCall/StoredProcedureQuery" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public class KeyedPage<R> {
this( keyDefinition, page, null, NO_KEY );
}

KeyedPage(List<Order<? super R>> keyDefinition, Page page, List<Comparable<?>> key, KeyInterpretation interpretation) {
protected KeyedPage(List<Order<? super R>> keyDefinition, Page page, List<Comparable<?>> key, KeyInterpretation interpretation) {
this.keyDefinition = unmodifiableList(keyDefinition);
this.page = page;
this.key = key;
Expand Down
11 changes: 11 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/query/Order.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.persistence.criteria.Nulls;
import jakarta.persistence.metamodel.SingularAttribute;
import org.hibernate.Incubating;
import org.hibernate.query.criteria.JpaPath;

import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -298,6 +299,16 @@ public static Order<Object[]> by(int element, SortDirection direction, Nulls nul
return new Order<>( direction, nullPrecedence, element );
}

public static <R> Order<R> of(Class<R> resultType, jakarta.persistence.criteria.Order order) {
return new Order<>(
order.isAscending() ? ASCENDING : DESCENDING,
order.getNullPrecedence(),
resultType,
((JpaPath<?>) order.getExpression())
.getNavigablePath().getLocalName()
);
}

public SortDirection getDirection() {
return order;
}
Expand Down
1 change: 1 addition & 0 deletions hibernate-core/src/main/java/org/hibernate/query/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,5 @@ public <R> KeyedPage<R> keyedBy(List<Order<? super R>> keyDefinition) {
}
return new KeyedPage<>( keyDefinition, this );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,21 @@ default Stream<R> stream() {
@Incubating
KeyedResultList<R> getKeyedResultList(KeyedPage<R> page);

/**
* Execute the query and return the results for the first
* page, using key-based pagination.
*
* @return the query results and the key of the next page
* as an instance of {@link KeyedResultList}
*
* @since 7.0
*
* @see KeyedPage
* @see KeyedResultList
*/
@Incubating
KeyedResultList<R> getKeyedResultList();

@Override
SelectionQuery<R> setHint(String hintName, Object value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,4 +664,8 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> keyedPage) {
throw new UnsupportedOperationException("Getting keyed result list is not supported by this query.");
}

@Override
public KeyedResultList<R> getKeyedResultList() {
throw new UnsupportedOperationException("Getting keyed result list is not supported by this query.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,11 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
throw new UnsupportedOperationException("native queries do not support key-based pagination");
}

@Override
public KeyedResultList<R> getKeyedResultList() {
throw new UnsupportedOperationException("native queries do not support key-based pagination");
}

protected SelectQueryPlan<R> resolveSelectQueryPlan() {
final ResultSetMapping mapping;
if ( resultType != null && resultSetMapping.isDynamic() && resultSetMapping.getNumberOfResultBuilders() == 0 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
package org.hibernate.query.sqm.internal;

import jakarta.persistence.TemporalType;
import jakarta.persistence.TupleElement;
import jakarta.persistence.criteria.CompoundSelection;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.query.BindableType;
Expand All @@ -14,11 +17,12 @@
import org.hibernate.query.KeyedResultList;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.QueryLogging;
import org.hibernate.query.restriction.Restriction;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.hql.internal.QuerySplitter;
import org.hibernate.query.restriction.Restriction;
import org.hibernate.query.spi.AbstractSelectionQuery;
import org.hibernate.query.spi.HqlInterpretation;
import org.hibernate.query.spi.MutableQueryOptions;
Expand All @@ -44,14 +48,16 @@
import org.hibernate.sql.results.internal.TupleMetadata;
import org.hibernate.type.BasicTypeRegistry;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import jakarta.persistence.TupleElement;
import jakarta.persistence.criteria.CompoundSelection;
import java.util.Map;

import static java.util.stream.Collectors.toList;
import static org.hibernate.cfg.QuerySettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
import static org.hibernate.query.KeyedPage.KeyInterpretation.KEY_OF_FIRST_ON_NEXT_PAGE;
import static org.hibernate.query.KeyedPage.KeyInterpretation.KEY_OF_LAST_ON_PREVIOUS_PAGE;
import static org.hibernate.query.KeyedPage.KeyInterpretation.NO_KEY;
import static org.hibernate.query.sqm.internal.KeyedResult.collectKeys;
import static org.hibernate.query.sqm.internal.KeyedResult.collectResults;
import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple;
Expand Down Expand Up @@ -223,14 +229,129 @@ public SelectionQuery<R> setPage(Page page) {
return this;
}

static class KeyedCursor<R> extends KeyedPage<R> {
// private final CriteriaQuery<R> selectionQuery;
private final Map<Integer,Object> ordinalParameters;
private final Map<String, Object> namedParameters;

public KeyedCursor(List<Order<? super R>> orders,
Page page,
List<Comparable<?>> key,
KeyInterpretation interpretation,
// CriteriaQuery<R> selectionQuery,
Map<Integer, Object> ordinalParameters,
Map<String, Object> namedParameters) {
super( orders, page, key, interpretation );
// this.selectionQuery = selectionQuery;
this.ordinalParameters = ordinalParameters;
this.namedParameters = namedParameters;
}

public KeyedCursor(List<Order<? super R>> orders,
Page page,
// CriteriaQuery<R> selectionQuery,
Map<Integer, Object> ordinalParameters,
Map<String, Object> namedParameters) {
super( orders, page, null, NO_KEY );
// this.selectionQuery = selectionQuery;
this.ordinalParameters = ordinalParameters;
this.namedParameters = namedParameters;
}

// public KeyedResultList<R> getKeyedResultList(QueryProducer session) {
// return session.createQuery( selectionQuery )
// .setProperties( namedParameters )
// .getKeyedResultList( this );
// }

@Internal
public KeyedCursor<R> nextPage(List<Comparable<?>> keyOfLastResultOnThisPage) {
return new KeyedCursor<>(
getKeyDefinition(),
getPage().next(),
keyOfLastResultOnThisPage,
KEY_OF_LAST_ON_PREVIOUS_PAGE,
// selectionQuery,
ordinalParameters,
namedParameters
);
}
@Internal
public KeyedCursor<R> previousPage(List<Comparable<?>> keyOfFirstResultOnThisPage) {
if ( getPage().isFirst() ) {
return null;
}
else {
return new KeyedCursor<>( getKeyDefinition(),
getPage().previous(),
keyOfFirstResultOnThisPage,
KEY_OF_FIRST_ON_NEXT_PAGE,
// selectionQuery,
ordinalParameters,
namedParameters
);
}
}
}

@Override
public KeyedResultList<R> getKeyedResultList() {
final SqmSelectStatement<R> sqmSelectStatement = getSqmSelectStatement();
final ParameterMetadata parameterMetadata = getParameterMetadata();
final QueryOptions queryOptions = getQueryOptions();
final int parameterCount = parameterMetadata.getParameterCount();
final Map<String,Object> namedParameters =
new HashMap<>( parameterMetadata.hasNamedParameters() ? parameterCount : 0 );
for ( String name : parameterMetadata.getNamedParameterNames() ) {
namedParameters.put( name, getParameterValue( name ) );
}
final Map<Integer,Object> ordinalParameters =
new HashMap<>( parameterMetadata.hasPositionalParameters() ? parameterCount : 0 );
for ( Integer label : parameterMetadata.getOrdinalParameterLabels() ) {
ordinalParameters.put( label, getParameterValue( label ) );
}
final List<Order<? super R>> orders = new ArrayList<>();
for ( var order : sqmSelectStatement.getOrderList() ) {
orders.add( Order.of( getExpectedResultType(), order ) );
}
if ( orders.isEmpty() ) {
throw new IllegalStateException( "Query has no order" );
}
final Integer maxResults = queryOptions.getMaxRows();
final Integer firstResult = queryOptions.getFirstRow();
if ( maxResults == null ) {
throw new IllegalStateException( "Query has no maxResults" );
}
// TODO: validate firstResult ??
final Page page = Page.page( maxResults, firstResult / maxResults );
final KeyedCursor<R> cursor =
new KeyedCursor<>( orders, page,
// sqmSelectStatement,
ordinalParameters, namedParameters );
final List<KeyedResult<R>> results =
new SqmSelectionQueryImpl<KeyedResult<R>>( this, cursor )
.getResultList();
return new KeyedResultList<>(
collectResults( results, maxResults, cursor.getKeyInterpretation() ),
collectKeys( results, maxResults ),
cursor,
nextPage( cursor, results ),
previousPage( cursor, results )
);
}

@Override
public KeyedResultList<R> getKeyedResultList(KeyedPage<R> keyedPage) {
if ( keyedPage == null ) {
throw new IllegalArgumentException( "KeyedPage was null" );
}
final List<KeyedResult<R>> results =
new SqmSelectionQueryImpl<KeyedResult<R>>( this, keyedPage )
.getResultList();
final SqmSelectionQueryImpl<KeyedResult<R>> query
= new SqmSelectionQueryImpl<>( this, keyedPage );
if ( keyedPage instanceof KeyedCursor<R> cursor ) {
cursor.namedParameters.forEach( query::setParameter );
cursor.ordinalParameters.forEach( query::setParameter );
}
final List<KeyedResult<R>> results = query.getResultList();
final int pageSize = keyedPage.getPage().getSize();
return new KeyedResultList<>(
collectResults( results, pageSize, keyedPage.getKeyInterpretation() ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*
* @author Gavin King
*/
class KeyedResult<R> {
public class KeyedResult<R> {
final R result;
final List<Comparable<?>> key;

Expand All @@ -33,7 +33,7 @@ public List<Comparable<?>> getKey() {
return key;
}

static <R> List<R> collectResults(List<KeyedResult<R>> executed, int pageSize, KeyInterpretation interpretation) {
public static <R> List<R> collectResults(List<KeyedResult<R>> executed, int pageSize, KeyInterpretation interpretation) {
//note: given list probably has one more result than needed
final int size = executed.size();
final List<R> resultList = new ArrayList<>( size );
Expand All @@ -58,7 +58,7 @@ static <R> List<R> collectResults(List<KeyedResult<R>> executed, int pageSize, K
return resultList;
}

static List<List<?>> collectKeys(List<? extends KeyedResult<?>> executed, int pageSize) {
public static List<List<?>> collectKeys(List<? extends KeyedResult<?>> executed, int pageSize) {
final int size = executed.size();
final List<List<?>> resultList = new ArrayList<>( size );
for ( int i = 0; i < size && i < pageSize; i++ ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
return getDelegate().getKeyedResultList( page );
}

@Override
public KeyedResultList<R> getKeyedResultList() {
return getDelegate().getKeyedResultList();
}

@Override
public ScrollableResults<R> scroll() {
return getDelegate().scroll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,32 @@
@SessionFactory
@DomainModel(annotatedClasses = KeyBasedPagingTest.Person.class)
public class KeyBasedPagingTest {
@Test void play(SessionFactoryScope scope) {
scope.inTransaction(session -> {
for (int i = 1; i<18; i++) {
Person p = new Person();
p.dob = LocalDate.of(1970, 2, i);
p.ssn = i*7 + "-" + i*123;
p.firstName = Integer.toString(i);
p.lastName = Integer.toString(i);
session.persist(p);
}
});

scope.inTransaction(session -> {
var firstPage =
session.createSelectionQuery("from Person where dob > :dob order by ssn", Person.class )
.setParameter( "dob", LocalDate.of(1970, 2, 1) )
.setPage( Page.first( 10 ) )
.getKeyedResultList();
firstPage.getResultList().forEach(p -> System.out.println(p.ssn));
var secondPage =
session.createSelectionQuery( "from Person where dob > :dob order by ssn", Person.class )
.getKeyedResultList( firstPage.getNextPage() );
secondPage.getResultList().forEach(p -> System.out.println(p.ssn));
});
}

@Test void test(SessionFactoryScope scope) {
scope.inTransaction(session -> {
for (int i = 1; i<18; i++) {
Expand Down
Loading