Skip to content

Commit 7d42831

Browse files
committed
HHH-19746 Identify unnamed JPA Criteria parameters through assigned id
1 parent 8361b7f commit 7d42831

File tree

13 files changed

+225
-97
lines changed

13 files changed

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

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ else if ( parameter.getPosition() != null ) {
716716

717717
protected <P> QueryParameterBinding<P> locateBinding(QueryParameterImplementor<P> parameter) {
718718
getCheckOpen();
719-
return getQueryParameterBindings().getBinding( parameter );
719+
return getQueryParameterBindings().getBinding( getQueryParameter( parameter ) );
720720
}
721721

722722
protected <P> QueryParameterBinding<P> locateBinding(String name) {
@@ -732,7 +732,11 @@ protected <P> QueryParameterBinding<P> locateBinding(int position) {
732732
public boolean isBound(Parameter<?> param) {
733733
getCheckOpen();
734734
final QueryParameterImplementor<?> parameter = getParameterMetadata().resolve( param );
735-
return parameter != null && getQueryParameterBindings().isBound( parameter );
735+
return parameter != null && getQueryParameterBindings().isBound( getQueryParameter( parameter ) );
736+
}
737+
738+
protected <P> QueryParameterImplementor<P> getQueryParameter(QueryParameterImplementor<P> parameter) {
739+
return parameter;
736740
}
737741

738742
public <T> T getParameterValue(Parameter<T> param) {
@@ -743,7 +747,7 @@ public <T> T getParameterValue(Parameter<T> param) {
743747
throw new IllegalArgumentException( "The parameter [" + param + "] is not part of this Query" );
744748
}
745749

746-
final QueryParameterBinding<T> binding = getQueryParameterBindings().getBinding( parameter );
750+
final QueryParameterBinding<T> binding = getQueryParameterBindings().getBinding( getQueryParameter( parameter ) );
747751
if ( binding == null || !binding.isBound() ) {
748752
throw new IllegalStateException( "Parameter value not yet bound : " + param.toString() );
749753
}

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
@@ -33,7 +33,7 @@ public AbstractQueryParameter(boolean allowMultiValuedBinding, BindableType<T> a
3333
@Override
3434
public void disallowMultiValuedBinding() {
3535
QUERY_MESSAGE_LOGGER.debugf( "QueryParameter#disallowMultiValuedBinding() called: %s", this );
36-
this.allowMultiValuedBinding = true;
36+
this.allowMultiValuedBinding = false;
3737
}
3838

3939
@Override

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

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
import org.hibernate.query.spi.QueryOptions;
2626
import org.hibernate.query.spi.QueryParameterBinding;
2727
import org.hibernate.query.spi.QueryParameterBindings;
28+
import org.hibernate.query.spi.QueryParameterImplementor;
2829
import org.hibernate.query.spi.SelectQueryPlan;
2930
import org.hibernate.query.sqm.spi.NamedSqmQueryMemento;
3031
import org.hibernate.query.sqm.tree.SqmStatement;
3132
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
3233
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
33-
import org.hibernate.query.sqm.tree.expression.SqmParameter;
3434
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
3535
import org.hibernate.query.sqm.tree.select.SqmSelection;
3636
import org.hibernate.sql.results.internal.TupleMetadata;
@@ -150,20 +150,39 @@ else if ( bindType != null ) {
150150
} );
151151

152152
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
153-
for ( SqmParameter<?> sqmParameter : getDomainParameterXref().getParameterResolutions().getSqmParameters() ) {
153+
bindValueBindCriteriaParameters( getDomainParameterXref(), parameterBindings );
154+
}
155+
156+
protected static void bindValueBindCriteriaParameters(
157+
DomainParameterXref domainParameterXref,
158+
QueryParameterBindings bindings) {
159+
for ( var entry : domainParameterXref.getQueryParameters().entrySet() ) {
160+
final var sqmParameter = entry.getValue().get( 0 );
154161
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> wrapper ) {
155-
bindCriteriaParameter( wrapper );
162+
@SuppressWarnings("unchecked")
163+
final var criteriaParameter = (JpaCriteriaParameter<Object>) wrapper.getJpaCriteriaParameter();
164+
if ( criteriaParameter instanceof ValueBindJpaCriteriaParameter<?> ) {
165+
// Use the anticipated type for binding the value if possible
166+
//noinspection unchecked
167+
final var parameter = (QueryParameterImplementor<Object>) entry.getKey();
168+
bindings.getBinding( parameter )
169+
.setBindValue( criteriaParameter.getValue(), criteriaParameter.getAnticipatedType() );
170+
}
156171
}
157172
}
158173
}
159174

160-
protected <T> void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper<T> sqmParameter) {
161-
final JpaCriteriaParameter<T> criteriaParameter = sqmParameter.getJpaCriteriaParameter();
162-
if ( criteriaParameter instanceof ValueBindJpaCriteriaParameter<?> ) {
163-
// Use the anticipated type for binding the value if possible
164-
getQueryParameterBindings()
165-
.getBinding( criteriaParameter )
166-
.setBindValue( criteriaParameter.getValue(), criteriaParameter.getAnticipatedType() );
175+
@Override
176+
protected <P> QueryParameterImplementor<P> getQueryParameter(QueryParameterImplementor<P> parameter) {
177+
if ( parameter instanceof JpaCriteriaParameter<?> criteriaParameter ) {
178+
final var parameterWrapper = getDomainParameterXref().getParameterResolutions()
179+
.getJpaCriteriaParamResolutions()
180+
.get( criteriaParameter );
181+
//noinspection unchecked
182+
return (QueryParameterImplementor<P>) getDomainParameterXref().getQueryParameter( parameterWrapper );
183+
}
184+
else {
185+
return parameter;
167186
}
168187
}
169188

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

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66

77
import java.util.ArrayList;
88
import java.util.IdentityHashMap;
9+
import java.util.LinkedHashMap;
910
import java.util.List;
1011
import java.util.Map;
11-
import java.util.TreeMap;
1212

13-
import org.hibernate.internal.util.collections.LinkedIdentityHashMap;
13+
import org.hibernate.query.internal.QueryParameterIdentifiedImpl;
1414
import org.hibernate.query.internal.QueryParameterNamedImpl;
1515
import org.hibernate.query.internal.QueryParameterPositionalImpl;
1616
import org.hibernate.query.spi.QueryParameterImplementor;
17-
import org.hibernate.query.sqm.SqmTreeTransformationLogger;
1817
import org.hibernate.query.sqm.tree.SqmStatement;
1918
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
2019
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
@@ -31,7 +30,7 @@
3130
public class DomainParameterXref {
3231

3332
public static final DomainParameterXref EMPTY = new DomainParameterXref(
34-
new LinkedIdentityHashMap<>( 0 ),
33+
new LinkedHashMap<>( 0 ),
3534
new IdentityHashMap<>( 0 ),
3635
SqmStatement.ParameterResolutions.empty()
3736
);
@@ -46,8 +45,8 @@ public static DomainParameterXref from(SqmStatement<?> sqmStatement) {
4645
}
4746
else {
4847
final int sqmParamCount = parameterResolutions.getSqmParameters().size();
49-
final Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam =
50-
new LinkedIdentityHashMap<>( sqmParamCount );
48+
final LinkedHashMap<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam =
49+
new LinkedHashMap<>( sqmParamCount );
5150
final IdentityHashMap<SqmParameter<?>, QueryParameterImplementor<?>> queryParamBySqmParam =
5251
new IdentityHashMap<>( sqmParamCount );
5352

@@ -60,48 +59,27 @@ public static DomainParameterXref from(SqmStatement<?> sqmStatement) {
6059
);
6160
}
6261

63-
// `xrefMap` is used to help maintain the proper cardinality between an
64-
// SqmParameter and a QueryParameter. Multiple SqmParameter references
65-
// can map to the same QueryParameter. Consider, e.g.,
66-
// `.. where a.b = :param or a.c = :param`. Here we have 2 SqmParameter
67-
// references (one for each occurrence of `:param`) both of which map to
68-
// the same QueryParameter.
69-
final Map<SqmParameter<?>, QueryParameterImplementor<?>> xrefMap = new TreeMap<>();
70-
71-
final QueryParameterImplementor<?> queryParameter = xrefMap.computeIfAbsent(
72-
sqmParameter,
73-
parameter -> {
74-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> sqmJpaCriteriaParameterWrapper ) {
75-
return sqmJpaCriteriaParameterWrapper.getJpaCriteriaParameter();
76-
}
77-
else if ( sqmParameter.getName() != null ) {
78-
return QueryParameterNamedImpl.fromSqm( sqmParameter );
79-
}
80-
else if ( sqmParameter.getPosition() != null ) {
81-
return QueryParameterPositionalImpl.fromSqm( sqmParameter );
82-
}
83-
else {
84-
throw new UnsupportedOperationException(
85-
"Unexpected SqmParameter type : " + sqmParameter );
86-
}
87-
}
88-
);
89-
90-
if ( !sqmParameter.allowMultiValuedBinding() ) {
91-
if ( queryParameter.allowsMultiValuedBinding() ) {
92-
SqmTreeTransformationLogger.LOGGER.debugf(
93-
"SqmParameter [%s] does not allow multi-valued binding, " +
94-
"but mapped to existing QueryParameter [%s] that does - " +
95-
"disallowing multi-valued binding",
96-
sqmParameter,
97-
queryParameter
98-
);
99-
queryParameter.disallowMultiValuedBinding();
62+
final QueryParameterImplementor<?> queryParameter;
63+
if ( sqmParameter.getName() != null ) {
64+
queryParameter = QueryParameterNamedImpl.fromSqm( sqmParameter );
65+
}
66+
else if ( sqmParameter.getPosition() != null ) {
67+
queryParameter = QueryParameterPositionalImpl.fromSqm( sqmParameter );
68+
}
69+
else if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
70+
final SqmJpaCriteriaParameterWrapper<?> criteriaParameter = (SqmJpaCriteriaParameterWrapper<?>) sqmParameter;
71+
if ( sqmParameter.allowMultiValuedBinding()
72+
&& sqmParameter.getExpressible() != null
73+
&& sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) {
74+
// The wrapper parameter was inferred to be of a basic collection type,
75+
// so we disallow multivalued bindings, because binding a list of collections isn't useful
76+
criteriaParameter.getJpaCriteriaParameter().disallowMultiValuedBinding();
10077
}
78+
queryParameter = QueryParameterIdentifiedImpl.fromSqm( criteriaParameter );
10179
}
102-
else if ( sqmParameter.getExpressible() != null
103-
&& sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) {
104-
queryParameter.disallowMultiValuedBinding();
80+
else {
81+
throw new UnsupportedOperationException(
82+
"Unexpected SqmParameter type : " + sqmParameter );
10583
}
10684

10785
sqmParamsByQueryParam.computeIfAbsent( queryParameter, impl -> new ArrayList<>() ).add( sqmParameter );
@@ -118,13 +96,13 @@ else if ( sqmParameter.getExpressible() != null
11896

11997
private final SqmStatement.ParameterResolutions parameterResolutions;
12098

121-
private final Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam;
99+
private final LinkedHashMap<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam;
122100
private final IdentityHashMap<SqmParameter<?>, QueryParameterImplementor<?>> queryParamBySqmParam;
123101

124102
private Map<SqmParameter<?>,List<SqmParameter<?>>> expansions;
125103

126104
private DomainParameterXref(
127-
Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam,
105+
LinkedHashMap<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam,
128106
IdentityHashMap<SqmParameter<?>, QueryParameterImplementor<?>> queryParamBySqmParam,
129107
SqmStatement.ParameterResolutions parameterResolutions) {
130108
this.sqmParamsByQueryParam = sqmParamsByQueryParam;
@@ -180,10 +158,7 @@ public List<SqmParameter<?>> getSqmParameters(QueryParameterImplementor<?> query
180158
}
181159

182160
public QueryParameterImplementor<?> getQueryParameter(SqmParameter<?> sqmParameter) {
183-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> parameterWrapper ) {
184-
return parameterWrapper.getJpaCriteriaParameter();
185-
}
186-
else if ( sqmParameter instanceof QueryParameterImplementor<?> parameterImplementor ) {
161+
if ( sqmParameter instanceof QueryParameterImplementor<?> parameterImplementor ) {
187162
return parameterImplementor;
188163
}
189164
else {

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@
6969
import org.hibernate.query.sqm.tree.SqmStatement;
7070
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
7171
import org.hibernate.query.sqm.tree.domain.SqmPath;
72-
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
73-
import org.hibernate.query.sqm.tree.expression.SqmParameter;
7472
import org.hibernate.query.sqm.tree.from.SqmRoot;
7573
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
7674
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
@@ -226,11 +224,7 @@ public SqmQueryImpl(
226224
parameterBindings = parameterMetadata.createBindings( session.getFactory() );
227225

228226
// 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<?> wrapper ) {
231-
bindCriteriaParameter( wrapper );
232-
}
233-
}
227+
bindValueBindCriteriaParameters( domainParameterXref, parameterBindings );
234228

235229
validateQuery( expectedResultType, sqm, hql );
236230

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@
5959
import org.hibernate.query.sqm.spi.InterpretationsKeySource;
6060
import org.hibernate.query.sqm.spi.SqmSelectionQueryImplementor;
6161
import org.hibernate.query.sqm.tree.SqmCopyContext;
62-
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
63-
import org.hibernate.query.sqm.tree.expression.SqmParameter;
6462
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
6563
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
6664
import org.hibernate.query.sqm.tree.select.SqmSelection;
@@ -194,11 +192,7 @@ public SqmSelectionQueryImpl(
194192
parameterBindings = parameterMetadata.createBindings( session.getFactory() );
195193

196194
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
197-
for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
198-
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> wrapper ) {
199-
bindCriteriaParameter( wrapper );
200-
}
201-
}
195+
bindValueBindCriteriaParameters( domainParameterXref, parameterBindings );
202196

203197
resultType = determineResultType( sqm, expectedResultType );
204198

@@ -254,11 +248,7 @@ <E> SqmSelectionQueryImpl(AbstractSqmSelectionQuery<?> original, KeyedPage<E> ke
254248
original.getQueryParameterBindings().visitBindings( this::setBindValues );
255249

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

263253
//noinspection unchecked
264254
expectedResultType = (Class<R>) KeyedResult.class;

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

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

59335933
private MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression, FromClauseIndex fromClauseIndex) {
59345934
if ( sqmExpression instanceof SqmParameter ) {
5935-
return determineValueMapping( (SqmParameter<?>) sqmExpression );
5935+
return determineValueMapping( getSqmParameter( sqmExpression ) );
59365936
}
59375937

59385938
if ( sqmExpression instanceof SqmPath ) {
@@ -8151,10 +8151,12 @@ private InListPredicate processInSingleCriteriaParameter(
81518151
SqmInListPredicate<?> sqmPredicate,
81528152
JpaCriteriaParameter<?> jpaCriteriaParameter) {
81538153
assert jpaCriteriaParameter.allowsMultiValuedBinding();
8154-
final QueryParameterBinding<?> domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter );
8154+
8155+
final SqmJpaCriteriaParameterWrapper<?> sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter );
8156+
final QueryParameterImplementor<?> domainParam = domainParameterXref.getQueryParameter( sqmWrapper );
8157+
final QueryParameterBinding<?> domainParamBinding = domainParameterBindings.getBinding( domainParam );
81558158
if ( domainParamBinding.isMultiValued() ) {
8156-
final SqmJpaCriteriaParameterWrapper<?> sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter );
8157-
return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding );
8159+
return processInSingleParameter( sqmPredicate, sqmWrapper, domainParam, domainParamBinding );
81588160
}
81598161
else {
81608162
return null;

0 commit comments

Comments
 (0)