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 f53fb220006a..e75a33417ea3 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
@@ -71,10 +71,9 @@ public
ProcedureParameterBinding
getQueryParamerBinding(ProcedureParamete
}
@Override
- public
ProcedureParameterBinding
getBinding(String name) {
- //noinspection unchecked
+ public ProcedureParameterBinding> getBinding(String name) {
final var parameter =
- (ProcedureParameterImplementor
)
+ (ProcedureParameterImplementor>)
parameterMetadata.getQueryParameter( name );
if ( parameter == null ) {
throw new IllegalArgumentException( "Parameter does not exist: " + name );
@@ -83,10 +82,9 @@ public
ProcedureParameterBinding
getBinding(String name) {
}
@Override
- public
ProcedureParameterBinding
getBinding(int position) {
- //noinspection unchecked
+ public ProcedureParameterBinding> getBinding(int position) {
final var parameter =
- (ProcedureParameterImplementor
)
+ (ProcedureParameterImplementor>)
parameterMetadata.getQueryParameter( position );
if ( parameter == null ) {
throw new IllegalArgumentException( "Parameter at position " + position + "does not exist" );
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..bdf001c496bc
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryArguments.java
@@ -0,0 +1,90 @@
+/*
+ * 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.WrapperOptions;
+
+import java.util.Collection;
+
+/**
+ * @since 7.3
+ *
+ * @author Gavin King
+ */
+public class QueryArguments {
+
+ public static boolean isInstance(
+ Type> parameterType, Object value,
+ BindingContext bindingContext, WrapperOptions options) {
+ if ( value == null ) {
+ return true;
+ }
+ final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
+ assert sqmExpressible != null;
+ final var javaType = sqmExpressible.getExpressibleJavaType();
+ if ( !javaType.isInstance( value ) ) {
+ try {
+ // if this succeeds, we are good
+ javaType.wrap( value, options );
+ }
+ catch (HibernateException | UnsupportedOperationException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean areInstances(
+ Type> parameterType, Collection> values,
+ BindingContext bindingContext, WrapperOptions options) {
+ if ( values.isEmpty() ) {
+ return true;
+ }
+ final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
+ assert sqmExpressible != null;
+ final var javaType = sqmExpressible.getExpressibleJavaType();
+ for ( Object value : values ) {
+ if ( !javaType.isInstance( value ) ) {
+ try {
+ // if this succeeds, we are good
+ javaType.wrap( value, options );
+ }
+ catch (HibernateException | UnsupportedOperationException e) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static boolean areInstances(
+ Type> parameterType, Object[] values,
+ BindingContext bindingContext, WrapperOptions options) {
+ 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 ( !javaType.isInstance( value ) ) {
+ try {
+ // if this succeeds, we are good
+ javaType.wrap( value, options );
+ }
+ catch (HibernateException | UnsupportedOperationException e) {
+ 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 1341b1fdd2ff..12db03c55795 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 );
}
@@ -284,18 +284,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() );
- }
-
@Override
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..2561fd7abd5e 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
@@ -129,30 +129,29 @@ public
QueryParameterBinding
getBinding(QueryParameterImplementor
para
"Cannot create binding for parameter reference [" + parameter + "] - reference is not a parameter of this query"
);
}
+ //TODO: typecheck!
//noinspection unchecked
return (QueryParameterBinding
) binding;
}
@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 a82b490e1e55..1899d1592328 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(), getSession() ) ) {
+ 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(), getSession() ) ) {
+ throw new QueryArgumentException(
+ "Argument to parameter at position " + position + " has an incompatible type",
+ parameterJavaType, javaType, value );
+ }
+ }
+ @SuppressWarnings("unchecked") // safe, just checked
+ var castBinding = (QueryParameterBinding