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

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.TestConfiguration;

public class OCRecoveryTest extends BaseJDBCTestCase {

    private static final String tableName = "RECTEST1";
    
    public OCRecoveryTest(String name) {
        super(name);
    }

    public static Test suite()
    {
        BaseTestSuite suite = new BaseTestSuite("OCRecoveryTest");
        //DERBY-4647 exec does not work on weme
        if (BaseTestCase.isJ9Platform())
            return suite;
        suite.addTest(decorateTest());
        return suite;
    }

    private static Test decorateTest()
    {
        Test test = TestConfiguration.embeddedSuite(
                        OCRecoveryTest.class);
        // using a singleUseDatabaseDecorator which should not create
        // the database until the first connection is made
        return TestConfiguration.singleUseDatabaseDecorator(test,
            "OCRecoveryDB");
    }

    public void testOCRecovery() throws Exception
    {
        
        // Now call forked processes - each of these will do something,
        // then *not* shutdown, forcing the next one to recover the
        // database.
        // Pass in the name of the database to be used.
        assertLaunchedJUnitTestMethod("org.apache.derbyTesting." +
                "functionTests.tests.store.OCRecoveryTest.launchOCRecovery_1",
                "OCRecoveryDB");
        assertLaunchedJUnitTestMethod("org.apache.derbyTesting." +
                "functionTests.tests.store.OCRecoveryTest.launchOCRecovery_2",
                "OCRecoveryDB");
        assertLaunchedJUnitTestMethod("org.apache.derbyTesting." +
                "functionTests.tests.store.OCRecoveryTest.launchOCRecovery_3",
                "OCRecoveryDB");
        assertLaunchedJUnitTestMethod("org.apache.derbyTesting." +
                "functionTests.tests.store.OCRecoveryTest.launchOCRecovery_4",
                "OCRecoveryDB");
    }

    public void launchOCRecovery_1() throws Exception
    {
        // setup to test restart recovery of online compress.  Real work
        // is done in next method launchOCRecovery_2 which will run restart
        // recovery on the work done in this step.
        // open a connection to the database, this should create the db
        getConnection();
        setAutoCommit(false);        
        createAndLoadTable(tableName, true, 5000, 0);
        Statement st = createStatement();
        st.executeUpdate("DELETE FROM " + tableName);
        commit();
        callCompress(tableName, true, true, true, true);
        st.close();        
    }

    public void launchOCRecovery_2() throws Exception
    {
        setAutoCommit(false);
        assertCheckTable(tableName);
        // make sure we can add data to the existing table after redo
        // recovery.
        createAndLoadTable(tableName, false, 6000, 0);
        assertCheckTable(tableName);
        String table_name =  tableName + "_2";
        // setup to test redo recovery on: 
        //      create table, delete rows, compress, commit
        createAndLoadTable(table_name, true, 2000, 0);
        Statement st = createStatement();
        st.executeUpdate("DELETE FROM " + tableName);
        commit();
        callCompress(tableName, true, true, true, true);

        st.close();
    }

    public void launchOCRecovery_3() throws SQLException
    {
        setAutoCommit(false);
        String table_name =  tableName + "_2";
        assertCheckTable(table_name);
        // make sure we can add data to the existing table after redo
        // recovery.
        createAndLoadTable(tableName, false, 2000, 0);
        assertCheckTable(tableName);

        // setup to test redo recovery on: 
        //      add more rows, delete rows, commit, compress, no commit
        createAndLoadTable(table_name, false, 4000, 2000);
        Statement st = createStatement();
        st.executeUpdate("DELETE FROM " + table_name);
        commit();
        callCompress(table_name, true, true, true, false);
        st.close();
    }
    
    public void launchOCRecovery_4() throws SQLException
    {
        // oc_rec3 left the table  with no rows, but compress command
        // did not commit.
        setAutoCommit(false);
        String table_name =  tableName + "_2";
        assertCheckTable(table_name);
        // make sure we can add data to the existing table after redo
        // recovery.
        createAndLoadTable(table_name, false, 6000, 0);
        assertCheckTable(table_name);
    }
    
    /**
     * Create and load a table.
     * <p>
     * If create_table is set creates a test data table with indexes.
     * Loads num_rows into the table.  This table defaults to 32k page size.
     * This schema fits 25 rows per page
     * <p>
     *
     * @param create_table  If true, create new table - otherwise load into
     *                      existing table.
     * @param tblname       table to use.
     * @param num_rows      number of rows to add to the table.
     *
     * @exception  SQLException  Standard exception policy.
     **/
    private void createAndLoadTable(
    String      tblname,
    boolean     create_table,
    int         num_rows,
    int         start_value)
        throws SQLException
    {
        if (create_table)
        {
            Statement s = createStatement();

            s.execute(
                "CREATE TABLE " + tblname + 
                    "(keycol int, indcol1 int, indcol2 int, indcol3 int, " +
                    "data1 varchar(2000), data2 varchar(2000))");
            s.close();
            println("table created: " + tblname);
        }

        PreparedStatement insert_stmt = 
                prepareStatement(
                    "INSERT INTO " + tblname + " VALUES(?, ?, ?, ?, ?, ?)");

        char[]  data1_data = new char[500];
        char[]  data2_data = new char[500];

        for (int i = 0; i < data1_data.length; i++)
        {
            data1_data[i] = 'a';
            data2_data[i] = 'b';
        }

        String  data1_str = new String(data1_data);
        String  data2_str = new String(data2_data);

        int row_count = 0;
            for (int i = start_value; row_count < num_rows; row_count++, i++)
            {
                insert_stmt.setInt(1, i);               // keycol
                insert_stmt.setInt(2, i * 10);          // indcol1
                insert_stmt.setInt(3, i * 100);         // indcol2
                insert_stmt.setInt(4, -i);              // indcol3
                insert_stmt.setString(5, data1_str);    // data1_data
                insert_stmt.setString(6, data2_str);    // data2_data

                insert_stmt.execute();
            }

        if (create_table)
        {
            Statement s = createStatement();

            s.execute(
                "create index " + tblname + "_idx_keycol on " + tblname +
                    "(keycol)");
            s.execute(
                "create index " + tblname + "_idx_indcol1 on " + tblname +
                    "(indcol1)");
            s.execute(
                "create index " + tblname + "_idx_indcol2 on " + tblname +
                    "(indcol2)");
            s.execute(
                "create unique index " + tblname + "_idx_indcol3 on " + 
                    tblname + "(indcol3)");
            s.close();
        }
        commit();
    }
    
    /**
     * call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE() system procedure.
     **/
    private void callCompress(
    String      tableName,
    boolean     purgeRows,
    boolean     defragmentRows,
    boolean     truncateEnd,
    boolean     commit_operation)
        throws SQLException
    {
        CallableStatement cstmt = 
                prepareCall(
                "call SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(?, ?, ?, ?, ?)");
        cstmt.setString(1, getTestConfiguration().getUserName());
        cstmt.setString(2, tableName);
        cstmt.setInt   (3, purgeRows      ? 1 : 0);
        cstmt.setInt   (4, defragmentRows ? 1 : 0);
        cstmt.setInt   (5, truncateEnd    ? 1 : 0);
        cstmt.execute();
        if (commit_operation) {
            commit();
        }
    }

}
