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 da...@apache.org on 2009/06/16 13:15:40 UTC

svn commit: r785163 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/services/io/ engine/org/apache/derby/iapi/sql/execute/ engine/org/apache/derby/impl/sql/execute/ testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/

Author: dag
Date: Tue Jun 16 11:15:39 2009
New Revision: 785163

URL: http://svn.apache.org/viewvc?rev=785163&view=rev
Log:
DERBY-4198 When using the FOR UPDATE OF clause with SUR (Scroll-insensive updatable result sets), the updateRow() method crashes

Patch derby-4198-4. This is the second part of the solution for this
issue. It solves the issue of column mapping in the presence of named
columns in a FOR UPDATE OF clause. The original code was not general
enough; ScrollInsensitiveResultSet.updateRow needs to make use of
RowChanger to do the right thing.

The patch also adds new test cases.


Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/FormatableBitSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/NoPutResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/RowChanger.java
    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/NoPutResultSetImpl.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NormalizeResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ProjectRestrictResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/RowChangerImpl.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScrollInsensitiveResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TemporaryRowHolderResultSet.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/jdbcapi/SURTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/FormatableBitSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/FormatableBitSet.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/FormatableBitSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/FormatableBitSet.java Tue Jun 16 11:15:39 2009
@@ -685,7 +685,9 @@
 	 * By using anySetBit() and anySetBit(beyondBit), one can quickly go
 	 * thru the entire bit array to return all set bit.
 	 *
-	 * @param beyondBit only look at bit that is greater than this bit number
+	 * @param beyondBit Only look at bit that is greater than this bit number.
+	 *                  Supplying a value of -1 makes the call equivalent to
+	 *                  anySetBit().
 	 * @return the bit number of a bit that is set, or -1 if no bit after
 	 * beyondBit is set
 	 */

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/NoPutResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/NoPutResultSet.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/NoPutResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/NoPutResultSet.java Tue Jun 16 11:15:39 2009
@@ -24,6 +24,7 @@
 import org.apache.derby.iapi.sql.ResultSet;
 import org.apache.derby.iapi.error.StandardException;
 
+import org.apache.derby.iapi.sql.execute.RowChanger;
 import org.apache.derby.iapi.types.RowLocation;
 import org.apache.derby.iapi.store.access.RowLocationRetRowSource;
 
@@ -181,10 +182,14 @@
 	 * JDBC's udpateRow method.
 	 *
 	 * @param row new values for the currentRow
+	 * @param rowChanger holds information about row: what columns of it is to
+	 *        be used for updating, and what underlying base table column each
+	 *        such column corresponds to.
 	 *
 	 * @exception StandardException thrown on failure.
 	 */
-	public void updateRow(ExecRow row) throws StandardException;
+	public void updateRow(ExecRow row, RowChanger rowChanger)
+			throws StandardException;
 	
 	/**
 	 * Marks the resultSet's currentRow as deleted after a delete has been 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/RowChanger.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/RowChanger.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/RowChanger.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/RowChanger.java Tue Jun 16 11:15:39 2009
@@ -23,6 +23,7 @@
 
 import org.apache.derby.iapi.services.context.ContextService;
 import org.apache.derby.iapi.services.sanity.SanityManager;
+import org.apache.derby.iapi.services.io.FormatableBitSet;
 
 import org.apache.derby.iapi.error.StandardException;
 
@@ -161,4 +162,15 @@
 	  */
 	public void open(int lockMode, boolean wait)
 		 throws StandardException;
+
+	/**
+	 * Return what column no in the input ExecRow (cf nextBaseRow argument to
+	 * #updateRow) would correspond to selected column, if any.
+	 *
+	 * @param selectedCol the column number in the base table of a selected
+	 *                    column or -1 (if selected column is not a base table
+	 *                    column, e.g. i+4).
+	 * @returns column no, or -1 if not found or not a base column
+	 */
+	public int findSelectedCol(int selectedCol);
 }

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=785163&r1=785162&r2=785163&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 Tue Jun 16 11:15:39 2009
@@ -39,6 +39,7 @@
 
 import org.apache.derby.iapi.services.sanity.SanityManager;
 import org.apache.derby.iapi.sql.depend.DependencyManager;
+import org.apache.derby.iapi.sql.execute.RowChanger;
 
 /**
  * Takes a cursor name and returns the current row
@@ -330,8 +331,9 @@
 	/**
 	 * @see NoPutResultSet#updateRow
 	 */
-	public void updateRow (ExecRow row) throws StandardException {
-		((NoPutResultSet)cursor).updateRow(row);
+	public void updateRow (ExecRow row, RowChanger rowChanger)
+			throws StandardException {
+		((NoPutResultSet)cursor).updateRow(row, rowChanger);
 	}
 	
 	/**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NoPutResultSetImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NoPutResultSetImpl.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NoPutResultSetImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NoPutResultSetImpl.java Tue Jun 16 11:15:39 2009
@@ -39,6 +39,7 @@
 import org.apache.derby.iapi.sql.execute.xplain.XPLAINVisitor;
 import org.apache.derby.iapi.sql.execute.NoPutResultSet;
 import org.apache.derby.iapi.sql.execute.TargetResultSet;
+import org.apache.derby.iapi.sql.execute.RowChanger;
 import org.apache.derby.iapi.store.access.Qualifier;
 import org.apache.derby.iapi.store.access.RowLocationRetRowSource;
 import org.apache.derby.iapi.store.access.RowSource;
@@ -512,7 +513,8 @@
 	 * This method is result sets used for scroll insensitive updatable 
 	 * result sets for other result set it is a no-op.
 	 */
-	public void updateRow(ExecRow row) throws StandardException {
+	public void updateRow(ExecRow row, RowChanger rowChanger)
+			throws StandardException {
 		// Only ResultSets of type Scroll Insensitive implement
 		// detectability, so for other result sets this method
 		// is a no-op

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NormalizeResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NormalizeResultSet.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NormalizeResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/NormalizeResultSet.java Tue Jun 16 11:15:39 2009
@@ -30,6 +30,7 @@
 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.types.DataTypeDescriptor;
 import org.apache.derby.iapi.types.DataValueDescriptor;
 import org.apache.derby.iapi.types.RowLocation;
@@ -403,8 +404,9 @@
 	/**
 	 * @see NoPutResultSet#updateRow
 	 */
-	public void updateRow (ExecRow row) throws StandardException {
-		source.updateRow(row);
+	public void updateRow (ExecRow row, RowChanger rowChanger)
+			throws StandardException {
+		source.updateRow(row, rowChanger);
 	}
 
 	/**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ProjectRestrictResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ProjectRestrictResultSet.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ProjectRestrictResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ProjectRestrictResultSet.java Tue Jun 16 11:15:39 2009
@@ -46,6 +46,7 @@
 import org.apache.derby.iapi.types.RowLocation;
 
 import org.apache.derby.catalog.types.ReferencedColumnsDescriptorImpl;
+import org.apache.derby.iapi.sql.execute.RowChanger;
 
 
 /**
@@ -583,8 +584,9 @@
 	/**
 	 * @see NoPutResultSet#updateRow
 	 */
-	public void updateRow (ExecRow row) throws StandardException {
-		source.updateRow(row);
+	public void updateRow (ExecRow row, RowChanger rowChanger)
+			throws StandardException {
+		source.updateRow(row, rowChanger);
 	}
 
 	/**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/RowChangerImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/RowChangerImpl.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/RowChangerImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/RowChangerImpl.java Tue Jun 16 11:15:39 2009
@@ -593,4 +593,52 @@
 		java.util.Arrays.sort(output);
 		return output;
 	}
+
+
+	public int findSelectedCol(int selectedCol) {
+		if (selectedCol == -1) {
+			// This is not a base column
+			return -1;
+		}
+
+		int[] changeColArray = (partialChangedColumnIds == null) ?
+			changedColumnIds : partialChangedColumnIds;
+
+		int nextColumnToUpdate = -1;
+		for (int i = 0; i < changeColArray.length; i++) {
+			nextColumnToUpdate =
+				changedColumnBitSet.anySetBit(nextColumnToUpdate);
+
+			if (selectedCol == nextColumnToUpdate + 1) { // bit set is 0 based
+				return changeColArray[i];
+			}
+		}
+
+		return -1;
+	}
+
+
+	public String toString() {
+		if (SanityManager.DEBUG) {
+			StringBuffer sb = new StringBuffer();
+			sb.append("changedColumnBitSet: " + changedColumnBitSet + "\n");
+
+			int[] changedColumnArray = (partialChangedColumnIds == null) ?
+				changedColumnIds : partialChangedColumnIds;
+
+			sb.append("changedColumnArray: [");
+			for (int i = 0; i < changedColumnArray.length; i++) {
+				sb.append(changedColumnArray[i]);
+
+				if (i < changedColumnArray.length-1) {
+					sb.append(",");
+				}
+			}
+			sb.append("]");
+
+			return sb.toString();
+		} else {
+			return super.toString();
+		}
+	}
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScrollInsensitiveResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScrollInsensitiveResultSet.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScrollInsensitiveResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScrollInsensitiveResultSet.java Tue Jun 16 11:15:39 2009
@@ -37,6 +37,7 @@
 
 import org.apache.derby.iapi.store.access.BackingStoreHashtable;
 
+import org.apache.derby.iapi.sql.execute.RowChanger;
 import org.apache.derby.iapi.types.SQLBoolean;
 import org.apache.derby.iapi.types.SQLInteger;
 
@@ -1092,15 +1093,15 @@
 		}
 	}
 
+
 	/**
 	 * @see NoPutResultSet#updateRow
 	 *
 	 * Sets the updated column of the hash table to true and updates the row
 	 * in the hash table with the new values for the row.
 	 */
-	public void updateRow(ExecRow row) throws StandardException {
-		ExecRow newRow = row;
-		boolean undoProjection = false;
+	public void updateRow(ExecRow row, RowChanger rowChanger)
+			throws StandardException {
 
 		ProjectRestrictResultSet prRS = null;
 
@@ -1112,41 +1113,72 @@
 			prRS = ((RowCountResultSet)source).getUnderlyingProjectRestrictRS();
 		}
 
-		if (prRS != null) {
-			newRow = prRS.doBaseRowProjection(row);
-			undoProjection = true;
-		}
-
 		positionInHashTable.setValue(currentPosition);
-		DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) 
+		DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
 				ht.get(positionInHashTable);
 		RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
+
+		// Maps from each selected column to underlying base table column
+		// number, i.e. as from getBaseProjectMapping if a PRN exists, if not
+		// we construct one, so we always know where in the hash table a
+		// modified column will need to go (we do our own projection).
+		int[] map;
+
+		if (prRS != null) {
+			map = prRS.getBaseProjectMapping();
+		} else {
+			// create a natural projection mapping for all columns in SELECT
+			// list so we can treat the cases of no PRN and PRN the same.
+			int noOfSelectedColumns =
+				hashRowArray.length - (LAST_EXTRA_COLUMN+1);
+
+			map = new int[noOfSelectedColumns];
+
+			// initialize as 1,2,3, .. n which we know is correct since there
+			// is no underlying PRN.
+			for (int i=0; i < noOfSelectedColumns; i++) {
+				map[i] = i+1; // column is 1-based
+			}
+		}
+
+		// Construct a new row based on the old one and the updated columns
+		ExecRow newRow = new ValueRow(map.length);
+
+		for (int i=0; i < map.length; i++) {
+			// What index in ExecRow "row" corresponds to this position in the
+			// hash table, if any?
+			int rowColumn = rowChanger.findSelectedCol(map[i]);
+
+			if (rowColumn > 0) {
+				// OK, a new value has been supplied, use it
+				newRow.setColumn(i+1, row.getColumn(rowColumn));
+			} else {
+				// No new value, so continue using old one
+				newRow.setColumn(i+1, hashRowArray[LAST_EXTRA_COLUMN + 1 + i]);
+			}
+		}
+
 		ht.remove(new SQLInteger(currentPosition));
 		addRowToHashTable(newRow, currentPosition, rowLoc, true);
-		
+
 		// Modify row to refer to data in the BackingStoreHashtable.
 		// This allows reading of data which goes over multiple pages
 		// when doing the actual update (LOBs). Putting columns of
 		// type SQLBinary to disk, has destructive effect on the columns,
 		// and they need to be re-read. That is the reason this is needed.
-		if (undoProjection) {
-			
-			final DataValueDescriptor[] newRowData = newRow.getRowArray();
-			
-			// Array of original position in row
-			final int[] origPos = prRS.getBaseProjectMapping();
-			
-			// We want the row to contain data backed in BackingStoreHashtable
-			final DataValueDescriptor[] backedData = 
-				getRowArrayFromHashTable(currentPosition);
-			
-			for (int i=0; i<origPos.length; i++) {
-				if (origPos[i]>=0) {
-					row.setColumn(origPos[i], backedData[i]);
-				}
+
+		DataValueDescriptor[] backedData =
+			getRowArrayFromHashTable(currentPosition);
+
+		for (int i=0; i < map.length; i++) {
+			// What index in "row" corresponds to this position in the table,
+			// if any?
+			int rowColumn = rowChanger.findSelectedCol(map[i]);
+
+			if (rowColumn > 0) {
+				// OK, put the value in the hash table back to row.
+				row.setColumn(rowColumn, backedData[i]);
 			}
-		} else {
-			row.setRowArray(getRowArrayFromHashTable(currentPosition));
 		}
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TemporaryRowHolderResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TemporaryRowHolderResultSet.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TemporaryRowHolderResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/TemporaryRowHolderResultSet.java Tue Jun 16 11:15:39 2009
@@ -33,6 +33,7 @@
 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.sql.execute.TargetResultSet;
 import org.apache.derby.iapi.store.access.ConglomerateController;
 import org.apache.derby.iapi.store.access.ScanController;
@@ -1124,7 +1125,8 @@
 	 * This method is result sets used for scroll insensitive updatable 
 	 * result sets for other result set it is a no-op.
 	 */
-	public void updateRow(ExecRow row) throws StandardException {
+	public void updateRow(ExecRow row, RowChanger rowChanger)
+			throws StandardException {
 		// Only ResultSets of type Scroll Insensitive implement
 		// detectability, so for other result sets this method
 		// is a no-op

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=785163&r1=785162&r2=785163&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 Tue Jun 16 11:15:39 2009
@@ -557,7 +557,7 @@
 					riChecker.doFKCheck(newBaseRow);
 				}
 
-				source.updateRow(newBaseRow);
+				source.updateRow(newBaseRow, rowChanger);
 				rowChanger.updateRow(row,newBaseRow,baseRowLocation);
 
 				//beetle 3865, update cursor use index.
@@ -885,7 +885,6 @@
 					rowChanger.updateRow(deferredBaseRow,
 										newBaseRow,
 										baseRowLocation);
-					source.updateRow(newBaseRow);
 				}
 			} finally
 			{

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SURTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SURTest.java?rev=785163&r1=785162&r2=785163&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SURTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SURTest.java Tue Jun 16 11:15:39 2009
@@ -1394,6 +1394,165 @@
     }
     
     /**
+     * DERBY-4198 "When using the FOR UPDATE OF clause with SUR
+     * (Scroll-insensive updatable result sets), the updateRow() method crashes"
+     *
+     * This bug revealed missing logic to handle the fact the the ExecRow
+     * passed down to ScrollInsensitiveResultSet.updateRow does not always
+     * contain all the rows of the basetable, cf. the logic of RowChangerImpl.
+     * When an explicit list of columns is given as in FOR UPDATE OF
+     * <column-list>, the ExecRow may contains a subset of the the base table
+     * columns and ScrollInsensitiveResultSet was not ready to handle that.
+     *
+     * Test some of the cases which went wrong before the fix.
+     *
+     */
+    public void testForUpdateWithColumnList() throws SQLException {
+        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+                                          ResultSet.CONCUR_UPDATABLE);
+
+        // case a)
+        ResultSet rs = s.executeQuery("select c from t1 for update of c");
+
+        rs.next();
+        rs.updateString(1,"foobar");
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals("foobar", rs.getString(1));
+        rs.close();
+
+        // case b)
+        rs = s.executeQuery("select id from t1 for update of id");
+        rs.next();
+        rs.updateInt(1,20);
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals(20, rs.getInt(1));
+        rs.close();
+
+        // case c)
+        rs = s.executeQuery("select * from t1 for update of id");
+        rs.next();
+        rs.updateInt(1,20);
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals(20, rs.getInt(1));
+        rs.close();
+
+        // case d)
+        rs = s.executeQuery("SELECT * from t1 for update of c");
+        rs.next();
+        int id = rs.getInt(1);
+        int a =  rs.getInt(2);
+        int b =  rs.getInt(3);
+        rs.updateString(4,"foobar");
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals(id, rs.getInt(1));
+        assertEquals(a, rs.getInt(2));
+        assertEquals(b, rs.getInt(3));
+        assertEquals("foobar", rs.getString(4));
+        rs.close();
+
+        // case e)
+        rs = s.executeQuery("SELECT * from t1 for update of id,a,b,c");
+        rs.next();
+        rs.updateInt(1, -20);
+        rs.updateInt(2, 20);
+        rs.updateInt(3, 21);
+        rs.updateString(4,"foobar");
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals(-20, rs.getInt(1));
+        assertEquals(20, rs.getInt(2));
+        assertEquals(21, rs.getInt(3));
+        assertEquals("foobar", rs.getString(4));
+        rs.close();
+
+        // case f)
+        rs = s.executeQuery("SELECT * from t1 for update of id, a,b,c");
+        rs.next();
+        rs.updateInt(1, 20);
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals(20, rs.getInt(1));
+        rs.close();
+
+        // case h)
+        rs = s.executeQuery("SELECT id from t1 for update of id, c");
+           String cursorname = rs.getCursorName();
+        rs.next();
+           Statement s2 = createStatement();
+        s2.executeUpdate("update t1 set c='foobar' where current of " +
+                         cursorname);
+        s2.close();
+        rs.next();
+        rs.previous();
+        rs.getInt(1); // gives error state 22018 before fix
+        rs.close();
+
+        // case i)
+        rs = s.executeQuery("SELECT id from t1 for update");
+        cursorname = rs.getCursorName();
+        rs.next();
+        s2 = createStatement();
+        s2.executeUpdate("update t1 set c='foobar' where current of " +
+                         cursorname);
+        s2.close();
+        rs.next();
+        rs.previous();
+        rs.getInt(1); // ok before fix
+        rs.close();
+
+        // Odd cases: base row mentioned twice in rs, update 1st instance
+        rs = s.executeQuery("SELECT id,a,id from t1");
+        rs.next();
+        rs.updateInt(1, 20);
+        rs.updateRow();
+        rs.next();
+        rs.previous();
+        assertEquals(20, rs.getInt(1));
+        assertEquals(20, rs.getInt(3));
+        rs.close();
+
+        // Odd cases: base row mentioned twice in rs, update 2nd instance
+        // with explicit column list; fails, see DERBY-4226.
+        rs = s.executeQuery("SELECT id,a,id from t1 for update of id");
+        rs.next();
+        try {
+            rs.updateInt(3, 20);
+            fail("should fail");
+        } catch (SQLException e) {
+            String sqlState = usingEmbedded() ? "42X31" : "XJ124";
+            assertSQLState(sqlState, e);
+        }
+        rs.close();
+
+        // Odd cases: base row mentioned twice in rs, update 2nd instance
+        // without explicit column list; works
+        rs = s.executeQuery("SELECT id,a,id from t1 for update");
+        rs.next();
+        rs.updateInt(3, 20);
+        rs.updateRow();
+        assertEquals(20, rs.getInt(1));
+        assertEquals(20, rs.getInt(3));
+        rs.next();
+        rs.previous();
+        assertEquals(20, rs.getInt(1));
+        assertEquals(20, rs.getInt(3));
+        rs.close();
+
+        s.close();
+    }
+
+
+    /**
      * Check that detectability methods throw the correct exception
      * when called in an illegal row state, that is, somehow not
      * positioned on a row. Minion of testDetectabilityExceptions.
@@ -1569,6 +1728,7 @@
         s.close();
     }
 
+
     /**
      * Get a cursor name. We use the same cursor name for all cursors.
      */