/**
 *  Derby - Class org.apache.derbyTesting.functionTests.tests.lang.UpdateCursorTest
 *  
 * 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.lang;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Properties;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
import org.apache.derbyTesting.junit.SystemPropertyTestSetup;

/**
 * This tests updateable cursor using index, Beetle entry 3865.
 * 
 * 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 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 String EXPECTED_SQL_CODE = "02000";

	/**
	 * Basic constructor.
	 */
	public UpdateCursorTest(String name) {
		super(name);
	}

	/**
	 * Sets the auto commit to false.
	 */
	protected void initializeConnection(Connection conn) throws SQLException {
		conn.setAutoCommit(false);
	}

	/**
	 * Returns the implemented tests.
	 * 
	 * @return An instance of <code>Test</code> with the implemented tests to
	 *         run.
	 */
	public static Test suite() {
		Properties props = new Properties();

		props.setProperty("derby.language.maxMemoryPerTable", "1");
		return new DatabasePropertyTestSetup(
            new SystemPropertyTestSetup(
                new CleanDatabaseTestSetup(
                    new BaseTestSuite(
                        UpdateCursorTest.class, "UpdateCursorTest")) {

			/**
			 * @see org.apache.derbyTesting.junit.CleanDatabaseTestSetup#decorateSQL(java.sql.Statement)
			 */
			protected void decorateSQL(Statement s) throws SQLException {
				StringBuffer sb = new StringBuffer(1000);
				String largeString;
				PreparedStatement pstmt;

				assertUpdateCount(s, 0, "create table T1 ("
						+ "  c1	int," + "  c2	char(50)," + "  c3	int," + "  c4   char(50),"
						+ "  c5   int," + "  c6   varchar(1000))"); 		
				assertUpdateCount(s, 0, "create index I11 on T1(c3, c1, c5)"); 	
				assertUpdateCount(s, 0, "create table T2("
						+ "  c1 	int)"); 		
				assertUpdateCount(s, 0, "create table T3("
						+ "  c1	char(20) not null primary key)"); 		
				assertUpdateCount(s, 0, "create table T4("
						+ "  c1 	char(20) references T3(c1) on delete cascade)"); 		

				/* fill the newly created tables */
				for (int i = 0; i < 1000; i++) {
					sb.append('a');
				}

				pstmt = s.getConnection().prepareStatement("insert into T1 values (?, ?, ?, ?, ?, ?), " +
						"(?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), "
						+ "(?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)");

				largeString = new String(sb);
				for (int i = 246; i > 0; i = i - 5) {
					int k = 0;

					for (int j = 0; j < 5; j++) {
						pstmt.setInt(1 + k, i + (4 - j));
						pstmt.setString(2 + k, Integer.toString(i));
						pstmt.setInt(3 + k, i + j);
						pstmt.setString(4 + k, Integer.toString(i));
						pstmt.setInt(5 + k, i);
						pstmt.setString(6 + k, largeString);

						k += 6;
					}

					assertUpdateCount(pstmt, 5);
				}
				s.executeUpdate("insert into t2 values (1)");
				pstmt.close();
			}

		}, props), props, true);
	}

	/**
	 * Test the virtual memory heap.
	 * 
	 * @throws SQLException
	 */
	public void testVirtualMemoryHeap() throws SQLException {
		PreparedStatement select = prepareStatement("select c1, c3 from t1 where c3 > 1 and c1 > 0 for update");
		Statement update = createStatement();
		String cursorName;
		ResultSet cursor;
		int expectedValue = 1;

		/* drop index and recreate it to be sure that it is ascending
                 * (other subtests may have changed it)
                 */
		assertUpdateCount(update, 0, "drop index I11");
		assertUpdateCount(update, 0, "create index I11 on T1 (c3, c1, c5)");

		cursor = select.executeQuery(); // cursor is now open
		cursorName = cursor.getCursorName();

		/* scan the entire table except the last row. */
		for (int i = 0; i < SIZE_OF_T1 - 1; i++) {	
            // 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);
			assertEquals("Virtual memory heap test failed! Got unexpected value.", expectedValue, cursor.getInt(2));
			update.execute("update t1 set c3 = c3 + 250 where current of " + cursorName);
		}
		assertFalse(
				"Update with virtual memory heap failed! Still got rows.",
				cursor.next());

		cursor.close();
		update.close();

		/* see what we have in the table */
		select = prepareStatement("select c1, c3 from t1");
		cursor = select.executeQuery(); // cursor is now open
		for (int i = 0; i < SIZE_OF_T1; i++) {
			assertEquals(cursor.next(), true);
		}
		assertFalse(
				"Update with virtual memory heeap failed! Got more rows.",
				cursor.next());

		select.close();
		cursor.close();

		rollback();
	}

	/**
	 * Tests non covering index.
	 * 
	 * @throws SQLException
	 */
	public void testNonCoveringIndex() throws SQLException {
		PreparedStatement select;
		Statement update;
		ResultSet cursor;
		String cursorName;

		update = createStatement();
		select = prepareStatement("select c3, c2 from t1 where c3 > 125 and c1 > 0 for update");
		cursor = select.executeQuery(); // cursor is now open
		cursorName = cursor.getCursorName();

		for (int i = 0; i < (SIZE_OF_T1 / 2); i++) {
			assertEquals(cursor.next(), true);
			update.execute("update t1 set c3 = c3 + 25 where current of " + cursorName);
		}
		assertFalse(
				"Update using noncovering index failed! Still got rows.",
				cursor.next());

		cursor.close();
		select.close();

		/* see what we have in the table */
		select = prepareStatement("select c1, c3 from t1");
		cursor = select.executeQuery(); // cursor is now open
		for (int i = 0; i < SIZE_OF_T1; i++) {
			assertEquals(cursor.next(), true);
		}
		assertFalse(
				"Update using noncovering index failed! Got more rows.", cursor
						.next());

		select.close();
		cursor.close();

		rollback();
	}

	/**
	 * Tests descending index.
	 * 
	 * @throws SQLException
	 */
	public void testDescendingIndex() throws SQLException {
		PreparedStatement select;
		Statement update;
		ResultSet cursor;

		update = createStatement();

		/* drop index and recreate it */
		assertUpdateCount(update, 0, "drop index I11");
		assertUpdateCount(update, 0, "create index I11 on T1 (c3 desc, c1, c5 desc)");
		commit();

		update = createStatement();
		select = prepareStatement("select c3, c1 from t1 where c3 > 125 and c1 > 0 for update");
		cursor = select.executeQuery(); // cursor is now open

		for (int i = 0; i < (SIZE_OF_T1 / 2); i++) {
			assertEquals(cursor.next(), true);
			if ((i % 2) == 0) {
				update.execute("update t1 set c3 = c3 + 1 where current of " + cursor.getCursorName());
			} else {
				update.execute("update t1 set c3 = c3 - 1 where current of " + cursor.getCursorName());
			}
		}
		assertFalse("Update using desc index failed! Got more rows.",
				cursor.next());

		cursor.close();
		select.close();

		/* see what we have in the table */
		select = prepareStatement("select c3, c2 from t1");
		cursor = select.executeQuery(); // cursor is now open
		for (int i = 0; i < SIZE_OF_T1; i++) {
			assertEquals(cursor.next(), true);
		}
		assertFalse("Update using desc index failed! Got more rows.",
				cursor.next());

		select.close();
		cursor.close();

		rollback();
	}

	/**
	 * Test if the correct warnings are raised.
	 * 
	 * @throws SQLException
	 */
	public void testUpdateDeleteWarning() throws SQLException {
		Statement stmt = createStatement();
		SQLWarning sw;

		stmt.executeUpdate("update t2 set c1 = 2 where c1 = 1");
		sw = stmt.getWarnings();
		assertNull("The update should not return a warning.", sw);

		stmt.executeUpdate("update t2 set c1 = 2 where c1 = 1");
		sw = stmt.getWarnings();
		assertNotNull("The update should return a warning.", sw);
		assertEquals("Wrong sql state.", EXPECTED_SQL_CODE, sw
				.getSQLState());

		stmt.executeUpdate("delete from t2 where c1 = 2");
		sw = stmt.getWarnings();
		assertNull("The delete should not return a warning.", sw);

		stmt.executeUpdate("delete from t2 where c1 = 2");
		sw = stmt.getWarnings();
		assertNotNull("The delete should return a warning.", sw);
		assertEquals("Wrong sql state.", EXPECTED_SQL_CODE, sw
				.getSQLState());

		stmt.executeUpdate("delete from t3");
		sw = stmt.getWarnings();
		assertNotNull("The delete cascade should return a warning.", sw);
		assertEquals("Wrong sql state.", EXPECTED_SQL_CODE, sw
				.getSQLState());

		stmt.close();

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