--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.IndexRowToBaseRowResultSet\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.services.monitor.Monitor;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.services.stream.HeaderPrintWriter;\r
+import org.apache.derby.iapi.services.stream.InfoStreams;\r
+\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.impl.sql.GenericPreparedStatement;\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+import org.apache.derby.iapi.sql.conn.StatementContext;\r
+\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\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.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+\r
+import org.apache.derby.catalog.types.ReferencedColumnsDescriptorImpl;\r
+\r
+/**\r
+ * Takes a result set with a RowLocation as the last column, and uses the\r
+ * RowLocation to get and return a row from the given base conglomerate.\r
+ * Normally, the input result set will be a TableScanResultSet scanning an\r
+ * index conglomerate.\r
+ *\r
+ */\r
+class IndexRowToBaseRowResultSet extends NoPutResultSetImpl\r
+ implements CursorResultSet {\r
+\r
+ // set in constructor and not altered during\r
+ // life of object.\r
+ private long conglomId;\r
+ public NoPutResultSet source;\r
+ private GeneratedMethod resultRowAllocator;\r
+ private GeneratedMethod restriction;\r
+ private long baseConglomId;\r
+ public FormatableBitSet accessedHeapCols;\r
+ //caching accessed columns (heap+index) beetle 3865\r
+ private FormatableBitSet accessedAllCols;\r
+ public String indexName;\r
+ private int[] indexCols;\r
+ private DynamicCompiledOpenConglomInfo dcoci;\r
+ private StaticCompiledOpenConglomInfo scoci;\r
+\r
+ // set in open() and not changed after that\r
+ private ConglomerateController baseCC;\r
+ private boolean closeBaseCCHere;\r
+ private ExecRow resultRow;\r
+ private boolean forUpdate;\r
+ private DataValueDescriptor[] rowArray;\r
+\r
+ // changed a whole bunch\r
+ RowLocation baseRowLocation;\r
+\r
+ /* Remember whether or not we have copied any\r
+ * columns from the source row to our row yet.\r
+ */\r
+ boolean copiedFromSource;\r
+\r
+ /* Run time statistics variables */\r
+ public long restrictionTime;\r
+\r
+ protected boolean currentRowPrescanned;\r
+ private boolean sourceIsForUpdateIndexScan;\r
+\r
+ //\r
+ // class interface\r
+ //\r
+ IndexRowToBaseRowResultSet(\r
+ long conglomId,\r
+ int scociItem,\r
+ Activation a,\r
+ NoPutResultSet source,\r
+ GeneratedMethod resultRowAllocator,\r
+ int resultSetNumber,\r
+ String indexName,\r
+ int heapColRefItem,\r
+ int allColRefItem,\r
+ int heapOnlyColRefItem,\r
+ int indexColMapItem,\r
+ GeneratedMethod restriction,\r
+ boolean forUpdate,\r
+ double optimizerEstimatedRowCount,\r
+ double optimizerEstimatedCost) \r
+ throws StandardException\r
+ {\r
+ super(a, resultSetNumber, optimizerEstimatedRowCount, optimizerEstimatedCost);\r
+ final GenericPreparedStatement gp =\r
+ (GenericPreparedStatement)a.getPreparedStatement();\r
+ final Object[] saved = gp.getSavedObjects();\r
+\r
+ scoci = (StaticCompiledOpenConglomInfo)saved[scociItem];\r
+ TransactionController tc = activation.getTransactionController();\r
+ dcoci = tc.getDynamicCompiledConglomInfo(conglomId);\r
+ this.source = source;\r
+ this.resultRowAllocator = resultRowAllocator;\r
+ this.indexName = indexName;\r
+ this.forUpdate = forUpdate;\r
+ this.restriction = restriction;\r
+\r
+ /* RESOLVE - once we push Qualifiers into the store we\r
+ * need to clear their Orderable cache on each open/reopen.\r
+ */\r
+\r
+ // retrieve the valid column list from\r
+ // the saved objects, if it exists\r
+ if (heapColRefItem != -1) {\r
+ this.accessedHeapCols = (FormatableBitSet)saved[heapColRefItem];\r
+ }\r
+ if (allColRefItem != -1) {\r
+ this.accessedAllCols = (FormatableBitSet)saved[allColRefItem];\r
+ }\r
+\r
+ // retrieve the array of columns coming from the index\r
+ indexCols = \r
+ ((ReferencedColumnsDescriptorImpl)\r
+ saved[indexColMapItem]).getReferencedColumnPositions();\r
+\r
+ /* Get the result row template */\r
+ resultRow = (ExecRow) resultRowAllocator.invoke(activation);\r
+\r
+ // Note that getCompactRow will assign its return value to the\r
+ // variable compactRow which can be accessed through\r
+ // inheritance. Hence we need not collect the return value\r
+ // of the method.\r
+ getCompactRow(resultRow, accessedAllCols, \r
+ (FormatableBitSet)null, false);\r
+\r
+ /* If there's no partial row bit map, then we want the entire\r
+ * row, otherwise we need to diddle with the row array so that\r
+ * we only get the columns coming from the heap on the fetch.\r
+ */\r
+ if (accessedHeapCols == null) {\r
+ rowArray = resultRow.getRowArray();\r
+ }\r
+ else {\r
+ // Figure out how many columns are coming from the heap\r
+\r
+ final DataValueDescriptor[] resultRowArray =\r
+ resultRow.getRowArray();\r
+ final FormatableBitSet heapOnly =\r
+ (FormatableBitSet)saved[heapOnlyColRefItem];\r
+ final int heapOnlyLen = heapOnly.getLength();\r
+\r
+ // Need a separate DataValueDescriptor array in this case\r
+ rowArray =\r
+ new DataValueDescriptor[heapOnlyLen];\r
+ final int minLen = Math.min(resultRowArray.length, heapOnlyLen);\r
+\r
+ // Make a copy of the relevant part of rowArray\r
+ for (int i = 0; i < minLen; ++i) {\r
+ if (resultRowArray[i] != null && heapOnly.isSet(i)) {\r
+ rowArray[i] = resultRowArray[i];\r
+ }\r
+ }\r
+ }\r
+ constructorTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ //\r
+ // ResultSet interface (leftover from NoPutResultSet)\r
+ //\r
+\r
+ /**\r
+ * open this ResultSet.\r
+ *\r
+ * @exception StandardException thrown if cursor finished.\r
+ */\r
+ public void openCore() throws StandardException \r
+ {\r
+ boolean lockingRequired = false;\r
+ TransactionController tc;\r
+\r
+ // REVISIT: through the direct DB API, this needs to be an\r
+ // error, not an ASSERT; users can open twice. Only through JDBC\r
+ // is access to open controlled and ensured valid.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT( ! isOpen,\r
+ "IndexRowToBaseRowResultSet already open");\r
+ }\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+\r
+ source.openCore();\r
+ if ((source instanceof TableScanResultSet) && \r
+ ((TableScanResultSet) source).indexCols != null)\r
+ sourceIsForUpdateIndexScan = true;\r
+\r
+ /* Get a ConglomerateController for the base conglomerate \r
+ * NOTE: We only need to acquire locks on the data pages when\r
+ * going through the index when we are at READ COMMITTED and\r
+ * the source is a BulkTableScan or HashScan. (The underlying\r
+ * row will not be guaranteed to be locked.)\r
+ */\r
+ if (source.requiresRelocking())\r
+ {\r
+ lockingRequired = true;\r
+ }\r
+\r
+ tc = activation.getTransactionController();\r
+\r
+ int openMode;\r
+ int isolationLevel;\r
+ \r
+ if (forUpdate)\r
+ {\r
+ openMode = TransactionController.OPENMODE_FORUPDATE;\r
+ }\r
+ else\r
+ {\r
+ openMode = 0;\r
+ }\r
+ isolationLevel = source.getScanIsolationLevel();\r
+\r
+ if (!lockingRequired)\r
+ {\r
+ // flag indicates that lock has already been acquired by access to\r
+ // the secondary index, and need not be gotten again in the base\r
+ // table.\r
+ openMode |= TransactionController.OPENMODE_SECONDARY_LOCKED;\r
+ }\r
+ \r
+ /* Try to get the ConglomerateController from the activation\r
+ * first, for the case that we are part of an update or delete.\r
+ * If so, then the RowChangerImpl did the correct locking.\r
+ * If not there, then we go off and open it ourself.\r
+ */\r
+ if (forUpdate)\r
+ {\r
+ baseCC = activation.getHeapConglomerateController();\r
+ }\r
+\r
+ if (baseCC == null)\r
+ {\r
+ baseCC = \r
+ tc.openCompiledConglomerate(\r
+ activation.getResultSetHoldability(),\r
+ openMode,\r
+ // consistent with FromBaseTable's updateTargetLockMode\r
+ TransactionController.MODE_RECORD,\r
+ isolationLevel,\r
+ scoci,\r
+ dcoci);\r
+ closeBaseCCHere = true;\r
+ }\r
+\r
+ isOpen = true;\r
+ numOpens++;\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * reopen this ResultSet.\r
+ *\r
+ * @exception StandardException thrown if cursor finished.\r
+ */\r
+ public void reopenCore() throws StandardException {\r
+ TransactionController tc;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(isOpen,\r
+ "IndexRowToBaseRowResultSet already open");\r
+ }\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+\r
+ source.reopenCore();\r
+\r
+ numOpens++;\r
+ openTime += getElapsedMillis(beginTime);\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 and projection parameters\r
+ * are evaluated for each row.\r
+ *\r
+ * @exception StandardException thrown on failure.\r
+ * @exception StandardException ResultSetNotOpen thrown if not yet open.\r
+ *\r
+ * @return the next row in the result\r
+ */\r
+ public ExecRow getNextRowCore() throws StandardException {\r
+\r
+ ExecRow sourceRow = null;\r
+ ExecRow retval = null;\r
+ boolean restrict = false;\r
+ DataValueDescriptor restrictBoolean;\r
+ long beginRT = 0;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+ if ( ! isOpen ) {\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");\r
+ }\r
+\r
+ /* beetle 3865, updateable cursor using index. When in-memory hash table was full, we\r
+ * read forward and saved future row id's in a virtual-memory-like temp table. So if\r
+ * we have rid's saved, and we are here, it must be non-covering index. Intercept it\r
+ * here, so that we don't have to go to underlying index scan. We get both heap cols\r
+ * and index cols together here for better performance.\r
+ */\r
+ if (sourceIsForUpdateIndexScan && ((TableScanResultSet) source).futureForUpdateRows != null)\r
+ {\r
+ currentRowPrescanned = false;\r
+ TableScanResultSet src = (TableScanResultSet) source;\r
+\r
+ if (src.futureRowResultSet == null)\r
+ {\r
+ src.futureRowResultSet = (TemporaryRowHolderResultSet) src.futureForUpdateRows.getResultSet();\r
+ src.futureRowResultSet.openCore();\r
+ }\r
+\r
+ ExecRow ridRow = src.futureRowResultSet.getNextRowCore();\r
+\r
+ currentRow = null;\r
+\r
+ if (ridRow != null)\r
+ {\r
+ /* To maximize performance, we only use virtual memory style heap, no\r
+ * position index is ever created. And we save and retrieve rows from the\r
+ * in-memory part of the heap as much as possible. We can also insert after\r
+ * we start retrieving, the assumption is that we delete the current row right\r
+ * after we retrieve it.\r
+ */\r
+ src.futureRowResultSet.deleteCurrentRow();\r
+ baseRowLocation = (RowLocation) ridRow.getColumn(1);\r
+ baseCC.fetch(\r
+ baseRowLocation, compactRow.getRowArray(), accessedAllCols);\r
+\r
+ currentRow = compactRow;\r
+ currentRowPrescanned = true;\r
+ }\r
+ else if (src.sourceDrained)\r
+ currentRowPrescanned = true;\r
+\r
+ if (currentRowPrescanned)\r
+ {\r
+ setCurrentRow(currentRow);\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+ return currentRow;\r
+ }\r
+ }\r
+\r
+ /* Loop until we get a row from the base page that qualifies or\r
+ * there's no more rows from the index that qualify. (If the RID\r
+ * returned by the index does not qualify, then we have to go back\r
+ * to the index to see if there is another RID to consider.)\r
+ */\r
+ do \r
+ {\r
+ sourceRow = source.getNextRowCore();\r
+\r
+ if (sourceRow != null) {\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(\r
+ sourceRow.getColumn(sourceRow.nColumns())\r
+ instanceof RowLocation,\r
+ "Last column of source row is not a RowLocation"\r
+ );\r
+ }\r
+\r
+ baseRowLocation = (RowLocation)\r
+ sourceRow.getColumn(sourceRow.nColumns());\r
+\r
+ // Fetch the columns coming from the heap\r
+ boolean row_exists = \r
+ baseCC.fetch(\r
+ baseRowLocation, rowArray, accessedHeapCols);\r
+\r
+ if (row_exists)\r
+ {\r
+ /* We only need to copy columns from the index row \r
+ * to our result row once as we will be reusing the\r
+ * wrappers in that case.\r
+ * NOTE: When the underlying ResultSet got an \r
+ * instantaneous lock (BulkTableScan or HashScan)\r
+ * then we will be getting all of the columns anew\r
+ * from the index (indexCols == null).\r
+ */\r
+ if (! copiedFromSource)\r
+ {\r
+ copiedFromSource = true;\r
+\r
+ // Copy the columns coming from the index into resultRow\r
+ for (int index = 0; index < indexCols.length; index++)\r
+ {\r
+ if (indexCols[index] != -1)\r
+ {\r
+ compactRow.setColumn(\r
+ index + 1,\r
+ sourceRow.getColumn(indexCols[index] + 1));\r
+ }\r
+ }\r
+ }\r
+\r
+ setCurrentRow(compactRow);\r
+\r
+ restrictBoolean = (DataValueDescriptor) \r
+ ((restriction == null) ? \r
+ null : restriction.invoke(activation));\r
+\r
+ restrictionTime += getElapsedMillis(beginRT);\r
+\r
+ // if the result is null, we make it false --\r
+ // so the row won't be returned.\r
+ restrict = (restrictBoolean == null) ||\r
+ ((! restrictBoolean.isNull()) &&\r
+ restrictBoolean.getBoolean());\r
+ }\r
+\r
+ if (! restrict || ! row_exists)\r
+ {\r
+ rowsFiltered++;\r
+ clearCurrentRow();\r
+ baseRowLocation = null;\r
+\r
+ }\r
+ else\r
+ {\r
+ currentRow = compactRow;\r
+ }\r
+\r
+ /* Update the run time statistics */\r
+ rowsSeen++;\r
+\r
+ retval = currentRow;\r
+ } else {\r
+ clearCurrentRow();\r
+ baseRowLocation = null;\r
+\r
+ retval = null;\r
+ }\r
+ } \r
+ while ( (sourceRow != null) && (! restrict ) );\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+ return retval;\r
+ }\r
+\r
+ /**\r
+ * If the result set has been opened,\r
+ * close the open scan.\r
+ *\r
+ * @exception StandardException thrown on error\r
+ */\r
+ public void close() throws StandardException\r
+ {\r
+ beginTime = getCurrentTimeMillis();\r
+ if ( isOpen ) {\r
+\r
+ // we don't want to keep around a pointer to the\r
+ // row ... so it can be thrown away.\r
+ // REVISIT: does this need to be in a finally\r
+ // block, to ensure that it is executed?\r
+ clearCurrentRow();\r
+\r
+ if (closeBaseCCHere)\r
+ {\r
+ // This check should only be needed in the error case where\r
+ // we may call this close() routine as part of transaction\r
+ // backout cleanup if any of the following routines fail.\r
+ // If one of the subsequent statements gets an error, we\r
+ // will try to close this result set as part of transaction\r
+ // cleanup, and without this check we get a null pointer\r
+ // exception because we have null'd out baseCC.\r
+ \r
+ if (baseCC != null)\r
+ baseCC.close();\r
+ }\r
+\r
+ /* Make sure to null out baseCC since\r
+ * we check for null baseCC after looking\r
+ * in the StatementContext.\r
+ */\r
+ baseCC = null;\r
+ source.close();\r
+\r
+ super.close();\r
+ }\r
+ else if (SanityManager.DEBUG) {\r
+ SanityManager.DEBUG("CloseRepeatInfo","Close of IndexRowToBaseRowResultSet repeated");\r
+ }\r
+\r
+ closeTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * Return the total amount of time spent in this ResultSet\r
+ *\r
+ * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet\r
+ * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.\r
+ *\r
+ * @return long The total amount of time spent (in milliseconds).\r
+ */\r
+ public long getTimeSpent(int type)\r
+ {\r
+ long totTime = constructorTime + openTime + nextTime + closeTime;\r
+\r
+ if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY)\r
+ {\r
+ return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);\r
+ }\r
+ else\r
+ {\r
+ return totTime;\r
+ }\r
+ }\r
+\r
+ //\r
+ // CursorResultSet interface\r
+ //\r
+ /**\r
+ * Return the RowLocation of the base row.\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the row location of the current cursor row.\r
+ * @exception StandardException thrown on failure.\r
+ */\r
+ public RowLocation getRowLocation() throws StandardException {\r
+ return baseRowLocation;\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#positionScanAtRowLocation\r
+ * \r
+ * Also remembers row location so that subsequent invocations of\r
+ * getCurrentRow will not read the index row to look up the row\r
+ * location base row, but reuse the saved row location.\r
+ */\r
+ public void positionScanAtRowLocation(RowLocation rl) \r
+ throws StandardException \r
+ {\r
+ baseRowLocation = rl;\r
+ source.positionScanAtRowLocation(rl);\r
+ }\r
+\r
+ /** * Gets last row returned.\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the last row returned.\r
+ * @exception StandardException thrown on failure.\r
+ */\r
+ /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),\r
+ * once there is such a method. (currentRow is redundant)\r
+ */\r
+ public ExecRow getCurrentRow() throws StandardException {\r
+ ExecRow sourceRow = null;\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(isOpen,\r
+ "IndexRowToBaseRowResultSet is expected to be open");\r
+ }\r
+\r
+ if (currentRowPrescanned)\r
+ return currentRow;\r
+\r
+ /* Nothing to do if we're not currently on a row */\r
+ if (currentRow == null)\r
+ {\r
+ return null;\r
+ }\r
+\r
+ // We do not need to read the row from the index first, since we already \r
+ // have the rowLocation of the current row and can read it directly from \r
+ // the heap.\r
+ sourceRow = activation.getExecutionFactory().\r
+ getValueRow(indexCols.length);\r
+ sourceRow.setRowArray(rowArray);\r
+ // Fetch the columns coming from the heap\r
+ boolean row_exists = \r
+ baseCC.fetch(\r
+ baseRowLocation, rowArray, (FormatableBitSet) null);\r
+ if (row_exists) {\r
+ setCurrentRow(sourceRow);\r
+ } else {\r
+ clearCurrentRow();\r
+ }\r
+ return currentRow;\r
+ }\r
+\r
+ /**\r
+ * Is this ResultSet or it's source result set for update.\r
+ * beetle 3865: updateable cursor using index scan. We didn't need this function\r
+ * before because we couldn't use index for update cursor.\r
+ * \r
+ * @return Whether or not the result set is for update.\r
+ */\r
+ public boolean isForUpdate()\r
+ {\r
+ return source.isForUpdate();\r
+ }\r
+\r
+}\r