From f8d84f9f7f5c28bbe7d1e86863deb44b2c787c9f Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 10 Jan 2024 10:45:56 +0100 Subject: [PATCH] HHH-17598 Allow array typed queries without result wrapping --- .../internal/ConcreteSqmSelectQueryPlan.java | 71 ++++++++++--------- .../ConvertedListAttributeQueryTest.java | 18 ++++- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index 0dcc541a9c..70b11fe518 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -195,18 +195,18 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { return new MySqmJdbcExecutionContextAdapter( executionContext, jdbcSelect, subSelectFetchKeyHandler, hql ); } - private static Class singleSelectionType(SqmSelectStatement sqm) { + private static SqmSelection singleSelection(SqmSelectStatement sqm) { final List> selections = sqm.getQueryPart() .getFirstQuerySpec() .getSelectClause() .getSelections(); - if ( selections.size() == 1 ) { - final SqmSelection sqmSelection = selections.get( 0 ); - return sqmSelection.getSelectableNode().isCompoundSelection() ? - null : - sqmSelection.getNodeJavaType().getJavaTypeClass(); - } - return null; + return selections.size() == 1 ? selections.get( 0 ) : null; + } + + private static Class selectionType(SqmSelection selection) { + return selection != null && !selection.getSelectableNode().isCompoundSelection() ? + selection.getNodeJavaType().getJavaTypeClass() + : null; } @SuppressWarnings("unchecked") @@ -221,37 +221,40 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { else if ( resultType == null ) { return RowTransformerStandardImpl.instance(); } - else if ( resultType == Object[].class ) { - return (RowTransformer) RowTransformerArrayImpl.instance(); - } - else if ( resultType == List.class && resultType != singleSelectionType( sqm ) ) { - return (RowTransformer) RowTransformerListImpl.instance(); - } else { - // NOTE : if we get here : - // 1) there is no TupleTransformer specified - // 2) an explicit result-type, other than an array or List, was specified - - if ( tupleMetadata == null ) { - if ( sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections().size() == 1 ) { - return RowTransformerSingularReturnImpl.instance(); - } - else { - throw new AssertionFailure( "Query defined multiple selections, should have had TupleMetadata" ); - } + final SqmSelection selection = singleSelection( sqm ); + if ( resultType.isArray() && resultType != selectionType( selection ) ) { + return (RowTransformer) RowTransformerArrayImpl.instance(); + } + else if ( resultType == List.class && resultType != selectionType( selection ) ) { + return (RowTransformer) RowTransformerListImpl.instance(); } else { - if ( Tuple.class.equals( resultType ) ) { - return (RowTransformer) new RowTransformerJpaTupleImpl( tupleMetadata ); - } - else if ( Map.class.equals( resultType ) ) { - return (RowTransformer) new RowTransformerMapImpl( tupleMetadata ); - } - else if ( isClass( resultType ) ) { - return new RowTransformerConstructorImpl<>( resultType, tupleMetadata ); + // NOTE : if we get here : + // 1) there is no TupleTransformer specified + // 2) an explicit result-type, other than an array or List, was specified + + if ( tupleMetadata == null ) { + if ( selection != null ) { + return RowTransformerSingularReturnImpl.instance(); + } + else { + throw new AssertionFailure( "Query defined multiple selections, should have had TupleMetadata" ); + } } else { - throw new InstantiationException( "Query result type is not instantiable", resultType ); + if ( Tuple.class.equals( resultType ) ) { + return (RowTransformer) new RowTransformerJpaTupleImpl( tupleMetadata ); + } + else if ( Map.class.equals( resultType ) ) { + return (RowTransformer) new RowTransformerMapImpl( tupleMetadata ); + } + else if ( isClass( resultType ) ) { + return new RowTransformerConstructorImpl<>( resultType, tupleMetadata ); + } + else { + throw new InstantiationException( "Query result type is not instantiable", resultType ); + } } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/ConvertedListAttributeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/ConvertedListAttributeQueryTest.java index 6fc1333adf..41669c74de 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/ConvertedListAttributeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/ConvertedListAttributeQueryTest.java @@ -47,7 +47,7 @@ public class ConvertedListAttributeQueryTest { @Test @SuppressWarnings( "rawtypes" ) - public void testHQL(SessionFactoryScope scope) { + public void testListHQL(SessionFactoryScope scope) { scope.inTransaction( session -> { final List resultList = session.createQuery( "select emp.phoneNumbers from Employee emp where emp.id = :EMP_ID", @@ -59,7 +59,7 @@ public class ConvertedListAttributeQueryTest { @Test @SuppressWarnings( "rawtypes" ) - public void testCriteria(SessionFactoryScope scope) { + public void testListCriteria(SessionFactoryScope scope) { scope.inTransaction( session -> { final CriteriaBuilder cb = session.getCriteriaBuilder(); final CriteriaQuery q = cb.createQuery( List.class ); @@ -71,6 +71,20 @@ public class ConvertedListAttributeQueryTest { } ); } + @Test + public void testArrayCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery q = cb.createQuery( Integer[].class ); + final Root r = q.from( Employee.class ); + q.multiselect( r.get( "id" ), r.get( "id" ) ); + q.where( cb.equal( r.get( "id" ), 1 ) ); + final Object result = session.createQuery( q ).getSingleResult(); + assertThat( result ).isInstanceOf( Object[].class ); + assertThat( ( (Object[]) result ) ).containsExactly( 1, 1 ); + } ); + } + @Entity( name = "Employee" ) public static class Employee { @Id