Skip to content

Commit a9461d1

Browse files
jrenaatgavinking
authored andcommitted
HHH-19383 - validation of NativeQuery result mappings
Signed-off-by: Jan Schatteman <[email protected]>
1 parent 109a4ed commit a9461d1

File tree

1 file changed

+46
-14
lines changed

1 file changed

+46
-14
lines changed

hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.query.sql.internal;
66

77
import java.io.Serializable;
8+
import java.lang.reflect.Constructor;
89
import java.time.Instant;
910
import java.util.Calendar;
1011
import java.util.Collection;
@@ -126,6 +127,7 @@
126127
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
127128
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
128129
import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy;
130+
import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType;
129131
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
130132
import static org.hibernate.query.results.internal.Builders.resultClassBuilder;
131133
import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping;
@@ -331,25 +333,54 @@ private void handleExplicitResultSetMapping() {
331333
setTupleTransformerForResultType( resultType );
332334
}
333335
else {
334-
checkResultType( resultType );
336+
checkResultType( resultType, resultSetMapping );
335337
}
336338
}
337339
}
338340

339-
private void checkResultType(Class<R> resultType) {
340-
switch ( resultSetMapping.getNumberOfResultBuilders() ) {
341-
case 0:
342-
throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" );
343-
case 1:
344-
final Class<?> actualResultJavaType =
345-
resultSetMapping.getResultBuilders().get( 0 ).getJavaType();
346-
if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) {
347-
throw buildIncompatibleException( resultType, actualResultJavaType );
348-
}
349-
break;
350-
default:
351-
throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" );
341+
private void checkResultType(Class<R> resultType, ResultSetMapping resultSetMapping) {
342+
// resultType can be null if any of the deprecated methods were used to create the query
343+
if ( resultType != null && !isResultTypeAlwaysAllowed( resultType )) {
344+
switch ( resultSetMapping.getNumberOfResultBuilders() ) {
345+
case 0:
346+
if ( !resultSetMapping.isDynamic() ) {
347+
throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" );
348+
}
349+
break;
350+
case 1:
351+
final Class<?> actualResultJavaType =
352+
resultSetMapping.getResultBuilders().get( 0 ).getJavaType();
353+
if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) {
354+
throw buildIncompatibleException( resultType, actualResultJavaType );
355+
}
356+
break;
357+
default:
358+
// The return type has to be a class with an appropriate constructor, i.e. one whose parameter types match
359+
// the types of the result builders. If none such constructor is found, throw an IAE
360+
if ( !validConstructorFoundForResultType( resultType, resultSetMapping ) ) {
361+
throw new IllegalArgumentException( "The declared return type for a multi-valued result set mapping should be Object[], Map, List, or Tuple" );
362+
}
363+
}
364+
}
365+
}
366+
367+
private boolean validConstructorFoundForResultType(Class<R> resultType, ResultSetMapping resultSetMapping) {
368+
// Only 1 constructor with the right number of parameters is allowed (see NativeQueryConstructorTransformer)
369+
Constructor<?> constructor = resultType.getConstructors()[0];
370+
if ( constructor.getParameterCount() != resultSetMapping.getNumberOfResultBuilders() ) {
371+
return false;
372+
}
373+
final List<ResultBuilder> resultBuilders = resultSetMapping.getResultBuilders();
374+
Class<?>[] paramTypes = constructor.getParameterTypes();
375+
for ( int i = 0; i < resultBuilders.size(); i++ ) {
376+
if (
377+
resultBuilders.get( i ).getJavaType() != ( paramTypes[i].isPrimitive() ?
378+
getDescriptorByPrimitiveType(paramTypes[i] ).getWrapperClass() :
379+
paramTypes[i]) ) {
380+
return false;
381+
}
352382
}
383+
return true;
353384
}
354385

355386
protected <T> void setTupleTransformerForResultType(Class<T> resultClass) {
@@ -747,6 +778,7 @@ else if ( !isResultTypeAlwaysAllowed( resultType )
747778
else {
748779
mapping = resultSetMapping;
749780
}
781+
checkResultType( resultType, mapping );
750782
return isCacheableQuery()
751783
? getInterpretationCache()
752784
.resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) )

0 commit comments

Comments
 (0)