/**
 *  Derby - Class org.apache.derbyTesting.functionTests.tests.lang.NestedCommitTest
 *
 * 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.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * Test nested commit
 */

public final class NestedCommitTest extends BaseJDBCTestCase {

    /**
     * Public constructor required for running test as standalone JUnit.
     * @param name test name
     */
    public NestedCommitTest(String name)
    {
        super(name);
    }

    public static Test suite()
    {
        return TestConfiguration.defaultSuite(NestedCommitTest.class);
    }

    public void testNestedCommit() throws Exception
    {
        ResultSet rs;
        CallableStatement cSt;
        Statement st = createStatement();

        String [][] expRS;
        String [] expColNames;

        st.getConnection().setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);

        try {
            // Make sure that we cannot do a commit/rollback on a nested
            // connection when we are in the middle of something that has to be
            // atomic (e.g. DML). commit/rollback on a nested connection is
            // only permitted when we are doing something simple like CALL
            // myMethod() or VALUES myMethod().

            st.executeUpdate("CREATE PROCEDURE doConnCommit() "
                    + "       DYNAMIC RESULT SETS 0 LANGUAGE JAVA "
                    + "       EXTERNAL NAME "
                    + "'org.apache.derbyTesting.functionTests.util.Triggers"
                    + ".doConnCommit' "
                    + "    CONTAINS SQL"
                    + "       PARAMETER STYLE JAVA");

            st.executeUpdate("CREATE PROCEDURE doConnRollback() "
                    + "       DYNAMIC RESULT SETS 0 LANGUAGE JAVA "
                    + "       EXTERNAL NAME "
                    + "'org.apache.derbyTesting.functionTests.util.Triggers"
                    + ".doConnRollback' "
                    + "    CONTAINS SQL"
                    + "       PARAMETER STYLE JAVA");

            st.executeUpdate("CREATE PROCEDURE doConnStmt(IN TEXT CHAR(50)) "
                    + "       DYNAMIC RESULT SETS 0 LANGUAGE JAVA "
                    + "       EXTERNAL NAME "
                    + "'org.apache.derbyTesting.functionTests.util.Triggers"
                    + ".doConnStmt' "
                    + "    CONTAINS SQL"
                    + "       PARAMETER STYLE JAVA");

            st.executeUpdate("CREATE FUNCTION doConnCommitInt() "
                    + "       RETURNS INT EXTERNAL NAME "
                    + "'org.apache.derbyTesting.functionTests.util.Triggers"
                    + ".doConnCommitInt' "
                    + "       LANGUAGE JAVA PARAMETER STYLE JAVA");

            st.executeUpdate("CREATE FUNCTION doConnStmtInt(TEXT CHAR(50)) "
                    + "       RETURNS INT EXTERNAL NAME "
                    + "'org.apache.derbyTesting.functionTests.util.Triggers"
                    + ".doConnStmtInt' "
                    + "       LANGUAGE JAVA PARAMETER STYLE JAVA");

            st.executeUpdate("create table x (x int)");
            st.executeUpdate("insert into x values 1,2,3,4");

            setAutoCommit(false);

            // All the following calls should succeed
            //
            cSt = prepareCall("call doConnCommit()");
            assertUpdateCount(cSt, 0);

            cSt = prepareCall("call doConnRollback()");
            assertUpdateCount(cSt, 0);

            // No longer supported as language statements.
            // call doConnStmt('commit'); call doConnStmt('rollback');

            cSt = prepareCall("call doConnStmt('call doConnCommit()')");
            assertUpdateCount(cSt, 0);

            cSt = prepareCall("call doConnStmt('call doConnRollback()')");
            assertUpdateCount(cSt, 0);

            // call doConnStmt('call doConnStmt(''call
            // doConnStmt(''''commit'''')'')');

            rs = st.executeQuery("values doConnCommitInt()");
            assertTrue(rs.next());
            try {
                rs.getString(1);
            } catch (SQLException e) {
                assertSQLState("XCL16", e);
            }

            // values doConnStmtInt('commit');
            // values doConnStmtInt('rollback');
            // values doConnStmtInt('call doConnStmt(
            //                     ''call doConnStmt(''''commit'''')'')');

            rs = st.executeQuery(
                    "values doConnStmtInt('values doConnCommitInt()')");
            JDBC.assertFullResultSet(rs, new String [][]{{"1"}}, true);

            // fail

            assertStatementError(
                    "38000", st,
                    "insert into x select x+doConnCommitInt() from x");

            assertStatementError(
                    "38000", st,
                    "delete from x where x in (select x+doConnCommitInt() from x)");

            assertStatementError(
                    "38000", st,
                    "delete from x where x = doConnCommitInt()");

            assertStatementError(
                    "38000", st,
                    "update x set x = doConnCommitInt()");

            // insert into x values doConnStmtInt(
            //          'call doConnStmt(''call doConnStmt(''''commit'''')'')');
            // select doConnStmtInt('call doConnStmt(
            //          ''call doConnStmt(''''rollback'''')'')') from x;

            assertStatementError(
                    "38000", st,
                    "select doConnStmtInt('call doConnStmt(" +
                            "''call doConnCommit()'')') from x");

            cSt = prepareCall(
                "call doConnStmt('set isolation serializable')");
            assertUpdateCount(cSt, 0);

        } finally {
            // clean up
            dontThrow(st, "drop table x");
            dontThrow(st, "drop procedure doConnCommit");
            dontThrow(st, "drop procedure doConnRollback");
            dontThrow(st, "drop function doConnCommitInt");
            dontThrow(st, "drop procedure doConnStmt");
            dontThrow(st, "drop function doConnStmtInt");
            commit();
        }
    }

    private void dontThrow(Statement st, String stm) {
        try {
            st.executeUpdate(stm);
        } catch (SQLException e) {
            // ignore, best effort here
        }
    }
}
