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();
+ }
}