HHH-18596 Get rid of ValueHandlingMode hack in query pagination

This commit is contained in:
Christian Beikov 2024-09-19 11:09:58 +02:00
parent 0e5846b805
commit ddadad2dac
16 changed files with 158 additions and 66 deletions

View File

@ -94,10 +94,16 @@ import static org.hibernate.jpa.internal.util.LockModeTypeHelper.interpretLockMo
*/
public abstract class AbstractCommonQueryContract implements CommonQueryContract {
private final SharedSessionContractImplementor session;
private final QueryOptionsImpl queryOptions = new QueryOptionsImpl();
private final QueryOptionsImpl queryOptions;
public AbstractCommonQueryContract(SharedSessionContractImplementor session) {
this.session = session;
this.queryOptions = new QueryOptionsImpl();
}
protected AbstractCommonQueryContract(AbstractCommonQueryContract original) {
this.session = original.session;
this.queryOptions = original.queryOptions;
}
public SharedSessionContractImplementor getSession() {

View File

@ -80,6 +80,12 @@ public abstract class AbstractSelectionQuery<R>
super( session );
}
protected AbstractSelectionQuery(AbstractSelectionQuery<?> original) {
super( original );
this.sessionFlushMode = original.sessionFlushMode;
this.sessionCacheMode = original.sessionCacheMode;
}
protected void applyOptions(NamedQueryMemento<?> memento) {
if ( memento.getHints() != null ) {
memento.getHints().forEach( this::applyHint );

View File

@ -15,7 +15,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.Internal;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.metamodel.model.domain.JpaMetamodel;
import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
@ -33,7 +32,6 @@ import org.hibernate.query.criteria.JpaSearchedCase;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.criteria.JpaSimpleCase;
import org.hibernate.query.criteria.JpaWindow;
import org.hibernate.query.criteria.ValueHandlingMode;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.domain.SqmBagJoin;
@ -91,9 +89,6 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
QueryEngine getQueryEngine();
@Internal
ValueHandlingMode setCriteriaValueHandlingMode(ValueHandlingMode criteriaValueHandlingMode);
<R> SqmTuple<R> tuple(
Class<R> tupleType,
SqmExpression<?>... expressions);

View File

@ -15,7 +15,6 @@ import org.hibernate.query.Page;
import org.hibernate.query.QueryLogging;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.criteria.ValueHandlingMode;
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
import org.hibernate.query.hql.internal.QuerySplitter;
import org.hibernate.query.named.NamedQueryMemento;
@ -25,7 +24,6 @@ import org.hibernate.query.spi.MutableQueryOptions;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.SelectQueryPlan;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.from.SqmRoot;
@ -48,7 +46,6 @@ import jakarta.persistence.criteria.CompoundSelection;
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.sqm.internal.KeyBasedPagination.paginate;
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;
@ -65,6 +62,10 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
super(session);
}
AbstractSqmSelectionQuery(AbstractSqmSelectionQuery<?> original) {
super( original );
}
protected int max(boolean hasLimit, SqmSelectStatement<?> sqmStatement, List<R> list) {
return !hasLimit || getQueryOptions().getLimit().getMaxRows() == null
? getMaxRows( sqmStatement, list.size() )
@ -154,46 +155,14 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
return this;
}
private SqmSelectStatement<KeyedResult<R>> paginateQuery(
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();
//TODO: find a better way handle parameters
final ValueHandlingMode valueHandlingMode = builder.setCriteriaValueHandlingMode(ValueHandlingMode.INLINE);
try {
return paginate( keyDefinition, keyValues, sqm, builder );
}
finally {
builder.setCriteriaValueHandlingMode( valueHandlingMode );
}
}
@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 Page page = keyedPage.getPage();
final List<Comparable<?>> key = keyedPage.getKey();
final List<Order<? super R>> keyDefinition = keyedPage.getKeyDefinition();
final List<Order<? super R>> appliedKeyDefinition =
keyedPage.getKeyInterpretation() == KEY_OF_FIRST_ON_NEXT_PAGE
? Order.reverse( keyDefinition ) : keyDefinition;
setMaxResults( page.getMaxResults() + 1 );
if ( key == null ) {
setFirstResult( page.getFirstResult() );
}
// getQueryOptions().setQueryPlanCachingEnabled( false );
final List<KeyedResult<R>> results =
buildConcreteQueryPlan( paginateQuery( appliedKeyDefinition, key ), getQueryOptions() )
.performList(this);
return new KeyedResultList<>(
collectResults( results, page.getSize(), keyedPage.getKeyInterpretation() ),
collectKeys( results, page.getSize() ),
@ -277,10 +246,6 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
);
}
private <T> SelectQueryPlan<T> buildConcreteQueryPlan(SqmSelectStatement<T> sqmStatement, QueryOptions options) {
return buildConcreteQueryPlan( sqmStatement, null, null, options );
}
protected void applyOptions(NamedSqmQueryMemento<?> memento) {
applyOptions( (NamedQueryMemento<?>) memento );

View File

@ -116,11 +116,6 @@ public class KeyBasedPagination {
Expression<? extends C> key, C keyValue, SortDirection direction,
List<SqmPath<?>> previousKeys, List<Comparable<?>> keyValues,
NodeBuilder builder) {
// TODO: use a parameter here and create a binding for it
// @SuppressWarnings("unchecked")
// final Class<C> valueClass = (Class<C>) keyValue.getClass();
// final JpaParameterExpression<C> parameter = builder.parameter(valueClass);
// setParameter( parameter, keyValue );
SqmPredicate predicate;
switch ( direction ) {
case ASCENDING:

View File

@ -4,6 +4,7 @@
*/
package org.hibernate.query.sqm.internal;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -12,6 +13,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* @author Marco Belladelli
*/
public class NoParamSqmCopyContext extends SimpleSqmCopyContext {
public NoParamSqmCopyContext() {
}
public NoParamSqmCopyContext(@Nullable SqmQuerySource querySource) {
super( querySource );
}
@Override
public <T> @Nullable T getCopy(T original) {
if ( original instanceof SqmParameter<?> ) {

View File

@ -6,6 +6,7 @@ package org.hibernate.query.sqm.internal;
import java.util.IdentityHashMap;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -15,6 +16,15 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public class SimpleSqmCopyContext implements SqmCopyContext {
private final IdentityHashMap<Object, Object> map = new IdentityHashMap<>();
private final @Nullable SqmQuerySource querySource;
public SimpleSqmCopyContext() {
this( null );
}
public SimpleSqmCopyContext(@Nullable SqmQuerySource querySource) {
this.querySource = querySource;
}
@Override
@SuppressWarnings( "unchecked" )
@ -30,4 +40,9 @@ public class SimpleSqmCopyContext implements SqmCopyContext {
}
return copy;
}
@Override
public @Nullable SqmQuerySource getQuerySource() {
return querySource;
}
}

View File

@ -242,13 +242,6 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
}
}
@Override
public ValueHandlingMode setCriteriaValueHandlingMode(ValueHandlingMode criteriaValueHandlingMode) {
ValueHandlingMode current = this.criteriaValueHandlingMode;
this.criteriaValueHandlingMode = criteriaValueHandlingMode;
return current;
}
@Override
public JpaMetamodel getDomainModel() {
return bindingContext.getJpaMetamodel();

View File

@ -29,7 +29,11 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.BindableType;
import org.hibernate.query.KeyedPage;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
@ -41,9 +45,11 @@ import org.hibernate.query.spi.MutableQueryOptions;
import org.hibernate.query.spi.ParameterMetadataImplementor;
import org.hibernate.query.spi.QueryInterpretationCache;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.query.spi.SelectQueryPlan;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.SqmSelectionQuery;
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
import org.hibernate.query.sqm.spi.SqmSelectionQueryImplementor;
@ -68,9 +74,12 @@ import static org.hibernate.jpa.LegacySpecHints.HINT_JAVAEE_CACHE_RETRIEVE_MODE;
import static org.hibernate.jpa.LegacySpecHints.HINT_JAVAEE_CACHE_STORE_MODE;
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_RETRIEVE_MODE;
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_STORE_MODE;
import static org.hibernate.query.KeyedPage.KeyInterpretation.KEY_OF_FIRST_ON_NEXT_PAGE;
import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions;
import static org.hibernate.query.sqm.internal.KeyBasedPagination.paginate;
import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInterpretationsKey;
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;
import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
/**
* @author Steve Ebersole
@ -178,6 +187,87 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
}
<E> SqmSelectionQueryImpl(AbstractSqmSelectionQuery<?> original, KeyedPage<E> keyedPage) {
super( original );
final Page page = keyedPage.getPage();
final List<Comparable<?>> key = keyedPage.getKey();
final List<Order<? super E>> keyDefinition = keyedPage.getKeyDefinition();
final List<Order<? super E>> appliedKeyDefinition =
keyedPage.getKeyInterpretation() == KEY_OF_FIRST_ON_NEXT_PAGE
? Order.reverse( keyDefinition ) : keyDefinition;
//noinspection unchecked
this.sqm = (SqmSelectStatement<R>) paginate(
appliedKeyDefinition,
key,
// Change the query source to CRITERIA, because we will change the query and introduce parameters
(SqmSelectStatement<KeyedResult<E>>) original.getSqmStatement()
.copy( noParamCopyContext( SqmQuerySource.CRITERIA ) ),
original.getSqmStatement().nodeBuilder()
);
this.hql = CRITERIA_HQL_STRING;
this.domainParameterXref = DomainParameterXref.from( sqm );
this.parameterMetadata = domainParameterXref.hasParameters()
? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() )
: ParameterMetadataImpl.EMPTY;
// Just use the original parameter bindings since this object is never going to be mutated
this.parameterBindings = parameterMetadata.createBindings( original.getSession().getSessionFactory() );
// Don't remove this cast. This is here to work around this bug: https://bugs.openjdk.org/browse/JDK-8340443
(( DomainQueryExecutionContext) original ).getQueryParameterBindings().visitBindings(
(parameter, binding) -> {
//noinspection unchecked
final QueryParameterBinding<Object> parameterBinding =
(QueryParameterBinding<Object>) this.parameterBindings.getBinding( parameter );
//noinspection unchecked
final BindableType<Object> bindType = (BindableType<Object>) binding.getBindType();
final TemporalType explicitTemporalPrecision = binding.getExplicitTemporalPrecision();
if ( explicitTemporalPrecision != null ) {
if ( binding.isMultiValued() ) {
parameterBinding.setBindValues(
binding.getBindValues(),
explicitTemporalPrecision,
getSessionFactory().getTypeConfiguration()
);
}
else {
parameterBinding.setBindValue( binding.getBindValue(), explicitTemporalPrecision );
}
}
else {
if ( binding.isMultiValued() ) {
parameterBinding.setBindValues( binding.getBindValues(), bindType );
}
else {
parameterBinding.setBindValue( binding.getBindValue(), bindType );
}
}
//noinspection unchecked
parameterBinding.setType( (MappingModelExpressible<Object>) binding.getType() );
}
);
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
bindCriteriaParameter( (SqmJpaCriteriaParameterWrapper<?>) sqmParameter );
}
}
//noinspection unchecked
this.expectedResultType = (Class<R>) KeyedResult.class;
this.resultType = determineResultType( sqm, expectedResultType );
this.tupleMetadata = null;
setMaxResults( page.getMaxResults() + 1 );
if ( key == null ) {
setFirstResult( page.getFirstResult() );
}
}
private static Class<?> determineResultType(SqmSelectStatement<?> sqm, Class<?> expectedResultType) {
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
if ( selections.size() == 1 ) {

View File

@ -5,6 +5,7 @@
package org.hibernate.query.sqm.tree;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.internal.NoParamSqmCopyContext;
import org.hibernate.query.sqm.internal.SimpleSqmCopyContext;
@ -29,11 +30,30 @@ public interface SqmCopyContext {
return true;
}
/**
* Returns the query source to use for copied queries.
* {@code null} means, that the original query source should be retained.
*
* @since 7.0
*/
@Incubating
default @Nullable SqmQuerySource getQuerySource() {
return null;
}
static SqmCopyContext simpleContext() {
return new SimpleSqmCopyContext();
}
static SqmCopyContext simpleContext(SqmQuerySource querySource) {
return new SimpleSqmCopyContext( querySource );
}
static SqmCopyContext noParamCopyContext() {
return new NoParamSqmCopyContext();
}
static SqmCopyContext noParamCopyContext(SqmQuerySource querySource) {
return new NoParamSqmCopyContext( querySource );
}
}

View File

@ -68,7 +68,7 @@ public class SqmDeleteStatement<T>
this,
new SqmDeleteStatement<>(
nodeBuilder(),
getQuerySource(),
context.getQuerySource() == null ? getQuerySource() : context.getQuerySource(),
copyParameters( context ),
copyCteStatements( context ),
getTarget().copy( context )

View File

@ -85,7 +85,7 @@ public class SqmInsertSelectStatement<T> extends AbstractSqmInsertStatement<T> i
this,
new SqmInsertSelectStatement<>(
nodeBuilder(),
getQuerySource(),
context.getQuerySource() == null ? getQuerySource() : context.getQuerySource(),
copyParameters( context ),
copyCteStatements( context ),
getTarget().copy( context ),

View File

@ -90,7 +90,7 @@ public class SqmInsertValuesStatement<T> extends AbstractSqmInsertStatement<T> i
this,
new SqmInsertValuesStatement<>(
nodeBuilder(),
getQuerySource(),
context.getQuerySource() == null ? getQuerySource() : context.getQuerySource(),
copyParameters( context ),
copyCteStatements( context ),
getTarget().copy( context ),
@ -106,7 +106,7 @@ public class SqmInsertValuesStatement<T> extends AbstractSqmInsertStatement<T> i
this,
new SqmInsertValuesStatement<>(
nodeBuilder(),
getQuerySource(),
context.getQuerySource() == null ? getQuerySource() : context.getQuerySource(),
copyParameters( context ),
copyCteStatements( context ),
getTarget().copy( context ),

View File

@ -146,7 +146,7 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
nodeBuilder(),
copyCteStatements( context ),
resultType,
getQuerySource(),
context.getQuerySource() == null ? getQuerySource() : context.getQuerySource(),
parameters
)
);

View File

@ -106,7 +106,7 @@ public class SqmUpdateStatement<T>
this,
new SqmUpdateStatement<>(
nodeBuilder(),
getQuerySource(),
context.getQuerySource() == null ? getQuerySource() : context.getQuerySource(),
copyParameters( context ),
copyCteStatements( context ),
getTarget().copy( context )

View File

@ -69,7 +69,6 @@ import static org.hamcrest.Matchers.isOneOf;
import static org.hibernate.testing.orm.domain.gambit.EntityOfBasics.Gender.FEMALE;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertThrows;