/*
 *
 * Derby - Class ConcurrencyTest
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package org.apache.derbyTesting.functionTests.tests.jdbcapi;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * Testing concurrency behaviour in derby when creating the resultsets with
 * different parameters.
 */
public class ConcurrencyTest extends SURBaseTest {
    
    /** Creates a new instance of ConcurrencyTest */
    public ConcurrencyTest(String name) {
        super(name);
    }

    /**
     * Sets up the connection, then create the data model
     */
    public void setUp() 
        throws Exception 
    {      
        // For the concurrency tests, we recreate the model
        // for each testcase (since we do commits)
        SURDataModelSetup.createDataModel
            (SURDataModelSetup.SURDataModel.MODEL_WITH_PK, getConnection());
        commit();
    }
    
    public void tearDown() throws Exception 
    {
        rollback();
        dropTable("T1");
        commit();

        super.tearDown();
    }
    
    /**
     * Test that update locks are downgraded to shared locks
     * after repositioning.
     * This test fails with Derby
     */
    public void testUpdateLockDownGrade1()
        throws SQLException 
    {
        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1 for update");
        
        // After navigating through the resultset, 
        // presumably all rows are locked with shared locks
        while (rs.next());
        
        // Now open up a connection
        Connection con2 = openDefaultConnection();
        Statement s2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                            ResultSet.CONCUR_UPDATABLE);
        
        ResultSet rs2 = s2.executeQuery("select * from t1 for update");
        try {
            rs2.next(); // We should be able to get a update lock here.
        } catch (SQLException e) {
            assertEquals("Unexpected SQL state",  LOCK_TIMEOUT_SQL_STATE,
                         e.getSQLState());
            return;
        } finally {
            con2.rollback();
        }
        assertTrue("Expected Derby to hold updatelocks in RR mode", false);
        
        s2.close();
        con2.close();
        
        s.close();
    }
    
    /**
     * Test that we can aquire a update lock even if the row is locked with 
     * a shared lock.
     */
    public void testAquireUpdateLock1()
        throws SQLException 
    {
        Statement s = createStatement();
        ResultSet rs = s.executeQuery("select * from t1");
        
        // After navigating through the resultset, 
        // presumably all rows are locked with shared locks
        while (rs.next());
        
        // Now open up a connection
        Connection con2 = openDefaultConnection();
        Statement s2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                            ResultSet.CONCUR_UPDATABLE);
        
        ResultSet rs2 = s2.executeQuery("select * from t1 for update");
        try {
            rs2.next(); // We should be able to get a update lock here.
        } finally {
            con2.rollback();
        }
        
        s2.close();
        con2.close();
        s.close();
    }
    
    /*
     * Test that we do not get a concurrency problem when opening two cursors
     * as readonly.
     **/
    public void testSharedLocks1()
        throws SQLException 
    {
        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                          ResultSet.CONCUR_READ_ONLY);
        final ResultSet rs = s.executeQuery("select * from t1");
        scrollForward(rs);
        Connection con2 = openDefaultConnection();
        Statement s2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                            ResultSet.CONCUR_READ_ONLY);
        try {
            final ResultSet rs2 = s2.executeQuery("select * from t1");
            scrollForward(rs2);
        } finally {
            rs.close();
            con2.rollback();
            con2.close();
        }
        
        s.close();
    }
    
    /*
     * Test that we do not get a concurrency problem when opening two cursors 
     * reading the same data (no parameters specified to create statement).
     **/
    public void testSharedLocks2()
        throws SQLException 
    {
        Statement s = createStatement();
        ResultSet rs = s.executeQuery("select * from t1");
        scrollForward(rs);
        Connection con2 = openDefaultConnection();
        Statement s2 = con2.createStatement();
        try {
            final ResultSet rs2 = s2.executeQuery("select * from t1");
            scrollForward(rs2);
        } finally {
            rs.close();
            con2.rollback();
            con2.close();
        }
        s.close();
    }
    
    /*
     * Test that we do not get a concurrency problem when opening one cursor
     * as updatable (not using "for update"), and another cursor as read only
     **/
    public void testSharedAndUpdateLocks1()
        throws SQLException {
        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                          ResultSet.CONCUR_UPDATABLE);
        
        ResultSet rs = s.executeQuery("select * from t1");
        scrollForward(rs);
        Connection con2 = openDefaultConnection();
        Statement s2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                            ResultSet.CONCUR_READ_ONLY);
        try {
            final ResultSet rs2 = s2.executeQuery("select * from t1");
            scrollForward(rs2);
        } finally {
            rs.close();
            con2.rollback();
            con2.close();
        }
        s.close();
    }
    
    /*
     * Test that we do no get a concurrency problem when opening one cursor
     * as updatable (using "for update"), and another cursor as read only.
     *
     **/
    public void testSharedAndUpdateLocks2()
        throws SQLException 
    {
        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1 for update");
        scrollForward(rs);
        Connection con2 = openDefaultConnection();
        Statement s2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                            ResultSet.CONCUR_READ_ONLY);
        try {
            final ResultSet rs2 = s2.executeQuery("select * from t1");
            scrollForward(rs2);
        } finally {
            rs.close();
            con2.rollback();
            con2.close();
        }
        s.close();
    }
    
    /**
     * Test what happens if you update a deleted + purged tuple.
     * The transaction which deletes the tuple, will also
     * ensure that the tuple is purged from the table, not only marked
     * as deleted.
     **/
    public void testUpdatePurgedTuple1()
        throws SQLException
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next();
        int firstKey = rs.getInt(1);
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        int lastKey = firstKey;
        while (rs.next()) {
            lastKey = rs.getInt(1);
            println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
        }
        
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        try {
            PreparedStatement ps2 = con2.prepareStatement
                ("delete from t1 where id=? or id=?");
            ps2.setInt(1, firstKey);
            ps2.setInt(2, lastKey);
            assertEquals("Expected two records to be deleted", 
                         2, ps2.executeUpdate());
            println("T2: Deleted records with id=" + firstKey + " and id=" + 
                    lastKey);
            con2.commit();
            println("T2: commit");
            ps2 = con2.prepareStatement
                ("call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(?,?,?,?,?)");
            ps2.setString(1, "APP"); // schema
            ps2.setString(2, "T1");  // table name
            ps2.setInt(3, 1); // purge
            ps2.setInt(4, 0); // defragment rows
            ps2.setInt(5, 0); // truncate end
            println("T3: call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE");
            println("T3: purges deleted records");
            ps2.executeUpdate();
            con2.commit();
            println("T3: commit");
        } catch (SQLException e) {
            con2.rollback();
            throw e;
        }
        rs.first(); // Go to first tuple
        println("T1: Read first Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        rs.updateInt(2, 3);
        println("T1: updateInt(2, 3);");
        rs.updateRow();
        println("T1: updateRow()");
        rs.last(); // Go to last tuple
        println("T1: Read last Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        rs.updateInt(2, 3);
        println("T1: updateInt(2, 3);");
        rs.updateRow();
        println("T1: updateRow()");
        commit();
        println("T1: commit");
        rs = s.executeQuery("select * from t1");
        println("T3: select * from table");
        while (rs.next()) {
            println("T3: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
            
        }
        
        con2.close();
        s.close();
    }
    
    /**
     * Test what happens if you update a deleted tuple using positioned update
     * (same as testUpdatePurgedTuple1, except here we use positioned updates)
     **/
    public void testUpdatePurgedTuple2()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next(); // Point to first tuple
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        int firstKey = rs.getInt(1);
        rs.next(); // Go to next
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        try {
            PreparedStatement ps2 = con2.prepareStatement
                ("delete from t1 where id=?");
            ps2.setInt(1, firstKey);
            assertEquals("Expected one record to be deleted", 1, 
                         ps2.executeUpdate());
            println("T2: Deleted record with id=" + firstKey);
            con2.commit();
            println("T2: commit");
        } catch (SQLException e) {
            con2.rollback();
            throw e;
        }
        rs.previous(); // Go back to first tuple
        println("T1: Read previous Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        
        PreparedStatement ps = prepareStatement
            ("update T1 set a=? where current of " + rs.getCursorName());
        ps.setInt(1, 3);
        int updateCount = ps.executeUpdate();
        println("T1: update table, set a=3 where current of " + 
                rs.getCursorName());
        println("T1: commit");
        commit();
        rs = s.executeQuery("select * from t1");
        while (rs.next()) {
            println("T3: Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
            
        }
        
        con2.close();
    }
    
    /**
     * Test what happens if you update a tuple which is deleted, purged and
     * reinserted
     **/
    public void testUpdatePurgedTuple3()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next(); // Point to first tuple
        int firstKey = rs.getInt(1);
        println("T1: read tuple with key " + firstKey);
        rs.next(); // Go to next
        println("T1: read next tuple");
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        try {
            PreparedStatement ps2 = con2.prepareStatement
                ("delete from t1 where id=?");
            ps2.setInt(1, firstKey);
            assertEquals("Expected one record to be deleted", 1, 
                         ps2.executeUpdate());
            println("T2: Deleted record with id=" + firstKey);
            con2.commit();
            println("T2: commit");
            
            // Now purge the table
            ps2 = con2.prepareStatement
                ("call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(?,?,?,?,?)");
            ps2.setString(1, "APP"); // schema
            ps2.setString(2, "T1");  // table name
            ps2.setInt(3, 1); // purge
            ps2.setInt(4, 0); // defragment rows
            ps2.setInt(5, 0); // truncate end
            println("T3: call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE");
            println("T3: purges deleted records");
            ps2.executeUpdate();
            con2.commit();
            
            ps2 = con2.prepareStatement("insert into t1 values(?,?,?,?,?)");
            ps2.setInt(1, firstKey);
            ps2.setInt(2, -1);
            ps2.setInt(3, -1);
            ps2.setString(4, "UPDATED TUPLE");
            ps2.setString(5, "UPDATED CLOB");
            assertEquals("Expected one record to be inserted", 1, 
                         ps2.executeUpdate());
            println("T4: Inserted record (" + firstKey + ",-1,-1)" );
            con2.commit();
            println("T4: commit");
        } catch (SQLException e) {
            con2.rollback();
            throw e;
        }
        println("T1: read previous tuple");
        rs.previous(); // Go back to first tuple
        println("T1: id=" + rs.getInt(1));
        rs.updateInt(2, 3);
        println("T1: updateInt(2, 3);");
        rs.updateRow();
        println("T1: updated column 2, to value=3");
        println("T1: commit");
        commit();
        rs = s.executeQuery("select * from t1");
        while (rs.next()) {
            println("T5: Read Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
            
        }
        
        con2.close();
    }
    
    /**
     * Test what happens if you update a tuple which is deleted, purged and 
     * then reinserted with the exact same values
     **/
    public void testUpdatePurgedTuple4()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next(); // Point to first tuple
        int firstKey = rs.getInt(1);
        int valA = rs.getInt(2);
        int valB = rs.getInt(3);
        
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        
        rs.next(); // Go to next
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        try {
            PreparedStatement ps2 = con2.prepareStatement
                ("delete from t1 where id=?");
            ps2.setInt(1, firstKey);
            assertEquals("Expected one record to be deleted", 1, 
                         ps2.executeUpdate());
            println("T2: Deleted record with id=" + firstKey);
            con2.commit();
            println("T2: commit");
            
            // Now purge the table
            ps2 = con2.prepareStatement
                ("call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(?,?,?,?,?)");
            ps2.setString(1, "APP"); // schema
            ps2.setString(2, "T1");  // table name
            ps2.setInt(3, 1); // purge
            ps2.setInt(4, 0); // defragment rows
            ps2.setInt(5, 0); // truncate end
            println("T3: call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE");
            println("T3: purges deleted records");
            ps2.executeUpdate();
            con2.commit();
            println("T3: commit");
            
            ps2 = con2.prepareStatement("insert into t1 values(?,?,?,?,?)");
            ps2.setInt(1, firstKey);
            ps2.setInt(2, valA);
            ps2.setInt(3, valB);
            ps2.setString(4, "UPDATE TUPLE " + firstKey);
            ps2.setString(5, "UPDATED CLOB " + firstKey);
            assertEquals("Expected one record to be inserted", 1, 
                         ps2.executeUpdate());
            println("T4: Inserted record (" + firstKey + "," + valA + "," + 
                    valB + ")" );
            con2.commit();
            println("T4: commit");
        } catch (SQLException e) {
            con2.rollback();
            throw e;
        }
        rs.previous(); // Go back to first tuple
        println("T1: Read previous Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        
        println("T1: id=" + rs.getInt(1));
        rs.updateInt(2, 3);
        rs.updateRow();
        println("T1: updated column 2, to value=3");
        println("T1: commit");
        commit();
        rs = s.executeQuery("select * from t1");
        while (rs.next()) {
            println("T4: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
            
        }
        con2.close();
    }
    
    /**
     * Test what happens if you update a tuple which has been modified by 
     * another transaction.
     **/
    public void testUpdateModifiedTuple1()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next(); // Point to first tuple
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        int firstKey = rs.getInt(1);
        rs.next(); // Go to next
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        try {
            PreparedStatement ps2 = con2.prepareStatement
                ("update t1 set b=? where id=?");
            ps2.setInt(1, 999);
            ps2.setInt(2, firstKey);
            assertEquals("Expected one record to be updated", 1, 
                         ps2.executeUpdate());
            println("T2: Updated b=999 where id=" + firstKey);
            con2.commit();
            println("T2: commit");
        } catch (SQLException e) {
            con2.rollback();
            throw e;
        }
        rs.previous(); // Go back to first tuple
        println("T1: Read previous Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        rs.updateInt(2, 3);
        rs.updateRow();
        println("T1: updated column 2, to value=3");
        commit();
        println("T1: commit");
        rs = s.executeQuery("select * from t1");
        while (rs.next()) {
            println("T3: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
            
        }
        con2.close();
    }
    
    /**
     * Test what happens if you update a tuple which has been modified by 
     * another transaction (in this case the same column)
     **/
    public void testUpdateModifiedTuple2()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next(); // Point to first tuple
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        int firstKey = rs.getInt(1);
        rs.next(); // Go to next
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        try {
            PreparedStatement ps2 = con2.prepareStatement
                ("update t1 set b=? where id=?");
            ps2.setInt(1, 999);
            ps2.setInt(2, firstKey);
            assertEquals("Expected one record to be updated", 1, 
                         ps2.executeUpdate());
            println("T2: Updated b=999 where id=" + firstKey);
            con2.commit();
            println("T2: commit");
        } catch (SQLException e) {
            con2.rollback();
            throw e;
        }
        rs.previous(); // Go back to first tuple
        println("T1: Read previous Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        rs.updateInt(3, 9999);
        rs.updateRow();
        println("T1: updated column 3, to value=9999");
        commit();
        println("T1: commit");
        rs = s.executeQuery("select * from t1");
        while (rs.next()) {
            println("T3: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
            
        }
        con2.close();
    }
    
    /**
     * Tests that a ResultSet opened even in read uncommitted, gets a 
     * table intent lock, and that another transaction then cannot compress 
     * the table while the ResultSet is open.
     **/
    public void testTableIntentLock1()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        println("T1: select * from t1");
        ResultSet rs = s.executeQuery("select * from t1 for update");
        while (rs.next()) {
            println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
        } // Now the cursor does not point to any tuples
        
        // Compressing the table in another transaction:
        Connection con2 = openDefaultConnection();
        
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        PreparedStatement ps2 = con2.prepareStatement
            ("call SYSCS_UTIL.SYSCS_COMPRESS_TABLE(?, ?, ?)");
        ps2.setString(1, "APP");
        ps2.setString(2, "T1");
        ps2.setInt(3, 0);
        println("T2: call SYSCS_UTIL.SYSCS_COMPRESS_TABLE(APP, T1, 0)");
        try {
            ps2.executeUpdate(); // This will hang
            fail("Expected T2 to hang");
        } catch (SQLException e) {
            println("T2: Got exception:" + e.getMessage());
            
            assertSQLState(LOCK_TIMEOUT_EXPRESSION_SQL_STATE, e);

        }
        ps2.close();
        con2.rollback();
        con2.close();
        
        s.close();
    }
    
    /**
     * Test that Derby set updatelock on current row when using
     * read-uncommitted
     **/
    public void testUpdateLockInReadUncommitted()
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next();
        int firstKey = rs.getInt(1);
        println("T1: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

        PreparedStatement ps2 = con2.prepareStatement
                ("delete from t1 where id=?");
        ps2.setInt(1, firstKey);
        try {
            ps2.executeUpdate();
            fail("expected record with id=" + firstKey + 
                       " to be locked");
        } catch (SQLException e) {
            assertSQLState(LOCK_TIMEOUT_SQL_STATE, e);
        }
        
        ps2.close();
        con2.rollback();
        con2.close();
        s.close();
    }
    
    /**
     * Test that the system cannot defragment any records
     * as long as an updatable result set is open against the table.
     **/
    public void testDefragmentDuringScan() 
        throws SQLException
    {
        testCompressDuringScan(true, false);
    }
    /**
     * Test that the system cannot truncate any records
     * as long as an updatable result set is open against the table.
     **/
    public void testTruncateDuringScan() 
        throws SQLException
    {
        testCompressDuringScan(false, true);
    }
    
    /**
     * Test that the system does not purge any records
     * as long as we do either a defragment, or truncate
     **/
    private void testCompressDuringScan(boolean testDefragment, 
                                        boolean testTruncate)
        throws SQLException 
    {
        getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
        Statement delStatement = createStatement();
        // First delete all records except the last and first
        int deleted = delStatement.executeUpdate
            ("delete from T1 where id>0 and id<" + (recordCount-1));
        int expectedDeleted = recordCount-2;    
        println("T1: delete records");
        assertEquals("Invalid number of records deleted", expectedDeleted, 
                     deleted);
        delStatement.close();
        commit();
        println("T1: commit");
        
        Statement s = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                          ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = s.executeQuery("select * from t1");
        rs.next();
        int firstKey = rs.getInt(1);
        println("T2: Read next Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        int lastKey = firstKey;
        while (rs.next()) {
            lastKey = rs.getInt(1);
            println("T2: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
        }
        
        final Connection con2 = openDefaultConnection();
        con2.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
        final PreparedStatement ps2 = con2.prepareStatement
            ("call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(?,?,?,?,?)");
        ps2.setString(1, "APP"); // schema
        ps2.setString(2, "T1");  // table name
        ps2.setInt(3, 0); // purge
        int defragment = testDefragment ? 1 : 0;
        int truncate = testTruncate ? 1 : 0;
        ps2.setInt(4, defragment); // defragment rows
        ps2.setInt(5, truncate); // truncate end
        
        println("T3: call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE");
        println("T3: defragmenting rows");
        try { 
            ps2.executeUpdate();
            con2.commit();
            println("T3: commit");
            fail("Expected T3 to hang waiting for Table lock");
        } catch (SQLException e) {            
            println("T3: got expected exception");
            con2.rollback();            
        }
        ps2.close();
        rs.first(); // Go to first tuple
        println("T1: Read first Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        rs.updateInt(2, 3);
        println("T1: updateInt(2, 3);");
        rs.updateRow();        
        println("T1: updateRow()");
        rs.last(); // Go to last tuple
        println("T1: Read last Tuple:(" + rs.getInt(1) + "," +
                rs.getInt(2) + "," +
                rs.getInt(3) + ")");
        rs.updateInt(2, 3);
        println("T1: updateInt(2, 3);");
        rs.updateRow();
        println("T1: updateRow()");
        commit();
        println("T1: commit");
        rs = s.executeQuery("select * from t1");
        println("T4: select * from table");
        while (rs.next()) {
            println("T4: Read next Tuple:(" + rs.getInt(1) + "," +
                    rs.getInt(2) + "," +
                    rs.getInt(3) + ")");
        }
        con2.close();
        s.close();
    }
    
    // By providing a static suite(), you can customize which tests to run.
    // The default is to run all tests in the TestCase subclass.
    
    /**
     * Run in embedded and client.
     */
    public static Test suite()
    {
        final BaseTestSuite suite = new BaseTestSuite("ConcurrencyTest");
        suite.addTest(baseSuite("ConcurrencyTest:embedded", true));
        
        suite.addTest(
                TestConfiguration.clientServerDecorator(
                        baseSuite("ConcurrencyTest:client", false)));
        
        // Since this test relies on lock waiting, setting this property will
        // make it go a lot faster:
        return DatabasePropertyTestSetup.setLockTimeouts(suite, -1, 4);
    }
    
    private static Test baseSuite(String name, boolean embedded) {
        final BaseTestSuite suite = new BaseTestSuite(name);
        
        // This testcase does not require JDBC3/JSR169, since it does not
        // specify result set concurrency) in Connection.createStatement().
        suite.addTest(new ConcurrencyTest("testSharedLocks2"));
        
        // The following testcases requires JDBC3/JSR169:
        if ((JDBC.vmSupportsJDBC3() || JDBC.vmSupportsJSR169())) {
            
            // The following testcases do not use updatable result sets:
            suite.addTest(new ConcurrencyTest("testUpdateLockDownGrade1"));
            suite.addTest(new ConcurrencyTest("testAquireUpdateLock1"));
            suite.addTest(new ConcurrencyTest("testSharedLocks1"));
            suite.addTest(new ConcurrencyTest("testSharedAndUpdateLocks1"));
            suite.addTest(new ConcurrencyTest("testSharedAndUpdateLocks2"));
            suite.addTest(new ConcurrencyTest ("testUpdatePurgedTuple2"));
            suite.addTest(new ConcurrencyTest("testUpdatePurgedTuple3"));
            suite.addTest(new ConcurrencyTest("testUpdatePurgedTuple4"));
            suite.addTest(new ConcurrencyTest("testUpdateModifiedTuple1"));
            suite.addTest(new ConcurrencyTest("testUpdateModifiedTuple2"));
            suite.addTest(new ConcurrencyTest("testTableIntentLock1"));
            suite.addTest
                (new ConcurrencyTest("testUpdateLockInReadUncommitted"));
            suite.addTest(new ConcurrencyTest("testDefragmentDuringScan"));
            suite.addTest(new ConcurrencyTest("testTruncateDuringScan"));
            
            // This testcase fails in DerbyNetClient framework due to 
            // DERBY-1696
            if (embedded) {
                suite.addTest
                    (new ConcurrencyTest("testUpdatePurgedTuple1"));
            }        
        }
        
        return suite;
    }
    
}
