/*
 * 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 java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import javax.persistence.Parameter;

import org.hibernate.HibernateException;
import org.hibernate.Incubating;
import org.hibernate.QueryException;
import org.hibernate.QueryParameterException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.query.spi.NamedParameterDescriptor;
import org.hibernate.engine.query.spi.OrdinalParameterDescriptor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.MathHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.spi.QueryParameterListBinding;
import org.hibernate.type.SerializableType;
import org.hibernate.type.Type;

Manages the group of QueryParameterBinding for a particular query.
Author:Steve Ebersole, Chris Cranford
/** * Manages the group of QueryParameterBinding for a particular query. * * @author Steve Ebersole * @author Chris Cranford */
@Incubating public class QueryParameterBindingsImpl implements QueryParameterBindings { private static final CoreMessageLogger log = CoreLogging.messageLogger( QueryParameterBindingsImpl.class ); private final SessionFactoryImplementor sessionFactory; private final ParameterMetadata parameterMetadata; private final boolean queryParametersValidationEnabled; private final int ordinalParamValueOffset; private final int jdbcStyleOrdinalCountBase; private Map<QueryParameter, QueryParameterBinding> parameterBindingMap; private Map<QueryParameter, QueryParameterListBinding> parameterListBindingMap; private Set<QueryParameter> parametersConvertedToListBindings; public static QueryParameterBindingsImpl from( ParameterMetadata parameterMetadata, SessionFactoryImplementor sessionFactory, boolean queryParametersValidationEnabled) { if ( parameterMetadata == null ) { throw new QueryParameterException( "Query parameter metadata cannot be null" ); } return new QueryParameterBindingsImpl( sessionFactory, parameterMetadata, queryParametersValidationEnabled ); } private QueryParameterBindingsImpl( SessionFactoryImplementor sessionFactory, ParameterMetadata parameterMetadata, boolean queryParametersValidationEnabled) { this.sessionFactory = sessionFactory; this.parameterMetadata = parameterMetadata; this.queryParametersValidationEnabled = queryParametersValidationEnabled; this.parameterBindingMap = CollectionHelper.concurrentMap( parameterMetadata.getParameterCount() ); this.jdbcStyleOrdinalCountBase = sessionFactory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() ? 0 : 1; if ( parameterMetadata.hasPositionalParameters() ) { int smallestOrdinalParamLabel = Integer.MAX_VALUE; for ( QueryParameter queryParameter : parameterMetadata.getPositionalParameters() ) { if ( queryParameter.getPosition() == null ) { throw new HibernateException( "Non-ordinal parameter ended up in ordinal param list" ); } if ( queryParameter.getPosition() < smallestOrdinalParamLabel ) { smallestOrdinalParamLabel = queryParameter.getPosition(); } } ordinalParamValueOffset = smallestOrdinalParamLabel; } else { ordinalParamValueOffset = 0; } } @SuppressWarnings("WeakerAccess") protected QueryParameterBinding makeBinding(QueryParameter queryParameter) { assert ! parameterBindingMap.containsKey( queryParameter ); if ( ! parameterMetadata.containsReference( queryParameter ) ) { throw new IllegalArgumentException( "Cannot create binding for parameter reference [" + queryParameter + "] - reference is not a parameter of this query" ); } final QueryParameterBinding binding = makeBinding( queryParameter.getType() ); parameterBindingMap.put( queryParameter, binding ); return binding; } @SuppressWarnings("WeakerAccess") protected QueryParameterBinding makeBinding(Type bindType) { return new QueryParameterBindingImpl( bindType, sessionFactory, shouldValidateBindingValue() ); } @SuppressWarnings({"unchecked", "WeakerAccess"}) protected <T> QueryParameterListBinding<T> makeListBinding(QueryParameter<T> param) { if ( parametersConvertedToListBindings == null ) { parametersConvertedToListBindings = new HashSet<>(); } parametersConvertedToListBindings.add( param ); if ( parameterListBindingMap == null ) { parameterListBindingMap = new HashMap<>(); } return parameterListBindingMap.computeIfAbsent( param, p -> new QueryParameterListBindingImpl( param.getType(), shouldValidateBindingValue() ) ); } @Override @SuppressWarnings( "unchecked" ) public boolean isBound(QueryParameter parameter) { final QueryParameterBinding binding = getBinding( parameter ); return binding.isBound(); } @SuppressWarnings("unchecked") public <T> QueryParameterBinding<T> getBinding(QueryParameter<T> parameter) { QueryParameterBinding<T> binding = parameterBindingMap.get( parameter ); if ( binding == null ) { if ( ! parameterMetadata.containsReference( parameter ) ) { throw new IllegalArgumentException( "Could not resolve QueryParameter reference [" + parameter + "] to QueryParameterBinding" ); } binding = makeBinding( parameter ); } return binding; } @Override @SuppressWarnings("unchecked") public QueryParameterBinding getBinding(int position) { return getBinding( parameterMetadata.getQueryParameter( position ) ); } @Override @SuppressWarnings("unchecked") public QueryParameterBinding getBinding(String name) { return getBinding( parameterMetadata.getQueryParameter( name ) ); } public void verifyParametersBound(boolean reserveFirstParameter) { for ( QueryParameter<?> parameter : parameterMetadata.collectAllParameters() ) { // check the "normal" bindings if ( parameterBindingMap.containsKey( parameter ) ) { continue; } // next check the "list" bindings if ( parameterListBindingMap != null && parameterListBindingMap.containsKey( parameter ) ) { continue; } if ( parametersConvertedToListBindings != null && parametersConvertedToListBindings.contains( parameter ) ) { continue; } if ( parameter.getName() != null ) { throw new QueryException( "Named parameter not bound : " + parameter.getName() ); } else { throw new QueryException( "Ordinal parameter not bound : " + parameter.getPosition() ); } } }
Deprecated:(since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0
/** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */
@Deprecated public Collection<Type> collectBindTypes() { return parameterBindingMap.values() .stream() .map( QueryParameterBinding::getBindType ) .collect( Collectors.toList() ); }
Deprecated:(since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0
/** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */
@Deprecated public Collection<Object> collectBindValues() { return parameterBindingMap.values() .stream() .map( QueryParameterBinding::getBindValue ) .collect( Collectors.toList() ); }
Deprecated:(since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0
/** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */
@Deprecated public Type[] collectPositionalBindTypes() { return ArrayHelper.EMPTY_TYPE_ARRAY; // if ( ! parameterMetadata.hasPositionalParameters() ) { // return ArrayHelper.EMPTY_TYPE_ARRAY; // } // // // callers expect these in ordinal order. In a way that is natural, but at the same // // time long term a way to find types/values by name/position would be better // // final TreeMap<QueryParameter, QueryParameterBinding> sortedPositionalParamBindings = getSortedPositionalParamBindingMap(); // final List<Type> types = CollectionHelper.arrayList( sortedPositionalParamBindings.size() ); // // for ( Map.Entry<QueryParameter, QueryParameterBinding> entry : sortedPositionalParamBindings.entrySet() ) { // if ( entry.getKey().getPosition() == null ) { // continue; // } // // Type type = entry.getValue().getBindType(); // if ( type == null ) { // type = entry.getKey().getType(); // } // // if ( type == null ) { // log.debugf( // "Binding for positional-parameter [%s] did not define type, using SerializableType", // entry.getKey().getPosition() // ); // type = SerializableType.INSTANCE; // } // // types.add( type ); // } // // return types.toArray( new Type[ types.size() ] ); } private TreeMap<QueryParameter, QueryParameterBinding> getSortedPositionalParamBindingMap() { final TreeMap<QueryParameter, QueryParameterBinding> map = new TreeMap<>( Comparator.comparing( Parameter::getPosition ) ); for ( Map.Entry<QueryParameter, QueryParameterBinding> entry : parameterBindingMap.entrySet() ) { if ( entry.getKey().getPosition() == null ) { continue; } map.put( entry.getKey(), entry.getValue() ); } return map; } private static final Object[] EMPTY_VALUES = new Object[0];
Deprecated:(since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0
/** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */
@Deprecated @SuppressWarnings("unchecked") public Object[] collectPositionalBindValues() { return EMPTY_VALUES; // if ( ! parameterMetadata.hasPositionalParameters() ) { // return EMPTY_VALUES; // } // // final TreeMap<QueryParameter, QueryParameterBinding> sortedPositionalParamBindings = getSortedPositionalParamBindingMap(); // final List values = CollectionHelper.arrayList( sortedPositionalParamBindings.size() ); // // for ( Map.Entry<QueryParameter, QueryParameterBinding> entry : sortedPositionalParamBindings.entrySet() ) { // if ( entry.getKey().getPosition() == null ) { // continue; // } // values.add( entry.getValue().getBindValue() ); // } // // return values.toArray( new Object[values.size()] ); }
Deprecated:(since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0
/** * @deprecated (since 5.2) expect a different approach to org.hibernate.engine.spi.QueryParameters in 6.0 */
@Deprecated public Map<String, TypedValue> collectNamedParameterBindings() { final Map<String, TypedValue> collectedBindings = new HashMap<>(); for ( Map.Entry<QueryParameter, QueryParameterBinding> entry : parameterBindingMap.entrySet() ) { final String key; if ( entry.getKey().getPosition() != null ) { key = Integer.toString( entry.getKey().getPosition() ); } else { key = entry.getKey().getName(); } Type bindType = entry.getValue().getBindType(); if ( bindType == null ) { log.debugf( "Binding for parameter [%s] did not define type", key ); bindType = SerializableType.INSTANCE; } collectedBindings.put( key, new TypedValue( bindType, entry.getValue().getBindValue() ) ); } return collectedBindings; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Parameter list binding - expect changes in 6.0
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") public <T> QueryParameterListBinding<T> getQueryParameterListBinding(QueryParameter<T> queryParameter) { if ( parameterListBindingMap == null ) { parameterListBindingMap = new HashMap<>(); } return transformQueryParameterBindingToQueryParameterListBinding( queryParameter ); }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated private QueryParameterListBinding locateQueryParameterListBinding(QueryParameter queryParameter) { if ( parameterListBindingMap == null ) { parameterListBindingMap = new HashMap<>(); } QueryParameterListBinding binding = parameterListBindingMap.get( queryParameter ); if ( binding == null ) { QueryParameter resolved = resolveParameter( queryParameter ); if ( resolved != queryParameter ) { binding = parameterListBindingMap.get( resolved ); } } if ( binding == null ) { throw new IllegalArgumentException( "Could not locate parameter list binding" ); } return binding; } private QueryParameter resolveParameter(QueryParameter queryParameter) { if ( queryParameter.getName() != null ) { return parameterMetadata.getQueryParameter( queryParameter.getName() ); } else { return parameterMetadata.getQueryParameter( queryParameter.getPosition() ); } }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") private <T> QueryParameterListBinding<T> transformQueryParameterBindingToQueryParameterListBinding(QueryParameter<T> queryParameter) { log.debugf( "Converting QueryParameterBinding to QueryParameterListBinding for given QueryParameter : %s", queryParameter ); getAndRemoveBinding( queryParameter ); return makeListBinding( queryParameter ); } private boolean shouldValidateBindingValue() { return sessionFactory.getSessionFactoryOptions().isJpaBootstrap() && queryParametersValidationEnabled; }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") private <T> QueryParameterBinding<T> getAndRemoveBinding(QueryParameter<T> parameter) { QueryParameterBinding<T> binding = parameterBindingMap.remove( parameter ); if ( binding == null ) { if ( parameter.getName() != null ) { parameter = parameterMetadata.getQueryParameter( parameter.getName() ); } else { parameter = parameterMetadata.getQueryParameter( parameter.getPosition() ); } if ( parameter == null ) { throw new HibernateException( "Unable to resolve QueryParameter" ); } } binding = parameterBindingMap.remove( parameter ); return binding; }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") public <T> QueryParameterListBinding<T> getQueryParameterListBinding(String name) { // find the QueryParameter instance for the given name final QueryParameter<T> queryParameter = resolveQueryParameter( name ); return getQueryParameterListBinding( queryParameter ); }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") private <T> QueryParameter<T> resolveQueryParameter(String name) { final QueryParameter<Object> param = parameterMetadata.getQueryParameter( name ); if ( param == null ) { throw new IllegalArgumentException( "Unable to resolve given parameter name [" + name + "] to QueryParameter reference" ); } return (QueryParameter<T>) param; }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") public <T> QueryParameterListBinding<T> getQueryParameterListBinding(int name) { // find the QueryParameter instance for the given name final QueryParameter<T> queryParameter = resolveQueryParameter( name ); return getQueryParameterListBinding( queryParameter ); }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") private <T> QueryParameter<T> resolveQueryParameter(int name) { final QueryParameter<Object> param = parameterMetadata.getQueryParameter( name ); if ( param == null ) { throw new IllegalArgumentException( "Unable to resolve given parameter name [" + name + "] to QueryParameter reference" ); } return (QueryParameter<T>) param; }
Deprecated:(since 5.2) expected changes to "collection-valued parameter binding" in 6.0
/** * @deprecated (since 5.2) expected changes to "collection-valued parameter binding" in 6.0 */
@Deprecated @SuppressWarnings("unchecked") public String expandListValuedParameters(String queryString, SharedSessionContractImplementor session) { if ( queryString == null ) { return null; } if ( parameterListBindingMap == null || parameterListBindingMap.isEmpty() ) { return queryString; } // more-or-less... for each entry in parameterListBindingMap we will create an // entry in parameterBindingMap for each of the values in the bound value list. afterwards // we will clear the parameterListBindingMap. // // NOTE that this is essentially the legacy logical prior to modeling QueryParameterBinding/QueryParameterListBinding. // Fully expect the details of how this is handled in 6.0 // HHH-1123 // Some DBs limit number of IN expressions. For now, warn... final Dialect dialect = session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getJdbcEnvironment().getDialect(); final int inExprLimit = dialect.getInExpressionCountLimit(); int maxOrdinalPosition = getMaxOrdinalPosition(); for ( Map.Entry<QueryParameter, QueryParameterListBinding> entry : parameterListBindingMap.entrySet() ) { final QueryParameter sourceParam = entry.getKey(); final Collection bindValues = entry.getValue().getBindValues(); int bindValueCount = bindValues.size(); int bindValueMaxCount = bindValueCount; boolean inClauseParameterPaddingEnabled = session.getFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled() && bindValueCount > 2; if ( inClauseParameterPaddingEnabled ) { int bindValuePaddingCount = MathHelper.ceilingPowerOfTwo( bindValueCount ); if ( bindValueCount < bindValuePaddingCount && (inExprLimit == 0 || bindValuePaddingCount < inExprLimit) ) { bindValueMaxCount = bindValuePaddingCount; } } if ( inExprLimit > 0 && bindValueCount > inExprLimit ) { log.tooManyInExpressions( dialect.getClass().getName(), inExprLimit, sourceParam.getName(), bindValueCount ); } final String sourceToken; if ( sourceParam instanceof NamedParameterDescriptor ) { sourceToken = ":" + NamedParameterDescriptor.class.cast( sourceParam ).getName(); } else { sourceToken = "?" + OrdinalParameterDescriptor.class.cast( sourceParam ).getPosition(); } final int loc = StringHelper.indexOfIdentifierWord( queryString, sourceToken ); if ( loc < 0 ) { continue; } final String beforePlaceholder = queryString.substring( 0, loc ); final String afterPlaceholder = queryString.substring( loc + sourceToken.length() ); // check if placeholder is already immediately enclosed in parentheses // (ignoring whitespace) boolean isEnclosedInParens = StringHelper.getLastNonWhitespaceCharacter( beforePlaceholder ) == '(' && StringHelper.getFirstNonWhitespaceCharacter( afterPlaceholder ) == ')'; if ( bindValues.size() == 1 && isEnclosedInParens ) { // short-circuit for performance when only 1 value and the // placeholder is already enclosed in parentheses... final QueryParameterBinding syntheticBinding = makeBinding( entry.getValue().getBindType() ); syntheticBinding.setBindValue( bindValues.iterator().next() ); parameterBindingMap.put( sourceParam, syntheticBinding ); continue; } StringBuilder expansionList = new StringBuilder(); Iterator bindValueIterator = entry.getValue().getBindValues().iterator(); Object bindValue = null; for ( int i = 0; i < bindValueMaxCount; i++ ) { if ( i < bindValueCount ) { bindValue = bindValueIterator.next(); } if ( i > 0 ) { expansionList.append( ", " ); } final QueryParameter syntheticParam; if ( sourceParam instanceof NamedParameterDescriptor ) { // in the case of a named parameter, for each value in the bound list-of-values we: // 1) create a synthetic named parameter // 2) expand the queryString to include each synthetic named param in place of the original // 3) create a new synthetic binding for just that single value under the synthetic name final String syntheticName = NamedParameterDescriptor.class.cast( sourceParam ).getName() + '_' + i; expansionList.append( ":" ).append( syntheticName ); syntheticParam = new NamedParameterDescriptor( syntheticName, sourceParam.getType(), sourceParam.getSourceLocations() ); } else { // in the case of an ordinal parameter, for each value in the bound list-of-values we: // 1) create a new ordinal parameter at a synthetic position of maxOrdinalPosition + 1 // 2) expand the queryString to include each new ordinal param in place of the original // 3) create a new ordinal binding for just that single value under the synthetic position // for the first item, we reuse the original parameter to avoid gaps in the positions if ( i == 0 ) { syntheticParam = sourceParam; } else { int syntheticPosition = ++maxOrdinalPosition; syntheticParam = new OrdinalParameterDescriptor( syntheticPosition, syntheticPosition - jdbcStyleOrdinalCountBase, sourceParam.getType(), sourceParam.getSourceLocations() ); } expansionList.append( "?" ).append( syntheticParam.getPosition() ); } final QueryParameterBinding syntheticBinding = makeBinding( entry.getValue().getBindType() ); syntheticBinding.setBindValue( bindValue ); parameterBindingMap.put( syntheticParam, syntheticBinding ); } queryString = StringHelper.replace( beforePlaceholder, afterPlaceholder, sourceToken, expansionList.toString(), true, true ); } return queryString; } private int getMaxOrdinalPosition() { int maxOrdinalPosition = 0; for ( QueryParameter<?> queryParameter : parameterBindingMap.keySet() ) { if ( queryParameter instanceof OrdinalParameterDescriptor ) { maxOrdinalPosition = Math.max( maxOrdinalPosition, queryParameter.getPosition() ); } } for ( QueryParameter<?> queryParameter : parameterListBindingMap.keySet() ) { if ( queryParameter instanceof OrdinalParameterDescriptor ) { maxOrdinalPosition = Math.max( maxOrdinalPosition, queryParameter.getPosition() ); } } return maxOrdinalPosition; } }