You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2012/09/18 14:24:10 UTC

svn commit: r1387111 - in /db/derby/code/branches/10.9: ./ java/engine/org/apache/derby/impl/sql/execute/ java/testing/org/apache/derbyTesting/functionTests/tests/lang/

Author: kahatlen
Date: Tue Sep 18 12:24:10 2012
New Revision: 1387111

URL: http://svn.apache.org/viewvc?rev=1387111&view=rev
Log:
DERBY-5425: Updateable holdable ResultSet terminates early after 65638 updates

Merged revision 1359052 from trunk.

Modified:
    db/derby/code/branches/10.9/   (props changed)
    db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java
    db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java
    db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java
    db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java
    db/derby/code/branches/10.9/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java

Propchange: db/derby/code/branches/10.9/
------------------------------------------------------------------------------
  Merged /db/derby/code/trunk:r1359052

Modified: db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java?rev=1387111&r1=1387110&r2=1387111&view=diff
==============================================================================
--- db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java (original)
+++ db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java Tue Sep 18 12:24:10 2012
@@ -30,15 +30,12 @@ import org.apache.derby.iapi.sql.execute
 import org.apache.derby.iapi.sql.execute.NoPutResultSet;
 
 import org.apache.derby.iapi.sql.Activation;
-import org.apache.derby.iapi.sql.ResultSet;
-import org.apache.derby.iapi.sql.PreparedStatement;
 
 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
 
 import org.apache.derby.iapi.types.RowLocation;
 
 import org.apache.derby.iapi.services.sanity.SanityManager;
-import org.apache.derby.iapi.sql.depend.DependencyManager;
 import org.apache.derby.iapi.sql.execute.RowChanger;
 
 /**
@@ -149,17 +146,6 @@ class CurrentOfResultSet extends NoPutRe
 					if (scan.indexCols != null && currentRow != null)
 						currentRow = getSparseRow(currentRow, scan.indexCols);
 				}
-				/* If we are updating rows from cached RIDs, we should compare with forward-most
-				 * scan key when deciding whether to add RID to hash table or not.
-				 */
-				TableScanResultSet scan = (TableScanResultSet) activation.getForUpdateIndexScan();
-				if (scan != null)
-				{
-					if (target instanceof IndexRowToBaseRowResultSet)
-						scan.compareToLastKey = ((IndexRowToBaseRowResultSet) target).currentRowPrescanned;
-					else if (target instanceof TableScanResultSet)
-						scan.compareToLastKey = ((TableScanResultSet) target).currentRowPrescanned;
-				}
 
 				// REMIND: verify the row is still there
 				// at present we get an ugly exception from the store,

Modified: db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java?rev=1387111&r1=1387110&r2=1387111&view=diff
==============================================================================
--- db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java (original)
+++ db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java Tue Sep 18 12:24:10 2012
@@ -79,9 +79,6 @@ class IndexRowToBaseRowResultSet extends
 	/* Run time statistics variables */
 	public long restrictionTime;
 
-	protected boolean currentRowPrescanned;
-	private boolean sourceIsForUpdateIndexScan;
-
     //
     // class interface
     //
@@ -200,9 +197,6 @@ class IndexRowToBaseRowResultSet extends
 		beginTime = getCurrentTimeMillis();
 
 		source.openCore();
-		if ((source instanceof TableScanResultSet) && 
-			((TableScanResultSet) source).indexCols != null)
-			sourceIsForUpdateIndexScan = true;
 
 		/* Get a ConglomerateController for the base conglomerate 
 		 * NOTE: We only need to acquire locks on the data pages when
@@ -314,55 +308,6 @@ class IndexRowToBaseRowResultSet extends
 			throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
 		}
 
-		/* beetle 3865, updateable cursor using index.  When in-memory hash table was full, we
-		 * read forward and saved future row id's in a virtual-memory-like temp table.  So if
-		 * we have rid's saved, and we are here, it must be non-covering index.  Intercept it
-		 * here, so that we don't have to go to underlying index scan.  We get both heap cols
-		 * and index cols together here for better performance.
-		 */
-		if (sourceIsForUpdateIndexScan && ((TableScanResultSet) source).futureForUpdateRows != null)
-		{
-			currentRowPrescanned = false;
-			TableScanResultSet src = (TableScanResultSet) source;
-
-			if (src.futureRowResultSet == null)
-			{
-				src.futureRowResultSet = (TemporaryRowHolderResultSet) src.futureForUpdateRows.getResultSet();
-				src.futureRowResultSet.openCore();
-			}
-
-			ExecRow ridRow = src.futureRowResultSet.getNextRowCore();
-
-			currentRow = null;
-
-			if (ridRow != null)
-			{
-				/* To maximize performance, we only use virtual memory style heap, no
-				 * position index is ever created.  And we save and retrieve rows from the
-				 * in-memory part of the heap as much as possible.  We can also insert after
-				 * we start retrieving, the assumption is that we delete the current row right
-				 * after we retrieve it.
-				 */
-				src.futureRowResultSet.deleteCurrentRow();
-				baseRowLocation = (RowLocation) ridRow.getColumn(1);
-               	baseCC.fetch(
-                  	      baseRowLocation, compactRow.getRowArray(), accessedAllCols);
-
-				currentRow = compactRow;
-				currentRowPrescanned = true;
-			}
-			else if (src.sourceDrained)
-				currentRowPrescanned = true;
-
-			if (currentRowPrescanned)
-			{
-				setCurrentRow(currentRow);
-
-				nextTime += getElapsedMillis(beginTime);
-	 	   		return currentRow;
-			}
-		}
-
 		/* Loop until we get a row from the base page that qualifies or
 		 * there's no more rows from the index that qualify. (If the RID
 		 * returned by the index does not qualify, then we have to go back
@@ -576,9 +521,6 @@ class IndexRowToBaseRowResultSet extends
 					"IndexRowToBaseRowResultSet is expected to be open");
 		}
 
-		if (currentRowPrescanned)
-			return currentRow;
-
 		/* Nothing to do if we're not currently on a row */
 		if (currentRow == null)
 		{

Modified: db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java?rev=1387111&r1=1387110&r2=1387111&view=diff
==============================================================================
--- db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java (original)
+++ db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java Tue Sep 18 12:24:10 2012
@@ -21,13 +21,11 @@
 
 package org.apache.derby.impl.sql.execute;
 
-import java.util.Hashtable;
 import java.util.Properties;
 
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.reference.SQLState;
 import org.apache.derby.iapi.services.i18n.MessageService;
-import org.apache.derby.iapi.services.io.FormatableBitSet;
 import org.apache.derby.iapi.services.loader.GeneratedMethod;
 import org.apache.derby.iapi.services.sanity.SanityManager;
 import org.apache.derby.iapi.sql.Activation;
@@ -35,7 +33,7 @@ import org.apache.derby.iapi.sql.execute
 import org.apache.derby.iapi.sql.execute.ExecIndexRow;
 import org.apache.derby.iapi.sql.execute.ExecRow;
 import org.apache.derby.iapi.sql.execute.NoPutResultSet;
-import org.apache.derby.iapi.sql.execute.TemporaryRowHolder;
+import org.apache.derby.iapi.store.access.BackingStoreHashtable;
 import org.apache.derby.iapi.store.access.ConglomerateController;
 import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
 import org.apache.derby.iapi.store.access.Qualifier;
@@ -98,22 +96,14 @@ class TableScanResultSet extends ScanRes
 
 	private long estimatedRowCount;
 
-	/* Following fields are used by beetle 3865, updateable cursor using index. "past2FutureTbl"
-	 * is a hash table containing updated rows that are thrown into future direction of the
-	 * index scan and as a result we'll hit it again but should skip it.  If this hash table
-	 * is full, we scan forward and have a virtual memory style temp heap holding future row
-	 * id's.
-	 */
-	protected Hashtable past2FutureTbl;
-	protected TemporaryRowHolder futureForUpdateRows;  //tmp table for materialized rids
-	protected TemporaryRowHolderResultSet futureRowResultSet;	//result set for reading from above
-	protected boolean skipFutureRowHolder;		//skip reading rows from above
-	protected boolean sourceDrained;			//all row ids materialized
-	protected boolean currentRowPrescanned;	//got a row from above tmp table
-	protected boolean compareToLastKey;		//see comments in UpdateResultSet
-	protected ExecRow lastCursorKey;
-	private ExecRow sparseRow;				//sparse row in heap column order
-	private FormatableBitSet sparseRowMap;			//which columns to read
+    /**
+     * This field is used by beetle 3865, updateable cursor using index. It
+     * is a hash table containing updated rows that are thrown into future
+     * direction of the index scan, and as a result we'll hit it again but
+     * should skip it. The hash table will spill to disk if it grows too big
+     * to be kept in memory.
+     */
+    protected BackingStoreHashtable past2FutureTbl;
 
 	// For Scrollable insensitive updatable result sets, only qualify a row the 
 	// first time it's been read, since an update can change a row so that it 
@@ -463,35 +453,6 @@ class TableScanResultSet extends ScanRes
 	}
 
 	/**
-     * Check and make sure sparse heap row and accessed bit map are created.
-	 * beetle 3865, update cursor using index.
-	 *
-	 * @exception StandardException thrown on failure
-	 */
-	private void getSparseRowAndMap() throws StandardException
-	{
-		int numCols = 1, colPos;
-		for (int i = 0; i < indexCols.length; i++)
-		{
-			colPos = (indexCols[i] > 0) ? indexCols[i] : -indexCols[i];
-			if (colPos > numCols)
-				numCols = colPos;
-		}
-		sparseRow = new ValueRow(numCols);
-		sparseRowMap = new FormatableBitSet(numCols);
-		for (int i = 0; i < indexCols.length; i++)
-		{
-			if (accessedCols.get(i))
-			{
-				colPos = (indexCols[i] > 0) ? indexCols[i] : -indexCols[i];
-				sparseRow.setColumn(colPos, candidate.getColumn(i + 1));
-				sparseRowMap.set(colPos - 1);
-			}
-		}
-	}
-		
-
-	/**
      * Return the next row (if any) from the scan (if open).
 	 *
 	 * @exception StandardException thrown on failure to get next row
@@ -510,59 +471,6 @@ class TableScanResultSet extends ScanRes
 
 		ExecRow result = null;
 
-		/* beetle 3865, updateable cursor using index. We first saved updated rows with new value
-		 * falling into future direction of index scan in hash table, if it's full, we scanned
-		 * forward and saved future row ids in a virtual mem heap.
-		 */
-		if (futureForUpdateRows != null)
-		{
-			currentRowPrescanned = false;
-			if (! skipFutureRowHolder)
-			{
-				if (futureRowResultSet == null)
-				{
-					futureRowResultSet = (TemporaryRowHolderResultSet) futureForUpdateRows.getResultSet();
-					futureRowResultSet.openCore();
-				}
-
-				ExecRow ridRow = futureRowResultSet.getNextRowCore();
-
-				if (ridRow != null)
-				{
-					/* to boost performance, we used virtual mem heap, and we can insert after
-					 * we start retrieving results.  The assumption is to
-					 * delete current row right after we retrieve it.
-					 */
-					futureRowResultSet.deleteCurrentRow();
-					RowLocation rl = (RowLocation) ridRow.getColumn(1);
-					ConglomerateController baseCC = activation.getHeapConglomerateController();
-					if (sparseRow == null)
-						getSparseRowAndMap();
-            	   	baseCC.fetch(
-        	 	      	      rl, sparseRow.getRowArray(), sparseRowMap);
-                    RowLocation rl2 = (RowLocation) rl.cloneValue(false);
-					currentRow.setColumn(currentRow.nColumns(), rl2);
-					candidate.setColumn(candidate.nColumns(), rl2);		// have to be consistent!
-
-					result = currentRow;
-					currentRowPrescanned = true;
-				}
-				else if (sourceDrained)
-				{
-					currentRowPrescanned = true;
-					currentRow = null;
-				}
-
-				if (currentRowPrescanned)
-				{
-					setCurrentRow(result);
-
-					nextTime += getElapsedMillis(beginTime);
-	 		   		return result;
-				}
-			}
-		}
-
 	    if ( isOpen  && !nextDone)
 	    {
 			/* Only need to do 1 next per scan
@@ -707,8 +615,11 @@ class TableScanResultSet extends ScanRes
 					activation.clearHeapConglomerateController();
 				}
 			}
-			if (futureRowResultSet != null)
-				futureRowResultSet.close();
+
+            if (past2FutureTbl != null)
+            {
+                past2FutureTbl.close();
+            }
 	    }
 		else
 			if (SanityManager.DEBUG)
@@ -828,9 +739,6 @@ class TableScanResultSet extends ScanRes
 		if (SanityManager.DEBUG)
 			SanityManager.ASSERT(isOpen, "TSRS expected to be open");
 
-		if (currentRowPrescanned)
-			return currentRow;
-
 		/* Nothing to do if we're not currently on a row or
 		 * if the current row get deleted out from under us
 		 * or if there is no current scan (can happen if the

Modified: db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java?rev=1387111&r1=1387110&r2=1387111&view=diff
==============================================================================
--- db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java (original)
+++ db/derby/code/branches/10.9/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java Tue Sep 18 12:24:10 2012
@@ -21,7 +21,6 @@
 
 package org.apache.derby.impl.sql.execute;
 
-import java.util.Hashtable;
 import java.util.Properties;
 
 import org.apache.derby.iapi.db.TriggerExecutionContext;
@@ -34,12 +33,12 @@ import org.apache.derby.iapi.services.sa
 import org.apache.derby.iapi.sql.Activation;
 import org.apache.derby.iapi.sql.ResultDescription;
 import org.apache.derby.iapi.sql.ResultSet;
-import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
 import org.apache.derby.iapi.sql.execute.ConstantAction;
 import org.apache.derby.iapi.sql.execute.CursorResultSet;
 import org.apache.derby.iapi.sql.execute.ExecRow;
 import org.apache.derby.iapi.sql.execute.NoPutResultSet;
 import org.apache.derby.iapi.sql.execute.RowChanger;
+import org.apache.derby.iapi.store.access.BackingStoreHashtable;
 import org.apache.derby.iapi.store.access.ConglomerateController;
 import org.apache.derby.iapi.store.access.ScanController;
 import org.apache.derby.iapi.store.access.TransactionController;
@@ -449,7 +448,7 @@ class UpdateResultSet extends DMLWriteRe
 
 		//beetle 3865, update cursor use index.
 		TableScanResultSet tableScan = (TableScanResultSet) activation.getForUpdateIndexScan();
-		boolean notifyCursor = ((tableScan != null) && ! tableScan.sourceDrained);
+		boolean notifyCursor = (tableScan != null);
 		boolean checkStream = (deferred && rowsFound && ! constants.singleRowSource);
 		FormatableBitSet streamCols = (checkStream ? checkStreamCols() : null);
 		checkStream = (streamCols != null);
@@ -592,13 +591,7 @@ class UpdateResultSet extends DMLWriteRe
 
 	/* beetle 3865, updateable cursor use index. If the row we are updating has new value that
 	 * falls into the direction of the index scan of the cursor, we save this rid into a hash table
-	 * (for fast search), so that when the cursor hits it again, it knows to skip it.  When we get
-	 * to a point that the hash table is full, we scan forward the cursor until one of two things
-	 * happen: (1) we hit a record whose rid is in the hash table (we went through it already, so
-	 * skip it), we remove it from hash table, so that we can continue to use hash table. OR, (2) the scan
-	 * forward hit the end.  If (2) happens, we can de-reference the hash table to make it available
-	 * for garbage collection.  We save the future row id's in a virtual mem heap.  In any case,
-	 * next read will use a row id that we saved.
+	 * (for fast search), so that when the cursor hits it again, it knows to skip it.
 	 */
 	private void notifyForUpdateCursor(DataValueDescriptor[] row, DataValueDescriptor[] newBaseRow,
 										RowLocation rl, TableScanResultSet tableScan)
@@ -638,16 +631,7 @@ class UpdateResultSet extends DMLWriteRe
 					else
 						k =  map[basePos - 1];
 
-					DataValueDescriptor key;
-					/* We need to compare with saved most-forward cursor scan key if we
-					 * are reading records from the saved RowLocation temp table (instead
-					 * of the old column value) because we only care if new update value
-					 * jumps forward the most-forward scan key.
-					 */
-					if (tableScan.compareToLastKey)
-						key = tableScan.lastCursorKey.getColumn(i + 1);
-					else
-						key = row[k];
+					DataValueDescriptor key = row[k];
 
 					/* Starting from the first index key column forward, we see if the direction
 					 * of the update change is consistent with the direction of index scan.
@@ -700,82 +684,20 @@ class UpdateResultSet extends DMLWriteRe
 				if (maxCapacity < initCapacity)
 					initCapacity = maxCapacity;
 
-				tableScan.past2FutureTbl = new Hashtable(initCapacity);
+                tableScan.past2FutureTbl = new BackingStoreHashtable(
+                        tc, null, new int[]{0}, false, -1,
+                        maxCapacity, initCapacity, -1, false,
+                        tableScan.getActivation().getResultSetHoldability());
 			}
 
-			Hashtable past2FutureTbl = tableScan.past2FutureTbl;
-            /* If hash table is not full, we add it in.
-             * The key of the hash entry is the string value of the RowLocation.
-             * If the hash table is full, as the comments above this function
-             * say, we scan forward.
+            /* Add the row location to the hash table.
              *
              * Need to save a clone because when we get cached currentRow, "rl"
              * shares the same reference, so is changed at the same time.
              */
-            RowLocation updatedRL = (RowLocation) rl.cloneValue(false);
-
-			if (past2FutureTbl.size() < maxCapacity)
-				past2FutureTbl.put(updatedRL, updatedRL);
-			else
-			{
-				tableScan.skipFutureRowHolder = true;
-				ExecRow rlRow = new ValueRow(1);
-
-				for (;;)
-				{
-					ExecRow aRow = tableScan.getNextRowCore();
-					if (aRow == null)
-					{
-						tableScan.sourceDrained = true;
-						tableScan.past2FutureTbl = null;	// de-reference for garbage coll.
-						break;
-					}
-					RowLocation rowLoc = (RowLocation) aRow.getColumn(aRow.nColumns());
-
-					if (updatedRL.equals(rowLoc))  //this row we are updating jumped forward
-					{
-						saveLastCusorKey(tableScan, aRow);
-						break;	// don't need to worry about adding this row to hash any more
-					}
-
-					if (tableScan.futureForUpdateRows == null)
-					{
-						// virtual memory heap. In-memory part size 100. With the co-operation
-						// of hash table and in-memory part of heap (hash table shrinks while
-						// in-memory heap grows), hopefully we never spill temp table to disk.
-
-						tableScan.futureForUpdateRows = new TemporaryRowHolderImpl
-							(activation, null, null, 100, false, true);
-					}
-
-					rlRow.setColumn(1, rowLoc);
-					tableScan.futureForUpdateRows.insert(rlRow);
-					if (past2FutureTbl.size() < maxCapacity) //we got space in the hash table now, stop!
-					{
-						past2FutureTbl.put(updatedRL, updatedRL);
-						saveLastCusorKey(tableScan, aRow);
-						break;
-					}
-				}
-				tableScan.skipFutureRowHolder = false;
-			}
-		}
-	}
-
-	private void saveLastCusorKey(TableScanResultSet tableScan, ExecRow aRow) throws StandardException
-	{
-		/* We save the most-forward cursor scan key where we are stopping, so
-		 * that next time when we decide if we need to put an updated row id into
-		 * hash table, we can compare with this key.  This is an optimization on
-		 * memory usage of the hash table, otherwise it may be "leaking".
-		 */
-		if (tableScan.lastCursorKey == null)
-			tableScan.lastCursorKey = new ValueRow(aRow.nColumns() - 1);
-		for (int i = 1; i <= tableScan.lastCursorKey.nColumns(); i++)
-		{
-			DataValueDescriptor aCol = aRow.getColumn(i);
-			if (aCol != null)
-                tableScan.lastCursorKey.setColumn(i, aCol.cloneValue(false));
+            tableScan.past2FutureTbl.putRow(
+                false,
+                new DataValueDescriptor[] { rl.cloneValue(false) });
 		}
 	}
 

Modified: db/derby/code/branches/10.9/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.9/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java?rev=1387111&r1=1387110&r2=1387111&view=diff
==============================================================================
--- db/derby/code/branches/10.9/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java (original)
+++ db/derby/code/branches/10.9/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java Tue Sep 18 12:24:10 2012
@@ -35,7 +35,6 @@ import org.apache.derbyTesting.junit.Bas
 import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
 import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
 import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
-import org.apache.derbyTesting.junit.TestConfiguration;
 
 /**
  * This tests updateable cursor using index, Beetle entry 3865.
@@ -43,15 +42,14 @@ import org.apache.derbyTesting.junit.Tes
  * Not done in ij since we need to do many "next" and "update" to be able to
  * excercise the code of creating temp conglomerate for virtual memory heap. We
  * need at minimum 200 rows in table, if "maxMemoryPerTable" property is set to
- * 1 (KB). This includes 100 rows to fill the hash table and another 100 rows to
- * fill the in-memory heap.
+ * 1 (KB). This includes 100 rows to fill the in-memory portion of the hash
+ * table, and another 100 rows to fill an in-memory heap that was used until
+ * DERBY-5425 removed it.
  */
 public class UpdateCursorTest extends BaseJDBCTestCase {
 
 	private static final int SIZE_OF_T1 = 250;
-	private static final int MAX_CAP_OF_HASH_TABLE = 100;
 	private static final String EXPECTED_SQL_CODE = "02000";
-	private static final String UNEXPECTED_MSG = "No row was found for FETCH, UPDATE or DELETE";
 
 	/**
 	 * Basic constructor.
@@ -156,31 +154,8 @@ public class UpdateCursorTest extends Ba
 
 		/* scan the entire table except the last row. */
 		for (int i = 0; i < SIZE_OF_T1 - 1; i++) {	
-			
-			/*	Notice the order in the rows we get: from 2 to 102 asc order on second column (c3)
-			 *	then from 202 down to 103 on that column; then from 203 up to 250.  The reason is
-			 *	we are using asc index on c3, all the rows updated are in the future direction of the
-			 *	index scan, so they all get filled into a hash table.  The MAX_MEMORY_PER_TABLE
-			 *	property determines max cap of hash table 100.  So from row 103 it goes into virtual
-			 *	memory heap, whose in memory part is also 100 entries.  So row 103 to 202 goes into
-			 *	the in-memory part and gets dumped out in reverse order.  Finally Row 203 to 250"
-			 *	goes into file system.  Here we mean row ids.
-			 */
-			if (i < MAX_CAP_OF_HASH_TABLE + 1) {
-				expectedValue++;
-			} else if (i > MAX_CAP_OF_HASH_TABLE && i <= MAX_CAP_OF_HASH_TABLE * 2) {
-				if (i == MAX_CAP_OF_HASH_TABLE + 1) {
-					expectedValue = 202;
-				} else {
-					expectedValue--;
-				}
-			} else if (i > MAX_CAP_OF_HASH_TABLE * 2) {
-				if (i == MAX_CAP_OF_HASH_TABLE * 2 + 1) {
-					expectedValue = 203;
-				} else {
-					expectedValue++;
-				}
-			}
+            // Expect the values to be returned in index order.
+            expectedValue++;
 			
 			assertEquals(cursor.next(), true);
 			//System.out.println("Row " + i + ": "+cursor.getInt(1)+","+cursor.getInt(2)+": "+expectedValue);
@@ -342,4 +317,33 @@ public class UpdateCursorTest extends Ba
 
 		rollback();
 	}
+
+    /**
+     * Regression test case for DERBY-5425. The scan used to lose rows that
+     * had spilt to disk from the data structure that keeps track of already
+     * seen rows, if the transaction was committed in the middle of the scan.
+     */
+    public void testDerby5425HoldOverCommit() throws SQLException {
+        Statement stmt = createStatement();
+
+        // Drop index and recreate it to be sure that it is ascending
+        // (other subtests may have changed it)
+        assertUpdateCount(stmt, 0, "drop index I11");
+        assertUpdateCount(stmt, 0, "create index I11 on T1 (c3, c1, c5)");
+
+        PreparedStatement sel = prepareStatement(
+                "select c3 from t1 --DERBY-PROPERTIES index=I11",
+                ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
+
+        ResultSet rs = sel.executeQuery();
+        for (int i = 1; i <= SIZE_OF_T1; i++) {
+            assertTrue("Too few rows", rs.next());
+            assertEquals(i, rs.getInt(1));
+            rs.updateInt(1, i);
+            rs.updateRow();
+            commit();
+        }
+        assertFalse("Too many rows", rs.next());
+        rs.close();
+    }
 }