/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/

package com.sap.dbtech.jdbc;

import java.sql.*;
import com.sap.dbtech.jdbc.exceptions.*;
import com.sap.dbtech.jdbc.packet.*;
import com.sap.dbtech.jdbc.translators.*;
import com.sap.dbtech.util.*;
import com.sap.dbtech.vsp001.*;
/**
 *
 */
public class StatementSapDB
    extends ConnectionItem
    implements java.sql.Statement
{
    protected ResultSet         currentResultSet;
    protected int               rowsAffected = -1;
    protected String            cursorName;
    protected String            defaultCursorName;
    protected int               resultSetType = ResultSet.TYPE_FORWARD_ONLY;
    protected int               resultSetConcurrency = defaultConcurrency_C;
    protected int               resultSetHoldability = defaultHoldability_C;
    protected int               fetchDirection = java.sql.ResultSet.FETCH_FORWARD;
    protected int               fetchSize;
    protected java.util.Vector  batchItems;
    protected int               maxRows = 0;
    protected int               maxFieldSize = 0;
    protected int               queryTimeout = 0;
    protected boolean           setWithInfo = false;
    protected boolean           hasRowCount = false;
    protected boolean           canceled = false;
    protected boolean           packetEncodingUnicode = false;
    protected int               cursorUsage; // flag for use of cursor.

    static final int Cursor_not_used = 0; // 0 - not used
    static final int Cursor_in_use   = 1; // 1 - used, must be dropped.
    static final int Cursor_show     = 2; // 2 - EXPLAIN with SHOW as result table name
    static final int Cursor_memory_resultset = 3; // 3 - 'Memory Result Set'
    static final int Cursor_Resurrected = 4; // 4 - Resurrected cursor that should be dropped in case it is not a query.
    static final int Cursor_forward_only = 5; // 5 - Select/Fetch optimized - must not be dropped if the last chunk was alredy fetched.

    protected int               statementType; // flag for use of cursor.
    static final int Statement_User = 0; // statement used by a JDBC application
    static final int Statement_Internal = 1; // internal statement
    static final int Statement_UpdatableResultSet = 2; // internal statement used for UpdatableresultSet


    // Constants
    protected static final int defaultConcurrency_C = java.sql.ResultSet.CONCUR_READ_ONLY;
    protected static final int defaultHoldability_C = java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
    public static final int BATCH_SUCCESS_NO_INFO_C = -2;  /*in JDBC 3.0 Part of standard*/
    public static final int BATCH_EXECUTE_FAILED_C = -3;   /*in JDBC 3.0 Part of standard*/

    /**
     *
     */
    StatementSapDB (ConnectionSapDB connection) throws SQLException{
        this(connection,
             ResultSet.TYPE_FORWARD_ONLY,
             defaultConcurrency_C,
             connection.getHoldability());
    }

    /**
     *
     */
    StatementSapDB (ConnectionSapDB connection, int resultSetType, int resultSetConcurrency,
            int resultSetHoldability) throws SQLException
    {
        super(connection);
        this.defaultCursorName = this.cursorName = connection.nextCursorName();
        switch (resultSetType) {
            case java.sql.ResultSet.TYPE_FORWARD_ONLY:
            case java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE:
            case java.sql.ResultSet.TYPE_SCROLL_SENSITIVE:
                break;
            default:
                throw  new InvalidArgumentValue("resultSetType", "TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, TYPE_SCROLL_SENSITIVE");
        }
        switch (resultSetConcurrency) {
            case java.sql.ResultSet.CONCUR_READ_ONLY:
                // OK
                break;
            case java.sql.ResultSet.CONCUR_UPDATABLE:{
                if (resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY){
                    resultSetType = java.sql.ResultSet.TYPE_SCROLL_SENSITIVE;  
                }
                break;
            }
            default:
                throw  new InvalidArgumentValue("resultSetConcurrency", "CONCUR_READ_ONLY, CONCUR_UPDATABLE");
        }
        switch (resultSetHoldability) {
            case java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT:
            case java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT:
                // OK
                break;
            default:
                throw  new InvalidArgumentValue("resultSetHoldability", "CLOSE_CURSORS_AT_COMMIT, HOLD_CURSORS_OVER_COMMIT");
        }
        this.resultSetType = resultSetType;
        this.resultSetConcurrency = resultSetConcurrency;
        this.resultSetHoldability = resultSetHoldability;
        this.statementType = StatementSapDB.Statement_User;

        /*adding the new stmt to the statement container of the corresponding connection*/
        if (this.resultSetHoldability == java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT){
          synchronized(connection.statementContainer){
             connection.statementContainer.add(new java.lang.ref.WeakReference(this));
          }
        }

    }
        /**
         * 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 {
            if (this.batchItems == null) {
                this.batchItems = new java.util.Vector ();
            }
            this.batchItems.addElement(sql);
        }
    /**
     * cancel method comment.
     */
    public void cancel() throws SQLException {
        this.assertOpen ();
        this.canceled = true;
        if (this.currentResultSet != null){
            ((ResultSetSapDB)this.currentResultSet).cancel();
        }
        this.connection.cancel (this);
    }
        /**
         * JDBC 2.0
         *
         * Makes 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 void clearBatch() throws SQLException {
            this.batchItems = null;
        }
    /**
     * close method comment.
     */
    public void close() throws SQLException {
        if (this.connection != null) {
            closeResultSet(false);
            this.connection = null;
        }
    }


    /**
     * Closes the current result set.
     * @exception SQLException if a database error occurs.
     */
    protected void closeResultSet(boolean restore)
        throws SQLException
    {
        // execute this synchronized on the connection, to prevent
        // too fast dropping.
        synchronized(this.connection) {
                // First, we close the current result set.
                if(this.currentResultSet != null) {
                        // Close the result set, hereby honoring EXPLAIN
                        this.currentResultSet.close();
                }
                // Backup to the default cursor if we had an explain
                if(this.cursorUsage == Cursor_show) {
                        this.cursorName = this.defaultCursorName;
                }
                // Now backup the original cursor name. This may even
                // be the default cursor name used before the EXPLAIN
                // command.
                if(restore) {
                                if(this.connection.restoreCursor(this.cursorName)) {
                                        this.cursorUsage = Cursor_Resurrected; // restored, maybe necessary
                                                              // to drop later.
                                } else {
                                        this.cursorUsage = Cursor_not_used; // Not restored, was gone.
                                }
                                this.currentResultSet = null;
                } else {
                        this.cursorUsage      = Cursor_not_used;
                        this.currentResultSet = null;
                }
        }
    }

    /**
     * execute method comment.
     */
    public boolean execute(String sql) throws SQLException {
        boolean isQuery = false;
        this.setWithInfo = true;
        return this.execute (sql, isQuery, this.resultSetType, this.resultSetConcurrency);
    }
    /**
     * execute method comment.
     */
    protected boolean
    execute(
        String sql,
        boolean forQuery,
        int resultSetType,
        int resultSetConcurrency)
    throws SQLException
    {
      try{
        this.canceled = false; 
        clearWarnings();

        this.assertOpen ();
        ReplyPacket replyPacket;
        boolean isQuery;
        boolean inTrans = this.connection.isInTransaction ();

        if (sql == null) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_SQLSTATEMENT_NULL),
                                        "42000");
        }

        try {
            this.closeResultSet(true);
            if (this.connection.dbsCache != null) {
                this.connection.dbsCache.use (sql);
            }
            replyPacket = this.sendSQL (sql, resultSetType, resultSetConcurrency, false);
            isQuery = this.parseResult (replyPacket,sql,null,null);
        }
        catch (TimeoutException timeout) {
            if (inTrans) {
                throw timeout;
            }
            else {
                isQuery = this.execute (sql,forQuery, resultSetType, resultSetConcurrency);
            }
        }
        return isQuery;
      } finally{
        this.canceled = false;  
      }  
    }
    /**
     *
     */
     protected String getUpdTablename(String sqlCmd)
     throws SQLException{
        String tabname = null;
        byte[] parseid = null;
        RequestPacket requestPacket = this.connection.getRequestPacket(this.packetEncodingUnicode);
        requestPacket.initParseCommand (sqlCmd+ " FOR UPDATE OF ", true, false);
        ReplyPacket replyPacket = this.connection.execute (requestPacket, this, ConnectionSapDB.GC_ALLOWED);
        PartEnumeration enume = replyPacket.partEnumeration ();
        while (enume.hasMoreElements ()) {
            enume.nextElement ();
            switch (enume.partKind ()) {
              case PartKind.Parsid_C:
                  int parseidPos = replyPacket.getPartDataPos ();
                  parseid = replyPacket.getBytes (parseidPos, 12);
                  break;
              case PartKind.Tablename_C:
                  tabname =  replyPacket.getString (replyPacket.getPartDataPos (), replyPacket.partLength ());
                  break;
            }
        }
        this.connection.dropParseid(parseid);
        return tabname;
     }
    /**
     * JDBC 2.0
     *
     * Submits a batch of commands to the database for execution.
     * This method is optional.
     *
     * @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
    {
        if (this.batchItems == null) {
            return new int [0];
        }
        this.setWithInfo = false;
        boolean inTrans = this.connection.isInTransaction ();
        int count = this.batchItems.size ();
        int[] result = new int[count];
        java.util.Vector items = this.batchItems;
        this.batchItems = null;
        try {
            this.canceled = false;
            ReplyPacket replyPacket;
            int inputCursor = 0;
            int receiveCursor = 0;

            this.packetEncodingUnicode = false;
            while (inputCursor < count) {
                RequestPacket requestPacket = this.connection.getRequestPacket(this.packetEncodingUnicode);
                try{
                    requestPacket.initDbsCommand (this.connection.autocommit, (String) items.elementAt(inputCursor), ResultSet.TYPE_FORWARD_ONLY);
                } catch (ConversionExceptionSapDB e) {
                    this.connection.freeRequestPacket(requestPacket);
                    requestPacket = this.connection.getRequestPacket (true);
                    this.packetEncodingUnicode = true;
                    requestPacket.initDbsCommand (this.connection.autocommit, (String) items.elementAt(inputCursor), ResultSet.TYPE_FORWARD_ONLY);
                }
                requestPacket.setMaxSegment(Short.MAX_VALUE);
                ++inputCursor;
                try {
                    while ((inputCursor < count)
                            && requestPacket.initDbsCommand((String) items
                                    .elementAt(inputCursor), false,
                                    this.connection.autocommit,
                                    ResultSet.TYPE_FORWARD_ONLY)) {
                        ++inputCursor;
                    }
                } catch (ConversionExceptionSapDB e) {
                    requestPacket.dropSegment();
                    this.packetEncodingUnicode = true;
                }
                replyPacket = this.connection.execute(requestPacket, true, false, this,
                                                      ConnectionSapDB.GC_DELAYED);
                for (; receiveCursor < inputCursor; ++receiveCursor) {
                    if (replyPacket.weakReturnCode () != 0) {
                        int [] resArr = new int[receiveCursor];
                        java.lang.System.arraycopy(result,0,resArr,0,receiveCursor);
                        throw new BatchUpdateExceptionSapDB (
                            resArr,
                            replyPacket.createException());
                    }
                    int functionCode = replyPacket.functionCode ();
                    if ((functionCode == FunctionCode.Select_FC)
                        || (functionCode == FunctionCode.Show_FC)
                        || (functionCode == FunctionCode.Explain_FC)) {
                      int [] resArr = new int[receiveCursor];
                      java.lang.System.arraycopy(result,0,resArr,0,receiveCursor);
                      throw new BatchUpdateExceptionSapDB
                          (MessageTranslator.translate(MessageKey.ERROR_BATCHRESULTSET_WITHNUMBER,
                                                       Integer.toString(receiveCursor+1)),
                           "2A000",
                           resArr);
                    }
                    else {
                      result [receiveCursor] = replyPacket.resultCount(false);
                      if (result[receiveCursor]==-1)
                        result [receiveCursor] = StatementSapDB.BATCH_SUCCESS_NO_INFO_C;
                    }
                    // jumps one segment to far, but this is
                    // catched in ReplyPacket
                    replyPacket.nextSegment ();
                }
            }
            return result;

        }
        catch (TimeoutException timeout) {
            if (inTrans) {
                throw timeout;
            }
            else {
                this.batchItems = items;
                return this.executeBatch ();
            }
        } finally{
            this.canceled = false;
        }
    }
    /**
     * executeQuery method comment.
     */
    public ResultSet executeQuery(String sql) throws SQLException {
        // this.assertOpen (); done in this.execute ()
        if (! this.cmdIsQuery(sql))
          throw new SQLExceptionSapDB (MessageTranslator.translate(MessageKey.ERROR_SQLSTATEMENT_ROWCOUNT));
        this.setWithInfo = true;

        this.execute (sql, true, this.resultSetType, this.resultSetConcurrency);
        return this.currentResultSet;
    }
    /**
     * executeQuery method comment.
     */
    public ResultSetSapDB executeQuerySap (String sql) throws SQLException {
        return (ResultSetSapDB) this.executeQuery (sql);
    }
    /**
     * executeUpdate method comment.
     */
    public int executeUpdate(String sql) throws SQLException {
        this.setWithInfo = false;
        boolean isQuery = this.execute (sql, false, ResultSet.TYPE_FORWARD_ONLY, defaultConcurrency_C);
        if (isQuery)
          throw new SQLExceptionSapDB (MessageTranslator.translate(MessageKey.ERROR_SQLSTATEMENT_RESULTSET));
        if(this.hasRowCount) {
            return this.rowsAffected;
        } else {
            return 0;
        }
    }
    /**
     *
     * @exception java.sql.SQLException The exception description.
     */
    protected void finalize () throws Throwable {
    	try {
    		this.close();	
    	} catch(SQLException ex) {
    		// ignored
    	}
    	super.finalize();
    }
        /**
         * JDBC 2.0
         *
         * Retrieves the direction for fetching rows from
         * database tables that is the default for result sets
         * generated from this <code>Statement</code> object.
         * If this <code>Statement</code> object has not set
         * a fetch direction by calling the method <code>setFetchDirection</code>,
         * the return value is implementation-specific.
         *
         * @return the default fetch direction for result sets generated
         *          from this <code>Statement</code> object
         * @exception SQLException if a database access error occurs
         */
        public int getFetchDirection() throws SQLException {
            return this.fetchDirection;
        }
        /**
         * JDBC 2.0
         *
         * Retrieves the number of result set rows that is the default
         * fetch size for result sets
         * generated from this <code>Statement</code> object.
         * If this <code>Statement</code> object has not set
         * a fetch size by calling the method <code>setFetchSize</code>,
         * the return value is implementation-specific.
         * @return the default fetch size for result sets generated
         *          from this <code>Statement</code> object
         * @exception SQLException if a database access error occurs
         */
        public int getFetchSize() throws SQLException {
            return this.fetchSize;
        }
        //----------------------------------------------------------------------

        /**
         * Returns the maximum number of bytes allowed
         * for any column value.
         * This limit is the maximum number of bytes that can be
         * returned for any column value.
         * The limit applies only 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 if a database access error occurs
         */
        public int getMaxFieldSize() throws SQLException {
            return this.maxFieldSize;
        }
    /**
     * getMaxRows method comment.
     */
    public int getMaxRows() throws SQLException {
        return this.maxRows;
    }
    /**
     * getMoreResults method comment.
     */
    public boolean getMoreResults() throws SQLException {
        this.assertOpen ();
        this.rowsAffected = -1;
        return false;
    }
    /**
     * getQueryTimeout method comment.
     */
    public int getQueryTimeout() throws SQLException {
        return this.queryTimeout;
    }
    /**
     * getResultSet method comment.
     */
    public ResultSet getResultSet() throws SQLException {
        this.assertOpen ();
        return this.currentResultSet;
    }
        /**
         * JDBC 2.0
         *
         * Retrieves the result set concurrency.
         */
        public int getResultSetConcurrency() throws SQLException {
            return this.resultSetConcurrency;
        }
        /**
         * JDBC 2.0
         *
         * Determine the result set type.
         */
        public int getResultSetType()  throws SQLException {
            return this.resultSetType;
        }
    /**
     * getUpdateCount method comment.
     */
    public int getUpdateCount() throws SQLException {
        this.assertOpen ();
        return this.rowsAffected;
    }
    /**
     *
     * @return boolean
     * @param sql java.lang.String
     */
    protected boolean isQuerySQL (String sql) {
        String begin = sql.substring (0, Math.min (30, sql.length ())).trim().toLowerCase();
        return begin.startsWith("select");
    }

    /**
     *
     */
    protected boolean parseResult (
        ReplyPacket replyPacket,
        String sqlCmd,
        DBTechTranslator [] infos,
        String [] columnNames)
            throws SQLException
    {
        PartEnumeration enume;
        String tableName = null;
        boolean isQuery = false;
        boolean rowNotFound = false;
        boolean dataPartFound = false;

        this.rowsAffected = -1;
        this.hasRowCount  = false;
        int functionCode = replyPacket.functionCode ();
        
        switch (functionCode) {
            case FunctionCode.Select_FC:
            case FunctionCode.Show_FC:
            case FunctionCode.DBProcWithResultSetExecute_FC:
            case FunctionCode.Explain_FC:{ 
                isQuery = true;
                break;
            } 
            case  FunctionCode.Commit_FC:{
               if (this.connection.closeCursorAtCommit()){
                 this.connection.isClosed();  
               }
               this.connection.setInTransaction(false);
               break;
            }
            case  FunctionCode.Commit_Release_FC:{
                this.connection.setInTransaction(false);
                this.connection.releaseSession();
                break;
            }
            case  FunctionCode.Rollback_FC:{
                this.connection.setInTransaction(false);
                break;
             }
             case  FunctionCode.Rollback_Release_FC:{
                 this.connection.setInTransaction(false);
                 this.connection.releaseSession();
                 break;
              }
        }

        enume = replyPacket.partEnumeration ();
        while (enume.hasMoreElements ()) {
            enume.nextElement ();
            int partKind = enume.partKind ();
            switch (partKind) {
                 case PartKind.Columnnames_C:{
                     if (columnNames==null)
                       columnNames = replyPacket.parseColumnNames ();
                       break;
                 }
                 case PartKind.Shortinfo_C:{
                     if (infos==null)
                         infos = replyPacket.parseShortFields (this.connection.isSpaceoptionSet, false, null, false);
                       break;
                }
                 case PartKind.Vardata_Shortinfo_C:{
                     if (infos==null){
                         infos = replyPacket.parseShortFields (this.connection.isSpaceoptionSet, false, null, true);
                     }    
                       break;
                }
                case PartKind.Resultcount_C:
                    // only if this is not a query
                    if(!isQuery) {
                        this.rowsAffected = replyPacket.resultCount (true);
                        this.hasRowCount=true;
                    }
                    break;
                case PartKind.Resulttablename_C: {
                    String cname = replyPacket.getString (replyPacket.getPartDataPos (), replyPacket.partLength ());
                    if (cname.length()>0){
                      this.cursorName = cname;
                    }
                    break;
                }
                case PartKind.Data_C: {
                    dataPartFound = true;
                    break;
                }
                case PartKind.Errortext_C:{
                    // the SELECT returned row not found
                    // this.addWarning (new SQLWarning (MessageTranslator.translate(MessageKey.WARNING_EMPTY_RESULTSET)));
                    if (replyPacket.returnCode()==100) {
                        rowsAffected=-1;
                        rowNotFound=true;
                        if(!isQuery) { // for any select update count must be -1
                            rowsAffected=0;
                        }
                    }
                    break;
                }
                case PartKind.Tablename_C:
                    tableName = replyPacket.getString (replyPacket.getPartDataPos (), replyPacket.partLength ());
                    break;
                case PartKind.ParsidOfSelect_C:
                    // ignore
                    break;
                default:
//                    this.addWarning
//                        (new SQLWarning
//                         (MessageTranslator.translate(MessageKey.WARNING_PART_NOTHANDLED,
//                                                      PartKind.names [enume.partKind ()])
//                          )
//                         );
                    break;
            }
        }

        if (isQuery){
            if(functionCode == FunctionCode.Explain_FC && this.cursorName.equals("SHOW")) {
               this.cursorUsage = Cursor_show;
            } else {
               this.cursorUsage = (this.resultSetType==ResultSet.TYPE_FORWARD_ONLY 
                                   && this.connection.isKernelFeaturesupported(Feature.sp1f_check_scrollableoption)
               		               && functionCode != FunctionCode.DBProcWithResultSetExecute_FC)
								   ?Cursor_forward_only:Cursor_in_use;
            }
            if (  replyPacket.nextSegment()!= -1
              &&replyPacket.functionCode() == FunctionCode.Describe_FC ){
            boolean newSFI = true;
            enume = replyPacket.partEnumeration ();
            while (enume.hasMoreElements ()) {
                enume.nextElement ();
                int partKind = enume.partKind ();
                switch (partKind) {
                     case PartKind.Columnnames_C:{
                         if (columnNames==null)
                           columnNames = replyPacket.parseColumnNames ();
                         break;
                     }
                     case PartKind.Shortinfo_C:{
                         if (infos==null)
                             infos = replyPacket.parseShortFields (this.connection.isSpaceoptionSet, false, null, false);
                         break;
                    }
                     case PartKind.Vardata_Shortinfo_C:{
                         if (infos==null)
                             infos = replyPacket.parseShortFields (this.connection.isSpaceoptionSet, false, null, true);
                         break;
                    }
                    case PartKind.Errortext_C:{
                      newSFI = false;
                      break;
                    }
                    default:
                        break;
                }
              }
            if (newSFI)
              this.updateFetchInfo(infos,  columnNames);
          }
          if (dataPartFound){
            createResultSet (sqlCmd, tableName, this.cursorName, infos, columnNames, rowNotFound, replyPacket);
          } else{
            createResultSet (sqlCmd, tableName, this.cursorName, infos, columnNames, rowNotFound, null);
          }
        } else if(cursorUsage == Cursor_Resurrected) { // Cursor was recycled, must be dropped now.
            this.connection.dropCursor(this.cursorName);
            this.cursorUsage = Cursor_not_used;
        }
        return isQuery;
      }

      protected void createResultSet (
                   String sqlCmd,
                   String tableName,
                   String cursorName,
                   DBTechTranslator [] infos,
                   String[] columnNames,
                   boolean rowNotFound,
                   ReplyPacket reply)
                    throws SQLException
      {
        try {
            FetchInfo fetchInfo = this.getFetchInfo (cursorName, infos, columnNames);
            if (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_UPDATABLE) {
                if (tableName==null){
                   tableName=this.getUpdTablename(sqlCmd);
                }
                this.currentResultSet = new UpdatableResultSetSapDB (this.connection, fetchInfo, this, this.fetchSize,
                                                                     this.maxRows, tableName, this.cursorUsage, reply);
            }
            else {
                this.currentResultSet = new ResultSetSapDB (this.connection, fetchInfo, this, this.fetchSize,
                                                            this.maxRows, this.cursorUsage, reply);
            }
        }
        catch (SQLException sqlExc) {
            if (sqlExc.getErrorCode () == -4000) {
                this.currentResultSet = new MemoryResultSetSapDB (
                    new String [] {"ROW NOT FOUND"}, new Object [][] {});
                this.cursorUsage = Cursor_memory_resultset;
            }
            else {
                throw sqlExc;
            }
        }
        if (this.fetchDirection == java.sql.ResultSet.FETCH_REVERSE) {
            this.currentResultSet.setFetchDirection (this.fetchDirection);
        }
        if (rowNotFound && this.currentResultSet instanceof ResultSetSapDB) {
            ((ResultSetSapDB)this.currentResultSet).setEmpty(true);
        }
      }
    /**
     *
     */
    protected FetchInfo
    getFetchInfo (
        String cursorName,
        DBTechTranslator [] infos,
        String [] columnNames)
    throws SQLException
    {
        FetchInfo result;

        result = new FetchInfo (this.connection,
                cursorName, infos, columnNames, this.packetEncodingUnicode);
        return result;
    }
    /**
     *
     * @param requestPacket com.sap.dbtech.jdbc.packet.RequestPacket
     * @param sqlCmd java.lang.String
     * @exception java.sql.SQLException The exception description.
     */
    ReplyPacket sendCommand (
        RequestPacket requestPacket,
        String sqlCmd,
        int gcFlags,
        boolean parseAgain)
    throws SQLException
    {
        ReplyPacket replyPacket;
        requestPacket.initDbsCommand (this.connection.autocommit, sqlCmd, this.resultSetType);
        if (this.setWithInfo)
          requestPacket.setWithInfo();
        requestPacket.addCursorPart(this.cursorName);
        replyPacket = this.connection.execute (requestPacket, this, gcFlags);
        return replyPacket;
    }
    /**
     *
     * @return com.sap.dbtech.jdbc.packet.ReplyPacket
     * @param sql java.lang.String
     * @param parseAgain true if this is a parse again operation.
     * @exception java.sql.SQLException The exception description.
     */
    protected ReplyPacket sendSQL (String sql, int resultSetType, int resultSetConcurrency, boolean parseAgain)

        throws SQLException
    {
        RequestPacket requestPacket;
        ReplyPacket replyPacket;
        String actualSQL = sql;
        boolean touchedCommand = false;
        if (resultSetConcurrency == java.sql.ResultSet.CONCUR_UPDATABLE) {
            actualSQL = actualSQL + " FOR UPDATE OF ";
            touchedCommand = true;
        }
        if (resultSetType == java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE) {
            actualSQL = actualSQL + " FOR REUSE";
            touchedCommand = true;
        }
        try{
          if (touchedCommand) {
              requestPacket = this.connection.getRequestPacket (false);
              this.packetEncodingUnicode = false;
              try {
                  try{
                      replyPacket = this.sendCommand (requestPacket, actualSQL, ConnectionSapDB.GC_ALLOWED, parseAgain);
                      this.packetEncodingUnicode = false;
                  } catch (ConversionExceptionSapDB e) {
                      this.connection.freeRequestPacket(requestPacket);
                      requestPacket = this.connection.getRequestPacket (true);
                      this.packetEncodingUnicode = true;
                      replyPacket = this.sendCommand (requestPacket, actualSQL, ConnectionSapDB.GC_ALLOWED, parseAgain);
                  }
              }
              catch (SQLException sqlExc) {
                  if (false) {
                      // check for special rc: stmt OK, but not updatable
                      throw new NotImplemented(MessageTranslator.translate(MessageKey.ERROR_NOTUPDATABLE));
                  }
                  // if (this.connection.getReplyPacket ().functionCode () != FunctionCode.Select_FC) {
                  requestPacket = this.connection.getRequestPacket (this.packetEncodingUnicode);
                  replyPacket = this.sendCommand (requestPacket, sql, ConnectionSapDB.GC_ALLOWED, parseAgain);
              }
          }
          else {
            requestPacket = this.connection.getRequestPacket (false);
            try {
                replyPacket = this.sendCommand (requestPacket, sql, ConnectionSapDB.GC_ALLOWED, parseAgain);
                this.packetEncodingUnicode = false;
            } catch (ConversionExceptionSapDB e) {
                this.connection.freeRequestPacket(requestPacket);
                requestPacket = this.connection.getRequestPacket (true);
                this.packetEncodingUnicode = true;
                replyPacket = this.sendCommand (requestPacket, sql, ConnectionSapDB.GC_ALLOWED, parseAgain);
            }
          }
        }
        catch (ArrayIndexOutOfBoundsException ex) {
            // tbd: info about current length?
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_SQLSTATEMENT_TOOLONG),
                                        "42000");
        }
        return replyPacket;
    }
    /**
     * setCursorName method comment.
     */
    public void setCursorName(String name) throws SQLException {
        this.assertOpen();
        if ((name != null) && (name.length() > 0)) {
            // old cursor name will be dropped in close result set,
            // when due to the name the resurrection does not match.
            this.defaultCursorName = this.cursorName = name;
        }
    }
    /**
     * setEscapeProcessing method comment.
     */
    public void setEscapeProcessing(boolean enable) throws SQLException {
        this.assertOpen ();
        // ignore, escapes are always processed by kernel
    }
        /**
         * JDBC 2.0
         *
         * Gives the driver 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.
         * <p>Note that this method sets the default fetch direction for
         * result sets generated by this <code>Statement</code> object.
         * Each result set has its own methods for getting and setting
         * its own fetch direction.
         * @param direction the initial direction for processing rows
         * @exception SQLException if a database access error occurs
         * or the given direction
         * is not one of ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or
         * ResultSet.FETCH_UNKNOWN
         */
        public void setFetchDirection(int direction) throws SQLException {
            switch (direction) {
                case java.sql.ResultSet.FETCH_FORWARD:
                case java.sql.ResultSet.FETCH_REVERSE:
                case java.sql.ResultSet.FETCH_UNKNOWN:
                    // OK
                    break;
                default:
                    throw new InvalidArgumentValue ("direction", "FETCH_FORWARD, FETCH_REVERSE, FETCH_UNKNOWN");
            }
            this.fetchDirection = direction;
        }
        /**
         * JDBC 2.0
         *
         * Gives 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 affects only 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 <= rows <= this.getMaxRows() is not satisfied.
         */
        public void setFetchSize(int rows) throws SQLException {
            if (rows < 0)
              throw new SQLExceptionSapDB
                  (MessageTranslator.translate(MessageKey.ERROR_INVALID_FETCHSIZE, Integer.toString(rows)),
                   "22003");

            this.fetchSize = rows;
        }
    /**
     * setMaxFieldSize method comment.
     */
    public void setMaxFieldSize(int max) throws SQLException {
       if (max < 0)
           throw new SQLExceptionSapDB
               (MessageTranslator.translate(MessageKey.ERROR_INVALID_MAXFIELDSIZE, Integer.toString(max)),
                "22003");
       this.assertOpen ();
       this.maxFieldSize = max;
    }
    /**
     * setMaxRows method comment.
     * why should anyone want to do this ???
     */
    public void setMaxRows(int max) throws SQLException {
       if (max < 0)
           throw new SQLExceptionSapDB
               (MessageTranslator.translate(MessageKey.ERROR_INVALID_MAXROWS, Integer.toString(max)),
                "22003");
       this.assertOpen ();
       if (max > 0)
        this.maxRows = max;
       else
        this.maxRows = Integer.MAX_VALUE; //no limit
    }
    /**
     * setQueryTimeout method comment.
     */
    public void setQueryTimeout(int seconds) throws SQLException {
       if (seconds < 0)
           throw new SQLExceptionSapDB
               (MessageTranslator.translate(MessageKey.ERROR_INVALID_QUERYTIMEOUT, Integer.toString(seconds)),
                "22003");
        this.assertOpen ();
        this.queryTimeout = seconds;
        // not handled, because no way of setting this
        // short of setting up another thread to watch this
    }
    /*
     * checks wheater the sql statement is a query or not
     */
    private boolean cmdIsQuery(String sql){
      String[] keywords = {"SELECT","CALL","DECLARE","SHOW","EXPLAIN"};
      int pos = 0;
      if (sql == null)
        return false;
      /*ignore leading open braces and whitespaces*/
      char current = sql.charAt(pos);
      while (current == '(' || Character.isWhitespace(current))
        current = sql.charAt(++pos);
      for (int i = 0; i < keywords.length; i++) {
        if (sql.regionMatches(true,pos,keywords[i],0,keywords[i].length()))
         return true;
      }
      return false;
    }

    protected void updateFetchInfo(DBTechTranslator [] infos,  String[] columnNames) throws SQLException  {
      return;
    }
    /**
     * Moves to this <code>Statement</code> object's next result, returns
     * <code>true</code> if it is a <code>ResultSet</code> object, and
     * implicitly closes any current <code>ResultSet</code>
     * object(s) obtained with the method <code>getResultSet</code>.
     *
     * <P>There are no more results when the following is true:
     * <PRE>
     *      <code>(!getMoreResults() && (getUpdateCount() == -1)</code>
     * </PRE>
     *
     * @return <code>true</code> if the next result is a <code>ResultSet</code>
     *         object; <code>false</code> if it is an update count or there are
     *         no more results
     * @exception SQLException if a database access error occurs
     * @see #execute
     */
    public boolean getMoreResults (int parm1) throws java.sql.SQLException {
        return  this.getMoreResults();
    }
    /**
     * Retrieves any auto-generated keys created as a result of executing this
     * <code>Statement</code> object. If this <code>Statement</code> object did
     * not generate any keys, an empty <code>ResultSet</code>
     * object is returned.
     *
     * @return a <code>ResultSet</code> object containing the auto-generated key(s)
     *         generated by the execution of this <code>Statement</code> object
     * @exception SQLException if a database access error occurs
     * @since 1.4
     */
    public ResultSet getGeneratedKeys () throws java.sql.SQLException {
        this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        return  null;
    }

    /**
     * Executes the given SQL statement and signals the driver with the
     * given flag about whether the
     * auto-generated keys produced by this <code>Statement</code> object
     * should be made available for retrieval.
     *
     * @param sql must be an SQL <code>INSERT</code>, <code>UPDATE</code> or
     *        <code>DELETE</code> statement or an 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:
     *         <code>Statement.RETURN_GENERATED_KEYS</code>
     *         <code>Statement.NO_GENERATED_KEYS</code>
     * @return either the row count for <code>INSERT</code>, <code>UPDATE</code>
     *         or <code>DELETE</code> statements, or <code>0</code> for SQL
     *         statements that return nothing
     * @exception SQLException if a database access error occurs, the given
     *            SQL statement returns a <code>ResultSet</code> object, or
     *            the given constant is not one of those allowed
     * @since 1.4
     */
    public int executeUpdate (String sql, int autoGeneratedKeys) throws SQLException {
        if (autoGeneratedKeys == StatementSapDB.RETURN_GENERATED_KEYS) {
            this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        }
        return  this.executeUpdate(sql);
    }

    /**
     * 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 <code>INSERT</code> statement.
     *
     * @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
     *        <code>DELETE</code> statement or an SQL statement that returns nothing,
     *        such as an SQL DDL statement
     * @param columnIndexes an array of column indexes indicating the columns
     *        that should be returned from the inserted row
     * @return either the row count for <code>INSERT</code>, <code>UPDATE</code>,
     *         or <code>DELETE</code> statements, or 0 for SQL statements
     *         that return nothing
     * @exception SQLException if a database access error occurs or the SQL
     *            statement returns a <code>ResultSet</code> object
     * @since 1.4
     */
    public int executeUpdate (String sql, int columnIndexes[]) throws SQLException {
        if (columnIndexes.length != 0) {
            this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        }
        return  this.executeUpdate(sql);
    }

    /**
     * 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 <code>INSERT</code> statement.
     *
     * @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
     *        <code>DELETE</code> statement or an 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 <code>INSERT</code>, <code>UPDATE</code>,
     *         or <code>DELETE</code> statements, or 0 for SQL statements
     *         that return nothing
     * @exception SQLException if a database access error occurs
     *
     * @since 1.4
     */
    public int executeUpdate (String sql, String columnNames[]) throws SQLException {
        if (columnNames.length != 0) {
            this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        }
        return  this.executeUpdate(sql);
    }

    /**
     * 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 <code>INSERT</code> statement.
     *
     * In some (uncommon) situations, a single SQL statement may return
     * multiple result sets and/or update counts.  Normally you can ignore
     * this unless you are (1) executing a stored procedure that you know may
     * return multiple results or (2) you are dynamically executing an
     * unknown SQL string.
     *
     * The <code>execute</code> method executes an SQL statement and indicates the
     * form of the first result.  You must then use the methods
     * <code>getResultSet</code> or <code>getUpdateCount</code>
     * to retrieve the result, and <code>getMoreResults</code> to
     * move to any subsequent result(s).
     *
     * @param sql any SQL statement
     * @param autoGeneratedKeys a constant indicating whether auto-generated
     *        keys should be made available for retrieval using the method
     *        <code>getGeneratedKeys</code>; one of the following constants:
     *        <code>Statement.RETURN_GENERATED_KEYS</code> or
     *	      <code>Statement.NO_GENERATED_KEYS</code>
     * @return <code>true</code> if the first result is a <code>ResultSet</code>
     *         object; <code>false</code> if it is an update count or there are
     *         no results
     * @exception SQLException if a database access error occurs
     * @see #getResultSet
     * @see #getUpdateCount
     * @see #getMoreResults
     * @see #getGeneratedKeys
     *
     * @since 1.4
     */
    public boolean execute (String sql, int autoGeneratedKeys) throws SQLException {
        if (autoGeneratedKeys == StatementSapDB.RETURN_GENERATED_KEYS) {
            this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        }
        return  this.execute(sql);
    }

    /**
     * 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 <code>INSERT</code> statement.
     *
     * Under some (uncommon) situations, a single SQL statement may return
     * multiple result sets and/or update counts.  Normally you can ignore
     * this unless you are (1) executing a stored procedure that you know may
     * return multiple results or (2) you are dynamically executing an
     * unknown SQL string.
     *
     * The <code>execute</code> method executes an SQL statement and indicates the
     * form of the first result.  You must then use the methods
     * <code>getResultSet</code> or <code>getUpdateCount</code>
     * to retrieve the result, and <code>getMoreResults</code> to
     * move to any subsequent result(s).
     *
     * @param sql any SQL statement
     * @param columnIndexes an array of the indexes of the columns in the
     *        inserted row that should be  made available for retrieval by a
     *        call to the method <code>getGeneratedKeys</code>
     * @return <code>true</code> if the first result is a <code>ResultSet</code>
     *         object; <code>false</code> if it is an update count or there
     *         are no results
     * @exception SQLException if a database access error occurs
     * @see #getResultSet
     * @see #getUpdateCount
     * @see #getMoreResults
     *
     * @since 1.4
     */
    public boolean execute (String sql, int columnIndexes[]) throws SQLException {
        if (columnIndexes.length != 0) {
            this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        }
        return  this.execute(sql);
    }

    /**
     * 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 <code>INSERT</code> statement.
     *
     * In some (uncommon) situations, a single SQL statement may return
     * multiple result sets and/or update counts.  Normally you can ignore
     * this unless you are (1) executing a stored procedure that you know may
     * return multiple results or (2) you are dynamically executing an
     * unknown SQL string.
     *
     * The <code>execute</code> method executes an SQL statement and indicates the
     * form of the first result.  You must then use the methods
     * <code>getResultSet</code> or <code>getUpdateCount</code>
     * to retrieve the result, and <code>getMoreResults</code> to
     * move to any subsequent result(s).
     *
     * @param sql any SQL statement
     * @param columnNames an array of the names of the columns in the inserted
     *        row that should be made available for retrieval by a call to the
     *        method <code>getGeneratedKeys</code>
     * @return <code>true</code> if the next result is a <code>ResultSet</code>
     *         object; <code>false</code> if it is an update count or there
     *         are no more results
     * @exception SQLException if a database access error occurs
     * @see #getResultSet
     * @see #getUpdateCount
     * @see #getMoreResults
     * @see #getGeneratedKeys
     *
     * @since 1.4
     */
    public boolean execute (String sql, String columnNames[]) throws SQLException {
        if (columnNames.length != 0) {
            this.throwNotSupported(MessageTranslator.translate(MessageKey.ERROR_AUTOGENKEYS_RETRIEVAL_UNSUPPORTED));
        }
        return  this.execute(sql);
    }

    /**
     * Retrieves the result set holdability for <code>ResultSet</code> objects
     * generated by this <code>Statement</code> object.
     *
     * @return either <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
     *         <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
     * @exception SQLException if a database access error occurs
     *
     * @since 1.4
     */
    public int getResultSetHoldability () throws java.sql.SQLException {
        return  this.resultSetHoldability;
    }
    
    public boolean isPacketEncodingUnicode(){
        return this.packetEncodingUnicode;
    }
}
