HHH-18306 - Implicit instantiation for queries with single selection item broken

HHH-18401 - SelectionQuery needs better validation of query return type
This commit is contained in:
Steve Ebersole 2024-07-18 15:23:40 -05:00
parent 3bf82e6d82
commit 39de0115f7
14 changed files with 742 additions and 95 deletions

View File

@ -828,12 +828,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
protected <R> HqlInterpretation<R> interpretHql(String hql, Class<R> resultType) { protected <R> HqlInterpretation<R> interpretHql(String hql, Class<R> resultType) {
final QueryEngine queryEngine = getFactory().getQueryEngine(); final QueryEngine queryEngine = getFactory().getQueryEngine();
return queryEngine.getInterpretationCache() return queryEngine.interpretHql( hql, resultType );
.resolveHqlInterpretation(
hql,
resultType,
queryEngine.getHqlTranslator()
);
} }
protected static void checkSelectionQuery(String hql, HqlInterpretation<?> hqlInterpretation) { protected static void checkSelectionQuery(String hql, HqlInterpretation<?> hqlInterpretation) {

View File

@ -407,17 +407,6 @@ public interface SelectionQuery<R> extends CommonQueryContract {
*/ */
SelectionQuery<R> setFirstResult(int startPosition); SelectionQuery<R> setFirstResult(int startPosition);
// /**
// * Set the page of results to return.
// *
// * @param pageNumber the page to return, where pages are numbered from zero
// * @param pageSize the number of results per page
// *
// * @since 6.3
// */
// @Incubating
// SelectionQuery<R> setPage(int pageSize, int pageNumber);
/** /**
* Set the {@linkplain Page page} of results to return. * Set the {@linkplain Page page} of results to return.
* *

View File

@ -19,19 +19,6 @@ import java.util.Spliterator;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import jakarta.persistence.CacheRetrieveMode;
import jakarta.persistence.CacheStoreMode;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType;
import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement;
import jakarta.persistence.criteria.CompoundSelection;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.CacheMode; import org.hibernate.CacheMode;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -48,6 +35,10 @@ import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.jpa.internal.util.LockModeTypeHelper; import org.hibernate.jpa.internal.util.LockModeTypeHelper;
import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.query.BindableType; import org.hibernate.query.BindableType;
import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.QueryParameter; import org.hibernate.query.QueryParameter;
@ -77,6 +68,19 @@ import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType; import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import jakarta.persistence.CacheRetrieveMode;
import jakarta.persistence.CacheStoreMode;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType;
import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement;
import jakarta.persistence.criteria.CompoundSelection;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Spliterators.spliteratorUnknownSize; import static java.util.Spliterators.spliteratorUnknownSize;
import static org.hibernate.CacheMode.fromJpaModes; import static org.hibernate.CacheMode.fromJpaModes;
import static org.hibernate.FlushMode.fromJpaFlushMode; import static org.hibernate.FlushMode.fromJpaFlushMode;
@ -84,12 +88,12 @@ import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_RETRIEVE_
import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_STORE_MODE; import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_STORE_MODE;
import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_RETRIEVE_MODE; import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_RETRIEVE_MODE;
import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_STORE_MODE; import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_STORE_MODE;
import static org.hibernate.jpa.QueryHints.HINT_CACHEABLE; import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
import static org.hibernate.jpa.QueryHints.HINT_CACHE_MODE; import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE;
import static org.hibernate.jpa.QueryHints.HINT_CACHE_REGION; import static org.hibernate.jpa.HibernateHints.HINT_CACHE_REGION;
import static org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE; import static org.hibernate.jpa.HibernateHints.HINT_FETCH_SIZE;
import static org.hibernate.jpa.QueryHints.HINT_FOLLOW_ON_LOCKING; import static org.hibernate.jpa.HibernateHints.HINT_FOLLOW_ON_LOCKING;
import static org.hibernate.jpa.QueryHints.HINT_READONLY; import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY;
import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple; import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple;
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType; import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;
@ -255,7 +259,8 @@ public abstract class AbstractSelectionQuery<R>
protected abstract String getQueryString(); protected abstract String getQueryString();
/** /**
* Used during handling of Criteria queries * Used to validate that the specified query return type is valid (i.e. the user
* did not pass {@code Integer.class} when the selection is an entity)
*/ */
protected void visitQueryReturnType( protected void visitQueryReturnType(
SqmQueryPart<R> queryPart, SqmQueryPart<R> queryPart,
@ -298,11 +303,13 @@ public abstract class AbstractSelectionQuery<R>
SqmQuerySpec<T> querySpec, SqmQuerySpec<T> querySpec,
Class<T> expectedResultClass, Class<T> expectedResultClass,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
if ( !isResultTypeAlwaysAllowed( expectedResultClass ) ) { if ( isResultTypeAlwaysAllowed( expectedResultClass ) ) {
// the result-class is always safe to use (Object, ...)
return;
}
final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections(); final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections();
if ( selections.size() == 1 ) { if ( selections.size() == 1 ) {
// we have one item in the select list,
// the type has to match (no instantiation)
final SqmSelection<?> sqmSelection = selections.get( 0 ); final SqmSelection<?> sqmSelection = selections.get( 0 );
final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode(); final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
if ( selectableNode.isCompoundSelection() ) { if ( selectableNode.isCompoundSelection() ) {
@ -314,7 +321,7 @@ public abstract class AbstractSelectionQuery<R>
} }
} }
else { else {
verifySelectionType( expectedResultClass, sessionFactory, sqmSelection.getSelectableNode() ); verifySingularSelectionType( expectedResultClass, sessionFactory, sqmSelection );
} }
} }
else if ( expectedResultClass.isArray() ) { else if ( expectedResultClass.isArray() ) {
@ -323,28 +330,50 @@ public abstract class AbstractSelectionQuery<R>
verifySelectionType( componentType, sessionFactory, selection.getSelectableNode() ); verifySelectionType( componentType, sessionFactory, selection.getSelectableNode() );
} }
} }
// else, let's assume we can instantiate it!
}
} }
private static <T> void verifySelectionType( /**
* Special case for a single, non-compound selection-item. It is essentially
* a special case of {@linkplain #verifySelectionType} which additionally
* handles the case where the type of the selection-item can be used to
* instantiate the result-class (result-class has a matching constructor).
*
* @apiNote We don't want to hoist this into {@linkplain #verifySelectionType}
* itself because this can only happen for the root non-compound case, and we
* want to avoid the try/catch otherwise
*/
private static <T> void verifySingularSelectionType(
Class<T> expectedResultClass, Class<T> expectedResultClass,
SessionFactoryImplementor sessionFactory, SessionFactoryImplementor sessionFactory,
SqmSelection<?> sqmSelection) { SqmSelection<?> sqmSelection) {
// special case for parameters in the select list final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
final SqmSelectableNode<?> selection = sqmSelection.getSelectableNode(); try {
if ( selection instanceof SqmParameter ) { verifySelectionType( expectedResultClass, sessionFactory, selectableNode );
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection; }
final SqmExpressible<?> nodeType = sqmParameter.getNodeType(); catch (QueryTypeMismatchException mismatchException) {
// we may not yet know a selection type // Check for special case of a single selection item and implicit instantiation.
if ( nodeType == null || nodeType.getExpressibleJavaType() == null ) { // See if the selected type can be used to instantiate the expected-type
// we can't verify the result type up front final JavaType<?> javaTypeDescriptor = selectableNode.getJavaTypeDescriptor();
return; if ( javaTypeDescriptor != null ) {
final Class<?> selectedJavaType = javaTypeDescriptor.getJavaTypeClass();
// ignore the exception if the expected type has a constructor accepting the selected item type
if ( hasMatchingConstructor( expectedResultClass, selectedJavaType ) ) {
// ignore it
}
else {
throw mismatchException;
}
}
} }
} }
if ( !sessionFactory.getSessionFactoryOptions().getJpaCompliance().isJpaQueryComplianceEnabled() ) { private static <T> boolean hasMatchingConstructor(Class<T> expectedResultClass, Class<?> selectedJavaType) {
verifyResultType( expectedResultClass, selection.getExpressible() ); try {
expectedResultClass.getDeclaredConstructor( selectedJavaType );
return true;
}
catch (NoSuchMethodException e) {
return false;
} }
} }
@ -384,24 +413,58 @@ public abstract class AbstractSelectionQuery<R>
|| expectedResultClass == Tuple.class; || expectedResultClass == Tuple.class;
} }
protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> sqmExpressible) { protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> selectionExpressible) {
if ( sqmExpressible != null ) { if ( selectionExpressible == null ) {
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType(); // nothing we can validate
assert expressibleJavaType != null; return;
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass(); }
if ( javaTypeClass != Object.class && !resultClass.isAssignableFrom( javaTypeClass ) ) {
if ( expressibleJavaType instanceof PrimitiveJavaType ) { final JavaType<?> selectionExpressibleJavaType = selectionExpressible.getExpressibleJavaType();
final PrimitiveJavaType<?> javaType = (PrimitiveJavaType<?>) expressibleJavaType; assert selectionExpressibleJavaType != null;
if ( javaType.getPrimitiveClass() != resultClass ) {
throwQueryTypeMismatchException( resultClass, sqmExpressible ); final Class<?> selectionExpressibleJavaTypeClass = selectionExpressibleJavaType.getJavaTypeClass();
if ( selectionExpressibleJavaTypeClass == Object.class ) {
}
if ( selectionExpressibleJavaTypeClass != Object.class ) {
// performs a series of opt-out checks for validity... each if branch and return indicates a valid case
if ( resultClass.isAssignableFrom( selectionExpressibleJavaTypeClass ) ) {
return;
}
if ( selectionExpressibleJavaType instanceof PrimitiveJavaType ) {
final PrimitiveJavaType<?> primitiveJavaType = (PrimitiveJavaType<?>) selectionExpressibleJavaType;
if ( primitiveJavaType.getPrimitiveClass() == resultClass ) {
return;
} }
} }
else if ( !isMatchingDateType( javaTypeClass, resultClass, sqmExpressible ) ) {
throwQueryTypeMismatchException( resultClass, sqmExpressible ); if ( isMatchingDateType( selectionExpressibleJavaTypeClass, resultClass, selectionExpressible ) ) {
return;
} }
// else special case, we are good
if ( isEntityIdType( selectionExpressible, resultClass ) ) {
return;
}
throwQueryTypeMismatchException( resultClass, selectionExpressible );
} }
} }
private static <T> boolean isEntityIdType(SqmExpressible<?> selectionExpressible, Class<T> resultClass) {
if ( selectionExpressible instanceof IdentifiableDomainType ) {
final IdentifiableDomainType<?> identifiableDomainType = (IdentifiableDomainType<?>) selectionExpressible;
final SimpleDomainType<?> idType = identifiableDomainType.getIdType();
return resultClass.isAssignableFrom( idType.getBindableJavaType() );
}
else if ( selectionExpressible instanceof EntitySqmPathSource ) {
final EntitySqmPathSource<?> entityPath = (EntitySqmPathSource<?>) selectionExpressible;
final EntityDomainType<?> entityType = entityPath.getSqmPathType();
final SimpleDomainType<?> idType = entityType.getIdType();
return resultClass.isAssignableFrom( idType.getBindableJavaType() );
}
return false;
} }
// Special case for date because we always report java.util.Date as expression type // Special case for date because we always report java.util.Date as expression type
@ -785,7 +848,7 @@ public abstract class AbstractSelectionQuery<R>
super.collectHints( hints ); super.collectHints( hints );
if ( isReadOnly() ) { if ( isReadOnly() ) {
hints.put( HINT_READONLY, true ); hints.put( HINT_READ_ONLY, true );
} }
putIfNotNull( hints, HINT_FETCH_SIZE, getFetchSize() ); putIfNotNull( hints, HINT_FETCH_SIZE, getFetchSize() );

View File

@ -49,5 +49,9 @@ public interface QueryEngine {
HqlTranslator getHqlTranslator(); HqlTranslator getHqlTranslator();
SqmTranslatorFactory getSqmTranslatorFactory(); SqmTranslatorFactory getSqmTranslatorFactory();
default <R> HqlInterpretation<R> interpretHql(String hql, Class<R> resultType) {
return getInterpretationCache().resolveHqlInterpretation( hql, resultType, getHqlTranslator() );
}
} }

View File

@ -107,11 +107,11 @@ public class SqmSelectionQueryImpl<R> extends AbstractSqmSelectionQuery<R>
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
this.expectedResultType = expectedResultType; this.expectedResultType = expectedResultType;
// visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() ); visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
this.resultType = determineResultType( sqm ); this.resultType = determineResultType( sqm );
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
setComment( hql ); setComment( hql );
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
} }
private Class<?> determineResultType(SqmSelectStatement<?> sqm) { private Class<?> determineResultType(SqmSelectStatement<?> sqm) {

View File

@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@DomainModel( annotatedClasses = SimpleEntity.class ) @DomainModel( annotatedClasses = {SimpleEntity.class, Dto.class, Dto2.class } )
@SessionFactory @SessionFactory
public class BasicCriteriaResultTests { public class BasicCriteriaResultTests {
@BeforeEach @BeforeEach

View File

@ -21,7 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@DomainModel( annotatedClasses = SimpleEntity.class ) @DomainModel( annotatedClasses = {SimpleEntity.class, Dto.class, Dto2.class } )
@SessionFactory @SessionFactory
public class BasicHqlResultTests { public class BasicHqlResultTests {
@BeforeEach @BeforeEach

View File

@ -0,0 +1,31 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.query.results;
import org.hibernate.annotations.Imported;
/**
* @author Steve Ebersole
*/
@Imported
public class Dto {
private final Integer key;
private final String text;
public Dto(Integer key, String text) {
this.key = key;
this.text = text;
}
public Integer getKey() {
return key;
}
public String getText() {
return text;
}
}

View File

@ -0,0 +1,25 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.query.results;
import org.hibernate.annotations.Imported;
/**
* @author Steve Ebersole
*/
@Imported
public class Dto2 {
private final String text;
public Dto2(String text) {
this.text = text;
}
public String getText() {
return text;
}
}

View File

@ -0,0 +1,140 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.query.results;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.Jira;
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.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@DomainModel(annotatedClasses = {SimpleEntity.class, SimpleComposite.class, Dto.class, Dto2.class})
@SessionFactory
@SuppressWarnings("JUnitMalformedDeclaration")
public class ImplicitInstantiationTests {
@BeforeEach
void prepareTestData(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
session.persist( new SimpleEntity( 1, "first", new SimpleComposite( "value1", "value2" ) ) );
} );
}
@AfterEach
public void dropTestData(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
session.createMutationQuery( "delete SimpleEntity" ).executeUpdate();
});
}
@Test
void testCreateQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final Dto rtn = session.createQuery( Queries.ID_NAME, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createQuery( Queries.ID_COMP_VAL, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
}
@Test
void testCreateSelectionQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final Dto rtn = session.createSelectionQuery( Queries.ID_NAME, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createSelectionQuery( Queries.ID_COMP_VAL, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
}
@Test
void testCriteria(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final HibernateCriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
final JpaCriteriaQuery<Dto> criteria = criteriaBuilder.createQuery( Dto.class );
final JpaRoot<SimpleEntity> root = criteria.from( SimpleEntity.class );
criteria.multiselect( root.get( "id" ), root.get( "name" ) );
final Dto rtn = session.createQuery( criteria ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-18306" )
void testCreateQuerySingleSelectItem(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final Dto2 rtn = session.createQuery( Queries.NAME, Dto2.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto2 rtn = session.createQuery( Queries.COMP_VAL, Dto2.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-18306" )
void testCreateSelectionQuerySingleSelectItem(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final Dto2 rtn = session.createSelectionQuery( Queries.NAME, Dto2.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto2 rtn = session.createSelectionQuery( Queries.COMP_VAL, Dto2.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-18306" )
void testCriteriaSingleSelectItem(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final HibernateCriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
final JpaCriteriaQuery<Dto2> criteria = criteriaBuilder.createQuery( Dto2.class );
final JpaRoot<SimpleEntity> root = criteria.from( SimpleEntity.class );
criteria.multiselect( root.get( "name" ) );
final Dto2 rtn = session.createQuery( criteria ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
}
}

View File

@ -0,0 +1,147 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.query.results;
import org.hibernate.query.QueryTypeMismatchException;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Steve Ebersole
*/
@Jira( "https://hibernate.atlassian.net/browse/HHH-18401" )
@DomainModel(annotatedClasses = {SimpleEntity.class, SimpleComposite.class, Dto.class, Dto2.class})
@SessionFactory
@SuppressWarnings("JUnitMalformedDeclaration")
public class InvalidReturnTests {
@Test
void testCreateQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
try {
session.createQuery( Queries.ENTITY, SimpleComposite.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createQuery( Queries.ENTITY, Dto.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createQuery( Queries.ENTITY_NO_SELECT, SimpleComposite.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createQuery( Queries.COMPOSITE, SimpleEntity.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createQuery( Queries.ID_NAME_DTO, SimpleEntity.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
} );
}
@Test
void testCreateSelectionQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
try {
session.createSelectionQuery( Queries.ENTITY, SimpleComposite.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createSelectionQuery( Queries.ENTITY_NO_SELECT, Dto.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createSelectionQuery( Queries.ENTITY_NO_SELECT, SimpleComposite.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createSelectionQuery( Queries.COMPOSITE, SimpleEntity.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createSelectionQuery( Queries.ID_NAME_DTO, SimpleEntity.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
} );
}
@Test
void testNamedQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
try {
session.createNamedQuery( Queries.NAMED_ENTITY, SimpleComposite.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createNamedQuery( Queries.NAMED_ENTITY_NO_SELECT, Dto.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createNamedQuery( Queries.NAMED_ENTITY_NO_SELECT, SimpleComposite.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createNamedQuery( Queries.NAMED_COMPOSITE, SimpleEntity.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
try {
session.createNamedQuery( Queries.NAMED_ID_NAME_DTO, SimpleEntity.class );
fail( "Expecting a QueryTypeMismatchException" );
}
catch (QueryTypeMismatchException expected) {
}
} );
}
}

View File

@ -0,0 +1,40 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.query.results;
/**
* @author Steve Ebersole
*/
public class Queries {
public static final String ENTITY = "select e from SimpleEntity e";
public static final String ENTITY_NO_SELECT = "from SimpleEntity e";
public static final String COMPOSITE = "select e.composite from SimpleEntity e";
public static final String NAME = "select e.name from SimpleEntity e";
public static final String COMP_VAL = "select e.composite.value1 from SimpleEntity e";
public static final String ID_NAME = "select e.id, e.name from SimpleEntity e";
public static final String ID_COMP_VAL = "select e.id, e.composite.value1 from SimpleEntity e";
public static final String ID_NAME_DTO = "select new Dto(e.id, e.name) from SimpleEntity e";
public static final String ID_COMP_VAL_DTO = "select new Dto(e.id, e.composite.value1) from SimpleEntity e";
public static final String NAME_DTO = "select new Dto2(e.name) from SimpleEntity e";
public static final String COMP_VAL_DTO = "select new Dto2(e.composite.value1) from SimpleEntity e";
public static final String NAMED_ENTITY = "entity";
public static final String NAMED_ENTITY_NO_SELECT = "entity-no-select";
public static final String NAMED_COMPOSITE = "composite";
public static final String NAMED_NAME = "name";
public static final String NAMED_COMP_VAL = "comp-val";
public static final String NAMED_ID_NAME = "id-name";
public static final String NAMED_ID_COMP_VAL = "id-comp-val";
public static final String NAMED_ID_NAME_DTO = "id-name-dto";
public static final String NAMED_ID_COMP_VAL_DTO = "id-comp-val-dto";
public static final String NAMED_NAME_DTO = "name-dto";
public static final String NAMED_COMP_VAL_DTO = "comp-val-dto";
}

View File

@ -7,8 +7,8 @@
package org.hibernate.orm.test.query.results; package org.hibernate.orm.test.query.results;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table; import jakarta.persistence.Table;
/** /**
@ -16,6 +16,17 @@ import jakarta.persistence.Table;
*/ */
@Entity(name = "SimpleEntity") @Entity(name = "SimpleEntity")
@Table(name = "simple_entity") @Table(name = "simple_entity")
@NamedQuery(name= Queries.NAMED_ENTITY, query = Queries.ENTITY)
@NamedQuery(name= Queries.NAMED_ENTITY_NO_SELECT, query = Queries.ENTITY_NO_SELECT)
@NamedQuery(name= Queries.NAMED_COMPOSITE, query = Queries.COMPOSITE)
@NamedQuery(name= Queries.NAMED_NAME, query = Queries.NAME)
@NamedQuery(name= Queries.NAMED_COMP_VAL, query = Queries.COMP_VAL)
@NamedQuery(name= Queries.NAMED_ID_NAME, query = Queries.ID_NAME)
@NamedQuery(name= Queries.NAMED_ID_COMP_VAL, query = Queries.ID_COMP_VAL)
@NamedQuery(name= Queries.NAMED_ID_NAME_DTO, query = Queries.ID_NAME_DTO)
@NamedQuery(name= Queries.NAMED_ID_COMP_VAL_DTO, query = Queries.ID_COMP_VAL_DTO)
@NamedQuery(name= Queries.NAMED_NAME_DTO, query = Queries.NAME_DTO)
@NamedQuery(name= Queries.NAMED_COMP_VAL_DTO, query = Queries.COMP_VAL_DTO)
public class SimpleEntity { public class SimpleEntity {
@Id @Id
public Integer id; public Integer id;

View File

@ -0,0 +1,202 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.query.results;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
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.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@Jira( "https://hibernate.atlassian.net/browse/HHH-18401" )
@DomainModel(annotatedClasses = {SimpleEntity.class, SimpleComposite.class, Dto.class, Dto2.class})
@SessionFactory
@SuppressWarnings("JUnitMalformedDeclaration")
public class TypedQueryCreationTests {
@BeforeEach
void prepareTestData(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
session.persist( new SimpleEntity( 1, "first", new SimpleComposite( "value1", "value2" ) ) );
} );
}
@AfterEach
public void dropTestData(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
session.createMutationQuery( "delete SimpleEntity" ).executeUpdate();
});
}
@Test
void testCreateQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final SimpleEntity rtn = session.createQuery( Queries.ENTITY, SimpleEntity.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
sessions.inTransaction( (session) -> {
final SimpleEntity rtn = session.createQuery( Queries.ENTITY_NO_SELECT, SimpleEntity.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
sessions.inTransaction( (session) -> {
final SimpleComposite rtn = session.createQuery( Queries.COMPOSITE, SimpleComposite.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.value1 ).isEqualTo( "value1" );
assertThat( rtn.value2 ).isEqualTo( "value2" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createQuery( Queries.ID_NAME_DTO, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createQuery( Queries.ID_COMP_VAL_DTO, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
sessions.inTransaction( (session) -> {
final String rtn = session.createQuery( Queries.NAME, String.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final String rtn = session.createQuery( Queries.COMP_VAL, String.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn ).isEqualTo( "value1" );
} );
}
@Test
void testCreateSelectionQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final SimpleEntity rtn = session.createSelectionQuery( Queries.ENTITY, SimpleEntity.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
sessions.inTransaction( (session) -> {
final SimpleEntity rtn = session.createSelectionQuery( Queries.ENTITY_NO_SELECT, SimpleEntity.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
sessions.inTransaction( (session) -> {
final SimpleComposite rtn = session.createSelectionQuery( Queries.COMPOSITE, SimpleComposite.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.value1 ).isEqualTo( "value1" );
assertThat( rtn.value2 ).isEqualTo( "value2" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createSelectionQuery( Queries.ID_NAME_DTO, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createSelectionQuery( Queries.ID_COMP_VAL_DTO, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
sessions.inTransaction( (session) -> {
final String rtn = session.createSelectionQuery( Queries.NAME, String.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final String rtn = session.createSelectionQuery( Queries.COMP_VAL, String.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn ).isEqualTo( "value1" );
} );
}
@Test
void testCreateNamedQuery(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final SimpleEntity rtn = session.createNamedQuery( Queries.NAMED_ENTITY, SimpleEntity.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
sessions.inTransaction( (session) -> {
final SimpleEntity rtn = session.createNamedQuery( Queries.NAMED_ENTITY_NO_SELECT, SimpleEntity.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
sessions.inTransaction( (session) -> {
final SimpleComposite rtn = session.createNamedQuery( Queries.NAMED_COMPOSITE, SimpleComposite.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.value1 ).isEqualTo( "value1" );
assertThat( rtn.value2 ).isEqualTo( "value2" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createNamedQuery( Queries.NAMED_ID_NAME_DTO, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final Dto rtn = session.createNamedQuery( Queries.NAMED_ID_COMP_VAL_DTO, Dto.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.getKey() ).isEqualTo( 1 );
assertThat( rtn.getText() ).isEqualTo( "value1" );
} );
sessions.inTransaction( (session) -> {
final String rtn = session.createNamedQuery( Queries.NAMED_NAME, String.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn ).isEqualTo( "first" );
} );
sessions.inTransaction( (session) -> {
final String rtn = session.createNamedQuery( Queries.NAMED_COMP_VAL, String.class ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn ).isEqualTo( "value1" );
} );
}
@Test
void testCriteria(SessionFactoryScope sessions) {
sessions.inTransaction( (session) -> {
final HibernateCriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
final JpaCriteriaQuery<SimpleEntity> criteria = criteriaBuilder.createQuery( SimpleEntity.class );
final JpaRoot<SimpleEntity> root = criteria.from( SimpleEntity.class );
criteria.select( root );
final SimpleEntity rtn = session.createQuery( criteria ).getSingleResultOrNull();
assertThat( rtn ).isNotNull();
assertThat( rtn.id ).isEqualTo( 1 );
} );
}
}