--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.TableScanResultSet\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 java.util.Hashtable;\r
+import java.util.Properties;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ExecIndexRow;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+import org.apache.derby.iapi.sql.execute.TemporaryRowHolder;\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.Qualifier;\r
+import org.apache.derby.iapi.store.access.ScanController;\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+/**\r
+ * Takes a table and a table filter and returns\r
+ * the table's rows satisfying the filter as a result set.\r
+ *\r
+ * There are several things we could do during object\r
+ * construction that are done in the open & next calls, to\r
+ * improve performance.\r
+ *\r
+ */\r
+class TableScanResultSet extends ScanResultSet\r
+ implements CursorResultSet, Cloneable\r
+{\r
+ protected ScanController scanController;\r
+ protected boolean scanControllerOpened;\r
+ protected boolean isKeyed;\r
+ protected boolean firstScan = true;\r
+ protected ExecIndexRow startPosition;\r
+ protected ExecIndexRow stopPosition;\r
+\r
+ // set in constructor and not altered during\r
+ // life of object.\r
+ protected long conglomId;\r
+ protected DynamicCompiledOpenConglomInfo dcoci;\r
+ protected StaticCompiledOpenConglomInfo scoci;\r
+ protected GeneratedMethod resultRowAllocator;\r
+ protected GeneratedMethod startKeyGetter;\r
+ protected int startSearchOperator;\r
+ protected GeneratedMethod stopKeyGetter;\r
+ protected int stopSearchOperator;\r
+ public Qualifier[][] qualifiers;\r
+ public String tableName;\r
+ public String userSuppliedOptimizerOverrides;\r
+ public String indexName;\r
+ protected boolean runTimeStatisticsOn;\r
+ protected FormatableBitSet accessedCols;\r
+ protected int[] indexCols; //index keys base column position array\r
+ public int rowsPerRead;\r
+ public boolean forUpdate;\r
+ private boolean sameStartStopPosition;\r
+ private boolean nextDone;\r
+ private RowLocation rlTemplate;\r
+\r
+ // Run time statistics\r
+ private Properties scanProperties;\r
+ public String startPositionString;\r
+ public String stopPositionString;\r
+ public boolean isConstraint;\r
+ public boolean coarserLock;\r
+ public boolean oneRowScan;\r
+\r
+ protected long rowsThisScan;\r
+\r
+ private long estimatedRowCount;\r
+\r
+ /* Following fields are used by beetle 3865, updateable cursor using index. "past2FutureTbl"\r
+ * is a hash table containing updated rows that are thrown into future direction of the\r
+ * index scan and as a result we'll hit it again but should skip it. If this hash table\r
+ * is full, we scan forward and have a virtual memory style temp heap holding future row\r
+ * id's.\r
+ */\r
+ protected Hashtable past2FutureTbl;\r
+ protected TemporaryRowHolder futureForUpdateRows; //tmp table for materialized rids\r
+ protected TemporaryRowHolderResultSet futureRowResultSet; //result set for reading from above\r
+ protected boolean skipFutureRowHolder; //skip reading rows from above\r
+ protected boolean sourceDrained; //all row ids materialized\r
+ protected boolean currentRowPrescanned; //got a row from above tmp table\r
+ protected boolean compareToLastKey; //see comments in UpdateResultSet\r
+ protected ExecRow lastCursorKey;\r
+ private ExecRow sparseRow; //sparse row in heap column order\r
+ private FormatableBitSet sparseRowMap; //which columns to read\r
+\r
+ // For Scrollable insensitive updatable result sets, only qualify a row the \r
+ // first time it's been read, since an update can change a row so that it \r
+ // no longer qualifies\r
+ private boolean qualify;\r
+\r
+ // currentRowIsValid is set to the result of positioning at a rowLocation.\r
+ // It will be true if the positioning was successful and false if the row \r
+ // was deleted under our feet. Whenenver currentRowIsValid is false it means \r
+ // that the row has been deleted.\r
+ private boolean currentRowIsValid;\r
+ \r
+ // Indicates whether the scan has been positioned back to a previously read\r
+ // row, or it is accessing a row for the first time.\r
+ private boolean scanRepositioned;\r
+\r
+ //\r
+ // class interface\r
+ //\r
+ TableScanResultSet(long conglomId,\r
+ StaticCompiledOpenConglomInfo scoci, \r
+ Activation activation, \r
+ GeneratedMethod resultRowAllocator, \r
+ int resultSetNumber,\r
+ GeneratedMethod startKeyGetter, int startSearchOperator,\r
+ GeneratedMethod stopKeyGetter, int stopSearchOperator,\r
+ boolean sameStartStopPosition,\r
+ Qualifier[][] qualifiers,\r
+ String tableName,\r
+ String userSuppliedOptimizerOverrides,\r
+ String indexName,\r
+ boolean isConstraint,\r
+ boolean forUpdate,\r
+ int colRefItem,\r
+ int indexColItem,\r
+ int lockMode,\r
+ boolean tableLocked,\r
+ int isolationLevel,\r
+ int rowsPerRead,\r
+ boolean oneRowScan,\r
+ double optimizerEstimatedRowCount,\r
+ double optimizerEstimatedCost)\r
+ throws StandardException\r
+ {\r
+ super(activation,\r
+ resultSetNumber,\r
+ resultRowAllocator,\r
+ lockMode, tableLocked, isolationLevel,\r
+ optimizerEstimatedRowCount,\r
+ optimizerEstimatedCost);\r
+\r
+ this.conglomId = conglomId;\r
+\r
+ /* Static info created at compile time and can be shared across\r
+ * instances of the plan.\r
+ * Dynamic info created on 1st opening of this ResultSet as\r
+ * it cannot be shared.\r
+ */\r
+ this.scoci = scoci;\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT( activation!=null, "table scan must get activation context");\r
+ SanityManager.ASSERT( resultRowAllocator!= null, "table scan must get row allocator");\r
+ if (sameStartStopPosition)\r
+ {\r
+ SanityManager.ASSERT(stopKeyGetter == null,\r
+ "stopKeyGetter expected to be null when sameStartStopPosition is true");\r
+ }\r
+ }\r
+\r
+ this.resultRowAllocator = resultRowAllocator;\r
+\r
+ this.startKeyGetter = startKeyGetter;\r
+ this.startSearchOperator = startSearchOperator;\r
+ this.stopKeyGetter = stopKeyGetter;\r
+ this.stopSearchOperator = stopSearchOperator;\r
+ this.sameStartStopPosition = sameStartStopPosition;\r
+ this.qualifiers = qualifiers;\r
+ this.tableName = tableName;\r
+ this.userSuppliedOptimizerOverrides = userSuppliedOptimizerOverrides;\r
+ this.indexName = indexName;\r
+ this.isConstraint = isConstraint;\r
+ this.forUpdate = forUpdate;\r
+ this.rowsPerRead = rowsPerRead;\r
+ this.oneRowScan = oneRowScan;\r
+\r
+ // retrieve the valid column list from\r
+ // the saved objects, if it exists\r
+ this.accessedCols = null;\r
+ if (colRefItem != -1)\r
+ {\r
+ this.accessedCols = (FormatableBitSet)(activation.getPreparedStatement().\r
+ getSavedObject(colRefItem));\r
+ }\r
+ if (indexColItem != -1)\r
+ {\r
+ this.indexCols = (int[])(activation.getPreparedStatement().\r
+ getSavedObject(indexColItem));\r
+ }\r
+ if (indexCols != null)\r
+ activation.setForUpdateIndexScan(this);\r
+\r
+ runTimeStatisticsOn = (activation != null &&\r
+ activation.getLanguageConnectionContext().getRunTimeStatisticsMode());\r
+\r
+ constructorTime += getElapsedMillis(beginTime);\r
+ \r
+ /* Always qualify the first time a row is being read */\r
+ qualify = true;\r
+ currentRowIsValid = false;\r
+ scanRepositioned = false;\r
+ }\r
+\r
+ //\r
+ // ResultSet interface (leftover from NoPutResultSet)\r
+ //\r
+\r
+ /**\r
+ * open a scan on the table. scan parameters are evaluated\r
+ * at each open, so there is probably some way of altering\r
+ * their values...\r
+ *\r
+ * @exception StandardException thrown on failure to open\r
+ */\r
+ public void openCore() throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT( ! isOpen, "TableScanResultSet already open");\r
+\r
+ // Get the current transaction controller\r
+ TransactionController tc = activation.getTransactionController();\r
+\r
+ initIsolationLevel();\r
+\r
+ if (dcoci == null)\r
+ dcoci = tc.getDynamicCompiledConglomInfo(conglomId);\r
+\r
+\r
+ if (startKeyGetter != null)\r
+ {\r
+ startPosition = (ExecIndexRow) startKeyGetter.invoke(activation);\r
+ if (sameStartStopPosition)\r
+ {\r
+ stopPosition = startPosition;\r
+ }\r
+ }\r
+ if (stopKeyGetter != null)\r
+ {\r
+ stopPosition = (ExecIndexRow) stopKeyGetter.invoke(activation);\r
+ }\r
+\r
+ /* NOTE: We always open the ScanController on the 1st open\r
+ * to do the keyed conglomerate check.\r
+ */\r
+\r
+ // Determine whether the conglomerate is keyed. This determines\r
+ // how we find the RowLocation for the base heap. For non-keyed\r
+ // conglomerates, we ask the scan. For keyed conglomerates, it\r
+ // is the last column in the row.\r
+ //\r
+ // Do this here, rather than in the constructor, so we can avoid\r
+ // throwing exceptions from the constructor\r
+ if (firstScan)\r
+ {\r
+ openScanController(tc);\r
+\r
+ isKeyed = scanController.isKeyed();\r
+\r
+ /*\r
+ ** If scan tracing is turned on, print information about this\r
+ ** TableScanResultSet when it is first opened. We would like\r
+ ** to do this when it is constructed, but it is not always\r
+ ** possible to get the start and stop positioners at the time\r
+ ** this object is constructed (because they may depend on outer\r
+ ** rows).\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("ScanTrace"))\r
+ {\r
+ //traceScanParameters();\r
+ }\r
+ }\r
+ }\r
+\r
+ // Check whether there are any comparisons with unordered nulls\r
+ // on either the start or stop position. If there are, we can\r
+ // (and must) skip the scan, because no rows can qualify\r
+ if (skipScan(startPosition, stopPosition))\r
+ {\r
+ scanControllerOpened = false;\r
+ }\r
+ /* NOTE: We always open the ScanController on the 1st open\r
+ * to do the keyed conglomerate check, so we only need to\r
+ * do it here if not the 1st scan.\r
+ */\r
+ else if (! firstScan)\r
+ {\r
+ openScanController(tc);\r
+ }\r
+\r
+ /* If the scan is on an index and opened for update,\r
+ * then we cache the scan controller and conglomerate\r
+ * number in the activation so that the scan controller\r
+ * can be re-used by the update/delete if the index\r
+ * that we are scanning also needs to be updated.\r
+ */\r
+ if (forUpdate && isKeyed)\r
+ {\r
+ activation.setIndexScanController(scanController);\r
+ activation.setIndexConglomerateNumber(conglomId);\r
+ }\r
+\r
+ firstScan = false;\r
+ isOpen = true;\r
+ numOpens++;\r
+ nextDone = false;\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /*\r
+ ** Open the scan controller\r
+ **\r
+ ** @param transaction controller will open one if null\r
+ */\r
+ protected void openScanController(TransactionController tc)\r
+ throws StandardException\r
+ {\r
+ openScanController(tc, (DataValueDescriptor)null);\r
+ }\r
+\r
+ /*\r
+ ** Does the work of openScanController.\r
+ **\r
+ ** @param tc transaction controller; will open one if null.\r
+ ** @param probeValue If non-null then we will open the scan controller\r
+ ** and position it using the received probeValue as the start key.\r
+ ** Otherwise we'll use whatever value is in startPosition (if non-\r
+ ** null) as the start key.\r
+ */\r
+ protected void openScanController(TransactionController tc,\r
+ DataValueDescriptor probeValue) throws StandardException\r
+ {\r
+ DataValueDescriptor[] startPositionRow = \r
+ startPosition == null ? null : startPosition.getRowArray();\r
+ DataValueDescriptor[] stopPositionRow = \r
+ stopPosition == null ? null : stopPosition.getRowArray();\r
+\r
+ /* If we have a probe value then we do the "probe" by positioning\r
+ * the scan at the first row matching the value. The way to do\r
+ * that is to use the value as a start key, which is what will\r
+ * happen if we plug it into first column of "startPositionRow".\r
+ * So in this case startPositionRow[0] functions as a "place-holder"\r
+ * for the probe value. The same goes for stopPositionRow[0].\r
+ *\r
+ * Note that it *is* possible for a start/stop key to contain more\r
+ * than one column (ex. if we're scanning a multi-column index). In\r
+ * that case we plug probeValue into the first column of the start\r
+ * and/or stop key and leave the rest of the key as it is. As an \r
+ * example, assume we have the following predicates:\r
+ *\r
+ * ... where d in (1, 20000) and b > 200 and b <= 500\r
+ *\r
+ * And assume further that we have an index defined on (d, b).\r
+ * In this case it's possible that we have TWO start predicates\r
+ * and TWO stop predicates: the IN list will give us "d = probeVal",\r
+ * which is a start predicate and a stop predicate; then "b > 200"\r
+ * may give us a second start predicate, while "b <= 500" may give\r
+ * us a second stop predicate. So in this situation we want our\r
+ * start key to be:\r
+ *\r
+ * (probeValue, 200)\r
+ *\r
+ * and our stop key to be:\r
+ *\r
+ * (probeValue, 500).\r
+ *\r
+ * This will effectively limit the scan so that it only returns\r
+ * rows whose "D" column equals probeValue and whose "B" column\r
+ * falls in the range of 200 thru 500.\r
+ *\r
+ * Note: Derby currently only allows a single start/stop predicate\r
+ * per column. See PredicateList.orderUsefulPredicates().\r
+ */\r
+ if (probeValue != null)\r
+ {\r
+ startPositionRow[0] = probeValue;\r
+\r
+ /* If the start key and stop key are the same, we've already set\r
+ * stopPosition equal to startPosition as part of openCore().\r
+ * So by putting the probe value into startPositionRow[0], we\r
+ * also put it into stopPositionRow[0].\r
+ */\r
+ if (!sameStartStopPosition)\r
+ stopPositionRow[0] = probeValue;\r
+ }\r
+\r
+ // Clear the Qualifiers's Orderable cache \r
+ if (qualifiers != null)\r
+ {\r
+ clearOrderableCache(qualifiers);\r
+ }\r
+\r
+ // Get the current transaction controller\r
+ if (tc == null)\r
+ tc = activation.getTransactionController();\r
+\r
+ int openMode = 0;\r
+ if (forUpdate)\r
+ {\r
+ openMode = TransactionController.OPENMODE_FORUPDATE;\r
+\r
+ if (activation.isCursorActivation())\r
+ openMode |= TransactionController.OPENMODE_USE_UPDATE_LOCKS;\r
+ }\r
+\r
+ scanController = tc.openCompiledScan(\r
+ activation.getResultSetHoldability(),\r
+ openMode,\r
+ lockMode,\r
+ isolationLevel,\r
+ accessedCols,\r
+ startPositionRow,\r
+ // not used when giving null start position\r
+ startSearchOperator,\r
+ qualifiers,\r
+ stopPositionRow,\r
+ // not used when giving null stop position\r
+ stopSearchOperator,\r
+ scoci,\r
+ dcoci);\r
+\r
+ /* Remember that we opened the scan */\r
+ scanControllerOpened = true;\r
+\r
+ rowsThisScan = 0;\r
+\r
+ /*\r
+ ** Inform the activation of the estimated number of rows. Only\r
+ ** do it here, not in reopen, so that we don't do this costly\r
+ ** check too often.\r
+ */\r
+ estimatedRowCount = scanController.getEstimatedRowCount();\r
+ activation.informOfRowCount(\r
+ this,\r
+ scanController.getEstimatedRowCount()\r
+ );\r
+ }\r
+\r
+ /*\r
+ ** reopen the scan controller\r
+ */\r
+ protected void reopenScanController() throws StandardException\r
+ {\r
+ reopenScanController((DataValueDescriptor)null);\r
+ }\r
+\r
+ /*\r
+ ** Does the work of reopenScanController.\r
+ **\r
+ ** @param probeValue If non-null then we will open the scan controller\r
+ ** and position it using the received probeValue as the start key.\r
+ ** Otherwise we'll use whatever value is in startPosition (if non-\r
+ ** null) as the start key.\r
+ */\r
+ protected void reopenScanController(DataValueDescriptor probeValue)\r
+ throws StandardException\r
+ {\r
+ DataValueDescriptor[] startPositionRow = \r
+ startPosition == null ? null : startPosition.getRowArray();\r
+ DataValueDescriptor[] stopPositionRow = \r
+ stopPosition == null ? null : stopPosition.getRowArray();\r
+\r
+ /* If we have a probe value then we do the "probe" by using the\r
+ * value as a start and stop key. See openScanController() for\r
+ * details. Note that in this case we do *not* want to reset\r
+ * the rowsThisScan variable because we are going to be doing\r
+ * multiple "probes" for a single scan. Logic to detect when\r
+ * when we've actually started a new scan (as opposed to just\r
+ * repositioning an existing scan based on a probe value) is\r
+ * in MultiProbeTableScanResultSet.reopenScanController(),\r
+ * and that method will then take care of resetting the variable\r
+ * (if needed) for probing scans.\r
+ */\r
+ if (probeValue != null)\r
+ {\r
+ startPositionRow[0] = probeValue;\r
+ if (!sameStartStopPosition)\r
+ stopPositionRow[0] = probeValue;\r
+ }\r
+ else\r
+ rowsThisScan = 0;\r
+\r
+ // Clear the Qualifiers's Orderable cache \r
+ if (qualifiers != null)\r
+ {\r
+ clearOrderableCache(qualifiers);\r
+ }\r
+\r
+ scanController.reopenScan(\r
+ startPositionRow,\r
+ startSearchOperator,\r
+ qualifiers,\r
+ stopPositionRow,\r
+ stopSearchOperator);\r
+\r
+ /* Remember that we opened the scan */\r
+ scanControllerOpened = true;\r
+ }\r
+\r
+ /**\r
+ * Reopen a table scan. Here we take advantage\r
+ * of the reopenScan() interface on scanController\r
+ * for optimimal performance on joins where we are\r
+ * an inner table.\r
+ *\r
+ * @exception StandardException thrown on failure to open\r
+ */\r
+ public void reopenCore() throws StandardException\r
+ {\r
+ beginTime = getCurrentTimeMillis();\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(isOpen, "TableScanResultSet not open, cannot reopen");\r
+\r
+ if (startKeyGetter != null)\r
+ {\r
+ startPosition = (ExecIndexRow) startKeyGetter.invoke(activation);\r
+ if (sameStartStopPosition)\r
+ {\r
+ stopPosition = startPosition;\r
+ }\r
+ }\r
+ if (stopKeyGetter != null)\r
+ {\r
+ stopPosition = (ExecIndexRow) stopKeyGetter.invoke(activation);\r
+ }\r
+\r
+ // Check whether there are any comparisons with unordered nulls\r
+ // on either the start or stop position. If there are, we can\r
+ // (and must) skip the scan, because no rows can qualify\r
+ if (skipScan(startPosition, stopPosition))\r
+ {\r
+ scanControllerOpened = false;\r
+ }\r
+ else\r
+ {\r
+ if (scanController == null)\r
+ openScanController((TransactionController)null);\r
+ else\r
+ reopenScanController();\r
+ \r
+ }\r
+\r
+ numOpens++;\r
+ nextDone = false;\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * Check and make sure sparse heap row and accessed bit map are created.\r
+ * beetle 3865, update cursor using index.\r
+ *\r
+ * @exception StandardException thrown on failure\r
+ */\r
+ private void getSparseRowAndMap() throws StandardException\r
+ {\r
+ int numCols = 1, colPos;\r
+ for (int i = 0; i < indexCols.length; i++)\r
+ {\r
+ colPos = (indexCols[i] > 0) ? indexCols[i] : -indexCols[i];\r
+ if (colPos > numCols)\r
+ numCols = colPos;\r
+ }\r
+ sparseRow = new ValueRow(numCols);\r
+ sparseRowMap = new FormatableBitSet(numCols);\r
+ for (int i = 0; i < indexCols.length; i++)\r
+ {\r
+ if (accessedCols.get(i))\r
+ {\r
+ colPos = (indexCols[i] > 0) ? indexCols[i] : -indexCols[i];\r
+ sparseRow.setColumn(colPos, candidate.getColumn(i + 1));\r
+ sparseRowMap.set(colPos - 1);\r
+ }\r
+ }\r
+ }\r
+ \r
+\r
+ /**\r
+ * Return the next row (if any) from the scan (if open).\r
+ *\r
+ * @exception StandardException thrown on failure to get next row\r
+ */\r
+ public ExecRow getNextRowCore() throws StandardException\r
+ {\r
+ checkCancellationFlag();\r
+ \r
+ if (currentRow == null || scanRepositioned)\r
+ {\r
+ currentRow =\r
+ getCompactRow(candidate, accessedCols, (FormatableBitSet) null, isKeyed);\r
+ }\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+\r
+ ExecRow result = null;\r
+\r
+ /* beetle 3865, updateable cursor using index. We first saved updated rows with new value\r
+ * falling into future direction of index scan in hash table, if it's full, we scanned\r
+ * forward and saved future row ids in a virtual mem heap.\r
+ */\r
+ if (futureForUpdateRows != null)\r
+ {\r
+ currentRowPrescanned = false;\r
+ if (! skipFutureRowHolder)\r
+ {\r
+ if (futureRowResultSet == null)\r
+ {\r
+ futureRowResultSet = (TemporaryRowHolderResultSet) futureForUpdateRows.getResultSet();\r
+ futureRowResultSet.openCore();\r
+ }\r
+\r
+ ExecRow ridRow = futureRowResultSet.getNextRowCore();\r
+\r
+ if (ridRow != null)\r
+ {\r
+ /* to boost performance, we used virtual mem heap, and we can insert after\r
+ * we start retrieving results. The assumption is to\r
+ * delete current row right after we retrieve it.\r
+ */\r
+ futureRowResultSet.deleteCurrentRow();\r
+ RowLocation rl = (RowLocation) ridRow.getColumn(1);\r
+ ConglomerateController baseCC = activation.getHeapConglomerateController();\r
+ if (sparseRow == null)\r
+ getSparseRowAndMap();\r
+ baseCC.fetch(\r
+ rl, sparseRow.getRowArray(), sparseRowMap);\r
+ RowLocation rl2 = (RowLocation) rl.getClone();\r
+ currentRow.setColumn(currentRow.nColumns(), rl2);\r
+ candidate.setColumn(candidate.nColumns(), rl2); // have to be consistent!\r
+\r
+ result = currentRow;\r
+ currentRowPrescanned = true;\r
+ }\r
+ else if (sourceDrained)\r
+ {\r
+ currentRowPrescanned = true;\r
+ currentRow = null;\r
+ }\r
+\r
+ if (currentRowPrescanned)\r
+ {\r
+ setCurrentRow(result);\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+ return result;\r
+ }\r
+ }\r
+ }\r
+\r
+ if ( isOpen && !nextDone)\r
+ {\r
+ /* Only need to do 1 next per scan\r
+ * for 1 row scans.\r
+ */\r
+ nextDone = oneRowScan;\r
+\r
+ if (scanControllerOpened)\r
+ {\r
+ boolean moreRows;\r
+\r
+ while (moreRows =\r
+ scanController.fetchNext(candidate.getRowArray()))\r
+ {\r
+ rowsSeen++;\r
+ rowsThisScan++;\r
+\r
+ /*\r
+ ** Skip rows where there are start or stop positioners\r
+ ** that do not implement ordered null semantics and\r
+ ** there are columns in those positions that contain\r
+ ** null.\r
+ ** No need to check if start and stop positions are the\r
+ ** same, since all predicates in both will be ='s,\r
+ ** and hence evaluated in the store.\r
+ */\r
+ if ((! sameStartStopPosition) && skipRow(candidate))\r
+ {\r
+ rowsFiltered++;\r
+ continue;\r
+ }\r
+\r
+ /* beetle 3865, updateable cursor use index. If we have a hash table that\r
+ * holds updated records, and we hit it again, skip it, and remove it from\r
+ * hash since we can't hit it again, and we have a space in hash, so can\r
+ * stop scanning forward.\r
+ */\r
+ if (past2FutureTbl != null)\r
+ {\r
+ RowLocation rowLoc = (RowLocation) currentRow.getColumn(currentRow.nColumns());\r
+ if (past2FutureTbl.get(rowLoc) != null)\r
+ {\r
+ past2FutureTbl.remove(rowLoc);\r
+ continue;\r
+ }\r
+ }\r
+\r
+ result = currentRow;\r
+\r
+ break;\r
+ }\r
+\r
+ /*\r
+ ** If we just finished a full scan of the heap, update\r
+ ** the number of rows in the scan controller.\r
+ **\r
+ ** NOTE: It would be more efficient to only update the\r
+ ** scan controller if the optimizer's estimated number of\r
+ ** rows were wrong by more than some threshold (like 10%).\r
+ ** This would require a little more work than I have the\r
+ ** time for now, however, as the row estimate that is given\r
+ ** to this result set is the total number of rows for all\r
+ ** scans, not the number of rows per scan.\r
+ */\r
+ if (! moreRows)\r
+ {\r
+ setRowCountIfPossible(rowsThisScan);\r
+ currentRow = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ setCurrentRow(result);\r
+ currentRowIsValid = true;\r
+ scanRepositioned = false;\r
+ qualify = true;\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * If the result set has been opened,\r
+ * close the open scan.\r
+ * @exception StandardException on error\r
+ */\r
+ public void close() throws StandardException\r
+ {\r
+ beginTime = getCurrentTimeMillis();\r
+ if ( isOpen )\r
+ {\r
+ /*\r
+ ** If scan tracing is turned on, print information about this\r
+ ** TableScanResultSet when it is closed.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("ScanTrace"))\r
+ {\r
+ //traceClose();\r
+ }\r
+ }\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 (scanController != null)\r
+ {\r
+ // This is where we get the positioner info for inner tables\r
+ if (runTimeStatisticsOn)\r
+ {\r
+ // This is where we get the scan properties for a subquery\r
+ scanProperties = getScanProperties();\r
+ startPositionString = printStartPosition();\r
+ stopPositionString = printStopPosition();\r
+ }\r
+ scanController.close();\r
+ scanController = null; // should not access after close\r
+ activation.clearIndexScanInfo();\r
+ }\r
+ scanControllerOpened = false;\r
+ startPosition = null;\r
+ stopPosition = null;\r
+\r
+ super.close();\r
+\r
+ if (indexCols != null)\r
+ {\r
+ ConglomerateController borrowedBaseCC = activation.getHeapConglomerateController();\r
+ if (borrowedBaseCC != null)\r
+ {\r
+ borrowedBaseCC.close();\r
+ activation.clearHeapConglomerateController();\r
+ }\r
+ }\r
+ if (futureRowResultSet != null)\r
+ futureRowResultSet.close();\r
+ }\r
+ else\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.DEBUG("CloseRepeatInfo","Close of TableScanResultSet repeated");\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
+ /* RESOLVE - subtract out store time later, when available */\r
+ if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY)\r
+ {\r
+ return totTime;\r
+ }\r
+ else\r
+ {\r
+ return totTime;\r
+ }\r
+ }\r
+\r
+\r
+ //\r
+ // CursorResultSet interface\r
+ //\r
+\r
+ /**\r
+ * This result set has its row location from\r
+ * the last fetch done. If the cursor is closed, \r
+ * or the row has been deleted a null is returned.\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the row location of the current cursor row.\r
+ * @exception StandardException thrown on failure to get row location\r
+ */\r
+ public RowLocation getRowLocation() throws StandardException\r
+ {\r
+ RowLocation rl;\r
+\r
+ if (! isOpen) return null;\r
+\r
+ if ( ! scanControllerOpened)\r
+ return null;\r
+\r
+ /*\r
+ ** If the conglomerate is keyed, the row location of the base row\r
+ ** is in the last column of the current row. If it's not keyed,\r
+ ** we get the row location from the scan of the heap.\r
+ */\r
+ if (isKeyed)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentRow != null,\r
+ "There must be a current row when fetching the row location");\r
+ }\r
+\r
+ rl = (RowLocation) currentRow.getColumn(\r
+ currentRow.nColumns());\r
+ }\r
+ else\r
+ {\r
+ if (currentRowIsValid) {\r
+ // we reuse the same rowlocation object across several calls.\r
+ if (rlTemplate == null)\r
+ rlTemplate = scanController.newRowLocationTemplate();\r
+ rl = rlTemplate;\r
+ try {\r
+ scanController.fetchLocation(rl);\r
+ } catch (StandardException se) {\r
+ if (se.getMessageId().\r
+ equals(SQLState.HEAP_SCAN_NOT_POSITIONED)) {\r
+ //Have a easier to understand error message than what \r
+ //we get from store \r
+ throw StandardException.\r
+ newException(SQLState.NO_CURRENT_ROW);\r
+ }\r
+ throw se;\r
+ }\r
+ } else {\r
+ rl = null;\r
+ }\r
+ }\r
+\r
+ return rl;\r
+ }\r
+\r
+ /**\r
+ * This result set has its row from the last fetch done. \r
+ * If the cursor is closed, the row has been deleted, or\r
+ * no longer qualifies (for forward only result sets) a \r
+ * null is 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
+ {\r
+ ExecRow result = null;\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(isOpen, "TSRS expected to be open");\r
+\r
+ if (currentRowPrescanned)\r
+ return currentRow;\r
+\r
+ /* Nothing to do if we're not currently on a row or\r
+ * if the current row get deleted out from under us\r
+ * or if there is no current scan (can happen if the\r
+ * scan is being skipped) or if the current position\r
+ * no longer qualifies.\r
+ */\r
+ try\r
+ {\r
+ if ((currentRow == null) ||\r
+ (!currentRowIsValid) ||\r
+ (!scanControllerOpened) ||\r
+ (qualify && scanController.isCurrentPositionDeleted()) ||\r
+ (qualify && (!scanController.doesCurrentPositionQualify())))\r
+ {\r
+ return null;\r
+ }\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ if (se.getMessageId().equals(SQLState.AM_SCAN_NOT_POSITIONED))\r
+ {\r
+ //bug 4515 - Have a easier to understand error message than what we get from store \r
+ se=StandardException.newException(SQLState.NO_CURRENT_ROW);\r
+ throw se;\r
+ }\r
+ }\r
+\r
+ result = (ExecRow) resultRowAllocator.invoke(activation);\r
+ currentRow = \r
+ getCompactRow(result, accessedCols, (FormatableBitSet) null, isKeyed);\r
+\r
+ try\r
+ {\r
+ scanController.fetchWithoutQualify(result.getRowArray());\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ if (se.getMessageId().equals(SQLState.AM_RECORD_NOT_FOUND))\r
+ {\r
+ // Somehow the row got deleted between the above \r
+ // doesCurrentPositionQualify() call and here (one way is if\r
+ // this scan is read uncommitted isolation level).\r
+ return null;\r
+ }\r
+ else\r
+ {\r
+ throw se;\r
+ }\r
+ }\r
+\r
+ setCurrentRow(result);\r
+ return currentRow;\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#positionScanAtRowLocation\r
+ * \r
+ * Also sets qualify to false so that later calls to getCurrentRow\r
+ * will not attempt to re-qualify the current row. \r
+ */\r
+ public void positionScanAtRowLocation(RowLocation rl) \r
+ throws StandardException \r
+ {\r
+ // Check if the scanController is a B-tree scan controller. Do not\r
+ // attempt to re-position a b-tree controller.\r
+ if (!isKeyed) {\r
+ currentRowIsValid = scanController.positionAtRowLocation(rl);\r
+ }\r
+ qualify = false;\r
+ scanRepositioned = true;\r
+ }\r
+\r
+ /**\r
+ * Print the parameters that constructed this result set to the\r
+ * trace stream.\r
+ */\r
+/*\r
+ private final void traceScanParameters()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ HeaderPrintWriter traceStream = SanityManager.GET_DEBUG_STREAM();\r
+\r
+ traceStream.println("");\r
+ traceStream.println("TableScanResultSet number " +\r
+ resultSetNumber +\r
+ " parameters:");\r
+\r
+ traceStream.println("");\r
+ traceStream.println("\tTable name: " + tableName);\r
+ if (indexName != null)\r
+ {\r
+ traceStream.println("\tIndex name: " + indexName);\r
+ }\r
+ traceStream.println("");\r
+ traceStream.println("\tStart position is: ");\r
+ tracePrintPosition(traceStream,\r
+ startSearchOperator,\r
+ startKeyGetter);\r
+ traceStream.println("");\r
+ traceStream.println("\tStop position is: " );\r
+ tracePrintPosition(traceStream,\r
+ stopSearchOperator,\r
+ stopKeyGetter);\r
+ traceStream.println("");\r
+ traceStream.println("\tQualifiers are: ");\r
+ tracePrintQualifiers(traceStream, qualifiers, 2);\r
+ traceStream.println("");\r
+ }\r
+ }\r
+*/\r
+\r
+ /**\r
+ * Print I/O statistics about a scan when it closes.\r
+ */\r
+/*\r
+ private final void traceClose()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ InfoStreams infoStreams;\r
+ HeaderPrintWriter traceStream;\r
+\r
+ traceStream = SanityManager.GET_DEBUG_STREAM();\r
+\r
+ traceStream.println("TableScanResultSet number " +\r
+ resultSetNumber +\r
+ " closed.");\r
+ if (isKeyed)\r
+ {\r
+ traceStream.println("\t" +\r
+ rowCount() +\r
+ " row(s) qualified from " +\r
+ "keyed" +\r
+ " table " +\r
+ tableName +\r
+ " using index " +\r
+ indexName);\r
+ }\r
+ else\r
+ {\r
+ traceStream.println("\t" +\r
+ rowCount() +\r
+ " row(s) qualified from " +\r
+ "non-keyed" +\r
+ " table " +\r
+ tableName);\r
+ }\r
+ traceStream.println("");\r
+ }\r
+ }\r
+*/\r
+\r
+ /**\r
+ * Print a start or stop positioner to the trace stream.\r
+ */\r
+/*\r
+ private final void tracePrintPosition(HeaderPrintWriter traceStream,\r
+ int searchOperator,\r
+ GeneratedMethod positionGetter)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (positionGetter == null)\r
+ {\r
+ traceStream.println("\t\tNone");\r
+ return;\r
+ }\r
+\r
+ ExecIndexRow positioner = null;\r
+\r
+ try\r
+ {\r
+ positioner = (ExecIndexRow) positionGetter.invoke(activation);\r
+ }\r
+ catch (StandardException e)\r
+ {\r
+ traceStream.println("\t\tUnexpected exception " +\r
+ e +\r
+ " getting positioner.");\r
+ e.printStackTrace(traceStream.getPrintWriter());\r
+ return;\r
+ }\r
+\r
+ if (positioner == null)\r
+ {\r
+ traceStream.println("\t\tNone");\r
+ return;\r
+ }\r
+\r
+ String searchOp = null;\r
+\r
+ switch (searchOperator)\r
+ {\r
+ case ScanController.GE:\r
+ searchOp = "GE";\r
+ break;\r
+\r
+ case ScanController.GT:\r
+ searchOp = "GT";\r
+ break;\r
+\r
+ default:\r
+ searchOp = "unknown value (" + searchOperator + ")";\r
+ break;\r
+ }\r
+\r
+ traceStream.println("\t\t" +\r
+ searchOp +\r
+ " on first " +\r
+ positioner.nColumns() +\r
+ " column(s).");\r
+\r
+ traceStream.print(\r
+ "\t\tOrdered null semantics on the following columns: ");\r
+ for (int position = 0; position < positioner.nColumns(); position++)\r
+ {\r
+ if (positioner.areNullsOrdered(position))\r
+ {\r
+ traceStream.print(position + " ");\r
+ }\r
+ }\r
+ traceStream.println("");\r
+ }\r
+ }\r
+*/\r
+\r
+\r
+ /**\r
+ * Print an array of Qualifiers to the trace stream.\r
+ */\r
+/*\r
+ private final void tracePrintQualifiers(HeaderPrintWriter traceStream,\r
+ Qualifier[][] qualifiers,\r
+ int depth)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ char[] indentchars = new char[depth];\r
+\r
+ /*\r
+ ** Form an array of tab characters for indentation.\r
+ *\r
+ while (depth > 0)\r
+ {\r
+ indentchars[depth - 1] = '\t';\r
+ depth--;\r
+ }\r
+ String indent = new String(indentchars);\r
+\r
+ if (qualifiers == null)\r
+ {\r
+ traceStream.println(indent +\r
+ MessageService.getTextMessage(\r
+ SQLState.LANG_NONE)\r
+ );\r
+ return;\r
+ }\r
+\r
+ // RESOLVE (mikem) We don't support 2-d qualifiers yet.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(qualifiers.length == 1);\r
+ }\r
+\r
+ for (int i = 0; i < qualifiers[0].length; i++)\r
+ {\r
+ Qualifier qual = qualifiers[0][i];\r
+\r
+ traceStream.println("");\r
+ traceStream.println(indent + "Column Id: " + qual.getColumnId());\r
+ \r
+ int operator = qual.getOperator();\r
+ String opString = null;\r
+ switch (operator)\r
+ {\r
+ case Orderable.ORDER_OP_EQUALS:\r
+ opString = "=";\r
+ break;\r
+\r
+ case Orderable.ORDER_OP_LESSOREQUALS:\r
+ opString = "<=";\r
+ break;\r
+\r
+ case Orderable.ORDER_OP_LESSTHAN:\r
+ opString = "<";\r
+ break;\r
+\r
+ default:\r
+ opString = "unknown value (" + operator + ")";\r
+ break;\r
+ }\r
+ traceStream.println(indent + "Operator: " + opString);\r
+ traceStream.println(indent + "Ordered nulls: " +\r
+ qual.getOrderedNulls());\r
+ traceStream.println(indent + "Unknown return value: " +\r
+ qual.getUnknownRV());\r
+ traceStream.println(indent + "Negate comparison result: " +\r
+ qual.negateCompareResult());\r
+ traceStream.println("");\r
+ }\r
+ }\r
+ }\r
+*/\r
+\r
+ public String printStartPosition()\r
+ {\r
+ return printPosition(startSearchOperator, startKeyGetter, startPosition);\r
+ }\r
+\r
+ public String printStopPosition()\r
+ {\r
+ if (sameStartStopPosition)\r
+ {\r
+ return printPosition(stopSearchOperator, startKeyGetter, startPosition);\r
+ }\r
+ else\r
+ {\r
+ return printPosition(stopSearchOperator, stopKeyGetter, stopPosition);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Return a start or stop positioner as a String.\r
+ *\r
+ * If we already generated the information, then use\r
+ * that. Otherwise, invoke the activation to get it.\r
+ */\r
+ private String printPosition(int searchOperator,\r
+ GeneratedMethod positionGetter,\r
+ ExecIndexRow positioner)\r
+ {\r
+ String idt = "";\r
+ String output = "";\r
+ if (positionGetter == null)\r
+ {\r
+ return "\t" +\r
+ MessageService.getTextMessage(SQLState.LANG_NONE) +\r
+ "\n";\r
+ }\r
+ \r
+ if (positioner == null)\r
+ {\r
+ try\r
+ {\r
+ positioner = (ExecIndexRow)positionGetter.invoke(activation);\r
+ }\r
+ catch (StandardException e)\r
+ {\r
+ // the positionGetter will fail with a NullPointerException\r
+ // if the outer table is empty\r
+ // (this isn't a problem since we won't call it on the inner\r
+ // table if there are no rows on the outer table)\r
+ if (e.getSQLState() == SQLState.LANG_UNEXPECTED_USER_EXCEPTION )\r
+ return "\t" + MessageService.getTextMessage(\r
+ SQLState.LANG_POSITION_NOT_AVAIL);\r
+ return "\t" + MessageService.getTextMessage(\r
+ SQLState.LANG_UNEXPECTED_EXC_GETTING_POSITIONER,\r
+ e.toString());\r
+ }\r
+ }\r
+ if (positioner == null)\r
+ {\r
+ return "\t" +\r
+ MessageService.getTextMessage(SQLState.LANG_NONE) +\r
+ "\n";\r
+ }\r
+ String searchOp = null;\r
+\r
+ switch (searchOperator)\r
+ {\r
+ case ScanController.GE:\r
+ searchOp = ">=";\r
+ break;\r
+\r
+ case ScanController.GT:\r
+ searchOp = ">";\r
+ break;\r
+\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT("Unknown search operator " +\r
+ searchOperator);\r
+ }\r
+\r
+ // NOTE: This does not have to be internationalized because\r
+ // this code should never be reached.\r
+ searchOp = "unknown value (" + searchOperator + ")";\r
+ break;\r
+ }\r
+\r
+ output = output + "\t" +\r
+ MessageService.getTextMessage(\r
+ SQLState.LANG_POSITIONER,\r
+ searchOp,\r
+ String.valueOf(positioner.nColumns())) +\r
+ "\n";\r
+\r
+ output = output + "\t" +\r
+ MessageService.getTextMessage(\r
+ SQLState.LANG_ORDERED_NULL_SEMANTICS) +\r
+ "\n";\r
+ for (int position = 0; position < positioner.nColumns(); position++)\r
+ {\r
+ if (positioner.areNullsOrdered(position))\r
+ {\r
+ output = output + position + " ";\r
+ }\r
+ }\r
+ \r
+ return output + "\n";\r
+ }\r
+\r
+ public Properties getScanProperties()\r
+ {\r
+ if (scanProperties == null)\r
+ {\r
+ scanProperties = new Properties();\r
+ }\r
+ try\r
+ {\r
+ if (scanController != null)\r
+ {\r
+ scanController.getScanInfo().getAllScanInfo(scanProperties);\r
+ /* Did we get a coarser lock due to\r
+ * a covering lock, lock escalation\r
+ * or configuration?\r
+ */\r
+ coarserLock = scanController.isTableLocked() &&\r
+ (lockMode == TransactionController.MODE_RECORD);\r
+ }\r
+ }\r
+ catch(StandardException se)\r
+ {\r
+ // ignore\r
+ }\r
+\r
+ return scanProperties;\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#requiresRelocking\r
+ */\r
+ public boolean requiresRelocking()\r
+ {\r
+ return(\r
+ isolationLevel == \r
+ TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK);\r
+ }\r
+\r
+ /**\r
+ * Update the number of rows in the scan controller.\r
+ *\r
+ * NOTE: It would be more efficient to only update the\r
+ * scan controller if the optimizer's estimated number of\r
+ * rows were wrong by more than some threshold (like 10%).\r
+ * This would require a little more work than I have the\r
+ * time for now, however, as the row estimate that is given\r
+ * to this result set is the total number of rows for all\r
+ * scans, not the number of rows per scan.\r
+ *\r
+ *\r
+ * @param rowsThisScan The number of rows to update the scanController to\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ protected final void setRowCountIfPossible(long rowsThisScan)\r
+ throws StandardException\r
+ {\r
+ /*\r
+ ** Is it a heap scan with no qualifiers (full table scan?)\r
+ ** and is it not for update (we don't want to count rows we're\r
+ ** about to delete.\r
+ */\r
+ if ( ( ! scanController.isKeyed() ) &&\r
+ (qualifiers == null || qualifiers.length == 0) &&\r
+ ( ! forUpdate ) )\r
+ {\r
+\r
+ // Only update rows if different by more than 10%\r
+ long diff = rowsThisScan - estimatedRowCount;\r
+\r
+ long tenPerCent = estimatedRowCount / 10;\r
+\r
+ if (diff < 0)\r
+ diff = -diff;\r
+\r
+ if (diff > tenPerCent)\r
+ scanController.setEstimatedRowCount(rowsThisScan);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Can we get instantaneous locks when getting share row\r
+ * locks at READ COMMITTED.\r
+ */\r
+ protected boolean canGetInstantaneousLocks()\r
+ {\r
+ return false;\r
+ }\r
+\r
+\r
+ /**\r
+ * Is this ResultSet or it's source result set for update\r
+ * \r
+ * @return Whether or not the result set is for update.\r
+ */\r
+ public boolean isForUpdate()\r
+ {\r
+ return forUpdate;\r
+ }\r
+\r
+ /**\r
+ * Shallow clone this result set. Used in trigger reference.\r
+ * beetle 4373.\r
+ */\r
+ public Object clone()\r
+ {\r
+ Object clo = null;\r
+ try {\r
+ clo = super.clone();\r
+ }\r
+ catch (CloneNotSupportedException e) {}\r
+ return clo;\r
+ }\r
+}\r