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/07/09 12:52:11 UTC

svn commit: r1359052 - in /db/derby/code/trunk/java: engine/org/apache/derby/impl/sql/execute/ testing/org/apache/derbyTesting/functionTests/tests/lang/

Author: kahatlen
Date: Mon Jul  9 10:52:11 2012
New Revision: 1359052

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

Use a BackingStoreHashtable to store rows updated in such a way that
they may be seen again later in the scan. The BackingStoreHashtable is
made holdable if the ResultSet is holdable so that rows are not lost on
commit, as they were before.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java?rev=1359052&r1=1359051&r2=1359052&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CurrentOfResultSet.java Mon Jul  9 10:52:11 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/trunk/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java?rev=1359052&r1=1359051&r2=1359052&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/IndexRowToBaseRowResultSet.java Mon Jul  9 10:52:11 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/trunk/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java?rev=1359052&r1=1359051&r2=1359052&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TableScanResultSet.java Mon Jul  9 10:52:11 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/trunk/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java?rev=1359052&r1=1359051&r2=1359052&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/UpdateResultSet.java Mon Jul  9 10:52:11 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/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java?rev=1359052&r1=1359051&r2=1359052&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateCursorTest.java Mon Jul  9 10:52:11 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();
+    }
 }