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) {
final QueryEngine queryEngine = getFactory().getQueryEngine();
return queryEngine.getInterpretationCache()
.resolveHqlInterpretation(
hql,
resultType,
queryEngine.getHqlTranslator()
);
return queryEngine.interpretHql( hql, resultType );
}
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);
// /**
// * 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.
*

View File

@ -19,19 +19,6 @@ import java.util.Spliterator;
import java.util.stream.Stream;
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.FlushMode;
import org.hibernate.HibernateException;
@ -48,6 +35,10 @@ import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
import org.hibernate.metamodel.model.domain.BasicDomainType;
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.IllegalQueryOperationException;
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.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 org.hibernate.CacheMode.fromJpaModes;
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.JPA_SHARED_CACHE_RETRIEVE_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.QueryHints.HINT_CACHE_MODE;
import static org.hibernate.jpa.QueryHints.HINT_CACHE_REGION;
import static org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE;
import static org.hibernate.jpa.QueryHints.HINT_FOLLOW_ON_LOCKING;
import static org.hibernate.jpa.QueryHints.HINT_READONLY;
import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE;
import static org.hibernate.jpa.HibernateHints.HINT_CACHE_REGION;
import static org.hibernate.jpa.HibernateHints.HINT_FETCH_SIZE;
import static org.hibernate.jpa.HibernateHints.HINT_FOLLOW_ON_LOCKING;
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.isSelectionAssignableToResultType;
@ -255,7 +259,8 @@ public abstract class AbstractSelectionQuery<R>
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(
SqmQueryPart<R> queryPart,
@ -298,11 +303,13 @@ public abstract class AbstractSelectionQuery<R>
SqmQuerySpec<T> querySpec,
Class<T> expectedResultClass,
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();
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 SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
if ( selectableNode.isCompoundSelection() ) {
@ -314,7 +321,7 @@ public abstract class AbstractSelectionQuery<R>
}
}
else {
verifySelectionType( expectedResultClass, sessionFactory, sqmSelection.getSelectableNode() );
verifySingularSelectionType( expectedResultClass, sessionFactory, sqmSelection );
}
}
else if ( expectedResultClass.isArray() ) {
@ -323,28 +330,50 @@ public abstract class AbstractSelectionQuery<R>
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,
SessionFactoryImplementor sessionFactory,
SqmSelection<?> sqmSelection) {
// special case for parameters in the select list
final SqmSelectableNode<?> selection = sqmSelection.getSelectableNode();
if ( selection instanceof SqmParameter ) {
final SqmParameter<?> sqmParameter = (SqmParameter<?>) selection;
final SqmExpressible<?> nodeType = sqmParameter.getNodeType();
// we may not yet know a selection type
if ( nodeType == null || nodeType.getExpressibleJavaType() == null ) {
// we can't verify the result type up front
return;
final SqmSelectableNode<?> selectableNode = sqmSelection.getSelectableNode();
try {
verifySelectionType( expectedResultClass, sessionFactory, selectableNode );
}
catch (QueryTypeMismatchException mismatchException) {
// Check for special case of a single selection item and implicit instantiation.
// See if the selected type can be used to instantiate the expected-type
final JavaType<?> javaTypeDescriptor = selectableNode.getJavaTypeDescriptor();
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() ) {
verifyResultType( expectedResultClass, selection.getExpressible() );
private static <T> boolean hasMatchingConstructor(Class<T> expectedResultClass, Class<?> selectedJavaType) {
try {
expectedResultClass.getDeclaredConstructor( selectedJavaType );
return true;
}
catch (NoSuchMethodException e) {
return false;
}
}
@ -384,24 +413,58 @@ public abstract class AbstractSelectionQuery<R>
|| expectedResultClass == Tuple.class;
}
protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> sqmExpressible) {
if ( sqmExpressible != null ) {
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType();
assert expressibleJavaType != null;
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass();
if ( javaTypeClass != Object.class && !resultClass.isAssignableFrom( javaTypeClass ) ) {
if ( expressibleJavaType instanceof PrimitiveJavaType ) {
final PrimitiveJavaType<?> javaType = (PrimitiveJavaType<?>) expressibleJavaType;
if ( javaType.getPrimitiveClass() != resultClass ) {
throwQueryTypeMismatchException( resultClass, sqmExpressible );
protected static <T> void verifyResultType(Class<T> resultClass, @Nullable SqmExpressible<?> selectionExpressible) {
if ( selectionExpressible == null ) {
// nothing we can validate
return;
}
final JavaType<?> selectionExpressibleJavaType = selectionExpressible.getExpressibleJavaType();
assert selectionExpressibleJavaType != null;
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
@ -785,7 +848,7 @@ public abstract class AbstractSelectionQuery<R>
super.collectHints( hints );
if ( isReadOnly() ) {
hints.put( HINT_READONLY, true );
hints.put( HINT_READ_ONLY, true );
}
putIfNotNull( hints, HINT_FETCH_SIZE, getFetchSize() );

View File

@ -49,5 +49,9 @@ public interface QueryEngine {
HqlTranslator getHqlTranslator();
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.expectedResultType = expectedResultType;
// visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
this.resultType = determineResultType( sqm );
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
setComment( hql );
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
}
private Class<?> determineResultType(SqmSelectStatement<?> sqm) {

View File

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

View File

@ -21,7 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*/
@DomainModel( annotatedClasses = SimpleEntity.class )
@DomainModel( annotatedClasses = {SimpleEntity.class, Dto.class, Dto2.class } )
@SessionFactory
public class BasicHqlResultTests {
@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;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;
/**
@ -16,6 +16,17 @@ import jakarta.persistence.Table;
*/
@Entity(name = "SimpleEntity")
@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 {
@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 );
} );
}
}