org.hibernate.query.internal.QueryParameterBindingsImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.query.internal.QueryParameterBindingsImpl.java

Source

/*
 * 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
 * @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.getHibernateType());
        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.getHibernateType(), 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
    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
    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
    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
    @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
    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
    @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
    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
    @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
    @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
    @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
    @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
    @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
    @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
    @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.getHibernateType(),
                            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.getHibernateType(),
                                sourceParam.getSourceLocations());
                    }

                    expansionList.append("?").append(syntheticParam.getPosition());
                }

                final QueryParameterBinding syntheticBinding = makeBinding(entry.getValue().getBindType());
                syntheticBinding.setBindValue(bindValue);
                parameterBindingMap.put(syntheticParam, syntheticBinding);
            }

            String expansionListAsString = expansionList.toString();

            // HHH-8901
            if (!dialect.supportsEmptyInList() && expansionListAsString.isEmpty()) {
                expansionListAsString = "null";
            }

            queryString = StringHelper.replace(beforePlaceholder, afterPlaceholder, sourceToken,
                    expansionListAsString, 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;
    }
}