Skip to content

Commit 1df7628

Browse files
committed
HHH-18596 Get rid of ValueHandlingMode hack in query pagination
1 parent 2d6303d commit 1df7628

15 files changed

+161
-64
lines changed

hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,16 @@
9999
*/
100100
public abstract class AbstractCommonQueryContract implements CommonQueryContract {
101101
private final SharedSessionContractImplementor session;
102-
private final QueryOptionsImpl queryOptions = new QueryOptionsImpl();
102+
private final QueryOptionsImpl queryOptions;
103103

104104
public AbstractCommonQueryContract(SharedSessionContractImplementor session) {
105105
this.session = session;
106+
this.queryOptions = new QueryOptionsImpl();
107+
}
108+
109+
protected AbstractCommonQueryContract(AbstractCommonQueryContract original) {
110+
this.session = original.session;
111+
this.queryOptions = original.queryOptions;
106112
}
107113

108114
public SharedSessionContractImplementor getSession() {

hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.hibernate.query.IllegalQueryOperationException;
3737
import org.hibernate.query.QueryParameter;
3838
import org.hibernate.query.SelectionQuery;
39+
import org.hibernate.query.internal.QueryOptionsImpl;
3940
import org.hibernate.query.internal.ScrollableResultsIterator;
4041
import org.hibernate.query.named.NamedQueryMemento;
4142
import org.hibernate.sql.exec.internal.CallbackImpl;
@@ -81,6 +82,12 @@ public AbstractSelectionQuery(SharedSessionContractImplementor session) {
8182
super( session );
8283
}
8384

85+
protected AbstractSelectionQuery(AbstractSelectionQuery<?> original) {
86+
super( original );
87+
this.sessionFlushMode = original.sessionFlushMode;
88+
this.sessionCacheMode = original.sessionCacheMode;
89+
}
90+
8491
protected void applyOptions(NamedQueryMemento memento) {
8592
if ( memento.getHints() != null ) {
8693
memento.getHints().forEach( this::applyHint );

hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import java.util.Map;
1818
import java.util.Set;
1919

20-
import org.hibernate.Internal;
2120
import org.hibernate.engine.spi.SessionFactoryImplementor;
2221
import org.hibernate.metamodel.model.domain.JpaMetamodel;
2322
import org.hibernate.query.NullPrecedence;
@@ -33,7 +32,6 @@
3332
import org.hibernate.query.criteria.JpaSelection;
3433
import org.hibernate.query.criteria.JpaSimpleCase;
3534
import org.hibernate.query.criteria.JpaWindow;
36-
import org.hibernate.query.criteria.ValueHandlingMode;
3735
import org.hibernate.query.spi.QueryEngine;
3836
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
3937
import org.hibernate.query.sqm.tree.domain.SqmBagJoin;
@@ -87,9 +85,6 @@ public interface NodeBuilder extends HibernateCriteriaBuilder {
8785

8886
QueryEngine getQueryEngine();
8987

90-
@Internal
91-
ValueHandlingMode setCriteriaValueHandlingMode(ValueHandlingMode criteriaValueHandlingMode);
92-
9388
<R> SqmTuple<R> tuple(
9489
Class<R> tupleType,
9590
SqmExpression<?>... expressions);

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.hibernate.query.spi.QueryOptions;
3030
import org.hibernate.query.spi.SelectQueryPlan;
3131
import org.hibernate.query.sqm.NodeBuilder;
32+
import org.hibernate.query.sqm.SqmQuerySource;
3233
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
3334
import org.hibernate.query.sqm.tree.SqmStatement;
3435
import org.hibernate.query.sqm.tree.from.SqmRoot;
@@ -69,6 +70,10 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
6970
super(session);
7071
}
7172

73+
AbstractSqmSelectionQuery(AbstractSqmSelectionQuery<?> original) {
74+
super( original );
75+
}
76+
7277
protected int max(boolean hasLimit, SqmSelectStatement<?> sqmStatement, List<R> list) {
7378
return !hasLimit || getQueryOptions().getLimit().getMaxRows() == null
7479
? getMaxRows( sqmStatement, list.size() )
@@ -158,46 +163,14 @@ public SelectionQuery<R> setPage(Page page) {
158163
return this;
159164
}
160165

161-
private SqmSelectStatement<KeyedResult<R>> paginateQuery(
162-
List<Order<? super R>> keyDefinition, List<Comparable<?>> keyValues) {
163-
@SuppressWarnings("unchecked")
164-
final SqmSelectStatement<KeyedResult<R>> sqm =
165-
(SqmSelectStatement<KeyedResult<R>>)
166-
getSqmSelectStatement().copy( noParamCopyContext() );
167-
final NodeBuilder builder = sqm.nodeBuilder();
168-
//TODO: find a better way handle parameters
169-
final ValueHandlingMode valueHandlingMode = builder.setCriteriaValueHandlingMode(ValueHandlingMode.INLINE);
170-
try {
171-
return paginate( keyDefinition, keyValues, sqm, builder );
172-
}
173-
finally {
174-
builder.setCriteriaValueHandlingMode( valueHandlingMode );
175-
}
176-
}
177-
178-
179166
@Override
180167
public KeyedResultList<R> getKeyedResultList(KeyedPage<R> keyedPage) {
181168
if ( keyedPage == null ) {
182169
throw new IllegalArgumentException( "KeyedPage was null" );
183170
}
171+
final List<KeyedResult<R>> results = new SqmSelectionQueryImpl<KeyedResult<R>>( this, keyedPage )
172+
.getResultList();
184173
final Page page = keyedPage.getPage();
185-
final List<Comparable<?>> key = keyedPage.getKey();
186-
final List<Order<? super R>> keyDefinition = keyedPage.getKeyDefinition();
187-
final List<Order<? super R>> appliedKeyDefinition =
188-
keyedPage.getKeyInterpretation() == KEY_OF_FIRST_ON_NEXT_PAGE
189-
? Order.reverse(keyDefinition) : keyDefinition;
190-
191-
setMaxResults( page.getMaxResults() + 1 );
192-
if ( key == null ) {
193-
setFirstResult( page.getFirstResult() );
194-
}
195-
196-
// getQueryOptions().setQueryPlanCachingEnabled( false );
197-
final List<KeyedResult<R>> results =
198-
buildConcreteQueryPlan( paginateQuery( appliedKeyDefinition, key ), getQueryOptions() )
199-
.performList(this);
200-
201174
return new KeyedResultList<>(
202175
collectResults( results, page.getSize(), keyedPage.getKeyInterpretation() ),
203176
collectKeys( results, page.getSize() ),
@@ -281,10 +254,6 @@ protected <T> ConcreteSqmSelectQueryPlan<T> buildConcreteQueryPlan(
281254
);
282255
}
283256

284-
private <T> SelectQueryPlan<T> buildConcreteQueryPlan(SqmSelectStatement<T> sqmStatement, QueryOptions options) {
285-
return buildConcreteQueryPlan( sqmStatement, null, null, options );
286-
}
287-
288257
protected void applyOptions(NamedSqmQueryMemento memento) {
289258
applyOptions( (NamedQueryMemento) memento );
290259

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/KeyBasedPagination.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,6 @@ private static <C extends Comparable<? super C>> SqmPredicate keyPredicate(
118118
Expression<? extends C> key, C keyValue, SortDirection direction,
119119
List<SqmPath<?>> previousKeys, List<Comparable<?>> keyValues,
120120
NodeBuilder builder) {
121-
// TODO: use a parameter here and create a binding for it
122-
// @SuppressWarnings("unchecked")
123-
// final Class<C> valueClass = (Class<C>) keyValue.getClass();
124-
// final JpaParameterExpression<C> parameter = builder.parameter(valueClass);
125-
// setParameter( parameter, keyValue );
126121
SqmPredicate predicate;
127122
switch ( direction ) {
128123
case ASCENDING:

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/NoParamSqmCopyContext.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
package org.hibernate.query.sqm.internal;
88

9+
import org.hibernate.query.sqm.SqmQuerySource;
910
import org.hibernate.query.sqm.tree.expression.SqmParameter;
1011

1112
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -14,6 +15,13 @@
1415
* @author Marco Belladelli
1516
*/
1617
public class NoParamSqmCopyContext extends SimpleSqmCopyContext {
18+
public NoParamSqmCopyContext() {
19+
}
20+
21+
public NoParamSqmCopyContext(@Nullable SqmQuerySource querySource) {
22+
super( querySource );
23+
}
24+
1725
@Override
1826
public <T> @Nullable T getCopy(T original) {
1927
if ( original instanceof SqmParameter<?> ) {

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleSqmCopyContext.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import java.util.IdentityHashMap;
1010

11+
import org.hibernate.query.sqm.SqmQuerySource;
1112
import org.hibernate.query.sqm.tree.SqmCopyContext;
1213

1314
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -17,6 +18,15 @@
1718
*/
1819
public class SimpleSqmCopyContext implements SqmCopyContext {
1920
private final IdentityHashMap<Object, Object> map = new IdentityHashMap<>();
21+
private final @Nullable SqmQuerySource querySource;
22+
23+
public SimpleSqmCopyContext() {
24+
this( null );
25+
}
26+
27+
public SimpleSqmCopyContext(@Nullable SqmQuerySource querySource) {
28+
this.querySource = querySource;
29+
}
2030

2131
@Override
2232
@SuppressWarnings( "unchecked" )
@@ -32,4 +42,9 @@ public <T> T registerCopy(T original, T copy) {
3242
}
3343
return copy;
3444
}
45+
46+
@Override
47+
public @Nullable SqmQuerySource getQuerySource() {
48+
return querySource;
49+
}
3550
}

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
import org.hibernate.metamodel.model.domain.DomainType;
4747
import org.hibernate.metamodel.model.domain.JpaMetamodel;
4848
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
49-
import org.hibernate.metamodel.model.domain.internal.BasicTypeImpl;
5049
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
5150
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
5251
import org.hibernate.query.BindableType;
@@ -199,7 +198,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
199198
private final transient boolean jpaComplianceEnabled;
200199
private final transient QueryEngine queryEngine;
201200
private final transient Supplier<SessionFactoryImplementor> sessionFactory;
202-
private transient ValueHandlingMode criteriaValueHandlingMode;
201+
private final transient ValueHandlingMode criteriaValueHandlingMode;
203202
private transient BasicType<Boolean> booleanType;
204203
private transient BasicType<Integer> integerType;
205204
private transient BasicType<Long> longType;
@@ -228,13 +227,6 @@ public SqmCriteriaNodeBuilder(
228227
}
229228
}
230229

231-
@Override
232-
public ValueHandlingMode setCriteriaValueHandlingMode(ValueHandlingMode criteriaValueHandlingMode) {
233-
ValueHandlingMode current = this.criteriaValueHandlingMode;
234-
this.criteriaValueHandlingMode = criteriaValueHandlingMode;
235-
return current;
236-
}
237-
238230
@Override
239231
public JpaMetamodel getDomainModel() {
240232
return getSessionFactory().getJpaMetamodel();

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030
import org.hibernate.engine.spi.SharedSessionContractImplementor;
3131
import org.hibernate.graph.spi.AppliedGraph;
3232
import org.hibernate.internal.util.collections.IdentitySet;
33+
import org.hibernate.metamodel.mapping.MappingModelExpressible;
3334
import org.hibernate.query.BindableType;
35+
import org.hibernate.query.KeyedPage;
36+
import org.hibernate.query.Order;
37+
import org.hibernate.query.Page;
3438
import org.hibernate.query.QueryParameter;
3539
import org.hibernate.query.criteria.internal.NamedCriteriaQueryMementoImpl;
3640
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
@@ -42,9 +46,11 @@
4246
import org.hibernate.query.spi.ParameterMetadataImplementor;
4347
import org.hibernate.query.spi.QueryInterpretationCache;
4448
import org.hibernate.query.spi.QueryOptions;
49+
import org.hibernate.query.spi.QueryParameterBinding;
4550
import org.hibernate.query.spi.QueryParameterBindings;
4651
import org.hibernate.query.spi.ScrollableResultsImplementor;
4752
import org.hibernate.query.spi.SelectQueryPlan;
53+
import org.hibernate.query.sqm.SqmQuerySource;
4854
import org.hibernate.query.sqm.SqmSelectionQuery;
4955
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
5056
import org.hibernate.query.sqm.spi.SqmSelectionQueryImplementor;
@@ -69,9 +75,12 @@
6975
import static org.hibernate.jpa.LegacySpecHints.HINT_JAVAEE_CACHE_STORE_MODE;
7076
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_RETRIEVE_MODE;
7177
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_STORE_MODE;
78+
import static org.hibernate.query.KeyedPage.KeyInterpretation.KEY_OF_FIRST_ON_NEXT_PAGE;
7279
import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions;
80+
import static org.hibernate.query.sqm.internal.KeyBasedPagination.paginate;
7381
import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInterpretationsKey;
7482
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;
83+
import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
7584

7685
/**
7786
* @author Steve Ebersole
@@ -179,6 +188,87 @@ public SqmSelectionQueryImpl(
179188
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
180189
}
181190

191+
<E> SqmSelectionQueryImpl(AbstractSqmSelectionQuery<?> original, KeyedPage<E> keyedPage) {
192+
super( original );
193+
194+
final Page page = keyedPage.getPage();
195+
final List<Comparable<?>> key = keyedPage.getKey();
196+
final List<Order<? super E>> keyDefinition = keyedPage.getKeyDefinition();
197+
final List<Order<? super E>> appliedKeyDefinition =
198+
keyedPage.getKeyInterpretation() == KEY_OF_FIRST_ON_NEXT_PAGE
199+
? Order.reverse( keyDefinition ) : keyDefinition;
200+
201+
//noinspection unchecked
202+
this.sqm = (SqmSelectStatement<R>) paginate(
203+
appliedKeyDefinition,
204+
key,
205+
// Change the query source to CRITERIA, because we will change the query and introduce parameters
206+
(SqmSelectStatement<KeyedResult<E>>) original.getSqmStatement()
207+
.copy( noParamCopyContext( SqmQuerySource.CRITERIA ) ),
208+
original.getSqmStatement().nodeBuilder()
209+
);
210+
this.hql = CRITERIA_HQL_STRING;
211+
212+
this.domainParameterXref = DomainParameterXref.from( sqm );
213+
this.parameterMetadata = domainParameterXref.hasParameters()
214+
? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() )
215+
: ParameterMetadataImpl.EMPTY;
216+
217+
// Just use the original parameter bindings since this object is never going to be mutated
218+
this.parameterBindings = parameterMetadata.createBindings( original.getSession().getSessionFactory() );
219+
// Don't remove this cast. This is here to work around this bug: https://bugs.openjdk.org/browse/JDK-8340443
220+
(( DomainQueryExecutionContext) original ).getQueryParameterBindings().visitBindings(
221+
(parameter, binding) -> {
222+
//noinspection unchecked
223+
final QueryParameterBinding<Object> parameterBinding =
224+
(QueryParameterBinding<Object>) this.parameterBindings.getBinding( parameter );
225+
//noinspection unchecked
226+
final BindableType<Object> bindType = (BindableType<Object>) binding.getBindType();
227+
final TemporalType explicitTemporalPrecision = binding.getExplicitTemporalPrecision();
228+
if ( explicitTemporalPrecision != null ) {
229+
if ( binding.isMultiValued() ) {
230+
parameterBinding.setBindValues(
231+
binding.getBindValues(),
232+
explicitTemporalPrecision,
233+
getSessionFactory().getTypeConfiguration()
234+
);
235+
}
236+
else {
237+
parameterBinding.setBindValue( binding.getBindValue(), explicitTemporalPrecision );
238+
}
239+
}
240+
else {
241+
if ( binding.isMultiValued() ) {
242+
parameterBinding.setBindValues( binding.getBindValues(), bindType );
243+
}
244+
else {
245+
parameterBinding.setBindValue( binding.getBindValue(), bindType );
246+
}
247+
}
248+
//noinspection unchecked
249+
parameterBinding.setType( (MappingModelExpressible<Object>) binding.getType() );
250+
}
251+
);
252+
253+
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
254+
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
255+
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
256+
bindCriteriaParameter( (SqmJpaCriteriaParameterWrapper<?>) sqmParameter );
257+
}
258+
}
259+
260+
//noinspection unchecked
261+
this.expectedResultType = (Class<R>) KeyedResult.class;
262+
this.resultType = determineResultType( sqm, expectedResultType );
263+
this.tupleMetadata = null;
264+
265+
setMaxResults( page.getMaxResults() + 1 );
266+
if ( key == null ) {
267+
setFirstResult( page.getFirstResult() );
268+
}
269+
}
270+
271+
182272
private static Class<?> determineResultType(SqmSelectStatement<?> sqm, Class<?> expectedResultType) {
183273
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
184274
if ( selections.size() == 1 ) {

0 commit comments

Comments
 (0)