HHH-2049 : LEFT OUTER JOIN subcriteria filters children (Mattias Jiderhamn)

This commit is contained in:
Gail Badner 2011-05-03 14:29:26 -07:00
parent 29bbef741a
commit 93016967b9
9 changed files with 579 additions and 4 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -238,4 +238,7 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker {
return translator.getWithClause( path.getFullPath() );
}
protected boolean hasRestriction(PropertyPath path) {
return translator.hasRestriction( path.getFullPath() );
}
}

View File

@ -637,4 +637,11 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
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();
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.test.criteria">
<class name="Order" table="t_order">
<id name="orderId" column="order_id" type="int" unsaved-value="0" access="field" >
<generator class="identity" />
</id>
<set name="orderLines" cascade="all-delete-orphan" access="field" inverse="true" fetch="select">
<key column="order_id" />
<one-to-many class="OrderLine" />
</set>
</class>
<class name="OrderLine" table="order_line">
<id name="lineId" column="order_line_id" type="int" unsaved-value="0" access="field" >
<generator class="identity" />
</id>
<many-to-one name="order" column="order_id" class="Order" />
<property name="articleId" column="article_id" type="string" />
</class>
</hibernate-mapping>

View File

@ -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<OrderLine> orderLines = new HashSet<OrderLine>();
public Set<OrderLine> getLines() {
return Collections.unmodifiableSet(orderLines);
}
public void addLine(OrderLine orderLine){
orderLine.setOrder(this);
this.orderLines.add(orderLine);
}
public String toString() {
return "" + getOrderId() + " - " + getLines();
}
}

View File

@ -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() + "]";
}
}

View File

@ -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;
}
}