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..bbee649d102b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterIdentifiedImpl.java @@ -0,0 +1,65 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.internal; + +import org.hibernate.query.BindableType; +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; + + +/** + * 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 fe40ab83a4b5..b89ae0641071 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 @@ -730,7 +730,7 @@ else if ( parameter.getPosition() != null ) { protected

QueryParameterBinding

locateBinding(QueryParameterImplementor

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

QueryParameterBinding

locateBinding(String name) { @@ -745,8 +745,12 @@ protected

QueryParameterBinding

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

QueryParameterImplementor

getQueryParameter(QueryParameterImplementor

parameter) { + return parameter; } public T getParameterValue(Parameter param) { @@ -759,7 +763,7 @@ public T getParameterValue(Parameter param) { throw new IllegalArgumentException( "The parameter [" + param + "] is not part of this Query" ); } - final QueryParameterBinding binding = getQueryParameterBindings().getBinding( qp ); + final QueryParameterBinding binding = getQueryParameterBindings().getBinding( getQueryParameter( qp ) ); 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 5719bc5a2f26..4d7048176abc 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 @@ -27,7 +27,7 @@ public AbstractQueryParameter( @Override public void disallowMultiValuedBinding() { QueryLogging.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 420b0e9d7413..3778821f3d43 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 @@ -17,7 +17,6 @@ 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; @@ -27,11 +26,13 @@ import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.SelectQueryPlan; -import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.SqmQuerySource; 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.from.SqmRoot; import org.hibernate.query.sqm.tree.select.SqmQueryGroup; import org.hibernate.query.sqm.tree.select.SqmQueryPart; @@ -53,7 +54,6 @@ 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; @@ -129,6 +129,44 @@ private SqmSelectStatement getSqmSelectStatement() { } } + 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 ) { + final SqmJpaCriteriaParameterWrapper wrapper = (SqmJpaCriteriaParameterWrapper) sqmParameter; + @SuppressWarnings("unchecked") + final var criteriaParameter = (JpaCriteriaParameter) wrapper.getJpaCriteriaParameter(); + final var value = criteriaParameter.getValue(); + // We don't set a null value, unless the type is also null which + // is the case when using HibernateCriteriaBuilder.value + if ( value != null || criteriaParameter.getNodeType() == null ) { + // Use the anticipated type for binding the value if possible + //noinspection unchecked + final var parameter = (QueryParameterImplementor) entry.getKey(); + bindings.getBinding( parameter ) + .setBindValue( value, criteriaParameter.getAnticipatedType() ); + } + } + } + } + + @Override + protected

QueryParameterImplementor

getQueryParameter(QueryParameterImplementor

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

) getDomainParameterXref().getQueryParameter( parameterWrapper ); + } + else { + return parameter; + } + } + @Override public SelectionQuery setOrder(List> orderList) { SqmSelectStatement sqm = getSqmSelectStatement(); 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 8368de1c20c0..6d6c15b24a1b 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 @@ -14,6 +14,7 @@ import java.util.Map; import java.util.TreeMap; +import org.hibernate.query.internal.QueryParameterIdentifiedImpl; import org.hibernate.query.internal.QueryParameterNamedImpl; import org.hibernate.query.internal.QueryParameterPositionalImpl; import org.hibernate.query.spi.QueryParameterImplementor; @@ -41,14 +42,6 @@ public class DomainParameterXref { * Create a DomainParameterXref for the parameters defined in the SQM statement */ 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 SqmStatement.ParameterResolutions parameterResolutions = sqmStatement.resolveParameters(); if ( parameterResolutions.getSqmParameters().isEmpty() ) { return EMPTY; @@ -67,41 +60,32 @@ public static DomainParameterXref from(SqmStatement sqmStatement) { ); } - final QueryParameterImplementor queryParameter = xrefMap.computeIfAbsent( - sqmParameter, - p -> { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper ) { - return ( (SqmJpaCriteriaParameterWrapper) sqmParameter ).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, qp -> new ArrayList<>() ).add( sqmParameter ); + sqmParamsByQueryParam.computeIfAbsent( queryParameter, impl -> new ArrayList<>() ).add( sqmParameter ); queryParamBySqmParam.put( sqmParameter, queryParameter ); } @@ -177,10 +161,7 @@ public List> getSqmParameters(QueryParameterImplementor query } public QueryParameterImplementor getQueryParameter(SqmParameter sqmParameter) { - if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper ) { - return ( (SqmJpaCriteriaParameterWrapper) sqmParameter ).getJpaCriteriaParameter(); - } - else if ( sqmParameter instanceof QueryParameterImplementor ) { + if ( sqmParameter instanceof QueryParameterImplementor ) { return (QueryParameterImplementor) sqmParameter; } return queryParamBySqmParam.get( sqmParameter ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 91fa946753ee..87e43b838e12 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -226,11 +226,7 @@ public QuerySqmImpl( this.parameterBindings = parameterMetadata.createBindings( producer.getFactory() ); // 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); - } - } + bindValueBindCriteriaParameters( domainParameterXref, parameterBindings ); if ( sqm instanceof SqmSelectStatement ) { final SqmSelectStatement selectStatement = (SqmSelectStatement) sqm; final SqmQueryPart queryPart = selectStatement.getQueryPart(); @@ -250,18 +246,6 @@ public QuerySqmImpl( tupleMetadata = buildTupleMetadata( criteria, expectedResultType ); } - private void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper sqmParameter) { - final JpaCriteriaParameter jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter(); - final T value = jpaCriteriaParameter.getValue(); - // We don't set a null value, unless the type is also null which - // is the case when using HibernateCriteriaBuilder.value - if ( value != null || jpaCriteriaParameter.getNodeType() == null ) { - // Use the anticipated type for binding the value if possible - getQueryParameterBindings().getBinding( jpaCriteriaParameter ) - .setBindValue( value, jpaCriteriaParameter.getAnticipatedType() ); - } - } - @Override public TupleMetadata getTupleMetadata() { return tupleMetadata; 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 5996996a3e9d..88b7c7412895 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 @@ -169,11 +169,7 @@ public SqmSelectionQueryImpl( this.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 ) { - bindCriteriaParameter( (SqmJpaCriteriaParameterWrapper) sqmParameter ); - } - } + bindValueBindCriteriaParameters( domainParameterXref, parameterBindings ); this.expectedResultType = expectedResultType; this.resultType = determineResultType( sqm, expectedResultType ); @@ -252,11 +248,7 @@ SqmSelectionQueryImpl(AbstractSqmSelectionQuery original, KeyedPage ke ); // 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 ); - } - } + bindValueBindCriteriaParameters( domainParameterXref, parameterBindings ); //noinspection unchecked this.expectedResultType = (Class) KeyedResult.class; @@ -300,19 +292,6 @@ else if ( expectedResultType != null ) { } } - private void bindCriteriaParameter(SqmJpaCriteriaParameterWrapper sqmParameter) { - final JpaCriteriaParameter jpaCriteriaParameter = sqmParameter.getJpaCriteriaParameter(); - final T value = jpaCriteriaParameter.getValue(); - // We don't set a null value, unless the type is also null which - // is the case when using HibernateCriteriaBuilder.value - if ( value != null || jpaCriteriaParameter.getNodeType() == null ) { - // Use the anticipated type for binding the value if possible - getQueryParameterBindings() - .getBinding( jpaCriteriaParameter ) - .setBindValue( value, jpaCriteriaParameter.getAnticipatedType() ); - } - } - @Override public TupleMetadata getTupleMetadata() { return tupleMetadata; 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 20842df1e051..761e268ce37d 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 @@ -5991,7 +5991,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 ) { @@ -8222,13 +8222,14 @@ private InListPredicate processInSingleCriteriaParameter( 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() ) { return null; } - final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ); - return processInSingleParameter( sqmPredicate, sqmWrapper, jpaCriteriaParameter, domainParamBinding ); + return processInSingleParameter( sqmPredicate, sqmWrapper, domainParam, domainParamBinding ); } @SuppressWarnings( "rawtypes" ) 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 11a97f6e9162..3b98f810d083 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 @@ -7,7 +7,7 @@ package org.hibernate.query.sqm.tree; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.hibernate.query.sqm.NodeBuilder; @@ -46,7 +46,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 ) ); } @@ -62,7 +62,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 0dbdc0154e7c..310422866897 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 @@ -27,13 +27,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 @@ -47,6 +53,8 @@ public SqmJpaCriteriaParameterWrapper copy(SqmCopyContext context) { new SqmJpaCriteriaParameterWrapper<>( getNodeType(), jpaCriteriaParameter.copy( context ), + criteriaParameterId, + unnamedParameterId, nodeBuilder() ) ); @@ -67,6 +75,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(); @@ -87,6 +116,8 @@ public SqmParameter copy() { return new SqmJpaCriteriaParameterWrapper<>( getNodeType(), jpaCriteriaParameter, + criteriaParameterId, + unnamedParameterId, nodeBuilder() ); } @@ -123,10 +154,21 @@ public void appendHqlString(StringBuilder sb) { jpaCriteriaParameter.appendHqlString( sb ); } + @Override + 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 ? - getJpaCriteriaParameter().compareTo( ( (SqmJpaCriteriaParameterWrapper) anotherParameter ).getJpaCriteriaParameter() ) + Integer.compare( criteriaParameterId, ( (SqmJpaCriteriaParameterWrapper) anotherParameter ).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 d91e03ba58b1..8fe0dd6832f6 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 @@ -6,6 +6,8 @@ */ package org.hibernate.query.sqm.tree.expression; +import java.util.Comparator; + import org.hibernate.HibernateException; import org.hibernate.query.BindableType; import org.hibernate.query.criteria.JpaParameterExpression; @@ -23,6 +25,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 f2d04eaca227..a7c1aa96f980 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 @@ -8,6 +8,7 @@ import java.util.Collections; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.Set; import java.util.function.Consumer; @@ -64,6 +65,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) { @@ -86,10 +89,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 3997d80a9cb9..09fa128e88a7 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 @@ -67,8 +67,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 );