Skip to content

Commit 727e6b8

Browse files
committed
HHH-19746 Identify unnamed JPA Criteria parameters through assigned id
1 parent cfd54a4 commit 727e6b8

File tree

13 files changed

+235
-102
lines changed

13 files changed

+235
-102
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.query.internal;
8+
9+
import org.hibernate.query.BindableType;
10+
import org.hibernate.query.named.NamedQueryMemento;
11+
import org.hibernate.query.spi.AbstractQueryParameter;
12+
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
13+
14+
import org.checkerframework.checker.nullness.qual.Nullable;
15+
16+
17+
/**
18+
* QueryParameter impl for unnamed JPA Criteria-parameters.
19+
*/
20+
public class QueryParameterIdentifiedImpl<T> extends AbstractQueryParameter<T> {
21+
/**
22+
* Create an identified parameter descriptor from the SQM parameter
23+
*
24+
* @param parameter The source parameter info
25+
*
26+
* @return The parameter descriptor
27+
*/
28+
public static <T> QueryParameterIdentifiedImpl<T> fromSqm(SqmJpaCriteriaParameterWrapper<T> parameter) {
29+
assert parameter.getName() == null;
30+
assert parameter.getPosition() == null;
31+
return new QueryParameterIdentifiedImpl<>(
32+
parameter.getUnnamedParameterId(),
33+
parameter.allowMultiValuedBinding(),
34+
parameter.getAnticipatedType()
35+
);
36+
}
37+
38+
private final int unnamedParameterId;
39+
40+
private QueryParameterIdentifiedImpl(int unnamedParameterId, boolean allowMultiValuedBinding, @Nullable BindableType<T> anticipatedType) {
41+
super( allowMultiValuedBinding, anticipatedType );
42+
this.unnamedParameterId = unnamedParameterId;
43+
}
44+
45+
public int getUnnamedParameterId() {
46+
return unnamedParameterId;
47+
}
48+
49+
@Override
50+
public NamedQueryMemento.ParameterMemento toMemento() {
51+
return session -> new QueryParameterIdentifiedImpl<>( unnamedParameterId, allowsMultiValuedBinding(), getHibernateType() );
52+
}
53+
54+
@Override
55+
public boolean equals(Object o) {
56+
return this == o
57+
|| o instanceof QueryParameterIdentifiedImpl<?>
58+
&& unnamedParameterId == ( (QueryParameterIdentifiedImpl<?>) o ).unnamedParameterId;
59+
}
60+
61+
@Override
62+
public int hashCode() {
63+
return unnamedParameterId;
64+
}
65+
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ else if ( parameter.getPosition() != null ) {
730730

731731
protected <P> QueryParameterBinding<P> locateBinding(QueryParameterImplementor<P> parameter) {
732732
getSession().checkOpen();
733-
return getQueryParameterBindings().getBinding( parameter );
733+
return getQueryParameterBindings().getBinding( getQueryParameter( parameter ) );
734734
}
735735

736736
protected <P> QueryParameterBinding<P> locateBinding(String name) {
@@ -745,8 +745,12 @@ protected <P> QueryParameterBinding<P> locateBinding(int position) {
745745

746746
public boolean isBound(Parameter<?> param) {
747747
getSession().checkOpen();
748-
final QueryParameterImplementor<?> qp = getParameterMetadata().resolve( param );
749-
return qp != null && getQueryParameterBindings().isBound( qp );
748+
final QueryParameterImplementor<?> parameter = getParameterMetadata().resolve( param );
749+
return parameter != null && getQueryParameterBindings().isBound( getQueryParameter( parameter ) );
750+
}
751+
752+
protected <P> QueryParameterImplementor<P> getQueryParameter(QueryParameterImplementor<P> parameter) {
753+
return parameter;
750754
}
751755

752756
public <T> T getParameterValue(Parameter<T> param) {
@@ -759,7 +763,7 @@ public <T> T getParameterValue(Parameter<T> param) {
759763
throw new IllegalArgumentException( "The parameter [" + param + "] is not part of this Query" );
760764
}
761765

762-
final QueryParameterBinding<T> binding = getQueryParameterBindings().getBinding( qp );
766+
final QueryParameterBinding<T> binding = getQueryParameterBindings().getBinding( getQueryParameter( qp ) );
763767
if ( binding == null || !binding.isBound() ) {
764768
throw new IllegalStateException( "Parameter value not yet bound : " + param.toString() );
765769
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public AbstractQueryParameter(
2727
@Override
2828
public void disallowMultiValuedBinding() {
2929
QueryLogging.QUERY_MESSAGE_LOGGER.debugf( "QueryParameter#disallowMultiValuedBinding() called : %s", this );
30-
this.allowMultiValuedBinding = true;
30+
this.allowMultiValuedBinding = false;
3131
}
3232

3333
@Override

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.hibernate.query.QueryLogging;
1818
import org.hibernate.query.SelectionQuery;
1919
import org.hibernate.query.criteria.JpaSelection;
20-
import org.hibernate.query.criteria.ValueHandlingMode;
2120
import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl;
2221
import org.hibernate.query.hql.internal.QuerySplitter;
2322
import org.hibernate.query.named.NamedQueryMemento;
@@ -27,11 +26,13 @@
2726
import org.hibernate.query.spi.QueryEngine;
2827
import org.hibernate.query.spi.QueryInterpretationCache;
2928
import org.hibernate.query.spi.QueryOptions;
29+
import org.hibernate.query.spi.QueryParameterBindings;
30+
import org.hibernate.query.spi.QueryParameterImplementor;
3031
import org.hibernate.query.spi.SelectQueryPlan;
31-
import org.hibernate.query.sqm.NodeBuilder;
32-
import org.hibernate.query.sqm.SqmQuerySource;
3332
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
3433
import org.hibernate.query.sqm.tree.SqmStatement;
34+
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
35+
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
3536
import org.hibernate.query.sqm.tree.from.SqmRoot;
3637
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
3738
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
@@ -53,7 +54,6 @@
5354
import static java.util.stream.Collectors.toList;
5455
import static org.hibernate.cfg.QuerySettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
5556
import static org.hibernate.query.KeyedPage.KeyInterpretation.KEY_OF_FIRST_ON_NEXT_PAGE;
56-
import static org.hibernate.query.sqm.internal.KeyBasedPagination.paginate;
5757
import static org.hibernate.query.sqm.internal.KeyedResult.collectKeys;
5858
import static org.hibernate.query.sqm.internal.KeyedResult.collectResults;
5959
import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple;
@@ -129,6 +129,44 @@ private SqmSelectStatement<R> getSqmSelectStatement() {
129129
}
130130
}
131131

132+
protected static void bindValueBindCriteriaParameters(
133+
DomainParameterXref domainParameterXref,
134+
QueryParameterBindings bindings) {
135+
for ( var entry : domainParameterXref.getQueryParameters().entrySet() ) {
136+
final var sqmParameter = entry.getValue().get( 0 );
137+
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
138+
final SqmJpaCriteriaParameterWrapper<?> wrapper = (SqmJpaCriteriaParameterWrapper<?>) sqmParameter;
139+
@SuppressWarnings("unchecked")
140+
final var criteriaParameter = (JpaCriteriaParameter<Object>) wrapper.getJpaCriteriaParameter();
141+
final var value = criteriaParameter.getValue();
142+
// We don't set a null value, unless the type is also null which
143+
// is the case when using HibernateCriteriaBuilder.value
144+
if ( value != null || criteriaParameter.getNodeType() == null ) {
145+
// Use the anticipated type for binding the value if possible
146+
//noinspection unchecked
147+
final var parameter = (QueryParameterImplementor<Object>) entry.getKey();
148+
bindings.getBinding( parameter )
149+
.setBindValue( value, criteriaParameter.getAnticipatedType() );
150+
}
151+
}
152+
}
153+
}
154+
155+
@Override
156+
protected <P> QueryParameterImplementor<P> getQueryParameter(QueryParameterImplementor<P> parameter) {
157+
if ( parameter instanceof JpaCriteriaParameter<?> ) {
158+
final JpaCriteriaParameter<?> criteriaParameter = (JpaCriteriaParameter<?>) parameter;
159+
final var parameterWrapper = getDomainParameterXref().getParameterResolutions()
160+
.getJpaCriteriaParamResolutions()
161+
.get( criteriaParameter );
162+
//noinspection unchecked
163+
return (QueryParameterImplementor<P>) getDomainParameterXref().getQueryParameter( parameterWrapper );
164+
}
165+
else {
166+
return parameter;
167+
}
168+
}
169+
132170
@Override
133171
public SelectionQuery<R> setOrder(List<Order<? super R>> orderList) {
134172
SqmSelectStatement<R> sqm = getSqmSelectStatement();

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

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Map;
1515
import java.util.TreeMap;
1616

17+
import org.hibernate.query.internal.QueryParameterIdentifiedImpl;
1718
import org.hibernate.query.internal.QueryParameterNamedImpl;
1819
import org.hibernate.query.internal.QueryParameterPositionalImpl;
1920
import org.hibernate.query.spi.QueryParameterImplementor;
@@ -41,14 +42,6 @@ public class DomainParameterXref {
4142
* Create a DomainParameterXref for the parameters defined in the SQM statement
4243
*/
4344
public static DomainParameterXref from(SqmStatement<?> sqmStatement) {
44-
// `xrefMap` is used to help maintain the proper cardinality between an
45-
// SqmParameter and a QueryParameter. Multiple SqmParameter references
46-
// can map to the same QueryParameter. Consider, e.g.,
47-
// `.. where a.b = :param or a.c = :param`. Here we have 2 SqmParameter
48-
// references (one for each occurrence of `:param`) both of which map to
49-
// the same QueryParameter.
50-
final Map<SqmParameter<?>, QueryParameterImplementor<?>> xrefMap = new TreeMap<>();
51-
5245
final SqmStatement.ParameterResolutions parameterResolutions = sqmStatement.resolveParameters();
5346
if ( parameterResolutions.getSqmParameters().isEmpty() ) {
5447
return EMPTY;
@@ -67,41 +60,32 @@ public static DomainParameterXref from(SqmStatement<?> sqmStatement) {
6760
);
6861
}
6962

70-
final QueryParameterImplementor<?> queryParameter = xrefMap.computeIfAbsent(
71-
sqmParameter,
72-
p -> {
73-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper ) {
74-
return ( (SqmJpaCriteriaParameterWrapper<?>) sqmParameter ).getJpaCriteriaParameter();
75-
}
76-
else if ( sqmParameter.getName() != null ) {
77-
return QueryParameterNamedImpl.fromSqm( sqmParameter );
78-
}
79-
else if ( sqmParameter.getPosition() != null ) {
80-
return QueryParameterPositionalImpl.fromSqm( sqmParameter );
81-
}
82-
else {
83-
throw new UnsupportedOperationException( "Unexpected SqmParameter type : " + sqmParameter );
84-
}
85-
}
86-
);
87-
88-
if ( ! sqmParameter.allowMultiValuedBinding() ) {
89-
if ( queryParameter.allowsMultiValuedBinding() ) {
90-
SqmTreeTransformationLogger.LOGGER.debugf(
91-
"SqmParameter [%s] does not allow multi-valued binding, " +
92-
"but mapped to existing QueryParameter [%s] that does - " +
93-
"disallowing multi-valued binding" ,
94-
sqmParameter,
95-
queryParameter
96-
);
97-
queryParameter.disallowMultiValuedBinding();
63+
64+
65+
final QueryParameterImplementor<?> queryParameter;
66+
if ( sqmParameter.getName() != null ) {
67+
queryParameter = QueryParameterNamedImpl.fromSqm( sqmParameter );
68+
}
69+
else if ( sqmParameter.getPosition() != null ) {
70+
queryParameter = QueryParameterPositionalImpl.fromSqm( sqmParameter );
71+
}
72+
else if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
73+
final SqmJpaCriteriaParameterWrapper<?> criteriaParameter = (SqmJpaCriteriaParameterWrapper<?>) sqmParameter;
74+
if ( sqmParameter.allowMultiValuedBinding()
75+
&& sqmParameter.getExpressible() != null
76+
&& sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) {
77+
// The wrapper parameter was inferred to be of a basic collection type,
78+
// so we disallow multivalued bindings, because binding a list of collections isn't useful
79+
criteriaParameter.getJpaCriteriaParameter().disallowMultiValuedBinding();
9880
}
81+
queryParameter = QueryParameterIdentifiedImpl.fromSqm( criteriaParameter );
9982
}
100-
else if ( sqmParameter.getExpressible() != null && sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) {
101-
queryParameter.disallowMultiValuedBinding();
83+
else {
84+
throw new UnsupportedOperationException(
85+
"Unexpected SqmParameter type : " + sqmParameter );
10286
}
10387

104-
sqmParamsByQueryParam.computeIfAbsent( queryParameter, qp -> new ArrayList<>() ).add( sqmParameter );
88+
sqmParamsByQueryParam.computeIfAbsent( queryParameter, impl -> new ArrayList<>() ).add( sqmParameter );
10589
queryParamBySqmParam.put( sqmParameter, queryParameter );
10690
}
10791

@@ -177,10 +161,7 @@ public List<SqmParameter<?>> getSqmParameters(QueryParameterImplementor<?> query
177161
}
178162

179163
public QueryParameterImplementor<?> getQueryParameter(SqmParameter<?> sqmParameter) {
180-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper ) {
181-
return ( (SqmJpaCriteriaParameterWrapper<?>) sqmParameter ).getJpaCriteriaParameter();
182-
}
183-
else if ( sqmParameter instanceof QueryParameterImplementor<?> ) {
164+
if ( sqmParameter instanceof QueryParameterImplementor<?> ) {
184165
return (QueryParameterImplementor<?>) sqmParameter;
185166
}
186167
return queryParamBySqmParam.get( sqmParameter );

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

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,7 @@ public QuerySqmImpl(
226226
this.parameterBindings = parameterMetadata.createBindings( producer.getFactory() );
227227

228228
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
229-
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
230-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
231-
bindCriteriaParameter((SqmJpaCriteriaParameterWrapper<?>) sqmParameter);
232-
}
233-
}
229+
bindValueBindCriteriaParameters( domainParameterXref, parameterBindings );
234230
if ( sqm instanceof SqmSelectStatement<?> ) {
235231
final SqmSelectStatement<R> selectStatement = (SqmSelectStatement<R>) sqm;
236232
final SqmQueryPart<R> queryPart = selectStatement.getQueryPart();
@@ -250,18 +246,6 @@ public QuerySqmImpl(
250246
tupleMetadata = buildTupleMetadata( criteria, expectedResultType );
251247
}
252248

253-
private <T> void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper<T> sqmParameter) {
254-
final JpaCriteriaParameter<T> jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter();
255-
final T value = jpaCriteriaParameter.getValue();
256-
// We don't set a null value, unless the type is also null which
257-
// is the case when using HibernateCriteriaBuilder.value
258-
if ( value != null || jpaCriteriaParameter.getNodeType() == null ) {
259-
// Use the anticipated type for binding the value if possible
260-
getQueryParameterBindings().getBinding( jpaCriteriaParameter )
261-
.setBindValue( value, jpaCriteriaParameter.getAnticipatedType() );
262-
}
263-
}
264-
265249
@Override
266250
public TupleMetadata getTupleMetadata() {
267251
return tupleMetadata;

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

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,7 @@ public SqmSelectionQueryImpl(
169169
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
170170

171171
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
172-
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
173-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
174-
bindCriteriaParameter( (SqmJpaCriteriaParameterWrapper<?>) sqmParameter );
175-
}
176-
}
172+
bindValueBindCriteriaParameters( domainParameterXref, parameterBindings );
177173

178174
this.expectedResultType = expectedResultType;
179175
this.resultType = determineResultType( sqm, expectedResultType );
@@ -252,11 +248,7 @@ <E> SqmSelectionQueryImpl(AbstractSqmSelectionQuery<?> original, KeyedPage<E> ke
252248
);
253249

254250
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
255-
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
256-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
257-
bindCriteriaParameter( (SqmJpaCriteriaParameterWrapper<?>) sqmParameter );
258-
}
259-
}
251+
bindValueBindCriteriaParameters( domainParameterXref, parameterBindings );
260252

261253
//noinspection unchecked
262254
this.expectedResultType = (Class<R>) KeyedResult.class;
@@ -300,19 +292,6 @@ else if ( expectedResultType != null ) {
300292
}
301293
}
302294

303-
private <T> void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper<T> sqmParameter) {
304-
final JpaCriteriaParameter<T> jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter();
305-
final T value = jpaCriteriaParameter.getValue();
306-
// We don't set a null value, unless the type is also null which
307-
// is the case when using HibernateCriteriaBuilder.value
308-
if ( value != null || jpaCriteriaParameter.getNodeType() == null ) {
309-
// Use the anticipated type for binding the value if possible
310-
getQueryParameterBindings()
311-
.getBinding( jpaCriteriaParameter )
312-
.setBindValue( value, jpaCriteriaParameter.getAnticipatedType() );
313-
}
314-
}
315-
316295
@Override
317296
public TupleMetadata getTupleMetadata() {
318297
return tupleMetadata;

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5991,7 +5991,7 @@ public MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpr
59915991

59925992
private MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression, FromClauseIndex fromClauseIndex) {
59935993
if ( sqmExpression instanceof SqmParameter ) {
5994-
return determineValueMapping( (SqmParameter<?>) sqmExpression );
5994+
return determineValueMapping( getSqmParameter( sqmExpression ) );
59955995
}
59965996

59975997
if ( sqmExpression instanceof SqmPath ) {
@@ -8222,13 +8222,14 @@ private InListPredicate processInSingleCriteriaParameter(
82228222
JpaCriteriaParameter<?> jpaCriteriaParameter) {
82238223
assert jpaCriteriaParameter.allowsMultiValuedBinding();
82248224

8225-
final QueryParameterBinding<?> domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter );
8225+
final SqmJpaCriteriaParameterWrapper<?> sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter );
8226+
final QueryParameterImplementor<?> domainParam = domainParameterXref.getQueryParameter( sqmWrapper );
8227+
final QueryParameterBinding<?> domainParamBinding = domainParameterBindings.getBinding( domainParam );
82268228
if ( !domainParamBinding.isMultiValued() ) {
82278229
return null;
82288230
}
8229-
final SqmJpaCriteriaParameterWrapper<?> sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter );
82308231

8231-
return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding );
8232+
return processInSingleParameter( sqmPredicate, sqmWrapper, domainParam, domainParamBinding );
82328233
}
82338234

82348235
@SuppressWarnings( "rawtypes" )

0 commit comments

Comments
 (0)