--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.ScrollInsensitiveResultSet\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.sanity.SanityManager;\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.sql.Activation;\r
+\r
+import org.apache.derby.iapi.types.RowLocation;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.store.access.BackingStoreHashtable;\r
+\r
+import org.apache.derby.iapi.types.SQLBoolean;\r
+import org.apache.derby.iapi.types.SQLInteger;\r
+\r
+/**\r
+ *\r
+ * Provide insensitive scrolling functionality for the underlying\r
+ * result set. We build a disk backed hash table of rows as the \r
+ * user scrolls forward, with the position as the key.\r
+ *\r
+ * For read-only result sets the hash table will containg the\r
+ * following columns:\r
+ *<pre>\r
+ * +-------------------------------+\r
+ * | KEY |\r
+ * +-------------------------------+\r
+ * | Row |\r
+ * +-------------------------------+\r
+ *</pre>\r
+ * where key is the position of the row in the result set and row is the data.\r
+ *\r
+ * And for updatable result sets it will contain:\r
+ * <pre>\r
+ * +-------------------------------+\r
+ * | KEY | [0]\r
+ * +-------------------------------+\r
+ * | RowLocation | [POS_ROWLOCATION]\r
+ * +-------------------------------+\r
+ * | Deleted | [POS_ROWDELETED]\r
+ * +-------------------------------+\r
+ * | Updated | [POS_ROWUPDATED]\r
+ * +-------------------------------+\r
+ * | Row | [extraColumns ... n]\r
+ * +-------------------------------+\r
+ *</pre>\r
+ * where key is the position of the row in the result set, rowLocation is\r
+ * the row location of that row in the Heap, Deleted indicates whether the\r
+ * row has been deleted, Updated indicates whether the row has been updated,\r
+ * and row is the data.\r
+ *\r
+ */\r
+\r
+public class ScrollInsensitiveResultSet extends NoPutResultSetImpl\r
+ implements CursorResultSet\r
+{\r
+ /*\r
+ ** Set in constructor and not altered during life of object.\r
+ */\r
+\r
+ public NoPutResultSet source;\r
+\r
+\r
+\r
+ private int sourceRowWidth;\r
+\r
+ private BackingStoreHashtable ht;\r
+ private ExecRow resultRow;\r
+\r
+ // Scroll tracking\r
+ private int positionInSource;\r
+ private int currentPosition;\r
+ private int lastPosition;\r
+ private boolean seenFirst;\r
+ private boolean seenLast;\r
+ private boolean beforeFirst = true;\r
+ private boolean afterLast;\r
+\r
+ public int numFromHashTable;\r
+ public int numToHashTable;\r
+\r
+ private int maxRows;\r
+\r
+ private boolean keepAfterCommit;\r
+\r
+ /* The hash table will contain a different number of extra columns depending\r
+ * on whether the result set is updatable or not.\r
+ * extraColumns will contain the number of extra columns on the hash table,\r
+ * 1 for read-only result sets and LAST_EXTRA_COLUMN + 1 for updatable \r
+ * result sets.\r
+ */\r
+ private int extraColumns;\r
+ \r
+ /* positionInHashTable is used for getting a row from the hash table. Prior\r
+ * to getting the row, positionInHashTable will be set to the desired KEY.\r
+ */\r
+ private SQLInteger positionInHashTable;\r
+\r
+ /* Reference to the target result set. Target is used for updatable result\r
+ * sets in order to keep the target result set on the same row as the\r
+ * ScrollInsensitiveResultSet. \r
+ */\r
+ private CursorResultSet target;\r
+\r
+ /* If the last row was fetched from the HashTable, updatable result sets\r
+ * need to be positioned in the last fetched row before resuming the \r
+ * fetch from core.\r
+ */\r
+ private boolean needsRepositioning;\r
+\r
+ /* Position of the different fields in the hash table row for updatable\r
+ * result sets \r
+ */\r
+ private static final int POS_ROWLOCATION = 1;\r
+ private static final int POS_ROWDELETED = 2;\r
+ private static final int POS_ROWUPDATED = 3;\r
+ private static final int LAST_EXTRA_COLUMN = 3;\r
+\r
+ /**\r
+ * Constructor for a ScrollInsensitiveResultSet\r
+ *\r
+ * @param source The NoPutResultSet from which to get rows\r
+ * to scroll through\r
+ * @param activation The activation for this execution\r
+ * @param resultSetNumber The resultSetNumber\r
+ * @param sourceRowWidth # of columns in the source row\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+\r
+ public ScrollInsensitiveResultSet(NoPutResultSet source,\r
+ Activation activation, int resultSetNumber,\r
+ int sourceRowWidth,\r
+ double optimizerEstimatedRowCount,\r
+ double optimizerEstimatedCost) throws StandardException\r
+ {\r
+ super(activation, resultSetNumber, \r
+ optimizerEstimatedRowCount, optimizerEstimatedCost);\r
+ this.source = source;\r
+ this.sourceRowWidth = sourceRowWidth;\r
+ keepAfterCommit = activation.getResultSetHoldability();\r
+ maxRows = activation.getMaxRows();\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(maxRows != -1,\r
+ "maxRows not expected to be -1");\r
+ }\r
+\r
+ constructorTime += getElapsedMillis(beginTime);\r
+\r
+ positionInHashTable = new SQLInteger();\r
+ needsRepositioning = false;\r
+ if (isForUpdate()) {\r
+ target = ((CursorActivation)activation).getTargetResultSet();\r
+ extraColumns = LAST_EXTRA_COLUMN + 1;\r
+ } else {\r
+ target = null;\r
+ extraColumns = 1;\r
+ }\r
+ }\r
+\r
+\r
+ //\r
+ // ResultSet interface (leftover from NoPutResultSet)\r
+ //\r
+\r
+ /**\r
+ * open a scan on the source. 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 \r
+ */\r
+ public void openCore() throws StandardException\r
+ {\r
+ beginTime = getCurrentTimeMillis();\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT( ! isOpen, "ScrollInsensitiveResultSet already open");\r
+\r
+ source.openCore();\r
+ isOpen = true;\r
+ numOpens++;\r
+\r
+ /* Create the hash table. We pass\r
+ * null in as the row source as we will\r
+ * build the hash table on demand as\r
+ * the user scrolls.\r
+ * The 1st column, the position in the\r
+ * scan, will be the key column.\r
+ */\r
+ final int[] keyCols = new int[] { 0 };\r
+ \r
+ /* We don't use the optimizer row count for this because it could be\r
+ * wildly pessimistic. We only use Hash tables when the optimizer row count\r
+ * is within certain bounds. We have no alternative for scrolling insensitive \r
+ * cursors so we'll just trust that it will fit.\r
+ * We need BackingStoreHashtable to actually go to disk when it doesn't fit.\r
+ * This is a known limitation.\r
+ */\r
+ ht = new BackingStoreHashtable(getTransactionController(),\r
+ null,\r
+ keyCols,\r
+ false,\r
+ -1, // don't trust optimizer row count\r
+ HashScanResultSet.DEFAULT_MAX_CAPACITY,\r
+ HashScanResultSet.DEFAULT_INITIAL_CAPACITY,\r
+ HashScanResultSet.DEFAULT_MAX_CAPACITY,\r
+ false,\r
+ keepAfterCommit);\r
+\r
+ // When re-using language result sets (DERBY-827) we need to\r
+ // reset some member variables to the value they would have\r
+ // had in a newly constructed object.\r
+ lastPosition = 0;\r
+ needsRepositioning = false;\r
+ numFromHashTable = 0;\r
+ numToHashTable = 0;\r
+ positionInSource = 0;\r
+ seenFirst = false;\r
+ seenLast = false;\r
+ maxRows = activation.getMaxRows();\r
+\r
+ openTime += getElapsedMillis(beginTime);\r
+ setBeforeFirstRow();\r
+ }\r
+\r
+ /**\r
+ * reopen 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 if cursor finished.\r
+ */\r
+ public void reopenCore() throws StandardException \r
+ {\r
+ boolean constantEval = true;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(isOpen, "ScrollInsensitiveResultSet already open");\r
+ SanityManager.THROWASSERT(\r
+ "reopenCore() not expected to be called");\r
+ }\r
+ setBeforeFirstRow();\r
+ }\r
+\r
+ /**\r
+ * Returns the row at the absolute position from the query, \r
+ * and returns NULL when there is no such position.\r
+ * (Negative position means from the end of the result set.)\r
+ * Moving the cursor to an invalid position leaves the cursor\r
+ * positioned either before the first row (negative position)\r
+ * or after the last row (positive position).\r
+ * NOTE: An exception will be thrown on 0.\r
+ *\r
+ * @param row The position.\r
+ * @return The row at the absolute position, or NULL if no such position.\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow getAbsoluteRow(int row) throws StandardException\r
+ {\r
+ if ( ! isOpen ) \r
+ {\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "absolute");\r
+ }\r
+\r
+ attachStatementContext();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!isTopResultSet)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ this + "expected to be the top ResultSet");\r
+ }\r
+ }\r
+\r
+ // Absolute 0 is defined to be before first!\r
+ if (row == 0)\r
+ {\r
+ setBeforeFirstRow();\r
+ return null;\r
+ }\r
+\r
+ if (seenLast && row > lastPosition) {\r
+ return setAfterLastRow();\r
+ } \r
+\r
+ if (row > 0)\r
+ {\r
+ // position is from the start of the result set\r
+ if (row <= positionInSource)\r
+ {\r
+ // We've already seen the row before\r
+ return getRowFromHashTable(row);\r
+ }\r
+ \r
+ /* We haven't seen the row yet, scan until we find\r
+ * it or we get to the end.\r
+ */\r
+ int diff = row - positionInSource;\r
+ ExecRow result = null;\r
+ while (diff > 0)\r
+ {\r
+ if ((result = getNextRowFromSource()) != null)\r
+ {\r
+ diff--;\r
+ }\r
+ else\r
+ {\r
+ break;\r
+ }\r
+ }\r
+ if (result != null) {\r
+ result = getRowFromHashTable(row);\r
+ }\r
+ currentRow = result;\r
+ return result;\r
+ }\r
+ else if (row < 0)\r
+ {\r
+ // position is from the end of the result set\r
+\r
+ // Get the last row, if we haven't already\r
+ if (!seenLast)\r
+ {\r
+ getLastRow();\r
+ }\r
+\r
+ // Note, for negative values position is from beyond the end\r
+ // of the result set, e.g. absolute(-1) points to the last row\r
+ int beyondResult = lastPosition + 1;\r
+ if (beyondResult + row > 0)\r
+ {\r
+ // valid row\r
+ return getRowFromHashTable(beyondResult + row);\r
+ }\r
+ else\r
+ {\r
+ // position before the beginning of the result set\r
+ return setBeforeFirstRow();\r
+ }\r
+ }\r
+ \r
+ currentRow = null;\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Returns the row at the relative position from the current\r
+ * cursor position, and returns NULL when there is no such position.\r
+ * (Negative position means toward the beginning of the result set.)\r
+ * Moving the cursor to an invalid position leaves the cursor\r
+ * positioned either before the first row (negative position)\r
+ * or after the last row (positive position).\r
+ * NOTE: 0 is valid.\r
+ * NOTE: An exception is thrown if the cursor is not currently\r
+ * positioned on a row.\r
+ *\r
+ * @param row The position.\r
+ * @return The row at the relative position, or NULL if no such position.\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow getRelativeRow(int row) throws StandardException\r
+ {\r
+ if ( ! isOpen ) \r
+ {\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "relative");\r
+ }\r
+\r
+ attachStatementContext();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!isTopResultSet)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ this + "expected to be the top ResultSet");\r
+ }\r
+ }\r
+\r
+ // Return the current row for 0\r
+ if (row == 0)\r
+ {\r
+ if (beforeFirst || afterLast || currentPosition==0) {\r
+ return null;\r
+ } else {\r
+ return getRowFromHashTable(currentPosition);\r
+ }\r
+ }\r
+ else if (row > 0)\r
+ {\r
+ return getAbsoluteRow(currentPosition + row);\r
+ }\r
+ else\r
+ {\r
+ // row < 0\r
+ if (currentPosition + row < 0)\r
+ {\r
+ return setBeforeFirstRow();\r
+ }\r
+ return getAbsoluteRow(currentPosition + row);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets the current position to before the first row and returns NULL\r
+ * because there is no current row.\r
+ *\r
+ * @return NULL.\r
+ *\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow setBeforeFirstRow() \r
+ {\r
+ currentPosition = 0;\r
+ beforeFirst = true;\r
+ afterLast = false;\r
+ currentRow = null;\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Returns the first row from the query, and returns NULL when there\r
+ * are no rows.\r
+ *\r
+ * @return The first row, or NULL if no rows.\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow getFirstRow() \r
+ throws StandardException\r
+ {\r
+ if ( ! isOpen ) \r
+ {\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "first");\r
+ }\r
+\r
+ /* Get the row from the hash table if\r
+ * we have already seen it before.\r
+ */\r
+ if (seenFirst)\r
+ {\r
+ return getRowFromHashTable(1);\r
+ }\r
+\r
+ attachStatementContext();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!isTopResultSet)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ this + "expected to be the top ResultSet");\r
+ }\r
+ }\r
+\r
+ return getNextRowCore();\r
+ }\r
+\r
+ /**\r
+ *\r
+ * @exception StandardException thrown on failure \r
+ */\r
+ public ExecRow getNextRowCore() throws StandardException\r
+ {\r
+ ExecRow result = null;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+ if (!isOpen)\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");\r
+\r
+ if (seenLast && currentPosition == lastPosition) {\r
+ return setAfterLastRow();\r
+ }\r
+\r
+ /* Should we get the next row from the source or the hash table? */\r
+ if (currentPosition == positionInSource)\r
+ {\r
+ /* Current position is same as position in source.\r
+ * Get row from the source.\r
+ */\r
+ result = getNextRowFromSource();\r
+ if (result !=null) {\r
+ result = getRowFromHashTable(currentPosition);\r
+ }\r
+ }\r
+ else if (currentPosition < positionInSource)\r
+ {\r
+ /* Current position is before position in source.\r
+ * Get row from the hash table.\r
+ */\r
+ result = getRowFromHashTable(currentPosition + 1);\r
+ }\r
+ else\r
+ {\r
+ result = null;\r
+ }\r
+\r
+ if (result != null)\r
+ {\r
+ rowsSeen++;\r
+ afterLast = false;\r
+ }\r
+\r
+ currentRow = result;\r
+ setCurrentRow(currentRow);\r
+ beforeFirst = false;\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * Returns the previous row from the query, and returns NULL when there\r
+ * are no more previous rows.\r
+ *\r
+ * @return The previous row, or NULL if no more previous rows.\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow getPreviousRow() \r
+ throws StandardException\r
+ {\r
+ if ( ! isOpen ) \r
+ {\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!isTopResultSet)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ this + "expected to be the top ResultSet");\r
+ }\r
+ }\r
+\r
+ /* No row if we are positioned before the first row\r
+ * or the result set is empty.\r
+ */\r
+ if (beforeFirst || currentPosition == 0)\r
+ {\r
+ currentRow = null;\r
+ return null;\r
+ }\r
+\r
+ // Get the last row, if we are after it\r
+ if (afterLast)\r
+ {\r
+ // Special case for empty tables\r
+ if (lastPosition == 0)\r
+ {\r
+ afterLast = false;\r
+ beforeFirst = false;\r
+ currentRow = null;\r
+ return null;\r
+ }\r
+ else\r
+ {\r
+ return getRowFromHashTable(lastPosition);\r
+ }\r
+ }\r
+\r
+ // Move back 1\r
+ currentPosition--;\r
+ if (currentPosition == 0)\r
+ {\r
+ setBeforeFirstRow();\r
+ return null;\r
+ }\r
+ return getRowFromHashTable(currentPosition);\r
+ }\r
+\r
+ /**\r
+ * Returns the last row from the query, and returns NULL when there\r
+ * are no rows.\r
+ *\r
+ * @return The last row, or NULL if no rows.\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow getLastRow()\r
+ throws StandardException\r
+ { \r
+ if ( ! isOpen ) \r
+ {\r
+ throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");\r
+ }\r
+ \r
+ if (!seenLast) \r
+ {\r
+ attachStatementContext();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!isTopResultSet)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ this + "expected to be the top ResultSet");\r
+ }\r
+ }\r
+ \r
+ /* Scroll to the end, filling the hash table as\r
+ * we scroll, and return the last row that we find.\r
+ */\r
+ ExecRow result = null;\r
+ while ((result = getNextRowFromSource()) != null);\r
+ }\r
+ \r
+ if (SanityManager.DEBUG && !seenLast)\r
+ {\r
+ SanityManager.THROWASSERT(this + "expected to have seen last");\r
+ }\r
+ \r
+ beforeFirst = false;\r
+ afterLast = false;\r
+\r
+ // Special case if table is empty\r
+ if (lastPosition == 0)\r
+ {\r
+ currentRow = null;\r
+ return null;\r
+ }\r
+ else\r
+ {\r
+ return getRowFromHashTable(lastPosition);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets the current position to after the last row and returns NULL\r
+ * because there is no current row.\r
+ *\r
+ * @return NULL.\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ * @see org.apache.derby.iapi.sql.Row\r
+ */\r
+ public ExecRow setAfterLastRow() \r
+ throws StandardException\r
+ {\r
+ if (! seenLast)\r
+ {\r
+ getLastRow();\r
+ }\r
+ if (lastPosition == 0) {\r
+ // empty rs special case\r
+ currentPosition = 0;\r
+ afterLast = false;\r
+ } else {\r
+ currentPosition = lastPosition + 1;\r
+ afterLast = true;\r
+ }\r
+\r
+ beforeFirst = false;\r
+ currentRow = null;\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Determine if the cursor is before the first row in the result \r
+ * set. \r
+ *\r
+ * @return true if before the first row, false otherwise. Returns\r
+ * false when the result set contains no rows.\r
+ * @exception StandardException Thrown on error.\r
+ */\r
+ public boolean checkRowPosition(int isType) throws StandardException\r
+ {\r
+ switch (isType) {\r
+ case ISBEFOREFIRST:\r
+\r
+ if (! beforeFirst)\r
+ {\r
+ return false;\r
+ }\r
+\r
+ // Spec says to return false if result set is empty\r
+ if (seenFirst)\r
+ {\r
+ return true;\r
+ }\r
+ else\r
+ {\r
+ ExecRow firstRow = getFirstRow();\r
+ if (firstRow == null)\r
+ {\r
+ // ResultSet is empty\r
+ return false;\r
+ }\r
+ else\r
+ {\r
+ // ResultSet is not empty - reset position\r
+ getPreviousRow();\r
+ return true;\r
+ }\r
+ }\r
+ case ISFIRST:\r
+ return (currentPosition == 1);\r
+ case ISLAST:\r
+ if (beforeFirst || afterLast || currentPosition==0 ||\r
+ currentPosition<positionInSource)\r
+ {\r
+ return false;\r
+ } \r
+ \r
+ /* If we have seen the last row, we can tell if we are \r
+ * on it by comparing currentPosition with lastPosition.\r
+ * Otherwise, we check if there is a next row.\r
+ */\r
+ if (seenLast)\r
+ {\r
+ return (currentPosition == lastPosition);\r
+ }\r
+ else\r
+ {\r
+ final int savePosition = currentPosition;\r
+ final boolean retval = (getNextRowFromSource() == null);\r
+ getRowFromHashTable(savePosition);\r
+ return retval;\r
+ }\r
+ case ISAFTERLAST:\r
+ return afterLast;\r
+ default:\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns the row number of the current row. Row\r
+ * numbers start from 1 and go to 'n'. Corresponds\r
+ * to row numbering used to position current row\r
+ * in the result set (as per JDBC).\r
+ *\r
+ * @return the row number, or 0 if not on a row\r
+ *\r
+ */\r
+ public int getRowNumber()\r
+ {\r
+ return currentRow == null ? 0 : currentPosition;\r
+ }\r
+\r
+ /* Get the next row from the source ResultSet tree and insert into the hash table */\r
+ private ExecRow getNextRowFromSource() throws StandardException\r
+ {\r
+ ExecRow sourceRow = null;\r
+ ExecRow result = null;\r
+\r
+ /* Don't give back more rows than requested */\r
+ if (maxRows > 0 && maxRows == positionInSource)\r
+ {\r
+ seenLast = true;\r
+ lastPosition = positionInSource;\r
+ afterLast = true;\r
+ return null;\r
+ }\r
+\r
+\r
+ if (needsRepositioning) {\r
+ positionInLastFetchedRow();\r
+ needsRepositioning = false;\r
+ }\r
+ sourceRow = source.getNextRowCore();\r
+\r
+ if (sourceRow != null)\r
+ {\r
+ seenFirst = true;\r
+ beforeFirst = false;\r
+\r
+ long beginTCTime = getCurrentTimeMillis();\r
+ /* If this is the first row from the source then we create a new row\r
+ * for use when fetching from the hash table.\r
+ */\r
+ if (resultRow == null)\r
+ {\r
+ resultRow = activation.getExecutionFactory().getValueRow(sourceRowWidth);\r
+ }\r
+\r
+ positionInSource++;\r
+ currentPosition = positionInSource;\r
+\r
+ RowLocation rowLoc = null;\r
+ if (source.isForUpdate()) {\r
+ rowLoc = ((CursorResultSet)source).getRowLocation();\r
+ }\r
+\r
+ addRowToHashTable(sourceRow, currentPosition, rowLoc, false);\r
+\r
+ }\r
+ // Remember whether or not we're past the end of the table\r
+ else\r
+ {\r
+ if (! seenLast)\r
+ {\r
+ lastPosition = positionInSource;\r
+ }\r
+ seenLast = true;\r
+ // Special case for empty table (afterLast is never true)\r
+ if (positionInSource == 0)\r
+ {\r
+ afterLast = false;\r
+ }\r
+ else\r
+ {\r
+ afterLast = true;\r
+ currentPosition = positionInSource + 1;\r
+ }\r
+ }\r
+\r
+ return sourceRow;\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
+ currentRow = null;\r
+ source.close();\r
+\r
+ if (ht != null)\r
+ {\r
+ ht.close();\r
+ ht = null;\r
+ }\r
+\r
+ super.close();\r
+ }\r
+ else\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.DEBUG("CloseRepeatInfo","Close of ScrollInsensitiveResultSet repeated");\r
+ setBeforeFirstRow();\r
+\r
+ closeTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ public void finish() throws StandardException\r
+ {\r
+ source.finish();\r
+ finishAndRTS();\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
+ /**\r
+ * Gets information from its source. We might want\r
+ * to have this take a CursorResultSet in its constructor some day,\r
+ * instead of doing a cast here?\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the row location of the current cursor row.\r
+ *\r
+ * @exception StandardException thrown on failure \r
+ */\r
+ public RowLocation getRowLocation() throws StandardException \r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(source instanceof CursorResultSet, "source not CursorResultSet");\r
+ return ( (CursorResultSet)source ).getRowLocation();\r
+ }\r
+\r
+ /**\r
+ * Gets information from last getNextRow call.\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the last row returned.\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
+ if (isForUpdate() && isDeleted()) {\r
+ return null;\r
+ } else {\r
+ return currentRow;\r
+ }\r
+ }\r
+\r
+ //\r
+ // class implementation\r
+ //\r
+\r
+ /**\r
+ * Add a row to the backing hash table, keyed on position.\r
+ * When a row gets updated when using scrollable insensitive updatable\r
+ * result sets, the old entry for the row will be deleted from the hash \r
+ * table and this method will be called to add the new values for the row\r
+ * to the hash table, with the parameter rowUpdated = true so as to mark \r
+ * the row as updated. The latter is done in order to implement \r
+ * detectability of own changes for result sets of this type.\r
+ *\r
+ * @param sourceRow The row to add.\r
+ * @param position The key\r
+ * @param rowLoc The rowLocation of the row to add.\r
+ * @param rowUpdated Indicates whether the row has been updated.\r
+ *\r
+ */\r
+ private void addRowToHashTable(ExecRow sourceRow, int position,\r
+ RowLocation rowLoc, boolean rowUpdated)\r
+ throws StandardException\r
+ {\r
+ DataValueDescriptor[] hashRowArray = new \r
+ DataValueDescriptor[sourceRowWidth + extraColumns];\r
+ // 1st element is the key\r
+ hashRowArray[0] = new SQLInteger(position);\r
+ if (isForUpdate()) {\r
+ hashRowArray[POS_ROWLOCATION] = rowLoc.getClone();\r
+ hashRowArray[POS_ROWDELETED] = new SQLBoolean(false);\r
+ hashRowArray[POS_ROWUPDATED] = new SQLBoolean(rowUpdated);\r
+ }\r
+\r
+ /* Copy rest of elements from sourceRow.\r
+ * NOTE: We need to clone the source row\r
+ * and we do our own cloning since the 1st column\r
+ * is not a wrapper.\r
+ */\r
+ DataValueDescriptor[] sourceRowArray = sourceRow.getRowArray();\r
+\r
+ System.arraycopy(sourceRowArray, 0, hashRowArray, extraColumns, \r
+ sourceRowArray.length);\r
+\r
+ ht.putRow(true, hashRowArray);\r
+\r
+ numToHashTable++;\r
+ }\r
+\r
+ /**\r
+ * Get the row at the specified position\r
+ * from the hash table.\r
+ *\r
+ * @param position The specified position.\r
+ *\r
+ * @return The row at that position.\r
+ *\r
+ * @exception StandardException thrown on failure \r
+ */\r
+ private ExecRow getRowFromHashTable(int position)\r
+ throws StandardException\r
+ {\r
+\r
+ // Get the row from the hash table\r
+ positionInHashTable.setValue(position);\r
+ DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(hashRowArray != null,\r
+ "hashRowArray expected to be non-null");\r
+ }\r
+ // Copy out the Object[] without the position.\r
+ DataValueDescriptor[] resultRowArray = new \r
+ DataValueDescriptor[hashRowArray.length - extraColumns];\r
+ System.arraycopy(hashRowArray, extraColumns, resultRowArray, 0, \r
+ resultRowArray.length);\r
+\r
+ resultRow.setRowArray(resultRowArray);\r
+\r
+ // Reset the current position to the user position\r
+ currentPosition = position;\r
+\r
+ numFromHashTable++;\r
+\r
+ if (resultRow != null)\r
+ {\r
+ beforeFirst = false;\r
+ afterLast = false;\r
+ }\r
+\r
+ if (isForUpdate()) {\r
+ RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];\r
+ // Keep source and target with the same currentRow\r
+ ((NoPutResultSet)target).setCurrentRow(resultRow);\r
+ ((NoPutResultSet)target).positionScanAtRowLocation(rowLoc);\r
+ needsRepositioning = true;\r
+ }\r
+ \r
+ setCurrentRow(resultRow);\r
+\r
+ return resultRow;\r
+ }\r
+ \r
+ /**\r
+ * Get the row data at the specified position \r
+ * from the hash table.\r
+ *\r
+ * @param position The specified position.\r
+ *\r
+ * @return The row data at that position.\r
+ *\r
+ * @exception StandardException thrown on failure \r
+ */\r
+ private DataValueDescriptor[] getRowArrayFromHashTable(int position)\r
+ throws StandardException\r
+ {\r
+ positionInHashTable.setValue(position);\r
+ final DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+ \r
+ // Copy out the Object[] without the position.\r
+ final DataValueDescriptor[] resultRowArray = new \r
+ DataValueDescriptor[hashRowArray.length - extraColumns];\r
+ System.arraycopy(hashRowArray, extraColumns, resultRowArray, 0, \r
+ resultRowArray.length);\r
+ return resultRowArray;\r
+ }\r
+\r
+ /**\r
+ * Positions the cursor in the last fetched row. This is done before\r
+ * navigating to a row that has not previously been fetched, so that\r
+ * getNextRowCore() will re-start from where it stopped.\r
+ */\r
+ private void positionInLastFetchedRow() throws StandardException {\r
+ if (positionInSource > 0) {\r
+ positionInHashTable.setValue(positionInSource);\r
+ DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+ RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];\r
+ ((NoPutResultSet)target).positionScanAtRowLocation(rowLoc);\r
+ currentPosition = positionInSource;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#updateRow\r
+ *\r
+ * Sets the updated column of the hash table to true and updates the row\r
+ * in the hash table with the new values for the row.\r
+ */\r
+ public void updateRow(ExecRow row) throws StandardException {\r
+ ExecRow newRow = row;\r
+ boolean undoProjection = false;\r
+ \r
+ if (source instanceof ProjectRestrictResultSet) {\r
+ newRow = ((ProjectRestrictResultSet)source).\r
+ doBaseRowProjection(row);\r
+ undoProjection = true;\r
+ }\r
+ positionInHashTable.setValue(currentPosition);\r
+ DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+ RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];\r
+ ht.remove(new SQLInteger(currentPosition));\r
+ addRowToHashTable(newRow, currentPosition, rowLoc, true);\r
+ \r
+ // Modify row to refer to data in the BackingStoreHashtable.\r
+ // This allows reading of data which goes over multiple pages\r
+ // when doing the actual update (LOBs). Putting columns of\r
+ // type SQLBinary to disk, has destructive effect on the columns,\r
+ // and they need to be re-read. That is the reason this is needed.\r
+ if (undoProjection) {\r
+ \r
+ final DataValueDescriptor[] newRowData = newRow.getRowArray();\r
+ \r
+ // Array of original position in row\r
+ final int[] origPos =((ProjectRestrictResultSet)source).\r
+ getBaseProjectMapping(); \r
+ \r
+ // We want the row to contain data backed in BackingStoreHashtable\r
+ final DataValueDescriptor[] backedData = \r
+ getRowArrayFromHashTable(currentPosition);\r
+ \r
+ for (int i=0; i<origPos.length; i++) {\r
+ if (origPos[i]>=0) {\r
+ row.setColumn(origPos[i], backedData[i]);\r
+ }\r
+ }\r
+ } else {\r
+ row.setRowArray(getRowArrayFromHashTable(currentPosition));\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#markRowAsDeleted\r
+ *\r
+ * Sets the deleted column of the hash table to true in the current row.\r
+ */\r
+ public void markRowAsDeleted() throws StandardException {\r
+ positionInHashTable.setValue(currentPosition);\r
+ DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+ RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];\r
+ ht.remove(new SQLInteger(currentPosition));\r
+ ((SQLBoolean)hashRowArray[POS_ROWDELETED]).setValue(true);\r
+ // Set all columns to NULL, the row is now a placeholder\r
+ for (int i=extraColumns; i<hashRowArray.length; i++) {\r
+ hashRowArray[i].setToNull();\r
+ }\r
+\r
+ ht.putRow(true, hashRowArray);\r
+ }\r
+\r
+ /**\r
+ * Returns TRUE if the row was been deleted within the transaction,\r
+ * otherwise returns FALSE\r
+ *\r
+ * @return True if the row has been deleted, otherwise false\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public boolean isDeleted() throws StandardException {\r
+ if (currentPosition <= positionInSource && currentPosition > 0) {\r
+ positionInHashTable.setValue(currentPosition);\r
+ DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+ return hashRowArray[POS_ROWDELETED].getBoolean();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Returns TRUE if the row was been updated within the transaction,\r
+ * otherwise returns FALSE\r
+ *\r
+ * @return True if the row has been deleted, otherwise false\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public boolean isUpdated() throws StandardException {\r
+ if (currentPosition <= positionInSource && currentPosition > 0) {\r
+ positionInHashTable.setValue(currentPosition);\r
+ DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) \r
+ ht.get(positionInHashTable);\r
+ return hashRowArray[POS_ROWUPDATED].getBoolean();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ public boolean isForUpdate() {\r
+ return source.isForUpdate();\r
+ }\r
+\r
+}\r