/*


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

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

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

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


*/

package com.sap.dbtech.jdbc;

import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;
import java.sql.Types;

import com.sap.dbtech.jdbc.exceptions.DatabaseException;
import com.sap.dbtech.jdbc.exceptions.InternalJDBCError;
import com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB;
import com.sap.dbtech.jdbc.translators.DBTechTranslator;
import com.sap.dbtech.util.MessageKey;
import com.sap.dbtech.util.MessageTranslator;

/**
 *
 */
public class UpdatableResultSetSapDB extends ResultSetSapDB {

    private String tableName;
    private CallableStatementSapDB insertCmd;
    private CallableStatementSapDB updateCmd;
    private CallableStatementSapDB deleteCmd;
    private CallableStatementSapDB currentChangeCmd;
    private int [] updateColMapping;

    private int savedCurrentRow;
    private int savedPositionState;

    // constants
    private static final int MODE_READ   = 0;
    private static final int MODE_INSERT = 1;
    private static final int MODE_UPDATE = 2;


    private int currentMode = MODE_READ;

    /**
     */
    UpdatableResultSetSapDB(ConnectionSapDB connection,
                            FetchInfo fetchInfo,
                            StatementSapDB producer,
                            int fetchSize,
                            int maxRow,
                            String tableName,
                            int cursorType,
                            com.sap.dbtech.jdbc.packet.ReplyPacket reply)
        throws java.sql.SQLException
    {
        super(connection, fetchInfo, producer, fetchSize, maxRow, cursorType, reply);
        this.tableName=tableName;
    }

    /**
     * UpdatableResultSetSapDBTech constructor comment.
     * @param connection com.sap.dbtech.jdbc.ConnectionSapDBTech
     * @param cursorName java.lang.String
     * @param infos com.sap.dbtech.jdbc.translators.DBTechTranslator[]
     * @param colNames java.lang.String[]
     * @exception java.sql.SQLException The exception description.
     */
    UpdatableResultSetSapDB(
            ConnectionSapDB connection,
            String cursorName,
            com.sap.dbtech.jdbc.translators.DBTechTranslator[] infos,
            java.lang.String[] colNames,
            StatementSapDB producer,
            int fetchSize,
            int maxRow,
            String tableName,
            int  cursorType,
            com.sap.dbtech.jdbc.packet.ReplyPacket reply)
        throws java.sql.SQLException
    {
        super(connection, cursorName, infos, colNames, producer, fetchSize, maxRow, cursorType, reply);
        this.tableName = tableName;
    }
    /**
     *
     * @return boolean
     * @param row int
     * @exception java.sql.SQLException The exception description.
     */
    public boolean absolute (int row) throws SQLException {
        this.internalCancelRowUpdates ();
        return super.absolute (row);
    }
    /**
    * JDBC 2.0
    *
    * <p>Moves the cursor to the end of the result set, just after the last
    * row.  Has no effect if the result set contains no rows.
    *
    * @exception java.sql.SQLException if a database access error occurs or the
    * result set type is TYPE_FORWARD_ONLY
    */
    public void afterLast() throws SQLException {
        this.internalCancelRowUpdates ();
        super.afterLast ();
    }

    /**
    * JDBC 2.0
    *
    * <p>Moves the cursor to the front of the result set, just before the
    * first row. Has no effect if the result set contains no rows.
    *
    * @exception java.sql.SQLException if a database access error occurs or the
    * result set type is TYPE_FORWARD_ONLY
    */
    public void beforeFirst() throws SQLException {
        this.internalCancelRowUpdates ();
        super.beforeFirst ();
    }

    /**
    * JDBC 2.0
    *
    * 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 java.sql.SQLException
    {
        if(this.currentMode == MODE_INSERT) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_CANCELUPDATES_INSERTROW));
        }
        internalCancelRowUpdates();
    }

    private void internalCancelRowUpdates()
        throws SQLException
    {
        if (this.currentChangeCmd != null) {
            this.currentChangeCmd.clearParameters ();
        }
        this.currentMode = MODE_READ;
    }


    public void deleteRow ()
        throws SQLException
    {
        assertNotClosed();

        if(currentMode==MODE_INSERT) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_DELETEROW_INSERTROW));
        }

        int i=getRow();

        if(i==0) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_DELETEROW_NOROW));
        }


        if (this.deleteCmd == null) {
            String cmdString = "DELETE FROM " + this.tableName
                + " WHERE POS OF \"" + this.getCursorName ()
                + "\" IS ? ";
            this.deleteCmd =
                (CallableStatementSapDB) this.connection.prepareStatement(cmdString);
        }


        /*set delete position to current row */
        this.deleteCmd.setInt(1,i);
        this.deleteCmd.execute ();

        // repositioning
        this.initializeFields();
        this.absolute(i);
    }

    /**
     * findColumn method comment.
     */
    private int findUpdateColumn (
        int colIndex)
            throws java.sql.SQLException
    {

        this.assertNotClosed();
        this.prepareUpdate ();
        int result = 0;
        boolean outOfBoundExceptionCatched = false;
        try {
            result = this.updateColMapping [colIndex];
        }
        catch (ArrayIndexOutOfBoundsException exc) {
            outOfBoundExceptionCatched = true;
        }
        if (result == 0 || outOfBoundExceptionCatched) {
            if (colIndex > 0 && colIndex < this.getColInfo().length){
                throw new SQLExceptionSapDB (MessageTranslator.translate(MessageKey.ERROR_UPDATEROW_COLUMNNOTUPDATABLE,
                        this.getColInfo()[colIndex-1].getColumnName()));
            }
            throw new com.sap.dbtech.jdbc.exceptions.InvalidColumnException (colIndex, this);
        }
        return result;
    }
    /**
     * findColumn method comment.
     */
    private int findUpdateColumn (
        String colName)
            throws java.sql.SQLException
    {
        int index = this.findColumnInfo (colName).getColIndex();
        return this.findUpdateColumn (index+1);
    }
    /**
     *
     */
    public boolean first ()
        throws SQLException
    {
        this.internalCancelRowUpdates ();
        return super.first ();
    }
    /**
    * JDBC 2.0
    *
    * 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 {
        this.assertNotClosed();
        return java.sql.ResultSet.CONCUR_UPDATABLE;
    }
    /**
    * JDBC 2.0
    *
    * 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 java.sql.SQLException
    {
        // TBD:
        // - reflect the change in the database
        // - reposition back to the row where it was before the insert

        if (this.currentMode == MODE_INSERT) {
            this.insertCmd.executeUpdate();
            this.currentMode= MODE_INSERT;
        } else {
            throw new SQLException(MessageTranslator.translate(MessageKey.ERROR_INSERTROW_INSERTROW));
        }
    }

    /**
     *
     * @exception java.sql.SQLException The exception description.
     */
    public boolean last () throws SQLException {
        this.internalCancelRowUpdates ();
        return super.last ();
    }

    /**
     *
     * 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 java.sql.SQLException {
        this.assertNotClosed();
        switch (this.currentMode) {
        case MODE_INSERT:
            // already there
            break;
        case MODE_UPDATE:
            this.internalCancelRowUpdates();
            // fallthrough
        case MODE_READ:
            if (this.insertCmd == null) {
                this.insertCmd = this.prepareHelper(MODE_INSERT);
            }
            savedPositionState=this.positionState; // before 1st, after last ?
            savedCurrentRow = this.getRow(); // this wrecks the mode, and the position.

            this.currentChangeCmd = this.insertCmd;
            this.currentMode = MODE_INSERT;


        }
    }

    /**
     * Moves to the current row, that was current before it was set to the
     * insert row. This is a no-op when the position is not the insert row.
     */
    public void moveToCurrentRow()
        throws SQLException
    {
        // check that result set is open
        this.assertNotClosed();

        if(this.currentMode == MODE_INSERT) {
            if (this.currentChangeCmd != null) {
                this.currentChangeCmd.clearParameters ();
            }
            this.currentMode = MODE_READ;
            // jump to position, and cancel pending updates through the absolute method
            switch(savedPositionState) {
            case POSITION_BEFORE_FIRST:
                this.beforeFirst();
                break;
            case POSITION_AFTER_LAST:
                this.afterLast();
                break;
            default:
                if(savedPositionState!=0) {
                    absolute(savedCurrentRow);
                }
            }
        } else {
            return;
        }
    }

    /**
     *
     * @return com.sap.dbtech.jdbc.CallableStatementSapDBTech
     * @param forUpdate boolean
     * @exception java.sql.SQLException The exception description.
     */
    private CallableStatementSapDB
    prepareHelper (
        int mode)
    throws java.sql.SQLException
    {
        String prelude;
        String finale;
        switch (mode) {
        case MODE_UPDATE:
            prelude = "UPDATE " + this.tableName + " SET \"";
            finale = "\" = ? WHERE POS OF \"" + this.getCursorName()+"\" IS ? ";
            break;
        case MODE_INSERT:
            prelude = "INSERT INTO " + this.tableName + " SET \"";
            finale = "\" = ?";
            break;
        default:
            return null;
        }
        CallableStatementSapDB result = null;
        DBTechTranslator[] colInfo=this.getColInfo();
        int colinfo_length=colInfo.length;
        java.util.Vector validColumns = new java.util.Vector (colinfo_length);


        for (int i = 0; i < colinfo_length; ++i) {
            validColumns.addElement(colInfo [i].getColumnName());
        }
        int colcount=colinfo_length;
        while ((result == null) && (validColumns.size() > 0) && colcount>0) {
            colcount--;
            String columns = com.sap.dbtech.util.StringUtil.join (validColumns.elements (), "\" = ?, \"");
            String sqlCmd = prelude + columns + finale;
            try {
                result = (CallableStatementSapDB) this.getConnection().prepareCall(sqlCmd);
                result.statementType = StatementSapDB.Statement_UpdatableResultSet;
            }
            catch (DatabaseException sqlExc) {
                if (sqlExc.getErrorCode()==-7032)
                  throw sqlExc;
                int errPos = sqlExc.getErrorPos() - 1;
                if (errPos < prelude.length()) {
                    throw new InternalJDBCError (MessageTranslator.translate(MessageKey.ERROR_INTERNAL_PREPAREHELPER), sqlExc);
                }
                int endCol = sqlCmd.indexOf ('\"', errPos);
                String colName = sqlCmd.substring (errPos, endCol);
                validColumns.removeElement(colName);
            }
        }
        if (result == null) {
            throw new com.sap.dbtech.jdbc.exceptions.JDBCDriverException
                (MessageTranslator.translate(MessageKey.ERROR_NOCOLUMNS_UPDATABLE));
        }
        int count = validColumns.size ();
        this.updateColMapping = new int [colinfo_length + 1];
        boolean useDefaultValue = DriverSapDB.getBooleanProperty(this.connection.getConnectProperties(),
                DriverSapDB.useDefaultValueForUpdatableRS_C, false);
        for (int i = 0; i < count; ++i) {
            com.sap.dbtech.jdbc.translators.DBTechTranslator current = this
                    .findColumnInfo((String) validColumns.elementAt(i));
            current.allowWrites();
            this.updateColMapping[current.getColIndex() + 1] = i + 1;
            if (mode == MODE_INSERT) {
                if (useDefaultValue) {
                    result.setDefault(i + 1);
                } else {
                    result.setNull(i + 1, Types.OTHER);
                }
            }
        }
        return result;
    }

    /**
     * @exception java.sql.SQLException The exception description.
     */
    private void prepareUpdate () throws java.sql.SQLException {
        if (this.currentMode == MODE_READ) {
            if (this.updateCmd == null) {
                this.updateCmd = this.prepareHelper(MODE_UPDATE);
            }
            this.currentChangeCmd = this.updateCmd;
            this.currentMode = MODE_UPDATE;
//            int columns=this.numberOfColumns();
//            for (int i = 0; i < columns; ++i) {
//                int colIndex = i + 1;
//                try {
//                    this.updateCmd.setObject(this.findUpdateColumn(colIndex), this.getObject (colIndex));
//                }
//                catch (com.sap.dbtech.jdbc.exceptions.InvalidColumnException exc) {
//                    // ignore
//                }
//            }
        }
    }



    /**
    * JDBC 2.0
    *
    * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setAsciiStream (updIndex, x, length);
    }
    /**
    * JDBC 2.0
    *
    * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setAsciiStream (updIndex, x, length);
    }
    /**
    * JDBC 2.0
    *
    * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setBigDecimal (updIndex, x);
    }
    /**
    * JDBC 2.0
    *
    * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setBigDecimal (updIndex, x);
    }
    /**
    * JDBC 2.0
    *
    * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setBinaryStream (updIndex, x, length);
    }
    /**
    * JDBC 2.0
    *
    * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setBinaryStream (updIndex, x, length);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setBoolean (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setBoolean (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setByte (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setByte (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setBytes (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setBytes (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setCharacterStream (updIndex, x, length);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setCharacterStream (updIndex, reader, length);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setDate (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setDate (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setDouble (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setDouble (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setFloat (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setFloat(updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setInt (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setInt (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setLong (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setLong (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setNull (updIndex, 0);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setNull (updIndex, 0);
    }

    public void updateDefault(String columnName) throws SQLException {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setDefault(updIndex);
    }

    public void updateDefault(int columnIndex) throws SQLException {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setDefault(updIndex);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setObject (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setObject (updIndex, x, scale);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setObject (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setObject (updIndex, x, scale);
    }

    private CallableStatementSapDB generatePartialUpdateStatement()
            throws SQLException {
        StringBuffer tmpStmtString = new StringBuffer("UPDATE "
                + this.tableName + " SET \"");
        Object args[] = this.updateCmd.getInputArgs();
        DBTechTranslator[] colInfo = this.getColInfo();
        String delimiter = "";
        for (int i = 1; i < this.updateColMapping.length; i++) {
            int colind = this.updateColMapping[i];
            if (colind != 0 && this.updateCmd.isParameterSet(colind)) {
                tmpStmtString.append(delimiter);
                tmpStmtString.append(colInfo[i - 1].getColumnName());
                delimiter = "\" = ?, \"";
            }
        }

        tmpStmtString.append("\" = ? WHERE POS OF \"" + this.getCursorName()
                + "\" IS ? ");
        CallableStatementSapDB updCmd = null;
        try {
            updCmd = (CallableStatementSapDB) this.connection
                    .prepareCall(tmpStmtString.toString());
        } catch (DatabaseException sql) {
            throw new InternalJDBCError(MessageTranslator
                    .translate(MessageKey.ERROR_INTERNAL_PREPAREHELPER), sql);
        }
        Object newargs[] = updCmd.getInputArgs();
        for (int i = 0, j = 0; i < args.length; i++) {
            if (this.updateCmd.isParameterSet(i + 1)) {
                newargs[j++] = args[i];
            }
        }
        return updCmd;
    }

    /**
     * JDBC 2.0
     *
     * 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 java.sql.SQLException {

        CallableStatementSapDB updCmd=this.updateCmd;
        int safeCurrentMode=this.currentMode;
        CallableStatementSapDB saveCurrentChangeCmd=this.currentChangeCmd;
        this.currentChangeCmd=null;
        // this will wreck mode etc.
        int internal=this.getRow();
        // ... therfore we keep them at a safe place meanwhile
        this.currentMode=safeCurrentMode;
        this.currentChangeCmd=saveCurrentChangeCmd;

        if(internal==0) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_UPDATEROW_NOROW));
        }
        if (this.currentMode == MODE_UPDATE) {
            Object args[] = this.updateCmd.getInputArgs();
            boolean needPartialUpdate = false;
            for (int i = 1; i < args.length; i++) {
               if (! this.updateCmd.isParameterSet(i)) {
                   needPartialUpdate = true;
                   break;
               }  
            }
            if (needPartialUpdate){
                updCmd = this.generatePartialUpdateStatement();
                this.currentChangeCmd = updCmd;
            } 
            
            /*set update position in current rowset -
              last parameter of update command*/
            updCmd.setInt(updCmd.parseinfo.inputCount, internal);
            updCmd.executeUpdate();
            if (needPartialUpdate){
                this.currentChangeCmd = saveCurrentChangeCmd;
            } 
            absolute(internal);
            this.currentMode= MODE_READ;
            modifiedKernelPos=internal; // the kernel position has changed to the current row.
        } else if(this.currentMode == MODE_INSERT) {
            throw new SQLExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_UPDATEROW_INSERTROW));
        }
        // otherwise some called updateRow without updating something ..., this will not harm
    }

    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setShort (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setShort (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 java.sql.SQLException {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setString(updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setString (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setTime (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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 {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setTime (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setTimestamp (updIndex, x);
    }
    /**
     * JDBC 2.0
     *
     * 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
    {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setTimestamp (updIndex, x);
    }

    /*
     * JDBC 3.0
     */
     public void updateBlob(int columnIndex, Blob x) throws java.sql.SQLException {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setBlob (updIndex, x);
     }
     public void updateBlob(String columnName, Blob x) throws java.sql.SQLException {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setBlob (updIndex, x);
     }
     public void updateClob(int columnIndex, Clob x) throws java.sql.SQLException {
        int updIndex = this.findUpdateColumn (columnIndex);
        this.currentChangeCmd.setClob (updIndex, x);
     }
     public void updateClob(String columnName, Clob x) throws java.sql.SQLException {
        int updIndex = this.findUpdateColumn (columnName);
        this.currentChangeCmd.setClob (updIndex, x);
     }
     /**
      * cancel method comment.
      */
     public void cancel() throws SQLException {
         this.currentChangeCmd.cancel();
     }
}
