Skip to content

Commit 3f047b6

Browse files
committed
HHH-19383 - validation of NativeQuery result mappings
Signed-off-by: Jan Schatteman <[email protected]>
1 parent d6c292e commit 3f047b6

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;
@@ -125,6 +126,7 @@
125126
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
126127
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
127128
import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy;
129+
import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType;
128130
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
129131
import static org.hibernate.query.results.internal.Builders.resultClassBuilder;
130132
import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping;
@@ -330,25 +332,54 @@ private void handleExplicitResultSetMapping() {
330332
setTupleTransformerForResultType( resultType );
331333
}
332334
else {
333-
checkResultType( resultType );
335+
checkResultType( resultType, resultSetMapping );
334336
}
335337
}
336338
}
337339

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

354385
protected <T> void setTupleTransformerForResultType(Class<T> resultClass) {
@@ -740,6 +771,7 @@ else if ( !isResultTypeAlwaysAllowed( resultType )
740771
else {
741772
mapping = resultSetMapping;
742773
}
774+
checkResultType( resultType, mapping );
743775
return isCacheableQuery()
744776
? getInterpretationCache()
745777
.resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) )

0 commit comments

Comments
 (0)