/*

   Derby - Class org.apache.derby.impl.jdbc.EmbedStatement

   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.derby.impl.jdbc;

import org.apache.derby.iapi.reference.SQLState;

import org.apache.derby.shared.common.sanity.SanityManager;

import org.apache.derby.iapi.sql.Activation;
import org.apache.derby.iapi.sql.PreparedStatement;
import org.apache.derby.iapi.sql.ResultSet;
import org.apache.derby.iapi.sql.ParameterValueSet;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.jdbc.EngineStatement;

import java.sql.BatchUpdateException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Vector;
import org.apache.derby.iapi.util.InterruptStatus;

/*
 We would import these, but have name-overlap
import java.sql.Statement;
import java.sql.ResultSet;
*/

/**
 * EmbedStatement is a local JDBC statement.
 * It supports JDBC 4.1.
 */
public class EmbedStatement extends ConnectionChild
    implements EngineStatement {

	private final java.sql.Connection applicationConnection;
    
    /**
     * Statement reference the application is using to execute
     * this Statement. Normally set to this, but if this was
     * created by a Connection from an XAConnection then this
     * will be a reference to the BrokeredStatement.
     *
     * Making it protected to allow access from EmbedPreparedStatement40
     * to be used for StatementEvents
     *
     */
    protected EngineStatement applicationStatement;

	long updateCount = -1;
	EmbedResultSet results;
	//for jdbc3.0 feature, where you can get a resultset of rows inserted
	//for auto generated columns after an insert
	private java.sql.ResultSet autoGeneratedKeysResultSet;
	private String cursorName;

	private final boolean forMetaData;
	final int resultSetType;
	final int resultSetConcurrency;
	private final int resultSetHoldability;
	final LanguageConnectionContext lcc;

	private SQLWarning warnings;
	String SQLText;

    private int fetchSize = 1;
    private int fetchDirection = java.sql.ResultSet.FETCH_FORWARD;
    int MaxFieldSize;
	/**
	 * Query timeout in milliseconds. By default, no statements time
	 * out. Timeout is set explicitly with setQueryTimeout().
	 */
    long timeoutMillis;

	//the state of this statement, set to false when close() is called
	private boolean active = true;

    //in case of batch update, save the individual statements in the batch in this vector
 	//this is only used by JDBC 2.0
 	Vector<Object> batchStatements;
	
	// The maximum # of rows to return per result set.
	// (0 means no limit.)
	long maxRows;

	private ParameterValueSet pvs;

	// An EmbedStatement is NOT poolable by default. The constructor for
	// PreparedStatement overrides this.
	protected boolean isPoolable = false;

    private boolean closeOnCompletion = false;
    private boolean closingResultSets = false;
    
	//
	// constructor
	//
	public EmbedStatement (EmbedConnection connection, boolean forMetaData,
							  int resultSetType, int resultSetConcurrency, int resultSetHoldability)
	{
	    super(connection);
		this.forMetaData = forMetaData;
		this.resultSetType = resultSetType;
		this.resultSetConcurrency = resultSetConcurrency;
		this.resultSetHoldability = resultSetHoldability;

		lcc = getLanguageConnectionContext( getEmbedConnection() );
		applicationConnection = getEmbedConnection().getApplicationConnection();
        applicationStatement = this;
	}

	//
	// java.sql.Statement interface
	// the comments are those from the JDBC interface,
	// so we know what we're supposed to to.

	/**
     * Execute a SQL statement that returns a single ResultSet.
     *
     * @param sql					typically this is a static SQL SELECT statement
	 * @return a ResultSet that contains the data produced by the
     * query; never null
	 * @exception SQLException thrown on failure.
     */
	public java.sql.ResultSet executeQuery(String sql)
		throws SQLException
	{
		execute(sql, true, false, Statement.NO_GENERATED_KEYS, null, null);

		if (SanityManager.DEBUG) {
			if (results == null)
				SanityManager.THROWASSERT("no results returned on executeQuery()");
		}

		return results;
	}

    /**
     * Execute a SQL INSERT, UPDATE or DELETE statement. In addition,
     * SQL statements that return nothing such as SQL DDL statements
     * can be executed.
     *
     * @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL
     * statement that returns nothing
     * @return either the row count for INSERT, UPDATE or DELETE; or 0
     * for SQL statements that return nothing
	 * @exception SQLException thrown on failure.
     */
	public int executeUpdate(String sql) throws SQLException
	{
		return (int) executeLargeUpdate( sql );
	}

    /**
     * JDBC 4.2
     *
     * Execute a SQL INSERT, UPDATE or DELETE statement. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     */
	public long executeLargeUpdate(String sql) throws SQLException
	{
		execute(sql, false, true, Statement.NO_GENERATED_KEYS, null, null);
		return updateCount;
	}

    /**
     * JDBC 3.0
     *
     * Execute the given SQL statement and signals the driver with the given flag
     * about whether the auto-generated keys produced by this Statement object
     * should be made available for retrieval.
     *
     * @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL
     * statement that returns nothing
     * @param autoGeneratedKeys - a flag indicating whether auto-generated keys
     * should be made available for retrieval; one of the following constants:
     * Statement.RETURN_GENERATED_KEYS Statement.NO_GENERATED_KEYS
     * @return either the row count for INSERT, UPDATE or DELETE; or 0
     * for SQL statements that return nothing
     * @exception SQLException if a database access error occurs
     */
	public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
	{
		return (int) executeLargeUpdate( sql, autoGeneratedKeys );
	}

    /**
     * JDBC 4.2
     *
     * Execute the given SQL statement and signals the driver with the given flag
     * about whether the auto-generated keys produced by this Statement object
     * should be made available for retrieval. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     */
	public long executeLargeUpdate( String sql, int autoGeneratedKeys ) throws SQLException
	{
		execute( sql, false, true, autoGeneratedKeys, null, null );
		return updateCount;
	}

    /**
     * JDBC 3.0
     *
     * Executes the given SQL statement and signals the driver that the
     * auto-generated keys indicated in the given array should be made
     * available for retrieval. The driver will ignore the array if the SQL
     * statement is not an INSERT statement
     *
     * @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL
     * statement that returns nothing
     * @param columnIndexes - an array of column indexes indicating the
     * columns that should be returned from the inserted row
     * @return either the row count for INSERT, UPDATE or DELETE; or 0
     * for SQL statements that return nothing
     * @exception SQLException if a database access error occurs
     */
	public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
	{
		return (int) executeLargeUpdate( sql, columnIndexes );
	}

    /**
     * JDBC 4.2
     *
     * Executes the given SQL statement and signals the driver that the
     * auto-generated keys indicated in the given array should be made
     * available for retrieval. The driver will ignore the array if the SQL
     * statement is not an INSERT/UPDATE statement. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     */
	public long executeLargeUpdate( String sql, int[] columnIndexes ) throws SQLException
	{
		execute(sql, false, true,
			((columnIndexes == null) || (columnIndexes.length == 0))
				? Statement.NO_GENERATED_KEYS
				: Statement.RETURN_GENERATED_KEYS,
			columnIndexes,
			null);
		return updateCount;
	}

    /**
     * JDBC 3.0
     *
     * Executes the given SQL statement and signals the driver that the
     * auto-generated keys indicated in the given array should be made
     * available for retrieval. The driver will ignore the array if the SQL
     * statement is not an INSERT statement
     *
     * @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL
     * statement that returns nothing
     * @param columnNames - an array of the names of the columns
     * that should be returned from the inserted row
     * @return either the row count for INSERT, UPDATE or DELETE; or 0
     * for SQL statements that return nothing
     * @exception SQLException if a database access error occurs
     */
	public int executeUpdate(String sql, String[] columnNames) throws SQLException
	{
		return (int) executeLargeUpdate( sql, columnNames );
	}

    /**
     * JDBC 4.2
     *
     * Executes the given SQL statement and signals the driver that the
     * auto-generated keys indicated in the given array should be made
     * available for retrieval. The driver will ignore the array if the SQL
     * statement is not an INSERT/UPDATE statement. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     */
	public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException
	{
		execute(sql, false, true,
			((columnNames == null) || (columnNames.length == 0))
				? Statement.NO_GENERATED_KEYS
				: Statement.RETURN_GENERATED_KEYS,
			null,
			columnNames);
		return updateCount;
	}

	final void checkIfInMiddleOfBatch() throws SQLException {
		/* If batchStatements is not null then we are in the middle
		 * of a batch. That's an invalid state. We need to finish the
		 * batch either by clearing the batch or executing the batch.
		 * executeUpdate is not allowed inside the batch.
		 */
		if (batchStatements != null)
  		throw newSQLException(SQLState.MIDDLE_OF_BATCH);
	}

    /**
     * Tell whether this statment has been closed or not.
     *
     * @return <code>true</code> is closed, <code>false</code> otherwise.
     * @exception SQLException if a database access error occurs.
     */
    public boolean isClosed() throws SQLException {
        // If active, verify state by consulting parent connection.
        if (active) {
            try {
                checkExecStatus();
            } catch (SQLException sqle) {
            }
        }
        return !active;
    }

    /**
     * In many cases, it is desirable to immediately release a
     * Statements's database and JDBC resources instead of waiting for
     * this to happen when it is automatically closed; the close
     * method provides this immediate release.
     *
     * <P><B>Note:</B> A Statement is automatically closed when it is
     * garbage collected. When a Statement is closed its current
     * ResultSet, if one exists, is also closed.
	 * @exception SQLException thrown on failure.
     */
	public final void close() throws SQLException {

		/* The close() method is the only method
		 * that is allowed to be called on a closed
		 * Statement, as per Jon Ellis.
		 */
		if (!active)
		{
			return;
		}

	  synchronized (getConnectionSynchronization()) {

		  closeActions();
		  
		  //we first set the status
		  active = false;

		  // first, clear the result sets.
		  clearResultSets();
		  
		  //next, release other resource
		  cursorName = null;
		  warnings = null;
		  SQLText = null;
		  batchStatements = null;
	  }
	}

    /**
     * Mark the statement and its single-use activation as unused. This method
     * should be called from <code>EmbedPreparedStatement</code>'s finalizer as
     * well, even though prepared statements reuse activations, since
     * <code>getGeneratedKeys()</code> uses a single-use activation regardless
     * of statement type.
     * <BR>
     * Dynamic result sets (those in dynamicResults array) need not
     * be handled here as they will be handled by the statement object
     * that created them. In some cases results will point to a
     * ResultSet in dynamicResults but all that will happen is that
     * the activation will get marked as unused twice.
     */
    //
    // This method in java.lang.Object was deprecated as of build 167
    // of JDK 9. See DERBY-6932.
    //
    @SuppressWarnings("deprecation")
    protected void finalize() throws Throwable {
        super.finalize();

        // We mark the activation as not being used and
        // that is it.  We rely on the connection to sweep
        // through the activations to find the ones that
        // aren't in use, and to close them.  We cannot
        // do a activation.close() here because there are
        // synchronized methods under close that cannot
        // be called during finalization.
        if (results != null && results.singleUseActivation != null) {
            results.singleUseActivation.markUnused();
        }
    }

	// allow sub-classes to execute additional close
	// logic while holding the synchronization.
	void closeActions() throws SQLException {
	}

    //----------------------------------------------------------------------

    /**
     * The maxFieldSize limit (in bytes) is the maximum amount of data
     * returned for any column value; it only applies to BINARY,
     * VARBINARY, LONGVARBINARY, CHAR, VARCHAR, and LONGVARCHAR
     * columns.  If the limit is exceeded, the excess data is silently
     * discarded.
     *
     * @return the current max column size limit; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public int getMaxFieldSize() throws SQLException {
		checkStatus();

        return MaxFieldSize;
	}

    /**
     * The maxFieldSize limit (in bytes) is set to limit the size of
     * data that can be returned for any column value; it only applies
     * to BINARY, VARBINARY, LONGVARBINARY, CHAR, VARCHAR, and
     * LONGVARCHAR fields.  If the limit is exceeded, the excess data
     * is silently discarded.
     *
     * @param max the new max column size limit; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public void setMaxFieldSize(int max) throws SQLException {
		checkStatus();

		if (max < 0)
		{
			throw newSQLException(SQLState.INVALID_MAXFIELD_SIZE, max);
		}
        this.MaxFieldSize = max;
	}

    /**
     * The maxRows limit is the maximum number of rows that a
     * ResultSet can contain.  If the limit is exceeded, the excess
     * rows are silently dropped.
     *
     * @return the current max row limit; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public int getMaxRows() throws SQLException 
	{
        return (int) getLargeMaxRows();
	}

    /**
     * JDBC 4.2
     *
     * The maxRows limit is the maximum number of rows that a
     * ResultSet can contain.  If the limit is exceeded, the excess
     * rows are silently dropped. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     *
     * @return the current max row limit; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public long getLargeMaxRows() throws SQLException 
	{
		checkStatus();
		return maxRows;
	}

    /**
     * The maxRows limit is set to limit the number of rows that any
     * ResultSet can contain.  If the limit is exceeded, the excess
     * rows are silently dropped.
     *
     * @param max the new max rows limit; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public void setMaxRows(int max) throws SQLException	
	{
        setLargeMaxRows( max );
	}

    /**
     * The maxRows limit is set to limit the number of rows that any
     * ResultSet can contain.  If the limit is exceeded, the excess
     * rows are silently dropped.
     *
     * @param max the new max rows limit; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public void setLargeMaxRows(long max) throws SQLException	
	{
		checkStatus();
		if (max < 0L)
		{
			throw newSQLException(SQLState.INVALID_MAX_ROWS_VALUE, max);
		}
		this.maxRows = max;
	}

    /**
     * If escape scanning is on (the default) the driver will do
     * escape substitution before sending the SQL to the database.
     *
     * @param enable true to enable; false to disable
	 * @exception SQLException thrown on failure.
     */
	public void setEscapeProcessing(boolean enable) throws SQLException	{
		checkStatus();
        // Nothing to do in our server , just ignore it.

	}

    /**
     * The queryTimeout limit is the number of seconds the driver will
     * wait for a Statement to execute. If the limit is exceeded a
     * SQLException is thrown.
     *
     * @return the current query timeout limit in seconds; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public final int getQueryTimeout() throws SQLException {
        checkStatus();
        return (int) (timeoutMillis / 1000);
	}

    /**
     * The queryTimeout limit is the number of seconds the driver will
     * wait for a Statement to execute. If the limit is exceeded a
     * SQLException is thrown.
     *
     * @param seconds the new query timeout limit in seconds; zero means unlimited
	 * @exception SQLException thrown on failure.
     */
	public final void setQueryTimeout(int seconds) throws SQLException {
		checkStatus();
        if (seconds < 0) {
            throw newSQLException(SQLState.INVALID_QUERYTIMEOUT_VALUE, seconds);
        }
        timeoutMillis = (long) seconds * 1000;
	}

    /**
     * Cancel can be used by one thread to cancel a statement that
     * is being executed by another thread.
	 * @exception SQLException thrown on failure.
     */
	public void cancel() throws SQLException {
		throw Util.notImplemented("cancel");
	}

    /**
     * The first warning reported by calls on this Statement is
     * returned.  A Statment's execute methods clear its SQLWarning
     * chain. Subsequent Statement warnings will be chained to this
     * SQLWarning.
     *
     * <p>The warning chain is automatically cleared each time
     * a statement is (re)executed.
     *
     * <P><B>Note:</B> If you are processing a ResultSet then any
     * warnings associated with ResultSet reads will be chained on the
     * ResultSet object.
     *
     * @return the first SQLWarning or null
	 * @exception SQLException thrown on failure.
     */
	public SQLWarning getWarnings() throws SQLException	{
		checkStatus();
		return warnings;
	}

    /**
     * After this call getWarnings returns null until a new warning is
     * reported for this Statement.
	 * @exception SQLException thrown on failure.
     */
	public void clearWarnings() throws SQLException	{
		checkStatus();
		warnings = null;
	}

    /**
     * setCursorName defines the SQL cursor name that will be used by
     * subsequent Statement execute methods. This name can then be
     * used in SQL positioned update/delete statements to identify the
     * current row in the ResultSet generated by this statement.  If
     * the database doesn't support positioned update/delete, this
     * method is a noop.
     *
     * <P><B>Note:</B> By definition, positioned update/delete
     * execution must be done by a different Statement than the one
     * which generated the ResultSet being used for positioning. Also,
     * cursor names must be unique within a Connection.
     *
     * @param name the new cursor name.
     */
	public void setCursorName(String name) throws SQLException {
		checkStatus();
		cursorName = name;
	}

    //----------------------- Multiple Results --------------------------

    /**
     * Execute a SQL statement that may return multiple results.
     * Under some (uncommon) situations a single SQL statement may return
     * multiple result sets and/or update counts.  Normally you can ignore
     * this, unless you're executing a stored procedure that you know may
     * return multiple results, or unless you're dynamically executing an
     * unknown SQL string.  The "execute", "getMoreResults", "getResultSet"
     * and "getUpdateCount" methods let you navigate through multiple results.
     *
     * The "execute" method executes a SQL statement and indicates the
     * form of the first result.  You can then use getResultSet or
     * getUpdateCount to retrieve the result, and getMoreResults to
     * move to any subsequent result(s).
     *
     * @param sql					any SQL statement
	 *
     * @return true if the first result is a ResultSet; false if it is an integer
     * @see #getResultSet
     * @see #getUpdateCount
     * @see #getMoreResults
	 * @exception SQLException thrown on failure
     */
	public boolean execute(String sql)
		throws SQLException
	{
		return execute(sql, false, false, Statement.NO_GENERATED_KEYS, null, null);
	}
	
    /**
     * Execute a SQL statement that may return multiple results.
     * Under some (uncommon) situations a single SQL statement may return
     * multiple result sets and/or update counts.  Normally you can ignore
     * this, unless you're executing a stored procedure that you know may
     * return multiple results, or unless you're dynamically executing an
     * unknown SQL string.  The "execute", "getMoreResults", "getResultSet"
     * and "getUpdateCount" methods let you navigate through multiple results.
     *
     * The "execute" method executes a SQL statement and indicates the
     * form of the first result.  You can then use getResultSet or
     * getUpdateCount to retrieve the result, and getMoreResults to
     * move to any subsequent result(s).
     *
     * @param sql					any SQL statement
	 * @param executeQuery			caller is executeQuery()
	 * @param executeUpdate			caller is executeUpdate()
     * @param autoGeneratedKeys
     * @param columnIndexes
     * @param columnNames
	 *
     * @return true if the first result is a ResultSet; false if it is an integer
     * @see #getResultSet
     * @see #getUpdateCount
     * @see #getMoreResults
	 * @exception SQLException thrown on failure
     */
	private boolean execute(String sql, boolean executeQuery, boolean executeUpdate,
		int autoGeneratedKeys, int[] columnIndexes, String[] columnNames) throws SQLException
	{
	  synchronized (getConnectionSynchronization()) {

		checkExecStatus();
		if (sql == null) {
			throw newSQLException(SQLState.NULL_SQL_TEXT);
		}
		checkIfInMiddleOfBatch();
		clearResultSets(); // release the last statement executed, if any.

        setupContextStack(); // make sure there's context


		// try to remember the SQL statement in case anybody asks for it
		SQLText = sql;		

		try {
			Activation activation;
			try {
				PreparedStatement preparedStatement = lcc.prepareInternalStatement
				    (lcc.getDefaultSchema(), sql, resultSetConcurrency==
                        java.sql.ResultSet.CONCUR_READ_ONLY, false);
				activation =
					preparedStatement.getActivation(lcc, resultSetType ==
                        java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE);
				checkRequiresCallableStatement(activation);
                InterruptStatus.restoreIntrFlagIfSeen(lcc);
			 } catch (Throwable t) {
				throw handleException(t);
			 }


			// this is for a Statement execution
			activation.setSingleExecution();

			//bug 4838 - save the auto-generated key information in activation. keeping this
			//information in lcc will not work work it can be tampered by a nested trasaction
			if (autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS)
            {
				activation.setAutoGeneratedKeysResultsetInfo(columnIndexes, columnNames);
            }
			return executeStatement(activation, executeQuery, executeUpdate);
		} finally {
		    restoreContextStack();
		}
	  }
	}

    /**
     * JDBC 3.0
     *
     * Executes the given SQL statement, which may return multiple
     * results, and signals the driver that any auto-generated keys
     * should be made available for retrieval. The driver will ignore
     * this signal if the SQL statement is not an INSERT/UPDATE statement.
     *
     * @param sql any SQL statement
     * @param autoGeneratedKeys - a constant indicating whether
     * auto-generated keys should be made available for retrieval using
     * the method getGeneratedKeys; one of the following constants:
     * Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
     * @return rue if the first result is a ResultSet object; false if
     * it is an update count or there are no results
     * @exception SQLException if a database access error occurs
     */
	public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
	{
		return execute(sql, false, false, autoGeneratedKeys, null, null);
	}

    /**
     * JDBC 3.0
     *
     * Executes the given SQL statement, which may return multiple
     * results, and signals the driver that the auto-generated keys
     * indicated in the given array should be made available for retrieval.
     * This array contains the indexes of the columns in the target table
     * that contain the auto-generated keys that should be made available.
     * The driver will ignore the array if the given SQL statement is not an
     * INSERT/UPDATE statement.
     *
     * @param sql any SQL statement
     * @param columnIndexes - an array of the indexes of the columns in the
     * inserted/updated row that should be made available for retrieval by a
     * call to the method getGeneratedKeys
     * @return rue if the first result is a ResultSet object; false if
     * it is an update count or there are no results
     * @exception SQLException if a database access error occurs
     */
	public boolean execute(String sql, int[] columnIndexes) throws SQLException
	{
		return execute(sql, false, true,
			((columnIndexes == null) || (columnIndexes.length == 0))
				? Statement.NO_GENERATED_KEYS
				: Statement.RETURN_GENERATED_KEYS,
			columnIndexes,
			null);
	}

    /**
     * JDBC 3.0
     *
     * Executes the given SQL statement, which may return multiple
     * results, and signals the driver that the auto-generated keys
     * indicated in the given array should be made available for retrieval.
     * This array contains the names of the columns in the target table
     * that contain the auto-generated keys that should be made available.
     * The driver will ignore the array if the given SQL statement is not an
     * INSERT/UPDATE statement.
     *
     * @param sql any SQL statement
     * @param columnNames - an array of the names of the columns in the
     * inserted/updated row that should be made available for retrieval by a
     * call to the method getGeneratedKeys
     * @return rue if the first result is a ResultSet object; false if
     * it is an update count or there are no results
     * @exception SQLException if a database access error occurs
     */
	public boolean execute(String sql, String[] columnNames) throws SQLException
	{
		return execute(sql, false, true,
			((columnNames == null) || (columnNames.length == 0))
				? Statement.NO_GENERATED_KEYS
				: Statement.RETURN_GENERATED_KEYS,
			null,
			columnNames);
	}

    /**
     *  getResultSet returns the current result as a ResultSet.  It
     *  should only be called once per result.
     *
     * @return the current result as a ResultSet; null if the result
     * is an update count or there are no more results or the statement
	 * was closed.
     * @see #execute
     */
	public final java.sql.ResultSet getResultSet() throws SQLException  {
		checkStatus();

		return results;
	}

    /**
     *  getUpdateCount returns the current result as an update count;
     *  if the result is a ResultSet or there are no more results -1
     *  is returned.  It should only be called once per result.
     *
     * <P>The only way to tell for sure that the result is an update
     *  count is to first test to see if it is a ResultSet. If it is
     *  not a ResultSet it is either an update count or there are no
     *  more results.
     *
     * @return the current result as an update count; -1 if it is a
     * ResultSet or there are no more results
     * @see #execute
     */
	public final int getUpdateCount()	throws SQLException  {
		checkStatus();
		return (int) updateCount;
	}

    /**
     * JDBC 4.2
     *
     *  getLargeUpdateCount returns the current result as an update count;
     *  if the result is a ResultSet or there are no more results -1
     *  is returned.  It should only be called once per result. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     */
	public final long getLargeUpdateCount()	throws SQLException  {
		checkStatus();
		return updateCount;
	}

    /**
     * getMoreResults moves to a Statement's next result.  It returns true if
     * this result is a ResultSet.  getMoreResults also implicitly
     * closes any current ResultSet obtained with getResultSet.
     *
     * There are no more results when (!getMoreResults() &amp;&amp;
     * (getUpdateCount() == -1)
     *
     * @return true if the next result is a ResultSet; false if it is
     * an update count or there are no more results
     * @see #execute
	 * @exception SQLException thrown on failure.
     */
	public final boolean getMoreResults() throws SQLException	{
		return getMoreResults(Statement.CLOSE_ALL_RESULTS);
	}

	/////////////////////////////////////////////////////////////////////////
	//
	//	JDBC 2.0 methods that are implemented here because EmbedPreparedStatement
	//  and EmbedCallableStatement in Local20 need access to them, and those
	//	classes extend their peer classes in Local, instead of EmbedStatement
	//	in Local20
	//
	//  We do the same of JDBC 3.0 methods.
	/////////////////////////////////////////////////////////////////////////

    /**
     * JDBC 2.0
     *
     * Determine the result set type.
     *
     * @exception SQLException Feature not implemented for now.
     */
    public final int getResultSetType()
		throws SQLException 
	{
		checkStatus();
		return resultSetType;
	}


    /**
     * JDBC 2.0
     *
     * Give a hint as to the direction in which the rows in a result set
     * will be processed. The hint applies only to result sets created
     * using this Statement object.  The default value is 
     * ResultSet.FETCH_FORWARD.
     *
     * @param direction the initial direction for processing rows
     * @exception SQLException if a database-access error occurs or direction
     * is not one of ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or
     * ResultSet.FETCH_UNKNOWN
     */
    public void setFetchDirection(int direction) throws SQLException {
		
		checkStatus();
                /* fetch direction is meaningless to us. we just save
                 * it off if it is valid  and return the current value if asked.
                 */
                if (direction == java.sql.ResultSet.FETCH_FORWARD || 
                    direction == java.sql.ResultSet.FETCH_REVERSE ||
                    direction == java.sql.ResultSet.FETCH_UNKNOWN )
                {
                    fetchDirection = direction;
                }else
                    throw newSQLException(SQLState.INVALID_FETCH_DIRECTION, direction);
	}

    /**
     * JDBC 2.0
     *
     * Determine the fetch direction.
     *
     * @return the default fetch direction
     * @exception SQLException if a database-access error occurs
     */
    public int getFetchDirection() throws SQLException {
		checkStatus();
		return fetchDirection;
	}


    /**
     * JDBC 2.0
     *
     * Give the JDBC driver a hint as to the number of rows that should
     * be fetched from the database when more rows are needed.  The number 
     * of rows specified only affects result sets created using this 
     * statement. If the value specified is zero, then the hint is ignored.
     * The default value is zero.
     *
     * @param rows the number of rows to fetch
     * @exception SQLException if a database-access error occurs, or the
     * condition 0 &lt;= rows &lt;= this.getMaxRows() is not satisfied.
     */
    public void setFetchSize(int rows) throws SQLException {
		checkStatus();
        if (rows < 0  || (this.getMaxRows() != 0 && 
                             rows > this.getMaxRows()))
        {
	        throw newSQLException(SQLState.INVALID_ST_FETCH_SIZE, rows);
        }else if ( rows > 0 ) // ignore the call if the value is zero
            fetchSize = rows;
	}
  
    /**
     * JDBC 2.0
     *
     * Determine the default fetch size.
     * @exception SQLException if a database-access error occurs
     *
     */
    public int getFetchSize() throws SQLException {
		checkStatus();
		return fetchSize;
	}

    /**
     * JDBC 2.0
     *
     * Determine the result set concurrency.
     *
     * @exception SQLException Feature not implemented for now.
     */
    public int getResultSetConcurrency() throws SQLException {
		checkStatus();
		return resultSetConcurrency;
	}

    /**
     * JDBC 3.0
     *
     * Retrieves the result set holdability for ResultSet objects
     * generated by this Statement object.
     *
     * @return either ResultSet.HOLD_CURSORS_OVER_COMMIT or
     * ResultSet.CLOSE_CURSORS_AT_COMMIT
     * @exception SQLException Feature not implemented for now.
     */
    public final int getResultSetHoldability() throws SQLException {
		checkStatus();
		return resultSetHoldability;
	}

    /**
     * JDBC 2.0
     *
     * Adds a SQL command to the current batch of commmands for the statement.
     * This method is optional.
     *
     * @param sql typically this is a static SQL INSERT or UPDATE statement
     * @exception SQLException if a database-access error occurs, or the
     * driver does not support batch statements
     */
    public void addBatch( String sql ) throws SQLException {
		checkStatus();
  	  synchronized (getConnectionSynchronization()) {
		  if (batchStatements == null)
			  batchStatements = new Vector<Object>();
        batchStatements.add(sql);
  		}
	}

    /**
     * JDBC 2.0
     *
     * Make the set of commands in the current batch empty.
     * This method is optional.
     *
     * @exception SQLException if a database-access error occurs, or the
     * driver does not support batch statements
     */
    public final void clearBatch() throws SQLException {
		checkStatus();
  	  synchronized (getConnectionSynchronization()) {
        batchStatements = null;
  		}
	}

    /**
     * JDBC 2.0
     * 
     * Submit a batch of commands to the database for execution.
     * This method is optional.
	 *
	 * Moving jdbc2.0 batch related code in this class because
	 * callableStatement in jdbc 20 needs this code too and it doesn't derive
	 * from prepared statement in jdbc 20 in our implementation. 
	 * BatchUpdateException is the only new class from jdbc 20 which is being
	 * referenced here and in order to avoid any jdk11x problems, using
	 * reflection code to make an instance of that class. 
     *
     * @return an array of update counts containing one element for each
     * command in the batch.  The array is ordered according
     * to the order in which commands were inserted into the batch
     * @exception SQLException if a database-access error occurs, or the
     * driver does not support batch statements
     */
    public int[] executeBatch() throws SQLException {
        return Util.squashLongs( executeLargeBatch() );
    }
    
    /**
     * JDBC 4.2
     * 
     * Submit a batch of commands to the database for execution.
     * This method is optional. For use with
     * statements which may touch more than Integer.MAX_VALUE rows.
     */
    public long[] executeLargeBatch() throws SQLException {
		checkExecStatus();
		synchronized (getConnectionSynchronization()) 
		{
            setupContextStack();
			int i = 0;
			// As per the jdbc 2.0 specs, close the statement object's current resultset
			// if one is open.
			// Are there results?
			// outside of the lower try/finally since results will
			// setup and restore themselves.
			clearResultSets();

			Vector<Object> stmts = batchStatements;
			batchStatements = null;
			int size;
			if (stmts == null)
				size = 0;
			else
				size = stmts.size();

			long[] returnUpdateCountForBatch = new long[size];

			SQLException sqle;
			try {
				for (; i< size; i++) 
				{
                    // If we saw an interrupt, stop execution of batch now.
                    // throwIf will likely only throw after at least one stm
                    // has been executed, since first time around we probably
                    // didn't do anything to notice interrupts yet.
                    InterruptStatus.throwIf(lcc);
					if (executeBatchElement(stmts.get(i)))
						throw newSQLException(SQLState.RESULTSET_RETURN_NOT_ALLOWED);
					returnUpdateCountForBatch[ i ] = getLargeUpdateCount();
				}

                InterruptStatus.restoreIntrFlagIfSeen(lcc);
				return returnUpdateCountForBatch;
			}
			catch (StandardException se) {

				sqle = handleException(se);
			}
			catch (SQLException sqle2) 
			{
				sqle = sqle2;
			}
			finally 
			{
				restoreContextStack();
			}

            long[] successfulUpdateCount =
                    Arrays.copyOf(returnUpdateCountForBatch, i);

            throw new BatchUpdateException(sqle.getMessage(),
                                           sqle.getSQLState(),
                                           sqle.getErrorCode(),
                                           successfulUpdateCount,
                                           sqle );
        }
	}

	/**
		Execute a single element of the batch. Overridden by EmbedPreparedStatement
	*/
	boolean executeBatchElement(Object batchElement) throws SQLException, StandardException {
		return execute((String)batchElement, false, true, Statement.NO_GENERATED_KEYS, null, null);
	}

    /**
     * JDBC 2.0
     *
     * Return the Connection that produced the Statement.
     *
     * @exception SQLException Exception if it cannot find the connection
     * associated to this statement.
     */
    public final java.sql.Connection getConnection()  throws SQLException {
		checkStatus();

    	java.sql.Connection appConn = getEmbedConnection().getApplicationConnection();
		if ((appConn != applicationConnection) || (appConn == null)) {

			throw Util.noCurrentConnection();
        }
		return appConn;
    }

    /**
     * JDBC 3.0
     *
     * Moves to this Statement obect's next result, deals with any current ResultSet
     * object(s) according to the instructions specified by the given flag, and
     * returns true if the next result is a ResultSet object
     *
     * @param current - one of the following Statement constants indicating what
     * should happen to current ResultSet objects obtained using the method
     * getResultSetCLOSE_CURRENT_RESULT, KEEP_CURRENT_RESULT, or CLOSE_ALL_RESULTS
     * @return true if the next result is a ResultSet; false if it is
     * an update count or there are no more results
     * @see #execute
     * @exception SQLException thrown on failure.
     */
	public final boolean getMoreResults(int current) throws SQLException	{
		checkExecStatus();

		synchronized (getConnectionSynchronization()) {
			if (dynamicResults == null) {
				// we only have the one resultset, so this is
				// simply a close for us.
				clearResultSets();
                closeMeOnCompletion();
				return false;
			}

			int startingClose;
			switch (current) {
			default:
			case Statement.CLOSE_ALL_RESULTS:
				startingClose = 0;
				break;
			case Statement.CLOSE_CURRENT_RESULT:
				// just close the current result set.
				startingClose = currentDynamicResultSet;
				break;
			case Statement.KEEP_CURRENT_RESULT:
				// make the close loop a no-op.
				startingClose = dynamicResults.length;
				break;
			}

			// Close loop.
			SQLException se = null;
			for (int i = startingClose; i <= currentDynamicResultSet && i < dynamicResults.length; i++) {
				EmbedResultSet lrs = dynamicResults[i];
				if (lrs == null)
					continue;


				try {
					lrs.close();
				} catch (SQLException sqle) {
					if (se == null)
						se = sqle;
					else
						se.setNextException(sqle);
				} finally {
					dynamicResults[i] = null;
				}
			}

			if (se != null) {
				// leave positioned on the current result set (?)
				throw se;
			}

			updateCount = -1L;

			while (++currentDynamicResultSet < dynamicResults.length) {

				EmbedResultSet lrs = dynamicResults[currentDynamicResultSet];
				if (lrs != null) {
					if (lrs.isClosed) {
						dynamicResults[currentDynamicResultSet] = null;
						continue;
					}

					results = lrs;

					return true;
				}
			}

			results = null;
			return false;
		}
	}

    /**
     * JDBC 3.0
     *
     * Retrieves any auto-generated keys created as a result of executing this
     * Statement object. If this Statement is a non-insert statement,
     * a null ResultSet object is returned.
     *
     * @return a ResultSet object containing the auto-generated key(s) generated by
     * the execution of this Statement object
     * @exception SQLException if a database access error occurs
     */
	public final java.sql.ResultSet getGeneratedKeys() throws SQLException	{
		checkStatus();
		if (autoGeneratedKeysResultSet == null)
			return null;
		else {
			execute("VALUES IDENTITY_VAL_LOCAL()", true, false, Statement.NO_GENERATED_KEYS, null, null);
			return results;
		}
	}

	/////////////////////////////////////////////////////////////////////////
	//
	//	Implementation specific methods	
	//
	/////////////////////////////////////////////////////////////////////////

	/**
		Execute the current statement.
	    @exception SQLException thrown on failure.
	*/
	boolean executeStatement(Activation a,
                     boolean executeQuery, boolean executeUpdate)
                     throws SQLException {

		// we don't differentiate the update from the resultset case.
		// so, there could be a result set.

		// note: the statement interface will paste together
		// an activation and make sure the prepared statement
		// is still valid, so it is preferrable, for now,
		// to creating our own activation and stuffing it in
		// the prepared statement.

		synchronized (getConnectionSynchronization()) {
            
            if (SanityManager.DEBUG)
            {
                // Ensure that clearResultSets has been called
                // to fulfill [JDBC4: section 15.2.5 ]
                // A ResultSet object is implicitly closed when:
                // The associated Statement object is re-executed
                
                SanityManager.ASSERT(results == null);
                SanityManager.ASSERT(dynamicResults == null);
                SanityManager.ASSERT(autoGeneratedKeysResultSet == null);
           }
            
                        setupContextStack(); // make sure there's context
			boolean retval;

			pvs = a.getParameterValueSet();

			try {

				clearWarnings();

				if (! forMetaData) {
					commitIfNeeded(); // commit the last statement if needed
					needCommit();
				} else {


		        	if (lcc.getActivationCount() > 1) {
		     		  // we do not want to commit here as there seems to be other
					  // statements/resultSets currently opened for this connection.
					} else {
						commitIfNeeded(); // we can legitimately commit
						needCommit();
					}
				}

				// Get the statement. We don't care if it's invalid, because it
				// will be recompiled when we execute it if needed (DERBY-3024).
				PreparedStatement ps = a.getPreparedStatement();

				/*
				** WARNING WARNING
				**
				** Any state set in the activation before execution *must* be copied
				** to the new activation in GenericActivationHolder.execute() when
				** the statement has been recompiled. State such as
				** singleExecution, cursorName, holdability, maxRows.
				*/

				if (cursorName != null)
				{
					a.setCursorName(cursorName);
				}
                
                boolean executeHoldable = getExecuteHoldable();
 
				a.setResultSetHoldability(executeHoldable);

				//reset the activation to clear warnings
				//and clear existing result sets in case this has been cached
				a.reset();
				a.setMaxRows(maxRows);
                ResultSet resultsToWrap =
                        ps.execute(a, forMetaData, timeoutMillis);
                addWarning(ps.getCompileTimeWarnings());
				addWarning(a.getWarnings());


				if (resultsToWrap.returnsRows()) {

                    // The statement returns rows, so calling it with
                    // executeUpdate() is not allowed.
                    if (executeUpdate) {
                        throw StandardException.newException(
                                SQLState.LANG_INVALID_CALL_TO_EXECUTE_UPDATE);
                    }

					EmbedResultSet lresults = factory.newEmbedResultSet(getEmbedConnection(), resultsToWrap, forMetaData, this, ps.isAtomic());
					results = lresults;


					// Set up the finalization of the ResultSet to
					// mark the activation as unused. It will be
					// closed sometime later by the connection
					// outside of finalization.
					if (a.isSingleExecution())
						lresults.singleUseActivation = a;

					updateCount = -1L;
					retval = true;
				}
				else {

					// Only applicable for an insert/update statement, which does not return rows.
					//the auto-generated keys resultset will be null if used for other statement
					if (a.getAutoGeneratedKeysResultsetMode() && (resultsToWrap.getAutoGeneratedKeysResultset() != null))
					{
						resultsToWrap.getAutoGeneratedKeysResultset().open();
						autoGeneratedKeysResultSet = factory.newEmbedResultSet(getEmbedConnection(),
							resultsToWrap.getAutoGeneratedKeysResultset(), false, this, ps.isAtomic());
					}

					updateCount = resultsToWrap.modifiedRowCount();
					results = null; // note that we have none.

                    int dynamicResultCount = 0;
					if (a.getDynamicResults() != null) {
                        dynamicResultCount =
                            processDynamicResults(a.getDynamicResults(),
                                                  a.getMaxDynamicResults());
					}
                    
                    resultsToWrap.close(); // Don't need the result set any more

                    // executeQuery() is not allowed if the statement
                    // doesn't return exactly one ResultSet.
                    if (executeQuery && dynamicResultCount != 1) {
                        throw StandardException.newException(
                                SQLState.LANG_INVALID_CALL_TO_EXECUTE_QUERY);
                    }

                    // executeUpdate() is not allowed if the statement
                    // returns ResultSets.
                    if (executeUpdate && dynamicResultCount > 0) {
                        throw StandardException.newException(
                                SQLState.LANG_INVALID_CALL_TO_EXECUTE_UPDATE);
                    }
					
                    if (dynamicResultCount == 0) {
						if (a.isSingleExecution()) {
							a.close();
						}

						if (!forMetaData)
							commitIfNeeded();
						else {

							if (lcc.getActivationCount() > 1) {
							  // we do not want to commit here as there seems to be other
							  // statements/resultSets currently opened for this connection.
							} else {
								commitIfNeeded(); // we can legitimately commit
							}
						}
					}

                    retval = (dynamicResultCount > 0);
				}

                InterruptStatus.restoreIntrFlagIfSeen(lcc);

            } catch (Throwable t) {
				if (a.isSingleExecution()) {
					try { a.close(); } catch (Throwable tt) {;}
				}
		        throw handleException(t);
			} finally {
			    restoreContextStack();
			}
			return retval;
		}
	}

    /**
     * Add a SQLWarning to this Statement object.
     * If the Statement already has a SQLWarning then it
     * is added to the end of the chain.
     * 
     * @see #getWarnings()
     */
	final void addWarning(SQLWarning sw)
	{
		if (sw != null) {
			if (warnings == null)
				warnings = sw;
			else
				warnings.setNextException(sw);
		}
	}


	/* package */
	public String getSQLText()
	{
		// no need to synchronize - accessing a reference is atomic
		// synchronized (getConnectionSynchronization()) 
		return SQLText;
	}

	public ParameterValueSet getParameterValueSet()
	{
		return pvs;
	}

	/**
     * Throw an exception if this Statement has been closed explictly
     * or it has noticed it has been closed implicitly.
     * JDBC specifications require nearly all methods throw a SQLException
     * if the Statement has been closed, thus most methods call this
     * method or checkExecStatus first.
     * 
     * @exception SQLException Thrown if the statement is marked as closed.
     * 
     * @see #checkExecStatus()
	 */
    final void checkStatus() throws SQLException {
		if (!active) {
            // 
            // Check the status of the connection first
            //
            java.sql.Connection appConn = getEmbedConnection().getApplicationConnection();
            if (appConn == null || appConn.isClosed()) {
                throw Util.noCurrentConnection();
            }

            throw newSQLException(SQLState.ALREADY_CLOSED, "Statement");
        }
	}

	/**
		A heavier weight version of checkStatus() that ensures the application's Connection
		object is still open. This is to stop errors or unexpected behaviour when a [Prepared]Statement
		object is used after the application has been closed. In particular to ensure that
		a Statement obtained from a PooledConnection cannot be used after the application has closed
		its connection (as the underlying Connection is still active).
		To avoid this heavier weight check on every method of [Prepared]Statement it is only used
		on those methods that would end up using the database's connection to read or modify data.
		E.g. execute*(), but not setXXX, etc.
        <BR>
        If this Statement's Connection is closed an exception will
        be thrown and the active field will be set to false,
        completely marking the Statement as closed.
        <BR>
        If the Statement is not currently connected to an active
        transaction, i.e. a suspended global transaction, then
        this method will throw a SQLException but the Statement
        will remain open. The Statement is open but unable to
        process any new requests until its global transaction
        is resumed.
        <BR>
        Upon return from the method, with or without a SQLException
        the field active will correctly represent the open state of
        the Statement.
        
        @exception SQLException Thrown if the statement is marked as closed
        or the Statement's transaction is suspended.
        
        @see #checkStatus()
	*/
	final void checkExecStatus() throws SQLException {
		// getConnection() checks if the Statement is closed
		if (!getConnection().isClosed())
			return;
        
        // Now this connection is closed for all
        // future use.
        active = false;
        	
		throw Util.noCurrentConnection();
	}

	/**
		Close and clear all result sets associated with this statement
		from the last execution.
	*/
	void clearResultSets() throws SQLException
    {
        //
        // Try not to let closeDependentResultSet() close this Statement
        // when we are just cleaning up before the next operation.
        //
        try {
            closingResultSets = true;
            clearResultSetsMinion();
        }
        finally { closingResultSets = false; }

    }
    // don't call this directly. call clearResultSets() instead.
    private void    clearResultSetsMinion() throws SQLException
    {
		SQLException sqle = null;

		try {
			// Are there results?
			// outside of the lower try/finally since results will
			// setup and restore themselves.
			if (results != null) {
				results.close();
				results = null;
			}
		} catch (SQLException s1) {
			sqle = s1;
		}

		try {
			if (autoGeneratedKeysResultSet != null) {
				autoGeneratedKeysResultSet.close();
				autoGeneratedKeysResultSet = null;
			}
		} catch (SQLException sauto) {
			if (sqle == null)
				sqle = sauto;
			else
				sqle.setNextException(sauto);
		}

		// close all the dynamic result sets.
		if (dynamicResults != null) {
			for (int i = 0; i < dynamicResults.length; i++) {
				EmbedResultSet lrs = dynamicResults[i];
				if (lrs == null)
					continue;

				try {
					lrs.close();
				} catch (SQLException sdynamic) {
					if (sqle == null)
						sqle = sdynamic;
					else
						sqle.setNextException(sdynamic);
				}
			}
			dynamicResults = null;
		}

		/*
			  We don't reset statement to null because PreparedStatement
			  relies on it being there for subsequent (post-close) execution
			  requests.  There is no close method on database statement objects.
		*/

		updateCount = -1L; // reset field

		if (sqle != null)
			throw sqle;
	 }  
	
	/**
		Check to see if a statement requires to be executed via a callable statement.
	*/
	void checkRequiresCallableStatement(Activation activation) throws SQLException {

		ParameterValueSet pvs = activation.getParameterValueSet();

		if (pvs == null)
			return;

		if (pvs.checkNoDeclaredOutputParameters()) {
			try {
				activation.close();
			} catch (StandardException se) {
			}
			throw newSQLException(SQLState.REQUIRES_CALLABLE_STATEMENT, SQLText);
		}
	}

	/**
		Transfer my batch of Statements to a newly created Statement.
	*/
	public void transferBatch(EmbedStatement other) throws SQLException {
		
		synchronized (getConnectionSynchronization()) {
			other.batchStatements = batchStatements;
			batchStatements = null;
		}
	}
    
    /**
     * Set the application statement for this Statement.
    */
    public final void setApplicationStatement(EngineStatement s) {
        this.applicationStatement = s;
    }

	private EmbedResultSet[] dynamicResults;
	private int currentDynamicResultSet;

    /**
     * Go through a holder of dynamic result sets, remove those that
     * should not be returned, and sort the result sets according to
     * their creation.
     *
     * @param holder a holder of dynamic result sets
     * @param maxDynamicResultSets the maximum number of result sets
     * to be returned
     * @return the actual number of result sets
     * @exception SQLException if an error occurs
     */
    private int processDynamicResults(java.sql.ResultSet[][] holder,
                                      int maxDynamicResultSets)
        throws SQLException
    {

		EmbedResultSet[] sorted = new EmbedResultSet[holder.length];

		int actualCount = 0;
		for (int i = 0; i < holder.length; i++) {

			java.sql.ResultSet[] param = holder[i];

			java.sql.ResultSet rs = param[0];

            // Clear the JDBC dynamic ResultSet from the language
            // ResultSet for the CALL statement. This stops the
            // CALL statement closing the ResultSet when its language
            // ResultSet is closed, which will happen just after the
            // call to the processDynamicResults() method.
			param[0] = null;
            
            // ignore non-Derby result sets or results sets from another connection
            // and closed result sets.
            EmbedResultSet lrs = EmbedStatement.processDynamicResult(
                    getEmbedConnection(), rs, this);
            
            if (lrs == null)
            {
                continue;
            }

			sorted[actualCount++] = lrs;
		}

		if (actualCount != 0) {

			// results are defined to be ordered according to their creation
			if (actualCount != 1) {
				java.util.Arrays.sort(sorted, 0, actualCount);
			}

			dynamicResults = sorted;

			if (actualCount > maxDynamicResultSets) {
				addWarning(StandardException.newWarning(SQLState.LANG_TOO_MANY_DYNAMIC_RESULTS_RETURNED));

				for (int i = maxDynamicResultSets; i < actualCount; i++) {
					sorted[i].close();
					sorted[i] = null;
				}

				actualCount = maxDynamicResultSets;
			}


			updateCount = -1L;
			results = sorted[0];
			currentDynamicResultSet = 0;

			// 0100C is not returned for procedures written in Java, from the SQL2003 spec.
			// getWarnings(StandardException.newWarning(SQLState.LANG_DYNAMIC_RESULTS_RETURNED));
		}


		return actualCount;
	}
    
    /**
     * Process a ResultSet created in a Java procedure as a dynamic result.
     * To be a valid dynamic result the ResultSet must be:
     * <UL>
     * <LI> From a Derby system
     * <LI> From a nested connection of connection passed in
     * or from the connection itself.
     * <LI> Open
     * </UL>
     * Any invalid ResultSet is ignored.
     * 
     * 
     * @param conn Connection ResultSet needs to belong to
     * @param resultSet ResultSet to be tested
     * @param callStatement Statement that executed the CALL, null if 
     * @return The result set cast down to EmbedResultSet, null if not a valid
     * dynamic result.
     */
    static EmbedResultSet processDynamicResult(EmbedConnection conn,
            java.sql.ResultSet resultSet,
            EmbedStatement callStatement)
    {
        if (resultSet == null)
            return null;

        // ignore non-Derby result sets or results sets from another connection
        if (!(resultSet instanceof EmbedResultSet))
            return null;

        EmbedResultSet lrs = (EmbedResultSet) resultSet;

        if (lrs.getEmbedConnection().rootConnection != conn.rootConnection)
            return null;

        // ignore closed result sets.
        try {
        	//following will check if the JDBC ResultSet or the language
        	//ResultSet is closed. If yes, then it will throw an exception.
        	//So, the exception indicates that the ResultSet is closed and
        	//hence we should ignore it. 
        	lrs.checkIfClosed("");
        } catch (SQLException ex) {
            return null;        	
        }
        
        lrs.setDynamicResultSet(callStatement);

        return lrs;
    }

	/**
		Callback on the statement when one of its result sets is closed.
		This allows the statement to control when it completes and hence
		when it commits in auto commit mode.

        Must have connection synchronization and setupContextStack(), this
        is required for the call to commitIfNeeded().
	*/
	void resultSetClosing(EmbedResultSet closingLRS) throws SQLException {

		// If the Connection is not in auto commit then this statement completion
		// cannot cause a commit.
		if (!getEmbedConnection().autoCommit)
			return;

		// If we have dynamic results, see if there is another result set open.
		// If so, then no commit. The last result set to close will close the statement.
		if (dynamicResults != null) {
			for (int i = 0; i < dynamicResults.length; i++) {
				EmbedResultSet lrs = dynamicResults[i];
				if (lrs == null)
					continue;
				if (lrs.isClosed)
					continue;
				if (lrs == closingLRS)
					continue;

				// at least one still open so no commit now.
				return;
			}
		}

		// new Throwable("COMMIT ON " + SQLText).printStackTrace(System.out);

        // beetle 5383.  Force a commit in autocommit always.  Before this
        // change if client in autocommit opened a result set, did a commit,
        // then next then close a commit would not be forced on the close.
		commitIfAutoCommit();
	}
    
    /**
     * Get the execute time holdability for the Statement.
     * When in a global transaction holdabilty defaults to false.
     * @throws SQLException Error from getResultSetHoldability.
     */
    private boolean getExecuteHoldable() throws SQLException
    {
        if (resultSetHoldability  == java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT)
            return false;
        
        // Simple non-XA case
        if (applicationStatement == this)
            return true;
        
        return applicationStatement.getResultSetHoldability() ==
            java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
    }

	/**
	 * Returns the value of the EmbedStatement's poolable hint,
	 * indicating whether pooling is requested.
	 *
	 * @return The value of the poolable hint.
	 * @throws SQLException if the Statement has been closed.
	 */

	public boolean isPoolable() throws SQLException {
		// Assert the statement is still active (not closed)
		checkStatus();

		return isPoolable;
	}                

	/**
	 * Requests that an EmbedStatement be pooled or not.
	 *
	 * @param poolable requests that the EmbedStatement be pooled if true
	 * and not be pooled if false.
	 * @throws SQLException if the EmbedStatement has been closed.
	 */
     
	public void setPoolable(boolean poolable) throws SQLException {
		// Assert the statement is still active (not closed)
		checkStatus();

		isPoolable = poolable;
	}

    /**
     * Returns false unless {@code interfaces} is implemented.
     *
     * @param interfaces a Class defining an interface.
     * @return true if this implements the interface or directly or indirectly
     * wraps an object that does.
     * @throws SQLException if an error occurs while determining
     * whether this is a wrapper for an object with the given interface.
     */
    public final boolean isWrapperFor(Class<?> interfaces) throws SQLException {
        checkStatus();
        return interfaces.isInstance(this);
    }

    /**
     * Returns {@code this} if this class implements the interface.
     *
     * @param interfaces a Class defining an interface
     * @return an object that implements the interface
     * @throws SQLException if no object if found that implements the interface
     */
    public final <T> T unwrap(java.lang.Class<T> interfaces)
            throws SQLException {
        checkStatus();
        try {
            return interfaces.cast(this);
        } catch (ClassCastException cce) {
            throw newSQLException(SQLState.UNABLE_TO_UNWRAP, interfaces);
        }
    }

    ////////////////////////////////////////////////////////////////////
    //
    // INTRODUCED BY JDBC 4.1 IN JAVA 7
    //
    ////////////////////////////////////////////////////////////////////

    public  void    closeOnCompletion() throws SQLException
    {
		// Assert the statement is still active (not closed)
        checkStatus();
        
        closeOnCompletion = true;
    }

    public  boolean isCloseOnCompletion() throws SQLException
    {
		// Assert the statement is still active (not closed)
        checkStatus();
        
        return closeOnCompletion;
    }

    //
    // For tracking all of the ResultSets produced by this Statement so
    // that the Statement can be cleaned up if closeOnCompletion() was invoked.
    //
    void    closeMeOnCompletion()
    {
        // If necessary, close the Statement after all of its dependent
        // ResultSets have closed.
        if ( active && (!closingResultSets) && closeOnCompletion )
        {
            try {
                if ( isOpen( results ) ) { return; }
                if ( autoGeneratedKeysResultSet != null ) { return; }

                if ( dynamicResults != null )
                {
                    int count = dynamicResults.length;
                    for ( int i = 0; i < count; i++ )
                    {
                        if ( isOpen( dynamicResults[ i ] ) ) { return; }
                    }
                }

                // if we got here, then the Statement has no open ResultSets left
                close();
            }
            catch (SQLException se) { Util.logSQLException( se ); }
        }
    }
    private boolean isOpen( EmbedResultSet rs ) throws SQLException
    {
        return ( (rs != null) && (!rs.isClosed()) );
    }
    
}

