Skip to content

Commit aeac3ec

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

File tree

2 files changed

+71
-14
lines changed

2 files changed

+71
-14
lines changed

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

Lines changed: 42 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;
@@ -330,25 +331,51 @@ private void handleExplicitResultSetMapping() {
330331
setTupleTransformerForResultType( resultType );
331332
}
332333
else {
333-
checkResultType( resultType );
334+
checkResultType( resultType, resultSetMapping );
334335
}
335336
}
336337
}
337338

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

354381
protected <T> void setTupleTransformerForResultType(Class<T> resultClass) {
@@ -740,6 +767,7 @@ else if ( !isResultTypeAlwaysAllowed( resultType )
740767
else {
741768
mapping = resultSetMapping;
742769
}
770+
checkResultType( resultType, mapping );
743771
return isCacheableQuery()
744772
? getInterpretationCache()
745773
.resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) )

hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import org.hibernate.query.NativeQuery;
3939
import org.hibernate.query.Query;
4040
import org.hibernate.query.ResultListTransformer;
41+
import org.hibernate.testing.orm.junit.Jira;
42+
import org.hibernate.testing.orm.junit.JiraGroup;
4143
import org.hibernate.transform.ResultTransformer;
4244
import org.hibernate.transform.Transformers;
4345
import org.hibernate.type.StandardBasicTypes;
@@ -59,6 +61,7 @@
5961
import static org.junit.jupiter.api.Assertions.assertEquals;
6062
import static org.junit.jupiter.api.Assertions.assertFalse;
6163
import static org.junit.jupiter.api.Assertions.assertNotNull;
64+
import static org.junit.jupiter.api.Assertions.assertThrows;
6265
import static org.junit.jupiter.api.Assertions.assertTrue;
6366
import static org.junit.jupiter.api.Assertions.fail;
6467

@@ -938,6 +941,32 @@ public void testAliasToBeanMap(SessionFactoryScope scope) {
938941
);
939942
}
940943

944+
@Test
945+
@JiraGroup(
946+
{
947+
@Jira("https://hibernate.atlassian.net/browse/HHH-19376"),
948+
@Jira("https://hibernate.atlassian.net/browse/HHH-19383")
949+
}
950+
)
951+
public void testMutateResultSetMapping(SessionFactoryScope scope) {
952+
scope.inTransaction(
953+
session -> {
954+
String sql = "SELECT p.*, COUNT(*) OVER() AS total_count "
955+
+ "FROM Person p "
956+
+ "WHERE p.name ILIKE :name "
957+
+ "ORDER BY p.id";
958+
// Declare Person as result type
959+
NativeQuery<Person> query = session.createNativeQuery(sql, Person.class);
960+
query.setParameter("name", "Ja%");
961+
query.setMaxResults(2);
962+
query.setFirstResult(0);
963+
// Now mutate the result set mapping and verify an Exception is thrown
964+
assertThrows( IllegalArgumentException.class,
965+
() -> query.addScalar( "total_count", StandardBasicTypes.LONG).list() );
966+
}
967+
);
968+
}
969+
941970
private String buildLongString(int size, char baseChar) {
942971
StringBuilder buff = new StringBuilder();
943972
for( int i = 0; i < size; i++ ) {

0 commit comments

Comments
 (0)