--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.JoinNode\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import org.apache.derby.iapi.services.context.ContextManager;\r
+\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.sql.compile.Optimizable;\r
+import org.apache.derby.iapi.sql.compile.OptimizablePredicate;\r
+import org.apache.derby.iapi.sql.compile.OptimizablePredicateList;\r
+import org.apache.derby.iapi.sql.compile.Optimizer;\r
+import org.apache.derby.iapi.sql.compile.Visitable;\r
+import org.apache.derby.iapi.sql.compile.Visitor;\r
+import org.apache.derby.iapi.sql.compile.CostEstimate;\r
+import org.apache.derby.iapi.sql.compile.RowOrdering;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;\r
+\r
+import org.apache.derby.iapi.types.TypeId;\r
+import org.apache.derby.iapi.types.DataTypeDescriptor;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.reference.ClassName;\r
+\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+\r
+import org.apache.derby.impl.sql.compile.ActivationClassBuilder;\r
+\r
+import org.apache.derby.iapi.util.JBitSet;\r
+import org.apache.derby.iapi.util.PropertyUtil;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+\r
+import java.util.Properties;\r
+import java.util.Vector;\r
+\r
+/**\r
+ * A JoinNode represents a join result set for either of the basic DML\r
+ * operations: SELECT and INSERT. For INSERT - SELECT, any of the\r
+ * fields in a JoinNode can be used (the JoinNode represents\r
+ * the (join) SELECT statement in the INSERT - SELECT). For INSERT,\r
+ * the resultColumns in the selectList will contain the names of the columns\r
+ * being inserted into or updated.\r
+ *\r
+ */\r
+\r
+public class JoinNode extends TableOperatorNode\r
+{\r
+ /* Join semantics */\r
+ public static final int INNERJOIN = 1;\r
+ public static final int CROSSJOIN = 2;\r
+ public static final int LEFTOUTERJOIN = 3;\r
+ public static final int RIGHTOUTERJOIN = 4;\r
+ public static final int FULLOUTERJOIN = 5;\r
+ public static final int UNIONJOIN = 6;\r
+\r
+ private boolean optimized;\r
+\r
+ private PredicateList leftPredicateList;\r
+ private PredicateList rightPredicateList;\r
+\r
+ protected boolean flattenableJoin = true;\r
+ Vector aggregateVector;\r
+ SubqueryList subqueryList;\r
+ ValueNode joinClause;\r
+ boolean joinClauseNormalized;\r
+ PredicateList joinPredicates;\r
+ ResultColumnList usingClause;\r
+ //User provided optimizer overrides\r
+ Properties joinOrderStrategyProperties;\r
+\r
+\r
+ /**\r
+ * Initializer for a JoinNode.\r
+ *\r
+ * @param leftResult The ResultSetNode on the left side of this join\r
+ * @param rightResult The ResultSetNode on the right side of this join\r
+ * @param onClause The ON clause\r
+ * @param usingClause The USING clause\r
+ * @param selectList The result column list for the join\r
+ * @param tableProperties Properties list associated with the table\r
+ * @param joinOrderStrategyProperties User provided optimizer overrides\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void init(\r
+ Object leftResult,\r
+ Object rightResult,\r
+ Object onClause,\r
+ Object usingClause,\r
+ Object selectList,\r
+ Object tableProperties,\r
+ Object joinOrderStrategyProperties)\r
+ throws StandardException\r
+ {\r
+ super.init(leftResult, rightResult, tableProperties);\r
+ resultColumns = (ResultColumnList) selectList;\r
+ joinClause = (ValueNode) onClause;\r
+ joinClauseNormalized = false;\r
+ this.usingClause = (ResultColumnList) usingClause;\r
+ this.joinOrderStrategyProperties = (Properties)joinOrderStrategyProperties;\r
+\r
+ /* JoinNodes can be generated in the parser or at the end of optimization.\r
+ * Those generated in the parser do not have resultColumns yet.\r
+ */\r
+ if (resultColumns != null)\r
+ {\r
+ /* A longer term assertion */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT((leftResultSet.getReferencedTableMap() != null &&\r
+ rightResultSet.getReferencedTableMap() != null) ||\r
+ (leftResultSet.getReferencedTableMap() == null &&\r
+ rightResultSet.getReferencedTableMap() == null),\r
+ "left and right referencedTableMaps are expected to either both be non-null or both be null");\r
+ }\r
+\r
+ /* Build the referenced table map (left || right) */\r
+ if (leftResultSet.getReferencedTableMap() != null)\r
+ {\r
+ referencedTableMap = (JBitSet) leftResultSet.getReferencedTableMap().clone();\r
+ referencedTableMap.or((JBitSet) rightResultSet.getReferencedTableMap());\r
+ }\r
+ }\r
+ joinPredicates = (PredicateList) getNodeFactory().getNode(\r
+ C_NodeTypes.PREDICATE_LIST,\r
+ getContextManager());\r
+ }\r
+\r
+ /*\r
+ * Optimizable interface\r
+ */\r
+\r
+ /**\r
+ * @see org.apache.derby.iapi.sql.compile.Optimizable#optimizeIt\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public CostEstimate optimizeIt(\r
+ Optimizer optimizer,\r
+ OptimizablePredicateList predList,\r
+ CostEstimate outerCost,\r
+ RowOrdering rowOrdering)\r
+ throws StandardException\r
+ {\r
+ optimizer.trace(Optimizer.CALLING_ON_JOIN_NODE, 0, 0, 0.0, null);\r
+\r
+ // It's possible that a call to optimize the left/right will cause\r
+ // a new "truly the best" plan to be stored in the underlying base\r
+ // tables. If that happens and then we decide to skip that plan\r
+ // (which we might do if the call to "considerCost()" below decides\r
+ // the current path is infeasible or not the best) we need to be\r
+ // able to revert back to the "truly the best" plans that we had\r
+ // saved before we got here. So with this next call we save the\r
+ // current plans using "this" node as the key. If needed, we'll\r
+ // then make the call to revert the plans in OptimizerImpl's\r
+ // getNextDecoratedPermutation() method.\r
+ updateBestPlanMap(ADD_PLAN, this);\r
+\r
+ /*\r
+ ** RESOLVE: Most types of Optimizables only implement estimateCost(),\r
+ ** and leave it up to optimizeIt() in FromTable to figure out the\r
+ ** total cost of the join. For joins, though, we want to figure out\r
+ ** the best plan for the join knowing how many outer rows there are -\r
+ ** it could affect the join strategy significantly. So we implement\r
+ ** optimizeIt() here, which overrides the optimizeIt() in FromTable.\r
+ ** This assumes that the join strategy for which this join node is\r
+ ** the inner table is a nested loop join, which will not be a valid\r
+ ** assumption when we implement other strategies like materialization\r
+ ** (hash join can work only on base tables).\r
+ */\r
+\r
+ /* RESOLVE - Need to figure out how to really optimize this node. */\r
+\r
+ // RESOLVE: NEED TO SET ROW ORDERING OF SOURCES IN THE ROW ORDERING\r
+ // THAT WAS PASSED IN.\r
+ leftResultSet = optimizeSource(\r
+ optimizer,\r
+ leftResultSet,\r
+ getLeftPredicateList(),\r
+ outerCost);\r
+\r
+ /* Move all joinPredicates down to the right.\r
+ * RESOLVE - When we consider the reverse join order then\r
+ * we will have to pull them back up and then push them\r
+ * down to the other side when considering the reverse\r
+ * join order.\r
+ * RESOLVE - This logic needs to be looked at when we\r
+ * implement full outer join.\r
+ */\r
+ // Walk joinPredicates backwards due to possible deletes\r
+ for (int index = joinPredicates.size() - 1; index >= 0; index --)\r
+ {\r
+ JBitSet curBitSet;\r
+ Predicate predicate;\r
+\r
+ predicate = (Predicate) joinPredicates.elementAt(index);\r
+ if (! predicate.getPushable())\r
+ {\r
+ continue;\r
+ }\r
+ joinPredicates.removeElementAt(index);\r
+ getRightPredicateList().addElement(predicate);\r
+ }\r
+\r
+ rightResultSet = optimizeSource(\r
+ optimizer,\r
+ rightResultSet,\r
+ getRightPredicateList(),\r
+ leftResultSet.getCostEstimate());\r
+\r
+ costEstimate = getCostEstimate(optimizer);\r
+\r
+ /*\r
+ ** We add the costs for the inner and outer table, but the number\r
+ ** of rows is that for the inner table only.\r
+ */\r
+ costEstimate.setCost(\r
+ leftResultSet.getCostEstimate().getEstimatedCost() +\r
+ rightResultSet.getCostEstimate().getEstimatedCost(),\r
+ rightResultSet.getCostEstimate().rowCount(),\r
+ rightResultSet.getCostEstimate().rowCount());\r
+\r
+ /*\r
+ ** Some types of joins (e.g. outer joins) will return a different\r
+ ** number of rows than is predicted by optimizeIt() in JoinNode.\r
+ ** So, adjust this value now. This method does nothing for most\r
+ ** join types.\r
+ */\r
+ adjustNumberOfRowsReturned(costEstimate);\r
+\r
+ /*\r
+ ** Get the cost of this result set in the context of the whole plan.\r
+ */\r
+ getCurrentAccessPath().\r
+ getJoinStrategy().\r
+ estimateCost(\r
+ this,\r
+ predList,\r
+ (ConglomerateDescriptor) null,\r
+ outerCost,\r
+ optimizer,\r
+ costEstimate\r
+ );\r
+\r
+ optimizer.considerCost(this, predList, costEstimate, outerCost);\r
+\r
+ /* Optimize subqueries only once, no matter how many times we're called */\r
+ if ( (! optimized) && (subqueryList != null))\r
+ {\r
+ /* RESOLVE - Need to figure out how to really optimize this node.\r
+ * Also need to figure out the pushing of the joinClause.\r
+ */\r
+ subqueryList.optimize(optimizer.getDataDictionary(),\r
+ costEstimate.rowCount());\r
+ subqueryList.modifyAccessPaths();\r
+ }\r
+\r
+ optimized = true;\r
+\r
+ return costEstimate;\r
+ }\r
+\r
+ /**\r
+ * @see Optimizable#pushOptPredicate\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public boolean pushOptPredicate(OptimizablePredicate optimizablePredicate)\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(optimizablePredicate instanceof Predicate,\r
+ "optimizablePredicate expected to be instanceof Predicate");\r
+ SanityManager.ASSERT(! optimizablePredicate.hasSubquery() &&\r
+ ! optimizablePredicate.hasMethodCall(),\r
+ "optimizablePredicate either has a subquery or a method call");\r
+ }\r
+\r
+ /* Add the matching predicate to the joinPredicates */\r
+ joinPredicates.addPredicate((Predicate) optimizablePredicate);\r
+\r
+ /* Remap all of the ColumnReferences to point to the\r
+ * source of the values.\r
+ */\r
+ RemapCRsVisitor rcrv = new RemapCRsVisitor(true);\r
+ ((Predicate) optimizablePredicate).getAndNode().accept(rcrv);\r
+\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * @see Optimizable#modifyAccessPath\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public Optimizable modifyAccessPath(JBitSet outerTables) throws StandardException\r
+ {\r
+ super.modifyAccessPath(outerTables);\r
+\r
+ /* By the time we're done here, both the left and right\r
+ * predicate lists should be empty because we pushed everything\r
+ * down.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (getLeftPredicateList().size() != 0)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "getLeftPredicateList().size() expected to be 0, not " +\r
+ getLeftPredicateList().size());\r
+ }\r
+ if (getRightPredicateList().size() != 0)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "getRightPredicateList().size() expected to be 0, not " +\r
+ getRightPredicateList().size());\r
+ }\r
+ }\r
+\r
+ return this;\r
+ }\r
+\r
+\r
+ /**\r
+ * Some types of joins (e.g. outer joins) will return a different\r
+ * number of rows than is predicted by optimizeIt() in JoinNode.\r
+ * So, adjust this value now. This method does nothing for most\r
+ * join types.\r
+ */\r
+ protected void adjustNumberOfRowsReturned(CostEstimate costEstimate)\r
+ {\r
+ }\r
+\r
+ /**\r
+ * Return a ResultColumnList with all of the columns in this table.\r
+ * (Used in expanding '*'s.)\r
+ * NOTE: Since this method is for expanding a "*" in the SELECT list,\r
+ * ResultColumn.expression will be a ColumnReference.\r
+ *\r
+ * @param allTableName The qualifier on the "*"\r
+ *\r
+ * @return ResultColumnList List of result columns from this table.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ResultColumnList getAllResultColumns(TableName allTableName)\r
+ throws StandardException\r
+ {\r
+ /* We need special processing when there is a USING clause.\r
+ * The resulting table will be the join columns from\r
+ * the outer table followed by the non-join columns from \r
+ * left side plus the non-join columns from the right side.\r
+ */\r
+ if (usingClause == null)\r
+ {\r
+ return getAllResultColumnsNoUsing(allTableName);\r
+ }\r
+\r
+ /* Get the logical left side of the join.\r
+ * This is where the join columns come from.\r
+ * (For RIGHT OUTER JOIN, the left is the right\r
+ * and the right is the left and the JOIN is the NIOJ).\r
+ */\r
+ ResultSetNode logicalLeftRS = getLogicalLeftResultSet();\r
+\r
+ // Get the join columns\r
+ ResultColumnList joinRCL = logicalLeftRS.getAllResultColumns(\r
+ null).\r
+ getJoinColumns(usingClause);\r
+\r
+ // Get the left and right RCLs\r
+ ResultColumnList leftRCL = leftResultSet.getAllResultColumns(allTableName); \r
+ ResultColumnList rightRCL = rightResultSet.getAllResultColumns(allTableName); \r
+\r
+ /* Chop the join columns out of the both left and right.\r
+ * Thanks to the ANSI committee, the join columns \r
+ * do not belong to either table.\r
+ */\r
+ if (leftRCL != null)\r
+ {\r
+ leftRCL.removeJoinColumns(usingClause);\r
+ }\r
+ if (rightRCL != null)\r
+ {\r
+ rightRCL.removeJoinColumns(usingClause);\r
+ }\r
+\r
+ /* If allTableName is null, then we want to return the splicing\r
+ * of the join columns followed by the non-join columns from\r
+ * the left followed by the non-join columns from the right.\r
+ * If not, then at most 1 side should match.\r
+ * NOTE: We need to make sure that the RC's VirtualColumnIds\r
+ * are correct (1 .. size).\r
+ */\r
+ if (leftRCL == null)\r
+ {\r
+ rightRCL.resetVirtualColumnIds();\r
+ return rightRCL;\r
+ }\r
+ else if (rightRCL == null)\r
+ {\r
+ leftRCL.resetVirtualColumnIds();\r
+ return leftRCL;\r
+ }\r
+ else\r
+ {\r
+ /* Both sides are non-null. This should only happen\r
+ * if allTableName is null.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (allTableName != null)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "allTableName (" + allTableName + \r
+ ") expected to be null");\r
+ }\r
+ }\r
+ joinRCL.destructiveAppend(leftRCL);\r
+ joinRCL.destructiveAppend(rightRCL);\r
+ joinRCL.resetVirtualColumnIds();\r
+ return joinRCL;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Return a ResultColumnList with all of the columns in this table.\r
+ * (Used in expanding '*'s.)\r
+ * NOTE: Since this method is for expanding a "*" in the SELECT list,\r
+ * ResultColumn.expression will be a ColumnReference.\r
+ * NOTE: This method is handles the case when there is no USING clause.\r
+ * The caller handles the case when there is a USING clause.\r
+ *\r
+ * @param allTableName The qualifier on the "*"\r
+ *\r
+ * @return ResultColumnList List of result columns from this table.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private ResultColumnList getAllResultColumnsNoUsing(TableName allTableName)\r
+ throws StandardException\r
+ {\r
+ ResultColumnList leftRCL = leftResultSet.getAllResultColumns(allTableName); \r
+ ResultColumnList rightRCL = rightResultSet.getAllResultColumns(allTableName); \r
+ /* If allTableName is null, then we want to return the spliced\r
+ * left and right RCLs. If not, then at most 1 side should match.\r
+ */\r
+ if (leftRCL == null)\r
+ {\r
+ return rightRCL;\r
+ }\r
+ else if (rightRCL == null)\r
+ {\r
+ return leftRCL;\r
+ }\r
+ else\r
+ {\r
+ /* Both sides are non-null. This should only happen\r
+ * if allTableName is null.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (allTableName != null)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "allTableName (" + allTableName + \r
+ ") expected to be null");\r
+ }\r
+ }\r
+\r
+ // Return a spliced copy of the 2 lists\r
+ ResultColumnList tempList =\r
+ (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+ tempList.nondestructiveAppend(leftRCL);\r
+ tempList.nondestructiveAppend(rightRCL);\r
+ return tempList;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Try to find a ResultColumn in the table represented by this FromTable\r
+ * that matches the name in the given ColumnReference.\r
+ *\r
+ * @param columnReference The columnReference whose name we're looking\r
+ * for in the given table.\r
+ *\r
+ * @return A ResultColumn whose expression is the ColumnNode\r
+ * that matches the ColumnReference.\r
+ * Returns null if there is no match.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultColumn getMatchingColumn(ColumnReference columnReference) throws StandardException\r
+ {\r
+ /* Get the logical left and right sides of the join.\r
+ * (For RIGHT OUTER JOIN, the left is the right\r
+ * and the right is the left and the JOIN is the NIOJ).\r
+ */\r
+ ResultSetNode logicalLeftRS = getLogicalLeftResultSet();\r
+ ResultSetNode logicalRightRS = getLogicalRightResultSet();\r
+ ResultColumn leftRC = null;\r
+ ResultColumn resultColumn = null;\r
+ ResultColumn rightRC = null;\r
+ ResultColumn usingRC = null;\r
+\r
+ leftRC = logicalLeftRS.getMatchingColumn(columnReference);\r
+\r
+ if (leftRC != null)\r
+ {\r
+ resultColumn = leftRC;\r
+\r
+ /* Find out if the column is in the using clause */\r
+ if (usingClause != null)\r
+ {\r
+ usingRC = usingClause.getResultColumn(leftRC.getName());\r
+ }\r
+ }\r
+\r
+ /* We only search on the right if the column isn't in the\r
+ * USING clause.\r
+ */\r
+ if (usingRC == null)\r
+ {\r
+ rightRC = logicalRightRS.getMatchingColumn(columnReference);\r
+ }\r
+\r
+ if (rightRC != null)\r
+ {\r
+ /* We must catch ambiguous column references for joins here,\r
+ * since FromList only checks for ambiguous references between\r
+ * nodes, not within a node.\r
+ */\r
+ if (leftRC != null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_AMBIGUOUS_COLUMN_NAME, \r
+ columnReference.getSQLColumnName());\r
+ }\r
+ resultColumn = rightRC;\r
+ }\r
+\r
+ /* Insert will bind the underlying result sets which have\r
+ * tables twice. On the 2nd bind, resultColumns != null,\r
+ * we must return the RC from the JoinNode's RCL which is above\r
+ * the RC that we just found above. (Otherwise, the source\r
+ * for the ColumnReference will be from the wrong ResultSet\r
+ * at generate().)\r
+ */\r
+ if (resultColumns != null)\r
+ {\r
+ int rclSize = resultColumns.size();\r
+ for (int index = 0; index < rclSize; index++)\r
+ {\r
+ ResultColumn rc = (ResultColumn) resultColumns.elementAt(index);\r
+ VirtualColumnNode vcn = (VirtualColumnNode) rc.getExpression();\r
+ if (resultColumn == vcn.getSourceColumn())\r
+ {\r
+ resultColumn = rc;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ return resultColumn;\r
+ }\r
+\r
+ /**\r
+ * Bind the result columns of this ResultSetNode when there is no\r
+ * base table to bind them to. This is useful for SELECT statements,\r
+ * where the result columns get their types from the expressions that\r
+ * live under them.\r
+ *\r
+ * @param fromListParam FromList to use/append to.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void bindResultColumns(FromList fromListParam)\r
+ throws StandardException\r
+ {\r
+ super.bindResultColumns(fromListParam);\r
+\r
+ /* Now we build our RCL */\r
+ buildRCL();\r
+\r
+ /* We cannot bind the join clause until after we've bound our\r
+ * result columns. This is because the resultColumns from the\r
+ * children are propagated and merged to create our resultColumns\r
+ * in super.bindRCs(). If we bind the join clause prior to that\r
+ * call, then the ColumnReferences in the join clause will point\r
+ * to the children's RCLs at the time that they are bound, but\r
+ * will end up pointing above themselves, to our resultColumns,\r
+ * after the call to super.bindRCS().\r
+ */\r
+ deferredBindExpressions(fromListParam);\r
+ }\r
+\r
+ /**\r
+ * Bind the result columns for this ResultSetNode to a base table.\r
+ * This is useful for INSERT and UPDATE statements, where the\r
+ * result columns get their types from the table being updated or\r
+ * inserted into.\r
+ * If a result column list is specified, then the verification that the \r
+ * result column list does not contain any duplicates will be done when\r
+ * binding them by name.\r
+ *\r
+ * @param targetTableDescriptor The TableDescriptor for the table being\r
+ * updated or inserted into\r
+ * @param targetColumnList For INSERT statements, the user\r
+ * does not have to supply column\r
+ * names (for example, "insert into t\r
+ * values (1,2,3)". When this\r
+ * parameter is null, it means that\r
+ * the user did not supply column\r
+ * names, and so the binding should\r
+ * be done based on order. When it\r
+ * is not null, it means do the binding\r
+ * by name, not position.\r
+ * @param statement Calling DMLStatementNode (Insert or Update)\r
+ * @param fromListParam FromList to use/append to.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void bindResultColumns(TableDescriptor targetTableDescriptor,\r
+ FromVTI targetVTI,\r
+ ResultColumnList targetColumnList,\r
+ DMLStatementNode statement,\r
+ FromList fromListParam)\r
+ throws StandardException\r
+ {\r
+ super.bindResultColumns(targetTableDescriptor,\r
+ targetVTI,\r
+ targetColumnList, statement, \r
+ fromListParam);\r
+\r
+ /* Now we build our RCL */\r
+ buildRCL();\r
+\r
+ /* We cannot bind the join clause until after we've bound our\r
+ * result columns. This is because the resultColumns from the\r
+ * children are propagated and merged to create our resultColumns\r
+ * in super.bindRCs(). If we bind the join clause prior to that\r
+ * call, then the ColumnReferences in the join clause will point\r
+ * to the children's RCLs at the time that they are bound, but\r
+ * will end up pointing above themselves, to our resultColumns,\r
+ * after the call to super.bindRCS().\r
+ */\r
+ deferredBindExpressions(fromListParam);\r
+ }\r
+\r
+ /**\r
+ * Build the RCL for this node. We propagate the RCLs up from the\r
+ * children and splice them to form this node's RCL.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ private void buildRCL() throws StandardException\r
+ {\r
+ /* NOTE - we only need to build this list if it does not already \r
+ * exist. This can happen in the degenerate case of an insert\r
+ * select with a join expression in a derived table within the select.\r
+ */\r
+ if (resultColumns != null)\r
+ {\r
+ return;\r
+ }\r
+\r
+ ResultColumnList leftRCL;\r
+ ResultColumnList rightRCL;\r
+ ResultColumnList tmpRCL;\r
+\r
+ /* We get a shallow copy of the left's ResultColumnList and its \r
+ * ResultColumns. (Copy maintains ResultColumn.expression for now.)\r
+ */\r
+ resultColumns = leftResultSet.getResultColumns();\r
+ leftRCL = resultColumns.copyListAndObjects();\r
+ leftResultSet.setResultColumns(leftRCL);\r
+\r
+ /* Replace ResultColumn.expression with new VirtualColumnNodes\r
+ * in the ProjectRestrictNode's ResultColumnList. (VirtualColumnNodes include\r
+ * pointers to source ResultSetNode, this, and source ResultColumn.)\r
+ */\r
+ resultColumns.genVirtualColumnNodes(leftResultSet, leftRCL, false);\r
+\r
+ /*\r
+ ** If this is a right outer join, we can get nulls on the left side,\r
+ ** so change the types of the left result set to be nullable.\r
+ */\r
+ if (this instanceof HalfOuterJoinNode && ((HalfOuterJoinNode)this).isRightOuterJoin())\r
+ {\r
+ resultColumns.setNullability(true);\r
+ }\r
+\r
+ /* Now, repeat the process with the right's RCL */\r
+ tmpRCL = rightResultSet.getResultColumns();\r
+ rightRCL = tmpRCL.copyListAndObjects();\r
+ rightResultSet.setResultColumns(rightRCL);\r
+\r
+ /* Replace ResultColumn.expression with new VirtualColumnNodes\r
+ * in the ProjectRestrictNode's ResultColumnList. (VirtualColumnNodes include\r
+ * pointers to source ResultSetNode, this, and source ResultColumn.)\r
+ */\r
+ tmpRCL.genVirtualColumnNodes(rightResultSet, rightRCL, false);\r
+ tmpRCL.adjustVirtualColumnIds(resultColumns.size());\r
+\r
+ /*\r
+ ** If this is a left outer join, we can get nulls on the right side,\r
+ ** so change the types of the right result set to be nullable.\r
+ */\r
+ if (this instanceof HalfOuterJoinNode && !((HalfOuterJoinNode)this).isRightOuterJoin())\r
+ {\r
+ tmpRCL.setNullability(true);\r
+ }\r
+ \r
+ /* Now we append the propagated RCL from the right to the one from\r
+ * the left and call it our own.\r
+ */\r
+ resultColumns.nondestructiveAppend(tmpRCL);\r
+ }\r
+\r
+ private void deferredBindExpressions(FromList fromListParam)\r
+ throws StandardException\r
+ {\r
+ /* Bind the expressions in the join clause */\r
+ subqueryList = (SubqueryList) getNodeFactory().getNode(\r
+ C_NodeTypes.SUBQUERY_LIST,\r
+ getContextManager());\r
+ aggregateVector = new Vector();\r
+\r
+ /* ON clause */\r
+ if (joinClause != null)\r
+ {\r
+ /* Create a new fromList with only left and right children before\r
+ * binding the join clause. Valid column references in the join clause\r
+ * are limited to columns from the 2 tables being joined. This\r
+ * algorithm enforces that.\r
+ */\r
+ FromList fromList = (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager());\r
+ fromList.addElement((FromTable) leftResultSet);\r
+ fromList.addElement((FromTable) rightResultSet);\r
+\r
+ /* First bind with all tables in the from clause, to detect ambiguous\r
+ * references. Push the left and right children to the front of the\r
+ * fromListParam before binding the join clause. (We will\r
+ * remove it before returning.) Valid column references in\r
+ * the join clause are limited to columns from the 2 tables being\r
+ * joined\r
+ */\r
+ fromListParam.insertElementAt(rightResultSet, 0);\r
+ fromListParam.insertElementAt(leftResultSet, 0);\r
+ joinClause = joinClause.bindExpression(\r
+ fromListParam, subqueryList,\r
+ aggregateVector);\r
+\r
+ /* Now bind with two tables being joined. If this raises column not found exception,\r
+ * then we have a reference to other tables in the from clause. Raise invalid\r
+ * ON clause error to match DB2.\r
+ */\r
+ try {\r
+ joinClause = joinClause.bindExpression(\r
+ fromList, subqueryList,\r
+ aggregateVector);\r
+ } catch (StandardException se) {\r
+ if (se.getSQLState().equals(SQLState.LANG_COLUMN_NOT_FOUND))\r
+ throw StandardException.newException(SQLState.LANG_DB2_ON_CLAUSE_INVALID); \r
+ throw se;\r
+ }\r
+\r
+ /* DB2 doesn't allow subquerries in the ON clause */\r
+ if (subqueryList.size() > 0)\r
+ throw StandardException.newException(SQLState.LANG_DB2_ON_CLAUSE_INVALID); \r
+ /*\r
+ ** We cannot have aggregates in the ON clause.\r
+ ** In the future, if we relax this, we'll need\r
+ ** to be able to pass the aggregateVector up\r
+ ** the tree.\r
+ */\r
+ if (aggregateVector.size() > 0)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_NO_AGGREGATES_IN_ON_CLAUSE);\r
+ }\r
+\r
+ fromListParam.removeElementAt(0);\r
+ fromListParam.removeElementAt(0);\r
+ }\r
+ /* USING clause */\r
+ else if (usingClause != null)\r
+ {\r
+ /* Build a join clause from the usingClause, using the\r
+ * exposed names in the left and right RSNs.\r
+ * For each column in the list, we generate 2 ColumnReferences,\r
+ * 1 for the left and 1 for the right. We bind each of these\r
+ * to the appropriate side and build an equality predicate\r
+ * between the 2. We bind the = and AND nodes by hand because\r
+ * we have to bind the ColumnReferences a side at a time.\r
+ * We need to bind the CRs a side at a time to ensure that\r
+ * we don't find an bogus ambiguous column reference. (Bug 377)\r
+ */\r
+ joinClause = (AndNode) getNodeFactory().getNode(\r
+ C_NodeTypes.AND_NODE,\r
+ null,\r
+ null,\r
+ getContextManager());\r
+ AndNode currAnd = (AndNode) joinClause;\r
+ ValueNode trueNode = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.BOOLEAN_CONSTANT_NODE,\r
+ Boolean.TRUE,\r
+ getContextManager());\r
+\r
+ int usingSize = usingClause.size();\r
+ for (int index = 0; index < usingSize; index++)\r
+ {\r
+ BinaryComparisonOperatorNode equalsNode;\r
+ ColumnReference leftCR;\r
+ ColumnReference rightCR;\r
+ ResultColumn rc = (ResultColumn) usingClause.elementAt(index);\r
+\r
+ /* currAnd starts as first point of insertion (leftOperand == null)\r
+ * and becomes last point of insertion.\r
+ */\r
+ if (currAnd.getLeftOperand() != null)\r
+ {\r
+ currAnd.setRightOperand(\r
+ (AndNode) getNodeFactory().getNode(\r
+ C_NodeTypes.AND_NODE,\r
+ null,\r
+ null,\r
+ getContextManager()));\r
+ currAnd = (AndNode) currAnd.getRightOperand();\r
+ }\r
+\r
+ /* Create and bind the left CR */\r
+ fromListParam.insertElementAt(leftResultSet, 0);\r
+ leftCR = (ColumnReference) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ rc.getName(),\r
+ ((FromTable) leftResultSet).getTableName(),\r
+ getContextManager()); \r
+ leftCR = (ColumnReference) leftCR.bindExpression(\r
+ fromListParam, subqueryList,\r
+ aggregateVector);\r
+ fromListParam.removeElementAt(0);\r
+\r
+ /* Create and bind the right CR */\r
+ fromListParam.insertElementAt(rightResultSet, 0);\r
+ rightCR = (ColumnReference) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ rc.getName(),\r
+ ((FromTable) rightResultSet).getTableName(),\r
+ getContextManager()); \r
+ rightCR = (ColumnReference) rightCR.bindExpression(\r
+ fromListParam, subqueryList,\r
+ aggregateVector);\r
+ fromListParam.removeElementAt(0);\r
+\r
+ /* Create and insert the new = condition */\r
+ equalsNode = (BinaryComparisonOperatorNode)\r
+ getNodeFactory().getNode(\r
+ C_NodeTypes.BINARY_EQUALS_OPERATOR_NODE,\r
+ leftCR,\r
+ rightCR,\r
+ getContextManager());\r
+ equalsNode.bindComparisonOperator();\r
+\r
+ currAnd.setLeftOperand(equalsNode);\r
+ /* The right deep chain of AndNodes ends in a BinaryTrueNode.\r
+ * NOTE: We set it for every AndNode, even though we will\r
+ * overwrite it if this is not the last column in the list,\r
+ * because postBindFixup() expects both the AndNode to have\r
+ * both the left and right operands.\r
+ */\r
+ currAnd.setRightOperand(trueNode);\r
+ currAnd.postBindFixup();\r
+ }\r
+ }\r
+\r
+ if (joinClause != null)\r
+ {\r
+ /* If joinClause is a parameter, (where ?), then we assume\r
+ * it will be a nullable boolean.\r
+ */\r
+ if (joinClause.requiresTypeFromContext())\r
+ {\r
+ joinClause.setType(new DataTypeDescriptor(TypeId.BOOLEAN_ID, true));\r
+ }\r
+ \r
+ /*\r
+ ** Is the datatype of the JOIN clause BOOLEAN?\r
+ **\r
+ ** NOTE: This test is not necessary in SQL92 entry level, because\r
+ ** it is syntactically impossible to have a non-Boolean JOIN clause\r
+ ** in that level of the standard. But we intend to extend the\r
+ ** language to allow Boolean user functions in the JOIN clause,\r
+ ** so we need to test for the error condition.\r
+ */\r
+ TypeId joinTypeId = joinClause.getTypeId();\r
+\r
+ /* If the where clause is not a built-in type, then generate a bound \r
+ * conversion tree to a built-in type.\r
+ */\r
+ if (joinTypeId.userType())\r
+ {\r
+ joinClause = joinClause.genSQLJavaSQLTree();\r
+ }\r
+\r
+ if (! joinClause.getTypeServices().getTypeId().equals(\r
+ TypeId.BOOLEAN_ID))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_NON_BOOLEAN_JOIN_CLAUSE, \r
+ joinClause.getTypeServices().getTypeId().getSQLTypeName()\r
+ );\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /** \r
+ * Put a ProjectRestrictNode on top of each FromTable in the FromList.\r
+ * ColumnReferences must continue to point to the same ResultColumn, so\r
+ * that ResultColumn must percolate up to the new PRN. However,\r
+ * that ResultColumn will point to a new expression, a VirtualColumnNode, \r
+ * which points to the FromTable and the ResultColumn that is the source for\r
+ * the ColumnReference. \r
+ * (The new PRN will have the original of the ResultColumnList and\r
+ * the ResultColumns from that list. The FromTable will get shallow copies\r
+ * of the ResultColumnList and its ResultColumns. ResultColumn.expression\r
+ * will remain at the FromTable, with the PRN getting a new \r
+ * VirtualColumnNode for each ResultColumn.expression.)\r
+ * We then project out the non-referenced columns. If there are no referenced\r
+ * columns, then the PRN's ResultColumnList will consist of a single ResultColumn\r
+ * whose expression is 1.\r
+ *\r
+ * @param numTables Number of tables in the DML Statement\r
+ * @param gbl The group by list, if any\r
+ * @param fromList The from list, if any\r
+ *\r
+ * @return The generated ProjectRestrictNode atop the original FromTable.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ResultSetNode preprocess(int numTables,\r
+ GroupByList gbl,\r
+ FromList fromList)\r
+ throws StandardException\r
+ {\r
+ ResultSetNode newTreeTop;\r
+ \r
+ newTreeTop = super.preprocess(numTables, gbl, fromList);\r
+\r
+ /* Put the expression trees in conjunctive normal form.\r
+ * NOTE - This needs to occur before we preprocess the subqueries\r
+ * because the subquery transformations assume that any subquery operator \r
+ * negation has already occurred.\r
+ */\r
+ if (joinClause != null)\r
+ {\r
+ normExpressions();\r
+\r
+ /* Preprocess any subqueries in the join clause */\r
+ if (subqueryList != null)\r
+ {\r
+ /* RESOLVE - In order to flatten a subquery in\r
+ * the ON clause of an inner join we'd have to pass\r
+ * the various lists from the outer select through to\r
+ * ResultSetNode.preprocess() and overload\r
+ * normExpressions in HalfOuterJoinNode. That's not\r
+ * worth the effort, so we say that the ON clause\r
+ * is not under a top level AND in normExpressions()\r
+ * to ensure that subqueries in the ON clause do not\r
+ * get flattened. That allows us to pass empty lists\r
+ * to joinClause.preprocess() because we know that no\r
+ * flattening will take place. (Bug #1206)\r
+ */\r
+ joinClause.preprocess(\r
+ numTables,\r
+ (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager()),\r
+ (SubqueryList) getNodeFactory().getNode(\r
+ C_NodeTypes.SUBQUERY_LIST,\r
+ getContextManager()),\r
+ (PredicateList) getNodeFactory().getNode(\r
+ C_NodeTypes.PREDICATE_LIST,\r
+ getContextManager()));\r
+ }\r
+\r
+ /* Pull apart the expression trees */\r
+ joinPredicates.pullExpressions(numTables, joinClause);\r
+ joinPredicates.categorize();\r
+ joinClause = null;\r
+ }\r
+\r
+ return newTreeTop;\r
+ }\r
+\r
+ /**\r
+ * Find the unreferenced result columns and project them out. This is used in pre-processing joins\r
+ * that are not flattened into the where clause.\r
+ */\r
+ void projectResultColumns() throws StandardException\r
+ {\r
+ leftResultSet.projectResultColumns();\r
+ rightResultSet.projectResultColumns();\r
+ resultColumns.pullVirtualIsReferenced();\r
+ super.projectResultColumns();\r
+ }\r
+ \r
+ /** Put the expression trees in conjunctive normal form \r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void normExpressions()\r
+ throws StandardException\r
+ {\r
+ if (joinClauseNormalized == true) return;\r
+\r
+ /* For each expression tree:\r
+ * o Eliminate NOTs (eliminateNots())\r
+ * o Ensure that there is an AndNode on top of every\r
+ * top level expression. (putAndsOnTop())\r
+ * o Finish the job (changeToCNF())\r
+ */\r
+ joinClause = joinClause.eliminateNots(false);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!(joinClause.verifyEliminateNots()) )\r
+ {\r
+ joinClause.treePrint();\r
+ SanityManager.THROWASSERT(\r
+ "joinClause in invalid form: " + joinClause); \r
+ }\r
+ }\r
+ joinClause = joinClause.putAndsOnTop();\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (! ((joinClause instanceof AndNode) &&\r
+ (joinClause.verifyPutAndsOnTop())) )\r
+ {\r
+ joinClause.treePrint();\r
+ SanityManager.THROWASSERT(\r
+ "joinClause in invalid form: " + joinClause); \r
+ }\r
+ }\r
+ /* RESOLVE - ON clause is temporarily "not under a top\r
+ * top level AND" until we figure out how to deal with\r
+ * subqueries in the ON clause. (Bug 1206)\r
+ */\r
+ joinClause = joinClause.changeToCNF(false);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (! ((joinClause instanceof AndNode) &&\r
+ (joinClause.verifyChangeToCNF())) )\r
+ {\r
+ joinClause.treePrint();\r
+ SanityManager.THROWASSERT(\r
+ "joinClause in invalid form: " + joinClause); \r
+ }\r
+ }\r
+\r
+ joinClauseNormalized = true;\r
+ }\r
+\r
+ /**\r
+ * Push expressions down to the first ResultSetNode which can do expression\r
+ * evaluation and has the same referenced table map.\r
+ * RESOLVE - This means only pushing down single table expressions to\r
+ * DistinctNodes today. Once we have a better understanding of how\r
+ * the optimizer will work, we can push down join clauses.\r
+ *\r
+ * @param outerPredicateList The PredicateList from the outer RS.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void pushExpressions(PredicateList outerPredicateList)\r
+ throws StandardException\r
+ {\r
+ FromTable leftFromTable = (FromTable) leftResultSet;\r
+ FromTable rightFromTable = (FromTable) rightResultSet;\r
+\r
+ /* OuterJoinNodes are responsible for overriding this\r
+ * method since they have different rules about where predicates\r
+ * can be applied.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (this instanceof HalfOuterJoinNode)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "JN.pushExpressions() not expected to be called for " +\r
+ getClass().getName());\r
+ }\r
+ }\r
+\r
+ /* We try to push "pushable" \r
+ * predicates to 1 of 3 places:\r
+ * o Predicates that only reference tables\r
+ * on the left are pushed to the leftPredicateList.\r
+ * o Predicates that only reference tables\r
+ * on the right are pushed to the rightPredicateList.\r
+ * o Predicates which reference tables on both\r
+ * sides (and no others) are pushed to \r
+ * the joinPredicates and may be pushed down\r
+ * further during optimization.\r
+ */\r
+ // Left only\r
+ pushExpressionsToLeft(outerPredicateList);\r
+ leftFromTable.pushExpressions(getLeftPredicateList());\r
+ // Right only\r
+ pushExpressionsToRight(outerPredicateList);\r
+ rightFromTable.pushExpressions(getRightPredicateList());\r
+ // Join predicates\r
+ grabJoinPredicates(outerPredicateList);\r
+\r
+ /* By the time we're done here, both the left and right\r
+ * predicate lists should be empty because we pushed everything\r
+ * down.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (getLeftPredicateList().size() != 0)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "getLeftPredicateList().size() expected to be 0, not " +\r
+ getLeftPredicateList().size());\r
+ }\r
+ if (getRightPredicateList().size() != 0)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "getRightPredicateList().size() expected to be 0, not " +\r
+ getRightPredicateList().size());\r
+ }\r
+ }\r
+ }\r
+\r
+ protected void pushExpressionsToLeft(PredicateList outerPredicateList)\r
+ throws StandardException\r
+ {\r
+ FromTable leftFromTable = (FromTable) leftResultSet;\r
+\r
+ JBitSet leftReferencedTableMap = leftFromTable.getReferencedTableMap();\r
+\r
+ /* Build a list of the single table predicates on left result set\r
+ * that we can push down \r
+ */\r
+ // Walk outerPredicateList backwards due to possible deletes\r
+ for (int index = outerPredicateList.size() - 1; index >= 0; index --)\r
+ {\r
+ JBitSet curBitSet;\r
+ Predicate predicate;\r
+\r
+ predicate = (Predicate) outerPredicateList.elementAt(index);\r
+ if (! predicate.getPushable())\r
+ {\r
+ continue;\r
+ }\r
+\r
+ curBitSet = predicate.getReferencedSet();\r
+ \r
+ /* Do we have a match? */\r
+ if (leftReferencedTableMap.contains(curBitSet))\r
+ {\r
+ /* Add the matching predicate to the push list */\r
+ getLeftPredicateList().addPredicate(predicate);\r
+\r
+ /* Remap all of the ColumnReferences to point to the\r
+ * source of the values.\r
+ * The tree is something like:\r
+ * PRN1\r
+ * |\r
+ * JN (this)\r
+ * / \\r
+ * PRN2 PRN3\r
+ * | |\r
+ * FBT1 FBT2\r
+ *\r
+ * The ColumnReferences start off pointing to the RCL off of\r
+ * PRN1. For optimization, we want them to point to the\r
+ * RCL off of PRN2. In order to do that, we remap them\r
+ * twice here. If optimization pushes them down to the\r
+ * base table, it will remap them again.\r
+ */\r
+ RemapCRsVisitor rcrv = new RemapCRsVisitor(true);\r
+ predicate.getAndNode().accept(rcrv);\r
+ predicate.getAndNode().accept(rcrv);\r
+\r
+ /* Remove the matching predicate from the outer list */\r
+ outerPredicateList.removeElementAt(index);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void pushExpressionsToRight(PredicateList outerPredicateList)\r
+ throws StandardException\r
+ {\r
+ FromTable rightFromTable = (FromTable) rightResultSet;\r
+\r
+ JBitSet rightReferencedTableMap = rightFromTable.getReferencedTableMap();\r
+\r
+ /* Build a list of the single table predicates on right result set\r
+ * that we can push down \r
+ */\r
+ // Walk outerPredicateList backwards due to possible deletes\r
+ for (int index = outerPredicateList.size() - 1; index >= 0; index --)\r
+ {\r
+ JBitSet curBitSet;\r
+ Predicate predicate;\r
+\r
+ predicate = (Predicate) outerPredicateList.elementAt(index);\r
+ if (! predicate.getPushable())\r
+ {\r
+ continue;\r
+ }\r
+\r
+ curBitSet = predicate.getReferencedSet();\r
+ \r
+ /* Do we have a match? */\r
+ if (rightReferencedTableMap.contains(curBitSet))\r
+ {\r
+ /* Add the matching predicate to the push list */\r
+ getRightPredicateList().addPredicate(predicate);\r
+\r
+ /* Remap all of the ColumnReferences to point to the\r
+ * source of the values.\r
+ * The tree is something like:\r
+ * PRN1\r
+ * |\r
+ * JN (this)\r
+ * / \\r
+ * PRN2 PRN3\r
+ * | |\r
+ * FBT1 FBT2\r
+ *\r
+ * The ColumnReferences start off pointing to the RCL off of\r
+ * PRN1. For optimization, we want them to point to the\r
+ * RCL off of PRN3. In order to do that, we remap them\r
+ * twice here. If optimization pushes them down to the\r
+ * base table, it will remap them again.\r
+ */\r
+ RemapCRsVisitor rcrv = new RemapCRsVisitor(true);\r
+ predicate.getAndNode().accept(rcrv);\r
+ predicate.getAndNode().accept(rcrv);\r
+\r
+ /* Remove the matching predicate from the outer list */\r
+ outerPredicateList.removeElementAt(index);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void grabJoinPredicates(PredicateList outerPredicateList)\r
+ throws StandardException\r
+ {\r
+ FromTable leftFromTable = (FromTable) leftResultSet;\r
+ FromTable rightFromTable = (FromTable) rightResultSet;\r
+\r
+ JBitSet leftReferencedTableMap = leftFromTable.getReferencedTableMap();\r
+ JBitSet rightReferencedTableMap = rightFromTable.getReferencedTableMap();\r
+\r
+ /* Build a list of the join predicates that we can push down */\r
+ // Walk outerPredicateList backwards due to possible deletes\r
+ for (int index = outerPredicateList.size() - 1; index >= 0; index --)\r
+ {\r
+ JBitSet curBitSet;\r
+ Predicate predicate;\r
+\r
+ predicate = (Predicate) outerPredicateList.elementAt(index);\r
+ if (! predicate.getPushable())\r
+ {\r
+ continue;\r
+ }\r
+\r
+ curBitSet = predicate.getReferencedSet();\r
+ \r
+ /* Do we have a match? */\r
+ JBitSet innerBitSet = (JBitSet) rightReferencedTableMap.clone();\r
+ innerBitSet.or(leftReferencedTableMap);\r
+ if (innerBitSet.contains(curBitSet))\r
+ {\r
+ /* Add the matching predicate to the push list */\r
+ joinPredicates.addPredicate(predicate);\r
+\r
+ /* Remap all of the ColumnReferences to point to the\r
+ * source of the values.\r
+ * The tree is something like:\r
+ * PRN1\r
+ * |\r
+ * JN (this)\r
+ * / \\r
+ * PRN2 PRN3\r
+ * | |\r
+ * FBT1 FBT2\r
+ *\r
+ * The ColumnReferences start off pointing to the RCL off of\r
+ * PRN1. For optimization, we want them to point to the\r
+ * RCL off of PRN2 or PRN3. In order to do that, we remap them\r
+ * twice here. If optimization pushes them down to the\r
+ * base table, it will remap them again.\r
+ */\r
+ RemapCRsVisitor rcrv = new RemapCRsVisitor(true);\r
+ predicate.getAndNode().accept(rcrv);\r
+ predicate.getAndNode().accept(rcrv);\r
+\r
+ /* Remove the matching predicate from the outer list */\r
+ outerPredicateList.removeElementAt(index);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Flatten this JoinNode into the outer query block. The steps in\r
+ * flattening are:\r
+ * o Mark all ResultColumns as redundant, so that they are "skipped over"\r
+ * at generate().\r
+ * o Append the joinPredicates to the outer list.\r
+ * o Create a FromList from the tables being joined and return \r
+ * that list so that the caller will merge the 2 lists \r
+ *\r
+ * @param rcl The RCL from the outer query\r
+ * @param outerPList PredicateList to append wherePredicates to.\r
+ * @param sql The SubqueryList from the outer query\r
+ * @param gbl The group by list, if any\r
+ *\r
+ * @return FromList The fromList from the underlying SelectNode.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public FromList flatten(ResultColumnList rcl,\r
+ PredicateList outerPList,\r
+ SubqueryList sql,\r
+ GroupByList gbl)\r
+\r
+ throws StandardException\r
+ {\r
+ /* OuterJoinNodes should never get here.\r
+ * (They can be transformed, but never\r
+ * flattened directly.)\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (this instanceof HalfOuterJoinNode)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "JN.flatten() not expected to be called for " +\r
+ getClass().getName());\r
+ }\r
+ }\r
+\r
+ /* Build a new FromList composed of left and right children \r
+ * NOTE: We must call FL.addElement() instead of FL.addFromTable()\r
+ * since there is no exposed name. (And even if there was,\r
+ * we could care less about unique exposed name checking here.)\r
+ */\r
+ FromList fromList = (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager());\r
+ fromList.addElement((FromTable) leftResultSet);\r
+ fromList.addElement((FromTable) rightResultSet);\r
+\r
+ /* Mark our RCL as redundant */\r
+ resultColumns.setRedundant();\r
+\r
+ /* Remap all ColumnReferences from the outer query to this node.\r
+ * (We replace those ColumnReferences with clones of the matching\r
+ * expression in the left and right's RCL.\r
+ */\r
+ rcl.remapColumnReferencesToExpressions();\r
+ outerPList.remapColumnReferencesToExpressions();\r
+ if (gbl != null)\r
+ {\r
+ gbl.remapColumnReferencesToExpressions();\r
+ }\r
+\r
+ if (joinPredicates.size() > 0)\r
+ {\r
+ outerPList.destructiveAppend(joinPredicates);\r
+ }\r
+\r
+ if (subqueryList != null && subqueryList.size() > 0)\r
+ {\r
+ sql.destructiveAppend(subqueryList);\r
+ }\r
+\r
+ return fromList;\r
+ }\r
+\r
+ /**\r
+ * Currently we don't reordering any outer join w/ inner joins.\r
+ */\r
+ public boolean LOJ_reorderable(int numTables)\r
+ throws StandardException\r
+ {\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Transform any Outer Join into an Inner Join where applicable.\r
+ * (Based on the existence of a null intolerant\r
+ * predicate on the inner table.)\r
+ *\r
+ * @param predicateTree The predicate tree for the query block\r
+ *\r
+ * @return The new tree top (OuterJoin or InnerJoin).\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public FromTable transformOuterJoins(ValueNode predicateTree, int numTables)\r
+ throws StandardException\r
+ {\r
+ /* Can't flatten if no predicates in where clause. */\r
+ if (predicateTree == null)\r
+ {\r
+ return this;\r
+ }\r
+\r
+ /* See if left or right sides can be transformed */\r
+ leftResultSet = ((FromTable) leftResultSet).transformOuterJoins(predicateTree, numTables);\r
+ rightResultSet = ((FromTable) rightResultSet).transformOuterJoins(predicateTree, numTables);\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * For joins, the tree will be (nodes are left out if the clauses\r
+ * are empty):\r
+ *\r
+ * ProjectRestrictResultSet -- for the having and the select list\r
+ * SortResultSet -- for the group by list\r
+ * ProjectRestrictResultSet -- for the where and the select list (if no group or having)\r
+ * the result set for the fromList\r
+ *\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void generate(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ generateCore(acb, mb, INNERJOIN, null, null);\r
+ }\r
+\r
+ /**\r
+ * Generate the code for a qualified join node.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void generateCore(ActivationClassBuilder acb,\r
+ MethodBuilder mb,\r
+ int joinType) \r
+ throws StandardException\r
+ {\r
+ generateCore(acb, mb, joinType, joinClause, subqueryList);\r
+ }\r
+\r
+ /**\r
+ * Do the generation work for the join node hierarchy.\r
+ *\r
+ * @param acb The ActivationClassBuilder\r
+ * @param mb the method the code is to go into\r
+ * @param joinType The join type\r
+ * @param joinClause The join clause, if any\r
+ * @param subquerys The list of subqueries in the join clause, if any\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ protected void generateCore(ActivationClassBuilder acb,\r
+ MethodBuilder mb,\r
+ int joinType,\r
+ ValueNode joinClause,\r
+ SubqueryList subquerys)\r
+ throws StandardException\r
+ {\r
+ /* Put the predicates back into the tree */\r
+ if (joinPredicates != null)\r
+ {\r
+ joinClause = joinPredicates.restorePredicates();\r
+ joinPredicates = null;\r
+ }\r
+\r
+ /* Get the next ResultSet #, so that we can number this ResultSetNode, its\r
+ * ResultColumnList and ResultSet.\r
+ */\r
+ assignResultSetNumber();\r
+\r
+ /* Set the point of attachment in all subqueries attached\r
+ * to this node.\r
+ */\r
+ if (subquerys != null && subquerys.size() > 0)\r
+ {\r
+ subquerys.setPointOfAttachment(resultSetNumber);\r
+ }\r
+\r
+ // build up the tree.\r
+\r
+ /* Generate the JoinResultSet */\r
+ /* Nested loop and hash are the only join strategy currently supporteds. \r
+ * Right outer joins are transformed into left outer joins.\r
+ */\r
+ String joinResultSetString;\r
+\r
+ if (joinType == LEFTOUTERJOIN)\r
+ {\r
+ joinResultSetString = \r
+ ((Optimizable) rightResultSet).getTrulyTheBestAccessPath().\r
+ getJoinStrategy().halfOuterJoinResultSetMethodName();\r
+ }\r
+ else\r
+ {\r
+ joinResultSetString = \r
+ ((Optimizable) rightResultSet).getTrulyTheBestAccessPath().\r
+ getJoinStrategy().joinResultSetMethodName();\r
+ }\r
+\r
+ acb.pushGetResultSetFactoryExpression(mb);\r
+ int nargs = getJoinArguments(acb, mb, joinClause);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, joinResultSetString, ClassName.NoPutResultSet, nargs);\r
+ }\r
+\r
+ /**\r
+ * Get the arguments to the join result set.\r
+ *\r
+ * @param acb The ActivationClassBuilder for the class we're building.\r
+ * @param mb the method the generated code is going into\r
+ * @param joinClause The join clause, if any\r
+ *\r
+ * @return The array of arguments to the join result set\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private int getJoinArguments(ActivationClassBuilder acb,\r
+ MethodBuilder mb,\r
+ ValueNode joinClause)\r
+ throws StandardException\r
+ {\r
+ int numArgs = getNumJoinArguments();\r
+\r
+ leftResultSet.generate(acb, mb); // arg 1\r
+ mb.push(leftResultSet.resultColumns.size()); // arg 2\r
+ rightResultSet.generate(acb, mb); // arg 3\r
+ mb.push(rightResultSet.resultColumns.size()); // arg 4\r
+\r
+ // Get our final cost estimate based on child estimates.\r
+ costEstimate = getFinalCostEstimate();\r
+\r
+ // for the join clause, we generate an exprFun\r
+ // that evaluates the expression of the clause\r
+ // against the current row of the child's result.\r
+ // if the join clause is empty, we generate a function\r
+ // that just returns true. (Performance tradeoff: have\r
+ // this function for the empty join clause, or have\r
+ // all non-empty join clauses check for a null at runtime).\r
+\r
+ // generate the function and initializer:\r
+ // Note: Boolean lets us return nulls (boolean would not)\r
+ // private Boolean exprN()\r
+ // {\r
+ // return <<joinClause.generate(ps)>>;\r
+ // }\r
+ // static Method exprN = method pointer to exprN;\r
+\r
+ // if there is no join clause, we just pass a null Expression.\r
+ if (joinClause == null)\r
+ {\r
+ mb.pushNull(ClassName.GeneratedMethod); // arg 5\r
+ }\r
+ else\r
+ {\r
+ // this sets up the method and the static field.\r
+ // generates:\r
+ // Object userExprFun { }\r
+ MethodBuilder userExprFun = acb.newUserExprFun();\r
+\r
+ // join clause knows it is returning its value;\r
+\r
+ /* generates:\r
+ * return <joinClause.generate(acb)>;\r
+ * and adds it to userExprFun\r
+ */\r
+ joinClause.generate(acb, userExprFun);\r
+ userExprFun.methodReturn();\r
+\r
+ // we are done modifying userExprFun, complete it.\r
+ userExprFun.complete();\r
+\r
+ // join clause is used in the final result set as an access of the new static\r
+ // field holding a reference to this new method.\r
+ // generates:\r
+ // ActivationClass.userExprFun\r
+ // which is the static field that "points" to the userExprFun\r
+ // that evaluates the where clause.\r
+ acb.pushMethodReference(mb, userExprFun); // arg 5\r
+ }\r
+\r
+ mb.push(resultSetNumber); // arg 6\r
+\r
+ addOuterJoinArguments(acb, mb);\r
+\r
+ // Does right side return a single row\r
+ oneRowRightSide(acb, mb);\r
+\r
+ // estimated row count\r
+ mb.push(costEstimate.rowCount());\r
+\r
+ // estimated cost\r
+ mb.push(costEstimate.getEstimatedCost());\r
+\r
+ //User may have supplied optimizer overrides in the sql\r
+ //Pass them onto execute phase so it can be shown in \r
+ //run time statistics.\r
+ if (joinOrderStrategyProperties != null)\r
+ mb.push(PropertyUtil.sortProperties(joinOrderStrategyProperties));\r
+ else\r
+ mb.pushNull("java.lang.String");\r
+\r
+ return numArgs;\r
+\r
+ }\r
+\r
+ /**\r
+ * @see ResultSetNode#getFinalCostEstimate\r
+ *\r
+ * Get the final CostEstimate for this JoinNode.\r
+ *\r
+ * @return The final CostEstimate for this JoinNode, which is sum\r
+ * the costs for the inner and outer table. The number of rows,\r
+ * though, is that for the inner table only.\r
+ */\r
+ public CostEstimate getFinalCostEstimate()\r
+ throws StandardException\r
+ {\r
+ // If we already found it, just return it.\r
+ if (finalCostEstimate != null)\r
+ return finalCostEstimate;\r
+\r
+ CostEstimate leftCE = leftResultSet.getFinalCostEstimate();\r
+ CostEstimate rightCE = rightResultSet.getFinalCostEstimate();\r
+\r
+ finalCostEstimate = getNewCostEstimate();\r
+ finalCostEstimate.setCost(\r
+ leftCE.getEstimatedCost() + rightCE.getEstimatedCost(),\r
+ rightCE.rowCount(),\r
+ rightCE.rowCount());\r
+\r
+ return finalCostEstimate;\r
+ }\r
+\r
+ protected void oneRowRightSide(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ mb.push(rightResultSet.isOneRowResultSet());\r
+ mb.push(rightResultSet.isNotExists()); //join is for NOT EXISTS\r
+ }\r
+\r
+ /**\r
+ * Return the number of arguments to the join result set. This will\r
+ * be overridden for other types of joins (for example, outer joins).\r
+ */\r
+ protected int getNumJoinArguments()\r
+ {\r
+ return 11;\r
+ }\r
+\r
+ /**\r
+ * Generate and add any arguments specifict to outer joins.\r
+ * (Expected to be overriden, where appropriate, in subclasses.)\r
+ *\r
+ * @param acb The ActivationClassBuilder\r
+ * @param mb the method the generated code is to go into\r
+ *\r
+ * return The number of args added\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ protected int addOuterJoinArguments(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ return 0;\r
+ }\r
+\r
+ /** \r
+ * Convert the joinType to a string.\r
+ *\r
+ * @param joinType The joinType as an int.\r
+ *\r
+ * @return String The joinType as a String.\r
+ */\r
+ public static String joinTypeToString(int joinType)\r
+ {\r
+ switch(joinType)\r
+ {\r
+ case INNERJOIN:\r
+ return "INNER JOIN";\r
+\r
+ case CROSSJOIN:\r
+ return "CROSS JOIN";\r
+\r
+ case LEFTOUTERJOIN:\r
+ return "LEFT OUTER JOIN";\r
+\r
+ case RIGHTOUTERJOIN:\r
+ return "RIGHT OUTER JOIN";\r
+\r
+ case FULLOUTERJOIN:\r
+ return "FULL OUTER JOIN";\r
+\r
+ case UNIONJOIN:\r
+ return "UNION JOIN";\r
+\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(false, "Unexpected joinType");\r
+ }\r
+ return null;\r
+ }\r
+ }\r
+\r
+ protected PredicateList getLeftPredicateList() throws StandardException\r
+ {\r
+ if (leftPredicateList == null)\r
+ leftPredicateList = (PredicateList) getNodeFactory().getNode(\r
+ C_NodeTypes.PREDICATE_LIST,\r
+ getContextManager());\r
+\r
+ return leftPredicateList;\r
+ }\r
+\r
+ protected PredicateList getRightPredicateList() throws StandardException\r
+ {\r
+ if (rightPredicateList == null)\r
+ rightPredicateList = (PredicateList) getNodeFactory().getNode(\r
+ C_NodeTypes.PREDICATE_LIST,\r
+ getContextManager());\r
+\r
+ return rightPredicateList;\r
+ }\r
+\r
+ /**\r
+ * Get the lock mode for the target of an update statement\r
+ * (a delete or update). The update mode will always be row for\r
+ * CurrentOfNodes. It will be table if there is no where clause.\r
+ *\r
+ * @return The lock mode\r
+ */\r
+ public int updateTargetLockMode()\r
+ {\r
+ /* Always use row locking if we have a join node.\r
+ * We can only have a join node if there is a subquery that\r
+ * got flattened, hence there is a restriction.\r
+ */\r
+ return TransactionController.MODE_RECORD;\r
+ }\r
+\r
+ /**\r
+ * Mark this node and its children as not being a flattenable join.\r
+ */\r
+ void notFlattenableJoin()\r
+ {\r
+ flattenableJoin = false;\r
+ leftResultSet.notFlattenableJoin();\r
+ rightResultSet.notFlattenableJoin();\r
+ }\r
+\r
+ /**\r
+ * Is this FromTable a JoinNode which can be flattened into \r
+ * the parents FromList.\r
+ *\r
+ * @return boolean Whether or not this FromTable can be flattened.\r
+ */\r
+ public boolean isFlattenableJoinNode()\r
+ {\r
+ return flattenableJoin;\r
+ }\r
+\r
+ /**\r
+ * Return whether or not the underlying ResultSet tree\r
+ * is ordered on the specified columns.\r
+ * RESOLVE - This method currently only considers the outermost table \r
+ * of the query block.\r
+ *\r
+ * @param crs The specified ColumnReference[]\r
+ * @param permuteOrdering Whether or not the order of the CRs in the array can be permuted\r
+ * @param fbtVector Vector that is to be filled with the FromBaseTable \r
+ *\r
+ * @return Whether the underlying ResultSet tree\r
+ * is ordered on the specified column.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ boolean isOrderedOn(ColumnReference[] crs, boolean permuteOrdering, Vector fbtVector)\r
+ throws StandardException\r
+ {\r
+ /* RESOLVE - easiest thing for now is to only consider the leftmost child */\r
+ return leftResultSet.isOrderedOn(crs, permuteOrdering, fbtVector);\r
+ }\r
+\r
+ /**\r
+ * Prints the sub-nodes of this object. See QueryTreeNode.java for\r
+ * how tree printing is supposed to work.\r
+ *\r
+ * @param depth The depth of this node in the tree\r
+ */\r
+\r
+ public void printSubNodes(int depth)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ super.printSubNodes(depth);\r
+\r
+ if (subqueryList != null)\r
+ {\r
+ printLabel(depth, "subqueryList: ");\r
+ subqueryList.treePrint(depth + 1);\r
+ }\r
+\r
+ if (joinClause != null)\r
+ {\r
+ printLabel(depth, "joinClause: ");\r
+ joinClause.treePrint(depth + 1);\r
+ }\r
+\r
+ if (joinPredicates.size() != 0)\r
+ {\r
+ printLabel(depth, "joinPredicates: ");\r
+ joinPredicates.treePrint(depth + 1);\r
+ }\r
+\r
+ if (usingClause != null)\r
+ {\r
+ printLabel(depth, "usingClause: ");\r
+ usingClause.treePrint(depth + 1);\r
+ }\r
+ }\r
+ }\r
+\r
+ void setSubqueryList(SubqueryList subqueryList)\r
+ {\r
+ this.subqueryList = subqueryList;\r
+ }\r
+\r
+ void setAggregateVector(Vector aggregateVector)\r
+ {\r
+ this.aggregateVector = aggregateVector;\r
+ }\r
+\r
+ /**\r
+ * Return the logical left result set for this qualified\r
+ * join node.\r
+ * (For RIGHT OUTER JOIN, the left is the right\r
+ * and the right is the left and the JOIN is the NIOJ).\r
+ */\r
+ ResultSetNode getLogicalLeftResultSet()\r
+ {\r
+ return leftResultSet;\r
+ }\r
+\r
+ /**\r
+ * Return the logical right result set for this qualified\r
+ * join node.\r
+ * (For RIGHT OUTER JOIN, the left is the right\r
+ * and the right is the left and the JOIN is the NIOJ).\r
+ */\r
+ ResultSetNode getLogicalRightResultSet()\r
+ {\r
+ return rightResultSet;\r
+ }\r
+\r
+ /**\r
+ * Accept a visitor, and call v.visit()\r
+ * on child nodes as necessary. \r
+ * \r
+ * @param v the visitor\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public Visitable accept(Visitor v) \r
+ throws StandardException\r
+ {\r
+ if (v.skipChildren(this))\r
+ {\r
+ return v.visit(this);\r
+ }\r
+\r
+ Visitable returnNode = super.accept(v);\r
+\r
+ if (resultColumns != null && !v.stopTraversal())\r
+ {\r
+ resultColumns = (ResultColumnList)resultColumns.accept(v);\r
+ }\r
+\r
+ if (joinClause != null && !v.stopTraversal())\r
+ {\r
+ joinClause = (ValueNode)joinClause.accept(v);\r
+ }\r
+\r
+ if (usingClause != null && !v.stopTraversal())\r
+ {\r
+ usingClause = (ResultColumnList)usingClause.accept(v);\r
+ }\r
+\r
+ return returnNode;\r
+ }\r
+\r
+ // This method returns the table references in Join node, and this may be\r
+ // needed for LOJ reordering. For example, we may have the following query:\r
+ // (T JOIN S) LOJ (X LOJ Y) \r
+ // The top most LOJ may be a join betw T and X and thus we can reorder the\r
+ // LOJs. However, as of 10/2002, we don't reorder LOJ mixed with join.\r
+ public JBitSet LOJgetReferencedTables(int numTables)\r
+ throws StandardException\r
+ {\r
+ JBitSet map = new JBitSet(numTables);\r
+\r
+ map = (JBitSet) leftResultSet.LOJgetReferencedTables(numTables);\r
+ if (map == null) return null;\r
+ else map.or((JBitSet) rightResultSet.LOJgetReferencedTables(numTables));\r
+\r
+ return map;\r
+ }\r
+ \r
+}\r