You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ba...@apache.org on 2006/04/12 22:24:37 UTC

svn commit: r393593 [1/3] - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/sql/compile/ engine/org/apache/derby/impl/sql/compile/ testing/org/apache/derbyTesting/functionTests/master/ testing/org/apache/derbyTesting/functionTests/tests/lang/

Author: bandaram
Date: Wed Apr 12 13:24:27 2006
New Revision: 393593

URL: http://svn.apache.org/viewcvs?rev=393593&view=rev
Log:
DERBY-805: Apply phase 4 changes to implement join-predicate pushdown. See attached write up in JIRA for details. Changes have been extensively documented there.

Submitted by Army Brown (qozinx@gmail.com)

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/compile/Optimizable.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/BaseTableNumbersVisitor.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromTable.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/JoinNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/OptimizerImpl.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ProjectRestrictNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SetOperatorNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SingleChildResultSetNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TableOperatorNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnionNode.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/master/predicatePushdown.out
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/predicatePushdown.sql

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/compile/Optimizable.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/compile/Optimizable.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/compile/Optimizable.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/compile/Optimizable.java Wed Apr 12 13:24:27 2006
@@ -237,26 +237,31 @@
 	 * question, but in cases where there are nested subqueries, there will be
 	 * one OptimizerImpl for every level of nesting, and each OptimizerImpl
 	 * might have its own idea of what this Optimizable's "truly the best path"
-	 * access path really is.  So whenever we save a "truly the best" path,
-	 * we take note of which Optimizer told us to do so.  Then as each level
-	 * of subquery finishes optimization, the corresponding OptimizerImpl
-	 * can load its preferred access path into this Optimizable's
-	 * trulyTheBestAccessPath field and pass it up the tree, until eventually
-	 * the outer-most OptimizerImpl can choose to either use the best path
-	 * that it received from below (by calling "rememberAsBest()") or else
+	 * access path really is.  In addition, there could be Optimizables
+	 * above this Optimizable that might need to override the best path
+	 * chosen during optimization.  So whenever we save a "truly the best" path,
+	 * we take note of which Optimizer/Optimizable told us to do so.  Then
+	 * as each level of subquery finishes optimization, the corresponding
+	 * OptimizerImpl/Optimizable can load its preferred access path into this
+	 * Optimizable's trulyTheBestAccessPath field and pass it up the tree, until
+	 * eventually the outer-most OptimizerImpl can choose to either use the best
+	 * path that it received from below (by calling "rememberAsBest()") or else
 	 * use the path that it found to be "best" for itself.
 	 *
-	 * This method is what allows us to keep track of which OptimizerImpl
-	 * saved which "best plan", and allows us to load the appropriate plans
-	 * after each round of optimization.
+	 * This method is what allows us to keep track of which OptimizerImpl or
+	 * Optimizable saved which "best plan", and allows us to load the
+	 * appropriate plans after each round of optimization.
 	 * 
-	 * @param doAdd True if we're saving a best plan for the OptimizerImpl,
-	 *  false if we're loading/retrieving the best plan for the OptimizerImpl.
-	 * @param optimizer The OptimizerImpl for which we're saving/loading
-	 *  the "truly the best" path.
+	 * @param doAdd True if we're saving a best plan for the OptimizerImpl/
+	 *  Optimizable; false if we're loading/retrieving the best plan.
+	 * @param planKey Object to use as the map key when adding/looking up
+	 *  a plan.  If it is an instance of OptimizerImpl then it corresponds
+	 *  to an outer query; otherwise it's some Optimizable above this
+	 *  Optimizable that could potentially reject plans chosen by the
+	 *  OptimizerImpl to which this Optimizable belongs.
 	 */
 	public void addOrLoadBestPlanMapping(boolean doAdd,
-		Optimizer optimizer) throws StandardException;
+		Object planKey) throws StandardException;
 
 	/**
 	 * Remember the current access path as the best one (so far).

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/BaseTableNumbersVisitor.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/BaseTableNumbersVisitor.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/BaseTableNumbersVisitor.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/BaseTableNumbersVisitor.java Wed Apr 12 13:24:27 2006
@@ -135,14 +135,14 @@
 
 				if (rcExpr instanceof ColumnReference)
 				// we found our column reference; recurse using that.
-					return rcExpr.accept(this);
-
+					rcExpr.accept(this);
+				else {
 				// Else we must have found the table number someplace
 				// other than within a ColumnReference (ex. we may
 				// have pulled it from a VirtualColumnNode's source
 				// table); so just set the number.
-				tableMap.set(baseTableNumber);
-
+					tableMap.set(baseTableNumber);
+				}
 			}
 			else if (node instanceof ColumnReference) {
 			// we couldn't find any other table numbers beneath the

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromTable.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromTable.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromTable.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromTable.java Wed Apr 12 13:24:27 2006
@@ -157,6 +157,18 @@
 							RowOrdering rowOrdering)
 			throws StandardException
 	{
+		// It's possible that a call to optimize the left/right will cause
+		// a new "truly the best" plan to be stored in the underlying base
+		// tables.  If that happens and then we decide to skip that plan
+		// (which we might do if the call to "considerCost()" below decides
+		// the current path is infeasible or not the best) we need to be
+		// able to revert back to the "truly the best" plans that we had
+		// saved before we got here.  So with this next call we save the
+		// current plans using "this" node as the key.  If needed, we'll
+		// then make the call to revert the plans in OptimizerImpl's
+		// getNextDecoratedPermutation() method.
+		addOrLoadBestPlanMapping(true, this);
+
 		CostEstimate singleScanCost = estimateCost(predList,
 												(ConglomerateDescriptor) null,
 												outerCost,
@@ -507,25 +519,38 @@
 
 	/** @see Optimizable#addOrLoadBestPlanMapping */
 	public void addOrLoadBestPlanMapping(boolean doAdd,
-		Optimizer optimizer) throws StandardException
+		Object planKey) throws StandardException
 	{
+		AccessPath bestPath = getTrulyTheBestAccessPath();
 		AccessPathImpl ap = null;
 		if (doAdd)
 		{
+			// If we get to this method before ever optimizing this node, then
+			// there will be no best path--so there's nothing to do.
+			if (bestPath == null)
+				return;
+
 			// If the optimizerToBestPlanMap already exists, search for an
-			// AccessPath for the target optimizer and use that if we can.
+			// AccessPath for the received key and use that if we can.
 			if (optimizerToBestPlanMap == null)
 				optimizerToBestPlanMap = new HashMap();
 			else
-				ap = (AccessPathImpl)optimizerToBestPlanMap.get(optimizer);
+				ap = (AccessPathImpl)optimizerToBestPlanMap.get(planKey);
 
-			// If we don't already have an AccessPath for the optimizer,
-			// create a new one.
+			// If we don't already have an AccessPath for the key,
+			// create a new one.  If the key is an OptimizerImpl then
+			// we might as well pass it in to the AccessPath constructor;
+			// otherwise just pass null.
 			if (ap == null)
-				ap = new AccessPathImpl(optimizer);
+			{
+				if (planKey instanceof Optimizer)
+					ap = new AccessPathImpl((Optimizer)planKey);
+				else
+					ap = new AccessPathImpl((Optimizer)null);
+			}
 
-			ap.copy(getTrulyTheBestAccessPath());
-			optimizerToBestPlanMap.put(optimizer, ap);
+			ap.copy(bestPath);
+			optimizerToBestPlanMap.put(planKey, ap);
 			return;
 		}
 
@@ -533,22 +558,21 @@
 		// into this Optimizable's trulyTheBestAccessPath field.
 
 		// If we don't have any plans saved, then there's nothing to load.
-		// This can happen if the optimizer tried some join order for which
-		// there was no valid plan.
+		// This can happen if the key is an OptimizerImpl that tried some
+		// join order for which there was no valid plan.
 		if (optimizerToBestPlanMap == null)
 			return;
 
-		ap = (AccessPathImpl)optimizerToBestPlanMap.get(optimizer);
+		ap = (AccessPathImpl)optimizerToBestPlanMap.get(planKey);
 
-		// Again, might be the case that there is no plan stored for
-		// the optimizer if no valid plans have been discovered for
-		// that optimizer's current join order.
-		if (ap == null)
+		// It might be the case that there is no plan stored for
+		// the key, in which case there's nothing to load.
+		if ((ap == null) || (ap.getCostEstimate() == null))
 			return;
 
 		// We found a best plan in our map, so load it into this Optimizable's
 		// trulyTheBestAccessPath field.
-		getTrulyTheBestAccessPath().copy(ap);
+		bestPath.copy(ap);
 		return;
 	}
 
@@ -582,7 +606,23 @@
 		// join order of the received optimizer, take note of what
 		// that path is, in case we need to "revert" back to this
 		// path later.  See Optimizable.addOrLoadBestPlanMapping().
-		addOrLoadBestPlanMapping(true, optimizer);
+		// Note: Since this call descends all the way down to base
+		// tables, it can be relatively expensive when we have deeply
+		// nested subqueries.  So in an attempt to save some work, we
+		// skip the call if this node is a ProjectRestrictNode whose
+		// child is an Optimizable--in that case the ProjectRestrictNode
+		// will in turn call "rememberAsBest" on its child and so
+		// the required call to addOrLoadBestPlanMapping() will be
+		// made at that time.  If we did it here, too, then we would
+		// just end up duplicating the work.
+		if (!(this instanceof ProjectRestrictNode))
+			addOrLoadBestPlanMapping(true, optimizer);
+		else
+		{
+			ProjectRestrictNode prn = (ProjectRestrictNode)this;
+			if (!(prn.getChildResult() instanceof Optimizable))
+				addOrLoadBestPlanMapping(true, optimizer);
+		}
 		 
 		/* also store the name of the access path; i.e index name/constraint
 		 * name if we're using an index to access the base table.

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/JoinNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/JoinNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/JoinNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/JoinNode.java Wed Apr 12 13:24:27 2006
@@ -176,6 +176,18 @@
 	{
 		optimizer.trace(Optimizer.CALLING_ON_JOIN_NODE, 0, 0, 0.0, null);
 
+		// It's possible that a call to optimize the left/right will cause
+		// a new "truly the best" plan to be stored in the underlying base
+		// tables.  If that happens and then we decide to skip that plan
+		// (which we might do if the call to "considerCost()" below decides
+		// the current path is infeasible or not the best) we need to be
+		// able to revert back to the "truly the best" plans that we had
+		// saved before we got here.  So with this next call we save the
+		// current plans using "this" node as the key.  If needed, we'll
+		// then make the call to revert the plans in OptimizerImpl's
+		// getNextDecoratedPermutation() method.
+		addOrLoadBestPlanMapping(true, this);
+
 		/*
 		** RESOLVE: Most types of Optimizables only implement estimateCost(),
 		** and leave it up to optimizeIt() in FromTable to figure out the

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/OptimizerImpl.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/OptimizerImpl.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/OptimizerImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/OptimizerImpl.java Wed Apr 12 13:24:27 2006
@@ -1138,6 +1138,38 @@
 										(OptimizablePredicateList) null,
 										currentRowOrdering);
 
+		// If the previous path that we considered for curOpt was _not_ the best
+		// path for this round, then we need to revert back to whatever the
+		// best plan for curOpt was this round.  Note that the cost estimate
+		// for bestAccessPath could be null here if the last path that we
+		// checked was the only one possible for this round.
+		if ((curOpt.getBestAccessPath().getCostEstimate() != null) &&
+			(curOpt.getCurrentAccessPath().getCostEstimate() != null))
+		{
+			// Note: we can't just check to see if bestCost is cheaper
+			// than currentCost because it's possible that currentCost
+			// is actually cheaper--but it may have been 'rejected' because
+			// it would have required too much memory.  So we just check
+			// to see if bestCost and currentCost are different.  If so
+			// then we know that the most recent access path (represented
+			// by "current" access path) was not the best.
+			if (curOpt.getBestAccessPath().getCostEstimate().compare(
+				curOpt.getCurrentAccessPath().getCostEstimate()) != 0)
+			{
+				curOpt.addOrLoadBestPlanMapping(false, curOpt);
+			}
+			else if (curOpt.getBestAccessPath().getCostEstimate().rowCount() <
+				curOpt.getCurrentAccessPath().getCostEstimate().rowCount())
+			{
+				// If currentCost and bestCost have the same cost estimate
+				// but currentCost has been rejected because of memory, we
+				// still need to revert the plans.  In this case the row
+				// count for currentCost will be greater than the row count
+				// for bestCost, so that's what we just checked.
+				curOpt.addOrLoadBestPlanMapping(false, curOpt);
+			}
+		}
+
 		/*
 		** When all the access paths have been looked at, we know what the
 		** cheapest one is, so remember it.  Only do this if a cost estimate
@@ -1810,15 +1842,21 @@
 			return;
 		}
 
+		// Before considering the cost, make sure we set the optimizable's
+		// "current" cost to be the one that we received.  Doing this allows
+		// us to compare "current" with "best" later on to find out if
+		// the "current" plan is also the "best" one this round--if it's
+		// not then we'll have to revert back to whatever the best plan is.
+		// That check is performed in getNextDecoratedPermutation() of
+		// this class.
+		optimizable.getCurrentAccessPath().setCostEstimate(estimatedCost);
+
 		/*
 		** Skip this access path if it takes too much memory.
 		**
 		** NOTE: The default assumption here is that the number of rows in
 		** a single scan is the total number of rows divided by the number
 		** of outer rows.  The optimizable may over-ride this assumption.
-		**
-		** NOTE: This is probably not necessary here, because we should
-		** get here only for nested loop joins, which don't use memory.
 		*/
         if( ! optimizable.memoryUsageOK( estimatedCost.rowCount() / outerCost.rowCount(),
                                          maxMemoryPerTable))
@@ -2152,11 +2190,14 @@
 	 * necessary.
 	 *
 	 * @param doAdd True if we're adding a mapping, false if we're loading.
-	 * @param outerOptimizer OptimizerImpl corresponding to an outer
-	 *  query; we will use this as the key for the mapping.
+	 * @param planKey Object to use as the map key when adding/looking up
+	 *  a plan.  If this is an instance of OptimizerImpl then it corresponds
+	 *  to an outer query; otherwise it's some Optimizable above this
+	 *  OptimizerImpl that could potentially reject plans chosen by this
+	 *  OptimizerImpl.
 	 */
 	protected void addOrLoadBestPlanMappings(boolean doAdd,
-		Optimizer outerOptimizer) throws StandardException
+		Object planKey) throws StandardException
 	{
 		// First we save this OptimizerImpl's best join order.  If there's
 		// only one optimizable in the list, then there's only one possible
@@ -2171,7 +2212,7 @@
 				if (savedJoinOrders == null)
 					savedJoinOrders = new HashMap();
 				else
-					joinOrder = (int[])savedJoinOrders.get(outerOptimizer);
+					joinOrder = (int[])savedJoinOrders.get(planKey);
 
 				// If we don't already have a join order array for the optimizer,
 				// create a new one.
@@ -2182,7 +2223,7 @@
 				for (int i = 0; i < bestJoinOrder.length; i++)
 					joinOrder[i] = bestJoinOrder[i];
 
-				savedJoinOrders.put(outerOptimizer, joinOrder);
+				savedJoinOrders.put(planKey, joinOrder);
 			}
 			else
 			{
@@ -2192,16 +2233,17 @@
 				// If we don't have any join orders saved, then there's nothing to
 				// load.  This can happen if the optimizer tried some join order
 				// for which there was no valid plan.
-				if (savedJoinOrders == null)
-					return;
-
-				joinOrder = (int[])savedJoinOrders.get(outerOptimizer);
-				if (joinOrder == null)
-					return;
-
-				// Load the join order we found into our bestJoinOrder array.
-				for (int i = 0; i < joinOrder.length; i++)
-					bestJoinOrder[i] = joinOrder[i];
+				if (savedJoinOrders != null)
+				{
+					joinOrder = (int[])savedJoinOrders.get(planKey);
+					if (joinOrder != null)
+					{
+						// Load the join order we found into our
+						// bestJoinOrder array.
+						for (int i = 0; i < joinOrder.length; i++)
+							bestJoinOrder[i] = joinOrder[i];
+					}
+				}
 			}
 		}
 
@@ -2211,7 +2253,7 @@
 		for (int i = optimizableList.size() - 1; i >= 0; i--)
 		{
 			optimizableList.getOptimizable(i).
-				addOrLoadBestPlanMapping(doAdd, outerOptimizer);
+				addOrLoadBestPlanMapping(doAdd, planKey);
 		}
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ProjectRestrictNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ProjectRestrictNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ProjectRestrictNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ProjectRestrictNode.java Wed Apr 12 13:24:27 2006
@@ -293,6 +293,18 @@
 		// if (childResultOptimized)
 		// 	return costEstimate;
 
+		// It's possible that a call to optimize the left/right will cause
+		// a new "truly the best" plan to be stored in the underlying base
+		// tables.  If that happens and then we decide to skip that plan
+		// (which we might do if the call to "considerCost()" below decides
+		// the current path is infeasible or not the best) we need to be
+		// able to revert back to the "truly the best" plans that we had
+		// saved before we got here.  So with this next call we save the
+		// current plans using "this" node as the key.  If needed, we'll
+		// then make the call to revert the plans in OptimizerImpl's
+		// getNextDecoratedPermutation() method.
+		addOrLoadBestPlanMapping(true, this);
+
 		/* If the childResult is instanceof Optimizable, then we optimizeIt.
 		 * Otherwise, we are going into a new query block.  If the new query
 		 * block has already had its access path modified, then there is
@@ -379,7 +391,25 @@
 		 */
 		if (childResult instanceof Optimizable)
 		{
-			return ((Optimizable) childResult).feasibleJoinStrategy(restrictionList, optimizer);
+			// With DERBY-805 it's possible that, when considering a nested
+			// loop join with this PRN, we pushed predicates down into the
+			// child if the child is a UNION node.  At this point, though, we
+			// may be considering doing a hash join with this PRN instead of a
+			// nested loop join, and if that's the case we need to pull any
+			// predicates back up so that they can be searched for equijoins
+			// that will in turn make the hash join possible.  So that's what
+			// the next call does.  Two things to note: 1) if no predicates
+			// were pushed, this call is a no-op; and 2) if we get here when
+			// considering a nested loop join, the predicates that we pull
+			// here (if any) will be re-pushed for subsequent costing/ 
+			// optimization as necessary (see OptimizerImpl.costPermutation(),
+			// which will call this class's optimizeIt() method and that's
+			// where the predicates are pushed down again).
+			if (childResult instanceof UnionNode)
+				((UnionNode)childResult).pullOptPredicates(restrictionList);
+
+			return ((Optimizable) childResult).
+				feasibleJoinStrategy(restrictionList, optimizer);
 		}
 		else
 		{
@@ -544,6 +574,7 @@
 		** Do nothing if the child result set is not optimizable, as there
 		** can be nothing to modify.
 		*/
+		boolean alreadyPushed = false;
 		if ( ! (childResult instanceof Optimizable))
 		{
 			// Remember that the original child was not Optimizable
@@ -607,6 +638,17 @@
 				childResult = (ResultSetNode)
 					((SetOperatorNode) childResult).modifyAccessPath(
 						outerTables, restrictionList);
+
+				// Take note of the fact that we already pushed predicates
+				// as part of the modifyAccessPaths call.  This is necessary
+				// because there may still be predicates in restrictionList
+				// that we intentionally decided not to push (ex. if we're
+				// going to do hash join then we chose to not push the join
+				// predicates).  Whatever the reason for not pushing the
+				// predicates, we have to make sure we don't inadvertenly
+				// push them later (esp. as part of the "pushUsefulPredicates"
+				// call below).
+				alreadyPushed = true;
 			}
 			else {
 				childResult = 
@@ -615,7 +657,8 @@
 			}
 		}
 
-		if (restrictionList != null)
+
+		if ((restrictionList != null) && !alreadyPushed)
 		{
 			restrictionList.pushUsefulPredicates((Optimizable) childResult);
 		}

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java Wed Apr 12 13:24:27 2006
@@ -1636,18 +1636,20 @@
 	}
 
 	/**
-	 * Get the optimizer for this result set; assumption is that
-	 * this.optimizer has already been created by the getOptimizer()
-	 * method above.
+	 * Get the optimizer for this result set.
+	 * 
+	 * @return If this.optimizer has has already been created by the
+	 *  getOptimizer() method above, then return it; otherwise,
+	 *  return null.
 	 */
-	protected OptimizerImpl getOptimizerImpl() {
-
-		if (SanityManager.DEBUG) {
-			SanityManager.ASSERT(optimizer != null,
-				"Tried to retrieve optimizer for a result set, but no such " +
-				"optimizer existed.");
-		}
-
+	protected OptimizerImpl getOptimizerImpl()
+	{
+		// Note that the optimizer might be null because it's possible that
+		// we'll get here before any calls to getOptimizer() were made, which
+		// can happen if we're trying to save a "best path" but we haven't
+		// actually found one yet.  In that case we just return the "null"
+		// value; the caller must check for it and behave appropriately.
+		// Ex. see TableOperatorNode.addOrLoadBestPlanMapping().
 		return (OptimizerImpl)optimizer;
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SetOperatorNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SetOperatorNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SetOperatorNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SetOperatorNode.java Wed Apr 12 13:24:27 2006
@@ -117,8 +117,12 @@
 		// might depend on those predicates.  So now that we're preparing
 		// to generate the best paths, we have to push those same predicates
 		// down again (this is the last time) so that the children can use
-		// them as appropriate.
-		if (predList != null) 
+		// them as appropriate. NOTE: If our final choice for join strategy
+		// is a hash join, then we do not push the predicates because we'll
+		// need them to be at this level in order to find out which of them
+		// is the equijoin predicate that is required by hash join.
+		if ((predList != null) &&
+			!getTrulyTheBestAccessPath().getJoinStrategy().isHashJoin())
 		{
 			for (int i = predList.size() - 1; i >= 0; i--)
 				if (pushOptPredicate(predList.getOptPredicate(i)))

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SingleChildResultSetNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SingleChildResultSetNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SingleChildResultSetNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SingleChildResultSetNode.java Wed Apr 12 13:24:27 2006
@@ -171,18 +171,25 @@
 	 * child, in order to ensure that we have a full plan mapped.
 	 */
 	public void addOrLoadBestPlanMapping(boolean doAdd,
-		Optimizer optimizer) throws StandardException
+		Object planKey) throws StandardException
 	{
-		super.addOrLoadBestPlanMapping(doAdd, optimizer);
+		super.addOrLoadBestPlanMapping(doAdd, planKey);
+
+		// Now walk the child.  Note that if the child is not an
+		// Optimizable and the call to child.getOptimizerImpl()
+		// returns null, then that means we haven't tried to optimize
+		// the child yet.  So in that case there's nothing to
+		// add/load.
+
 		if (childResult instanceof Optimizable)
 		{
 			((Optimizable)childResult).
-				addOrLoadBestPlanMapping(doAdd, optimizer);
+				addOrLoadBestPlanMapping(doAdd, planKey);
 		}
-		else
+		else if (childResult.getOptimizerImpl() != null)
 		{
 			childResult.getOptimizerImpl().
-				addOrLoadBestPlanMappings(doAdd, optimizer);
+				addOrLoadBestPlanMappings(doAdd, planKey);
 		}
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TableOperatorNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TableOperatorNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TableOperatorNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TableOperatorNode.java Wed Apr 12 13:24:27 2006
@@ -164,29 +164,36 @@
 	 * full plan mapped.
 	 */
 	public void addOrLoadBestPlanMapping(boolean doAdd,
-		Optimizer optimizer) throws StandardException
+		Object planKey) throws StandardException
 	{
-		super.addOrLoadBestPlanMapping(doAdd, optimizer);
+		super.addOrLoadBestPlanMapping(doAdd, planKey);
+
+		// Now walk the children.  Note that if either child is not
+		// an Optimizable and the call to child.getOptimizerImpl()
+		// returns null, then that means we haven't tried to optimize
+		// the child yet.  So in that case there's nothing to
+		// add/load.
+
 		if (leftResultSet instanceof Optimizable)
 		{
 			((Optimizable)leftResultSet).
-				addOrLoadBestPlanMapping(doAdd, optimizer);
+				addOrLoadBestPlanMapping(doAdd, planKey);
 		}
-		else
+		else if (leftResultSet.getOptimizerImpl() != null)
 		{
 			leftResultSet.getOptimizerImpl().
-				addOrLoadBestPlanMappings(doAdd, optimizer);
+				addOrLoadBestPlanMappings(doAdd, planKey);
 		}
 
 		if (rightResultSet instanceof Optimizable)
 		{
 			((Optimizable)rightResultSet).
-				addOrLoadBestPlanMapping(doAdd, optimizer);
+				addOrLoadBestPlanMapping(doAdd, planKey);
 		}
-		else
+		else if (rightResultSet.getOptimizerImpl() != null)
 		{
 			rightResultSet.getOptimizerImpl().
-				addOrLoadBestPlanMappings(doAdd, optimizer);
+				addOrLoadBestPlanMappings(doAdd, planKey);
 		}
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnionNode.java
URL: http://svn.apache.org/viewcvs/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnionNode.java?rev=393593&r1=393592&r2=393593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnionNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnionNode.java Wed Apr 12 13:24:27 2006
@@ -220,14 +220,31 @@
 		// need to scope them for the child result sets first, and
 		// then push the scoped versions.  This is all done in the
 		// call to pushOptPredicate() here; for more, see that method's
-		// definition in SetOperatorNode.
-		if (predList != null)
+		// definition in SetOperatorNode.  NOTE: If we're considering a
+		// hash join then we do not push the predicates because we'll
+		// need the predicates to be at this level in order to find
+		// out if one of them is an equijoin predicate that can be used
+		// for the hash join.
+		if ((predList != null) &&
+			!getCurrentAccessPath().getJoinStrategy().isHashJoin())
 		{
 			for (int i = predList.size() - 1; i >= 0; i--) {
 				if (pushOptPredicate(predList.getOptPredicate(i)))
 					predList.removeOptPredicate(i);
 			}
 		}
+
+		// It's possible that a call to optimize the left/right will cause
+		// a new "truly the best" plan to be stored in the underlying base
+		// tables.  If that happens and then we decide to skip that plan
+		// (which we might do if the call to "considerCost()" below decides
+		// the current path is infeasible or not the best) we need to be
+		// able to revert back to the "truly the best" plans that we had
+		// saved before we got here.  So with this next call we save the
+		// current plans using "this" node as the key.  If needed, we'll
+		// then make the call to revert the plans in OptimizerImpl's
+		// getNextDecoratedPermutation() method.
+		addOrLoadBestPlanMapping(true, this);
 
 		leftResultSet = optimizeSource(
 							optimizer,