diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterIdentifiedImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterIdentifiedImpl.java new file mode 100644 index 000000000000..f82dc793609c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterIdentifiedImpl.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.internal; + +import org.hibernate.query.named.NamedQueryMemento; +import org.hibernate.query.spi.AbstractQueryParameter; +import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.type.BindableType; + + +/** + * QueryParameter impl for unnamed JPA Criteria-parameters. + */ +public class QueryParameterIdentifiedImpl extends AbstractQueryParameter { + /** + * Create an identified parameter descriptor from the SQM parameter + * + * @param parameter The source parameter info + * + * @return The parameter descriptor + */ + public static QueryParameterIdentifiedImpl fromSqm(SqmJpaCriteriaParameterWrapper parameter) { + assert parameter.getName() == null; + assert parameter.getPosition() == null; + return new QueryParameterIdentifiedImpl<>( + parameter.getUnnamedParameterId(), + parameter.allowMultiValuedBinding(), + parameter.getAnticipatedType() + ); + } + + private final int unnamedParameterId; + + private QueryParameterIdentifiedImpl(int unnamedParameterId, boolean allowMultiValuedBinding, @Nullable BindableType anticipatedType) { + super( allowMultiValuedBinding, anticipatedType ); + this.unnamedParameterId = unnamedParameterId; + } + + public int getUnnamedParameterId() { + return unnamedParameterId; + } + + @Override + public NamedQueryMemento.ParameterMemento toMemento() { + return session -> new QueryParameterIdentifiedImpl<>( unnamedParameterId, allowsMultiValuedBinding(), getHibernateType() ); + } + + @Override + public boolean equals(Object o) { + return this == o + || o instanceof QueryParameterIdentifiedImpl + && unnamedParameterId == ( (QueryParameterIdentifiedImpl) o ).unnamedParameterId; + } + + @Override + public int hashCode() { + return unnamedParameterId; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java index c581e7a96a16..1a5c51ad5396 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java @@ -716,7 +716,7 @@ else if ( parameter.getPosition() != null ) { protected

QueryParameterBinding

locateBinding(QueryParameterImplementor

parameter) { getCheckOpen(); - return getQueryParameterBindings().getBinding( parameter ); + return getQueryParameterBindings().getBinding( getQueryParameter( parameter ) ); } protected

QueryParameterBinding

locateBinding(String name) { @@ -732,7 +732,11 @@ protected

QueryParameterBinding

locateBinding(int position) { public boolean isBound(Parameter param) { getCheckOpen(); final QueryParameterImplementor parameter = getParameterMetadata().resolve( param ); - return parameter != null && getQueryParameterBindings().isBound( parameter ); + return parameter != null && getQueryParameterBindings().isBound( getQueryParameter( parameter ) ); + } + + protected

QueryParameterImplementor

getQueryParameter(QueryParameterImplementor

parameter) { + return parameter; } public T getParameterValue(Parameter param) { @@ -743,7 +747,7 @@ public T getParameterValue(Parameter param) { throw new IllegalArgumentException( "The parameter [" + param + "] is not part of this Query" ); } - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( parameter ); + final QueryParameterBinding binding = getQueryParameterBindings().getBinding( getQueryParameter( parameter ) ); if ( binding == null || !binding.isBound() ) { throw new IllegalStateException( "Parameter value not yet bound : " + param.toString() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQueryParameter.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQueryParameter.java index d9ed101570bc..b19cead4457f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQueryParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQueryParameter.java @@ -33,7 +33,7 @@ public AbstractQueryParameter(boolean allowMultiValuedBinding, BindableType a @Override public void disallowMultiValuedBinding() { QUERY_MESSAGE_LOGGER.debugf( "QueryParameter#disallowMultiValuedBinding() called: %s", this ); - this.allowMultiValuedBinding = true; + this.allowMultiValuedBinding = false; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java index b2d267a70c93..fce711e3835c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java @@ -25,12 +25,12 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.sql.results.internal.TupleMetadata; @@ -150,20 +150,39 @@ else if ( bindType != null ) { } ); // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here - for ( SqmParameter sqmParameter : getDomainParameterXref().getParameterResolutions().getSqmParameters() ) { + bindValueBindCriteriaParameters( getDomainParameterXref(), parameterBindings ); + } + + protected static void bindValueBindCriteriaParameters( + DomainParameterXref domainParameterXref, + QueryParameterBindings bindings) { + for ( var entry : domainParameterXref.getQueryParameters().entrySet() ) { + final var sqmParameter = entry.getValue().get( 0 ); if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper wrapper ) { - bindCriteriaParameter( wrapper ); + @SuppressWarnings("unchecked") + final var criteriaParameter = (JpaCriteriaParameter) wrapper.getJpaCriteriaParameter(); + if ( criteriaParameter instanceof ValueBindJpaCriteriaParameter ) { + // Use the anticipated type for binding the value if possible + //noinspection unchecked + final var parameter = (QueryParameterImplementor) entry.getKey(); + bindings.getBinding( parameter ) + .setBindValue( criteriaParameter.getValue(), criteriaParameter.getAnticipatedType() ); + } } } } - protected void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper sqmParameter) { - final JpaCriteriaParameter criteriaParameter = sqmParameter.getJpaCriteriaParameter(); - if ( criteriaParameter instanceof ValueBindJpaCriteriaParameter ) { - // Use the anticipated type for binding the value if possible - getQueryParameterBindings() - .getBinding( criteriaParameter ) - .setBindValue( criteriaParameter.getValue(), criteriaParameter.getAnticipatedType() ); + @Override + protected

QueryParameterImplementor

getQueryParameter(QueryParameterImplementor

parameter) { + if ( parameter instanceof JpaCriteriaParameter criteriaParameter ) { + final var parameterWrapper = getDomainParameterXref().getParameterResolutions() + .getJpaCriteriaParamResolutions() + .get( criteriaParameter ); + //noinspection unchecked + return (QueryParameterImplementor

) getDomainParameterXref().getQueryParameter( parameterWrapper ); + } + else { + return parameter; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java index 79d7e2929f10..f17d5663f347 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java @@ -6,15 +6,14 @@ import java.util.ArrayList; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; -import org.hibernate.internal.util.collections.LinkedIdentityHashMap; +import org.hibernate.query.internal.QueryParameterIdentifiedImpl; import org.hibernate.query.internal.QueryParameterNamedImpl; import org.hibernate.query.internal.QueryParameterPositionalImpl; import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.SqmTreeTransformationLogger; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; @@ -31,7 +30,7 @@ public class DomainParameterXref { public static final DomainParameterXref EMPTY = new DomainParameterXref( - new LinkedIdentityHashMap<>( 0 ), + new LinkedHashMap<>( 0 ), new IdentityHashMap<>( 0 ), SqmStatement.ParameterResolutions.empty() ); @@ -46,8 +45,8 @@ public static DomainParameterXref from(SqmStatement sqmStatement) { } else { final int sqmParamCount = parameterResolutions.getSqmParameters().size(); - final Map, List>> sqmParamsByQueryParam = - new LinkedIdentityHashMap<>( sqmParamCount ); + final LinkedHashMap, List>> sqmParamsByQueryParam = + new LinkedHashMap<>( sqmParamCount ); final IdentityHashMap, QueryParameterImplementor> queryParamBySqmParam = new IdentityHashMap<>( sqmParamCount ); @@ -60,48 +59,27 @@ public static DomainParameterXref from(SqmStatement sqmStatement) { ); } - // `xrefMap` is used to help maintain the proper cardinality between an - // SqmParameter and a QueryParameter. Multiple SqmParameter references - // can map to the same QueryParameter. Consider, e.g., - // `.. where a.b = :param or a.c = :param`. Here we have 2 SqmParameter - // references (one for each occurrence of `:param`) both of which map to - // the same QueryParameter. - final Map, QueryParameterImplementor> xrefMap = new TreeMap<>(); - - final QueryParameterImplementor queryParameter = xrefMap.computeIfAbsent( - sqmParameter, - parameter -> { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper sqmJpaCriteriaParameterWrapper ) { - return sqmJpaCriteriaParameterWrapper.getJpaCriteriaParameter(); - } - else if ( sqmParameter.getName() != null ) { - return QueryParameterNamedImpl.fromSqm( sqmParameter ); - } - else if ( sqmParameter.getPosition() != null ) { - return QueryParameterPositionalImpl.fromSqm( sqmParameter ); - } - else { - throw new UnsupportedOperationException( - "Unexpected SqmParameter type : " + sqmParameter ); - } - } - ); - - if ( !sqmParameter.allowMultiValuedBinding() ) { - if ( queryParameter.allowsMultiValuedBinding() ) { - SqmTreeTransformationLogger.LOGGER.debugf( - "SqmParameter [%s] does not allow multi-valued binding, " + - "but mapped to existing QueryParameter [%s] that does - " + - "disallowing multi-valued binding", - sqmParameter, - queryParameter - ); - queryParameter.disallowMultiValuedBinding(); + final QueryParameterImplementor queryParameter; + if ( sqmParameter.getName() != null ) { + queryParameter = QueryParameterNamedImpl.fromSqm( sqmParameter ); + } + else if ( sqmParameter.getPosition() != null ) { + queryParameter = QueryParameterPositionalImpl.fromSqm( sqmParameter ); + } + else if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper ) { + final SqmJpaCriteriaParameterWrapper criteriaParameter = (SqmJpaCriteriaParameterWrapper) sqmParameter; + if ( sqmParameter.allowMultiValuedBinding() + && sqmParameter.getExpressible() != null + && sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) { + // The wrapper parameter was inferred to be of a basic collection type, + // so we disallow multivalued bindings, because binding a list of collections isn't useful + criteriaParameter.getJpaCriteriaParameter().disallowMultiValuedBinding(); } + queryParameter = QueryParameterIdentifiedImpl.fromSqm( criteriaParameter ); } - else if ( sqmParameter.getExpressible() != null - && sqmParameter.getExpressible().getSqmType() instanceof BasicCollectionType ) { - queryParameter.disallowMultiValuedBinding(); + else { + throw new UnsupportedOperationException( + "Unexpected SqmParameter type : " + sqmParameter ); } sqmParamsByQueryParam.computeIfAbsent( queryParameter, impl -> new ArrayList<>() ).add( sqmParameter ); @@ -118,13 +96,13 @@ else if ( sqmParameter.getExpressible() != null private final SqmStatement.ParameterResolutions parameterResolutions; - private final Map, List>> sqmParamsByQueryParam; + private final LinkedHashMap, List>> sqmParamsByQueryParam; private final IdentityHashMap, QueryParameterImplementor> queryParamBySqmParam; private Map,List>> expansions; private DomainParameterXref( - Map, List>> sqmParamsByQueryParam, + LinkedHashMap, List>> sqmParamsByQueryParam, IdentityHashMap, QueryParameterImplementor> queryParamBySqmParam, SqmStatement.ParameterResolutions parameterResolutions) { this.sqmParamsByQueryParam = sqmParamsByQueryParam; @@ -180,10 +158,7 @@ public List> getSqmParameters(QueryParameterImplementor query } public QueryParameterImplementor getQueryParameter(SqmParameter sqmParameter) { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper parameterWrapper ) { - return parameterWrapper.getJpaCriteriaParameter(); - } - else if ( sqmParameter instanceof QueryParameterImplementor parameterImplementor ) { + if ( sqmParameter instanceof QueryParameterImplementor parameterImplementor ) { return parameterImplementor; } else { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java index 1cbe89f1fae8..4fa3cf122305 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java @@ -69,8 +69,6 @@ import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.SqmPath; -import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; @@ -226,11 +224,7 @@ public SqmQueryImpl( parameterBindings = parameterMetadata.createBindings( session.getFactory() ); // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here - for ( SqmParameter sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper wrapper ) { - bindCriteriaParameter( wrapper ); - } - } + bindValueBindCriteriaParameters( domainParameterXref, parameterBindings ); validateQuery( expectedResultType, sqm, hql ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java index 9bd0900b52a5..bc6a7740a686 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java @@ -59,8 +59,6 @@ import org.hibernate.query.sqm.spi.InterpretationsKeySource; import org.hibernate.query.sqm.spi.SqmSelectionQueryImplementor; import org.hibernate.query.sqm.tree.SqmCopyContext; -import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; @@ -194,11 +192,7 @@ public SqmSelectionQueryImpl( parameterBindings = parameterMetadata.createBindings( session.getFactory() ); // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here - for ( SqmParameter sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper wrapper ) { - bindCriteriaParameter( wrapper ); - } - } + bindValueBindCriteriaParameters( domainParameterXref, parameterBindings ); resultType = determineResultType( sqm, expectedResultType ); @@ -254,11 +248,7 @@ SqmSelectionQueryImpl(AbstractSqmSelectionQuery original, KeyedPage ke original.getQueryParameterBindings().visitBindings( this::setBindValues ); // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here - for ( SqmParameter sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper parameterWrapper ) { - bindCriteriaParameter( parameterWrapper ); - } - } + bindValueBindCriteriaParameters( domainParameterXref, parameterBindings ); //noinspection unchecked expectedResultType = (Class) KeyedResult.class; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 06e4da5ade55..dfb4bfbc1b80 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -5932,7 +5932,7 @@ public MappingModelExpressible determineValueMapping(SqmExpression sqmExpr private MappingModelExpressible determineValueMapping(SqmExpression sqmExpression, FromClauseIndex fromClauseIndex) { if ( sqmExpression instanceof SqmParameter ) { - return determineValueMapping( (SqmParameter) sqmExpression ); + return determineValueMapping( getSqmParameter( sqmExpression ) ); } if ( sqmExpression instanceof SqmPath ) { @@ -8151,10 +8151,12 @@ private InListPredicate processInSingleCriteriaParameter( SqmInListPredicate sqmPredicate, JpaCriteriaParameter jpaCriteriaParameter) { assert jpaCriteriaParameter.allowsMultiValuedBinding(); - final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter ); + + final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ); + final QueryParameterImplementor domainParam = domainParameterXref.getQueryParameter( sqmWrapper ); + final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( domainParam ); if ( domainParamBinding.isMultiValued() ) { - final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ); - return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding ); + return processInSingleParameter( sqmPredicate, sqmWrapper, domainParam, domainParamBinding ); } else { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java index 0e120199148a..516723e5ca06 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java @@ -4,7 +4,7 @@ */ package org.hibernate.query.sqm.tree; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.hibernate.query.sqm.NodeBuilder; @@ -47,7 +47,7 @@ protected Set> copyParameters(SqmCopyContext context) { return null; } else { - final Set> parameters = new HashSet<>( this.parameters.size() ); + final Set> parameters = new LinkedHashSet<>( this.parameters.size() ); for ( SqmParameter parameter : this.parameters ) { parameters.add( parameter.copy( context ) ); } @@ -63,7 +63,7 @@ public SqmQuerySource getQuerySource() { @Override public void addParameter(SqmParameter parameter) { if ( parameters == null ) { - parameters = new HashSet<>(); + parameters = new LinkedHashSet<>(); } parameters.add( parameter ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java index cf4fc3a28d18..3d18e658709c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java @@ -28,13 +28,19 @@ public class SqmJpaCriteriaParameterWrapper extends AbstractSqmExpression implements SqmParameter { private final JpaCriteriaParameter jpaCriteriaParameter; + private final int criteriaParameterId; + private final int unnamedParameterId; public SqmJpaCriteriaParameterWrapper( BindableType type, JpaCriteriaParameter jpaCriteriaParameter, + int criteriaParameterId, + int unnamedParameterId, NodeBuilder criteriaBuilder) { super( toSqmType( type, criteriaBuilder ), criteriaBuilder ); this.jpaCriteriaParameter = jpaCriteriaParameter; + this.criteriaParameterId = criteriaParameterId; + this.unnamedParameterId = unnamedParameterId; } @Override @@ -48,6 +54,8 @@ public SqmJpaCriteriaParameterWrapper copy(SqmCopyContext context) { new SqmJpaCriteriaParameterWrapper<>( getNodeType(), jpaCriteriaParameter.copy( context ), + criteriaParameterId, + unnamedParameterId, nodeBuilder() ) ); @@ -68,6 +76,27 @@ public JpaCriteriaParameter getJpaCriteriaParameter() { return jpaCriteriaParameter; } + /** + * The 0-based encounter of a {@link JpaCriteriaParameter} instance in a + * {@link org.hibernate.query.sqm.SqmQuerySource#CRITERIA} query. + * + * @see org.hibernate.query.sqm.tree.jpa.ParameterCollector + */ + public int getCriteriaParameterId() { + return criteriaParameterId; + } + + /** + * The 0-based encounter of an unnamed {@link JpaCriteriaParameter} instance in a + * {@link org.hibernate.query.sqm.SqmQuerySource#CRITERIA} query. + * If the {@link #getJpaCriteriaParameter()} has a name, returns -1. + * + * @see org.hibernate.query.sqm.tree.jpa.ParameterCollector + */ + public int getUnnamedParameterId() { + return unnamedParameterId; + } + @Override public Class getParameterType() { return jpaCriteriaParameter.getParameterType(); @@ -88,6 +117,8 @@ public SqmParameter copy() { return new SqmJpaCriteriaParameterWrapper<>( getNodeType(), jpaCriteriaParameter, + criteriaParameterId, + unnamedParameterId, nodeBuilder() ); } @@ -129,9 +160,20 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { } @Override - public int compareTo(SqmParameter parameter) { - return parameter instanceof SqmJpaCriteriaParameterWrapper wrapper - ? getJpaCriteriaParameter().compareTo( wrapper.getJpaCriteriaParameter() ) + public final boolean equals(Object o) { + return o instanceof SqmJpaCriteriaParameterWrapper + && criteriaParameterId == ( (SqmJpaCriteriaParameterWrapper) o ).criteriaParameterId; + } + + @Override + public int hashCode() { + return criteriaParameterId; + } + + @Override + public int compareTo(SqmParameter anotherParameter) { + return anotherParameter instanceof SqmJpaCriteriaParameterWrapper wrapper ? + Integer.compare( criteriaParameterId, wrapper.getCriteriaParameterId() ) : 1; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java index 9263eab1c8e6..2e601a7c4210 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java @@ -4,6 +4,8 @@ */ package org.hibernate.query.sqm.tree.expression; +import java.util.Comparator; + import org.hibernate.HibernateException; import org.hibernate.type.BindableType; import org.hibernate.query.criteria.JpaParameterExpression; @@ -21,6 +23,22 @@ * @author Steve Ebersole */ public interface SqmParameter extends SqmExpression, JpaParameterExpression, Comparable> { + Comparator> COMPARATOR = new Comparator<>() { + @Override + public int compare(SqmParameter o1, SqmParameter o2) { + if ( o1 instanceof SqmNamedParameter ) { + return o2 instanceof SqmNamedParameter + ? o1.getName().compareTo( o2.getName() ) + : -1; + } + else if ( o1 instanceof SqmPositionalParameter ) { + return o2 instanceof SqmPositionalParameter + ? o1.getPosition().compareTo( o2.getPosition() ) + : 1; + } + throw new HibernateException( "Unexpected SqmParameter type for comparison : " + this + " & " + o2 ); + } + }; /** * If this represents a named parameter, return that parameter name; * otherwise return {@code null}. diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java index 633e5f2451a2..3d8cb53ce460 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.IdentityHashMap; +import java.util.IdentityHashMap; import java.util.Set; import java.util.function.Consumer; @@ -66,6 +67,8 @@ private ParameterCollector(Consumer> consumer) { private Set> parameterExpressions; private final Consumer> consumer; + private int criteriaParameterId; + private IdentityHashMap, Integer> unnamedParameterIdMap; @Override public Object visitPositionalParameterExpression(SqmPositionalParameter expression) { @@ -88,10 +91,28 @@ public Object visitNamedParameterExpression(SqmNamedParameter expression) { */ @Override public SqmJpaCriteriaParameterWrapper visitJpaCriteriaParameter(JpaCriteriaParameter expression) { + final int unnamedParameterId; + if ( expression.getName() == null ) { + if ( unnamedParameterIdMap == null ) { + unnamedParameterIdMap = new IdentityHashMap<>(); + } + final var index = unnamedParameterIdMap.get( expression ); + if ( index == null ) { + unnamedParameterIdMap.put( expression, unnamedParameterId = unnamedParameterIdMap.size() ); + } + else { + unnamedParameterId = index; + } + } + else { + unnamedParameterId = -1; + } return visitParameter( new SqmJpaCriteriaParameterWrapper<>( getInferredParameterType( expression ), expression, + criteriaParameterId++, + unnamedParameterId, expression.nodeBuilder() ) ); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NamedQueryMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NamedQueryMethod.java index 0e86826aab89..48bda455ef94 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NamedQueryMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NamedQueryMethod.java @@ -61,8 +61,8 @@ public boolean hasStringAttribute() { @Override public String getAttributeDeclarationString() { - final TreeSet> sortedParameters = - new TreeSet<>( select.getSqmParameters() ); + final TreeSet> sortedParameters = new TreeSet<>( SqmParameter.COMPARATOR ); + sortedParameters.addAll( select.getSqmParameters() ); StringBuilder declaration = new StringBuilder(); comment( declaration ); modifiers( declaration );