/*
    Copyright (C) 2000 SAP AG

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

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package com.sap.dbtech.jdbc;

import java.sql.*;
import java.util.*;
import java.io.*;

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.*;


/**
 * Implementation of the result set interface for SAP DB.
 * @see com.sap.dbtech.jdbc.UpdatableResultSetSapDB
 */
public class ResultSetSapDB
    extends ConnectionItem
    implements ResultSet, SQLParamController
{
    /**
     * The default fetch size to use if none is specified.
     */
    public static final int DEFAULT_FETCHSIZE=30000;

    /**
     * Constant indicating that the current position is <i>before the first row</i>.
     */
    protected static final int POSITION_BEFORE_FIRST  =1;

    /**
     * Constant indicating that the current position is <i>at the result set</i>.
     */
    protected static final int POSITION_INSIDE        =2;

    /**
     * Constant indicating that the current position is <i>behind the last row</i>.
     */
    protected static final int POSITION_AFTER_LAST    =3;

    /**
     * Constant indicating that the current position is not available.
     */
    protected static final int POSITION_NOT_AVAILABLE =4;


    /**
     * The fetch details.
     */
    private FetchInfo       fetchInfo;

    /**
     * The statement that generated this result set.
     */
    private StatementSapDB  statement;

    /**
     * The fetch size that is set.
     */
    private int             fetchSize;

    /**
     * The <code>maxRows</code> value. This is set to -1 if no max rows
     * are set.
     */
    private int             maxRows;

    /**
     * The data of the last fetch operation.
     */
    private FetchChunk      currentChunk;

    /**
     * The status of the position, i.e. one of the <code>POSITION_XXX</code> constants.
     */
    protected int             positionState;

    /**
     * The status of the current chunk.
     */
    private int             positionStateOfChunk;

    private boolean         fromMetaData;          // was a metadata operation ?
    private boolean         lastWasNull;           // was last getXXX null ?
    private boolean         isClosed;              // is this result set closed?
    private boolean         empty;                 // is this result set totally empty
    private int             fetchDirection;        // the fetchdirection (relatively ignored)

    private Vector          openStreams;           // a vector of all streams that went outside.
    private int             rowsInResultSet;       // the number of rows in this result set, or -1 if not known

    private int             safeFetchSize;         // The fetch size that is known to be good.
                                                   // This one is used when going backwards in the result set.
    private boolean         safeFetchSizeDetermined; 

    private int             largestKnownAbsPos;    // largest known absolute position to be inside.
    private int             maxRowsOutSideResult;  // flag: -1 -> maxrows is inside the result
                                                   //        0 -> unknown
                                                   //        1 -> maxrows is outside the result

    protected int           modifiedKernelPos;     // contains 0 if the kernel pos is not modified
                                                   // or the current kernel position.
    private int             cursorType;       // Flags if the cursor should not be dropped on close.

    /**
     * Creates a new result set object. The position will be <i>before the first row</i>,
     * and the preferred direction will be <code>ResultSet.FETCH_FORWARD</code>.
     * @param connection the current connection.
     * @param fetchInfo short info and column names.
     * @param statement the statement that produced this result set.
     * @param fetchSize the maximum number of rows to fetch at once, use <code>-1</code>
     *        for default fetch size.
     * @param maxRows the last row to fetch, use <code>0</code> to disable.
     */
    ResultSetSapDB(ConnectionSapDB connection,
                   FetchInfo       fetchInfo,
                   StatementSapDB  statement,
                   int             fetchSize,
                   int             maxRows,
                   int             cursorType,
                   ReplyPacket     reply)
        throws SQLException
    {
        super(connection);
        this.fetchInfo=fetchInfo;
        this.statement=statement;
        if(fetchSize>=1) {
            this.fetchSize=fetchSize;
        } else {
            this.fetchSize=DEFAULT_FETCHSIZE;
        }
        this.maxRows=maxRows;
        this.isClosed=false;
        this.fetchDirection=FETCH_FORWARD;
        initializeFields();
        openStreams=new java.util.Vector(5);
        this.cursorType = cursorType;
        if (reply != null){
          setCurrentChunk(new FetchChunk(FetchChunk.TYPE_FIRST, // fetch first is forward
                               1,    // absolute start position
                               reply,          // reply packet
                               fetchInfo.getRecordSize(), // the size for data part navigation
                               maxRows, // the current maxRows setting, for determining the last
                               // condition in that case
                               this.rowsInResultSet
                               ));
           positionState = POSITION_BEFORE_FIRST;
        }
    }

    protected void initializeFields()
    {
        this.currentChunk=null;
        this.positionState=POSITION_BEFORE_FIRST;
        this.positionStateOfChunk=POSITION_NOT_AVAILABLE;
        this.empty=false;
        this.safeFetchSize=1;
        this.safeFetchSizeDetermined = false;
        this.largestKnownAbsPos=1;
        this.maxRowsOutSideResult=0;
        this.rowsInResultSet = -1;
        this.modifiedKernelPos = 0;
    }

    /**
     * Creates a new result set from separate fetch informations.
     * @param connection the current connection.
     * @param infos short infos of columns and fields.
     * @param columnNames the names of the columns.
     * @param statement the statement that produced this result set.
     * @param fetchSize the maximum number of rows to fetch at once.
     * @param maxRows the last row to fetch.
     */
    ResultSetSapDB(ConnectionSapDB connection,
                   String cursorName,
                   DBTechTranslator[] infos,
                   String[] columnNames,
                   StatementSapDB statement,
                   int fetchSize,
                   int maxRows,
                   int cursorType,
                   ReplyPacket reply)
        throws SQLException
    {
        this(connection, new FetchInfo(connection, cursorName, infos, columnNames, statement.isPacketEncodingUnicode()),
             statement, fetchSize, maxRows, cursorType, reply);
    }


    //----------------------------------------------------------------------
    // java.sql.ResultSetSet interface
    //----------------------------------------------------------------------

    /**
     *
     *
     * Moves the cursor to the given row number in
     * this <code>ResultSet</code> object.
     *
     * <p>If the row number is positive, the cursor moves to
     * the given row number with respect to the
     * beginning of the result set.  The first row is row 1, the second
     * is row 2, and so on.
     *
     * <p>If the given row number is negative, the cursor moves to
     * an absolute row position with respect to
     * the end of the result set.  For example, calling the method
     * <code>absolute(-1)</code> positions the
     * cursor on the last row; calling the method <code>absolute(-2)</code>
     * moves the cursor to the next-to-last row, and so on.
     *
     * <p>An attempt to position the cursor beyond the first/last row in
     * the result set leaves the cursor before the first row or after
     * the last row.
     *
     * <p><B>Note:</B> Calling <code>absolute(1)</code> is the same
     * as calling <code>first()</code>. Calling <code>absolute(-1)</code>
     * is the same as calling <code>last()</code>.
     *
     * @param row the number of the row to which the cursor should move.
     *        A positive number indicates the row number counting from the
     *        beginning of the result set; a negative number indicates the
     *        row number counting from the end of the result set
     * @param b
     * @return <code>true</code> if the cursor is on the result set;
     * <code>false</code> otherwise
     * @exception SQLException if a database access error
     * occurs, or the result set type is <code>TYPE_FORWARD_ONLY</code>
     * @since 1.2
     */
    public boolean absolute(int row)
    	throws SQLException {
        return absolute(row, false);
    }
    
    public boolean absolute(int row, boolean backward)
        throws SQLException
    {
        clearWarnings();
        assertNotClosed();
        assertNotForwardOnly();
        if(row==0) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_ROW_ISNULL),
                                        "21000");
        }

        // don't make anything if it is empty, just tell that we did shoot
        // out of the boundary
        if(this.empty) {
            if(row > 0) {
                this.positionState=POSITION_AFTER_LAST;
                return false;
            } else {
                this.positionState=POSITION_BEFORE_FIRST;
                return false;
            }
        }

        if(row > 0) {
            // trap 1: maxrows set.
            if(maxRowIsSet() && row > maxRows) {
                this.positionState=POSITION_AFTER_LAST;
                return false;
            }

            // trap 2: result count known.
            if(rowsInResultSetKnown() && row > rowsInResultSet) {
                this.positionState=POSITION_AFTER_LAST;
                return false;
            }

            // if we are somewhere out there, and there was no last chunk, force the fetch
            if(positionStateOfChunk!=POSITION_INSIDE) {
                boolean result=fetchAbsoluteUp(row, backward);
                if(!result) this.positionState=POSITION_AFTER_LAST;
                return result;
            } else {
                // ok, we are in a state where we have a chunk.
                // if this chunk contains our row, we are happy and do nothing
                if(currentChunk.setRow(row)) {
                    this.positionState=POSITION_INSIDE;
                    return true;
                } else {
                    // otherwise we cannot avoid doing a fetch
                    boolean result = fetchAbsoluteUp(row, backward);
                    if(!result) this.positionState=POSITION_AFTER_LAST;
                    return result;
                }
            }
        } else {
            // trap: if the rows in the result set are known,
            // invert the row, and come back.
            if(rowsInResultSetKnown()) {
                int invertedPos=invertPosition(row);
                if(invertedPos <=0) {
                    this.positionState=POSITION_BEFORE_FIRST;
                    return false;
                } else {
                    return absolute(invertedPos);
                }
            }

            // if there is nothing to do leave here
            if(maxRowIsSet() && -row > maxRows) {
                this.positionState=POSITION_BEFORE_FIRST;
                return false;
            }

            // check of chunk present, then do physical fetch if not
            if(positionStateOfChunk!=POSITION_INSIDE) {
                boolean result= fetchAbsoluteDown(row);
                if(!result) this.positionState=POSITION_BEFORE_FIRST;
                return result;
            } else {
                if(currentChunk.setRow(row)) {
                    this.positionState=POSITION_INSIDE;
                    return true;
                } else {
                    boolean result= fetchAbsoluteDown(row);
                    if(!result) this.positionState=POSITION_BEFORE_FIRST;
                    return result;
                }
            }
        }
    }

    /**
     *
     * Moves the cursor down one row from its current position.
     * A <code>ResultSet</code> cursor is initially positioned
     * before the first row; the first call to the method
     * <code>next</code> makes the first row the current row; the
     * second call makes the second row the current row, and so on.
     *
     * <P>If an input stream is open for the current row, a call
     * to the method <code>next</code> will
     * implicitly close it. A <code>ResultSet</code> object's
     * warning chain is cleared when a new row is read.
     *
     * @return <code>true</code> if the new current row is valid;
     * <code>false</code> if there are no more rows
     * @exception SQLException if a database access error occurs
     */
    public boolean next()
        throws SQLException
    {
        clearWarnings();
        assertNotClosed();
        // if we have nothing, there is nothing to do.
        if(this.empty) {
            this.positionState=POSITION_AFTER_LAST;
            return false;
        }

        boolean result=false;

        // at first we have to close all input streams
        closeOpenStreams();
        
       // if we are outside, ...
        if(positionState==POSITION_BEFORE_FIRST) {
            // ... check whether we still have it
            if(positionStateOfChunk==POSITION_INSIDE &&
               currentChunk.containsRow(1)) {
                currentChunk.setRow(1);
                this.positionState=POSITION_INSIDE;
                result = true;
            } else {
       		  result = fetchFirst();
            }
        } else if(positionState==POSITION_INSIDE) {
            if(currentChunk.move(1)) {
                result=true;
            } else {
                if(currentChunk.isLast()) {
                    this.positionState=POSITION_AFTER_LAST;
                    return false;
                }
                result = fetchNextChunk();
            }
        } else if(positionState==POSITION_AFTER_LAST) {
            //
        }

        // in case we did a repositioning, we have to clear
        // the warnings
        if(result) {
            clearWarnings();
        }

        return result;
    }

        /**
     * Moves the cursor a relative number of rows, either positive or negative.
     * Attempting to move beyond the first/last row in the
     * result set positions the cursor before/after the
     * the first/last row. Calling <code>relative(0)</code> is valid, but does
     * not change the cursor position.
     *
     * <p>Note: Calling the method <code>relative(1)</code>
     * is identical to calling the method <code>next()</code> and
     * calling the method <code>relative(-1)</code> is identical
     * to calling the method <code>previous()</code>.
     *
     * @param rows an <code>int</code> specifying the number of rows to
     *        move from the current row; a positive number moves the cursor
     *        forward; a negative number moves the cursor backward
     * @return <code>true</code> if the cursor is on a row;
     *         <code>false</code> otherwise
     * @exception SQLException if a database access error occurs,
     *            there is no current row, or the result set type is
     *            <code>TYPE_FORWARD_ONLY</code>
     * @since 1.2
     */
    public boolean relative(int relativePos)
    throws SQLException {
      return this.relative(relativePos, true); 
    } 
    
    public boolean relative(int relativePos, boolean throwException)
    throws SQLException
    {
        clearWarnings();
        assertNotClosed();
        assertNotForwardOnly();

        if(this.empty) {
            if (throwException){
                throw new JDBCDriverException
                (MessageTranslator.translate(MessageKey.WARNING_EMPTY_RESULTSET));
            } else {
              return false;
            }  
        }

        if(positionState!=POSITION_INSIDE) {
            if (throwException){
                if(positionState==POSITION_BEFORE_FIRST) {
                    throw new JDBCDriverException
                        (MessageTranslator.translate(MessageKey.ERROR_RESULTSET_BEFOREFIRST));
                } else {
                    throw new JDBCDriverException
                        (MessageTranslator.translate(MessageKey.ERROR_RESULTSET_AFTERLAST));
                }
            } else {
                return false;
            }
        } else {
            int internal = getInternalRow();
            if(internal>0) {
                if(internal + relativePos <= 0) {
                    this.positionState=POSITION_BEFORE_FIRST;
                    return false;
                } else {
                    return absolute(internal + relativePos, relativePos < 0);
                }
            } else {
                if(internal + relativePos >= 0) {
                    this.positionState=POSITION_AFTER_LAST;
                    return false;
                } else {
                    return absolute(internal + relativePos, relativePos < 0);
                }
            }
        }
    }


    /**
     * Moves the cursor to the previous row in this
     * <code>ResultSet</code> object.
     *
     * @return <code>true</code> if the cursor is on a valid row;
     * <code>false</code> if it is off the result set
     * @exception SQLException if a database access error
     * occurs or the result set type is <code>TYPE_FORWARD_ONLY</code>
     * @since 1.2
     */
    public boolean previous()
        throws SQLException
    {
        clearWarnings();
        assertNotClosed();
        if(positionState==POSITION_AFTER_LAST) {
            return absolute(-1);
        } else {
            return this.relative(-1, false); 
        }
    }


    /**
     * Moves the cursor to the first row in
     * this <code>ResultSet</code> object.
     *
     * @return <code>true</code> if the cursor is on a valid row;
     * <code>false</code> if there are no rows in the result set
     * @exception SQLException if a database access error
     * occurs or the result set type is <code>TYPE_FORWARD_ONLY</code>
     *
     */
    public boolean first()
        throws SQLException
    {
        clearWarnings();
        assertNotClosed();
        assertNotForwardOnly();

        // if we have nothing, there is nothing to do.
        if(this.empty) {
            this.positionState=POSITION_AFTER_LAST;
            return false;
        }

        // like in next()
        closeOpenStreams();

        boolean result=false;

        if(positionStateOfChunk == POSITION_INSIDE &&
           currentChunk.containsRow(1)) {
            currentChunk.setRow(1);
            this.positionState=POSITION_INSIDE;
            result = true;
        } else {
            result=fetchFirst();
        }

        if(result) {
            clearWarnings();
        }

        return result;
    }

    /**
     *
     * Moves the cursor to the last row in
     * this <code>ResultSet</code> object.
     *
     * @return <code>true</code> if the cursor is on a valid row;
     * <code>false</code> if there are no rows in the result set
     * @exception SQLException if a database access error
     * occurs or the result set type is <code>TYPE_FORWARD_ONLY</code>
     * @since 1.2
     */
    public boolean last()
        throws SQLException
    {
        clearWarnings();
        assertNotClosed();
        assertNotForwardOnly();

        // if we have nothing, there is nothing to do.
        if(this.empty) {
            this.positionState=POSITION_AFTER_LAST;
            return false;
        }

        // like in next()
        closeOpenStreams();

        boolean result=false;

        if(positionStateOfChunk==POSITION_INSIDE &&
           currentChunk.setRow(-1)) {
            this.positionState=POSITION_INSIDE;
            result=true;
        } else {
            result = fetchLast();
        }

        if(result) {
            clearWarnings();
        }

        return result;
    }

    public void afterLast()
        throws SQLException
    {
        assertNotForwardOnly();
        assertNotClosed();
        this.positionState=POSITION_AFTER_LAST;
    }


    public void beforeFirst()
        throws SQLException
    {
        clearWarnings();
        assertNotForwardOnly();
        assertNotClosed();
        this.positionState=POSITION_BEFORE_FIRST;
    }

    public boolean isFirst()
        throws SQLException
    {
        assertNotClosed();
        return currentChunk!=null && !this.empty && currentChunk.isFirst()
                && currentChunk.isAtLowerBound();
    }

    public boolean isLast()
        throws SQLException
    {
        assertNotClosed();
        return currentChunk!=null && !this.empty && currentChunk.isLast()
                && currentChunk.isAtUpperBound();
    }


    public boolean isBeforeFirst()
        throws SQLException
    {
        assertNotClosed();
        return (!this.empty && this.positionState==POSITION_BEFORE_FIRST);
    }

    public boolean isAfterLast()
        throws SQLException
    {
        assertNotClosed();
        return (!this.empty && this.positionState==POSITION_AFTER_LAST);
    }

    /**
     * Retrieves whether the retrieved data value was <code>NULL</code>.
     */
    public boolean wasNull()
        throws SQLException
    {
        assertNotClosed();
        return this.lastWasNull;
    }


    /**
     * Closes this result set. Further operations are not allowed.
     */
    public void close()
        throws SQLException
    {
       this.close (false);
    }

    public void close(boolean keepCursorOpen)
        throws SQLException
    {
        clearWarnings();
        if (this.fetchInfo != null){
          if (cursorType == StatementSapDB.Cursor_forward_only
              && (     this.currentChunk == null
                   || !this.currentChunk.isLast()
                  )

              ){
            cursorType = StatementSapDB.Cursor_in_use;
          }
          if( !keepCursorOpen
              && (cursorType == StatementSapDB.Cursor_in_use
                  || cursorType == StatementSapDB.Cursor_Resurrected)
            ) {
              this.connection.dropCursor(this.fetchInfo.getCursorName());
          }
        }
        this.cursorType = StatementSapDB.Cursor_not_used;
        this.isClosed = true;
        this.currentChunk=null;
        this.fetchInfo=null;
    }


    public int findColumn(String columnName)
        throws SQLException
    {
        this.assertNotClosed ();
        return this.findColumnInfo (columnName).getColIndex () + 1;
    }

    /**
     *
     *
     * Gets an SQL ARRAY value from the current row of this <code>ResultSet</code> object.
     *
     * @param i the first column is 1, the second is 2, ...
     * @return an <code>Array</code> object representing the SQL ARRAY value in
     *         the specified column
     */
    public Array getArray(int i) throws SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_ARRAY_UNSUPPORTED),
             this);
    }

    /**
     *
     *
     * Gets an SQL ARRAY value in the current row of this <code>ResultSet</code> object.
     *
     * @param colName the name of the column from which to retrieve the value
     * @return an <code>Array</code> object representing the SQL ARRAY value in
     *         the specified column
     */
    public Array getArray(String colName) throws SQLException
    {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_ARRAY_UNSUPPORTED),
             this);
    }

    /**
     * getAsciiStream method comment.
     */
    public java.io.InputStream getAsciiStream(int columnIndex) throws SQLException {
        InputStream is= this.findColumnInfo (columnIndex)
                .getAsciiStream (this, this.getCurrentRecord (), this.getReplyData());
        if(is!=null) {
            this.openStreams.addElement(is);
        }
        return is;
    }

    /**
     * getAsciiStream method comment.
     */
    public java.io.InputStream getAsciiStream(String columnName) throws SQLException {
        InputStream is=this.findColumnInfo (columnName)
                .getAsciiStream (this, this.getCurrentRecord (), this.getReplyData());
        if(is!=null) {
            this.openStreams.addElement(is);
        }
        return is;
    }

    /**
     *
     *
     * Gets the value of a column in the current row as a java.math.BigDecimal
     * object with full precision.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @return the column value (full precision); if the value is SQL NULL,
     * the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.math.BigDecimal getBigDecimal(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getBigDecimal (this, this.getCurrentRecord ());
    }

    /**
     * getBigDecimal method comment.
     */
    public java.math.BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
        return this.findColumnInfo (columnIndex).getBigDecimal (scale, this, this.getCurrentRecord ());
    }

    /**
     *
     *
     * Gets the value of a column in the current row as a java.math.BigDecimal
     * object with full precision.
     * @param columnName the column name
     * @return the column value (full precision); if the value is SQL NULL,
     * the result is null
     * @exception java.sql.SQLException if a database access error occurs
     *
     */
    public java.math.BigDecimal getBigDecimal(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getBigDecimal (this, this.getCurrentRecord ());
    }

    /**
     * getBigDecimal method comment.
     */
    public java.math.BigDecimal getBigDecimal(String columnName, int scale) throws SQLException {
        return this.findColumnInfo (columnName).getBigDecimal (scale, this, this.getCurrentRecord ());
    }

    /**
     * getBinaryStream method comment.
     */
    public java.io.InputStream getBinaryStream(int columnIndex) throws SQLException {
        InputStream is= this.findColumnInfo (columnIndex)
            .getBinaryStream (this, this.getCurrentRecord (), this.getReplyData());
        if(is!=null) {
            this.openStreams.addElement(is);
        }
        return is;
    }

    /**
     * getBinaryStream method comment.
     */
    public java.io.InputStream getBinaryStream(String columnName) throws SQLException {
        InputStream is= this.findColumnInfo (columnName)
            .getBinaryStream (this, this.getCurrentRecord (), this.getReplyData());
        if(is!=null) {
            this.openStreams.addElement(is);
        }
        return is;
    }

    /**
     *
     *
     * Gets a BLOB value in the current row of this <code>ResultSet</code> object.
     *
     * @param i the first column is 1, the second is 2, ...
     * @return a <code>Blob</code> object representing the SQL BLOB value in
     *         the specified column
     */
    public Blob getBlob(int i) throws SQLException {
        return this.findColumnInfo (i)
                .getBlob (this, this.getCurrentRecord (), this.getReplyData());
    }

    /**
     *
     *
     * Gets a BLOB value in the current row of this <code>ResultSet</code> object.
     *
     * @param colName the name of the column from which to retrieve the value
     * @return a <code>Blob</code> object representing the SQL BLOB value in
     *         the specified column
     */
    public Blob getBlob(String colName) throws SQLException {
        return this.findColumnInfo (colName)
                .getBlob (this, this.getCurrentRecord (), this.getReplyData());
    }

    /**
     * getBoolean method comment.
     */
    public boolean getBoolean(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getBoolean (this, this.getCurrentRecord ());
    }

    /**
     * getBoolean method comment.
     */
    public boolean getBoolean(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getBoolean (this, this.getCurrentRecord ());
    }

    /**
     * getByte method comment.
     */
    public byte getByte(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getByte (this, this.getCurrentRecord ());
    }

    /**
     * getByte method comment.
     */
    public byte getByte(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getByte (this, this.getCurrentRecord ());
    }

    /**
     * getBytes method comment.
     */
    public byte[] getBytes(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getBytes (this, this.getCurrentRecord ());
    }

    /**
     * getBytes method comment.
     */
    public byte[] getBytes(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getBytes (this, this.getCurrentRecord ());
    }

    /**
     *
     *
     * <p>Gets the value of a column in the current row as a java.io.Reader.
     * @param columnIndex the first column is 1, the second is 2, ...
     */
    public java.io.Reader getCharacterStream(int columnIndex) throws SQLException {
        Reader r=this.findColumnInfo (columnIndex)
                .getCharacterStream (this, this.getCurrentRecord (), this.getReplyData());
        if(r!=null) {
            this.openStreams.addElement(r);
        }
        return r;
    }

    /**
     *
     *
     * <p>Gets the value of a column in the current row as a java.io.Reader.
     * @param columnName the name of the column
     * @return the value in the specified column as a <code>java.io.Reader</code>
     */
    public java.io.Reader getCharacterStream(String columnName) throws SQLException {
        Reader r=this.findColumnInfo (columnName)
            .getCharacterStream (this, this.getCurrentRecord (), this.getReplyData());
        if(r!=null) {
            this.openStreams.addElement(r);
        }
        return r;
    }

    /**
     *
     *
     * Gets a CLOB value in the current row of this <code>ResultSet</code> object.
     *
     * @param i the first column is 1, the second is 2, ...
     * @return a <code>Clob</code> object representing the SQL CLOB value in
     *         the specified column
     */
    public Clob getClob(int i) throws SQLException {
        return this.findColumnInfo (i)
                .getClob (this, this.getCurrentRecord (), this.getReplyData());
    }

    /**
     *
     *
     * Gets a CLOB value in the current row of this <code>ResultSet</code> object.
     *
     * @param colName the name of the column from which to retrieve the value
     * @return a <code>Clob</code> object representing the SQL CLOB value in
     *         the specified column
     */
    public Clob getClob(String colName) throws SQLException {
        return this.findColumnInfo (colName)
                .getClob (this, this.getCurrentRecord (), this.getReplyData());
    }

    /**
     *
     *
     * Returns the concurrency mode of this result set.  The concurrency
     * used is determined by the statement that created the result set.
     *
     * @return the concurrency type, CONCUR_READ_ONLY or CONCUR_UPDATABLE
     * @exception java.sql.SQLException if a database access error occurs
     */
    public int getConcurrency() throws SQLException {
        return java.sql.ResultSet.CONCUR_READ_ONLY;
    }


    /**
     * getDate method comment.
     */
    public java.sql.Date getDate(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getDate (this, this.getCurrentRecord (),null);
    }
    /**
     *
     *
     * Gets the value of a column in the current row as a java.sql.Date
     * object. This method uses the given calendar to construct an appropriate millisecond
     * value for the Date if the underlying database does not store
     * timezone information.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param cal the calendar to use in constructing the date
     * @return the column value; if the value is SQL NULL, the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Date getDate(int columnIndex, java.util.Calendar cal)
        throws SQLException
    {
        return this.findColumnInfo (columnIndex).getDate (this, this.getCurrentRecord (),cal);
    }

    /**
     * getDate method comment.
     */
    public java.sql.Date getDate(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getDate (this, this.getCurrentRecord (),null);
    }
    /**
     * Gets the value of a column in the current row as a java.sql.Date
     * object. This method uses the given calendar to construct an appropriate millisecond
     * value for the Date, if the underlying database does not store
     * timezone information.
     *
     * @param columnName the SQL name of the column from which to retrieve the value
     * @param cal the calendar to use in constructing the date
     * @return the column value; if the value is SQL NULL, the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Date getDate(String columnName, java.util.Calendar cal) throws SQLException {
        return this.findColumnInfo (columnName).getDate (this, this.getCurrentRecord (),cal);
    }
    /**
     * getDouble method comment.
     */
    public double getDouble(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getDouble (this, this.getCurrentRecord ());
    }
    /**
     * getDouble method comment.
     */
    public double getDouble(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getDouble (this, this.getCurrentRecord ());
    }

    public int getFetchDirection() throws SQLException {
        return this.fetchDirection;

    }

    public void setFetchDirection(int direction) throws SQLException {
        assertNotClosed();
        switch (direction) {
        case java.sql.ResultSet.FETCH_REVERSE:
        case java.sql.ResultSet.FETCH_UNKNOWN:
            assertNotForwardOnly();
        case java.sql.ResultSet.FETCH_FORWARD:
            this.fetchDirection=direction;
            break;
        default:
            throw new InvalidArgumentValue ("direction", "FETCH_FORWARD, FETCH_REVERSE, FETCH_UNKNOWN");
        }
    }

    public int getFetchSize()
            throws SQLException
    {
        assertNotClosed();
        return fetchSize;
    }

    public void setFetchSize(int fetchSize)
            throws SQLException
    {
        this.assertNotClosed();
        if(fetchSize>=0) {
            this.fetchSize=fetchSize;
            this.safeFetchSize=Math.min(this.safeFetchSize, fetchSize);
            if(this.safeFetchSize <= this.fetchSize && this.safeFetchSize != 1) {
                this.safeFetchSizeDetermined = true;
            }
        } else {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_INVALID_FETCHSIZE,
                                                                    Integer.toString(fetchSize)),
                                        "22003");

        }
    }

    public boolean rowUpdated() throws SQLException {
        assertNotClosed();
        return false;
    }

    public boolean rowInserted() throws SQLException {
        assertNotClosed();
        return false;
    }

    public boolean rowDeleted() throws SQLException {
        assertNotClosed();
        return false;
    }

    /**
     * getFloat method comment.
     */
    public float getFloat(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getFloat (this, this.getCurrentRecord ());
    }
    /**
     * getFloat method comment.
     */
    public float getFloat(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getFloat (this, this.getCurrentRecord ());
    }
    /**
     * getInt method comment.
     */
    public int getInt(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getInt (this, this.getCurrentRecord ());
    }
    /**
     * getInt method comment.
     */
    public int getInt(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getInt (this, this.getCurrentRecord ());
    }
    /**
     * getLong method comment.
     */
    public long getLong(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getLong (this, this.getCurrentRecord ());
    }
    /**
     * getLong method comment.
     */
    public long getLong(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getLong (this, this.getCurrentRecord ());
    }
    /**
     * getMetaData method comment.
     */
    public ResultSetMetaData getMetaData() throws SQLException {
        assertNotClosed();
        return new ResultSetMetaDataSapDB (this.fetchInfo.getColInfo());
    }
    /**
     * getObject method comment.
     */
    public Object getObject(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getObject (this, this.getCurrentRecord ());
    }
    /**
     *
     *
     * Returns the value of a column in the current row as a Java object.
     * This method uses the given <code>Map</code> object
     * for the custom mapping of the
     * SQL structured or distinct type that is being retrieved.
     *
     * @param i the first column is 1, the second is 2, ...
     * @param map the mapping from SQL type names to Java classes
     * @return an object representing the SQL value
     */
    public Object getObject(int i, Map map) throws SQLException {
        throw new NotImplemented ();
    }
    /**
     * getObject method comment.
     */
    public Object getObject(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getObject (this, this.getCurrentRecord ());
    }
    /**
     *
     *
     * Returns the value in the specified column as a Java object.
     * This method uses the specified <code>Map</code> object for
     * custom mapping if appropriate.
     *
     * @param colName the name of the column from which to retrieve the value
     * @param map the mapping from SQL type names to Java classes
     * @return an object representing the SQL value in the specified column
     */
    public Object getObject(String colName, Map map) throws SQLException {
        throw new NotImplemented ();
    }
    /**
     *
     *
     * Gets a REF(&lt;structured-type&gt;) column value from the current row.
     *
     * @param i the first column is 1, the second is 2, ...
     * @return a <code>Ref</code> object representing an SQL REF value
     */
    public Ref getRef(int i) throws SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_REF_UNSUPPORTED),
             this);
    }
    /**
     *
     *
     * Gets a REF(&lt;structured-type&gt;) column value from the current row.
     *
     * @param colName the column name
     * @return a <code>Ref</code> object representing the SQL REF value in
     *         the specified column
     */
    public Ref getRef(String colName) throws SQLException {

        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_REF_UNSUPPORTED),
             this);
    }

    /**
     *
     *
     * <p>Retrieves the current row number.  The first row is number 1, the
     * second number 2, and so on.
     *
     * @return the current row number; 0 if there is no current row
     * @exception java.sql.SQLException if a database access error occurs
     */
    public int getRow() throws SQLException {

        assertNotClosed();
        if(this.positionState!=POSITION_INSIDE) {
            return 0;
        }
        int internalRow=getInternalRow();
        //  System.out.println("INTERNAL ROW IS : " + internalRow);
        if(internalRow<0) {
            getRowsInResult();
            absolute(internalRow);
            internalRow=getInternalRow(); // now positive
        }
        return internalRow;
    }

    public int getInternalRow()
    {
        if(currentChunk!=null) {
            return currentChunk.getLogicalPos();
        } else {
            return 0;
        }
    }

    public void traceChunk()
    {
        System.err.println(currentChunk.traceString());
    }

    /**
     * getShort method comment.
     */
    public short getShort(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getShort (this, this.getCurrentRecord ());
    }
    /**
     * getShort method comment.
     */
    public short getShort(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getShort (this, this.getCurrentRecord ());
    }
    /**
     *
     *
     * Returns the Statement that produced this <code>ResultSet</code> object.
     * If the result set was generated some other way, such as by a
     * <code>DatabaseMetaData</code> method, this method returns <code>null</code>.
     *
     * @return the Statment that produced the result set or
     * null if the result set was produced some other way
     * @exception java.sql.SQLException if a database access error occurs
     */
    public Statement getStatement() throws SQLException {
        assertNotClosed();
        // Std. compliance requires to return null if
        // this is a result set from a meta data operation.
        if(fromMetaData) {
            return null;
        }
        return this.statement;
    }
    /**
     * getString method comment.
     */
    public String getString(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getString (this, this.getCurrentRecord ());
    }
    /**
     * getString method comment.
     */
    public String getString(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getString (this, this.getCurrentRecord ());
    }
    /**
     * getTime method comment.
     */
    public Time getTime(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getTime (this, this.getCurrentRecord (), null);
    }
    /**
     * Gets the value of a column in the current row as a java.sql.Time
     * object. This method uses the given calendar to construct an appropriate millisecond
     * value for the Time if the underlying database does not store
     * timezone information.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param cal the calendar to use in constructing the time
     * @return the column value; if the value is SQL NULL, the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Time getTime(int columnIndex, java.util.Calendar cal) throws SQLException {
        return this.findColumnInfo (columnIndex).getTime (this, this.getCurrentRecord (), cal);
    }
    /**
     * getTime method comment.
     */
    public Time getTime(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getTime (this, this.getCurrentRecord (), null);
    }
    /**
     * Gets the value of a column in the current row as a java.sql.Time
     * object. This method uses the given calendar to construct an appropriate millisecond
     * value for the Time if the underlying database does not store
     * timezone information.
     *
     * @param columnName the SQL name of the column
     * @param cal the calendar to use in constructing the time
     * @return the column value; if the value is SQL NULL, the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Time getTime(String columnName, java.util.Calendar cal) throws SQLException {
        return this.findColumnInfo (columnName).getTime (this, this.getCurrentRecord (), cal);
    }

    /**
     * getTimestamp method comment.
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException {
        return this.findColumnInfo (columnIndex).getTimestamp (this, this.getCurrentRecord (), null);
    }
    /**
     * Gets the value of a column in the current row as a java.sql.Timestamp
     * object. This method uses the given calendar to construct an appropriate millisecond
     * value for the Timestamp if the underlying database does not store
     * timezone information.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param cal the calendar to use in constructing the timestamp
     * @return the column value; if the value is SQL NULL, the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Timestamp getTimestamp(int columnIndex, java.util.Calendar cal)
            throws java.sql.SQLException
    {
       return this.findColumnInfo (columnIndex).getTimestamp (this, this.getCurrentRecord (), cal);
   }

    /**
     * getTimestamp method comment.
     */
    public Timestamp getTimestamp(String columnName) throws SQLException {
        return this.findColumnInfo (columnName).getTimestamp (this, this.getCurrentRecord (), null);
    }
    /**
     * Gets the value of a column in the current row as a java.sql.Timestamp
     * object. This method uses the given calendar to construct an appropriate millisecond
     * value for the Timestamp if the underlying database does not store
     * timezone information.
     *
     * @param columnName the SQL name of the column
     * @param cal the calendar to use in constructing the timestamp
     * @return the column value; if the value is SQL NULL, the result is null
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Timestamp getTimestamp(String columnName, java.util.Calendar cal)
            throws java.sql.SQLException
    {
        return this.findColumnInfo (columnName).getTimestamp (this, this.getCurrentRecord (), cal);
    }

    /**
     *
     *
     * Returns the type of this result set.  The type is determined by
     * the statement that created the result set.
     *
     * @return TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, or
     * TYPE_SCROLL_SENSITIVE
     * @exception java.sql.SQLException if a database access error occurs
     */
    public int getType() throws SQLException {
        return this.statement.getResultSetType();
    }
    /**
     * getUnicodeStream method comment.
     */
    public java.io.InputStream getUnicodeStream(int columnIndex) throws SQLException {
        InputStream is= this.findColumnInfo (columnIndex).getUnicodeStream (this, this.getCurrentRecord ());
        if(is!=null) {
            this.openStreams.addElement(is);
        }
        return is;
    }
    /**
     * getUnicodeStream method comment.
     */
    public java.io.InputStream getUnicodeStream(String columnName) throws SQLException {
        InputStream is = this.findColumnInfo (columnName).getUnicodeStream (this, this.getCurrentRecord ());
        if(is!=null) {
            this.openStreams.addElement(is);
        }
        return is;
    }

    /**
     *
     * @exception java.sql.SQLException The exception description.
     */
    public Object [] getValues () throws SQLException {
        int colinfo_length;
        Object [] result = new Object [colinfo_length=this.fetchInfo.numberOfColumns()];

        for (int i = 0; i < colinfo_length; ++i) {
            result [i] = this.getObject (i + 1);
        }
        return result;
    }

     /**
     *
     *
     * Updates a column with an ascii stream value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @param length the length of the stream
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateAsciiStream(int columnIndex,
            java.io.InputStream x,
            int length) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an ascii stream value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @param length of the stream
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateAsciiStream(String columnName,
            java.io.InputStream x,
            int length) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a BigDecimal value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBigDecimal(int columnIndex, java.math.BigDecimal x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a BigDecimal value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBigDecimal(String columnName, java.math.BigDecimal x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a binary stream value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @param length the length of the stream
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBinaryStream(int columnIndex,
            java.io.InputStream x,
            int length) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a binary stream value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @param length of the stream
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBinaryStream(String columnName,
            java.io.InputStream x,
            int length) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a boolean value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBoolean(int columnIndex, boolean x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a boolean value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBoolean(String columnName, boolean x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a byte value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateByte(int columnIndex, byte x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a byte value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateByte(String columnName, byte x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a byte array value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBytes(int columnIndex, byte x[]) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a byte array value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateBytes(String columnName, byte x[]) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a character stream value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @param length the length of the stream
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateCharacterStream(int columnIndex,
            java.io.Reader x,
            int length) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a character stream value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @param length of the stream
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateCharacterStream(String columnName,
            java.io.Reader reader,
            int length) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Date value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateDate(int columnIndex, java.sql.Date x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Date value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateDate(String columnName, java.sql.Date x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Double value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateDouble(int columnIndex, double x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a double value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateDouble(String columnName, double x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a float value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateFloat(int columnIndex, float x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a float value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateFloat(String columnName, float x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an integer value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateInt(int columnIndex, int x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an integer value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateInt(String columnName, int x) throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a long value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateLong(int columnIndex, long x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a long value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateLong(String columnName, long x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Give a nullable column a null value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateNull(int columnIndex) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a null value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateNull(String columnName) throws SQLException {
        this.throwNotUpdatable();
    }
    public void updateDefault(String columnName) throws SQLException {
        this.throwNotUpdatable();
    }

    public void updateDefault(int columnIndex) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an Object value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateObject(int columnIndex, Object x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an Object value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types
     *  this is the number of digits after the decimal.  For all other
     *  types this value will be ignored.
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateObject(int columnIndex, Object x, int scale)
            throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an Object value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateObject(String columnName, Object x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with an Object value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types
     *  this is the number of digits after the decimal.  For all other
     *  types this value will be ignored.
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateObject(String columnName, Object x, int scale)
            throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates the underlying database with the new contents of the
     * current row.  Cannot be called when on the insert row.
     *
     * @exception java.sql.SQLException if a database access error occurs or
     * if called when on the insert row
     */
    public void updateRow() throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a short value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateShort(int columnIndex, short x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a short value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateShort(String columnName, short x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a String value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateString(int columnIndex, String x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a String value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateString(String columnName, String x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Time value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateTime(int columnIndex, java.sql.Time x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Time value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateTime(String columnName, java.sql.Time x) throws SQLException {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Timestamp value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateTimestamp(int columnIndex, java.sql.Timestamp x)
            throws SQLException
    {
        this.throwNotUpdatable();
    }
    /**
     *
     *
     * Updates a column with a Timestamp value.
     *
     * The <code>updateXXX</code> methods are used to update column values in the
     * current row, or the insert row.  The <code>updateXXX</code> methods do not
     * update the underlying database; instead the <code>updateRow</code> or <code>insertRow</code>
     * methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void updateTimestamp(String columnName, java.sql.Timestamp x)
            throws SQLException
    {
        this.throwNotUpdatable();
    }

    /**
     *
     *
     * Inserts the contents of the insert row into the result set and
     * the database.  Must be on the insert row when this method is called.
     *
     * @exception java.sql.SQLException if a database access error occurs,
     * if called when not on the insert row, or if not all of non-nullable columns in
     * the insert row have been given a value
     */
    public void insertRow() throws SQLException {
        this.throwNotUpdatable();
    }

    /**
     *
     *
     * Deletes the current row from the result set and the underlying
     * database.  Cannot be called when on the insert row.
     *
     * @exception java.sql.SQLException if a database access error occurs or if
     * called when on the insert row.
     */
    public void deleteRow() throws SQLException {
        this.throwNotUpdatable();
    }

    /**
     *
     * Refreshes the current row with its most recent value in
     * the database.  Cannot be called when on the insert row.
     *
     * The <code>refreshRow</code> method provides a way for an application to
     * explicitly tell the JDBC driver to refetch a row(s) from the
     * database.  An application may want to call <code>refreshRow</code> when
     * caching or prefetching is being done by the JDBC driver to
     * fetch the latest value of a row from the database.  The JDBC driver
     * may actually refresh multiple rows at once if the fetch size is
     * greater than one.
     *
     * All values are refetched subject to the transaction isolation
     * level and cursor sensitivity.  If <code>refreshRow</code> is called after
     * calling <code>updateXXX</code>, but before calling <code>updateRow</code>, then the
     * updates made to the row are lost.  Calling the method <code>refreshRow</code> frequently
     * will likely slow performance.
     *
     * @exception java.sql.SQLException if a database access error occurs or if
     * called when on the insert row
     */
    public void refreshRow()
        throws SQLException
    {
        assertNotClosed();
        int internalPos=getInternalRow();
        initializeFields();
        absolute(internalPos);
    }

    /**
     *
     *
     * Cancels the updates made to a row.
     * This method may be called after calling an
     * <code>updateXXX</code> method(s) and before calling <code>updateRow</code> to rollback
     * the updates made to a row.  If no updates have been made or
     * <code>updateRow</code> has already been called, then this method has no
     * effect.
     *
     * @exception java.sql.SQLException if a database access error occurs or if
     * called when on the insert row
     *
     */
    public void cancelRowUpdates() throws SQLException {
        this.throwNotUpdatable();
    }

    /**
     *
     *
     * Moves the cursor to the remembered cursor position, usually the
     * current row.  This method has no effect if the cursor is not on the insert
     * row.
     *
     * @exception java.sql.SQLException if a database access error occurs
     * or the result set is not updatable
     */
    public void moveToCurrentRow() throws SQLException {
        this.throwNotUpdatable();
    }

    /**
     *
     *
     * Moves the cursor to the insert row.  The current cursor position is
     * remembered while the cursor is positioned on the insert row.
     *
     * The insert row is a special row associated with an updatable
     * result set.  It is essentially a buffer where a new row may
     * be constructed by calling the <code>updateXXX</code> methods prior to
     * inserting the row into the result set.
     *
     * Only the <code>updateXXX</code>, <code>getXXX</code>,
     * and <code>insertRow</code> methods may be
     * called when the cursor is on the insert row.  All of the columns in
     * a result set must be given a value each time this method is
     * called before calling <code>insertRow</code>.
     * The method <code>updateXXX</code> must be called before a
     * <code>getXXX</code> method can be called on a column value.
     *
     * @exception java.sql.SQLException if a database access error occurs
     * or the result set is not updatable
     */
    public void moveToInsertRow() throws SQLException {
        this.throwNotUpdatable();
    }


    /**
     *
     *
     * Retrieves the name of the SQL cursor used by this <code>ResultSet</code>
     * object.
     *
     * <P>In SQL, a result table is retrieved through a cursor that is
     * named. The current row of a result set can be updated or deleted
     * using a positioned update/delete statement that references the
     * cursor name. To insure that the cursor has the proper isolation
     * level to support update, the cursor's <code>SELECT</code> statement
     * should be of the form <code>SELECT FOR UPDATE</code>. If
     * <code>FOR UPDATE</code> is omitted, the positioned updates may fail.
     *
     * <P>The JDBC API supports this SQL feature by providing the name of the
     * SQL cursor used by a <code>ResultSet</code> object.
     * The current row of a <code>ResultSet</code> object
     * is also the current row of this SQL cursor.
     *
     * <P><B>Note:</B> If positioned update is not supported, a
     * <code>SQLException</code> is thrown.
     *
     * @return the SQL name for this <code>ResultSet</code> object's cursor
     * @exception SQLException if a database access error occurs
     */
    public String getCursorName() throws SQLException
    {
        assertNotClosed();
        return this.fetchInfo.getCursorName();
    }

    //----------------------------------------------------------------------
    // SQLParamController interface
    //----------------------------------------------------------------------

    /**
     * Get the data part of the curren reply packet.
     * @return the data part of the reply packet, as <code>StructuredMem</code>.
     */
    public StructuredMem getReplyData()
    {
        if(currentChunk!=null) {
            return currentChunk.getReplyData();
        } else {
            return null;
        }
    }


    /**
     * Set the flag that a null value was found.
     * @param lastWasNull The flag for the <code>null</code> detection.
     */
    public void setLastWasNull(boolean lastWasNull)
    {
        this.lastWasNull=lastWasNull;
    }


    /**
     * Sets whether this result set had its origin in a meta data
     * operation, and thus the statement don't have to be exposed.
     * @param fromMetaData Whether this statement was produced by a
     *   <code>DatabaseMetaData</code> operation.
     */
    void setFromMetaData(boolean fromMetaData)
    {
        this.fromMetaData=fromMetaData;
    }

    /**
     * Sets whether this result set is empty.
     * @param empty the emptiness flag.
     */
    void setEmpty(boolean empty)
    {
        this.empty=empty;
    }

    void setRowsInResultSet(int rows)
        throws SQLException
    {
        if(maxRows > 0)
            this.rowsInResultSet=Math.min(rows, this.maxRows);
        else
            this.rowsInResultSet = rows;
    }

    // ----------------------------------------------------------------------
    // Internal methods
    //----------------------------------------------------------------------


    /**
     * Fetch the next chunk, moving forward over the result set.
     */
    private boolean fetchNextChunk()
        throws SQLException
    {
        ReplyPacket reply;

        int usedFetchSize=this.fetchSize;
        int usedOffset=1;


        if(currentChunk.isForward()) {
            // in case we come to an end, we may have to limit the fetch size.
            if(maxRowIsSet()) {
                usedFetchSize=Math.min(this.maxRows - currentChunk.getEnd() + 1, usedFetchSize);
            }
            if(modifiedKernelPos != 0) {
                usedOffset +=  currentChunk.getEnd() - modifiedKernelPos;
            }
        } else {
            // if an update destroyed the cursor position, we have to honor this ...
            if(modifiedKernelPos==0) {
                usedOffset +=  currentChunk.getEnd() - currentChunk.getKernelPos();
            } else {
                usedOffset +=  currentChunk.getEnd() - modifiedKernelPos;
            }
        }

        // else {
        // // kernel sits on the opposite edge of the chunk, honor this
        //      usedOffset+=currentChunk.size();
        // }
        try {
            if (this.getType()==ResultSet.TYPE_FORWARD_ONLY){
              reply=this.fetchInfo.executeFetchNext(usedFetchSize);
            } else {
              reply=this.fetchInfo.executeFetchRelative(usedOffset, usedFetchSize);
            }
        } catch(SQLException sqlEx) {
            if(sqlEx.getErrorCode()==100) {
                // fine, we are at the end.
                this.currentChunk.setLast(true);
                this.updateRowStatistics();
                // but invalidate it, as it is
                // thrown away by the kernel
                this.currentChunk=null;
                this.positionStateOfChunk=POSITION_NOT_AVAILABLE;
                this.positionState=POSITION_AFTER_LAST;
                return false;
            }
            throw sqlEx;
        }
        setCurrentChunk(new FetchChunk(FetchChunk.TYPE_RELATIVE_UP,
                                       this.currentChunk.getEnd() + 1,
                                       reply,
                                       this.fetchInfo.getRecordSize(),
                                       this.maxRows,
                                       this.rowsInResultSet));
        return true;
    }

    private boolean fetchLast()
        throws SQLException
    {
        ReplyPacket reply;
        // in case a max row is set, fetching the last is totally different
        // thing
        if(!maxRowIsSet() || maxRowsOutSideResult==1) {
            try {
                // we have to use the safe fetch size here.
                reply=this.fetchInfo.executeFetchLast(safeFetchSize);
            } catch(SQLException sqlEx) {
                if(sqlEx.getErrorCode() == 100) {
                    this.empty=true;
                    this.positionState=POSITION_AFTER_LAST;
                    this.currentChunk=null;
                    return false;
                } else {
                    throw sqlEx;
                }
            }
            // If we did know a safe fetch size for LAST, we may have landed
            // before the position we wanted to fetch. In this case the chunk
            // starts a few rows before the end
            try {
                reply.findPart(PartKind.Data_C);
            } catch(PartNotFound pnf) {
                throw new InternalJDBCError("Fetch operation delivered no data part");
            }
            setCurrentChunk(new FetchChunk(FetchChunk.TYPE_LAST,
                                           - reply.partArguments(),    // absolute start position
                                           reply,          // reply packet
                                           fetchInfo.getRecordSize(), // the size for data part navigation
                                           0, // already ensured that there is no maxRows
                                           this.rowsInResultSet
                                           )
                            );
            currentChunk.moveToUpperBound(); // what we really want is the last position inside
            // this chunk, as this is the position of the last record.
            return true;
        } else {
            // we have the good and the messy way. We may know how many
            // records are inside, and thus make something senseful.
            if(rowsInResultSetKnown()) {
                // determined to what position we have to fetch
                int usedFetchSize=this.safeFetchSize;
                int usedPhysicalRow=this.rowsInResultSet - usedFetchSize + 1;
                try {
                    reply = this.fetchInfo.executeFetchAbsolute(usedPhysicalRow, usedFetchSize);
                } catch(SQLException sqlEx) {
                    if(sqlEx.getErrorCode() == 100) {
                        // something wicked happened. we had a valid
                        // value for the rows, and it was not good.
                        // erase the value for the rows, and retry.
                        this.rowsInResultSet=-1;
                        return fetchLast();
                    } else {
                        throw sqlEx;
                    }
                }
                setCurrentChunk(new FetchChunk(FetchChunk.TYPE_ABSOLUTE_UP,
                                               usedPhysicalRow,
                                               reply,
                                               fetchInfo.getRecordSize(),
                                               maxRows,
                                               this.rowsInResultSet));
                currentChunk.moveToUpperBound(); // It does not matter whether we
                // did get a few less records, just position at the last.
                return true;
            } else {
                try {
                    // try a fetch at the position the user wants.
                    // if this does not find anything, try the next one ...
                    reply=this.fetchInfo.executeFetchAbsolute(maxRows, 1);
                    setCurrentChunk(new FetchChunk(FetchChunk.TYPE_ABSOLUTE_UP,
                                                   maxRows,
                                                   reply,
                                                   fetchInfo.getRecordSize(),
                                                   maxRows,
                                                   this.rowsInResultSet));
                    currentChunk.moveToUpperBound();
                    return true;
                } catch(SQLException sqlEx) {
                    if(sqlEx.getErrorCode()!=100) {
                        throw sqlEx;
                    }
                }
                this.maxRowsOutSideResult=1; // set indicator
                return fetchLast(); // recursive call will trap in other condition
            }
        }
    }



    /**
     * Executes a FETCH FIRST, and stores the result internally.
     * @return true if the cursor is positioned correctly.
     */
    private boolean fetchFirst()
        throws SQLException
    {
        ReplyPacket reply;

        int usedFetchSize=this.fetchSize;

        // avoid fetching too much by limiting the fetch size.
        if(maxRowIsSet()) {
            usedFetchSize=Math.min(usedFetchSize, this.maxRows);
        }

        try {
        	if (this.statement.resultSetType == ResultSet.TYPE_FORWARD_ONLY){
              reply=this.fetchInfo.executeFetchNext(usedFetchSize);
        	} else {
              reply=this.fetchInfo.executeFetchFirst(usedFetchSize);
        	} 
      	} catch(SQLExceptionSapDB sqlEx) {
            if(sqlEx.getErrorCode() == 100) {
                this.empty=true;
                this.positionState=POSITION_AFTER_LAST;
                this.currentChunk=null;
            } else {
                throw sqlEx;
            }
            return false;
        }
        setCurrentChunk(new FetchChunk(FetchChunk.TYPE_FIRST, // fetch first is forward
                                       1,    // absolute start position
                                       reply,          // reply packet
                                       fetchInfo.getRecordSize(), // the size for data part navigation
                                       maxRows, // the current maxRows setting, for determining the last
                                       // condition in that case
                                       this.rowsInResultSet
                                       ));
        return true;
    }

    /**
     * Executes an absolute fetch, to the physical row given.
     * @param physicalRow the physical row. It is assumed that this is
     *   is < 0, and within the possibly set maxRows limit.
     * @return true if the fetch did position at the row.
     */
    private boolean fetchAbsoluteDown(int physicalRow)
        throws SQLException
    {
        if(maxRowIsSet()) {
            if(maxRowsOutSideResult == -1) {
                if(rowsInResultSet==-1) {
                    throw new InternalJDBCError(MessageTranslator.translate(MessageKey.ERROR_ASSERTION_MAXROWS_IN_RESULT));
                }
                int absrow=maxRows + physicalRow +1;
                if(absrow <= 0) {
                    this.positionState=POSITION_BEFORE_FIRST;
                    return false;
                }
                return absolute(absrow);
            } else if(maxRowsOutSideResult == 0) {
                try {
                    ReplyPacket reply=this.fetchInfo.executeFetchAbsolute(this.maxRows, 1);
                    setCurrentChunk(new FetchChunk(FetchChunk.TYPE_ABSOLUTE_UP,
                                                   maxRows,
                                                   reply,
                                                   fetchInfo.getRecordSize(),
                                                   maxRows,
                                                   this.rowsInResultSet));
                    currentChunk.moveToUpperBound();
                } catch(SQLException sqlEx) {
                    if(sqlEx.getErrorCode() != 100) {
                        throw sqlEx;
                    }
                    this.maxRowsOutSideResult=1;
                    return absolute(physicalRow);
                }
                this.maxRowsOutSideResult=-1;
                return absolute(physicalRow);
            } else {
                ReplyPacket reply=null;
                try {
                    reply=this.fetchInfo.executeFetchAbsolute(physicalRow, this.fetchSize);
                } catch(SQLExceptionSapDB sqlEx) {
                    if(sqlEx.getErrorCode()==100) {
                        this.positionState=POSITION_BEFORE_FIRST;
                        return false;
                    } else {
                        throw sqlEx;
                    }
                }
                setCurrentChunk(new FetchChunk(FetchChunk.TYPE_ABSOLUTE_DOWN,
                                               physicalRow,
                                               reply,
                                               fetchInfo.getRecordSize(),
                                               maxRows,
                                               this.rowsInResultSet));
                if(!currentChunk.setRow(physicalRow)) {
                    this.positionState=POSITION_BEFORE_FIRST;
                    return false;
                }
                return true;
            }
        } else {
            ReplyPacket reply=null;
            try {
                reply=this.fetchInfo.executeFetchAbsolute(physicalRow, this.fetchSize);
            } catch(SQLExceptionSapDB sqlEx) {
                if(sqlEx.getErrorCode()==100) {
                    this.positionState=POSITION_BEFORE_FIRST;
                    return false;
                } else {
                    throw sqlEx;
                }
            }
            setCurrentChunk(new FetchChunk(FetchChunk.TYPE_ABSOLUTE_DOWN,
                                           physicalRow,
                                           reply,
                                           fetchInfo.getRecordSize(),
                                           maxRows,
                                           this.rowsInResultSet));
            if(!currentChunk.setRow(physicalRow)) {
                this.positionState=POSITION_BEFORE_FIRST;
                return false;
            }
            return true;
        }

    }


    /**
     * Executes an absolute fetch, to the physical row given.
     * @param physicalRow the physical row. It is assumed that it is > 0, and
     *   less than maxrows/rowsInResultSet
     * @param backward Whether this positioning is done on the way backward. 
     * @return true if the fetch did position at the row, false otherwise.
     */
    private boolean fetchAbsoluteUp(int physicalRow, boolean backward )
        throws SQLException
    {
        ReplyPacket reply;

        // now check where is the end ... at least
        // the end we know - actually the end may be earlier,
        // if there is an unchecked maxRows out there.
        int maxKnownEnd;
        if(maxRowIsSet()) {
            if(rowsInResultSetKnown()) {
                maxKnownEnd=Math.min(this.maxRows, this.rowsInResultSet);
            } else {
                maxKnownEnd=this.maxRows;
            }
        } else {
            if(rowsInResultSetKnown()) {
                maxKnownEnd=this.rowsInResultSet;
            } else {
                maxKnownEnd=Integer.MAX_VALUE;
            }
        }


        int usedFetchSize=this.fetchSize;
        int usedPhysicalRow=physicalRow;
        // The backward navigation is only honored when the 'safe fetch size'
        // is determined.
        if(backward && safeFetchSizeDetermined) {
            usedFetchSize = this.safeFetchSize;
            usedPhysicalRow = physicalRow - usedFetchSize + 1;
            if(usedPhysicalRow <= 0) {
                usedPhysicalRow = 1;
            }
            // System.out.println("ABSOLUTE " + usedPhysicalRow + " FETCHSIZE " + usedFetchSize);
        } else {
            // if we would definitely shoot above the end,
            // we have to modify our query ... we have
            // to move back the real row so that our window
            // just finishes at the end of the result
            // physical row can be only made less using this attempt.
            // we make this only if the used fetch size is safe,
            // as otherwise we possibly do not get the expected row.
            if(physicalRow + usedFetchSize > maxKnownEnd 
                    && usedFetchSize <= this.safeFetchSize) {
                usedPhysicalRow = maxKnownEnd - usedFetchSize + 1;
            }
        }
        
        try {
            reply=this.fetchInfo.executeFetchAbsolute(usedPhysicalRow, usedFetchSize);
        } catch(SQLExceptionSapDB sqlEx) {
            if(sqlEx.getErrorCode() == 100) {
                // don't touch the chunk, in case we may need it ...
                // as the the row is >0, we are after the last
                this.positionState=POSITION_AFTER_LAST;
            } else {
                throw sqlEx;
            }
            return false;
        }
        setCurrentChunk(new FetchChunk(FetchChunk.TYPE_ABSOLUTE_UP,
                                       usedPhysicalRow,
                                       reply,
                                       fetchInfo.getRecordSize(),
                                       maxRows,
                                       this.rowsInResultSet));
        // if we moved the window, to avoid fetching behind the last,
        // we need to adjust the logical position.
        if(usedPhysicalRow != physicalRow) {
            if(!currentChunk.setRow(physicalRow)) {
                this.positionState=POSITION_AFTER_LAST;
                return false;
            }
        }

        return true;
    }



    /**
     * Check whether to honor <code>maxRows</code>.
     */
    private boolean maxRowIsSet()
    {
        return maxRows!=0;
    }

    /**
     * Check whether the number of rows is there.
     */
    private boolean rowsInResultSetKnown()
    {
        return rowsInResultSet!=-1;
    }

    /**
     * Asserts that the result set is not a FORWARD_ONLY result.
     */
    private void assertNotForwardOnly()
        throws SQLException
    {
        if(this.getType()==ResultSet.TYPE_FORWARD_ONLY) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_RESULTSET_FORWARDONLY));
        }
    }

    /**
     * Asserts that the result set is not closed.
     */
    protected void assertNotClosed()
        throws SQLException
    {
        super.assertOpen();
        if(this.isClosed) {
            throw new ObjectIsClosedException(this);
        }
    }


    final protected DBTechTranslator findColumnInfo(String columnName)
        throws SQLException
    {
        assertNotClosed();
        DBTechTranslator info=this.fetchInfo.getColumnInfo(columnName);
        if(info==null) {
            throw new InvalidColumnException (columnName, this);
        }
        return info;
    }

    final protected DBTechTranslator[] getColInfo() throws SQLException
    {
        return this.fetchInfo.getColInfo();
    }

    final protected int numberOfColumns()
    {
        return fetchInfo.numberOfColumns();
    }

    final protected StructuredMem getCurrentRecord()
        throws SQLException
    {
        if(positionState==POSITION_BEFORE_FIRST) {
            throw new JDBCDriverException
                (MessageTranslator.translate(MessageKey.ERROR_RESULTSET_BEFOREFIRST));
        }
        if(positionState==POSITION_AFTER_LAST) {
            throw new JDBCDriverException(MessageTranslator.translate(MessageKey.ERROR_RESULTSET_AFTERLAST));
        }
        return currentChunk.getCurrentRecord();
    }

    private DBTechTranslator findColumnInfo (int colIndex)
        throws SQLException
    {
        assertNotClosed();
        DBTechTranslator info;

        try {
            info = this.getColInfo() [colIndex - 1];
        }
        catch (ArrayIndexOutOfBoundsException exc) {
            throw new InvalidColumnException (colIndex, this);
        }
        return info;
    }

    /**
     * Helper method for indicating that the result is read-only.
     *
     * @exception JDBCDriverException always.
     */
    private void throwNotUpdatable()
        throws JDBCDriverException
    {
        throw new JDBCDriverException (MessageTranslator.translate(MessageKey.ERROR_RESULTSET_NOTUPDATABLE), this);
    }

    /**
     * Helper method that closes the accumulated outgoing streams
     * and readers if a cursor is moved.
     */
    private void closeOpenStreams()
    {
        Enumeration e=openStreams.elements();
        while(e.hasMoreElements()) {
            try {
                Object o=e.nextElement();
                try {
                    InputStream is=(InputStream)o;
                    is.close();
                } catch(ClassCastException ccx) {
                    Reader r=(Reader)o;
                    r.close();
                }
            } catch(IOException io_ex) {
                // ignore
            }
        }
        openStreams.clear();
    }

    /**
     * Updates the current chunk.
     * Also consistently:
     * <ul>
     *  <li>moves the position states to <code>POSITION_INSIDE</code></li>
     *  <li>updates the chunk size of necessary</li>
     *  <li>eventually updates the number of rows in this result set</li>
     *  <li>clears out a modified kernel pos</i>
     * </ul>
     */
    private void setCurrentChunk(FetchChunk newChunk)
        throws SQLException
    {
        this.positionState=this.positionStateOfChunk=POSITION_INSIDE;
        this.currentChunk=newChunk;
        int safe_fetchsize = Math.min(this.fetchSize, Math.max(newChunk.size(), this.safeFetchSize));
        if(this.safeFetchSize != safe_fetchsize) {
            this.safeFetchSize = safe_fetchsize;
            this.safeFetchSizeDetermined = false;
        } else {
            this.safeFetchSizeDetermined = safe_fetchsize != 1;
        }
        this.modifiedKernelPos = 0; // clear this out, until someone will de
        updateRowStatistics();
    }

    private void updateRowStatistics()
        throws SQLException
    {
        if(!rowsInResultSetKnown()) {
            // If this is the one and only chunk, yes then we
            // have only the records in this chunk.
            if(currentChunk.isLast() && currentChunk.isFirst()) {
                setRowsInResultSet(currentChunk.size());
                currentChunk.setRowsInResultSet(rowsInResultSet);
            }
            // otherwise, we may have navigated through it from start ...
            else if(currentChunk.isLast() && currentChunk.isForward()) {
                setRowsInResultSet(currentChunk.getEnd());
                currentChunk.setRowsInResultSet(rowsInResultSet);
            }
            // ... or from end
            else if(currentChunk.isFirst() && !currentChunk.isForward()) {
                setRowsInResultSet(- currentChunk.getStart());
                currentChunk.setRowsInResultSet(rowsInResultSet);
            } else if (currentChunk.isForward()) {
                largestKnownAbsPos=Math.max(largestKnownAbsPos, currentChunk.getEnd());
            }
        }
    }

    private int invertPosition(int row)
    {
        // dont check:
        //  - row negative
        //  - rowsInResultSet known
        //         if(row>0) {
        //             return row;
        //         }
        //         if(!rowsInResultSetKnown()) {
        //             throw new SQLException("Rows of result set must be known.");
        //         }

        return rowsInResultSet + row + 1;
    }

    
    private void printPosInfo(int largestknown, int smallestbad, int stepsize, int pos) {
        System.err.println("---------------------------------------------------");
        System.err.println("LARGEST KNOWN POS: " + largestknown);
        System.err.println("SMALLEST BAD  POS: " + smallestbad);
        System.err.println("STEP SIZE        : " + stepsize);
        System.err.println("POSITION         : " + pos);
    }

    private void getRowsInResult()
        throws SQLException
    {
        // what we do:
        // take the largest known position, then make fetches going forward (FETCH ABSOLUTE)
        // the step size is doubled when it was successful and again divided by 2 when it
        // failed.
        int stepsize=32;            // just a guess for a good start value
        int pos=largestKnownAbsPos;
        int smallestbadpos=Integer.MAX_VALUE;

        // printPosInfo(largestKnownAbsPos, smallestbadpos, stepsize, pos);
        
        try {
           this.fetchInfo.executeFetchAbsolute(largestKnownAbsPos, 1);
        } catch(SQLException sqlEx) {
            // we may find an empty result set due to
            // isolation level / delete operations
            if(sqlEx.getErrorCode() == 100) {
                if(largestKnownAbsPos == 1) {
                    this.empty=true;
                    this.positionState=POSITION_BEFORE_FIRST;
                    return;
                } else {
                    largestKnownAbsPos=1;
                    getRowsInResult();
                    return;
                }
            } else {
                throw sqlEx;
            }
        }

        while(true) {
            
            // printPosInfo(largestKnownAbsPos, smallestbadpos, stepsize, pos);
            // don't fetch above maxrows
            if(maxRows!=0 &&  pos + stepsize > maxRows) {
                stepsize=maxRows - pos;
            }


            // launch the probe
            try {
                // Unfortunately, a fetch relative destroy the cursor position when
                // failing, thus we have to use a FETCH ABSOLUTE.
                this.fetchInfo.executeFetchAbsolute(pos+stepsize, 1);
            } catch(SQLException sqlEx) {
                // probe failed, check what to do
                if(sqlEx.getErrorCode() == 100) {
                    // if we are one step into the ground, we certainly know
                    // where the edge was
                    if(stepsize==1) {
                        // as it did throw an exception, reply was not re-assigned
                        setRowsInResultSet(pos);
                        this.currentChunk=null;
                        this.positionState=POSITION_BEFORE_FIRST;
                        this.positionStateOfChunk=POSITION_NOT_AVAILABLE;
                        return;
                    }
                    // otherwise launch a smaller probe.
                    smallestbadpos = pos + stepsize;

                    stepsize /= 2;
                    if(stepsize == 0)
                        stepsize=1;
                    continue;
                } else {
                    throw sqlEx;
                }
            }

            // update pos
            pos=pos+stepsize;

            // update step size
            //
            stepsize *= 2;
            if(stepsize >= (smallestbadpos - pos))  {
                stepsize = (smallestbadpos - pos) / 2;
                if(stepsize == 0) { stepsize = 1; }
            }

            // we may be at maxrows
            if(maxRows!= 0 && pos == maxRows) {
                setRowsInResultSet(pos);
                this.currentChunk=null;
                this.positionState=POSITION_BEFORE_FIRST;
                this.positionStateOfChunk=POSITION_NOT_AVAILABLE;
                return;
            }
        }

    }

    public java.net.URL getURL(int parm1) throws java.sql.SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_URL_UNSUPPORTED),
             this);
    }
    public java.net.URL getURL(String parm1) throws java.sql.SQLException{
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_URL_UNSUPPORTED),
             this);
    }

    public void updateRef(int parm1, Ref parm2) throws java.sql.SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_REF_UNSUPPORTED),
             this);
    }

    public void updateRef(String parm1, Ref parm2) throws java.sql.SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_REF_UNSUPPORTED),
             this);
    }

    public void updateBlob(int parm1, Blob parm2) throws java.sql.SQLException {
        this.throwNotUpdatable();
    }

    public void updateBlob(String parm1, Blob parm2) throws java.sql.SQLException {
        this.throwNotUpdatable();
    }

    public void updateClob(int parm1, Clob parm2) throws java.sql.SQLException {
        this.throwNotUpdatable();
    }

    public void updateClob(String parm1, Clob parm2) throws java.sql.SQLException {
        this.throwNotUpdatable();
    }

    public void updateArray(int parm1, Array parm2) throws java.sql.SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_ARRAY_UNSUPPORTED),
             this);
    }

    public void updateArray(String parm1, Array parm2) throws java.sql.SQLException {
        throw new NotSupportedException
            (MessageTranslator.translate(MessageKey.ERROR_ARRAY_UNSUPPORTED),
             this);
    }

        public AbstractABAPStreamGetval getOMSGetval(int i) {
                return null;
        }

    protected void finalize() throws Throwable {
		try {
			this.close();
		} catch (SQLException ex) {
		}
		super.finalize();
	}
    
    /**
	 * cancel internal commands.
	 */
    public void cancel() throws SQLException {
    }
}
