Skip to content

Commit 1c548c0

Browse files
committed
introduce QueryArguments
allow sensible conversions on query parameter arguments as defined by what our JavaTypes already know how to convert
1 parent 043ee48 commit 1c548c0

File tree

12 files changed

+270
-270
lines changed

12 files changed

+270
-270
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.internal;
6+
7+
import jakarta.persistence.metamodel.Type;
8+
import org.hibernate.HibernateException;
9+
import org.hibernate.type.BindingContext;
10+
import org.hibernate.type.descriptor.java.JavaType;
11+
import org.hibernate.type.descriptor.java.spi.EntityJavaType;
12+
13+
import java.util.Collection;
14+
15+
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
16+
17+
/**
18+
* @since 7.3
19+
*
20+
* @author Gavin King
21+
*/
22+
public class QueryArguments {
23+
24+
private static boolean isInstance(Object value, JavaType<?> javaType) {
25+
try {
26+
// special handling for entity arguments due to
27+
// the possibility of an uninitialized proxy
28+
// (which we don't want or need to fetch)
29+
if ( javaType instanceof EntityJavaType<?> ) {
30+
final var javaTypeClass = javaType.getJavaTypeClass();
31+
final var initializer = extractLazyInitializer( value );
32+
final var valueEntityClass =
33+
initializer != null
34+
? initializer.getPersistentClass()
35+
: value.getClass();
36+
// accept assignability in either direction
37+
return javaTypeClass.isAssignableFrom( valueEntityClass )
38+
|| valueEntityClass.isAssignableFrom( javaTypeClass );
39+
}
40+
else {
41+
// require that the argument be assignable to the parameter
42+
return javaType.isInstance( javaType.coerce( value ) );
43+
}
44+
}
45+
catch (HibernateException ce) {
46+
return false;
47+
}
48+
}
49+
50+
public static boolean isInstance(
51+
Type<?> parameterType, Object value,
52+
BindingContext bindingContext) {
53+
if ( value == null ) {
54+
return true;
55+
}
56+
final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
57+
assert sqmExpressible != null;
58+
final var javaType = sqmExpressible.getExpressibleJavaType();
59+
return isInstance( value, javaType );
60+
}
61+
62+
public static boolean areInstances(
63+
Type<?> parameterType, Collection<?> values,
64+
BindingContext bindingContext) {
65+
if ( values.isEmpty() ) {
66+
return true;
67+
}
68+
final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
69+
assert sqmExpressible != null;
70+
final var javaType = sqmExpressible.getExpressibleJavaType();
71+
for ( Object value : values ) {
72+
if ( !isInstance( value, javaType ) ) {
73+
return false;
74+
}
75+
}
76+
return true;
77+
}
78+
79+
public static boolean areInstances(
80+
Type<?> parameterType, Object[] values,
81+
BindingContext bindingContext) {
82+
if ( values.length == 0 ) {
83+
return true;
84+
}
85+
if ( parameterType.getJavaType()
86+
.isAssignableFrom( values.getClass().getComponentType() ) ) {
87+
return true;
88+
}
89+
final var sqmExpressible = bindingContext.resolveExpressible( parameterType );
90+
assert sqmExpressible != null;
91+
final var javaType = sqmExpressible.getExpressibleJavaType();
92+
for ( Object value : values ) {
93+
if ( !isInstance( value, javaType ) ) {
94+
return false;
95+
}
96+
}
97+
return true;
98+
}
99+
}

hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
import org.hibernate.query.QueryParameter;
1717
import org.hibernate.query.spi.QueryParameterBinding;
1818
import org.hibernate.query.spi.QueryParameterBindingTypeResolver;
19-
import org.hibernate.query.spi.QueryParameterBindingValidator;
2019
import org.hibernate.query.sqm.NodeBuilder;
2120
import org.hibernate.type.descriptor.java.JavaType;
2221
import org.hibernate.type.spi.TypeConfiguration;
2322

2423
import jakarta.persistence.TemporalType;
2524

25+
import static org.hibernate.query.spi.QueryParameterBindingValidator.validate;
2626
import static org.hibernate.type.descriptor.java.JavaTypeHelper.isTemporal;
2727
import static org.hibernate.type.internal.BindingTypeHelper.resolveTemporalPrecision;
2828

@@ -116,7 +116,7 @@ public T getBindValue() {
116116
public void setBindValue(T value, boolean resolveJdbcTypeIfNecessary) {
117117
if ( !handleAsMultiValue( value, null ) ) {
118118
final Object coerced = coerceIfNotJpa( value );
119-
validate( coerced );
119+
validate( getBindType(), coerced, sessionFactory );
120120

121121
if ( value == null ) {
122122
// 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<T> clarifiedType) {
174174
}
175175

176176
final Object coerced = coerce( value );
177-
validate( coerced, clarifiedType );
177+
validate( clarifiedType, coerced, sessionFactory );
178178
bindValue( coerced );
179179
}
180180
}
@@ -187,7 +187,7 @@ public void setBindValue(T value, TemporalType temporalTypePrecision) {
187187
}
188188

189189
final Object coerced = coerceIfNotJpa( value );
190-
validate( coerced, temporalTypePrecision );
190+
validate( getBindType(), coerced, sessionFactory );
191191
bindValue( coerced );
192192
setExplicitTemporalPrecision( temporalTypePrecision );
193193
}
@@ -218,15 +218,19 @@ public void setBindValues(Collection<? extends T> values) {
218218
this.bindValue = null;
219219
this.bindValues = values;
220220

221+
final T value = firstNonNull( values );
222+
if ( canBindValueBeSet( value, bindType ) ) {
223+
bindType = getParameterBindingTypeResolver().resolveParameterBindType( value );
224+
}
225+
}
226+
227+
private static <T> @Nullable T firstNonNull(Collection<? extends T> values) {
221228
final var iterator = values.iterator();
222229
T value = null;
223230
while ( value == null && iterator.hasNext() ) {
224231
value = iterator.next();
225232
}
226-
227-
if ( canBindValueBeSet( value, bindType ) ) {
228-
bindType = getParameterBindingTypeResolver().resolveParameterBindType( value );
229-
}
233+
return value;
230234
}
231235

232236
@Override
@@ -284,18 +288,6 @@ else if ( type instanceof BasicValuedMapping basicValuedMapping ) {
284288
return false;
285289
}
286290

287-
private void validate(Object value) {
288-
QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, getCriteriaBuilder() );
289-
}
290-
291-
private void validate(Object value, BindableType<?> clarifiedType) {
292-
QueryParameterBindingValidator.INSTANCE.validate( clarifiedType, value, getCriteriaBuilder() );
293-
}
294-
295-
private void validate(Object value, TemporalType clarifiedTemporalType) {
296-
QueryParameterBindingValidator.INSTANCE.validate( getBindType(), value, clarifiedTemporalType, getCriteriaBuilder() );
297-
}
298-
299291
public TypeConfiguration getTypeConfiguration() {
300292
return sessionFactory.getTypeConfiguration();
301293
}

hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ private QueryParameterBindingsImpl(
6868
this.parameterBindingMap = linkedMapOfSize( queryParameters.size() );
6969
this.parameterBindingMapByNameOrPosition = mapOfSize( queryParameters.size() );
7070
for ( var queryParameter : queryParameters ) {
71-
parameterBindingMap.put( queryParameter, createBinding( sessionFactory, parameterMetadata, queryParameter ) );
71+
parameterBindingMap.put( queryParameter,
72+
createBinding( sessionFactory, parameterMetadata, queryParameter ) );
7273
}
7374
for ( var entry : parameterBindingMap.entrySet() ) {
7475
final var queryParameter = entry.getKey();
@@ -129,9 +130,12 @@ public <P> QueryParameterBinding<P> getBinding(QueryParameterImplementor<P> para
129130
"Cannot create binding for parameter reference [" + parameter + "] - reference is not a parameter of this query"
130131
);
131132
}
132-
//TODO: typecheck!
133-
//noinspection unchecked
134-
return (QueryParameterBinding<P>) binding;
133+
if ( !binding.getQueryParameter().equals( parameter ) ) {
134+
throw new IllegalStateException("Parameter binding corrupted for: " + parameter.getName() );
135+
}
136+
@SuppressWarnings("unchecked") // safe because we checked the parameter
137+
final var castBinding = (QueryParameterBinding<P>) binding;
138+
return castBinding;
135139
}
136140

137141
@Override

hibernate-core/src/main/java/org/hibernate/query/spi/AbstractCommonQueryContract.java

Lines changed: 11 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989
import static org.hibernate.jpa.internal.util.ConfigurationHelper.getCacheMode;
9090
import static org.hibernate.jpa.internal.util.ConfigurationHelper.getInteger;
9191
import static org.hibernate.query.QueryLogging.QUERY_MESSAGE_LOGGER;
92+
import static org.hibernate.query.internal.QueryArguments.areInstances;
93+
import static org.hibernate.query.internal.QueryArguments.isInstance;
9294

9395
/**
9496
* Base implementation of {@link CommonQueryContract}.
@@ -755,7 +757,7 @@ protected <P> QueryParameterBinding<P> locateBinding(String name, Class<P> javaT
755757
if ( parameterType != null ) {
756758
final var parameterJavaType = parameterType.getJavaType();
757759
if ( !parameterJavaType.isAssignableFrom( javaType )
758-
&& !isInstance( parameterType, value ) ) {
760+
&& !isInstance( parameterType, value, getNodeBuilder() ) ) {
759761
throw new QueryArgumentException(
760762
"Argument to parameter named '" + name + "' has an incompatible type",
761763
parameterJavaType, javaType, value );
@@ -773,7 +775,7 @@ protected <P> QueryParameterBinding<P> locateBinding(int position, Class<P> java
773775
if ( parameterType != null ) {
774776
final var parameterJavaType = parameterType.getJavaType();
775777
if ( !parameterJavaType.isAssignableFrom( javaType )
776-
&& !isInstance( parameterType, value ) ) {
778+
&& !isInstance( parameterType, value, getNodeBuilder() ) ) {
777779
throw new QueryArgumentException(
778780
"Argument to parameter at position " + position + " has an incompatible type",
779781
parameterJavaType, javaType, value );
@@ -791,7 +793,7 @@ protected <P> QueryParameterBinding<P> locateBinding(String name, Class<P> javaT
791793
if ( parameterType != null ) {
792794
final var parameterJavaType = parameterType.getJavaType();
793795
if ( !parameterJavaType.isAssignableFrom( javaType )
794-
&& !areInstances( parameterType, values ) ) {
796+
&& !areInstances( parameterType, values, getNodeBuilder() ) ) {
795797
throw new QueryArgumentException(
796798
"Argument to parameter named '" + name + "' has an incompatible type",
797799
parameterJavaType, javaType, values );
@@ -809,7 +811,7 @@ protected <P> QueryParameterBinding<P> locateBinding(int position, Class<P> java
809811
if ( parameterType != null ) {
810812
final var parameterJavaType = parameterType.getJavaType();
811813
if ( !parameterJavaType.isAssignableFrom( javaType )
812-
&& !areInstances( parameterType, values ) ) {
814+
&& !areInstances( parameterType, values, getNodeBuilder() ) ) {
813815
throw new QueryArgumentException(
814816
"Argument to parameter at position " + position + " has an incompatible type",
815817
parameterJavaType, javaType, values );
@@ -886,8 +888,8 @@ private <P> void setParameterValue(Object value, QueryParameterBinding<P> bindin
886888
private <P> boolean isInstanceOrAreInstances(
887889
Object value, QueryParameterBinding<P> binding, BindableType<? super P> parameterType) {
888890
return binding.isMultiValued() && value instanceof Collection<?> values
889-
? areInstances( parameterType, values )
890-
: isInstance( parameterType, value );
891+
? areInstances( parameterType, values, getNodeBuilder() )
892+
: isInstance( parameterType, value, getNodeBuilder() );
891893
}
892894

893895
@Override
@@ -916,8 +918,8 @@ private boolean multipleBinding(QueryParameter<?> parameter, Object value){
916918
final var hibernateType = parameter.getHibernateType();
917919
return hibernateType == null
918920
|| values.isEmpty()
919-
|| !isInstance( hibernateType, value )
920-
|| isInstance( hibernateType, values.iterator().next() );
921+
|| !isInstance( hibernateType, value, getNodeBuilder() )
922+
|| isInstance( hibernateType, values.iterator().next(), getNodeBuilder() );
921923
}
922924
else {
923925
return false;
@@ -932,67 +934,6 @@ private <T> void setTypedParameter(int position, TypedParameterValue<T> typedVal
932934
setParameter( position, typedValue.value(), typedValue.type() );
933935
}
934936

935-
private boolean isInstance(Type<?> parameterType, Object value) {
936-
if ( value == null ) {
937-
return true;
938-
}
939-
final var sqmExpressible = getNodeBuilder().resolveExpressible( parameterType );
940-
assert sqmExpressible != null;
941-
final var javaType = sqmExpressible.getExpressibleJavaType();
942-
if ( !javaType.isInstance( value ) ) {
943-
try {
944-
// if this succeeds, we are good
945-
javaType.wrap( value, session );
946-
}
947-
catch ( HibernateException|UnsupportedOperationException e) {
948-
return false;
949-
}
950-
}
951-
return true;
952-
}
953-
954-
private boolean areInstances(Type<?> parameterType, Collection<?> values) {
955-
if ( values.isEmpty() ) {
956-
return true;
957-
}
958-
final var sqmExpressible = getNodeBuilder().resolveExpressible( parameterType );
959-
assert sqmExpressible != null;
960-
final var javaType = sqmExpressible.getExpressibleJavaType();
961-
for ( Object value : values ) {
962-
if ( !javaType.isInstance( value ) ) {
963-
try {
964-
// if this succeeds, we are good
965-
javaType.wrap( value, session );
966-
}
967-
catch (HibernateException | UnsupportedOperationException e) {
968-
return false;
969-
}
970-
}
971-
}
972-
return true;
973-
}
974-
975-
private boolean areInstances(Type<?> parameterType, Object[] values) {
976-
if ( values.length == 0 ) {
977-
return true;
978-
}
979-
final var sqmExpressible = getNodeBuilder().resolveExpressible( parameterType );
980-
assert sqmExpressible != null;
981-
final var javaType = sqmExpressible.getExpressibleJavaType();
982-
for ( Object value : values ) {
983-
if ( !javaType.isInstance( value ) ) {
984-
try {
985-
// if this succeeds, we are good
986-
javaType.wrap( value, session );
987-
}
988-
catch (HibernateException | UnsupportedOperationException e) {
989-
return false;
990-
}
991-
}
992-
}
993-
return true;
994-
}
995-
996937
private NodeBuilder getNodeBuilder() {
997938
return getSessionFactory().getQueryEngine().getCriteriaBuilder();
998939
}
@@ -1196,7 +1137,7 @@ public CommonQueryContract setParameterList(String name, Object[] values) {
11961137
private <P> void setParameterValues(Object[] values, QueryParameterBinding<P> binding) {
11971138
final var parameterType = binding.getBindType();
11981139
if ( parameterType != null
1199-
&& !areInstances( values, parameterType ) ) {
1140+
&& !areInstances( parameterType, values, getNodeBuilder() ) ) {
12001141
throw new QueryArgumentException( "Argument to query parameter has an incompatible type",
12011142
parameterType.getJavaType(), values.getClass().getComponentType(), values );
12021143
}
@@ -1205,11 +1146,6 @@ private <P> void setParameterValues(Object[] values, QueryParameterBinding<P> bi
12051146
binding.setBindValues( List.of( castArray ) );
12061147
}
12071148

1208-
private <P> boolean areInstances(Object[] values, BindableType<? super P> parameterType) {
1209-
return parameterType.getJavaType().isAssignableFrom( values.getClass().getComponentType() )
1210-
|| areInstances( parameterType, values );
1211-
}
1212-
12131149
@Override
12141150
public <P> CommonQueryContract setParameterList(String name, P[] values, Class<P> javaType) {
12151151
final var javaDescriptor = getJavaType( javaType );

0 commit comments

Comments
 (0)