From 93016967b99ac5cde990fbb32491f224f377bd54 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 3 May 2011 14:29:26 -0700 Subject: [PATCH] HHH-2049 : LEFT OUTER JOIN subcriteria filters children (Mattias Jiderhamn) --- .../java/org/hibernate/impl/CriteriaImpl.java | 6 + .../java/org/hibernate/loader/JoinWalker.java | 11 +- .../loader/OuterJoinableAssociation.java | 8 + .../loader/criteria/CriteriaJoinWalker.java | 5 +- .../criteria/CriteriaQueryTranslator.java | 9 +- .../org/hibernate/test/criteria/Order.hbm.xml | 22 + .../org/hibernate/test/criteria/Order.java | 50 +++ .../hibernate/test/criteria/OrderLine.java | 58 +++ .../test/criteria/OuterJoinCriteriaTest.java | 414 ++++++++++++++++++ 9 files changed, 579 insertions(+), 4 deletions(-) create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.java create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/impl/CriteriaImpl.java b/hibernate-core/src/main/java/org/hibernate/impl/CriteriaImpl.java index a484ca043a..3167768a76 100644 --- a/hibernate-core/src/main/java/org/hibernate/impl/CriteriaImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/impl/CriteriaImpl.java @@ -416,6 +416,7 @@ public class CriteriaImpl implements Criteria, Serializable { private LockMode lockMode; private int joinType; private Criterion withClause; + private boolean hasRestriction; // Constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -425,6 +426,7 @@ public class CriteriaImpl implements Criteria, Serializable { this.parent = parent; this.joinType = joinType; this.withClause = withClause; + this.hasRestriction = withClause != null; CriteriaImpl.this.subcriteriaList.add( this ); } @@ -479,10 +481,14 @@ public class CriteriaImpl implements Criteria, Serializable { return this.withClause; } + public boolean hasRestriction() { + return hasRestriction; + } // Criteria impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Criteria add(Criterion expression) { + hasRestriction = true; CriteriaImpl.this.add(this, expression); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java index 1201eba9e5..41cdc648b1 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java @@ -207,6 +207,10 @@ public class JoinWalker { } } + protected boolean hasRestriction(PropertyPath path) { + return false; + } + protected String getWithClause(PropertyPath path) { return ""; } @@ -241,6 +245,7 @@ public class JoinWalker { subalias, joinType, getWithClause(path), + hasRestriction( path ), getFactory(), loadQueryInfluencers.getEnabledFilters() ); @@ -877,7 +882,9 @@ public class JoinWalker { Iterator iter = associations.iterator(); while ( iter.hasNext() ) { OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next(); - if ( oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN && oj.getJoinable().isCollection() ) { + if ( oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN && + oj.getJoinable().isCollection() && + ! oj.hasRestriction() ) { result++; } } @@ -1012,7 +1019,7 @@ public class JoinWalker { else { QueryableCollection collPersister = (QueryableCollection) oj.getJoinable(); - if ( oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN ) { + if ( oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN && ! oj.hasRestriction() ) { //it must be a collection fetch collectionPersisters[j] = collPersister; collectionOwners[j] = oj.getOwner(associations); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java b/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java index 2392577e09..87d3676ba0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java @@ -53,6 +53,7 @@ public final class OuterJoinableAssociation { private final int joinType; private final String on; private final Map enabledFilters; + private final boolean hasRestriction; public static OuterJoinableAssociation createRoot( AssociationType joinableType, @@ -66,6 +67,7 @@ public final class OuterJoinableAssociation { alias, JoinFragment.LEFT_OUTER_JOIN, null, + false, factory, CollectionHelper.EMPTY_MAP ); @@ -79,6 +81,7 @@ public final class OuterJoinableAssociation { String rhsAlias, int joinType, String withClause, + boolean hasRestriction, SessionFactoryImplementor factory, Map enabledFilters) throws MappingException { this.propertyPath = propertyPath; @@ -91,6 +94,7 @@ public final class OuterJoinableAssociation { this.rhsColumns = JoinHelper.getRHSColumnNames(joinableType, factory); this.on = joinableType.getOnCondition(rhsAlias, factory, enabledFilters) + ( withClause == null || withClause.trim().length() == 0 ? "" : " and ( " + withClause + " )" ); + this.hasRestriction = hasRestriction; this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application } @@ -140,6 +144,10 @@ public final class OuterJoinableAssociation { return joinable; } + public boolean hasRestriction() { + return hasRestriction; + } + public int getOwner(final List associations) { if ( isOneToOne() || isCollection() ) { return getPosition(lhsAlias, associations); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java index 633527652e..2764189075 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java @@ -237,5 +237,8 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker { protected String getWithClause(PropertyPath path) { return translator.getWithClause( path.getFullPath() ); } - + + protected boolean hasRestriction(PropertyPath path) { + return translator.hasRestriction( path.getFullPath() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java index bdcaad0d9a..456d2d0ca2 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java @@ -636,5 +636,12 @@ public class CriteriaQueryTranslator implements CriteriaQuery { final Criterion crit = (Criterion)this.withClauseMap.get(path); return crit == null ? null : crit.toSqlString(getCriteria(path), this); } - + + public boolean hasRestriction(String path) + { + final CriteriaImpl.Subcriteria crit = ( CriteriaImpl.Subcriteria ) getCriteria( path ); + return crit == null ? false : crit.hasRestriction(); + } + + } diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml new file mode 100644 index 0000000000..ba594d469d --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.java b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.java new file mode 100644 index 0000000000..63f3deff5d --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/Order.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.criteria; + +import java.util.*; + +public class Order { + + private int orderId; + + public int getOrderId() { + return orderId; + } + + private Set orderLines = new HashSet(); + + public Set getLines() { + return Collections.unmodifiableSet(orderLines); + } + + public void addLine(OrderLine orderLine){ + orderLine.setOrder(this); + this.orderLines.add(orderLine); + } + + public String toString() { + return "" + getOrderId() + " - " + getLines(); + } +} \ No newline at end of file diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java new file mode 100644 index 0000000000..77f35a7544 --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.criteria; + +public class OrderLine { + + private int lineId = 0; + + private Order order; + + private String articleId; + + + public int getLineId() { + return lineId; + } + + public Order getOrder() { + return order; + } + + public String getArticleId() { + return articleId; + } + + public void setOrder(Order order) { + this.order = order; + } + + public void setArticleId(String articleId) { + this.articleId = articleId; + } + + public String toString() { + return "[" + getLineId() + ":" + getArticleId() + "]"; + } +} \ No newline at end of file diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java new file mode 100644 index 0000000000..a29b305e84 --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java @@ -0,0 +1,414 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.criteria; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.hibernate.Criteria; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; +import org.hibernate.sql.JoinFragment; +import org.hibernate.testing.junit.functional.FunctionalTestCase; + +/** + * @author Mattias Jiderhamn + * @author Gail Badner + */ +public class OuterJoinCriteriaTest extends FunctionalTestCase { + private Order order1; + private Order order2; + private Order order3; + + public OuterJoinCriteriaTest(String s) { + super( s ); + } + + public String[] getMappings() { + return new String[] { "criteria/Order.hbm.xml" }; + } + + public void testSubcriteriaWithNonNullRestrictions() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class ); + Criteria subCriteria = rootCriteria.createCriteria( "orderLines", JoinFragment.LEFT_OUTER_JOIN ); + assertNotSame( rootCriteria, subCriteria ); + + // add restrictions to subCriteria, ensuring we stay on subCriteria + assertSame( subCriteria, subCriteria.add( Restrictions.eq( "articleId", "3000" ) ) ); + + List orders = rootCriteria.list(); + + // order1 and order3 should be returned because each has articleId == "3000" + // both should have their full collection + assertEquals( 2, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Order o = (Order) it.next(); + if ( order1.getOrderId() == o.getOrderId() ) { + assertEquals( order1.getLines().size(), o.getLines().size() ); + } + else if ( order3.getOrderId() == o.getOrderId() ) { + assertEquals( order3.getLines().size(), o.getLines().size() ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + public void testSubcriteriaWithNonNullRestrictionsAliasToEntityMap() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class, "o" ); + Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ); + assertNotSame( rootCriteria, subCriteria ); + + // add restriction to subCriteria, ensuring we stay on subCriteria + assertSame( subCriteria, subCriteria.add( Restrictions.eq( "articleId", "3000" ) ) ); + + List orders = rootCriteria.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP ).list(); + + // order1 and order3 should be returned because each has articleId == "3000"; + // the orders should both should have their full collection; + assertEquals( 2, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Map map = (Map) it.next(); + Order o = ( Order ) map.get( "o" ); + // the orderLine returned from the map should have articleId = "3000" + OrderLine ol = ( OrderLine ) map.get( "ol" ); + if ( order1.getOrderId() == o.getOrderId() ) { + assertEquals( order1.getLines().size(), o.getLines().size() ); + assertEquals( "3000", ol.getArticleId() ); + } + else if ( order3.getOrderId() == o.getOrderId() ) { + assertEquals( order3.getLines().size(), o.getLines().size() ); + assertEquals( "3000", ol.getArticleId() ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + + public void testSubcriteriaWithNullOrNonNullRestrictions() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class ); + Criteria subCriteria = rootCriteria.createCriteria( "orderLines", JoinFragment.LEFT_OUTER_JOIN ); + assertNotSame( rootCriteria, subCriteria ); + + // add restrictions to subCriteria, ensuring we stay on subCriteria + // add restriction to subCriteria, ensuring we stay on subCriteria + assertSame( + subCriteria, + subCriteria.add( + Restrictions.or( + Restrictions.isNull( "articleId" ), // Allow null + Restrictions.eq( "articleId", "1000" ) + ) + ) + ); + + List orders = rootCriteria.list(); + + // order1 should be returned because it has an orderline with articleId == "1000"; + // order2 should be returned because it has no orderlines + assertEquals( 2, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Order o = ( Order ) it.next(); + if ( order1.getOrderId() == o.getOrderId() ) { + // o.getLines() should contain all of its orderLines + assertEquals( order1.getLines().size(), o.getLines().size() ); + } + else if ( order2.getOrderId() == o.getOrderId() ) { + assertEquals( order2.getLines() , o.getLines() ); + assertTrue( o.getLines().isEmpty() ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + public void testSubcriteriaWithNullOrNonNullRestrictionsAliasToEntityMap() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class, "o" ); + Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ); + assertNotSame( rootCriteria, subCriteria ); + + // add restriction to subCriteria, ensuring we stay on subCriteria + assertSame( + subCriteria, + subCriteria.add( + Restrictions.or( + Restrictions.isNull( "ol.articleId" ), // Allow null + Restrictions.eq( "ol.articleId", "1000" ) + ) + ) + ); + + List orders = rootCriteria.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP ).list(); + + // order1 should be returned because it has an orderline with articleId == "1000"; + // order2 should be returned because it has no orderlines + assertEquals( 2, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Map map = (Map) it.next(); + Order o = ( Order ) map.get( "o" ); + // the orderLine returned from the map should either be null or have articleId = "1000" + OrderLine ol = ( OrderLine ) map.get( "ol" ); + if ( order1.getOrderId() == o.getOrderId() ) { + // o.getLines() should contain all of its orderLines + assertEquals( order1.getLines().size(), o.getLines().size() ); + assertNotNull( ol ); + assertEquals( "1000", ol.getArticleId() ); + } + else if ( order2.getOrderId() == o.getOrderId() ) { + assertEquals( order2.getLines() , o.getLines() ); + assertTrue( o.getLines().isEmpty() ); + assertNull( ol ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + public void testSubcriteriaWithClauseAliasToEntityMap() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class, "o" ); + Criteria subCriteria = rootCriteria.createCriteria( + "orderLines", + "ol", JoinFragment.LEFT_OUTER_JOIN, + Restrictions.or( + Restrictions.isNull( "ol.articleId" ), // Allow null + Restrictions.eq( "ol.articleId", "1000" ) + ) + ); + assertNotSame( rootCriteria, subCriteria ); + + List orders = rootCriteria.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP ).list(); + + // all orders should be returned (via map.get( "o" )) with their full collections; + assertEquals( 3, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Map map = ( Map ) it.next(); + Order o = ( Order ) map.get( "o" ); + // the orderLine returned from the map should either be null or have articleId = "1000" + OrderLine ol = ( OrderLine ) map.get( "ol" ); + if ( order1.getOrderId() == o.getOrderId() ) { + // o.getLines() should contain all of its orderLines + assertEquals( order1.getLines().size(), o.getLines().size() ); + assertNotNull( ol ); + assertEquals( "1000", ol.getArticleId() ); + } + else if ( order2.getOrderId() == o.getOrderId() ) { + assertTrue( o.getLines().isEmpty() ); + assertNull( ol ); + } + else if ( order3.getOrderId() == o.getOrderId() ) { + assertEquals( order3.getLines().size(), o.getLines().size() ); + assertNull( ol); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + public void testAliasWithNonNullRestrictions() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class ); + // create alias, ensuring we stay on the root criteria + assertSame( rootCriteria, rootCriteria.createAlias( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ) ); + + // add restrictions to rootCriteria + assertSame( rootCriteria, rootCriteria.add( Restrictions.eq( "ol.articleId", "3000" ) ) ); + + List orders = rootCriteria.list(); + + // order1 and order3 should be returned because each has articleId == "3000" + // the contained collections should only have the orderLine with articleId == "3000" + assertEquals( 2, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Order o = (Order) it.next(); + if ( order1.getOrderId() == o.getOrderId() ) { + assertEquals( 1, o.getLines().size() ); + assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + } + else if ( order3.getOrderId() == o.getOrderId() ) { + assertEquals( 1, o.getLines().size() ); + assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + public void testAliasWithNullOrNonNullRestrictions() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class ); + // create alias, ensuring we stay on the root criteria + assertSame( rootCriteria, rootCriteria.createAlias( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ) ); + + // add restrictions to rootCriteria + assertSame( + rootCriteria, + rootCriteria.add( + Restrictions.or( + Restrictions.isNull( "ol.articleId" ), // Allow null + Restrictions.eq( "ol.articleId", "1000" ) + ) + ) + ); + + List orders = rootCriteria.list(); + + // order1 should be returned because it has an orderline with articleId == "1000"; + // the contained collection for order1 should only have the orderLine with articleId == "1000"; + // order2 should be returned because it has no orderlines + assertEquals( 2, orders.size() ); + for ( Object order : orders ) { + Order o = (Order) order; + if ( order1.getOrderId() == o.getOrderId() ) { + assertEquals( "1000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + } + else if ( order2.getOrderId() == o.getOrderId() ) { + assertEquals( 0, o.getLines().size() ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + public void testNonNullSubcriteriaRestrictionsOnRootCriteria() { + Session s = openSession(); + s.getTransaction().begin(); + + Criteria rootCriteria = s.createCriteria( Order.class ); + Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ); + assertNotSame( rootCriteria, subCriteria ); + + // add restriction to rootCriteria (NOT subcriteria) + assertSame( rootCriteria, rootCriteria.add( Restrictions.eq( "ol.articleId", "3000" ) ) ); + + List orders = rootCriteria.list(); + + // results should be the same as testAliasWithNonNullRestrictions() (using Criteria.createAlias()) + // order1 and order3 should be returned because each has articleId == "3000" + // the contained collections should only have the orderLine with articleId == "3000" + assertEquals( 2, orders.size() ); + for ( Iterator it = orders.iterator(); it.hasNext(); ) { + Order o = (Order) it.next(); + if ( order1.getOrderId() == o.getOrderId() ) { + assertEquals( 1, o.getLines().size() ); + assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + } + else if ( order3.getOrderId() == o.getOrderId() ) { + assertEquals( 1, o.getLines().size() ); + assertEquals( "3000", ( ( OrderLine ) o.getLines().iterator().next() ).getArticleId() ); + } + else { + fail( "unknown order" ); + } + } + s.getTransaction().commit(); + s.close(); + } + + protected void prepareTest() { + Session s = openSession(); + s.getTransaction().begin(); + + // Order with one mathing line + order1 = new Order(); + OrderLine line = new OrderLine(); + line.setArticleId( "1000" ); + order1.addLine( line ); + line = new OrderLine(); + line.setArticleId( "3000" ); + order1.addLine( line ); + s.persist( order1 ); + + // Order with no lines + order2 = new Order(); + s.persist( order2 ); + + // Order with non-matching line + order3 = new Order(); + line = new OrderLine(); + line.setArticleId( "3000" ); + order3.addLine( line ); + s.persist( order3 ); + + s.getTransaction().commit(); + s.close(); + } + + protected void cleanupTest() { + Session s = openSession(); + s.getTransaction().begin(); + + s.createQuery( "delete from OrderLine" ).executeUpdate(); + + s.createQuery( "delete from Order" ).executeUpdate(); + + s.getTransaction().commit(); + s.close(); + } + + private static boolean isBlank(String s) { + return s == null || s.trim().length() == 0; + } +} \ No newline at end of file