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;
@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() ) {
if ( parameterBindingMap.containsKey( parameter ) ) {
continue;
}
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
public Collection<Type> collectBindTypes() {
return parameterBindingMap.values()
.stream()
.map( QueryParameterBinding::getBindType )
.collect( Collectors.toList() );
}
@Deprecated
public Collection<Object> collectBindValues() {
return parameterBindingMap.values()
.stream()
.map( QueryParameterBinding::getBindValue )
.collect( Collectors.toList() );
}
@Deprecated
public Type[] collectPositionalBindTypes() {
return ArrayHelper.EMPTY_TYPE_ARRAY;
}
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
@SuppressWarnings("unchecked")
public Object[] collectPositionalBindValues() {
return EMPTY_VALUES;
}
@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;
}
@Deprecated
@SuppressWarnings("unchecked")
public <T> QueryParameterListBinding<T> getQueryParameterListBinding(QueryParameter<T> queryParameter) {
if ( parameterListBindingMap == null ) {
parameterListBindingMap = new HashMap<>();
}
return transformQueryParameterBindingToQueryParameterListBinding( queryParameter );
}
@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
@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
@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
@SuppressWarnings("unchecked")
public <T> QueryParameterListBinding<T> getQueryParameterListBinding(String name) {
final QueryParameter<T> queryParameter = resolveQueryParameter( name );
return getQueryParameterListBinding( queryParameter );
}
@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
@SuppressWarnings("unchecked")
public <T> QueryParameterListBinding<T> getQueryParameterListBinding(int name) {
final QueryParameter<T> queryParameter = resolveQueryParameter( name );
return getQueryParameterListBinding( queryParameter );
}
@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
@SuppressWarnings("unchecked")
public String expandListValuedParameters(String queryString, SharedSessionContractImplementor session) {
if ( queryString == null ) {
return null;
}
if ( parameterListBindingMap == null || parameterListBindingMap.isEmpty() ) {
return queryString;
}
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() );
boolean isEnclosedInParens =
StringHelper.getLastNonWhitespaceCharacter( beforePlaceholder ) == '(' &&
StringHelper.getFirstNonWhitespaceCharacter( afterPlaceholder ) == ')';
if ( bindValues.size() == 1 && isEnclosedInParens ) {
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 ) {
final String syntheticName = NamedParameterDescriptor.class.cast( sourceParam ).getName() + '_' + i;
expansionList.append( ":" ).append( syntheticName );
syntheticParam = new NamedParameterDescriptor(
syntheticName,
sourceParam.getType(),
sourceParam.getSourceLocations()
);
}
else {
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;
}
}