diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index 9a6630ae57d9..6ce9859f81b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -25,8 +25,11 @@ import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; +import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; +import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFromClause; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -528,8 +531,9 @@ public SqmSelectStatement createCountQuery() { // in 'select' list (we don't even need to hit the database to // know they return exactly one row) if ( queryPart.isSimpleQueryPart() - && !( querySpec = (SqmQuerySpec) queryPart ).isDistinct() - && querySpec.getGroupingExpressions().isEmpty() ) { + && !( querySpec = queryPart.getFirstQuerySpec() ).isDistinct() + && querySpec.getGroupingExpressions().isEmpty() + && !selectsEntityValuedPaths( querySpec ) ) { for ( SqmRoot root : querySpec.getRootList() ) { root.removeLeftFetchJoins(); } @@ -553,6 +557,31 @@ public SqmSelectStatement createCountQuery() { } } + private static boolean selectsEntityValuedPaths(SqmQuerySpec querySpec) { + for ( SqmSelectableNode selection : querySpec.getSelectClause().getSelectionItems() ) { + if ( selection instanceof SqmEntityValuedSimplePath entityPath ) { + final SqmPath lhs = entityPath.getLhs(); + if ( lhs instanceof SqmFrom from ) { + // force an explicit inner join for this implicit entity valued path + from.join( entityPath.getNavigablePath().getLocalName() ); + } + else { + return true; + } + } + else if ( selection instanceof SqmPath path ) { + SqmPath lhs = path.getLhs(); + while ( lhs != null ) { + if ( lhs instanceof SqmEntityValuedSimplePath ) { + return true; + } + lhs = lhs.getLhs(); + } + } + } + return false; + } + private void aliasSelections(SqmQueryPart queryPart) { if ( queryPart.isSimpleQueryPart() ) { final SqmQuerySpec querySpec = queryPart.getFirstQuerySpec(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/count/CountTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/count/CountTest.java index 246df5d3bfbf..6055d4ae91be 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/count/CountTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/count/CountTest.java @@ -14,9 +14,13 @@ import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.ParameterExpression; import jakarta.persistence.criteria.Root; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.query.spi.QueryImplementor; 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.Test; import java.util.List; @@ -28,7 +32,6 @@ public class CountTest { @Test void testCount(SessionFactoryScope scope) { - scope.inTransaction(session -> session.createMutationQuery("delete Book").executeUpdate()); scope.inTransaction(session -> { session.persist(new Book("9781932394153", "Hibernate in Action")); session.persist(new Book("9781617290459", "Java Persistence with Hibernate")); @@ -64,7 +67,6 @@ public class CountTest { } @Test void testCountNative(SessionFactoryScope scope) { - scope.inTransaction(session -> session.createMutationQuery("delete Book").executeUpdate()); scope.inTransaction(session -> { session.persist(new Book("9781932394153", "Hibernate in Action")); session.persist(new Book("9781617290459", "Java Persistence with Hibernate")); @@ -86,7 +88,6 @@ public class CountTest { } @Test void testCountCriteria(SessionFactoryScope scope) { - scope.inTransaction(session -> session.createMutationQuery("delete Book").executeUpdate()); scope.inTransaction(session -> { session.persist(new Book("9781932394153", "Hibernate in Action")); session.persist(new Book("9781617290459", "Java Persistence with Hibernate")); @@ -120,6 +121,50 @@ public class CountTest { }); } + @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-19065" ) + public void testJoins(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Publisher p = new Publisher( 1L, "Manning" ); + session.persist( p ); + final Book book = new Book( "9781932394153", "Hibernate in Action" ); + book.publisher = p; + session.persist( book ); + session.persist( new Book( "9781617290459", "Java Persistence with Hibernate" ) ); + } ); + scope.inSession( session -> { + // explicit inner join + assertCount( 1, "select p from Book b join b.publisher p", Publisher.class, session ); + assertCount( 1, "select p.name from Book b join b.publisher p", String.class, session ); + // explicit left join + assertCount( 2, "select p from Book b left join b.publisher p", Publisher.class, session ); + assertCount( 2, "select p.name from Book b left join b.publisher p", String.class, session ); + // implicit join + assertCount( 1, "select b.publisher from Book b", Publisher.class, session ); + assertCount( 1, "select b.publisher from Book b join b.publisher", Publisher.class, session ); + assertCount( 1, "select b.publisher.name from Book b", String.class, session ); + assertCount( 1, "select publisher.name from Book b left join b.publisher", String.class, session ); + assertCount( 1, + "select publisher.name from Book b left join b.publisher where publisher.name is null or length(publisher.name) > 0", + String.class, session ); + // selecting only the id does not create an explicit join + assertCount( 2, "select b.publisher.id from Book b", Long.class, session ); + } ); + } + + private void assertCount(int expected, String hql, Class resultClass, SessionImplementor session) { + final QueryImplementor query = session.createQuery( hql, resultClass ); + final List resultList = query.getResultList(); + final long resultCount = query.getResultCount(); + assertEquals( expected, resultList.size() ); + assertEquals( expected, resultCount ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + @Entity(name="Book") @Table(name = "books") static class Book { @@ -151,6 +196,15 @@ static class Author { @Entity(name="Publisher") @Table(name = "pubs") static class Publisher { - @Id String name; + @Id Long id; + String name; + + Publisher() { + } + + Publisher(Long id, String name) { + this.id = id; + this.name = name; + } } }