[hibernate-commits] Hibernate SVN: r20973 - in core/branches/Branch_3_3_2_GA_CP: core/src/main/java/org/hibernate/loader and 2 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Mon Aug 22 21:49:52 EDT 2011


Author: stliu
Date: 2011-08-22 21:49:52 -0400 (Mon, 22 Aug 2011)
New Revision: 20973

Added:
   core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml
   core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.java
   core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java
   core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java
Modified:
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/CriteriaImpl.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/JoinWalker.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java
Log:
JBPAPP-6395 HHH-2049 - LEFT OUTER JOIN subcriteria filters children

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/CriteriaImpl.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/CriteriaImpl.java	2011-06-28 19:34:11 UTC (rev 20972)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/CriteriaImpl.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -374,6 +374,7 @@
 		private Criteria parent;
 		private LockMode lockMode;
 		private int joinType;
+		private boolean hasRestriction;
 
 
 		// Constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -428,12 +429,16 @@
 		public int getJoinType() {
 			return joinType;
 		}
+		public boolean hasRestriction(){
+			return hasRestriction;
+		}
 
 
 		// Criteria impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 		public Criteria add(Criterion expression) {
 			CriteriaImpl.this.add(this, expression);
+			hasRestriction = true;
 			return this;
 		}
 

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/JoinWalker.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/JoinWalker.java	2011-06-28 19:34:11 UTC (rev 20972)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/JoinWalker.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -60,12 +60,12 @@
 /**
  * Walks the metamodel, searching for joins, and collecting
  * together information needed by <tt>OuterJoinLoader</tt>.
- * 
+ *
  * @see OuterJoinLoader
  * @author Gavin King, Jon Lipsky
  */
 public class JoinWalker {
-	
+
 	private final SessionFactoryImplementor factory;
 	protected final List associations = new ArrayList();
 	private final Set visitedAssociationKeys = new HashSet();
@@ -81,7 +81,7 @@
 	protected String[] aliases;
 	protected LockMode[] lockModeArray;
 	protected String sql;
-	
+
 	public String[] getCollectionSuffixes() {
 		return collectionSuffixes;
 	}
@@ -169,7 +169,7 @@
 	protected Dialect getDialect() {
 		return factory.getDialect();
 	}
-	
+
 	protected Map getEnabledFilters() {
 		return enabledFilters;
 	}
@@ -180,7 +180,7 @@
 	}
 
 	/**
-	 * Add on association (one-to-one, many-to-one, or a collection) to a list 
+	 * Add on association (one-to-one, many-to-one, or a collection) to a list
 	 * of associations to be fetched by outerjoin (if necessary)
 	 */
 	private void addAssociationToJoinTreeIfNecessary(
@@ -191,12 +191,12 @@
 		int currentDepth,
 		final int joinType)
 	throws MappingException {
-		
-		if (joinType>=0) {	
+
+		if (joinType>=0) {
 			addAssociationToJoinTree(
-					type, 
-					aliasedLhsColumns, 
-					alias, 
+					type,
+					aliasedLhsColumns,
+					alias,
 					path,
 					currentDepth,
 					joinType
@@ -206,8 +206,8 @@
 	}
 
 	/**
-	 * Add on association (one-to-one, many-to-one, or a collection) to a list 
-	 * of associations to be fetched by outerjoin 
+	 * Add on association (one-to-one, many-to-one, or a collection) to a list
+	 * of associations to be fetched by outerjoin
 	 */
 	private void addAssociationToJoinTree(
 		final AssociationType type,
@@ -222,17 +222,18 @@
 
 		String subalias = generateTableAlias(
 				associations.size()+1, //before adding to collection!
-				path, 
+				path,
 				joinable
 			);
 
 		OuterJoinableAssociation assoc = new OuterJoinableAssociation(
-				type, 
-				alias, 
-				aliasedLhsColumns, 
-				subalias, 
-				joinType, 
-				getFactory(), 
+				type,
+				alias,
+				aliasedLhsColumns,
+				subalias,
+				joinType,
+				hasRestriction( path ),
+				getFactory(),
 				enabledFilters
 			);
 		assoc.validateJoin(path);
@@ -242,9 +243,9 @@
 		if ( !joinable.isCollection() ) {
 			if (joinable instanceof OuterJoinLoadable) {
 				walkEntityTree(
-					(OuterJoinLoadable) joinable, 
+					(OuterJoinLoadable) joinable,
 					subalias,
-					path, 
+					path,
 					nextDepth
 				);
 			}
@@ -252,9 +253,9 @@
 		else {
 			if (joinable instanceof QueryableCollection) {
 				walkCollectionTree(
-					(QueryableCollection) joinable, 
-					subalias, 
-					path, 
+					(QueryableCollection) joinable,
+					subalias,
+					path,
 					nextDepth
 				);
 			}
@@ -317,7 +318,7 @@
 						persister.getTableName(),
 						lhsColumns,
 						!useInnerJoin,
-						currentDepth - 1, 
+						currentDepth - 1,
 						null //operations which cascade as far as the collection also cascade to collection elements
 					);
 				addAssociationToJoinTreeIfNecessary(
@@ -342,7 +343,7 @@
 		}
 
 	}
-	
+
 	/**
 	 * Walk the tree for a particular entity association
 	 */
@@ -373,7 +374,7 @@
 				lhsTable,
 				lhsColumns,
 				nullable,
-				currentDepth, 
+				currentDepth,
 				persister.getCascadeStyle(propertyNumber)
 			);
 		addAssociationToJoinTreeIfNecessary(
@@ -388,14 +389,14 @@
 	}
 
 	/**
-	 * For an entity class, add to a list of associations to be fetched 
+	 * For an entity class, add to a list of associations to be fetched
 	 * by outerjoin
 	 */
 	private final void walkEntityTree(
 		final OuterJoinLoadable persister,
 		final String alias,
 		final String path,
-		final int currentDepth) 
+		final int currentDepth)
 	throws MappingException {
 
 		int n = persister.countSubclassProperties();
@@ -464,10 +465,10 @@
 						lhsTable,
 						lhsColumns,
 						propertyNullability==null || propertyNullability[i],
-						currentDepth, 
+						currentDepth,
 						componentType.getCascadeStyle(i)
 					);
-				addAssociationToJoinTreeIfNecessary(			
+				addAssociationToJoinTreeIfNecessary(
 						associationType,
 						aliasedLhsColumns,
 						alias,
@@ -489,7 +490,7 @@
 						currentDepth
 					);
 			}
-			
+
 			begin+=types[i].getColumnSpan( getFactory() );
 		}
 
@@ -504,7 +505,7 @@
 		final QueryableCollection persister,
 		final String alias,
 		final String path,
-		final int currentDepth) 
+		final int currentDepth)
 	throws MappingException {
 
 		Type[] types = compositeType.getSubtypes();
@@ -530,7 +531,7 @@
 						persister.getTableName(),
 						lhsColumns,
 						propertyNullability==null || propertyNullability[i],
-						currentDepth, 
+						currentDepth,
 						compositeType.getCascadeStyle(i)
 					);
 				addAssociationToJoinTreeIfNecessary(
@@ -576,27 +577,27 @@
 	 * subclasses.
 	 */
 	protected int getJoinType(
-			AssociationType type, 
-			FetchMode config, 
-			String path, 
+			AssociationType type,
+			FetchMode config,
+			String path,
 			String lhsTable,
 			String[] lhsColumns,
 			boolean nullable,
-			int currentDepth, 
+			int currentDepth,
 			CascadeStyle cascadeStyle)
 	throws MappingException {
-		
+
 		if  ( !isJoinedFetchEnabled(type, config, cascadeStyle) ) return -1;
-		
+
 		if ( isTooDeep(currentDepth) || ( type.isCollectionType() && isTooManyCollections() ) ) return -1;
-		
+
 		final boolean dupe = isDuplicateAssociation(lhsTable,  lhsColumns, type);
 		if (dupe) return -1;
-		
+
 		return getJoinType(nullable, currentDepth);
-		
+
 	}
-	
+
 	/**
 	 * Use an inner join if it is a non-null association and this
 	 * is the "first" join in a series
@@ -604,8 +605,8 @@
 	protected int getJoinType(boolean nullable, int currentDepth) {
 		//TODO: this is too conservative; if all preceding joins were 
 		//      also inner joins, we could use an inner join here
-		return !nullable && currentDepth==0 ? 
-					JoinFragment.INNER_JOIN : 
+		return !nullable && currentDepth==0 ?
+					JoinFragment.INNER_JOIN :
 					JoinFragment.LEFT_OUTER_JOIN;
 	}
 
@@ -613,16 +614,16 @@
 		Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
 		return maxFetchDepth!=null && currentDepth >= maxFetchDepth.intValue();
 	}
-	
+
 	protected boolean isTooManyCollections() {
 		return false;
 	}
-	
+
 	/**
 	 * Does the mapping, and Hibernate default semantics, specify that
 	 * this association should be fetched by outer joining
 	 */
-	protected boolean isJoinedFetchEnabledInMapping(FetchMode config, AssociationType type) 
+	protected boolean isJoinedFetchEnabledInMapping(FetchMode config, AssociationType type)
 	throws MappingException {
 		if ( !type.isEntityType() && !type.isCollectionType() ) {
 			return false;
@@ -644,13 +645,13 @@
 	}
 
 	/**
-	 * Override on subclasses to enable or suppress joining 
+	 * Override on subclasses to enable or suppress joining
 	 * of certain association types
 	 */
 	protected boolean isJoinedFetchEnabled(AssociationType type, FetchMode config, CascadeStyle cascadeStyle) {
 		return type.isEntityType() && isJoinedFetchEnabledInMapping(config, type) ;
 	}
-	
+
 	protected String generateTableAlias(
 			final int n,
 			final String path,
@@ -664,19 +665,19 @@
 	}
 
 	/**
-	 * Used to detect circularities in the joined graph, note that 
+	 * Used to detect circularities in the joined graph, note that
 	 * this method is side-effecty
 	 */
 	protected boolean isDuplicateAssociation(
-		final String foreignKeyTable, 
+		final String foreignKeyTable,
 		final String[] foreignKeyColumns
 	) {
 		AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable);
 		return !visitedAssociationKeys.add( associationKey );
 	}
-	
+
 	/**
-	 * Used to detect circularities in the joined graph, note that 
+	 * Used to detect circularities in the joined graph, note that
 	 * this method is side-effecty
 	 */
 	protected boolean isDuplicateAssociation(
@@ -696,7 +697,7 @@
 		}
 		return isDuplicateAssociation(foreignKeyTable, foreignKeyColumns);
 	}
-	
+
 	/**
 	 * Uniquely identifier a foreign key, so that we don't
 	 * join it more than once, and create circularities
@@ -716,29 +717,29 @@
 			return table.hashCode(); //TODO: inefficient
 		}
 	}
-	
+
 	/**
 	 * Should we join this association?
 	 */
 	protected boolean isJoinable(
 		final int joinType,
-		final Set visitedAssociationKeys, 
+		final Set visitedAssociationKeys,
 		final String lhsTable,
 		final String[] lhsColumnNames,
 		final AssociationType type,
 		final int depth
 	) {
 		if (joinType<0) return false;
-		
+
 		if (joinType==JoinFragment.INNER_JOIN) return true;
-		
+
 		Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
-		final boolean tooDeep = maxFetchDepth!=null && 
+		final boolean tooDeep = maxFetchDepth!=null &&
 			depth >= maxFetchDepth.intValue();
-		
+
 		return !tooDeep && !isDuplicateAssociation(lhsTable, lhsColumnNames, type);
 	}
-	
+
 	protected String orderBy(final List associations, final String orderBy) {
 		return mergeOrderings( orderBy( associations ), orderBy );
 	}
@@ -754,7 +755,7 @@
 			return ordering1 + ", " + ordering2;
 		}
 	}
-	
+
 	/**
 	 * Generate a sequence of <tt>LEFT OUTER JOIN</tt> clauses for the given associations.
 	 */
@@ -793,7 +794,7 @@
 		}
 		return result;
 	}
-	
+
 	/**
 	 * Count the number of instances of Joinable which are actually
 	 * also instances of PersistentCollection which are being fetched
@@ -805,13 +806,15 @@
 		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++;
 			}
 		}
 		return result;
 	}
-	
+
 	/**
 	 * Get the order by string required for collection fetching
 	 */
@@ -849,7 +852,7 @@
 		if ( buf.length()>0 ) buf.setLength( buf.length()-2 );
 		return buf.toString();
 	}
-	
+
 	/**
 	 * Render the where condition for a (batch) load by identifier / collection key
 	 */
@@ -866,7 +869,7 @@
 			ConditionFragment byId = new ConditionFragment()
 					.setTableAlias(alias)
 					.setCondition( columnNames, "?" );
-	
+
 			StringBuffer whereString = new StringBuffer();
 			if ( batchSize==1 ) {
 				// if no batch, use "foo = ? and bar = ?"
@@ -887,7 +890,7 @@
 	}
 
 	protected void initPersisters(final List associations, final LockMode lockMode) throws MappingException {
-		
+
 		final int joins = countEntityPersisters(associations);
 		final int collections = countCollectionPersisters(associations);
 
@@ -900,31 +903,31 @@
 		owners = new int[joins];
 		ownerAssociationTypes = new EntityType[joins];
 		lockModeArray = ArrayHelper.fillArray(lockMode, joins);
-		
+
 		int i=0;
 		int j=0;
 		Iterator iter = associations.iterator();
 		while ( iter.hasNext() ) {
 			final OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
 			if ( !oj.isCollection() ) {
-				
+
 				persisters[i] = (Loadable) oj.getJoinable();
 				aliases[i] = oj.getRHSAlias();
 				owners[i] = oj.getOwner(associations);
 				ownerAssociationTypes[i] = (EntityType) oj.getJoinableType();
 				i++;
-				
+
 			}
 			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);
 					j++;
 				}
-	
+
 				if ( collPersister.isOneToMany() ) {
 					persisters[i] = (Loadable) collPersister.getElementPersister();
 					aliases[i] = oj.getRHSAlias();
@@ -932,7 +935,7 @@
 				}
 			}
 		}
-	
+
 		if ( ArrayHelper.isAllNegative(owners) ) owners = null;
 		if ( collectionOwners!=null && ArrayHelper.isAllNegative(collectionOwners) ) {
 			collectionOwners = null;
@@ -986,5 +989,8 @@
 			return buf.toString();
 		}
 	}
+	protected boolean hasRestriction(String path)	{
+		return false;
+	}
 
 }

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java	2011-06-28 19:34:11 UTC (rev 20972)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -46,7 +46,7 @@
 	private final int joinType;
 	private final String on;
 	private final Map enabledFilters;
-
+	private final boolean hasRestriction;
 	public OuterJoinableAssociation(
 		AssociationType joinableType,
 		String lhsAlias,
@@ -56,6 +56,17 @@
 		SessionFactoryImplementor factory,
 		Map enabledFilters)
 	throws MappingException {
+		this(joinableType,lhsAlias,lhsColumns,rhsAlias,joinType,false,factory,enabledFilters);
+	}
+	public OuterJoinableAssociation(
+		AssociationType joinableType,
+		String lhsAlias,
+		String[] lhsColumns,
+		String rhsAlias,
+		int joinType,   boolean hasRestriction,
+		SessionFactoryImplementor factory,
+		Map enabledFilters)
+	throws MappingException {
 		this.joinableType = joinableType;
 		this.lhsAlias = lhsAlias;
 		this.lhsColumns = lhsColumns;
@@ -64,6 +75,7 @@
 		this.joinable = joinableType.getAssociatedJoinable(factory);
 		this.rhsColumns = JoinHelper.getRHSColumnNames(joinableType, factory);
 		this.on = joinableType.getOnCondition(rhsAlias, factory, enabledFilters);
+		this.hasRestriction = hasRestriction;
 		this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application
 	}
 
@@ -101,6 +113,9 @@
 	public Joinable getJoinable() {
 		return joinable;
 	}
+	public boolean hasRestriction() {
+		return hasRestriction;
+	}
 
 	public int getOwner(final List associations) {
 		if ( isOneToOne() || isCollection() ) {

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java	2011-06-28 19:34:11 UTC (rev 20972)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -222,5 +222,8 @@
 	public String getComment() {
 		return "criteria query";
 	}
+	protected boolean hasRestriction(String path)	{
+		return translator.hasRestriction( path );
+	}
 
 }

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java	2011-06-28 19:34:11 UTC (rev 20972)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/loader/criteria/CriteriaQueryTranslator.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -581,5 +581,9 @@
 		}
 		return propertyName;
 	}
+	public boolean hasRestriction(String path){
+		final CriteriaImpl.Subcriteria crit = ( CriteriaImpl.Subcriteria ) getCriteria( path );
+		return crit == null ? false : crit.hasRestriction();
+	}
 
 }

Added: core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml	                        (rev 0)
+++ core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.hbm.xml	2011-08-23 01:49:52 UTC (rev 20973)
@@ -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>
+

Added: core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.java	                        (rev 0)
+++ core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/Order.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -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();
+  }
+}
\ No newline at end of file

Added: core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java	                        (rev 0)
+++ core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OrderLine.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -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

Added: core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java	                        (rev 0)
+++ core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/criteria/OuterJoinCriteriaTest.java	2011-08-23 01:49:52 UTC (rev 20973)
@@ -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.junit.functional.FunctionalTestCase;
+import org.hibernate.sql.JoinFragment;
+
+/**
+ * @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



More information about the hibernate-commits mailing list