--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.NestedLoopLeftOuterJoinResultSet\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.execute;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+\r
+\r
+/**\r
+ * Takes 2 NoPutResultSets and a join filter and returns\r
+ * the join's rows satisfying the filter as a result set\r
+ * plus the rows from the left joined with a null row from\r
+ * the right when there is no matching row in the right\r
+ * result set.\r
+ */\r
+class NestedLoopLeftOuterJoinResultSet extends NestedLoopJoinResultSet\r
+{\r
+ protected GeneratedMethod emptyRowFun;\r
+ /* Was this originally a right outer join? */\r
+ private boolean wasRightOuterJoin;\r
+\r
+ /* Have we found a matching row from the right yet? */\r
+ private boolean matchRight = false;\r
+ private boolean returnedEmptyRight = false;\r
+ private ExecRow rightEmptyRow = null;\r
+\r
+ public int emptyRightRowsReturned = 0;\r
+\r
+ //\r
+ // ResultSet interface (leftover from NoPutResultSet)\r
+ //\r
+\r
+ /**\r
+ * Return the requested values computed\r
+ * from the next row (if any) for which\r
+ * the restriction evaluates to true.\r
+ * <p>\r
+ * restriction parameters\r
+ * are evaluated for each row.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ * @exception StandardException ResultSetNotOpen thrown if closed\r
+ * @return the next row in the join result\r
+ */\r
+ public ExecRow getNextRowCore() throws StandardException\r
+ {\r
+ ExecRow result = null;\r
+ boolean haveRow = false;\r
+ boolean restrict = false;\r
+ DataValueDescriptor restrictBoolean;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+ if (! isOpen)\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");\r
+\r
+ /* Close right and advance left if we found no match\r
+ * on right on last next().\r
+ */\r
+ if (returnedEmptyRight)\r
+ {\r
+ /* Current scan on right is exhausted. Need to close old scan \r
+ * and open new scan with new "parameters". openRight will\r
+ * reopen the scan.\r
+ */\r
+ leftRow = leftResultSet.getNextRowCore();\r
+ if (leftRow == null)\r
+ {\r
+ closeRight();\r
+ }\r
+ else\r
+ {\r
+ rowsSeenLeft++;\r
+ openRight();\r
+ }\r
+ returnedEmptyRight = false;\r
+ }\r
+\r
+ while (leftRow != null && !haveRow)\r
+ {\r
+ rightRow = rightResultSet.getNextRowCore();\r
+\r
+ if (rightRow == null)\r
+ {\r
+ /* If we haven't found a match on the right, then\r
+ * we join the left with a row of nulls from the\r
+ * right.\r
+ */\r
+ if (! matchRight)\r
+ {\r
+ haveRow = true;\r
+ returnedEmptyRight = true;\r
+ if (rightEmptyRow == null)\r
+ {\r
+ rightEmptyRow = (ExecRow) emptyRowFun.invoke(activation);\r
+ }\r
+\r
+ getMergedRow(leftRow, rightEmptyRow);\r
+ emptyRightRowsReturned++;\r
+ continue;\r
+ }\r
+\r
+ /* Current scan on right is exhausted. Need to close old scan \r
+ * and open new scan with new "parameters". openRight()\r
+ * will reopen the scan.\r
+ */\r
+ matchRight = false;\r
+ leftRow = leftResultSet.getNextRowCore();\r
+ if (leftRow == null)\r
+ {\r
+ closeRight();\r
+ }\r
+ else\r
+ {\r
+ rowsSeenLeft++;\r
+ openRight();\r
+ }\r
+ }\r
+ else\r
+ {\r
+ rowsSeenRight++;\r
+\r
+ if (restriction != null)\r
+ {\r
+ restrictBoolean =\r
+ (DataValueDescriptor) restriction.invoke(activation);\r
+\r
+ // if the result is null, we make it false --\r
+ // so the row won't be returned.\r
+ restrict = (! restrictBoolean.isNull()) &&\r
+ restrictBoolean.getBoolean();\r
+\r
+ if (! restrict)\r
+ {\r
+ /* Update the run time statistics */\r
+ rowsFiltered++;\r
+ continue;\r
+ }\r
+ }\r
+\r
+ matchRight = true;\r
+\r
+ getMergedRow(leftRow, rightRow);\r
+ haveRow = true;\r
+ }\r
+ }\r
+\r
+ /* Do we have a row to return? */\r
+ if (haveRow)\r
+ {\r
+ result = mergedRow;\r
+ setCurrentRow(mergedRow);\r
+ rowsReturned++;\r
+ }\r
+ else\r
+ {\r
+ clearCurrentRow();\r
+ }\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+ return result;\r
+ }\r
+\r
+ protected void getMergedRow(ExecRow leftRow, ExecRow rightRow) \r
+ throws StandardException\r
+ {\r
+ int colInCtr;\r
+ int colOutCtr;\r
+ int leftNumCols;\r
+ int rightNumCols;\r
+\r
+ /* Reverse left and right for return of row if this was originally\r
+ * a right outer join. (Result columns ordered according to\r
+ * original query.)\r
+ */\r
+ if (wasRightOuterJoin)\r
+ {\r
+ ExecRow tmp;\r
+\r
+ tmp = leftRow;\r
+ leftRow = rightRow;\r
+ rightRow = tmp;\r
+ leftNumCols = this.rightNumCols;\r
+ rightNumCols = this.leftNumCols;\r
+ }\r
+ else\r
+ {\r
+ leftNumCols = this.leftNumCols;\r
+ rightNumCols = this.rightNumCols;\r
+ }\r
+\r
+ /* Merge the rows, doing just in time allocation for mergedRow.\r
+ * (By convention, left Row is to left of right Row.)\r
+ */\r
+ if (mergedRow == null)\r
+ {\r
+ mergedRow = getExecutionFactory().getValueRow(leftNumCols + rightNumCols);\r
+ }\r
+\r
+ for (colInCtr = 1, colOutCtr = 1; colInCtr <= leftNumCols;\r
+ colInCtr++, colOutCtr++)\r
+ {\r
+ mergedRow.setColumn(colOutCtr, \r
+ leftRow.getColumn(colInCtr));\r
+ }\r
+ for (colInCtr = 1; colInCtr <= rightNumCols; \r
+ colInCtr++, colOutCtr++)\r
+ {\r
+ mergedRow.setColumn(colOutCtr, \r
+ rightRow.getColumn(colInCtr));\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Clear any private state that changes during scans.\r
+ * This includes things like the last row seen, etc.\r
+ * THis does not include immutable things that are\r
+ * typically set up in the constructor.\r
+ * <p>\r
+ * This method is called on open()/close() and reopen()\r
+ * <p>\r
+ * WARNING: this should be implemented in every sub\r
+ * class and it should always call super.clearScanState().\r
+ */\r
+ void clearScanState()\r
+ {\r
+ matchRight = false;\r
+ returnedEmptyRight = false;\r
+ rightEmptyRow = null;\r
+ emptyRightRowsReturned = 0;\r
+ super.clearScanState();\r
+ }\r
+\r
+\r
+ /*\r
+ * class interface\r
+ *\r
+ */\r
+ NestedLoopLeftOuterJoinResultSet(\r
+ NoPutResultSet leftResultSet,\r
+ int leftNumCols,\r
+ NoPutResultSet rightResultSet,\r
+ int rightNumCols,\r
+ Activation activation,\r
+ GeneratedMethod restriction,\r
+ int resultSetNumber,\r
+ GeneratedMethod emptyRowFun,\r
+ boolean wasRightOuterJoin,\r
+ boolean oneRowRightSide,\r
+ boolean notExistsRightSide,\r
+ double optimizerEstimatedRowCount,\r
+ double optimizerEstimatedCost,\r
+ String userSuppliedOptimizerOverrides)\r
+ {\r
+ super(leftResultSet, leftNumCols, rightResultSet, rightNumCols,\r
+ activation, restriction, resultSetNumber, \r
+ oneRowRightSide, notExistsRightSide,\r
+ optimizerEstimatedRowCount, optimizerEstimatedCost, \r
+ userSuppliedOptimizerOverrides);\r
+ this.emptyRowFun = emptyRowFun;\r
+ this.wasRightOuterJoin = wasRightOuterJoin;\r
+ }\r
+}\r