diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java index 812b87e1f2..29ef48f7cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java @@ -86,6 +86,10 @@ import static org.hibernate.jpa.QueryHints.HINT_READONLY; public abstract class AbstractSelectionQuery extends AbstractCommonQueryContract implements SelectionQuery, DomainQueryExecutionContext { + /** + * The value used for {@link #getQueryString} for Criteria-based queries + */ + public static final String CRITERIA_HQL_STRING = ""; private Callback callback; @@ -189,10 +193,17 @@ public abstract class AbstractSelectionQuery } } + protected abstract String getQueryString(); + + /** + * Used during handling of Criteria queries + */ protected void visitQueryReturnType( SqmQueryPart queryPart, Class resultType, SessionFactoryImplementor factory) { + assert getQueryString().equals( CRITERIA_HQL_STRING ); + if ( queryPart instanceof SqmQuerySpec ) { final SqmQuerySpec sqmQuerySpec = (SqmQuerySpec) queryPart; final List> sqmSelections = sqmQuerySpec.getSelectClause().getSelections(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 064cc1b15d..d881f05168 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -119,11 +119,6 @@ import static org.hibernate.query.sqm.internal.SqmUtil.isSelect; public class QuerySqmImpl extends AbstractSelectionQuery implements SqmQueryImplementor, InterpretationsKeySource, DomainQueryExecutionContext { - - /** - * The value used for {@link #getQueryString} for Criteria-based queries - */ - public static final String CRITERIA_HQL_STRING = ""; private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QuerySqmImpl.class ); private final String hql; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java index a0c9664e72..3d7be6a7f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java @@ -18,6 +18,7 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.Tuple; import org.hibernate.CacheMode; import org.hibernate.FlushMode; @@ -78,8 +79,6 @@ import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOption * @author Steve Ebersole */ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implements SqmSelectionQuery, InterpretationsKeySource { - public static final String CRITERIA_HQL_STRING = ""; - private final String hql; private final SqmSelectStatement sqm; @@ -105,14 +104,19 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen this.domainParameterXref = hqlInterpretation.getDomainParameterXref(); this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); - visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() ); +// visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() ); this.resultType = determineResultType( sqm, expectedResultType ); setComment( hql ); - this.tupleMetadata = null; + this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType ); } private static Class determineResultType(SqmSelectStatement sqm, Class expectedResultType) { + if ( expectedResultType != null && expectedResultType.equals( Tuple.class ) ) { + //noinspection unchecked + return (Class) Tuple.class; + } + if ( expectedResultType == null || ! expectedResultType.isArray() ) { final List> selections = sqm.getQuerySpec().getSelectClause().getSelections(); if ( selections.size() == 1 ) { @@ -152,10 +156,9 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); - visitQueryReturnType( sqm.getQueryPart(), resultType, getSessionFactory() ); setComment( hql ); - applyOptions( memento ); + this.tupleMetadata = buildTupleMetadata( sqm, resultType ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/BasicSelectionQueryTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/BasicSelectionQueryTests.java index 4a9956106e..6113a9ff36 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/BasicSelectionQueryTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/BasicSelectionQueryTests.java @@ -6,11 +6,13 @@ */ package org.hibernate.orm.test.query.sqm; +import java.util.List; import jakarta.persistence.Basic; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; +import jakarta.persistence.Tuple; import org.hibernate.ScrollMode; import org.hibernate.engine.spi.SessionImplementor; @@ -22,8 +24,10 @@ import org.hibernate.testing.orm.domain.contacts.Contact; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; /** @@ -63,6 +67,54 @@ public class BasicSelectionQueryTests { } ); } + private void createDummyEntity(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new DummyEntity( 1, "whatever" ) ); + } ); + } + + private final String tuple_selection_hql = "select c.id as id, c.name as name from DummyEntity c"; + + @Test + public void tupleSelectionTestBaseline(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + checkResults( session.createQuery( tuple_selection_hql, Tuple.class ), session ); + } ); + } + + @Test + public void tupleSelectionTest(SessionFactoryScope scope) { + createDummyEntity( scope ); + + // first make sure we get back the correct results via list + scope.inTransaction( (session) -> { + final SelectionQuery selectionQuery = session.createSelectionQuery( tuple_selection_hql, Tuple.class ); + + final List tuples = selectionQuery.list(); + assertThat( tuples ).hasSize( 1 ); + + assertThat( tuples.get( 0 ) ).isInstanceOf( Tuple.class ); + final Tuple tuple = tuples.get( 0 ); + assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); + assertThat( tuple.get( "id" ) ).isEqualTo( 1 ); + assertThat( tuple.get( 1 ) ).isEqualTo( "whatever" ); + assertThat( tuple.get( "name" ) ).isEqualTo( "whatever" ); + } ); + + // next make sure the rest of the execution methods work + scope.inTransaction( (session) -> { + final SelectionQuery selectionQuery = session.createSelectionQuery( tuple_selection_hql, Tuple.class ); + checkResults( selectionQuery, session ); + } ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createMutationQuery( "delete DummyEntity" ).executeUpdate(); + } ); + } + @Test public void rawScalarSelectTest(SessionFactoryScope scope) { scope.inTransaction( (session) -> {