From 3958ee23607892aaab69d735706481c02b99cf50 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 14 Apr 2021 15:58:03 -0500 Subject: [PATCH] re-enable tests re-organize some tests work on `org.hibernate.test.hql.size` improved key/target handling for fk-descriptor --- .../internal/ExceptionConverterImpl.java | 5 +- .../mapping/ForeignKeyDescriptor.java | 18 +- .../EmbeddedForeignKeyDescriptor.java | 34 +- .../internal/SimpleForeignKeyDescriptor.java | 47 +- .../query/hql/internal/DomainPathPart.java | 18 +- ...aphLogger.java => ResultGraphLogging.java} | 2 +- .../internal/EagerCollectionFetch.java | 43 +- .../orm/test/query/hql/CastFunctionTest.java | 110 ++++ .../query}/hql/size/ManyToManySizeTest.java | 126 ++-- .../query/hql/size/ManyToManySizeTest2.java | 495 ++++++++++++++ .../query}/hql/size/OneToManySizeTest.java | 112 ++-- .../query/hql/size/OneToManySizeTest2.java | 613 ++++++++++++++++++ .../orm/test/query/hql/size/Skill.java | 50 ++ .../orm/test/query/hql/size/Student.java | 46 ++ .../orm/test/query/hql/size/Teacher.java | 67 ++ .../hibernate/test/hql/CastFunctionTest.java | 113 ---- .../test/hql/size/ManyToManySizeTest2.java | 563 ---------------- .../test/hql/size/OneToManySizeTest2.java | 560 ---------------- .../testing/jdbc/SQLStatementInspector.java | 17 +- .../orm/junit/ServiceRegistryExtension.java | 3 + 20 files changed, 1663 insertions(+), 1379 deletions(-) rename hibernate-core/src/main/java/org/hibernate/sql/results/graph/{ResultGraphLogger.java => ResultGraphLogging.java} (93%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CastFunctionTest.java rename hibernate-core/src/test/java/org/hibernate/{test => orm/test/query}/hql/size/ManyToManySizeTest.java (76%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest2.java rename hibernate-core/src/test/java/org/hibernate/{test => orm/test/query}/hql/size/OneToManySizeTest.java (77%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest2.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Skill.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Student.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Teacher.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/CastFunctionTest.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java index 77fa3763ef..33c499e1a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java @@ -163,7 +163,10 @@ public class ExceptionConverterImpl implements ExceptionConverter { return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules } else { - final PersistenceException converted = new PersistenceException( cause ); + final PersistenceException converted = new PersistenceException( + "Converting `" + cause.getClass().getName() + "` to JPA `PersistenceException` : " + cause.getMessage(), + cause + ); handlePersistenceException( converted ); return converted; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java index 8398476238..8984fcd381 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java @@ -6,8 +6,6 @@ */ package org.hibernate.metamodel.mapping; -import java.util.function.BiConsumer; -import java.util.function.Function; import java.util.function.IntFunction; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -34,6 +32,22 @@ public interface ForeignKeyDescriptor extends VirtualModelPart { ModelPart getKeyPart(); + /** + * Create a DomainResult for the referring-side of the fk + */ + DomainResult createKeyDomainResult( + NavigablePath collectionPath, + TableGroup tableGroup, + DomainResultCreationState creationState); + + /** + * Create a DomainResult for the target-side of the fk + */ + DomainResult createTargetDomainResult( + NavigablePath collectionPath, + TableGroup tableGroup, + DomainResultCreationState creationState); + DomainResult createCollectionFetchDomainResult( NavigablePath collectionPath, TableGroup tableGroup, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java index 6289310dfb..49ab89fc99 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java @@ -8,8 +8,6 @@ package org.hibernate.metamodel.mapping.internal; import java.util.ArrayList; import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Function; import java.util.function.IntFunction; import org.hibernate.LockMode; @@ -135,6 +133,38 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor { ); } + @Override + public DomainResult createKeyDomainResult( + NavigablePath collectionPath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + assert tableGroup.getTableReference( keyTable ) != null; + + return createDomainResult( + collectionPath, + tableGroup, + keyTable, + keyMappingType, + creationState + ); + } + + @Override + public DomainResult createTargetDomainResult( + NavigablePath collectionPath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + assert tableGroup.getTableReference( targetTable ) != null; + + return createDomainResult( + collectionPath, + tableGroup, + targetTable, + targetMappingType, + creationState + ); + } + @Override public DomainResult createCollectionFetchDomainResult( NavigablePath collectionPath, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 07984f958a..b2d10fc853 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.IntFunction; @@ -46,6 +47,7 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.results.DomainResultCreationException; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; @@ -112,6 +114,33 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa ); } + @Override + public DomainResult createKeyDomainResult( + NavigablePath collectionPath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + assert tableGroup.getTableReference( keySide.getContainingTableExpression() ) != null; + + return createDomainResult( + collectionPath, + tableGroup, + keySide, + creationState + ); + } + + @Override + public DomainResult createTargetDomainResult(NavigablePath collectionPath, TableGroup tableGroup, DomainResultCreationState creationState) { + assert tableGroup.getTableReference( targetSide.getContainingTableExpression() ) != null; + + return createDomainResult( + collectionPath, + tableGroup, + targetSide, + creationState + ); + } + @Override public DomainResult createCollectionFetchDomainResult( NavigablePath collectionPath, @@ -129,7 +158,21 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { - return createDomainResult( navigablePath, tableGroup, keySide, creationState ); + try { + return createDomainResult( navigablePath, tableGroup, keySide, creationState ); + } + catch (Exception e) { + throw new DomainResultCreationException( + String.format( + Locale.ROOT, + "Unable to create fk key domain-result `%s.%s` relative to `%s`", + keySide.getContainingTableExpression(), + keySide.getSelectionExpression(), + tableGroup + ), + e + ); + } } @Override @@ -160,8 +203,10 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa DomainResultCreationState creationState) { final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); + final TableReference tableReference = tableGroup.resolveTableReference( selectableMapping.getContainingTableExpression() ); final String identificationVariable = tableReference.getIdentificationVariable(); + final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( sqlExpressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java index 12df3c3ff4..69fd42de5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java @@ -10,8 +10,8 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.query.SemanticException; import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.spi.SemanticPathPart; -import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -55,6 +55,22 @@ public class DomainPathPart implements SemanticPathPart { return this; } +// if we want to allow re-use of matched unaliased SqmFrom nodes +// +// final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry(); +// final NavigablePath possibleImplicitAliasPath = lhs.getNavigablePath().append( name ); +// final SqmPath fromByImplicitAlias = pathRegistry.findPath( possibleImplicitAliasPath ); +// +// if ( fromByImplicitAlias != null ) { +// if ( fromByImplicitAlias instanceof SqmFrom ) { +// final String explicitPathAlias = fromByImplicitAlias.getExplicitAlias(); +// if ( explicitPathAlias == null || Objects.equals( possibleImplicitAliasPath.getFullPath(), explicitPathAlias ) ) { +// currentPath = fromByImplicitAlias; +// return isTerminal ? currentPath : this; +// } +// } +// } +// currentPath = subPathSource.createSqmPath( lhs, creationState ); if ( isTerminal ) { return currentPath; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/ResultGraphLogger.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/ResultGraphLogging.java similarity index 93% rename from hibernate-core/src/main/java/org/hibernate/sql/results/graph/ResultGraphLogger.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/graph/ResultGraphLogging.java index 2df5a24687..56a2cc5da7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/ResultGraphLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/ResultGraphLogging.java @@ -13,7 +13,7 @@ import org.jboss.logging.Logger; /** * @author Steve Ebersole */ -public interface ResultGraphLogger { +public interface ResultGraphLogging { Logger LOGGER = ResultsLogger.subLogger( "graph" ); boolean DEBUG_ENABLED = LOGGER.isDebugEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EagerCollectionFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EagerCollectionFetch.java index 5eb574fe69..4afde5012c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EagerCollectionFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EagerCollectionFetch.java @@ -15,6 +15,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -28,6 +29,7 @@ import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.FetchableContainer; import org.hibernate.sql.results.graph.collection.CollectionInitializer; +import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** @@ -56,14 +58,51 @@ public class EagerCollectionFetch extends CollectionFetch implements FetchParent final NavigablePath parentPath = fetchedPath.getParent(); final TableGroup parentTableGroup = parentPath == null ? null : fromClauseAccess.findTableGroup( parentPath ); + + // NOTE : + // `keyContainerResult` = fk target-side + // `keyCollectionResult` = fk key-side + + // 3 cases: + // + // #1 - one-to-many : Teacher#students + // teacher( id ), student( id, teacher_fk ) + // + // student.teacher_fk -> teacher.id + // + // referring : student.teacher_fk + // target : teacher.id + // + // #2 - many-to-many : Teacher#skills + // teacher( id ), skill( id ), teacher_skill( teacher_fk, skill_id ) + // + // teacher_skill.skill_id -> skill.id + // teacher_skill.teacher_fk -> teacher.id + // + // referring : teacher_skill.teacher_fk + // target : teacher.id + // + // #3 - element-collection : Teacher#nickNames + // teacher( id ), teacher_nicks( teacher_fk, nick ) + // + // teacher_nicks.teacher_fk -> teacher.id + // + // referring : teacher_nicks.teacher_fk + // target : teacher.id + final ForeignKeyDescriptor keyDescriptor = fetchedAttribute.getKeyDescriptor(); if ( parentTableGroup != null ) { // join fetch - keyContainerResult = keyDescriptor.createCollectionFetchDomainResult( fetchedPath, parentTableGroup, creationState ); - keyCollectionResult = keyDescriptor.createDomainResult( fetchedPath, collectionTableGroup, creationState ); + + // target-side + keyContainerResult = keyDescriptor.createTargetDomainResult( fetchedPath, parentTableGroup, creationState ); + + // referring(key)-side + keyCollectionResult = keyDescriptor.createKeyDomainResult( fetchedPath, collectionTableGroup, creationState ); } else { // select fetch + // todo (6.0) : we could potentially leverage batch fetching for performance keyContainerResult = keyDescriptor.createCollectionFetchDomainResult( fetchedPath, collectionTableGroup, creationState ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CastFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CastFunctionTest.java new file mode 100644 index 0000000000..58e87d4234 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CastFunctionTest.java @@ -0,0 +1,110 @@ +/* + * 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.hql; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.Dialect; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +/** + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = CastFunctionTest.MyEntity.class ) +@SessionFactory +@FailureExpected( reason = "requires https://github.com/hibernate/hibernate-orm/pull/3912" ) +public class CastFunctionTest { + @Test + public void testStringCasting(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final Dialect dialect = session.getFactory().getJdbcServices().getDialect(); + if ( DerbyDialect.class.isInstance( dialect ) ) { + + // the conversion from DOUBLE to VARCHAR is not supported by Derby, + // using the short name + session.createQuery( "select cast(char(e.theLostNumber) as string) from MyEntity e" ).list(); + // using the java class name + session.createQuery( "select cast(char(e.theLostNumber) as java.lang.String) from MyEntity e" ).list(); + // using the fqn Hibernate Type name + session.createQuery( "select cast(char(e.theLostNumber) as org.hibernate.type.StringType) from MyEntity e" ) + .list(); + } + else { + // using the short name + session.createQuery( "select cast(e.theLostNumber as string) from MyEntity e" ).list(); + // using the java class name + session.createQuery( "select cast(e.theLostNumber as java.lang.String) from MyEntity e" ).list(); + // using the fqn Hibernate Type name + session.createQuery( "select cast(e.theLostNumber as org.hibernate.type.StringType) from MyEntity e" ) + .list(); + } + } + ); + } + + @Test + public void testIntegerCasting(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + // using the short name + session.createQuery( "select cast(e.theLostNumber as integer) from MyEntity e" ).list(); + // using the java class name (primitive) + session.createQuery( "select cast(e.theLostNumber as int) from MyEntity e" ).list(); + // using the java class name + session.createQuery( "select cast(e.theLostNumber as java.lang.Integer) from MyEntity e" ).list(); + // using the fqn Hibernate Type name + session.createQuery( "select cast(e.theLostNumber as org.hibernate.type.IntegerType) from MyEntity e" ).list(); + } + ); + } + + @Test + public void testLongCasting(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + // using the short name (also the primitive name) + session.createQuery( "select cast(e.theLostNumber as long) from MyEntity e" ).list(); + // using the java class name + session.createQuery( "select cast(e.theLostNumber as java.lang.Long) from MyEntity e" ).list(); + // using the fqn Hibernate Type name + session.createQuery( "select cast(e.theLostNumber as org.hibernate.type.LongType) from MyEntity e" ).list(); + } + ); + } + + @Test + public void testFloatCasting(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + // using the short name (also the primitive name) + session.createQuery( "select cast(e.theLostNumber as float) from MyEntity e" ).list(); + // using the java class name + session.createQuery( "select cast(e.theLostNumber as java.lang.Float) from MyEntity e" ).list(); + // using the fqn Hibernate Type name + session.createQuery( "select cast(e.theLostNumber as org.hibernate.type.FloatType) from MyEntity e" ).list(); + + } + ); + } + + @Entity( name="MyEntity" ) + public static class MyEntity { + @Id + private Integer id; + private String name; + private Double theLostNumber; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest.java similarity index 76% rename from hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest.java index 22f5924a6b..02084e309f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest.java @@ -5,7 +5,7 @@ * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.hql.size; +package org.hibernate.orm.test.query.hql.size; import java.util.ArrayList; import java.util.List; @@ -18,25 +18,27 @@ import javax.persistence.ManyToMany; import org.hibernate.Hibernate; import org.hibernate.dialect.DerbyDialect; -import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @TestForIssue( jiraKey = "HHH-13619" ) -public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { +@DomainModel( annotatedClasses = { ManyToManySizeTest.Company.class, ManyToManySizeTest.Customer.class } ) +@SessionFactory +public class ManyToManySizeTest { @Test - public void testSizeAsRestriction() { - doInHibernate( - this::sessionFactory, - session -> { + public void testSizeAsRestriction(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( "select c.id from Company c where size( c.customers ) = 0" ).list(); @@ -47,11 +49,10 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsCompoundSelectExpression() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsCompoundSelectExpression(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( "select c.id, c.name, size( c.customers )" + " from Company c" + @@ -76,13 +77,12 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsCtorSelectExpression() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsCtorSelectExpression(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.ManyToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.ManyToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c" + " group by c.id, c.name" + @@ -106,13 +106,12 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionWithLeftJoin() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionWithLeftJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.ManyToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.ManyToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c left join c.customers cu" + " group by c.id, c.name" + @@ -136,13 +135,12 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionWithInnerJoin() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionWithInnerJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.ManyToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.ManyToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c inner join c.customers cu" + " group by c.id, c.name" + @@ -162,13 +160,12 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionOfAliasWithInnerJoin() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionOfAliasWithInnerJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.ManyToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.ManyToManySizeTest$CompanyDto(" + " c.id, c.name, size( cu ) )" + " from Company c inner join c.customers cu" + " group by c.id, c.name" + @@ -188,13 +185,12 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionExcludeEmptyCollection() { - doInHibernate( - this::sessionFactory, + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionExcludeEmptyCollection(SessionFactoryScope scope) { + scope.inTransaction( session -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.ManyToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.ManyToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c" + " where c.id != 0" + @@ -214,10 +210,9 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - public void testSizeAsConditionalExpressionExcludeEmptyCollection() { - doInHibernate( - this::sessionFactory, - session -> { + public void testSizeAsConditionalExpressionExcludeEmptyCollection(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( "from Company c" + " where size( c.customers ) > 0" + @@ -240,10 +235,9 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - public void testSizeAsConditionalExpressionIncludeEmptyCollection() { - doInHibernate( - this::sessionFactory, - session -> { + public void testSizeAsConditionalExpressionIncludeEmptyCollection(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( "from Company c" + " where size( c.customers ) > -1" + @@ -270,12 +264,10 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { ); } - @Before - public void initDataBase(){ - - doInHibernate( - this::sessionFactory, - session -> { + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { // Add a company with no customers final Company companyWithNoCustomers = new Company( 0 ); companyWithNoCustomers.name = "Company 0"; @@ -297,11 +289,10 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { ); } - @After - public void cleanupDatabase() { - doInHibernate( - this::sessionFactory, - session -> { + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { for ( Company company : session.createQuery( "from Company", Company.class ).list() ) { session.delete( company ); } @@ -309,11 +300,6 @@ public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { ); } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Company.class, Customer.class }; - } - @Entity(name ="Company") public static class Company { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest2.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest2.java new file mode 100644 index 0000000000..53e1b05cd4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/ManyToManySizeTest2.java @@ -0,0 +1,495 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.query.hql.size; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hibernate.testing.jdbc.SQLStatementInspector.extractFromSession; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-13944") +//@ServiceRegistry( +// settings = @Setting( name = AvailableSettings.STATEMENT_INSPECTOR, value = "org.hibernate.testing.jdbc.SQLStatementInspector" ) +//) +@DomainModel( annotatedClasses = { Skill.class, Teacher.class, Student.class } ) +@SessionFactory +public class ManyToManySizeTest2 { + + @Test + public void testSize(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // Using "join" (instead of "left join") removes the teacher with no skills from the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // Using "left join" includes the teacher with no skills in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } + ); + } + + @Test + public void testSizeAddFetch(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + + + // Using "join" (instead of "left join") removes the teacher with no skills from the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + + // Using "left join" includes the teacher with no skills in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills left join teacher.skills skills where size(skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getSkills() ) ); + } + ); + } + + @Test + public void testSizeWithoutNestedPath(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // If there is no "join", then results include the teacher with no skills + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } + ); + } + + @Test + public void testSizeWithoutNestedPathAddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 2", + Teacher.class + ).getResultList(); + statementInspector.assertNumberOfJoins( 0, 2 ); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getSkills() ) ); + } + ); + } + + @Test +// @FailureExpected( reason = "org.hibernate.PropertyAccessException : Student#teacher (I think its a stack-overflow)" ) + public void testSizeWithNestedPathAndImplicitJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select student from Student student where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + statementInspector.assertNumberOfJoins( 0, 1 ); + + students = session.createQuery( + "select student from Student student where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1, students.size() ); + + students = session.createQuery( + "select student from Student student where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + + // If there is no "join" in the query, then results include the student with the teacher with no skills + students = session.createQuery( + "select student from Student student where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + } + ); + } + + @Test + public void testSizeWithNestedPathAndImplicitJoinAddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select distinct student " + + "from Student student " + + " left join fetch student.teacher t " + + " left join fetch t.skills " + + "where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + statementInspector.assertNumberOfJoins( 0, 4 ); + + students = session.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); + } + ); + } + + @Test + @FailureExpected( reason = "org.hibernate.PropertyAccessException : Student#teacher (I think its a stack-overflow)" ) + public void testSizeWithNestedPath(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + { + List students = session.createQuery( + "select student from Student student join student.teacher t join t.skills", + Student.class + ).getResultList(); + + } + + List students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + extractFromSession( session ).assertNumberOfJoins( 0, 1 ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @Test + @FailureExpected( reason = "org.hibernate.PropertyAccessException : Student#teacher (I think its a stack-overflow)" ) + public void testSizeWithNestedPathAddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + extractFromSession( session ).assertNumberOfJoins( 0, 4 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @Test +// @FailureExpected( reason = "org.hibernate.PropertyAccessException : Student#teacher (I think its a stack-overflow)" ) + public void testSizeWithNestedPath2(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List students = session.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + extractFromSession( session ).assertNumberOfJoins( 0, 1 ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(t.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + } + ); + } + + @Test + @FailureExpected( reason = "org.hibernate.PropertyAccessException : Student#teacher (I think its a stack-overflow)" ) + public void testSizeWithNestedPath2AddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + extractFromSession( session ).assertNumberOfJoins( 0, 4 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 0", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 1", + Student.class + ).getResultList(); + assertEquals( 1L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + Skill mathSkill = new Skill(); + Skill frenchSkill = new Skill(); + + session.persist( mathSkill ); + session.persist( frenchSkill ); + + Teacher teacherWithNoSkills = new Teacher(); + + Teacher teacherWithOneSkill = new Teacher(); + teacherWithOneSkill.addSkill( mathSkill ); + + Teacher teacherWithTwoSkills = new Teacher(); + teacherWithTwoSkills.addSkill( mathSkill ); + teacherWithTwoSkills.addSkill( frenchSkill ); + + session.persist( teacherWithNoSkills ); + session.persist( teacherWithOneSkill ); + session.persist( teacherWithTwoSkills ); + + Student studentWithTeacherWithNoSkills = new Student(); + studentWithTeacherWithNoSkills.setTeacher( teacherWithNoSkills ); + session.persist( studentWithTeacherWithNoSkills ); + + Student studentWithTeacherWithOneSkill = new Student(); + studentWithTeacherWithOneSkill.setTeacher( teacherWithOneSkill ); + session.persist( studentWithTeacherWithOneSkill ); + + Student studentWithTeacherWithTwoSkills = new Student(); + studentWithTeacherWithTwoSkills.setTeacher( teacherWithTwoSkills ); + session.persist( studentWithTeacherWithTwoSkills ); + } + ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.createQuery( "delete from Student" ).executeUpdate(); + session.createQuery( "delete from Teacher" ).executeUpdate(); + session.createQuery( "delete from Skill" ).executeUpdate(); + } + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest.java similarity index 77% rename from hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest.java index 1643732acb..b0efe90422 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest.java @@ -5,7 +5,7 @@ * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.hql.size; +package org.hibernate.orm.test.query.hql.size; import java.util.ArrayList; import java.util.List; @@ -19,28 +19,30 @@ import javax.persistence.OneToMany; import org.hibernate.Hibernate; import org.hibernate.dialect.DerbyDialect; -import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @TestForIssue( jiraKey = "HHH-13619" ) -public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { +@DomainModel( annotatedClasses = { OneToManySizeTest.Company.class, OneToManySizeTest.Customer.class } ) +@SessionFactory +public class OneToManySizeTest { @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpression() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpression(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.OneToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.OneToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c" + " group by c.id, c.name" + @@ -64,13 +66,12 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionWithLeftJoin() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionWithLeftJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.OneToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.OneToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c left join c.customers cu" + " group by c.id, c.name" + @@ -94,13 +95,12 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionWithInnerJoin() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionWithInnerJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.OneToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.OneToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c inner join c.customers cu" + " group by c.id, c.name" + @@ -120,13 +120,12 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionOfAliasWithInnerJoin() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionOfAliasWithInnerJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.OneToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.OneToManySizeTest$CompanyDto(" + " c.id, c.name, size( cu ) )" + " from Company c inner join c.customers cu" + " group by c.id, c.name" + @@ -146,13 +145,12 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't see that the subquery is functionally dependent") - public void testSizeAsSelectExpressionExcludeEmptyCollection() { - doInHibernate( - this::sessionFactory, - session -> { + @SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby doesn't see that the subquery is functionally dependent" ) + public void testSizeAsSelectExpressionExcludeEmptyCollection(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( - "select new org.hibernate.test.hql.size.OneToManySizeTest$CompanyDto(" + + "select new org.hibernate.orm.test.query.hql.size.OneToManySizeTest$CompanyDto(" + " c.id, c.name, size( c.customers ) )" + " from Company c" + " where c.id != 0" + @@ -172,10 +170,9 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - public void testSizeAsConditionalExpressionExcludeEmptyCollection() { - doInHibernate( - this::sessionFactory, - session -> { + public void testSizeAsConditionalExpressionExcludeEmptyCollection(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( "from Company c" + " where size( c.customers ) > 0" + @@ -198,10 +195,9 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - public void testSizeAsConditionalExpressionIncludeEmptyCollection() { - doInHibernate( - this::sessionFactory, - session -> { + public void testSizeAsConditionalExpressionIncludeEmptyCollection(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { final List results = session.createQuery( "from Company c" + " where size( c.customers ) > -1" + @@ -228,12 +224,10 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { ); } - @Before - public void initDataBase(){ - - doInHibernate( - this::sessionFactory, - session -> { + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { // Add a company with no customers final Company companyWithNoCustomers = new Company( 0 ); companyWithNoCustomers.name = "Company 0"; @@ -255,11 +249,10 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { ); } - @After - public void cleanupDatabase() { - doInHibernate( - this::sessionFactory, - session -> { + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { for ( Company company : session.createQuery( "from Company", Company.class ).list() ) { session.delete( company ); } @@ -267,11 +260,6 @@ public class OneToManySizeTest extends BaseNonConfigCoreFunctionalTestCase { ); } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Company.class, Customer.class }; - } - @Entity(name ="Company") public static class Company { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest2.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest2.java new file mode 100644 index 0000000000..d9adb837bc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/OneToManySizeTest2.java @@ -0,0 +1,613 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.query.hql.size; + +import java.util.List; +import java.util.Locale; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; +import static org.hibernate.testing.jdbc.SQLStatementInspector.extractFromSession; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-13944") +@ServiceRegistry( + settings = @Setting( name = AvailableSettings.STATEMENT_INSPECTOR, value = "org.hibernate.testing.jdbc.SQLStatementInspector" ) +) +@DomainModel( + annotatedClasses = { Skill.class, Teacher.class, Student.class } +) +@SessionFactory +public class OneToManySizeTest2 { + @Test + public void testSize(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // Using "left join" includes the teacher with no students in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } + ); + } + + @Test + public void testSizeAddFetch(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + + // Using "join" (instead of "left join") removes the teacher with no students from the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + + // Using "left join" includes the teacher with no students in the results. + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students left join teacher.students students where size(students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getStudents() ) ); + } + ); + } + + @Test + public void testSizeWithoutNestedPath(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + List teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > 2", + Teacher.class + ).getResultList(); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + + // If there is no "join", then results include the teacher with no students + teachers = session.createQuery( + "select teacher from Teacher teacher where size(teacher.students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + } + ); + } + + @Test + public void testSizeWithoutNestedPathAddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 2", + Teacher.class + ).getResultList(); + assertEquals( countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ), 1 ); + assertEquals( 0, teachers.size() ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 1", + Teacher.class + ).getResultList(); + assertEquals( 1, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 0", + Teacher.class + ).getResultList(); + assertEquals( 2, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + + teachers = session.createQuery( + "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > -1", + Teacher.class + ).getResultList(); + assertEquals( 3, teachers.size() ); + assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); + assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getStudents() ) ); + } + ); + } + + @Test + public void testSizeWithNestedPathAndImplicitJoin(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select student from Student student where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0, students.size() ); + assertEquals( countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ), 1 ); + + students = session.createQuery( + "select student from Student student where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + + students = session.createQuery( + "select student from Student student where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + + students = session.createQuery( + "select student from Student student where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + } + ); + } + + @Test + public void testCollectionFetchBaseline(SessionFactoryScope scope) { + // tests various "normal" ways to use a fetch + scope.inTransaction( + (session) -> { + + final List list = session.createQuery( "from Teacher t join fetch t.students" ).list(); + assertThat( list.size(), is( 2 ) ); + } + ); + } + + + @Test + public void testSizeWithNestedPathAndImplicitJoinAddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select distinct student " + + "from Student student " + + " left join fetch student.teacher t " + + " left join fetch t.students " + + "where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 3, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + assertEquals( 0, students.size() ); + + students = session.createQuery( + "select distinct student " + + "from Student student " + + " left join fetch student.teacher t " + + " left join fetch t.students " + + "where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student " + + "from Student student " + + " left join fetch student.teacher t " + + " left join fetch t.students " + + "where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student " + + "from Student student " + + " left join fetch student.teacher t " + + " left join fetch t.students " + + "where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + } + ); + } + + @Test + public void testSizeWithNestedPathBase(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInterceptor = extractFromSession( session ); + statementInterceptor.clear(); + + List students = null; + + students = session.createQuery( + "select student from Student student join student.teacher t", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( 1, countNumberOfJoins( statementInterceptor.getSqlQueries().get( 0 ) ) ); + + statementInterceptor.clear(); + + students = session.createQuery( + "select student from Student student join student.teacher t where size( t.students ) > -1", + Student.class + ).getResultList(); + assertEquals( 1, countNumberOfJoins( statementInterceptor.getSqlQueries().get( 0 ) ) ); + + statementInterceptor.clear(); + + students = session.createQuery( + "select student from Student student join student.teacher where size( student.teacher.students ) > -1", + Student.class + ).getResultList(); + statementInterceptor.assertNumberOfJoins( 0, 2 ); + + } + ); + } + + @Test + public void testSizeWithNestedPath(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( 2, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @Test + public void testSizeWithNestedPathAddFetchDistinctBase(SessionFactoryScope scope) { + + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + { + List students = session.createQuery( + "select distinct student " + + "from Student student " + + " join student.teacher t " + + " join fetch student.teacher tjoin " + + " left join fetch tjoin.students " + + "where size(tjoin.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + assertEquals( 3L, students.size() ); + } + + { + List students = session.createQuery( + "select distinct student " + + "from Student student " + + " join student.teacher t " + + " join fetch student.teacher tjoin " + + " left join fetch tjoin.students " + + "where size(t.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + assertEquals( 3L, students.size() ); + } + } + ); + } + + @Test + public void testSizeWithNestedPathAddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students; + students = session.createQuery( + "select distinct student " + + "from Student student " + + " join student.teacher t " + + " join fetch student.teacher tjoin " + + " left join fetch tjoin.students " + + "where size(student.teacher.students) > -1", + Student.class + ).getResultList(); + assertEquals( 4, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student " + + "from Student student " + + " join student.teacher t " + + " join fetch student.teacher tjoin " + + " left join fetch tjoin.students " + + "where size(student.teacher.students) > 0", + Student.class + ).getResultList(); + assertEquals( 4, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student " + + "from Student student " + + " join student.teacher t " + + " join fetch student.teacher tjoin " + + " left join fetch tjoin.students " + + "where size(student.teacher.students) > 1", + Student.class + ).getResultList(); + assertEquals( 4, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) ); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student " + + "from Student student " + + " join student.teacher t " + + " join fetch student.teacher tjoin " + + " left join fetch tjoin.students " + + "where size(student.teacher.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @Test + public void testSizeWithNestedPath2(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select student from Student student join student.teacher t where size(t.students) > -1", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertEquals( countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ), 1 ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(t.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(t.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + + students = session.createQuery( + "select student from Student student join student.teacher t where size(t.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @Test + public void testSizeWithNestedPath2AddFetchDistinct(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + + List students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > -1", + Student.class + ).getResultList(); + // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. + // A possible optimization would be to reuse this INNER JOIN. + assertEquals( countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ), 3 ); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 0", + Student.class + ).getResultList(); + assertEquals( 3L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 1", + Student.class + ).getResultList(); + assertEquals( 2L, students.size() ); + assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); + assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); + + students = session.createQuery( + "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 2", + Student.class + ).getResultList(); + assertEquals( 0L, students.size() ); + } + ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + Student mathStudent = new Student(); + Student frenchStudent = new Student(); + Student scienceStudent = new Student(); + + Teacher teacherWithNoStudents = new Teacher(); + Teacher teacherWithOneStudent = new Teacher(); + Teacher teacherWithTwoStudents = new Teacher(); + + session.persist( teacherWithNoStudents ); + session.persist( teacherWithOneStudent ); + session.persist( teacherWithTwoStudents ); + + mathStudent.setTeacher( teacherWithOneStudent ); + teacherWithOneStudent.addStudent( mathStudent ); + + frenchStudent.setTeacher( teacherWithTwoStudents ); + teacherWithTwoStudents.addStudent( frenchStudent ); + + scienceStudent.setTeacher( teacherWithTwoStudents ); + teacherWithTwoStudents.addStudent( scienceStudent ); + + session.persist( mathStudent ); + session.persist( frenchStudent ); + session.persist( scienceStudent ); + + final SQLStatementInspector statementInspector = extractFromSession( session ); + statementInspector.clear(); + } + ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.createQuery( "delete from Student" ).executeUpdate(); + session.createQuery( "delete from Teacher" ).executeUpdate(); + session.createQuery( "delete from Skill" ).executeUpdate(); + } + ); + } + + private static int countNumberOfJoins(String query) { + String fromPart = query.toLowerCase( Locale.ROOT ).split( " from " )[1].split( " where " )[0]; + return fromPart.split( "(\\sjoin\\s|,\\s)", -1 ).length - 1; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Skill.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Skill.java new file mode 100644 index 0000000000..5aa70e3836 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Skill.java @@ -0,0 +1,50 @@ +/* + * 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.hql.size; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +/** + * @author Steve Ebersole + */ +@Entity +public class Skill { + + private Integer id; + + private Set teachers = new HashSet<>(); + + @Id + @Column(name = "skill_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToMany(mappedBy = "skills") + public Set getTeachers() { + return teachers; + } + + public void setTeachers(Set teachers) { + this.teachers = teachers; + } + + public void addTeacher(Teacher teacher) { + teachers.add( teacher ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Student.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Student.java new file mode 100644 index 0000000000..a55b01c404 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Student.java @@ -0,0 +1,46 @@ +/* + * 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.hql.size; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class Student { + + private Integer id; + + private Teacher teacher; + + @Id + @Column(name = "student_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne(optional = false) + @JoinColumn(name = "teacher_fk_id") + public Teacher getTeacher() { + return teacher; + } + + public void setTeacher(Teacher teacher) { + this.teacher = teacher; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Teacher.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Teacher.java new file mode 100644 index 0000000000..9d3af47107 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/size/Teacher.java @@ -0,0 +1,67 @@ +/* + * 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.hql.size; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; + +/** + * @author Steve Ebersole + */ +@Entity +public class Teacher { + + private Integer id; + + private Set students = new HashSet<>(); + + private Set skills = new HashSet<>(); + + @Id + @Column(name = "teacher_id") + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany(mappedBy = "teacher") + public Set getStudents() { + return students; + } + + public void setStudents(Set students) { + this.students = students; + } + + public void addStudent(Student student) { + students.add( student ); + } + + @ManyToMany + public Set getSkills() { + return skills; + } + + public void addSkill(Skill skill) { + skills.add( skill ); + skill.addTeacher( this ); + } + + public void setSkills(Set skills) { + this.skills = skills; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/CastFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/CastFunctionTest.java deleted file mode 100644 index 8262d26e8a..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/CastFunctionTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.test.hql; - -import javax.persistence.Entity; -import javax.persistence.Id; - -import org.hibernate.Session; -import org.hibernate.dialect.DerbyDialect; - -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; - -/** - * @author Steve Ebersole - */ -public class CastFunctionTest extends BaseCoreFunctionalTestCase { - @Entity( name="MyEntity" ) - public static class MyEntity { - @Id - private Integer id; - private String name; - private Double theLostNumber; - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { MyEntity.class }; - } - - @Test - public void testStringCasting() { - Session s = openSession(); - s.beginTransaction(); - - if ( getDialect() instanceof DerbyDialect ) { - // the conversion from DOUBLE to VARCHAR is not supported by Derby, - // using the short name - s.createQuery( "select cast(char(e.theLostNumber) as string) from MyEntity e" ).list(); - // using the java class name - s.createQuery( "select cast(char(e.theLostNumber) as java.lang.String) from MyEntity e" ).list(); - // using the fqn Hibernate Type name - s.createQuery( "select cast(char(e.theLostNumber) as org.hibernate.type.StringType) from MyEntity e" ) - .list(); - } - else { - // using the short name - s.createQuery( "select cast(e.theLostNumber as string) from MyEntity e" ).list(); - // using the java class name - s.createQuery( "select cast(e.theLostNumber as java.lang.String) from MyEntity e" ).list(); - // using the fqn Hibernate Type name - s.createQuery( "select cast(e.theLostNumber as org.hibernate.type.StringType) from MyEntity e" ) - .list(); - } - - s.getTransaction().commit(); - s.close(); - } - - @Test - public void testIntegerCasting() { - Session s = openSession(); - s.beginTransaction(); - - // using the short name - s.createQuery( "select cast(e.theLostNumber as integer) from MyEntity e" ).list(); - // using the java class name (primitive) - s.createQuery( "select cast(e.theLostNumber as int) from MyEntity e" ).list(); - // using the java class name - s.createQuery( "select cast(e.theLostNumber as java.lang.Integer) from MyEntity e" ).list(); - // using the fqn Hibernate Type name - s.createQuery( "select cast(e.theLostNumber as org.hibernate.type.IntegerType) from MyEntity e" ).list(); - - s.getTransaction().commit(); - s.close(); - } - - @Test - public void testLongCasting() { - Session s = openSession(); - s.beginTransaction(); - - // using the short name (also the primitive name) - s.createQuery( "select cast(e.theLostNumber as long) from MyEntity e" ).list(); - // using the java class name - s.createQuery( "select cast(e.theLostNumber as java.lang.Long) from MyEntity e" ).list(); - // using the fqn Hibernate Type name - s.createQuery( "select cast(e.theLostNumber as org.hibernate.type.LongType) from MyEntity e" ).list(); - - s.getTransaction().commit(); - s.close(); - } - - @Test - public void testFloatCasting() { - Session s = openSession(); - s.beginTransaction(); - - // using the short name (also the primitive name) - s.createQuery( "select cast(e.theLostNumber as float) from MyEntity e" ).list(); - // using the java class name - s.createQuery( "select cast(e.theLostNumber as java.lang.Float) from MyEntity e" ).list(); - // using the fqn Hibernate Type name - s.createQuery( "select cast(e.theLostNumber as org.hibernate.type.FloatType) from MyEntity e" ).list(); - - s.getTransaction().commit(); - s.close(); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java deleted file mode 100644 index f134d966ae..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/ManyToManySizeTest2.java +++ /dev/null @@ -1,563 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.test.hql.size; - -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import org.hibernate.Hibernate; -import org.hibernate.boot.SessionFactoryBuilder; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.jdbc.SQLStatementInterceptor; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@TestForIssue(jiraKey = "HHH-13944") -public class ManyToManySizeTest2 extends BaseNonConfigCoreFunctionalTestCase { - - private SQLStatementInterceptor sqlStatementInterceptor; - - @Override - protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { - sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); - } - - @Before - public void setUp() { - inTransaction( session -> { - Skill mathSkill = new Skill(); - Skill frenchSkill = new Skill(); - - session.persist( mathSkill ); - session.persist( frenchSkill ); - - Teacher teacherWithNoSkills = new Teacher(); - - Teacher teacherWithOneSkill = new Teacher(); - teacherWithOneSkill.addSkill( mathSkill ); - - Teacher teacherWithTwoSkills = new Teacher(); - teacherWithTwoSkills.addSkill( mathSkill ); - teacherWithTwoSkills.addSkill( frenchSkill ); - - session.persist( teacherWithNoSkills ); - session.persist( teacherWithOneSkill ); - session.persist( teacherWithTwoSkills ); - - Student studentWithTeacherWithNoSkills = new Student(); - studentWithTeacherWithNoSkills.setTeacher( teacherWithNoSkills ); - session.persist( studentWithTeacherWithNoSkills ); - - Student studentWithTeacherWithOneSkill = new Student(); - studentWithTeacherWithOneSkill.setTeacher( teacherWithOneSkill ); - session.persist( studentWithTeacherWithOneSkill ); - - Student studentWithTeacherWithTwoSkills = new Student(); - studentWithTeacherWithTwoSkills.setTeacher( teacherWithTwoSkills ); - session.persist( studentWithTeacherWithTwoSkills ); - } ); - sqlStatementInterceptor.clear(); - } - - @After - public void tearDown() { - inTransaction( session -> { - session.createQuery( "delete from Student" ).executeUpdate(); - session.createQuery( "delete from Teacher" ).executeUpdate(); - session.createQuery( "delete from Skill" ).executeUpdate(); - } ); - } - - @Test - public void testSize() { - inTransaction( session -> { - List teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 2", - Teacher.class - ).getResultList(); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - - // Using "join" (instead of "left join") removes the teacher with no skills from the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.skills skills where size(skills) > -1", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - - // Using "left join" includes the teacher with no skills in the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join teacher.skills skills where size(skills) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - } ); - } - - @Test - public void testSizeAddFetch() { - inTransaction( session -> { - List teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 2", - Teacher.class - ).getResultList(); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); - - - // Using "join" (instead of "left join") removes the teacher with no skills from the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills join teacher.skills skills where size(skills) > -1", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); - - // Using "left join" includes the teacher with no skills in the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills left join teacher.skills skills where size(skills) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getSkills() ) ); - } ); - } - - @Test - public void testSizeWithoutNestedPath() { - inTransaction( session -> { - List teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.skills) > 2", - Teacher.class - ).getResultList(); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.skills) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - - teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.skills) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - - // If there is no "join", then results include the teacher with no skills - teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.skills) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - } ); - } - - @Test - public void testSizeWithoutNestedPathAddFetchDistinct() { - inTransaction( session -> { - List teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 2", - Teacher.class - ).getResultList(); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 2 ); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.skills where size(teacher.skills) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getSkills() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getSkills() ) ); - } ); - } - - @Test - public void testSizeWithNestedPathAndImplicitJoin() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select student from Student student where size(student.teacher.skills) > 2", - Student.class - ).getResultList(); - assertEquals( 0, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - - students = em.createQuery( - "select student from Student student where size(student.teacher.skills) > 1", - Student.class - ).getResultList(); - assertEquals( 1, students.size() ); - - students = em.createQuery( - "select student from Student student where size(student.teacher.skills) > 0", - Student.class - ).getResultList(); - assertEquals( 2, students.size() ); - - // If there is no "join" in the query, then results include the student with the teacher with no skills - students = em.createQuery( - "select student from Student student where size(student.teacher.skills) > -1", - Student.class - ).getResultList(); - assertEquals( 3, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPathAndImplicitJoinAddFetchDistinct() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 2", - Student.class - ).getResultList(); - assertEquals( 0, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 3 ); - - students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 1", - Student.class - ).getResultList(); - assertEquals( 1, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > 0", - Student.class - ).getResultList(); - assertEquals( 2, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.skills where size(student.teacher.skills) > -1", - Student.class - ).getResultList(); - assertEquals( 3, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); - } ); - } - - @Test - public void testSizeWithNestedPath() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.skills) > -1", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.skills) > 0", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.skills) > 1", - Student.class - ).getResultList(); - assertEquals( 1L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.skills) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPathAddFetchDistinct() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > -1", - Student.class - ).getResultList(); - // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. - // A possible optimization would be to reuse this INNER JOIN. - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 4 ); - assertEquals( 3L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 0", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 1", - Student.class - ).getResultList(); - assertEquals( 1L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.skills where size(student.teacher.skills) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPath2() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select student from Student student join student.teacher t where size(t.skills) > -1", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(t.skills) > 0", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(t.skills) > 1", - Student.class - ).getResultList(); - assertEquals( 1L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(t.skills) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPath2AddFetchDistinct() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > -1", - Student.class - ).getResultList(); - // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. - // A possible optimization would be to reuse this INNER JOIN. - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 4 ); - assertEquals( 3L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 0", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 1", - Student.class - ).getResultList(); - assertEquals( 1L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getSkills() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch left join fetch tfetch.skills where size(t.skills) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Skill.class, Teacher.class, Student.class }; - } - - @Entity(name = "Skill") - public static class Skill { - - private Integer id; - - private Set teachers = new HashSet<>(); - - @Id - @Column(name = "skill_id") - @GeneratedValue - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @ManyToMany(mappedBy = "skills") - public Set getTeachers() { - return teachers; - } - - public void setTeachers(Set teachers) { - this.teachers = teachers; - } - - public void addTeacher(Teacher teacher) { - teachers.add( teacher ); - } - } - - @Entity(name = "Teacher") - public static class Teacher { - - private Integer id; - - private Set students = new HashSet<>(); - - private Set skills = new HashSet<>(); - - @Id - @Column(name = "teacher_id") - @GeneratedValue - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @OneToMany(mappedBy = "teacher") - public Set getStudents() { - return students; - } - - public void setStudents(Set students) { - this.students = students; - } - - public void addStudent(Student student) { - students.add( student ); - } - - @ManyToMany - public Set getSkills() { - return skills; - } - - public void addSkill(Skill skill) { - skills.add( skill ); - skill.addTeacher( this ); - } - - public void setSkills(Set skills) { - this.skills = skills; - } - } - - @Entity(name = "Student") - public static class Student { - - private Integer id; - - private Teacher teacher; - - @Id - @Column(name = "student_id") - @GeneratedValue - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @ManyToOne(optional = false) - @JoinColumn(name = "teacher_fk_id") - public Teacher getTeacher() { - return teacher; - } - - public void setTeacher(Teacher teacher) { - this.teacher = teacher; - teacher.addStudent( this ); - } - } - - private static int countNumberOfJoins(String query) { - String fromPart = query.toLowerCase( Locale.ROOT ).split( " from " )[1].split( " where " )[0]; - return fromPart.split( "(\\sjoin\\s|,\\s)", -1 ).length - 1; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java deleted file mode 100644 index 5670173021..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/OneToManySizeTest2.java +++ /dev/null @@ -1,560 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.test.hql.size; - -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import org.hibernate.Hibernate; -import org.hibernate.boot.SessionFactoryBuilder; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.jdbc.SQLStatementInterceptor; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@TestForIssue(jiraKey = "HHH-13944") -public class OneToManySizeTest2 extends BaseNonConfigCoreFunctionalTestCase { - - private SQLStatementInterceptor sqlStatementInterceptor; - - @Override - protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { - sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); - } - - @Before - public void setUp() { - inTransaction( session -> { - Student mathStudent = new Student(); - Student frenchStudent = new Student(); - Student scienceStudent = new Student(); - - Teacher teacherWithNoStudents = new Teacher(); - Teacher teacherWithOneStudent = new Teacher(); - Teacher teacherWithTwoStudents = new Teacher(); - - session.persist( teacherWithNoStudents ); - session.persist( teacherWithOneStudent ); - session.persist( teacherWithTwoStudents ); - - mathStudent.setTeacher( teacherWithOneStudent ); - teacherWithOneStudent.addStudent( mathStudent ); - - frenchStudent.setTeacher( teacherWithTwoStudents ); - teacherWithTwoStudents.addStudent( frenchStudent ); - - scienceStudent.setTeacher( teacherWithTwoStudents ); - teacherWithTwoStudents.addStudent( scienceStudent ); - - session.persist( mathStudent ); - session.persist( frenchStudent ); - session.persist( scienceStudent ); - } ); - sqlStatementInterceptor.clear(); - } - - @After - public void tearDown() { - inTransaction( session -> { - session.createQuery( "delete from Student" ).executeUpdate(); - session.createQuery( "delete from Teacher" ).executeUpdate(); - session.createQuery( "delete from Skill" ).executeUpdate(); - } ); - } - - @Test - public void testSize() { - inTransaction( session -> { - List teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 2", - Teacher.class - ).getResultList(); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.students students where size(students) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher join teacher.students students where size(students) > -1", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - - // Using "left join" includes the teacher with no students in the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join teacher.students students where size(students) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - } ); - } - - @Test - public void testSizeAddFetch() { - inTransaction( session -> { - List teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 2", - Teacher.class - ).getResultList(); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); - - - // Using "join" (instead of "left join") removes the teacher with no students from the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students join teacher.students students where size(students) > -1", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); - - // Using "left join" includes the teacher with no students in the results. - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students left join teacher.students students where size(students) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getStudents() ) ); - } ); - } - - @Test - public void testSizeWithoutNestedPath() { - inTransaction( session -> { - List teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.students) > 2", - Teacher.class - ).getResultList(); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.students) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - - teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.students) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - - // If there is no "join", then results include the teacher with no students - teachers = session.createQuery( - "select teacher from Teacher teacher where size(teacher.students) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - } ); - } - - @Test - public void testSizeWithoutNestedPathAddFetchDistinct() { - inTransaction( session -> { - List teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 2", - Teacher.class - ).getResultList(); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - assertEquals( 0, teachers.size() ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 1", - Teacher.class - ).getResultList(); - assertEquals( 1, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > 0", - Teacher.class - ).getResultList(); - assertEquals( 2, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); - - teachers = session.createQuery( - "select distinct teacher from Teacher teacher left join fetch teacher.students where size(teacher.students) > -1", - Teacher.class - ).getResultList(); - assertEquals( 3, teachers.size() ); - assertTrue( Hibernate.isInitialized( teachers.get( 0 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 1 ).getStudents() ) ); - assertTrue( Hibernate.isInitialized( teachers.get( 2 ).getStudents() ) ); - } ); - } - - @Test - public void testSizeWithNestedPathAndImplicitJoin() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select student from Student student where size(student.teacher.students) > 2", - Student.class - ).getResultList(); - assertEquals( 0, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - - students = em.createQuery( - "select student from Student student where size(student.teacher.students) > 1", - Student.class - ).getResultList(); - assertEquals( 2, students.size() ); - - students = em.createQuery( - "select student from Student student where size(student.teacher.students) > 0", - Student.class - ).getResultList(); - assertEquals( 3, students.size() ); - - students = em.createQuery( - "select student from Student student where size(student.teacher.students) > -1", - Student.class - ).getResultList(); - assertEquals( 3, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPathAndImplicitJoinAddFetchDistinct() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > 2", - Student.class - ).getResultList(); - assertEquals( 0, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 2 ); - - students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > 1", - Student.class - ).getResultList(); - assertEquals( 2, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > 0", - Student.class - ).getResultList(); - assertEquals( 3, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student left join fetch student.teacher t left join fetch t.students where size(student.teacher.students) > -1", - Student.class - ).getResultList(); - assertEquals( 3, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); - } ); - } - - @Test - public void testSizeWithNestedPath() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.students) > -1", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.students) > 0", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.students) > 1", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(student.teacher.students) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPathAddFetchDistinct() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > -1", - Student.class - ).getResultList(); - // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. - // A possible optimization would be to reuse this INNER JOIN. - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 3 ); - assertEquals( 3L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > 0", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > 1", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tjoin left join fetch tjoin.students where size(student.teacher.students) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPath2() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select student from Student student join student.teacher t where size(t.students) > -1", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 1 ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(t.students) > 0", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(t.students) > 1", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - - students = em.createQuery( - "select student from Student student join student.teacher t where size(t.students) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Test - public void testSizeWithNestedPath2AddFetchDistinct() { - doInJPA( this::sessionFactory, em -> { - List students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > -1", - Student.class - ).getResultList(); - // NOTE: An INNER JOIN is done on Teacher twice, which results in 4 joins. - // A possible optimization would be to reuse this INNER JOIN. - assertEquals( countNumberOfJoins( sqlStatementInterceptor.getSqlQueries().get( 0 ) ), 3 ); - assertEquals( 3L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 0", - Student.class - ).getResultList(); - assertEquals( 3L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 2 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 1", - Student.class - ).getResultList(); - assertEquals( 2L, students.size() ); - assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) ); - assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) ); - - students = em.createQuery( - "select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > 2", - Student.class - ).getResultList(); - assertEquals( 0L, students.size() ); - } ); - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Skill.class, Teacher.class, Student.class }; - } - - @Entity(name = "Skill") - public static class Skill { - - private Integer id; - - private Set teachers = new HashSet<>(); - - @Id - @Column(name = "skill_id") - @GeneratedValue - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @ManyToMany(mappedBy = "skills") - public Set getTeachers() { - return teachers; - } - - public void setTeachers(Set teachers) { - this.teachers = teachers; - } - - public void addTeacher(Teacher teacher) { - teachers.add( teacher ); - } - } - - @Entity(name = "Teacher") - public static class Teacher { - - private Integer id; - - private Set students = new HashSet<>(); - - private Set skills = new HashSet<>(); - - @Id - @Column(name = "teacher_id") - @GeneratedValue - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @OneToMany(mappedBy = "teacher") - public Set getStudents() { - return students; - } - - public void setStudents(Set students) { - this.students = students; - } - - public void addStudent(Student student) { - students.add( student ); - } - - @ManyToMany - public Set getSkills() { - return skills; - } - - public void addSkill(Skill skill) { - skills.add( skill ); - skill.addTeacher( this ); - } - - public void setSkills(Set skills) { - this.skills = skills; - } - } - - @Entity(name = "Student") - public static class Student { - - private Integer id; - - private Teacher teacher; - - @Id - @Column(name = "student_id") - @GeneratedValue - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @ManyToOne(optional = false) - @JoinColumn(name = "teacher_fk_id") - public Teacher getTeacher() { - return teacher; - } - - public void setTeacher(Teacher teacher) { - this.teacher = teacher; - } - } - - private static int countNumberOfJoins(String query) { - String fromPart = query.toLowerCase( Locale.ROOT ).split( " from " )[1].split( " where " )[0]; - return fromPart.split( "(\\sjoin\\s|,\\s)", -1 ).length - 1; - } -} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java index b236659313..d461327da2 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java @@ -10,6 +10,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.resource.jdbc.spi.StatementInspector; import static org.hamcrest.CoreMatchers.is; @@ -40,10 +41,20 @@ public class SQLStatementInspector implements StatementInspector { sqlQueries.clear(); } + public int getNumberOfJoins(int position) { + final String sql = sqlQueries.get( position ); + String fromPart = sql.toLowerCase( Locale.ROOT ).split( " from " )[1].split( " where " )[0]; + return fromPart.split( "(\\sjoin\\s|,\\s)", -1 ).length - 1; + } + public void assertExecuted(String expected) { assertTrue( sqlQueries.contains( expected ) ); } + public void assertNumberOfJoins(int queryNumber, int expectedNumberOfJoins) { + assertNumberOfOccurrenceInQuery( queryNumber, "join", expectedNumberOfJoins ); + } + public void assertExecutedCount(int expected) { assertEquals( "Number of executed statements ",expected, sqlQueries.size() ); } @@ -51,7 +62,7 @@ public class SQLStatementInspector implements StatementInspector { public void assertNumberOfOccurrenceInQuery(int queryNumber, String toCheck, int expectedNumberOfOccurrences) { String query = sqlQueries.get( queryNumber ); int actual = query.split( " " + toCheck + " ", -1 ).length - 1; - assertThat( "number of " + toCheck,actual, is( expectedNumberOfOccurrences ) ); + assertThat( "number of " + toCheck, actual, is( expectedNumberOfOccurrences ) ); } public void assertIsSelect(int queryNumber) { @@ -68,4 +79,8 @@ public class SQLStatementInspector implements StatementInspector { String query = sqlQueries.get( queryNumber ); assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "update" ) ); } + + public static SQLStatementInspector extractFromSession(SessionImplementor session) { + return (SQLStatementInspector) session.getJdbcSessionContext().getStatementInspector(); + } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java index 54e0030b2e..bb46d38a58 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java @@ -16,6 +16,7 @@ import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.integrator.spi.Integrator; import org.hibernate.service.spi.ServiceContributor; @@ -96,6 +97,8 @@ public class ServiceRegistryExtension throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); } + // set some baseline test settings + ssrb.applySetting( AvailableSettings.STATEMENT_INSPECTOR, org.hibernate.testing.jdbc.SQLStatementInspector.class ); final Optional ssrAnnWrapper = AnnotationSupport.findAnnotation( context.getElement().get(),