/*
 * Derby - Class org.apache.derbyTesting.functionTests.tests.jdbcapi.ProcedureTest
 *
 * 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.math.BigDecimal;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import junit.framework.Test;
import org.apache.derby.iapi.types.HarmonySerialBlob;
import org.apache.derby.iapi.types.HarmonySerialClob;
import org.apache.derbyTesting.functionTests.tests.lang.Price;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * Tests of stored procedures.
 */
public class ProcedureTest extends BaseJDBCTestCase {

    /**
     * Creates a new <code>ProcedureTest</code> instance.
     *
     * @param name name of the test
     */
    public ProcedureTest(String name) {
        super(name);
    }

    // TESTS

    /**
     * Tests that <code>Statement.executeQuery()</code> fails when no
     * result sets are returned.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithNoDynamicResultSets() throws SQLException {
        Statement stmt = createStatement();
        try {
            stmt.executeQuery("CALL RETRIEVE_DYNAMIC_RESULTS(0)");
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that <code>Statement.executeQuery()</code> succeeds when
     * one result set is returned from a stored procedure.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithOneDynamicResultSet() throws SQLException {
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("CALL RETRIEVE_DYNAMIC_RESULTS(1)");
        assertNotNull("executeQuery() returned null.", rs);
        assertSame(stmt, rs.getStatement());
        JDBC.assertDrainResultsHasData(rs);
    }

    /**
     * Tests that <code>Statement.executeQuery()</code> fails when
     * multiple result sets are returned.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithMoreThanOneDynamicResultSet()
        throws SQLException
    {
        Statement stmt = createStatement();
        try {
            stmt.executeQuery("CALL RETRIEVE_DYNAMIC_RESULTS(2)");
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertMultipleResultsFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that <code>Statement.executeUpdate()</code> succeeds when
     * no result sets are returned.
     *
     * @exception SQLException if a database error occurs
     */
    public void testExecuteUpdateWithNoDynamicResultSets()
        throws SQLException
    {
        Statement stmt = createStatement();
        assertUpdateCount(stmt, 0, "CALL RETRIEVE_DYNAMIC_RESULTS(0)");
        JDBC.assertNoMoreResults(stmt);
    }

    /**
     * Tests that <code>Statement.executeUpdate()</code> fails when a
     * result set is returned from a stored procedure.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteUpdateWithOneDynamicResultSet() throws SQLException {
        Statement stmt = createStatement();
        try {
            stmt.executeUpdate("CALL RETRIEVE_DYNAMIC_RESULTS(1)");
            fail("executeUpdate() didn't fail.");
        } catch (SQLException sqle) {
            assertResultsFromExecuteUpdate(sqle);
        }
    }

    /**
     * Tests that <code>PreparedStatement.executeQuery()</code> fails
     * when no result sets are returned.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithNoDynamicResultSets_prepared()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        ps.setInt(1, 0);
        try {
            ps.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that <code>PreparedStatement.executeQuery()</code>
     * succeeds when one result set is returned from a stored
     * procedure.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithOneDynamicResultSet_prepared()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        ps.setInt(1, 1);
        ResultSet rs = ps.executeQuery();
        assertNotNull("executeQuery() returned null.", rs);
        assertSame(ps, rs.getStatement());
        JDBC.assertDrainResultsHasData(rs);

    }

    /**
     * Tests that <code>PreparedStatement.executeQuery()</code> fails
     * when multiple result sets are returned.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithMoreThanOneDynamicResultSet_prepared()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        ps.setInt(1, 2);
        try {
            ps.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertMultipleResultsFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that <code>PreparedStatement.executeUpdate()</code>
     * succeeds when no result sets are returned.
     *
     * @exception SQLException if a database error occurs
     */
    public void testExecuteUpdateWithNoDynamicResultSets_prepared()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        ps.setInt(1, 0);
        assertUpdateCount(ps, 0);
        JDBC.assertNoMoreResults(ps);
    }

    /**
     * Tests that <code>PreparedStatement.executeUpdate()</code> fails
     * when a result set is returned from a stored procedure.
     *
     * @exception SQLException if a database error occurs
     */
    public void testExecuteUpdateWithOneDynamicResultSet_prepared()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        ps.setInt(1, 1);
        try {
            ps.executeUpdate();
            fail("executeUpdate() didn't fail.");
        } catch (SQLException sqle) {
            assertResultsFromExecuteUpdate(sqle);
        }
    }

    /**
     * Tests that <code>CallableStatement.executeQuery()</code> fails
     * when no result sets are returned.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithNoDynamicResultSets_callable()
        throws SQLException
    {
        CallableStatement cs =
            prepareCall("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        cs.setInt(1, 0);
        try {
            cs.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that <code>CallableStatement.executeQuery()</code>
     * succeeds when one result set is returned from a stored
     * procedure.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithOneDynamicResultSet_callable()
        throws SQLException
    {
        CallableStatement cs =
            prepareCall("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        cs.setInt(1, 1);
        ResultSet rs = cs.executeQuery();
        assertNotNull("executeQuery() returned null.", rs);
        assertSame(cs, rs.getStatement());
        JDBC.assertDrainResultsHasData(rs);
    }

    /**
     * Tests that <code>CallableStatement.executeQuery()</code> fails
     * when multiple result sets are returned.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteQueryWithMoreThanOneDynamicResultSet_callable()
        throws SQLException
    {
        CallableStatement cs =
            prepareCall("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        cs.setInt(1, 2);
        try {
            cs.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertMultipleResultsFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that <code>CallableStatement.executeUpdate()</code>
     * succeeds when no result sets are returned.
     *
     * @exception SQLException if a database error occurs
     */
    public void testExecuteUpdateWithNoDynamicResultSets_callable()
        throws SQLException
    {
        CallableStatement cs =
            prepareCall("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        cs.setInt(1, 0);
        assertUpdateCount(cs, 0);
        JDBC.assertNoMoreResults(cs);
    }

    /**
     * Tests that <code>CallableStatement.executeUpdate()</code> fails
     * when a result set is returned from a stored procedure.
     * @exception SQLException if a database error occurs
     */
    public void testExecuteUpdateWithOneDynamicResultSet_callable()
        throws SQLException
    {
        CallableStatement cs =
            prepareCall("CALL RETRIEVE_DYNAMIC_RESULTS(?)");
        cs.setInt(1, 1);
        try {
            cs.executeUpdate();
            fail("executeUpdate() didn't fail.");
        } catch (SQLException sqle) {
            assertResultsFromExecuteUpdate(sqle);
        }
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeQuery()</code> are correctly rolled back when
     * <code>Connection.rollback()</code> is called.
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWithExecuteQuery() throws SQLException {

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("CALL PROC_WITH_SIDE_EFFECTS(1)");
        rs.close();
        rollback();
        
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
 
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeUpdate()</code> are correctly rolled back when
     * <code>Connection.rollback()</code> is called.
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWithExecuteUpdate() throws SQLException {
        Statement stmt = createStatement();
        stmt.executeUpdate("CALL PROC_WITH_SIDE_EFFECTS(0)");
        rollback();
        
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
 
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeQuery()</code> are correctly rolled back when the
     * query fails because the number of returned result sets is zero.
     *
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWhenExecuteQueryReturnsNothing()
        throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(true);
        Statement stmt = createStatement();
        try {
            stmt.executeQuery("CALL PROC_WITH_SIDE_EFFECTS(0)");
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }

        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeQuery()</code> are correctly rolled back when the
     * query fails because the number of returned result sets is more
     * than one.
     *
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWhenExecuteQueryReturnsTooMuch()
        throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(true);
        Statement stmt = createStatement();
        try {
            stmt.executeQuery("CALL PROC_WITH_SIDE_EFFECTS(2)");
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertMultipleResultsFromExecuteQuery(sqle);
        }
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
 
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeUpdate()</code> are correctly rolled back when the
     * query fails because the stored procedure returned a result set.
     *
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWhenExecuteUpdateReturnsResults()
        throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(true);
        Statement stmt = createStatement();
        try {
            stmt.executeUpdate("CALL PROC_WITH_SIDE_EFFECTS(1)");
            fail("executeUpdate() didn't fail.");
        } catch (SQLException sqle) {
            assertResultsFromExecuteUpdate(sqle);
        }
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
 
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeQuery()</code> are correctly rolled back when the
     * query fails because the number of returned result sets is zero.
     *
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWhenExecuteQueryReturnsNothing_prepared()
        throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(true);
        PreparedStatement ps =
            prepareStatement("CALL PROC_WITH_SIDE_EFFECTS(?)");
        ps.setInt(1, 0);
        try {
            ps.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }
        Statement stmt = createStatement();
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
 
    }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeQuery()</code> are correctly rolled back when the
     * query fails because the number of returned result sets is more
     * than one.
     *
     * @exception SQLException if a database error occurs
     */
    public void testRollbackStoredProcWhenExecuteQueryReturnsTooMuch_prepared()
        throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(true);
        PreparedStatement ps =
            prepareStatement("CALL PROC_WITH_SIDE_EFFECTS(?)");
        ps.setInt(1, 2);
        try {
            ps.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertMultipleResultsFromExecuteQuery(sqle);
        }
        Statement stmt = createStatement();
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
     }

    /**
     * Tests that the effects of executing a stored procedure with
     * <code>executeUpdate()</code> are correctly rolled back when the
     * query fails because the stored procedure returned a result set.
     *
     * @exception SQLException if a database error occurs
     */
    public void
        testRollbackStoredProcWhenExecuteUpdateReturnsResults_prepared()
        throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(true);
        PreparedStatement ps =
            prepareStatement("CALL PROC_WITH_SIDE_EFFECTS(?)");
        ps.setInt(1, 1);
        try {
            ps.executeUpdate();
            fail("executeUpdate() didn't fail.");
        } catch (SQLException sqle) {
            assertResultsFromExecuteUpdate(sqle);
        }
        Statement stmt = createStatement();
        // Expect Side effects from stored procedure to be rolled back.
        JDBC.assertEmpty(stmt.executeQuery("SELECT * FROM SIMPLE_TABLE"));
 
    }

    /**
     * Tests that closed result sets are not returned when calling
     * <code>executeQuery()</code>.
     * @exception SQLException if a database error occurs
     */
    public void testClosedDynamicResultSetsFromExecuteQuery()
        throws SQLException
    {
        Statement stmt = createStatement();
        try {
            ResultSet rs = stmt.executeQuery("CALL RETRIEVE_CLOSED_RESULT()");
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that closed result sets are ignored when calling
     * <code>executeUpdate()</code>.
     * @exception SQLException if a database error occurs
     */
    public void testClosedDynamicResultSetsFromExecuteUpdate()
        throws SQLException
    {
        Statement stmt = createStatement();
        stmt.executeUpdate("CALL RETRIEVE_CLOSED_RESULT()");
        JDBC.assertNoMoreResults(stmt);
    }

    /**
     * Tests that dynamic result sets from other connections are
     * ignored when calling <code>executeQuery</code>.
     * @exception SQLException if a database error occurs
     */
    public void testDynamicResultSetsFromOtherConnectionWithExecuteQuery()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_EXTERNAL_RESULT(?,?,?)");
        
        ps.setString(1, getTestConfiguration().getDefaultDatabaseName());
        ps.setString(2, getTestConfiguration().getUserName());
        ps.setString(3, getTestConfiguration().getUserPassword());
        try {
            ps.executeQuery();
            fail("executeQuery() didn't fail.");
        } catch (SQLException sqle) {
            assertNoResultSetFromExecuteQuery(sqle);
        }
    }

    /**
     * Tests that dynamic result sets from other connections are
     * ignored when calling <code>executeUpdate</code>.
     * @exception SQLException if a database error occurs
     */
    public void testDynamicResultSetsFromOtherConnectionWithExecuteUpdate()
        throws SQLException
    {
        PreparedStatement ps =
            prepareStatement("CALL RETRIEVE_EXTERNAL_RESULT(?,?,?)");
        
        ps.setString(1, getTestConfiguration().getDefaultDatabaseName());
        ps.setString(2, getTestConfiguration().getUserName());
        ps.setString(3, getTestConfiguration().getUserPassword());
        
        ps.executeUpdate();
        
        JDBC.assertNoMoreResults(ps);
    }

    /**
     * Test that a call to getBlob() to retrieve the value of a non-BLOB
     * parameter fails with the expected SQLException. Used to throw
     * ClassCastException, see DERBY-4970.
     */
    public void testGetBlobFromIntParameter() throws SQLException {
        CallableStatement cs = prepareCall("call int_out(?)");
        cs.registerOutParameter(1, Types.INTEGER);
        cs.execute();
        try {
            cs.getBlob(1);
            fail("getBlob() on int parameter expected to fail");
        } catch (SQLException sqle) {
            assertSQLState("22005", sqle);
        }
    }

    /**
     * Test that a call to getClob() to retrieve the value of a non-CLOB
     * parameter fails with the expected SQLException. Used to throw
     * ClassCastException, see DERBY-4970.
     */
    public void testGetClobFromIntParameter() throws SQLException {
        CallableStatement cs = prepareCall("call int_out(?)");
        cs.registerOutParameter(1, Types.INTEGER);
        cs.execute();
        try {
            cs.getClob(1);
            fail("getClob() on int parameter expected to fail");
        } catch (SQLException sqle) {
            assertSQLState("22005", sqle);
        }
    }

    /**
     * Test that a statement severity error inside a procedure doesn't kill
     * the top-level statement that executes the stored procedure. Regression
     * test case for DERBY-5280.
     */
    public void testStatementSeverityErrorInProcedure() throws SQLException {
        Statement s = createStatement();
        s.execute("create procedure proc_5280() language java " +
                  "parameter style java external name '" +
                  getClass().getName() + ".proc_5280' reads sql data");
        s.execute("call proc_5280()");
    }

    /**
     * Procedure that drops a non-existent table and ignores the exception
     * thrown because of it. Used by the regression test case for DERBY-5280.
     */
    public static void proc_5280() throws SQLException {
        Connection c = DriverManager.getConnection("jdbc:default:connection");
        Statement s = c.createStatement();

        // Drop a non-existent table and verify that it fails with the
        // expected exception. Ignore the exception.
        try {
            s.execute("drop table this_table_does_not_exist");
            fail("dropping non-existent table should fail");
        } catch (SQLException sqle) {
            assertSQLState("42Y55", sqle);
        }

        // The statement should still work.
        JDBC.assertSingleValueResultSet(s.executeQuery("values 1"), "1");
    }

    /**
     * Test that INOUT args are preserved over procedure invocations.
     * See DERBY-2515.
     */
    public  void    test_2515()   throws Exception
    {
        Connection  conn = getConnection();
        
        PreparedStatement ps = conn.prepareStatement
            (
             "create type price_2515 external name 'org.apache.derbyTesting.functionTests.tests.lang.Price' language java\n"
             );
        ps.execute();
        ps.close();
        
        ps = conn.prepareStatement
            (
             "create procedure proc_2515\n" +
             "(\n" +
             "\tin passNumber int,\n" +
             "\tout returnMessage varchar( 32672 ),\n" +
             "\tinout bigintArg bigint,\n" +
             "\tinout blobArg blob,\n" +
             "inout booleanArg boolean,\n" +
             "inout charArg char( 6 ),\n" +
             "inout charForBitDataArg char( 3 ) for bit data,\n" +
             "inout clobArg clob,\n" +
             "inout dateArg date,\n" +
             "inout decimalArg decimal,\n" +
             "inout doubleArg double,\n" +
             "inout intArg int,\n" +
             "inout longVarcharArg long varchar,\n" +
             "inout longVarcharForBitDataArg long varchar for bit data,\n" +
             "inout realArg real,\n" +
             "inout smallintArg smallint,\n" +
             "inout timeArg time,\n" +
             "inout timestampArg timestamp,\n" +
             "inout priceArg price_2515,\n" +
             "inout varcharArg varchar( 20 ),\n" +
             "inout varcharForBitDataArg varchar( 3 ) for bit data\n" +
            ")\n" +
            "parameter style java language java no sql\n" +
            "external name '" + ProcedureTest.class.getName() + ".proc_2515'"
             );
        ps.execute();
        ps.close();

        CallableStatement   cs = conn.prepareCall
            ( "call proc_2515( ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ? )" );
        AllTypesTuple   firstArgs = makeFirstAllTypesTuple();

        int     idx = 2;
        
        cs.registerOutParameter( idx++, Types.VARCHAR );

        cs.registerOutParameter( idx, Types.BIGINT );
        cs.setLong( idx++, firstArgs.get_bigintArg().longValue() );

        cs.registerOutParameter( idx, Types.BLOB );
        cs.setBlob( idx++, firstArgs.get_blobArg() );

        cs.registerOutParameter( idx, Types.BOOLEAN );
        cs.setBoolean( idx++, firstArgs.get_booleanArg().booleanValue() );

        cs.registerOutParameter( idx, Types.CHAR );
        cs.setString( idx++, firstArgs.get_charArg() );

        cs.registerOutParameter( idx, Types.BINARY );
        cs.setBytes( idx++, firstArgs.get_charForBitDataArg() );

        cs.registerOutParameter( idx, Types.CLOB );
        cs.setClob( idx++, firstArgs.get_clobArg() );

        cs.registerOutParameter( idx, Types.DATE );
        cs.setDate( idx++, firstArgs.get_dateArg() );

        cs.registerOutParameter( idx, Types.DECIMAL );
        cs.setBigDecimal( idx++, firstArgs.get_decimalArg() );

        cs.registerOutParameter( idx, Types.DOUBLE );
        cs.setDouble( idx++, firstArgs.get_doubleArg().doubleValue() );

        cs.registerOutParameter( idx, Types.INTEGER );
        cs.setInt( idx++, firstArgs.get_intArg().intValue() );

        cs.registerOutParameter( idx, Types.LONGVARCHAR );
        cs.setString( idx++, firstArgs.get_longVarcharArg() );

        cs.registerOutParameter( idx, Types.LONGVARBINARY );
        cs.setBytes( idx++, firstArgs.get_longVarcharForBitDataArg() );

        cs.registerOutParameter( idx, Types.REAL );
        cs.setFloat( idx++, firstArgs.get_realArg().floatValue() );

        cs.registerOutParameter( idx, Types.SMALLINT );
        cs.setShort( idx++, firstArgs.get_smallintArg().shortValue() );

        cs.registerOutParameter( idx, Types.TIME );
        cs.setTime( idx++, firstArgs.get_timeArg() );

        cs.registerOutParameter( idx, Types.TIMESTAMP );
        cs.setTimestamp( idx++, firstArgs.get_timestampArg() );

        cs.registerOutParameter( idx, Types.JAVA_OBJECT );
        cs.setObject( idx++, firstArgs.get_priceArg() );

        cs.registerOutParameter( idx, Types.VARCHAR );
        cs.setString( idx++, firstArgs.get_varcharArg() );

        cs.registerOutParameter( idx, Types.VARBINARY );
        cs.setBytes( idx++, firstArgs.get_varcharForBitDataArg() );

        cs.setInt( 1, 0 );
        cs.execute();
        assertEquals( "", cs.getString( 2 ) );  // the return message should be empty, meaning the call args were what the procedure expected
        assertEquals( "", makeSecondAllTypesTuple().compare( getActualReturnArgs( cs ) ) );

        cs.setInt( 1, 1 );
        cs.execute();
        assertEquals( "", cs.getString( 2 ) );  // the return message should be empty, meaning the call args were what the procedure expected
        assertEquals( "", makeThirdAllTypesTuple().compare( getActualReturnArgs( cs ) ) );

        cs.setInt( 1, 2 );
        cs.execute();
        assertEquals( "", cs.getString( 2 ) );  // the return message should be empty, meaning the call args were what the procedure expected
        assertEquals( "", makeFourthAllTypesTuple().compare( getActualReturnArgs( cs ) ) );

        ps = conn.prepareStatement( "drop procedure proc_2515" );
        ps.execute();
        ps.close();

        ps = conn.prepareStatement( "drop type price_2515 restrict" );
        ps.execute();
        ps.close();
    }
    private AllTypesTuple   getActualReturnArgs( CallableStatement cs )
        throws Exception
    {
        int idx = 3;
        
        return new AllTypesTuple
            (
             (Long) cs.getObject( idx++ ),
             (Blob) cs.getObject( idx++ ),
             (Boolean) cs.getObject( idx++ ),
             (String) cs.getObject( idx++ ),
             (byte[]) cs.getObject( idx++ ),
             (Clob) cs.getObject( idx++ ),
             (Date) cs.getObject( idx++ ),
             (BigDecimal) cs.getObject( idx++ ),
             (Double) cs.getObject( idx++ ),
             (Integer) cs.getObject( idx++ ),
             (String) cs.getObject( idx++ ),
             (byte[]) cs.getObject( idx++ ),
             (Float) cs.getObject( idx++ ),
             (Integer) cs.getObject( idx++ ),
             (Time) cs.getObject( idx++ ),
             (Timestamp) cs.getObject( idx++ ),
             (Price) cs.getObject( idx++ ),
             (String) cs.getObject( idx++ ),
             (byte[]) cs.getObject( idx++ )
             );
    }

    /**
     * Regression test case for DERBY-2516. If an INOUT parameter had been
     * registered as an output parameter, but no input value had been assigned
     * to it, the client driver would go ahead and execute the statement
     * using null as input.
     */
    public void testInOutParamNotSet() throws SQLException {
        setAutoCommit(false);

        Statement s = createStatement();
        s.execute("create procedure proc_2516 (inout i int) " +
                  "language java parameter style java external name '" +
                  getClass().getName() + ".proc_2516' no sql");

        // Register an INOUT parameter, but don't set it. Expect failure.
        // Client used to execute without error.
        CallableStatement cs = prepareCall("call proc_2516(?)");
        cs.registerOutParameter(1, Types.INTEGER);
        assertStatementError("07000", cs);

        // Should work if the parameter has been set.
        cs.setInt(1, 0);
        cs.execute();
        assertEquals(10, cs.getInt(1));

        // After clearing the parameters, execution should fail. Client used
        // to succeed.
        cs.clearParameters();
        assertStatementError("07000", cs);

        // Setting the parameter again should make it work.
        cs.setInt(1, 1);
        cs.execute();
        assertEquals(10, cs.getInt(1));
    }

    /**
     * Stored procedure used by the regression test case for DERBY-2516.
     *
     * @param i INOUT parameter that gets set to 10 by the procedure
     */
    public static void proc_2516(Integer[] i) {
        i[0] = 10;
    }

    /**
     * Test that we create and execute stored procedures with as many
     * parameters as the Java specification allows.
     */
    public void testMaxNumberOfParameters() throws SQLException {
        // Test with the maximum number of parameters allowed by the
        // Java Virtual Machine specification. That is, 255 parameters.
        testMaxNumberOfParameters(255, true);

        // Test with one more parameter than allowed. Since we have no way
        // to declare a method with that many parameters, expect execution to
        // fail gracefully. The DDL will work, however.
        testMaxNumberOfParameters(256, false);

        // Test with a very high number of parameters. Again, expect DDL to
        // succeed and execution to fail gracefully.
        testMaxNumberOfParameters(10000, false);
    }

    /**
     * Create and execute a stored procedure backed by a Java method with the
     * specified number of parameters.
     *
     * @param params the number of parameters
     * @param methodExists whether or not a method called
     * {@code procWithManyParams} with the specified number of parameters
     * exists
     */
    private void testMaxNumberOfParameters(int params, boolean methodExists)
            throws SQLException {
        final String javaMethod = getClass().getName() + ".procWithManyParams";
        final String sqlProc = "PROC_WITH_LOTS_OF_PARAMETERS";

        // Disable auto-commit for easy cleanup with rollback().
        setAutoCommit(false);

        // Create a procedure with many parameters.

        StringBuffer sb = new StringBuffer("create procedure ");
        sb.append(sqlProc).append('(');
        for (int i = 0; i < params; i++) {
            if (i > 0) {
                sb.append(',');
            }
            sb.append('p').append(i).append(" int");
        }
        sb.append(") language java parameter style java external name '");
        sb.append(javaMethod).append("' no sql");

        Statement s = createStatement();
        s.execute(sb.toString());

        // Check that the database meta-data has correct information.
        DatabaseMetaData dmd = getConnection().getMetaData();

        JDBC.assertFullResultSet(
            dmd.getProcedures(
                null, null, sqlProc),
            new Object[][] {{
                "", "APP", sqlProc, null, null, null,
                javaMethod,
                Integer.valueOf(DatabaseMetaData.procedureNoResult),
                new JDBC.GeneratedId()
            }},
            false);

        JDBC.assertDrainResults(
                dmd.getProcedureColumns(null, null, sqlProc, "%"),
                params);

        // Execute the procedure.
        sb.setLength(0);
        sb.append("call ").append(sqlProc).append('(');
        for (int i = 0; i < params; i++) {
            if (i > 0) {
                sb.append(',');
            }
            sb.append(i);
        }
        sb.append(')');

        if (methodExists) {
            s.execute(sb.toString());
        } else {
            assertCallError("42X50", sb.toString());
        }

        rollback();
    }

    public static void procWithManyParams(
        int p001, int p002, int p003, int p004, int p005, int p006, int p007,
        int p008, int p009, int p010, int p011, int p012, int p013, int p014,
        int p015, int p016, int p017, int p018, int p019, int p020, int p021,
        int p022, int p023, int p024, int p025, int p026, int p027, int p028,
        int p029, int p030, int p031, int p032, int p033, int p034, int p035,
        int p036, int p037, int p038, int p039, int p040, int p041, int p042,
        int p043, int p044, int p045, int p046, int p047, int p048, int p049,
        int p050, int p051, int p052, int p053, int p054, int p055, int p056,
        int p057, int p058, int p059, int p060, int p061, int p062, int p063,
        int p064, int p065, int p066, int p067, int p068, int p069, int p070,
        int p071, int p072, int p073, int p074, int p075, int p076, int p077,
        int p078, int p079, int p080, int p081, int p082, int p083, int p084,
        int p085, int p086, int p087, int p088, int p089, int p090, int p091,
        int p092, int p093, int p094, int p095, int p096, int p097, int p098,
        int p099, int p100, int p101, int p102, int p103, int p104, int p105,
        int p106, int p107, int p108, int p109, int p110, int p111, int p112,
        int p113, int p114, int p115, int p116, int p117, int p118, int p119,
        int p120, int p121, int p122, int p123, int p124, int p125, int p126,
        int p127, int p128, int p129, int p130, int p131, int p132, int p133,
        int p134, int p135, int p136, int p137, int p138, int p139, int p140,
        int p141, int p142, int p143, int p144, int p145, int p146, int p147,
        int p148, int p149, int p150, int p151, int p152, int p153, int p154,
        int p155, int p156, int p157, int p158, int p159, int p160, int p161,
        int p162, int p163, int p164, int p165, int p166, int p167, int p168,
        int p169, int p170, int p171, int p172, int p173, int p174, int p175,
        int p176, int p177, int p178, int p179, int p180, int p181, int p182,
        int p183, int p184, int p185, int p186, int p187, int p188, int p189,
        int p190, int p191, int p192, int p193, int p194, int p195, int p196,
        int p197, int p198, int p199, int p200, int p201, int p202, int p203,
        int p204, int p205, int p206, int p207, int p208, int p209, int p210,
        int p211, int p212, int p213, int p214, int p215, int p216, int p217,
        int p218, int p219, int p220, int p221, int p222, int p223, int p224,
        int p225, int p226, int p227, int p228, int p229, int p230, int p231,
        int p232, int p233, int p234, int p235, int p236, int p237, int p238,
        int p239, int p240, int p241, int p242, int p243, int p244, int p245,
        int p246, int p247, int p248, int p249, int p250, int p251, int p252,
        int p253, int p254, int p255)
    {
    }

    // UTILITY METHODS

    /**
     * Raises an exception if the exception is not caused by
     * <code>executeQuery()</code> returning no result set.
     *
     * @param sqle a <code>SQLException</code> value
     */
    private void assertNoResultSetFromExecuteQuery(SQLException sqle) {
        assertSQLState("Unexpected SQL state.", "X0Y78", sqle);        
    }

    /**
     * Raises an exception if the exception is not caused by
     * <code>executeQuery()</code> returning multiple result sets.
     *
     * @param sqle a <code>SQLException</code> value
     */
    private void assertMultipleResultsFromExecuteQuery(SQLException sqle)
    {
        assertSQLState("Unexpected SQL state.", "X0Y78", sqle);        
    }

    /**
     * Raises an exception if the exception is not caused by
     * <code>executeUpdate()</code> returning result sets.
     *
     * @param sqle a <code>SQLException</code> value
     */
    private void assertResultsFromExecuteUpdate(SQLException sqle) {
        assertSQLState("Unexpected SQL state.", "X0Y79", sqle);
    }

    // SETUP

    /**
     * Runs the test fixtures in embedded and client.
     * @return test suite
     */
    public static Test suite() {
        BaseTestSuite suite = new BaseTestSuite("ProcedureTest");

        suite.addTest(baseSuite("ProcedureTest:embedded"));

        suite.addTest(
                TestConfiguration.clientServerDecorator(
                        baseSuite("ProcedureTest:client")));    
        return suite;
    }

    /**
     * Creates the test suite and wraps it in a <code>TestSetup</code>
     * instance which sets up and tears down the test environment.
     * @return test suite
     */
    private static Test baseSuite(String name)
    {
        BaseTestSuite suite = new BaseTestSuite(name);
        
        // Need JDBC DriverManager to run these tests
        if (!JDBC.vmSupportsJDBC3())
            return suite;
        
        suite.addTestSuite(ProcedureTest.class);
        
        return new CleanDatabaseTestSetup(suite) {
            /**
             * Creates the tables and the stored procedures used in the test
             * cases.
             * @exception SQLException if a database error occurs
             */
            protected void decorateSQL(Statement s) throws SQLException
            {
                for (int i = 0; i < PROCEDURES.length; i++) {
                    s.execute(PROCEDURES[i]);
                }
                for (int i = 0; i < TABLES.length; i++) {
                    s.execute(TABLES[i][1]);
                }
            }
        };
    }

    /**
     * Sets up the connection for a test case and clears all tables
     * used in the test cases.
     * @exception SQLException if a database error occurs
     */
    public void setUp() throws SQLException {
        Connection conn = getConnection();
        conn.setAutoCommit(false);
        Statement s = createStatement();
        for (int i = 0; i < TABLES.length; i++) {
            s.execute("DELETE FROM " + TABLES[i][0]);
        }
        commit();
    }

    /**
     * Procedures that should be created before the tests are run and
     * dropped when the tests have finished. First element in each row
     * is the name of the procedure, second element is SQL which
     * creates it.
     */
    private static final String[] PROCEDURES = {
       
          "CREATE PROCEDURE RETRIEVE_DYNAMIC_RESULTS(number INT) " +
          "LANGUAGE JAVA PARAMETER STYLE JAVA EXTERNAL NAME '" +
          ProcedureTest.class.getName() + ".retrieveDynamicResults' " +
          "DYNAMIC RESULT SETS 4",


          "CREATE PROCEDURE RETRIEVE_CLOSED_RESULT() LANGUAGE JAVA " +
          "PARAMETER STYLE JAVA EXTERNAL NAME '" +
          ProcedureTest.class.getName() + ".retrieveClosedResult' " +
          "DYNAMIC RESULT SETS 1",

          "CREATE PROCEDURE RETRIEVE_EXTERNAL_RESULT(" +
          "DBNAME VARCHAR(128), DBUSER VARCHAR(128), DBPWD VARCHAR(128)) LANGUAGE JAVA " +
          "PARAMETER STYLE JAVA EXTERNAL NAME '" +
          ProcedureTest.class.getName() + ".retrieveExternalResult' " +
          "DYNAMIC RESULT SETS 1",

          "CREATE PROCEDURE PROC_WITH_SIDE_EFFECTS(ret INT) LANGUAGE JAVA " +
          "PARAMETER STYLE JAVA EXTERNAL NAME '" +
          ProcedureTest.class.getName() + ".procWithSideEffects' " +
          "DYNAMIC RESULT SETS 2",
          
          "CREATE PROCEDURE NESTED_RESULT_SETS(proctext VARCHAR(128)) LANGUAGE JAVA " +
          "PARAMETER STYLE JAVA EXTERNAL NAME '" +
          ProcedureTest.class.getName() + ".nestedDynamicResultSets' " +
          "DYNAMIC RESULT SETS 6",

          "CREATE PROCEDURE INT_OUT(OUT X INTEGER) LANGUAGE JAVA " +
          "PARAMETER STYLE JAVA EXTERNAL NAME '" +
          ProcedureTest.class.getName() + ".intOut'",
    };

    /**
     * Tables that should be created before the tests are run and
     * dropped when the tests have finished. The tables will be
     * cleared before each test case is run. First element in each row
     * is the name of the table, second element is the SQL text which
     * creates it.
     */
    private static final String[][] TABLES = {
        // SIMPLE_TABLE is used by PROC_WITH_SIDE_EFFECTS
        { "SIMPLE_TABLE", "CREATE TABLE SIMPLE_TABLE (id INT)" },
    };

    // PROCEDURES

    /**
     * Stored procedure which returns 0, 1, 2, 3 or 4 <code>ResultSet</code>s.
     *
     * @param number the number of <code>ResultSet</code>s to return
     * @param rs1 first <code>ResultSet</code>
     * @param rs2 second <code>ResultSet</code>
     * @param rs3 third <code>ResultSet</code>
     * @param rs4 fourth <code>ResultSet</code>
     * @exception SQLException if a database error occurs
     */
    public static void retrieveDynamicResults(int number,
                                              ResultSet[] rs1,
                                              ResultSet[] rs2,
                                              ResultSet[] rs3,
                                              ResultSet[] rs4)
        throws SQLException
    {
        Connection c = DriverManager.getConnection("jdbc:default:connection");
        if (number > 0) {
            rs1[0] = c.createStatement().executeQuery("VALUES(1)");
        }
        if (number > 1) {
            rs2[0] = c.createStatement().executeQuery("VALUES(1)");
        }
        if (number > 2) {
            rs3[0] = c.createStatement().executeQuery("VALUES(1)");
        }
        if (number > 3) {
            rs4[0] = c.createStatement().executeQuery("VALUES(1)");
        }
        c.close();
    }

    /**
     * Stored procedure which produces a closed result set.
     *
     * @param closed holder for the closed result set
     * @exception SQLException if a database error occurs
     */
    public static void retrieveClosedResult(ResultSet[] closed)
        throws SQLException
    {
        Connection c = DriverManager.getConnection("jdbc:default:connection");
        closed[0] = c.createStatement().executeQuery("VALUES(1)");
        closed[0].close();
        c.close();
    }

    /**
     * Stored procedure which produces a result set in another
     * connection.
     *
     * @param external result set from another connection
     * @exception SQLException if a database error occurs
     */
    public static void retrieveExternalResult(String dbName, 
            String user, String password, ResultSet[] external)
        throws SQLException
    {
        // Use a server-side connection to the same database.
        String url = "jdbc:derby:" + dbName;
        
        Connection conn = DriverManager.getConnection(url, user, password);
        
        external[0] =
            conn.createStatement().executeQuery("VALUES(1)");
    }

    /**
     * Stored procedure which inserts a row into SIMPLE_TABLE and
     * optionally returns result sets.
     *
     * @param returnResults if one, return one result set; if greater
     * than one, return two result sets; otherwise, return no result
     * set
     * @param rs1 first result set to return
     * @param rs2 second result set to return
     * @exception SQLException if a database error occurs
     */
    public static void procWithSideEffects(int returnResults,
                                           ResultSet[] rs1,
                                           ResultSet[] rs2)
        throws SQLException
    {
        Connection c = DriverManager.getConnection("jdbc:default:connection");
        Statement stmt = c.createStatement();
        stmt.executeUpdate("INSERT INTO SIMPLE_TABLE VALUES (42)");
        if (returnResults > 0) {
            rs1[0] = c.createStatement().executeQuery("VALUES(1)");
        }
        if (returnResults > 1) {
            rs2[0] = c.createStatement().executeQuery("VALUES(1)");
        }
        c.close();
    }
    
    /**
     * Method for a Java procedure that calls another procedure
     * and just passes on the dynamic results from that call.
     */
    public static void nestedDynamicResultSets(String procedureText,
            ResultSet[] rs1, ResultSet[] rs2, ResultSet[] rs3, ResultSet[] rs4,
            ResultSet[] rs5, ResultSet[] rs6)
    throws SQLException
    {
        Connection c = DriverManager.getConnection("jdbc:default:connection");
        
        CallableStatement cs = c.prepareCall("CALL " + procedureText);
        
        cs.execute();
        
        // Mix up the order of the result sets in the returned
        // parameters, ensures order is defined by creation
        // and not parameter order.
        rs6[0] = cs.getResultSet();
        if (!cs.getMoreResults(Statement.KEEP_CURRENT_RESULT))
            return;
        rs3[0] = cs.getResultSet();
        if (!cs.getMoreResults(Statement.KEEP_CURRENT_RESULT))
            return;
        rs4[0] = cs.getResultSet();
        if (!cs.getMoreResults(Statement.KEEP_CURRENT_RESULT))
            return;
        rs2[0] = cs.getResultSet();
        if (!cs.getMoreResults(Statement.KEEP_CURRENT_RESULT))
            return;
        rs1[0] = cs.getResultSet();
        if (!cs.getMoreResults(Statement.KEEP_CURRENT_RESULT))
            return;
        rs5[0] = cs.getResultSet();
    
    }

    /**
     * Stored procedure with an integer output parameter.
     * @param out an output parameter
     */
    public static void intOut(int[] out) {
        out[0] = 42;
    }

    /**
     * Procedure to test that INOUT args preserve their value when the
     * procedure is re-executed (DERBY-2515). If you add a new datatype
     * to Derby, you will need to add a new argument at the end of this
     * procedure's signature.
     */
    public static  AllTypesTuple   makeFirstAllTypesTuple() throws Exception
    {
        return new AllTypesTuple
            (
             1L,
             new HarmonySerialBlob( new byte[] { (byte) 1, (byte) 1, (byte) 1 } ),
             Boolean.TRUE,
             "firstt",
             new byte[] { (byte) 1, (byte) 1, (byte) 1 },
             new HarmonySerialClob( "firstt" ),
             new Date( 1L ),
             new BigDecimal( "1" ),
             1.0,
             1 ,
             new String( "firstt" ),
             new byte[] { (byte) 1, (byte) 1, (byte) 1 },
             1.0F,
             1,
             new Time( 1L ),
             new Timestamp( 1L ),
             new Price( "USD", new BigDecimal( "1" ), new Timestamp( 1 ) ),
             "firstt",
             new byte[] { (byte) 1, (byte) 1, (byte) 1 }
             );
    }
    public static  AllTypesTuple   makeSecondAllTypesTuple() throws Exception
    {
        return new AllTypesTuple
            (
             2L,
             new HarmonySerialBlob( new byte[] { (byte) 2, (byte) 2, (byte) 2 } ),
             Boolean.FALSE,
             "second",
             new byte[] { (byte) 2, (byte) 2, (byte) 2 },
             new HarmonySerialClob( "second" ),
             new Date( 2L ),
             new BigDecimal( "2" ),
             2.0,
             2,
             new String( "second" ),
             new byte[] { (byte) 2, (byte) 2, (byte) 2 },
             2.0F,
             2,
             new Time( 2L ),
             new Timestamp( 2L ),
             new Price( "USD", new BigDecimal( "2" ), new Timestamp( 2 ) ),
             "second",
             new byte[] { (byte) 2, (byte) 2, (byte) 2 }
             );
    }
    public static  AllTypesTuple   makeThirdAllTypesTuple() throws Exception
    {
        return new AllTypesTuple
            (
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null,
             null
             );
    }
    public static  AllTypesTuple   makeFourthAllTypesTuple() throws Exception
    {
        return makeFirstAllTypesTuple();
    }
    public  static  void    proc_2515
        (
         int passNumber,
         String[] message,
         
         Long[] bigintArg,
         Blob[] blobArg,
         Boolean[] booleanArg,
         String[] charArg,
         byte[][] charForBitDataArg,
         Clob[] clobArg,
         Date[] dateArg,
         BigDecimal[] decimalArg,
         Double[] doubleArg,
         Integer[] intArg,
         String[] longVarcharArg,
         byte[][] longVarcharForBitDataArg,
         Float[] realArg,
         Integer[] smallintArg,
         Time[] timeArg,
         Timestamp[] timestampArg,
         Price[] priceArg,
         String[] varcharArg,
         byte[][] varcharForBitDataArg
         )
        throws Exception
    {
        AllTypesTuple   actualCallSignature = new AllTypesTuple
            (
             bigintArg[ 0 ],
             blobArg[ 0 ],
             booleanArg[ 0 ],
             charArg[ 0 ],
             charForBitDataArg[ 0 ],
             clobArg[ 0 ],
             dateArg[ 0 ],
             decimalArg[ 0 ],
             doubleArg[ 0 ],
             intArg[ 0 ],
             longVarcharArg[ 0 ],
             longVarcharForBitDataArg[ 0 ],
             realArg[ 0 ],
             smallintArg[ 0 ],
             timeArg[ 0 ],
             timestampArg[ 0 ],
             priceArg[ 0 ],
             varcharArg[ 0 ],
             varcharForBitDataArg[ 0 ]
             );
        AllTypesTuple   expectedCallSignature;
        AllTypesTuple   returnSignature;

        switch( passNumber )
        {
        case 0:
            expectedCallSignature = makeFirstAllTypesTuple();
            returnSignature = makeSecondAllTypesTuple();
            break;
        case 1:
            expectedCallSignature = makeSecondAllTypesTuple();
            returnSignature = makeThirdAllTypesTuple();
            break;
        case 2:
        default:
            expectedCallSignature = makeThirdAllTypesTuple();
            returnSignature = makeFourthAllTypesTuple();
            break;
        }

        message[ 0 ] = expectedCallSignature.compare( actualCallSignature );
        
        bigintArg[ 0 ] = returnSignature.get_bigintArg();
        blobArg[ 0 ] = returnSignature.get_blobArg();
        booleanArg[ 0 ] = returnSignature.get_booleanArg();
        charArg[ 0 ] = returnSignature.get_charArg();
        charForBitDataArg[ 0 ] = returnSignature.get_charForBitDataArg();
        clobArg[ 0 ] = returnSignature.get_clobArg();
        dateArg[ 0 ] = returnSignature.get_dateArg();
        decimalArg[ 0 ] = returnSignature.get_decimalArg();
        doubleArg[ 0 ] = returnSignature.get_doubleArg();
        intArg[ 0 ] = returnSignature.get_intArg();
        longVarcharArg[ 0 ] = returnSignature.get_longVarcharArg();
        longVarcharForBitDataArg[ 0 ] = returnSignature.get_longVarcharForBitDataArg();
        realArg[ 0 ] = returnSignature.get_realArg();
        smallintArg[ 0 ] = returnSignature.get_smallintArg();
        timeArg[ 0 ] = returnSignature.get_timeArg();
        timestampArg[ 0 ] = returnSignature.get_timestampArg();
        priceArg[ 0 ] = returnSignature.get_priceArg();
        varcharArg[ 0 ] = returnSignature.get_varcharArg();
        varcharForBitDataArg[ 0 ] = returnSignature.get_varcharForBitDataArg();
    }
    
        /**
         * Test various combinations of getMoreResults
         * 
         * @throws SQLException
         */
        public void testGetMoreResults() throws SQLException {

                Statement s = createStatement();
                

                s.executeUpdate("create table MRS.FIVERS(i integer)");
                PreparedStatement ps = prepareStatement("insert into MRS.FIVERS values (?)");
                for (int i = 1; i <= 20; i++) {
                        ps.setInt(1, i);
                        ps.executeUpdate();
                }

                // create a procedure that returns 5 result sets.
                        
                s.executeUpdate("create procedure MRS.FIVEJP() parameter style JAVA READS SQL DATA dynamic result sets 5 language java external name 'org.apache.derbyTesting.functionTests.util.ProcedureTest.fivejp'");


                CallableStatement cs = prepareCall("CALL MRS.FIVEJP()");
                ResultSet[] allRS = new ResultSet[5];

                defaultGetMoreResults(cs, allRS);
                java.util.Arrays.fill(allRS, null);
                closeCurrentGetMoreResults(cs, allRS);
                java.util.Arrays.fill(allRS, null);
                keepCurrentGetMoreResults(cs, allRS);                              
                java.util.Arrays.fill(allRS, null);
                mixedGetMoreResults(cs, allRS);
                java.util.Arrays.fill(allRS, null);
                checkExecuteClosesResults(cs, allRS);
                java.util.Arrays.fill(allRS, null);
                checkCSCloseClosesResults(cs,allRS);
                java.util.Arrays.fill(allRS, null);
                
                // a procedure that calls another procedure that returns
                // dynamic result sets, see if the result sets are handled
                // correctly through the nesting.
                CallableStatement nestedCs = prepareCall(
                        "CALL NESTED_RESULT_SETS('MRS.FIVEJP()')");
                defaultGetMoreResults(nestedCs, allRS);
                
        }

        
        /**
         * Check that CallableStatement.execute() closes results
         * @param cs
         * @param allRS
         * @throws SQLException
         */
        private void checkExecuteClosesResults(CallableStatement cs, ResultSet[] allRS) throws SQLException {
            //Fetching result sets with getMoreResults(Statement.KEEP_CURRENT_RESULT) and checking that cs.execute() closes them");          
            cs.execute();
            int pass = 0;
            do {

                    allRS[pass++] = cs.getResultSet();      
                    assertSame(cs, allRS[pass-1].getStatement());
                    // expect everything to stay open.                        

            } while (cs.getMoreResults(Statement.KEEP_CURRENT_RESULT));
            //fetched all results
            // All should still be open.
            for (int i = 0; i < 5; i++)
                JDBC.assertDrainResults(allRS[i]);                
            
            cs.execute();
            // all should be closed.
            for (int i = 0; i < 5; i++)
                JDBC.assertClosed(allRS[i]);
        }

        /**
         * Check that CallableStatement.close() closes results
         * @param cs
         * @param allRS
         * @throws SQLException
         */
        private void checkCSCloseClosesResults(CallableStatement cs, ResultSet[] allRS) throws SQLException {
            cs.execute();
            int pass = 0;
            do {

                    allRS[pass++] = cs.getResultSet();         
                    assertSame(cs, allRS[pass-1].getStatement());
                    // expect everything to stay open.                        

            } while (cs.getMoreResults(Statement.KEEP_CURRENT_RESULT));
            //fetched all results
            // All should still be open.
            for (int i = 0; i < 5; i++)
                JDBC.assertDrainResults(allRS[i]);                
            
            cs.close();
            // all should be closed.
            for (int i = 0; i < 5; i++)
                JDBC.assertClosed(allRS[i]);
        }

        private void mixedGetMoreResults(CallableStatement cs, ResultSet[] allRS) throws SQLException {
            //Fetching result sets with getMoreResults(<mixture>)"
            cs.execute();

            //first two with KEEP_CURRENT_RESULT"
            allRS[0] = cs.getResultSet();
            assertSame(cs, allRS[0].getStatement());
            boolean moreRS = cs.getMoreResults(Statement.KEEP_CURRENT_RESULT);
            if (!moreRS)
                    fail("FAIL - no second result set");
            allRS[1] = cs.getResultSet();   
            assertSame(cs, allRS[1].getStatement());
            // two open
            allRS[0].next();
            assertEquals(2,allRS[0].getInt(1));
            allRS[1].next();
            assertEquals(3,allRS[1].getInt(1));
            
            //third with CLOSE_CURRENT_RESULT"
            moreRS = cs.getMoreResults(Statement.CLOSE_CURRENT_RESULT);
            if (!moreRS)
                    fail("FAIL - no third result set");
            // first and third open
            allRS[2] = cs.getResultSet();
            assertSame(cs, allRS[2].getStatement());
            assertEquals(2,allRS[0].getInt(1));
            JDBC.assertClosed(allRS[1]);
            allRS[2].next();
            assertEquals(4,allRS[2].getInt(1));

            
            //fourth with KEEP_CURRENT_RESULT"
            moreRS = cs.getMoreResults(Statement.KEEP_CURRENT_RESULT);
            if (!moreRS)
                    fail("FAIL - no fourth result set");
            allRS[3] = cs.getResultSet();
            assertSame(cs, allRS[3].getStatement());
            allRS[3].next();
            // first, third and fourth open, second closed
            assertEquals(2,allRS[0].getInt(1));
            JDBC.assertClosed(allRS[1]);
            assertEquals(4,allRS[2].getInt(1));
            assertEquals(5,allRS[3].getInt(1));
            
            //fifth with CLOSE_ALL_RESULTS"
            moreRS = cs.getMoreResults(Statement.CLOSE_ALL_RESULTS);
            if (!moreRS)
                   fail("FAIL - no fifth result set");
            allRS[4] = cs.getResultSet();
            assertSame(cs, allRS[4].getStatement());
            allRS[4].next();
            // only fifth open
            JDBC.assertClosed(allRS[0]);
            JDBC.assertClosed(allRS[1]);
            JDBC.assertClosed(allRS[2]);
            JDBC.assertClosed(allRS[3]);
            assertEquals(6,allRS[4].getInt(1));
            
            //no more results with with KEEP_CURRENT_RESULT"
            moreRS = cs.getMoreResults(Statement.KEEP_CURRENT_RESULT);
            if (moreRS)
                    fail("FAIL - too many result sets");
            // only fifth open
            JDBC.assertClosed(allRS[0]);
            JDBC.assertClosed(allRS[1]);
            JDBC.assertClosed(allRS[2]);
            JDBC.assertClosed(allRS[3]);
            assertEquals(6,allRS[4].getInt(1));
            
            allRS[4].close();
        }

        /**
         * Check getMoreResults(Statement.KEEP_CURRENT_RESULT)  
         * 
         * @param cs
         * @param allRS
         * @throws SQLException
         */
        private void keepCurrentGetMoreResults(CallableStatement cs, ResultSet[] allRS) throws SQLException {
            cs.execute();
            
            for (int i = 0; i < 5; i++)
            {
                allRS[i] = cs.getResultSet();
                assertSame(cs, allRS[i].getStatement());
                allRS[i].next();
                assertEquals(2+i, allRS[i].getInt(1));
                
                if (i < 4)
                    assertTrue(cs.getMoreResults(Statement.KEEP_CURRENT_RESULT));
                else
                    assertFalse(cs.getMoreResults(Statement.KEEP_CURRENT_RESULT));
            }            
            
            // resultSets should still be open
            for (int i = 0; i < 5; i++)
                JDBC.assertDrainResults(allRS[i]);
        }

        private void closeCurrentGetMoreResults(CallableStatement cs, ResultSet[] allRS) throws SQLException {
            cs.execute();
            
            for (int i = 0; i < 5; i++)
            {
                allRS[i] = cs.getResultSet();
                assertSame(cs, allRS[i].getStatement());
                allRS[i].next();
                assertEquals(2+i, allRS[i].getInt(1));
                
                if (i < 4)
                    assertTrue(cs.getMoreResults(Statement.CLOSE_CURRENT_RESULT));
                else
                    assertFalse(cs.getMoreResults(Statement.CLOSE_CURRENT_RESULT));
            }
            
            // verify resultSets are closed
            for (int i = 0; i < 5; i++)
                JDBC.assertClosed(allRS[i]);
        }

        /**
         * Test default getMoreResults() closes result set.
         * @param cs
         * @param allRS
         * @throws SQLException
         */
        private void defaultGetMoreResults(CallableStatement cs, ResultSet[] allRS) throws SQLException {
            // execute the procedure that returns 5 result sets and then use the various
            // options of getMoreResults().

            cs.execute();
            
            for (int i = 0; i < 5; i++)
            {
                allRS[i] = cs.getResultSet();
                assertSame(cs, allRS[i].getStatement());
                allRS[i].next();
                assertEquals(2+i, allRS[i].getInt(1));
                
                if (i < 4)
                    assertTrue(cs.getMoreResults());
                else
                    assertFalse(cs.getMoreResults());
            } 
                        
            // verify resultSets are closed
            for (int i = 0; i < 5; i++)
                JDBC.assertClosed(allRS[i]);
        }

    ////////////////////////////////////////////
    //
    // Nested classes.
    //
    ////////////////////////////////////////////

    public  static  final   class   AllTypesTuple
    {
        private Long _bigintArg;
        private Blob _blobArg;
        private Boolean _booleanArg;
        private String _charArg;
        private byte[] _charForBitDataArg;
        private Clob _clobArg;
        private Date _dateArg;
        private BigDecimal _decimalArg;
        private Double _doubleArg;
        private Integer _intArg;
        private String _longVarcharArg;
        private byte[] _longVarcharForBitDataArg;
        private Float _realArg;
        private Integer _smallintArg;
        private Time _timeArg;
        private Timestamp _timestampArg;
        private Price _priceArg;
        private String _varcharArg;
        private byte[] _varcharForBitDataArg;

        public AllTypesTuple
            (
             Long  bigintArg,
             Blob  blobArg,
             Boolean  booleanArg,
             String  charArg,
             byte[]   charForBitDataArg,
             Clob  clobArg,
             Date  dateArg,
             BigDecimal  decimalArg,
             Double  doubleArg,
             Integer  intArg,
             String  longVarcharArg,
             byte[]   longVarcharForBitDataArg,
             Float  realArg,
             Integer  smallintArg,
             Time  timeArg,
             Timestamp  timestampArg,
             Price  priceArg,
             String  varcharArg,
             byte[]   varcharForBitDataArg
             )
        {
            _bigintArg = bigintArg;
            _blobArg = blobArg;
            _booleanArg = booleanArg;
            _charArg = charArg;
            _charForBitDataArg = charForBitDataArg;
            _clobArg = clobArg;
            _dateArg = dateArg;
            _decimalArg = decimalArg;
            _doubleArg = doubleArg;
            _intArg = intArg;
            _longVarcharArg = longVarcharArg;
            _longVarcharForBitDataArg = longVarcharForBitDataArg;
            _realArg = realArg;
            _smallintArg = smallintArg;
            _timeArg = timeArg;
            _timestampArg = timestampArg;
            _priceArg = priceArg;
            _varcharArg = varcharArg;
            _varcharForBitDataArg = varcharForBitDataArg;
        }

        public Long get_bigintArg() { return _bigintArg; }
        public Blob get_blobArg() { return _blobArg; }
        public Boolean get_booleanArg() { return _booleanArg; }
        public String get_charArg() { return _charArg; }
        public byte[] get_charForBitDataArg() { return _charForBitDataArg; }
        public Clob get_clobArg() { return _clobArg; }
        public Date get_dateArg() { return _dateArg; }
        public BigDecimal get_decimalArg() { return _decimalArg; }
        public Double get_doubleArg() { return _doubleArg; }
        public Integer get_intArg() { return _intArg; }
        public String get_longVarcharArg() { return _longVarcharArg; }
        public byte[] get_longVarcharForBitDataArg() { return _longVarcharForBitDataArg; }
        public Float get_realArg() { return _realArg; }
        public Integer get_smallintArg() { return _smallintArg; }
        public Time get_timeArg() { return _timeArg; }
        public Timestamp get_timestampArg() { return _timestampArg; }
        public Price get_priceArg() { return _priceArg; }
        public String get_varcharArg() { return _varcharArg; }
        public byte[] get_varcharForBitDataArg() { return _varcharForBitDataArg; }

        public  String  compare( AllTypesTuple that ) throws Exception
        {
            String  message = "";

            message = message + compare( "_bigintArg", this._bigintArg, that._bigintArg );
            message = message + compare( "_blobArg", this.getBlobBytes(), that.getBlobBytes() );
            message = message + compare( "_booleanArg", this._booleanArg, that._booleanArg );
            message = message + compare( "_charArg", this._charArg, that._charArg );
            message = message + compare( "_charForBitDataArg", this._charForBitDataArg, that._charForBitDataArg );
            message = message + compare( "_clobArg", this.getClobString(), that.getClobString() );
            message = message + compare( "_dateArg", this.getDateString(), that.getDateString() );
            message = message + compare( "_decimalArg", this._decimalArg, that._decimalArg );
            message = message + compare( "_doubleArg", this._doubleArg, that._doubleArg );
            message = message + compare( "_intArg", this._intArg, that._intArg );
            message = message + compare( "_longVarcharArg", this._longVarcharArg, that._longVarcharArg );
            message = message + compare( "_longVarcharForBitDataArg", this._longVarcharForBitDataArg, that._longVarcharForBitDataArg );
            message = message + compare( "_realArg", this._realArg, that._realArg );
            message = message + compare( "_smallintArg", this._smallintArg, that._smallintArg );
            message = message + compare( "_timeArg", this.getTimeString(), that.getTimeString() );
            message = message + compare( "_timestampArg", this._timestampArg, that._timestampArg );
            message = message + compare( "_priceArg", this._priceArg, that._priceArg );
            message = message + compare( "_varcharArg", this._varcharArg, that._varcharArg );
            message = message + compare( "_varcharForBitDataArg", this._varcharForBitDataArg, that._varcharForBitDataArg );

            return message;
        }
        private byte[]  getBlobBytes() throws Exception
        {
            if ( _blobArg == null ) { return null; }
            else { return _blobArg.getBytes( 1, (int) _blobArg.length() ); }
        }
        private String  getClobString() throws Exception
        {
            if ( _clobArg == null ) { return null; }
            else { return _clobArg.getSubString( 1, (int) _clobArg.length() ); }
        }
        private String  getDateString()
        {
            if ( _dateArg ==  null ) { return null; }
            else { return _dateArg.toString(); }
        }
        private String  getTimeString()
        {
            if ( _timeArg == null ) { return null; }
            else { return _timeArg.toString(); }
        }
        private String  compare( String argName, Object left, Object right )
        {
            if ( left == null )
            {
                if ( right == null ) { return ""; }
                return (argName + ": left was null but right was " + right);
            }
            if ( right == null ) { return (argName + ": left = " + left + " but right is null" ); }
            if ( left instanceof byte[] ) { return compareBytes( argName, (byte[]) left, (byte[]) right ); }

            if ( left.equals( right ) ) { return ""; }

            return (argName + ": left = " + left + " but right = " + right);
        }
        private String  compareBytes( String argName, byte[] left, byte[] right )
        {
            int count = left.length;

            if ( count != right.length )
            {
                return (argName + ": left count = " + count + " but right count = " + right.length );
            }
            for ( int i = 0; i < count; i++ )
            {
                if ( left[ i ] != right[ i ] )
                {
                    return (argName + ": left[ " + i + " ] = " + left[ i ] + " but right[ " + i + " ] = " + right[ i ] );
                }
            }

            return "";
        }
    }

    

}
