diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java
index 95ed5c205ac4..6c2deaf960c6 100644
--- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java
+++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java
@@ -75,23 +75,21 @@ public
ProcedureParameterBinding
getQueryParameterBinding(ProcedureParame
}
@Override
- public
ProcedureParameterBinding
getBinding(String name) {
+ public ProcedureParameterBinding> getBinding(String name) {
final var parameter = parameterMetadata.getQueryParameter( name );
if ( parameter == null ) {
throw new IllegalArgumentException( "Parameter with name '" + name + "' does not exist" );
}
- //noinspection unchecked
- return getQueryParameterBinding( (ProcedureParameterImplementor
getBinding(int position) {
+ public ProcedureParameterBinding> getBinding(int position) {
final var parameter = parameterMetadata.getQueryParameter( position );
if ( parameter == null ) {
throw new IllegalArgumentException( "Parameter at position " + position + "does not exist" );
}
- //noinspection unchecked
- return getQueryParameterBinding( (ProcedureParameterImplementor
) parameter );
+ return getQueryParameterBinding( (ProcedureParameterImplementor>) parameter );
}
@Override
diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java b/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java
index 828f3ac61e17..1b6705d39364 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/QueryArgumentException.java
@@ -15,11 +15,20 @@
*/
public class QueryArgumentException extends IllegalArgumentException {
private final Class> parameterType;
+ private final Class> argumentType;
private final Object argument;
public QueryArgumentException(String message, Class> parameterType, Object argument) {
- super(message);
+ super( message + " (argument [" + argument + "] is not assignable to " + parameterType.getName() + ")" );
this.parameterType = parameterType;
+ this.argumentType = argument == null ? null : argument.getClass();
+ this.argument = argument;
+ }
+
+ public QueryArgumentException(String message, Class> parameterType, Class> argumentType, Object argument) {
+ super( message + " (" + argumentType.getName() + " is not assignable to " + parameterType.getName() + ")" );
+ this.parameterType = parameterType;
+ this.argumentType = argumentType;
this.argument = argument;
}
@@ -27,6 +36,10 @@ public Class> getParameterType() {
return parameterType;
}
+ public Class> getArgumentType() {
+ return argumentType;
+ }
+
public Object getArgument() {
return argument;
}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java
new file mode 100644
index 000000000000..872dbfe53959
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.query.internal;
+
+import jakarta.persistence.metamodel.Type;
+import org.hibernate.HibernateException;
+import org.hibernate.type.BindingContext;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.java.spi.EntityJavaType;
+
+import java.util.Collection;
+
+import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
+
+/**
+ * @since 7.3
+ *
+ * @author Gavin King
+ */
+public class QueryArguments {
+
+ private static boolean isInstance(Object value, JavaType> javaType) {
+ try {
+ // special handling for entity arguments due to
+ // the possibility of an uninitialized proxy
+ // (which we don't want or need to fetch)
+ if ( javaType instanceof EntityJavaType> ) {
+ final var javaTypeClass = javaType.getJavaTypeClass();
+ final var initializer = extractLazyInitializer( value );
+ final var valueEntityClass =
+ initializer != null
+ ? initializer.getPersistentClass()
+ : value.getClass();
+ // accept assignability in either direction
+ return javaTypeClass.isAssignableFrom( valueEntityClass )
+ || valueEntityClass.isAssignableFrom( javaTypeClass );
+ }
+ else {
+ // require that the argument be assignable to the parameter
+ return javaType.isInstance( javaType.coerce( value ) );
+ }
+ }
+ catch (HibernateException ce) {
+ return false;
+ }
+ }
+
+ public static boolean isInstance(
+ Type> parameterType, Object value,
+ BindingContext bindingContext) {
+ if ( value == null ) {
+ return true;
+ }
+ final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
+ assert sqmExpressible != null;
+ final var javaType = sqmExpressible.getExpressibleJavaType();
+ return isInstance( value, javaType );
+ }
+
+ public static boolean areInstances(
+ Type> parameterType, Collection> values,
+ BindingContext bindingContext) {
+ if ( values.isEmpty() ) {
+ return true;
+ }
+ final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
+ assert sqmExpressible != null;
+ final var javaType = sqmExpressible.getExpressibleJavaType();
+ for ( Object value : values ) {
+ if ( !isInstance( value, javaType ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean areInstances(
+ Type> parameterType, Object[] values,
+ BindingContext bindingContext) {
+ if ( values.length == 0 ) {
+ return true;
+ }
+ if ( parameterType.getJavaType()
+ .isAssignableFrom( values.getClass().getComponentType() ) ) {
+ return true;
+ }
+ final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
+ assert sqmExpressible != null;
+ final var javaType = sqmExpressible.getExpressibleJavaType();
+ for ( Object value : values ) {
+ if ( !isInstance( value, javaType ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java
index 4c9bdd7d91b5..0acc3415c676 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java
@@ -16,13 +16,13 @@
import org.hibernate.query.QueryParameter;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindingTypeResolver;
-import org.hibernate.query.spi.QueryParameterBindingValidator;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
+import static org.hibernate.query.spi.QueryParameterBindingValidator.validate;
import static org.hibernate.type.descriptor.java.JavaTypeHelper.isTemporal;
import static org.hibernate.type.internal.BindingTypeHelper.resolveTemporalPrecision;
@@ -116,7 +116,7 @@ public T getBindValue() {
public void setBindValue(T value, boolean resolveJdbcTypeIfNecessary) {
if ( !handleAsMultiValue( value, null ) ) {
final Object coerced = coerceIfNotJpa( value );
- validate( coerced );
+ validate( getBindType(), coerced, sessionFactory );
if ( value == null ) {
// needed when setting a null value to the parameter of a native SQL query
@@ -174,7 +174,7 @@ public void setBindValue(T value, @Nullable BindableType clarifiedType) {
}
final Object coerced = coerce( value );
- validate( coerced, clarifiedType );
+ validate( clarifiedType, coerced, sessionFactory );
bindValue( coerced );
}
}
@@ -187,7 +187,7 @@ public void setBindValue(T value, TemporalType temporalTypePrecision) {
}
final Object coerced = coerceIfNotJpa( value );
- validate( coerced, temporalTypePrecision );
+ validate( getBindType(), coerced, sessionFactory );
bindValue( coerced );
setExplicitTemporalPrecision( temporalTypePrecision );
}
@@ -218,15 +218,19 @@ public void setBindValues(Collection extends T> values) {
this.bindValue = null;
this.bindValues = values;
+ final T value = firstNonNull( values );
+ if ( canBindValueBeSet( value, bindType ) ) {
+ bindType = getParameterBindingTypeResolver().resolveParameterBindType( value );
+ }
+ }
+
+ private static @Nullable T firstNonNull(Collection extends T> values) {
final var iterator = values.iterator();
T value = null;
while ( value == null && iterator.hasNext() ) {
value = iterator.next();
}
-
- if ( canBindValueBeSet( value, bindType ) ) {
- bindType = getParameterBindingTypeResolver().resolveParameterBindType( value );
- }
+ return value;
}
@Override
@@ -284,18 +288,6 @@ else if ( type instanceof BasicValuedMapping basicValuedMapping ) {
return false;
}
- private void validate(Object value) {
- QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, getCriteriaBuilder() );
- }
-
- private void validate(Object value, BindableType> clarifiedType) {
- QueryParameterBindingValidator.INSTANCE.validate( clarifiedType, value, getCriteriaBuilder() );
- }
-
- private void validate(Object value, TemporalType clarifiedTemporalType) {
- QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, clarifiedTemporalType, getCriteriaBuilder() );
- }
-
public TypeConfiguration getTypeConfiguration() {
return sessionFactory.getTypeConfiguration();
}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java
index 281a0b5de19e..a7f63b73fa8e 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java
@@ -68,7 +68,8 @@ private QueryParameterBindingsImpl(
this.parameterBindingMap = linkedMapOfSize( queryParameters.size() );
this.parameterBindingMapByNameOrPosition = mapOfSize( queryParameters.size() );
for ( var queryParameter : queryParameters ) {
- parameterBindingMap.put( queryParameter, createBinding( sessionFactory, parameterMetadata, queryParameter ) );
+ parameterBindingMap.put( queryParameter,
+ createBinding( sessionFactory, parameterMetadata, queryParameter ) );
}
for ( var entry : parameterBindingMap.entrySet() ) {
final var queryParameter = entry.getKey();
@@ -129,30 +130,32 @@ public
QueryParameterBinding
getBinding(QueryParameterImplementor
para
"Cannot create binding for parameter reference [" + parameter + "] - reference is not a parameter of this query"
);
}
- //noinspection unchecked
- return (QueryParameterBinding
) binding;
+ if ( !binding.getQueryParameter().equals( parameter ) ) {
+ throw new IllegalStateException("Parameter binding corrupted for: " + parameter.getName() );
+ }
+ @SuppressWarnings("unchecked") // safe because we checked the parameter
+ final var castBinding = (QueryParameterBinding
) binding;
+ return castBinding;
}
@Override
- public
QueryParameterBinding
getBinding(int position) {
+ public QueryParameterBinding> getBinding(int position) {
final var binding = parameterBindingMapByNameOrPosition.get( position );
if ( binding == null ) {
// Invoke this method to throw the exception
parameterMetadata.getQueryParameter( position );
}
- //noinspection unchecked
- return (QueryParameterBinding
) binding;
+ return binding;
}
@Override
- public
QueryParameterBinding
getBinding(String name) {
+ public QueryParameterBinding> getBinding(String name) {
final var binding = parameterBindingMapByNameOrPosition.get( name );
if ( binding == null ) {
// Invoke this method to throw the exception
parameterMetadata.getQueryParameter( name );
}
- //noinspection unchecked
- return (QueryParameterBinding
) binding;
+ return binding;
}
@Override
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 b6678db8e43c..4817faa4374a 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
@@ -38,7 +38,6 @@
import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.internal.QueryOptionsImpl;
import org.hibernate.query.sqm.NodeBuilder;
-import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@@ -51,10 +50,12 @@
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import static java.util.Arrays.asList;
+import static java.lang.Math.round;
+import static java.util.Collections.unmodifiableSet;
import static java.util.Locale.ROOT;
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
@@ -88,6 +89,8 @@
import static org.hibernate.jpa.internal.util.ConfigurationHelper.getCacheMode;
import static org.hibernate.jpa.internal.util.ConfigurationHelper.getInteger;
import static org.hibernate.query.QueryLogging.QUERY_MESSAGE_LOGGER;
+import static org.hibernate.query.internal.QueryArguments.areInstances;
+import static org.hibernate.query.internal.QueryArguments.isInstance;
/**
* Base implementation of {@link CommonQueryContract}.
@@ -282,7 +285,7 @@ public final boolean applyHint(String hintName, Object value) {
//fall through:
case HINT_SPEC_QUERY_TIMEOUT:
// convert milliseconds to seconds
- int timeout = (int) Math.round( getInteger( value ).doubleValue() / 1000.0 );
+ final int timeout = (int) round( getInteger( value ).doubleValue() / 1000.0 );
applyTimeoutHint( timeout );
return true;
case HINT_COMMENT:
@@ -621,15 +624,13 @@ public int getFirstResult() {
@Override
public abstract ParameterMetadataImplementor getParameterMetadata();
- @SuppressWarnings({"unchecked", "rawtypes"})
public Set> getParameters() {
checkOpenNoRollback();
- return (Set) getParameterMetadata().getRegistrations();
+ return unmodifiableSet( getParameterMetadata().getRegistrations() );
}
public QueryParameterImplementor> getParameter(String name) {
checkOpenNoRollback();
-
try {
return getParameterMetadata().getQueryParameter( name );
}
@@ -638,21 +639,20 @@ public QueryParameterImplementor> getParameter(String name) {
}
}
- @SuppressWarnings("unchecked")
public QueryParameterImplementor getParameter(String name, Class type) {
checkOpenNoRollback();
-
try {
- //noinspection rawtypes
- final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( name );
- if ( !type.isAssignableFrom( parameter.getParameterType() ) ) {
+ final var parameter = getParameterMetadata().getQueryParameter( name );
+ final var parameterType = parameter.getParameterType();
+ if ( !type.isAssignableFrom( parameterType ) ) {
throw new IllegalArgumentException(
- "The type [" + parameter.getParameterType().getName() +
- "] associated with the parameter corresponding to name [" + name +
- "] is not assignable to requested Java type [" + type.getName() + "]"
+ "Type specified for parameter named '" + name + "' is incompatible"
+ + " (" + parameterType.getName() + " is not assignable to " + type.getName() + ")"
);
}
- return parameter;
+ @SuppressWarnings("unchecked") // safe, just checked
+ var castParameter = (QueryParameterImplementor) parameter;
+ return castParameter;
}
catch ( HibernateException e ) {
throw getExceptionConverter().convert( e );
@@ -661,7 +661,6 @@ public QueryParameterImplementor getParameter(String name, Class type)
public QueryParameterImplementor> getParameter(int position) {
checkOpenNoRollback();
-
try {
return getParameterMetadata().getQueryParameter( position );
}
@@ -670,20 +669,20 @@ public QueryParameterImplementor> getParameter(int position) {
}
}
- @SuppressWarnings( {"unchecked", "rawtypes"} )
public QueryParameterImplementor getParameter(int position, Class type) {
checkOpenNoRollback();
-
try {
- final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( position );
- if ( !type.isAssignableFrom( parameter.getParameterType() ) ) {
+ final var parameter = getParameterMetadata().getQueryParameter( position );
+ final var parameterType = parameter.getParameterType();
+ if ( !type.isAssignableFrom( parameterType ) ) {
throw new IllegalArgumentException(
- "The type [" + parameter.getParameterType().getName() +
- "] associated with the parameter corresponding to position [" + position +
- "] is not assignable to requested Java type [" + type.getName() + "]"
+ "Type specified for parameter at position " + position + " is incompatible"
+ + " (" + parameterType.getName() + " is not assignable to " + type.getName() + ")"
);
}
- return parameter;
+ @SuppressWarnings("unchecked") // safe, just checked
+ var castParameter = (QueryParameterImplementor) parameter;
+ return castParameter;
}
catch ( HibernateException e ) {
throw getExceptionConverter().convert( e );
@@ -703,15 +702,41 @@ private
javaType, P value) {
getCheckOpen();
- return getQueryParameterBindings().getBinding( name );
+ final var binding = getQueryParameterBindings().getBinding( name );
+ final var parameterType = binding.getBindType();
+ if ( parameterType != null ) {
+ final var parameterJavaType = parameterType.getJavaType();
+ if ( !parameterJavaType.isAssignableFrom( javaType )
+ && !isInstance( parameterType, value, getNodeBuilder() ) ) {
+ throw new QueryArgumentException(
+ "Argument to parameter named '" + name + "' has an incompatible type",
+ parameterJavaType, javaType, value );
+ }
+ }
+ @SuppressWarnings("unchecked") // safe, just checked
+ var castBinding = (QueryParameterBinding
) binding;
+ return castBinding;
}
- protected
QueryParameterBinding
locateBinding(int position) {
+ protected
QueryParameterBinding
locateBinding(int position, Class
javaType, P value) {
getCheckOpen();
- return getQueryParameterBindings().getBinding( position );
+ final var binding = getQueryParameterBindings().getBinding( position );
+ final var parameterType = binding.getBindType();
+ if ( parameterType != null ) {
+ final var parameterJavaType = parameterType.getJavaType();
+ if ( !parameterJavaType.isAssignableFrom( javaType )
+ && !isInstance( parameterType, value, getNodeBuilder() ) ) {
+ throw new QueryArgumentException(
+ "Argument to parameter at position " + position + " has an incompatible type",
+ parameterJavaType, javaType, value );
+ }
+ }
+ @SuppressWarnings("unchecked") // safe, just checked
+ var castBinding = (QueryParameterBinding
javaType, Collection extends P> values) {
+ getCheckOpen();
+ final var binding = getQueryParameterBindings().getBinding( position );
+ final var parameterType = binding.getBindType();
+ if ( parameterType != null ) {
+ final var parameterJavaType = parameterType.getJavaType();
+ if ( !parameterJavaType.isAssignableFrom( javaType )
+ && !areInstances( parameterType, values, getNodeBuilder() ) ) {
+ throw new QueryArgumentException(
+ "Argument to parameter at position " + position + " has an incompatible type",
+ parameterJavaType, javaType, values );
+ }
+ }
+ @SuppressWarnings("unchecked") // safe, just checked
+ var castBinding = (QueryParameterBinding
) binding;
+ return castBinding;
}
protected
QueryParameterImplementor
getQueryParameter(QueryParameterImplementor
parameter) {
@@ -741,22 +829,22 @@ protected
QueryParameterImplementor
getQueryParameter(QueryParameterImple
public boolean isBound(Parameter> param) {
getCheckOpen();
final var parameter = getParameterMetadata().resolve( param );
- return parameter != null && getQueryParameterBindings().isBound( getQueryParameter( parameter ) );
+ return parameter != null
+ && getQueryParameterBindings().isBound( getQueryParameter( parameter ) );
}
public T getParameterValue(Parameter param) {
checkOpenNoRollback();
-
final var parameter = getParameterMetadata().resolve( param );
if ( parameter == null ) {
throw new IllegalArgumentException( "The parameter [" + param + "] is not part of this Query" );
}
-
- final var binding = getQueryParameterBindings().getBinding( getQueryParameter( parameter ) );
+ final var binding =
+ getQueryParameterBindings()
+ .getBinding( getQueryParameter( parameter ) );
if ( binding == null || !binding.isBound() ) {
throw new IllegalStateException( "Parameter value not yet bound : " + param.toString() );
}
-
if ( binding.isMultiValued() ) {
// TODO: THIS IS UNSOUND
//noinspection unchecked
@@ -769,54 +857,69 @@ public T getParameterValue(Parameter param) {
public Object getParameterValue(String name) {
checkOpenNoRollback();
-
- final QueryParameterBinding> binding = getQueryParameterBindings().getBinding( name );
+ final var binding = getQueryParameterBindings().getBinding( name );
if ( !binding.isBound() ) {
throw new IllegalStateException( "The parameter [" + name + "] has not yet been bound" );
}
-
- if ( binding.isMultiValued() ) {
- return binding.getBindValues();
- }
- else {
- return binding.getBindValue();
- }
+ return binding.isMultiValued() ? binding.getBindValues() : binding.getBindValue();
}
public Object getParameterValue(int position) {
checkOpenNoRollback();
-
- final QueryParameterBinding> binding = getQueryParameterBindings().getBinding( position );
+ final var binding = getQueryParameterBindings().getBinding( position );
if ( !binding.isBound() ) {
throw new IllegalStateException( "The parameter [" + position + "] has not yet been bound" );
}
-
return binding.isMultiValued() ? binding.getBindValues() : binding.getBindValue();
}
+
+ private