/*
 Copyright (C) 2002-2004 MySQL AB

 This program is free software; you can redistribute it and/or modify
 it under the terms of version 2 of the GNU General Public License as
 published by the Free Software Foundation.
 

 There are special exceptions to the terms and conditions of the GPL 
 as it is applied to this software. View the full text of the 
 exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this 
 software distribution.

 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

 */
package com.mysql.jdbc;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Array;
import java.sql.Date;
import java.sql.Ref;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;


/**
 * A ResultSet provides access to a table of data generated by executing a
 * Statement.  The table rows are retrieved in sequence.  Within a row its
 * column values can be accessed in any order.
 * 
 * <P>
 * A ResultSet maintains a cursor pointing to its current row of data.
 * Initially the cursor is positioned before the first row.  The 'next' method
 * moves the cursor to the next row.
 * </p>
 * 
 * <P>
 * The getXXX methods retrieve column values for the current row.  You can
 * retrieve values either using the index number of the column, or by using
 * the name of the column.  In general using the column index will be more
 * efficient.  Columns are numbered from 1.
 * </p>
 * 
 * <P>
 * For maximum portability, ResultSet columns within each row should be read in
 * left-to-right order and each column should be read only once.
 * </p>
 * 
 * <P>
 * For the getXXX methods, the JDBC driver attempts to convert the underlying
 * data to the specified Java type and returns a suitable Java value.  See the
 * JDBC specification for allowable mappings from SQL types to Java types with
 * the ResultSet getXXX methods.
 * </p>
 * 
 * <P>
 * Column names used as input to getXXX methods are case insenstive.  When
 * performing a getXXX using a column name, if several columns have the same
 * name, then the value of the first matching column will be returned.  The
 * column name option is designed to be used when column names are used in the
 * SQL Query.  For columns that are NOT explicitly named in the query, it is
 * best to use column numbers.  If column names were used there is no way for
 * the programmer to guarentee that they actually refer to the intended
 * columns.
 * </p>
 * 
 * <P>
 * A ResultSet is automatically closed by the Statement that generated it when
 * that Statement is closed, re-executed, or is used to retrieve the next
 * result from a sequence of multiple results.
 * </p>
 * 
 * <P>
 * The number, types and properties of a ResultSet's columns are provided by
 * the ResultSetMetaData object returned by the getMetaData method.
 * </p>
 *
 * @author Mark Matthews
 * @version $Id: ResultSet.java,v 1.18.2.35 2004/11/05 16:57:46 mmatthew Exp $
 *
 * @see ResultSetMetaData
 * @see java.sql.ResultSet
 */
public class ResultSet implements java.sql.ResultSet {
    /**
     * This method ends up being staticly synchronized, so just store our own
     * copy....
     */
    private TimeZone defaultTimeZone;

    /** The Connection instance that created us */
    protected com.mysql.jdbc.Connection connection; // The connection that created us

    /** Map column names (and all of their permutations) to column indices */
    protected Map columnNameToIndex = null;

    /** Map of fully-specified column names to column indices */
    protected Map fullColumnNameToIndex = null;

    /** The actual rows */
    protected RowData rowData; // The results

    /** The warning chain */
    protected java.sql.SQLWarning warningChain = null;

    /** The statement that created us */
    protected com.mysql.jdbc.Statement owningStatement;

    /** The catalog that was in use when we were created */
    protected String catalog = null;

    /**
     * Any info message from the server that was created while generating this
     * result set (if 'info parsing'  is enabled for the connection).
     */
    protected String serverInfo = null;

    /** The fields for this result set */
    protected Field[] fields; // The fields

    /** Pointer to current row data */
    protected byte[][] thisRow; // Values for current row

    /** Are we in the middle of doing updates to the current row? */
    protected boolean doingUpdates = false;

    /** Has this result set been closed? */
    protected boolean isClosed = false;

    /** Are we on the insert row? */
    protected boolean onInsertRow = false;

    /**
     * Do we actually contain rows, or just information about
     * UPDATE/INSERT/DELETE?
     */
    protected boolean reallyResult = false;

    /** Did the previous value retrieval find a NULL? */
    protected boolean wasNullFlag = false;

    /**
     * First character of the query that created this result set...Used to
     * determine whether or not to parse server info messages in certain
     * circumstances.
     */
    protected char firstCharOfQuery;

    /** The current row #, -1 == before start of result set */
    protected int currentRow = -1; // Cursor to current row;

    /** The direction to fetch rows (always FETCH_FORWARD) */
    protected int fetchDirection = FETCH_FORWARD;

    /** The number of rows to fetch in one go... */
    protected int fetchSize = 0;

    /** Are we read-only or updatable? */
    protected int resultSetConcurrency = 0;

    /** Are we scroll-sensitive/insensitive? */
    protected int resultSetType = 0;

    /** How many rows were affected by UPDATE/INSERT/DELETE? */
    protected long updateCount;

    // These are longs for
    // recent versions of the MySQL server.
    //
    // They get reduced to ints via the JDBC API,
    // but can be retrieved through a MySQLStatement
    // in their entirety.
    //

    /** Value generated for AUTO_INCREMENT columns */
    protected long updateId = -1;
    private Calendar fastDateCal = null;
    private boolean hasBuiltIndexMapping = false;
    private boolean useStrictFloatingPoint = false;

    /**
     * Create a result set for an executeUpdate statement.
     *
     * @param updateCount the number of rows affected by the update
     * @param updateID the autoincrement value (if any)
     */
    public ResultSet(long updateCount, long updateID) {
        this.updateCount = updateCount;
        this.updateId = updateID;
        reallyResult = false;
        fields = new Field[0];
    }

    /**
     * Create a new ResultSet
     *
     * @param catalog the database in use when we were created
     * @param fields an array of Field objects (basically, the ResultSet
     *        MetaData)
     * @param tuples actual row data
     * @param conn the Connection that created us.
     *
     * @throws SQLException if an error occurs
     */
    public ResultSet(String catalog, Field[] fields, RowData tuples,
        com.mysql.jdbc.Connection conn) throws SQLException {
        this(fields, tuples);
        setConnection(conn);
        this.catalog = catalog;
        
    }

    /**
     * Creates a new ResultSet object.
     *
     * @param fields DOCUMENT ME!
     * @param tuples DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     */
    public ResultSet(Field[] fields, RowData tuples) throws SQLException {
        //_currentRow   = -1;
        this.fields = fields;
        this.rowData = tuples;
        this.updateCount = (long) rowData.size();

        if (Driver.DEBUG) {
            System.out.println("Retrieved " + updateCount + " rows");
        }

        this.reallyResult = true;

        // Check for no results
        if (this.rowData.size() > 0) {
            //_thisRow = _rows.next();
            if (this.updateCount == 1) {
                if (this.thisRow == null) {
                    //_currentRow = -1;
                    this.rowData.close(); // empty result set
                    this.updateCount = -1;
                }
            }
        } else {
            this.thisRow = null;
        }

        this.rowData.setOwner(this);
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Determine if the cursor is after the last row in the result set.
     * </p>
     *
     * @return true if after the last row, false otherwise.  Returns false when
     *         the result set contains no rows.
     *
     * @exception SQLException if a database-access error occurs.
     */
    public boolean isAfterLast() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "isAfterLast", args);
        }

        checkClosed();
        
        boolean b = rowData.isAfterLast();

        if (Driver.TRACE) {
            Debug.returnValue(this, "isAfterLast", new Boolean(b));
        }

        return b;
    }

    /**
     * JDBC 2.0 Get an array column.
     *
     * @param i the first column is 1, the second is 2, ...
     *
     * @return an object representing an SQL array
     *
     * @throws SQLException if a database error occurs
     * @throws NotImplemented DOCUMENT ME!
     */
    public java.sql.Array getArray(int i) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * JDBC 2.0 Get an array column.
     *
     * @param colName the column name
     *
     * @return an object representing an SQL array
     *
     * @throws SQLException if a database error occurs
     * @throws NotImplemented DOCUMENT ME!
     */
    public java.sql.Array getArray(String colName) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * A column value can be retrieved as a stream of ASCII characters and then
     * read in chunks from the stream.  This method is particulary suitable
     * for retrieving large LONGVARCHAR values. The JDBC driver will do any
     * necessary conversion from the database format into ASCII.
     * 
     * <p>
     * <B>Note:</B> All the data in the returned stream must be read prior to
     * getting the value of any other column.  The next call to a get method
     * implicitly closes the stream.  Also, a stream may return 0 for
     * available() whether there is data available or not.
     * </p>
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     *
     * @return a Java InputStream that delivers the database column value as a
     *         stream of one byte ASCII characters.  If the value is SQL NULL
     *         then the result is null
     *
     * @exception java.sql.SQLException if a database access error occurs
     *
     * @see getBinaryStream
     */
    public InputStream getAsciiStream(int columnIndex)
        throws java.sql.SQLException {
        checkRowPos();

        return getBinaryStream(columnIndex);
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public InputStream getAsciiStream(String columnName)
        throws java.sql.SQLException {
        return getAsciiStream(findColumn(columnName));
    }

    //---------------------------------------------------------------------
    // Traversal/Positioning
    //---------------------------------------------------------------------

    /**
     * JDBC 2.0
     * 
     * <p>
     * Determine if the cursor is before the first row in the result set.
     * </p>
     *
     * @return true if before the first row, false otherwise. Returns false
     *         when the result set contains no rows.
     *
     * @exception SQLException if a database-access error occurs.
     */
    public boolean isBeforeFirst() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "isBeforeFirst", args);
        }

        checkClosed();
        
        boolean b = rowData.isBeforeFirst();

        if (Driver.TRACE) {
            Debug.returnValue(this, "isBeforeFirst", new Boolean(b));
        }

        return b;
    }

    /**
     * Get the value of a column in the current row as a java.math.BigDecimal
     * object
     *
     * @param columnIndex the first column is 1, the second is 2...
     * @param scale the number of digits to the right of the decimal
     *
     * @return the column value; if the value is SQL NULL, null
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public BigDecimal getBigDecimal(int columnIndex, int scale)
        throws java.sql.SQLException {
        String stringVal = getString(columnIndex);
        BigDecimal val;

        if (stringVal != null) {
            if (stringVal.length() == 0) {
                val = new BigDecimal(0);

                return val.setScale(scale);
            }

            try {
                val = new BigDecimal(stringVal);
            } catch (NumberFormatException ex) {
                throw new java.sql.SQLException("Bad format for BigDecimal '"
                    + stringVal + "' in column " + columnIndex + "("
                    + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }

            try {
                return val.setScale(scale);
            } catch (ArithmeticException ex) {
                throw new java.sql.SQLException("Bad format for BigDecimal '"
                    + stringVal + "' in column " + columnIndex + "("
                    + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }
        }

        return null;
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     * @param scale DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public BigDecimal getBigDecimal(String columnName, int scale)
        throws java.sql.SQLException {
        return getBigDecimal(findColumn(columnName), scale);
    }

    /**
     * JDBC 2.0 Get the value of a column in the current row as a
     * java.math.BigDecimal object.
     *
     * @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 SQLException if a database-access error occurs.
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
        String stringVal = getString(columnIndex);
        BigDecimal val;

        if (stringVal != null) {
            if (stringVal.length() == 0) {
                val = new BigDecimal(0);

                return val;
            }

            try {
                val = new BigDecimal(stringVal);

                return val;
            } catch (NumberFormatException ex) {
                throw new java.sql.SQLException("Bad format for BigDecimal '"
                    + stringVal + "' in column " + columnIndex + "("
                    + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }
        }

        return null;
    }

    /**
     * JDBC 2.0 Get the value of a column in the current row as a
     * java.math.BigDecimal object.
     *
     * @param columnName the name of the column to retrieve the value from
     *
     * @return the BigDecimal value in the column
     *
     * @throws SQLException if an error occurs
     */
    public BigDecimal getBigDecimal(String columnName)
        throws SQLException {
        return getBigDecimal(findColumn(columnName));
    }

    /**
     * A column value can also be retrieved as a binary strea.  This method is
     * suitable for retrieving LONGVARBINARY values.
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return a Java InputStream that delivers the database column value as a
     *         stream of bytes.  If the value is SQL NULL, then the result is
     *         null
     *
     * @exception java.sql.SQLException if a database access error occurs
     *
     * @see getAsciiStream
     * @see getUnicodeStream
     */
    public InputStream getBinaryStream(int columnIndex)
        throws java.sql.SQLException {
        checkRowPos();

        byte[] b = getBytes(columnIndex);

        if (b != null) {
            return new ByteArrayInputStream(b);
        }

        return null;
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public InputStream getBinaryStream(String columnName)
        throws java.sql.SQLException {
        return getBinaryStream(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Get a BLOB column.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     *
     * @return an object representing a BLOB
     *
     * @throws SQLException if an error occurs.
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public java.sql.Blob getBlob(int columnIndex) throws SQLException {
        checkRowPos();

        if ((columnIndex < 1) || (columnIndex > fields.length)) {
            throw new java.sql.SQLException("Column Index out of range ( "
                + columnIndex + " > " + fields.length + ").", SQLError.SQL_STATE_INVALID_COLUMN_NUMBER);
        }

        try {
            if (thisRow[columnIndex - 1] == null) {
                wasNullFlag = true;
            } else {
                wasNullFlag = false;
            }
        } catch (NullPointerException ex) {
            wasNullFlag = true;
        }

        if (wasNullFlag) {
            return null;
        }

        return new Blob(thisRow[columnIndex - 1]);
    }

    /**
     * JDBC 2.0 Get a BLOB column.
     *
     * @param colName the column name
     *
     * @return an object representing a BLOB
     *
     * @throws SQLException if an error occurs.
     */
    public java.sql.Blob getBlob(String colName) throws SQLException {
        return getBlob(findColumn(colName));
    }

    /**
     * Get the value of a column in the current row as a Java boolean
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return the column value, false for SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public boolean getBoolean(int columnIndex) throws java.sql.SQLException {
        String stringVal = getString(columnIndex);

        if ((stringVal != null) && (stringVal.length() > 0)) {
            int c = Character.toLowerCase(stringVal.charAt(0));

            return ((c == 't') || (c == 'y') || (c == '1')
            || stringVal.equals("-1"));
        }

        return false;
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public boolean getBoolean(String columnName) throws java.sql.SQLException {
        return getBoolean(findColumn(columnName));
    }

    /**
     * Get the value of a column in the current row as a Java byte.
     *
     * @param columnIndex the first column is 1, the second is 2,...
     *
     * @return the column value; 0 if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public byte getByte(int columnIndex) throws java.sql.SQLException {
    	String stringVal = getString(columnIndex);

        if (this.wasNullFlag || stringVal == null) {
            return 0;
        }

        Field field = fields[columnIndex - 1];

        switch (field.getMysqlType()) {
        case MysqlDefs.FIELD_TYPE_DECIMAL:
        case MysqlDefs.FIELD_TYPE_TINY:
        case MysqlDefs.FIELD_TYPE_SHORT:
        case MysqlDefs.FIELD_TYPE_LONG:
        case MysqlDefs.FIELD_TYPE_FLOAT:
        case MysqlDefs.FIELD_TYPE_DOUBLE:
        case MysqlDefs.FIELD_TYPE_LONGLONG:
        case MysqlDefs.FIELD_TYPE_INT24:

            try {
                int decimalIndex = stringVal.indexOf(".");

                // Strip off the decimals
                if (decimalIndex != -1) {
                    stringVal = stringVal.substring(0, decimalIndex);
                }

                return Byte.parseByte(stringVal);
            } catch (NumberFormatException NFE) {
                throw new SQLException("Value '" + getString(columnIndex)
                    + "' is out of range [-127,127]", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }

        default:

            try {
                int decimalIndex = stringVal.indexOf(".");

                // Strip off the decimals
                if (decimalIndex != -1) {
                    stringVal = stringVal.substring(0, decimalIndex);
                }

                return Byte.parseByte(stringVal);
            } catch (NumberFormatException NFE) {
                throw new SQLException("Value '" + getString(columnIndex)
                    + "' is out of range [-127,127]", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public byte getByte(String columnName) throws java.sql.SQLException {
        return getByte(findColumn(columnName));
    }

    /**
     * Get the value of a column in the current row as a Java byte array.
     * 
     * <p>
     * <b>Be warned</b> If the blob is huge, then you may run out of memory.
     * </p>
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     *
     * @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 byte[] getBytes(int columnIndex) throws java.sql.SQLException {
        checkRowPos();

        try {
            if (thisRow[columnIndex - 1] == null) {
                wasNullFlag = true;
            } else {
                wasNullFlag = false;
            }
        } catch (NullPointerException E) {
            wasNullFlag = true;
        } catch (ArrayIndexOutOfBoundsException aioobEx) {
            throw new java.sql.SQLException("Column Index out of range ( "
                + columnIndex + " > " + fields.length + ").", SQLError.SQL_STATE_INVALID_COLUMN_NUMBER);
        }

        if (wasNullFlag) {
            return null;
        } else {
            return thisRow[columnIndex - 1];
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public byte[] getBytes(String columnName) throws java.sql.SQLException {
        return getBytes(findColumn(columnName));
    }

    //--------------------------JDBC 2.0-----------------------------------
    //---------------------------------------------------------------------
    // Getter's and Setter's
    //---------------------------------------------------------------------

    /**
     * JDBC 2.0
     * 
     * <p>
     * Get the value of a column in the current row as a java.io.Reader.
     * </p>
     *
     * @param columnIndex the column to get the value from
     *
     * @return the value in the column as a java.io.Reader.
     *
     * @throws SQLException if an error occurs
     */
    public java.io.Reader getCharacterStream(int columnIndex)
        throws SQLException {
        String stringVal = getString(columnIndex);

        if (stringVal != null) {
            return new StringReader(stringVal);
        } else {
            return null;
        }
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Get the value of a column in the current row as a java.io.Reader.
     * </p>
     *
     * @param columnName the column name to retrieve the value from
     *
     * @return the value as a java.io.Reader
     *
     * @throws SQLException if an error occurs
     */
    public java.io.Reader getCharacterStream(String columnName)
        throws SQLException {
        return getCharacterStream(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Get a CLOB column.
     *
     * @param i the first column is 1, the second is 2, ...
     *
     * @return an object representing a CLOB
     *
     * @throws SQLException if an error occurs
     */
    public java.sql.Clob getClob(int i) throws SQLException {
        return new com.mysql.jdbc.Clob(getString(i));
    }

    /**
     * JDBC 2.0 Get a CLOB column.
     *
     * @param colName the column name
     *
     * @return an object representing a CLOB
     *
     * @throws SQLException if an error occurs
     */
    public java.sql.Clob getClob(String colName) throws SQLException {
        return getClob(findColumn(colName));
    }

    /**
     * JDBC 2.0 Return the concurrency of this result set.  The concurrency
     * used is determined by the statement that created the result set.
     *
     * @return the concurrency type, CONCUR_READ_ONLY, etc.
     *
     * @throws SQLException if a database-access error occurs
     */
    public int getConcurrency() throws SQLException {
        return this.resultSetConcurrency;
    }

    /**
     * DOCUMENT ME!
     *
     * @param conn the connection that created this result set.
     */
    public synchronized void setConnection(com.mysql.jdbc.Connection conn) {
        this.connection = conn;

		if (this.connection != null) {
			this.useStrictFloatingPoint = this.connection.useStrictFloatingPoint();
			this.defaultTimeZone = this.connection.getDefaultTimeZone();
		} else {
			this.defaultTimeZone = TimeZone.getDefault();
		}
    }

    /**
     * Get the name of the SQL cursor used by this ResultSet
     * 
     * <p>
     * In SQL, a result table is retrieved though a cursor that is named.  The
     * current row of a result can be updated or deleted using a positioned
     * update/delete statement that references the cursor name.
     * </p>
     * 
     * <p>
     * JDBC supports this SQL feature by providing the name of the SQL cursor
     * used by a ResultSet.  The current row of a ResulSet is also the current
     * row of this SQL cursor.
     * </p>
     * 
     * <p>
     * <B>Note:</B> If positioned update is not supported, a
     * java.sql.SQLException is thrown.
     * </p>
     *
     * @return the ResultSet's SQL cursor name.
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public String getCursorName() throws java.sql.SQLException {
        throw new java.sql.SQLException("Positioned Update not supported.",
            "S1C00");
    }

    /**
     * Get the value of a column in the current row as a java.sql.Date object
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return the column value; null if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.Date getDate(int columnIndex) throws java.sql.SQLException {
        return getDate(columnIndex, null);
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public java.sql.Date getDate(String columnName)
        throws java.sql.SQLException {
        return getDate(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Get the value of a column in the current row as a java.sql.Date
     * object.  Use the calendar to construct an appropriate millisecond value
     * for the Date, if the underlying database doesn't 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 SQLException if a database-access error occurs.
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public java.sql.Date getDate(int columnIndex, Calendar cal)
        throws SQLException {
        Integer year = null;
        Integer month = null;
        Integer day = null;
        String stringVal = "";

        try {
            stringVal = getString(columnIndex);

            if (stringVal == null) {
                return null;
            } else {
                int length = stringVal.length();

                if ((length > 0) && (stringVal.charAt(0) == '0')
                        && (stringVal.equals("0000-00-00")
                        || stringVal.equals("0000-00-00 00:00:00")
                        || stringVal.equals("00000000000000")
                        || stringVal.equals("0"))) {
                    wasNullFlag = true;

                    return null;
                } else if (fields[columnIndex - 1].getMysqlType() == MysqlDefs.FIELD_TYPE_TIMESTAMP) {
                    // Convert from TIMESTAMP
                    switch (length) {
                    case 14:
                    case 8: {
                        year = new Integer(stringVal.substring(0, 4));
                        month = new Integer(stringVal.substring(4, 6));
                        day = new Integer(stringVal.substring(6, 8));

                        return fastDateCreate(cal, year.intValue() - 1900,
                            month.intValue() - 1, day.intValue());
                    }

                    case 12:
                    case 10:
                    case 6: {
                        year = new Integer(stringVal.substring(0, 2));

                        if (year.intValue() <= 69) {
                            year = new Integer(year.intValue() + 100);
                        }

                        month = new Integer(stringVal.substring(2, 4));
                        day = new Integer(stringVal.substring(4, 6));

                        return fastDateCreate(cal, year.intValue(),
                            month.intValue() - 1, day.intValue());
                    }

                    case 4: {
                        year = new Integer(stringVal.substring(0, 4));

                        if (year.intValue() <= 69) {
                            year = new Integer(year.intValue() + 100);
                        }

                        month = new Integer(stringVal.substring(2, 4));

                        return fastDateCreate(cal, year.intValue(),
                            month.intValue() - 1, 1);
                    }

                    case 2: {
                        year = new Integer(stringVal.substring(0, 2));

                        if (year.intValue() <= 69) {
                            year = new Integer(year.intValue() + 100);
                        }

                        return fastDateCreate(cal, year.intValue(), 0, 1);
                    }

                    default:
                        throw new SQLException("Bad format for Date '"
                            + stringVal + "' in column " + columnIndex + "("
                            + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
                    } /* endswitch */
                } else if (fields[columnIndex - 1].getMysqlType() == MysqlDefs.FIELD_TYPE_YEAR) {
                    year = new Integer(stringVal.substring(0, 4));

                    return fastDateCreate(cal, year.intValue() - 1900, 0, 1);
                } else {
                    if (length < 10) {
                        throw new SQLException("Bad format for Date '"
                            + stringVal + "' in column " + columnIndex + "("
                            + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
                    }

                    year = new Integer(stringVal.substring(0, 4));
                    month = new Integer(stringVal.substring(5, 7));
                    day = new Integer(stringVal.substring(8, 10));
                }

                return fastDateCreate(cal, year.intValue() - 1900,
                    month.intValue() - 1, day.intValue());
            }
        } catch (Exception e) {
            throw new java.sql.SQLException("Cannot convert value '"
                + stringVal + "' from column " + columnIndex + "(" + stringVal
                + " ) to DATE.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * Get the value of a column in the current row as a java.sql.Date object.
     * Use the calendar to construct an appropriate millisecond value for the
     * Date, if the underlying database doesn't store timezone information.
     *
     * @param columnName is the SQL name of the column
     * @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 SQLException if a database-access error occurs.
     */
    public java.sql.Date getDate(String columnName, Calendar cal)
        throws SQLException {
        return getDate(columnName);
    }

    /**
     * Get the value of a column in the current row as a Java double.
     *
     * @param columnIndex the first column is 1, the second is 2,...
     *
     * @return the column value; 0 if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public double getDouble(int columnIndex) throws java.sql.SQLException {
        try {
            return getDoubleInternal(columnIndex);
        } catch (NumberFormatException E) {
            throw new java.sql.SQLException("Bad format for number '"
                + new String(thisRow[columnIndex - 1]) + "' in column "
                + columnIndex + "(" + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public double getDouble(String columnName) throws java.sql.SQLException {
        return getDouble(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Give a hint as to the direction in which the rows in this
     * result set will be processed.  The initial value is determined by the
     * statement that produced the result set.  The fetch direction may be
     * changed at any time.
     *
     * @param direction the direction to fetch rows in.
     *
     * @exception SQLException if a database-access error occurs, or the result
     *            set type is TYPE_FORWARD_ONLY and direction is not
     *            FETCH_FORWARD. MM.MySQL actually ignores this, because it
     *            has the whole result set anyway, so the direction is
     *            immaterial.
     */
    public void setFetchDirection(int direction) throws SQLException {
        if ((direction != FETCH_FORWARD) && (direction != FETCH_REVERSE)
                && (direction != FETCH_UNKNOWN)) {
            throw new SQLException("Illegal value for fetch direction", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        } else {
            fetchDirection = direction;
        }
    }

    /**
     * JDBC 2.0 Returns the fetch direction for this result set.
     *
     * @return the fetch direction for this result set.
     *
     * @exception SQLException if a database-access error occurs
     */
    public int getFetchDirection() throws SQLException {
        return fetchDirection;
    }

    /**
     * JDBC 2.0 Give the JDBC driver a hint as to the number of rows that
     * should be fetched from the database when more rows are needed for this
     * result set.  If the fetch size specified is zero, then the JDBC driver
     * ignores the value, and is free to make its own best guess as to what
     * the fetch size should be.  The default value is set by the statement
     * that creates the result set.  The fetch size may be changed at any
     * time.
     *
     * @param rows the number of rows to fetch
     *
     * @exception SQLException if a database-access error occurs, or the
     *            condition 0 &lt;= rows &lt;= this.getMaxRows() is not
     *            satisfied. Currently ignored by this driver.
     */
    public void setFetchSize(int rows) throws SQLException {
        if (rows < 0) { /* || rows > getMaxRows()*/
            throw new SQLException("Value must be between 0 and getMaxRows()",
                SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }

        fetchSize = rows;
    }

    /**
     * JDBC 2.0 Return the fetch size for this result set.
     *
     * @return the fetch size for this result set.
     *
     * @exception SQLException if a database-access error occurs
     */
    public int getFetchSize() throws SQLException {
        return fetchSize;
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Determine if the cursor is on the first row of the result set.
     * </p>
     *
     * @return true if on the first row, false otherwise.
     *
     * @exception SQLException if a database-access error occurs.
     */
    public boolean isFirst() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "isFirst", args);
        }

        checkClosed();
        
        boolean b = rowData.isFirst();

        if (Driver.TRACE) {
            Debug.returnValue(this, "isFirst", new Boolean(b));
        }

        return b;
    }

    /**
     * Get the value of a column in the current row as a Java float.
     *
     * @param columnIndex the first column is 1, the second is 2,...
     *
     * @return the column value; 0 if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public float getFloat(int columnIndex) throws java.sql.SQLException {
        String val = null;

        try {
            val = getString(columnIndex);

            if ((val != null) && (val.length() != 0)) {
                float f = Float.parseFloat(val);

                return f;
            } else {
                return 0;
            }
        } catch (NumberFormatException nfe) {
            try {
                // To do: warn on under/overflow?
                return (float) Double.parseDouble(val);
            } catch (NumberFormatException newNfe) {
                ; // ignore, it's not a number
            }

            throw new SQLException("Invalid value for getFloat() - '" + val
                + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public float getFloat(String columnName) throws java.sql.SQLException {
        return getFloat(findColumn(columnName));
    }

    /**
     * Get the value of a column in the current row as a Java int.
     *
     * @param columnIndex the first column is 1, the second is 2,...
     *
     * @return the column value; 0 if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public int getInt(int columnIndex) throws java.sql.SQLException {
        String val = null;

        try {
            val = getString(columnIndex);

            if ((val != null) && (val.length() != 0)) {
                if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)
                        && (val.indexOf(".") == -1)) {
                    return Integer.parseInt(val);
                } else {
                    // Convert floating point
                    return (int) (Double.parseDouble(val));
                }
            } else {
                return 0;
            }
        } catch (NumberFormatException nfe) {
            try {
                // To do: warn on under/overflow?
                return (int) Double.parseDouble(val);
            } catch (NumberFormatException newNfe) {
                ; // ignore, it's not a number
            }

            throw new SQLException("Invalid value for getInt() - '" + val + "'",
                SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public int getInt(String columnName) throws java.sql.SQLException {
        return getInt(findColumn(columnName));
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Determine if the cursor is on the last row of the result set. Note:
     * Calling isLast() may be expensive since the JDBC driver might need to
     * fetch ahead one row in order to determine whether the current row is
     * the last row in the result set.
     * </p>
     *
     * @return true if on the last row, false otherwise.
     *
     * @exception SQLException if a database-access error occurs.
     */
    public boolean isLast() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "isLast", args);
        }

        checkClosed();
        
        boolean b = rowData.isLast();

        if (Driver.TRACE) {
            Debug.returnValue(this, "relative", new Boolean(b));
        }

        return b;
    }

    /**
     * Get the value of a column in the current row as a Java long.
     *
     * @param columnIndex the first column is 1, the second is 2,...
     *
     * @return the column value; 0 if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public long getLong(int columnIndex) throws java.sql.SQLException {
        String val = null;

        try {
            val = getString(columnIndex);

            if ((val != null) && (val.length() != 0)) {
                if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)) {
                    return Long.parseLong(val);
                } else {
                    // Convert floating point
                    return Double.doubleToLongBits(Double.parseDouble(val));
                }
            } else {
                return 0;
            }
        } catch (NumberFormatException nfe) {
            try {
                // To do: warn on under/overflow?
                return (long) Double.parseDouble(val);
            } catch (NumberFormatException newNfe) {
                ; // ignore, it's not a number
            }

            throw new SQLException("Invalid value for getLong() - '" + val
                + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public long getLong(String columnName) throws java.sql.SQLException {
        return getLong(findColumn(columnName));
    }

    /**
     * The numbers, types and properties of a ResultSet's columns are provided
     * by the getMetaData method
     *
     * @return a description of the ResultSet's columns
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public java.sql.ResultSetMetaData getMetaData()
        throws java.sql.SQLException {
    	checkClosed();
    	
        return new com.mysql.jdbc.ResultSetMetaData(fields);
    }

    /**
     * Get the value of a column in the current row as a Java object
     * 
     * <p>
     * This method will return the value of the given column as a Java object.
     * The type of the Java object will be the default Java Object type
     * corresponding to the column's SQL type, following the mapping specified
     * in the JDBC specification.
     * </p>
     * 
     * <p>
     * This method may also be used to read database specific abstract data
     * types.
     * </p>
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return a Object holding the column value
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public Object getObject(int columnIndex) throws java.sql.SQLException {
        checkRowPos();

        if (Driver.TRACE) {
            Object[] args = { new Integer(columnIndex) };
            Debug.methodCall(this, "getObject", args);
        }

        try {
            if (thisRow[columnIndex - 1] == null) {
                wasNullFlag = true;

                return null;
            }
        } catch (ArrayIndexOutOfBoundsException aioobEx) {
            throw new java.sql.SQLException("Column Index out of range ( "
                + columnIndex + " > " + fields.length + ").", SQLError.SQL_STATE_INVALID_COLUMN_NUMBER);
        }

        wasNullFlag = false;

        Field field;
        field = fields[columnIndex - 1];

        switch (field.getSQLType()) {
        case Types.BIT:
            return Boolean.valueOf(getBoolean(columnIndex));

        case Types.TINYINT:

            return new Integer(getInt(columnIndex));
           
        case Types.SMALLINT:
        	
            return new Integer(getInt(columnIndex));
           
        case Types.INTEGER:

            if (field.isUnsigned()) {
                return new Long(getLong(columnIndex));
            } else {
                return new Integer(getInt(columnIndex));
            }

        case Types.BIGINT:

            if (field.isUnsigned()) {
                return getBigDecimal(columnIndex);
            } else {
                return new Long(getLong(columnIndex));
            }

        case Types.DECIMAL:
        case Types.NUMERIC:

            String stringVal = getString(columnIndex);
            BigDecimal val;

            if (stringVal != null) {
                if (stringVal.length() == 0) {
                    val = new BigDecimal(0);

                    return val;
                }

                try {
                    val = new BigDecimal(stringVal);
                } catch (NumberFormatException ex) {
                    throw new java.sql.SQLException(
                        "Bad format for BigDecimal '" + stringVal
                        + "' in column " + columnIndex + "("
                        + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
                }

                return val;
            } else {
                return null;
            }

        case Types.REAL:
            return new Float(getFloat(columnIndex));

        case Types.FLOAT:
        case Types.DOUBLE:
            return new Double(getDouble(columnIndex));

        case Types.CHAR:
        case Types.VARCHAR:
        case Types.LONGVARCHAR:
            return getString(columnIndex);

        case Types.BINARY:
        case Types.VARBINARY:
        case Types.LONGVARBINARY:

            if (!field.isBlob()) {
                return getString(columnIndex);
            } else if (!field.isBinary()) {
                return getString(columnIndex);
            } else {
                byte[] data = getBytes(columnIndex);
                Object obj = data;

                if ((data != null) && (data.length >= 2)) {
                    if ((data[0] == -84) && (data[1] == -19)) {
                        // Serialized object?
                        try {
                            ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
                            ObjectInputStream objIn = new ObjectInputStream(bytesIn);
                            obj = objIn.readObject();
                            objIn.close();
                            bytesIn.close();
                        } catch (ClassNotFoundException cnfe) {
                            throw new SQLException("Class not found: "
                                + cnfe.toString()
                                + " while reading serialized object");
                        } catch (IOException ex) {
                            obj = data; // not serialized?
                        }
                    }
                }

                return obj;
            }

        case Types.DATE:
            return getDate(columnIndex);

        case Types.TIME:
            return getTime(columnIndex);

        case Types.TIMESTAMP:
            return getTimestamp(columnIndex);

        default:
            return getString(columnIndex);
        }
    }

    /**
     * Get the value of a column in the current row as a Java object
     * 
     * <p>
     * This method will return the value of the given column as a Java object.
     * The type of the Java object will be the default Java Object type
     * corresponding to the column's SQL type, following the mapping specified
     * in the JDBC specification.
     * </p>
     * 
     * <p>
     * This method may also be used to read database specific abstract data
     * types.
     * </p>
     *
     * @param columnName is the SQL name of the column
     *
     * @return a Object holding the column value
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public Object getObject(String columnName) throws java.sql.SQLException {
        return getObject(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Returns the value of column i as a Java object.  Use the map to
     * determine the class from which to construct data of SQL structured and
     * distinct types.
     *
     * @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
     *
     * @throws SQLException because this is not implemented
     */
    public Object getObject(int i, java.util.Map map) throws SQLException {
        return getObject(i);
    }

    /**
     * JDBC 2.0 Returns the value of column i as a Java object.  Use the map to
     * determine the class from which to construct data of SQL structured and
     * distinct types.
     *
     * @param colName the column name
     * @param map the mapping from SQL type names to Java classes
     *
     * @return an object representing the SQL value
     *
     * @throws SQLException as this is not implemented
     */
    public Object getObject(String colName, java.util.Map map)
        throws SQLException {
        return getObject(findColumn(colName), map);
    }

    /**
     * JDBC 2.0 Get a REF(&lt;structured-type&gt;) column.
     *
     * @param i the first column is 1, the second is 2, ...
     *
     * @return an object representing data of an SQL REF type
     *
     * @throws SQLException as this is not implemented
     * @throws NotImplemented DOCUMENT ME!
     */
    public java.sql.Ref getRef(int i) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * JDBC 2.0 Get a REF(&lt;structured-type&gt;) column.
     *
     * @param colName the column name
     *
     * @return an object representing data of an SQL REF type
     *
     * @throws SQLException as this method is not implemented.
     * @throws NotImplemented DOCUMENT ME!
     */
    public java.sql.Ref getRef(String colName) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Determine the current row number.  The first row is number 1, the second
     * number 2, etc.
     * </p>
     *
     * @return the current row number, else return 0 if there is no current row
     *
     * @exception SQLException if a database-access error occurs.
     */
    public int getRow() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "getRow", args);
        }

        checkClosed();
        
        int currentRow = rowData.getCurrentRowNumber();
        int row = 0;

        // Non-dynamic result sets can be interrogated
        // for this information
        if (!rowData.isDynamic()) {
            if ((currentRow < 0) || rowData.isAfterLast() || rowData.isEmpty()) {
                row = 0;
            } else {
                row = currentRow + 1;
            }
        } else {
            // dynamic (streaming) can not
            row = currentRow + 1;
        }

        if (Driver.TRACE) {
            Debug.returnValue(this, "getRow", new Integer(row));
        }

        if (Driver.TRACE) {
            Debug.returnValue(this, "getRow", new Integer(row));
        }

        return row;
    }

    /**
     * Get the value of a column in the current row as a Java short.
     *
     * @param columnIndex the first column is 1, the second is 2,...
     *
     * @return the column value; 0 if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public short getShort(int columnIndex) throws java.sql.SQLException {
        String val = null;

        try {
            val = getString(columnIndex);

            if ((val != null) && (val.length() != 0)) {
                if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)
                        && (val.indexOf(".") == -1)) {
                    return Short.parseShort(val);
                } else {
                    // Convert floating point
                    return (short) (Double.parseDouble(val));
                }
            } else {
                return 0;
            }
        } catch (NumberFormatException nfe) {
            try {
                // To do: warn on under/overflow?
                return (short) Double.parseDouble(val);
            } catch (NumberFormatException newNfe) {
                ; // ignore, it's not a number
            }

            throw new SQLException("Invalid value for getShort() - '" + val
                + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public short getShort(String columnName) throws java.sql.SQLException {
        return getShort(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Return the Statement that produced the ResultSet.
     *
     * @return the Statment that produced the result set, or null if the result
     *         was produced some other way.
     *
     * @exception SQLException if a database-access error occurs
     */
    public java.sql.Statement getStatement() throws SQLException {
        return (java.sql.Statement) owningStatement;
    }

    /**
     * Get the value of a column in the current row as a Java String
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return the column value, null for SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     * @throws SQLException DOCUMENT ME!
     */
    public String getString(int columnIndex) throws java.sql.SQLException {
        checkRowPos();

        if (fields == null) {
            throw new java.sql.SQLException("Query generated no fields for ResultSet",
                SQLError.SQL_STATE_INVALID_COLUMN_NUMBER);
        }

        try {
            if (thisRow[columnIndex - 1] == null) {
                wasNullFlag = true;

                return null;
            } else {
                wasNullFlag = false;
            }
        } catch (NullPointerException E) {
            wasNullFlag = true;

            return null;
        } catch (ArrayIndexOutOfBoundsException aioobEx) {
            throw new java.sql.SQLException("Column Index out of range ( "
                + columnIndex + " > " + fields.length + ").", SQLError.SQL_STATE_INVALID_COLUMN_NUMBER);
        }

        String stringVal = null;
        columnIndex--; // JDBC is 1-based, Java is not !?

        if ((connection != null) && connection.useUnicode()) {
            try {
                String encoding = this.fields[columnIndex].getCharacterSet();

                if (encoding == null) {
                    stringVal = new String(thisRow[columnIndex]);
                } else {
                    SingleByteCharsetConverter converter = this.connection.getCharsetConverter(encoding);

                    if (converter != null) {
                        stringVal = converter.toString(thisRow[columnIndex]);
                    } else {
                        stringVal = new String(thisRow[columnIndex], encoding);
                    }
                }
            } catch (java.io.UnsupportedEncodingException E) {
                throw new SQLException("Unsupported character encoding '"
                    + connection.getEncoding() + "'.", SQLError.SQL_STATE_GENERAL_ERROR);
            }
        } else {
            stringVal = StringUtils.toAsciiString(thisRow[columnIndex]);
        }

        return stringVal;
    }

    /**
     * The following routines simply convert the columnName into a columnIndex
     * and then call the appropriate routine above.
     *
     * @param columnName is the SQL name of the column
     *
     * @return the column value
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public String getString(String columnName) throws java.sql.SQLException {
        return getString(findColumn(columnName));
    }

    /**
     * Get the value of a column in the current row as a java.sql.Time object
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return the column value; null if SQL NULL
     *
     * @throws java.sql.SQLException if a database access error occurs
     */
    public synchronized Time getTime(int columnIndex) throws java.sql.SQLException {
        return getTimeInternal(columnIndex, this.defaultTimeZone);
    }

    /**
     * Get the value of a column in the current row as a java.sql.Time object.
     *
     * @param columnName is the SQL name of the column
     *
     * @return the column value; if the value is SQL NULL, the result is null
     *
     * @throws java.sql.SQLException if a database-access error occurs.
     */
    public Time getTime(String columnName) throws java.sql.SQLException {
        return getTime(findColumn(columnName));
    }

    /**
     * Get the value of a column in the current row as a java.sql.Time object.
     * Use the calendar to construct an appropriate millisecond value for the
     * Time, if the underlying database doesn't 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 SQLException if a database-access error occurs.
     */
    public java.sql.Time getTime(int columnIndex, Calendar cal)
        throws SQLException {
        return getTimeInternal(columnIndex, cal.getTimeZone());
    }

    /**
     * Get the value of a column in the current row as a java.sql.Time object.
     * Use the calendar to construct an appropriate millisecond value for the
     * Time, if the underlying database doesn't store timezone information.
     *
     * @param columnName is 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 SQLException if a database-access error occurs.
     */
    public java.sql.Time getTime(String columnName, Calendar cal)
        throws SQLException {
        return getTime(findColumn(columnName), cal);
    }

    /**
     * Get the value of a column in the current row as a java.sql.Timestamp
     * object
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return the column value; null if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public synchronized Timestamp getTimestamp(int columnIndex) throws java.sql.SQLException {
        return getTimestampInternal(columnIndex, this.defaultTimeZone);
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public Timestamp getTimestamp(String columnName)
        throws java.sql.SQLException {
        return getTimestamp(findColumn(columnName));
    }

    /**
     * Get the value of a column in the current row as a java.sql.Timestamp
     * object. Use the calendar to construct an appropriate millisecond value
     * for the Timestamp, if the underlying database doesn't 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 SQLException if a database-access error occurs.
     */
    public java.sql.Timestamp getTimestamp(int columnIndex, Calendar cal)
        throws SQLException {
        return getTimestampInternal(columnIndex, cal.getTimeZone());
    }

    /**
     * Get the value of a column in the current row as a java.sql.Timestamp
     * object. Use the calendar to construct an appropriate millisecond value
     * for the Timestamp, if the underlying database doesn't store timezone
     * information.
     *
     * @param columnName is 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 SQLException if a database-access error occurs.
     */
    public java.sql.Timestamp getTimestamp(String columnName, Calendar cal)
        throws SQLException {
        return getTimestamp(findColumn(columnName), cal);
    }

    /**
     * JDBC 2.0 Return the type of this result set.  The type is determined
     * based on the statement that created the result set.
     *
     * @return TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, or
     *         TYPE_SCROLL_SENSITIVE
     *
     * @exception SQLException if a database-access error occurs
     */
    public int getType() throws SQLException {
        return resultSetType;
    }

    /**
     * @see ResultSet#getURL(int)
     */
    public URL getURL(int colIndex) throws SQLException {
        String val = getString(colIndex);

        if (val == null) {
            return null;
        } else {
            try {
                return new URL(val);
            } catch (MalformedURLException mfe) {
                throw new SQLException("Malformed URL '" + val + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }
        }
    }

    /**
     * @see ResultSet#getURL(String)
     */
    public URL getURL(String colName) throws SQLException {
        String val = getString(colName);

        if (val == null) {
            return null;
        } else {
            try {
                return new URL(val);
            } catch (MalformedURLException mfe) {
                throw new SQLException("Malformed URL '" + val + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }
        }
    }

    /**
     * A column value can also be retrieved as a stream of Unicode characters.
     * We implement this as a binary stream.
     *
     * @param columnIndex the first column is 1, the second is 2...
     *
     * @return a Java InputStream that delivers the database column value as a
     *         stream of two byte Unicode characters.  If the value is SQL
     *         NULL, then the result is null
     *
     * @exception java.sql.SQLException if a database access error occurs
     *
     * @see getAsciiStream
     * @see getBinaryStream
     */
    public InputStream getUnicodeStream(int columnIndex)
        throws java.sql.SQLException {
        checkRowPos();

        return getBinaryStream(columnIndex);
    }

    /**
     * DOCUMENT ME!
     *
     * @param columnName DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws java.sql.SQLException DOCUMENT ME!
     */
    public InputStream getUnicodeStream(String columnName)
        throws java.sql.SQLException {
        return getUnicodeStream(findColumn(columnName));
    }

    /**
     * The first warning reported by calls on this ResultSet is returned.
     * Subsequent ResultSet warnings will be chained to this
     * java.sql.SQLWarning.
     * 
     * <p>
     * The warning chain is automatically cleared each time a new row is read.
     * </p>
     * 
     * <p>
     * <B>Note:</B> This warning chain only covers warnings caused by ResultSet
     * methods.  Any warnings caused by statement methods (such as reading OUT
     * parameters) will be chained on the Statement object.
     * </p>
     *
     * @return the first java.sql.SQLWarning or null;
     *
     * @exception java.sql.SQLException if a database access error occurs.
     */
    public java.sql.SQLWarning getWarnings() throws java.sql.SQLException {
    	checkClosed();
    	
        return warningChain;
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Move to an absolute row number in the result set.
     * </p>
     * 
     * <p>
     * If row is positive, moves to an absolute row with respect to the
     * beginning of the result set.  The first row is row 1, the second is row
     * 2, etc.
     * </p>
     * 
     * <p>
     * If row is negative, moves to an absolute row position with respect to
     * the end of result set.  For example, calling absolute(-1) positions the
     * cursor on the last row, absolute(-2) indicates the next-to-last row,
     * etc.
     * </p>
     * 
     * <p>
     * An attempt to position the cursor beyond the first/last row in the
     * result set, leaves the cursor before/after the first/last row,
     * respectively.
     * </p>
     * 
     * <p>
     * Note: Calling absolute(1) is the same as calling first(). Calling
     * absolute(-1) is the same as calling last().
     * </p>
     *
     * @param row the row number to move to
     *
     * @return true if on the result set, false if off.
     *
     * @exception SQLException if a database-access error occurs, or row is 0,
     *            or result set type is TYPE_FORWARD_ONLY.
     */
    public boolean absolute(int row) throws SQLException {
        if (Driver.TRACE) {
            Object[] args = { new Integer(row) };
            Debug.methodCall(this, "absolute", args);
        }

        checkClosed();

        boolean b;

        if (rowData.size() == 0) {
            b = false;
        } else {
            if (row == 0) {
                throw new SQLException("Cannot absolute position to row 0",
                    SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
            }

            if (onInsertRow) {
                onInsertRow = false;
            }

            if (doingUpdates) {
                doingUpdates = false;
            }

            if (row == 1) {
                b = first();
            } else if (row == -1) {
                b = last();
            } else if (row > rowData.size()) {
                afterLast();
                b = false;
            } else {
                if (row < 0) {
                    // adjust to reflect after end of result set
                    int newRowPosition = rowData.size() + row + 1;

                    if (newRowPosition <= 0) {
                        beforeFirst();
                        b = false;
                    } else {
                        b = absolute(newRowPosition);
                    }
                } else {
                    row--; // adjust for index difference
                    rowData.setCurrentRow(row);
                    thisRow = (byte[][]) rowData.getAt(row);
                    b = true;
                }
            }
        }

        if (Driver.TRACE) {
            Debug.returnValue(this, "absolute", new Boolean(b));
        }

        return b;
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Moves to the end of the result set, just after the last row.  Has no
     * effect if the result set contains no rows.
     * </p>
     *
     * @exception SQLException if a database-access error occurs, or result set
     *            type is TYPE_FORWARD_ONLY.
     */
    public void afterLast() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "afterLast", args);
        }

        checkClosed();

        if (onInsertRow) {
            onInsertRow = false;
        }

        if (doingUpdates) {
            doingUpdates = false;
        }

        if (rowData.size() != 0) {
            rowData.afterLast();
            thisRow = null;
        }
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Moves to the front of the result set, just before the first row. Has no
     * effect if the result set contains no rows.
     * </p>
     *
     * @exception SQLException if a database-access error occurs, or result set
     *            type is TYPE_FORWARD_ONLY
     */
    public void beforeFirst() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "beforeFirst", args);
        }

        checkClosed();

        if (onInsertRow) {
            onInsertRow = false;
        }

        if (doingUpdates) {
            doingUpdates = false;
        }

        if (rowData.size() == 0) {
            return;
        } else {
            rowData.beforeFirst();
            thisRow = null;
        }
    }

    /**
     * JDBC 2.0 The cancelRowUpdates() method may be called after calling an
     * updateXXX() method(s) and before calling updateRow() to rollback the
     * updates made to a row.  If no updates have been made or updateRow() has
     * already been called, then this method has no effect.
     *
     * @exception SQLException if a database-access error occurs, or if called
     *            when on the insert row.
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void cancelRowUpdates() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * After this call, getWarnings returns null until a new warning is
     * reported for this ResultSet
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void clearWarnings() throws java.sql.SQLException {
        warningChain = null;
    }

    /**
     * In some cases, it is desirable to immediately release a ResultSet
     * database and JDBC resources instead of waiting for this to happen when
     * it is automatically closed.  The close method provides this immediate
     * release.
     * 
     * <p>
     * <B>Note:</B> A ResultSet is automatically closed by the Statement the
     * Statement that generated it when that Statement is closed, re-executed,
     * or is used to retrieve the next result from a sequence of multiple
     * results.  A ResultSet is also automatically closed when it is garbage
     * collected.
     * </p>
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public void close() throws java.sql.SQLException {
        realClose(true);
    }

    /**
     * JDBC 2.0 Delete the current row from the result set and the underlying
     * database.  Cannot be called when on the insert row.
     *
     * @exception SQLException if a database-access error occurs, or if called
     *            when on the insert row.
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void deleteRow() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * Map a ResultSet column name to a ResultSet column index
     *
     * @param columnName the name of the column
     *
     * @return the column index
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public int findColumn(String columnName) throws java.sql.SQLException {
        Integer index;

        synchronized (this) {
            if (!hasBuiltIndexMapping) {
                buildIndexMapping();
            }
        }

        index = (Integer) columnNameToIndex.get(columnName);

        if (index == null) {
            index = (Integer) fullColumnNameToIndex.get(columnName);
        }

        if (index != null) {
            return index.intValue() + 1;
        } else {
            // Try this inefficient way, now
            
            for (int i = 0; i < fields.length; i++) {
                if (fields[i].getName().equalsIgnoreCase(columnName)) {
                    return i + 1;
                } else if (fields[i].getFullName().equalsIgnoreCase(columnName)) {
                    return i + 1;
                }
            }

            throw new java.sql.SQLException("Column '" + columnName
                + "' not found.", SQLError.SQL_STATE_COLUMN_NOT_FOUND);
        }
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Moves to the first row in the result set.
     * </p>
     *
     * @return true if on a valid row, false if no rows in the result set.
     *
     * @exception SQLException if a database-access error occurs, or result set
     *            type is TYPE_FORWARD_ONLY.
     */
    public boolean first() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "first", args);
        }

        checkClosed();

        if (onInsertRow) {
            onInsertRow = false;
        }

        if (rowData.isEmpty()) {
            return false;
        } else {
            if (doingUpdates) {
                doingUpdates = false;
            }

            rowData.beforeFirst();
            thisRow = rowData.next();

            return true;
        }
    }

    /**
     * JDBC 2.0 Insert 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 SQLException if a database-access error occurs, if called
     *            when not on the insert row, or if all non-nullable columns
     *            in the insert row have not been given a value
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void insertRow() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Moves to the last row in the result set.
     * </p>
     *
     * @return true if on a valid row, false if no rows in the result set.
     *
     * @exception SQLException if a database-access error occurs, or result set
     *            type is TYPE_FORWARD_ONLY.
     */
    public boolean last() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "last", args);
        }

        checkClosed();

        if (rowData.size() == 0) {
            return false;
        } else {
            if (onInsertRow) {
                onInsertRow = false;
            }

            if (doingUpdates) {
                doingUpdates = false;
            }

            rowData.beforeLast();
            thisRow = rowData.next();

            return true;
        }
    }

    /**
     * JDBC 2.0 Move the cursor to the remembered cursor position, usually the
     * current row.  Has no effect unless the cursor is on the insert row.
     *
     * @exception SQLException if a database-access error occurs, or the result
     *            set is not updatable
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void moveToCurrentRow() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Move 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
     * updateXXX() methods prior to inserting the row into the result set.
     * Only the updateXXX(), getXXX(), and insertRow() 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 insertRow().  UpdateXXX()must be called before getXXX() on a
     * column.
     *
     * @exception SQLException if a database-access error occurs, or the result
     *            set is not updatable
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void moveToInsertRow() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * A ResultSet is initially positioned before its first row, the first call
     * to next makes the first row the current row; the second call makes the
     * second row the current row, etc.
     * 
     * <p>
     * If an input stream from the previous row is open, it is implicitly
     * closed.  The ResultSet's warning chain is cleared when a new row is
     * read
     * </p>
     *
     * @return true if the new current is valid; false if there are no more
     *         rows
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public boolean next() throws java.sql.SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "next", args);
        }

        checkClosed();

        if (onInsertRow) {
            onInsertRow = false;
        }

        if (doingUpdates) {
            doingUpdates = false;
        }

        boolean b;

        if (!reallyResult()) {
            throw new java.sql.SQLException("ResultSet is from UPDATE. No Data",
                SQLError.SQL_STATE_GENERAL_ERROR);
        }

        if (rowData.size() == 0) {
            b = false;
        } else {
            if (!rowData.hasNext()) {
                // force scroll past end
                rowData.next();
                b = false;
            } else {
                clearWarnings();
                thisRow = rowData.next();
                b = true;
            }
        }

        if (Driver.TRACE) {
            Debug.returnValue(this, "next", new Boolean(b));
        }

        return b;
    }

    /**
     * The prev method is not part of JDBC, but because of the architecture of
     * this driver it is possible to move both forward and backward within the
     * result set.
     * 
     * <p>
     * If an input stream from the previous row is open, it is implicitly
     * closed.  The ResultSet's warning chain is cleared when a new row is
     * read
     * </p>
     *
     * @return true if the new current is valid; false if there are no more
     *         rows
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    public boolean prev() throws java.sql.SQLException {
        checkClosed();

        int rowIndex = rowData.getCurrentRowNumber();

        if ((rowIndex - 1) >= 0) {
            rowIndex--;
            rowData.setCurrentRow(rowIndex);
            thisRow = (byte[][]) rowData.getAt(rowIndex);

            return true;
        } else if ((rowIndex - 1) == -1) {
            rowIndex--;
            rowData.setCurrentRow(rowIndex);
            thisRow = null;

            return false;
        } else {
            return false;
        }
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Moves to the previous row in the result set.
     * </p>
     * 
     * <p>
     * Note: previous() is not the same as relative(-1) since it makes sense to
     * call previous() when there is no current row.
     * </p>
     *
     * @return true if on a valid row, false if off the result set.
     *
     * @exception SQLException if a database-access error occurs, or result set
     *            type is TYPE_FORWAR_DONLY.
     */
    public boolean previous() throws SQLException {
        if (Driver.TRACE) {
            Object[] args = {  };
            Debug.methodCall(this, "previous", args);
        }

        if (onInsertRow) {
            onInsertRow = false;
        }

        if (doingUpdates) {
            doingUpdates = false;
        }

        return prev();
    }

    /**
     * JDBC 2.0 Refresh the value of the current row with its current value in
     * the database.  Cannot be called when on the insert row. The
     * refreshRow() 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 refreshRow() 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
     * refreshRow() is called after calling updateXXX(), but before calling
     * updateRow() then the updates made to the row are lost.  Calling
     * refreshRow() frequently will likely slow performance.
     *
     * @exception SQLException if a database-access error occurs, or if called
     *            when on the insert row.
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void refreshRow() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0
     * 
     * <p>
     * Moves 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 relative(0) is
     * valid, but does not change the cursor position.
     * </p>
     * 
     * <p>
     * Note: Calling relative(1) is different than calling next() since is
     * makes sense to call next() when there is no current row, for example,
     * when the cursor is positioned before the first row or after the last
     * row of the result set.
     * </p>
     *
     * @param rows the number of relative rows to move the cursor.
     *
     * @return true if on a row, false otherwise.
     *
     * @throws SQLException if a database-access error occurs, or there is no
     *         current row, or result set type is TYPE_FORWARD_ONLY.
     */
    public boolean relative(int rows) throws SQLException {
        if (Driver.TRACE) {
            Object[] args = { new Integer(rows) };
            Debug.methodCall(this, "relative", args);
        }

        checkClosed();

        if (rowData.size() == 0) {
            return false;
        }

        rowData.moveRowRelative(rows);
        thisRow = rowData.getAt(rowData.getCurrentRowNumber());

        boolean b = (!rowData.isAfterLast() && !rowData.isBeforeFirst());

        if (Driver.TRACE) {
            Debug.returnValue(this, "relative", new Boolean(b));
        }

        return b;
    }

    /**
     * JDBC 2.0 Determine if this row has been deleted.  A deleted row may
     * leave a visible "hole" in a result set.  This method can be used to
     * detect holes in a result set.  The value returned depends on whether or
     * not the result set can detect deletions.
     *
     * @return true if deleted and deletes are detected
     *
     * @exception SQLException if a database-access error occurs
     * @throws NotImplemented DOCUMENT ME!
     *
     * @see DatabaseMetaData#deletesAreDetected
     */
    public boolean rowDeleted() throws SQLException {
        throw new NotImplemented();
    }

    /**
     * JDBC 2.0 Determine if the current row has been inserted.  The value
     * returned depends on whether or not the result set can detect visible
     * inserts.
     *
     * @return true if inserted and inserts are detected
     *
     * @exception SQLException if a database-access error occurs
     * @throws NotImplemented DOCUMENT ME!
     *
     * @see DatabaseMetaData#insertsAreDetected
     */
    public boolean rowInserted() throws SQLException {
        throw new NotImplemented();
    }

    //---------------------------------------------------------------------
    // Updates
    //---------------------------------------------------------------------

    /**
     * JDBC 2.0 Determine if the current row has been updated.  The value
     * returned depends on whether or not the result set can detect updates.
     *
     * @return true if the row has been visibly updated by the owner or
     *         another, and updates are detected
     *
     * @exception SQLException if a database-access error occurs
     * @throws NotImplemented DOCUMENT ME!
     *
     * @see DatabaseMetaData#updatesAreDetected
     */
    public boolean rowUpdated() throws SQLException {
        throw new NotImplemented();
    }

    /**
     * @see ResultSet#updateArray(int, Array)
     */
    public void updateArray(int arg0, Array arg1) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * @see ResultSet#updateArray(String, Array)
     */
    public void updateArray(String arg0, Array arg1) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * JDBC 2.0 Update a column with an ascii stream value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateAsciiStream(int columnIndex, java.io.InputStream x,
        int length) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with an ascii stream value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     */
    public void updateAsciiStream(String columnName, java.io.InputStream x,
        int length) throws SQLException {
        updateAsciiStream(findColumn(columnName), x, length);
    }

    /**
     * JDBC 2.0 Update a column with a BigDecimal value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateBigDecimal(int columnIndex, BigDecimal x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a BigDecimal value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() methods are called to
     * update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateBigDecimal(String columnName, BigDecimal x)
        throws SQLException {
        updateBigDecimal(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a binary stream value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateBinaryStream(int columnIndex, java.io.InputStream x,
        int length) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a binary stream value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     */
    public void updateBinaryStream(String columnName, java.io.InputStream x,
        int length) throws SQLException {
        updateBinaryStream(findColumn(columnName), x, length);
    }

    /**
     * @see ResultSet#updateBlob(int, Blob)
     */
    public void updateBlob(int arg0, java.sql.Blob arg1)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * @see ResultSet#updateBlob(String, Blob)
     */
    public void updateBlob(String arg0, java.sql.Blob arg1)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a boolean value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateBoolean(int columnIndex, boolean x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a boolean value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateBoolean(String columnName, boolean x)
        throws SQLException {
        updateBoolean(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a byte value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateByte(int columnIndex, byte x) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a byte value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateByte(String columnName, byte x) throws SQLException {
        updateByte(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a byte array value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateBytes(int columnIndex, byte[] x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a byte array value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() methods are called to
     * update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateBytes(String columnName, byte[] x)
        throws SQLException {
        updateBytes(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a character stream value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateCharacterStream(int columnIndex, java.io.Reader x,
        int length) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a character stream value. The updateXXX()
     * methods are used to update column values in the current row, or the
     * insert row.  The updateXXX() methods do not update the underlying
     * database, instead the updateRow() or insertRow() methods are called to
     * update the database.
     *
     * @param columnName the name of the column
     * @param reader the stream to update the column with
     * @param length of the stream
     *
     * @throws SQLException if a database-access error occurs
     */
    public void updateCharacterStream(String columnName, java.io.Reader reader,
        int length) throws SQLException {
        updateCharacterStream(findColumn(columnName), reader, length);
    }

    /**
     * @see ResultSet#updateClob(int, Clob)
     */
    public void updateClob(int arg0, java.sql.Clob arg1) throws SQLException {
		throw new NotUpdatable();
    }

    /**
     * @see ResultSet#updateClob(String, Clob)
     */
    public void updateClob(String columnName, java.sql.Clob clob) throws SQLException {
        updateClob(findColumn(columnName), clob);
    }

    /**
     * JDBC 2.0 Update a column with a Date value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateDate(int columnIndex, java.sql.Date x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a Date value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateDate(String columnName, java.sql.Date x)
        throws SQLException {
        updateDate(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a Double value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateDouble(int columnIndex, double x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a double value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateDouble(String columnName, double x)
        throws SQLException {
        updateDouble(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a float value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateFloat(int columnIndex, float x) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a float value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateFloat(String columnName, float x)
        throws SQLException {
        updateFloat(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with an integer value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateInt(int columnIndex, int x) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with an integer value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateInt(String columnName, int x) throws SQLException {
        updateInt(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a long value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateLong(int columnIndex, long x) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a long value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateLong(String columnName, long x) throws SQLException {
        updateLong(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Give a nullable column a null value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnIndex the first column is 1, the second is 2, ...
     *
     * @exception SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateNull(int columnIndex) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a null value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateNull(String columnName) throws SQLException {
        updateNull(findColumn(columnName));
    }

    /**
     * JDBC 2.0 Update a column with an Object value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateObject(int columnIndex, Object x, int scale)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with an Object value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateObject(int columnIndex, Object x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with an Object value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     */
    public void updateObject(String columnName, Object x, int scale)
        throws SQLException {
        updateObject(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with an Object value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateObject(String columnName, Object x)
        throws SQLException {
        updateObject(findColumn(columnName), x);
    }

    /**
     * @see ResultSet#updateRef(int, Ref)
     */
    public void updateRef(int arg0, Ref arg1) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * @see ResultSet#updateRef(String, Ref)
     */
    public void updateRef(String arg0, Ref arg1) throws SQLException {
        throw new NotImplemented();
    }

    /**
     * JDBC 2.0 Update the underlying database with the new contents of the
     * current row.  Cannot be called when on the insert row.
     *
     * @exception SQLException if a database-access error occurs, or if called
     *            when on the insert row
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateRow() throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a short value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateShort(int columnIndex, short x) throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a short value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateShort(String columnName, short x)
        throws SQLException {
        updateShort(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a String value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateString(int columnIndex, String x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a String value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateString(String columnName, String x)
        throws SQLException {
        updateString(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a Time value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateTime(int columnIndex, java.sql.Time x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a Time value. The updateXXX() methods are
     * used to update column values in the current row, or the insert row. The
     * updateXXX() methods do not update the underlying database, instead the
     * updateRow() or insertRow() methods are called to update the database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateTime(String columnName, java.sql.Time x)
        throws SQLException {
        updateTime(findColumn(columnName), x);
    }

    /**
     * JDBC 2.0 Update a column with a Timestamp value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() 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 SQLException if a database-access error occurs
     * @throws NotUpdatable DOCUMENT ME!
     */
    public void updateTimestamp(int columnIndex, java.sql.Timestamp x)
        throws SQLException {
        throw new NotUpdatable();
    }

    /**
     * JDBC 2.0 Update a column with a Timestamp value. The updateXXX() methods
     * are used to update column values in the current row, or the insert row.
     * The updateXXX() methods do not update the underlying database, instead
     * the updateRow() or insertRow() methods are called to update the
     * database.
     *
     * @param columnName the name of the column
     * @param x the new column value
     *
     * @exception SQLException if a database-access error occurs
     */
    public void updateTimestamp(String columnName, java.sql.Timestamp x)
        throws SQLException {
        updateTimestamp(findColumn(columnName), x);
    }

    /**
     * A column may have the value of SQL NULL; wasNull() reports whether the
     * last column read had this special value.  Note that you must first call
     * getXXX on a column to try to read its value and then call wasNull() to
     * find if the value was SQL NULL
     *
     * @return true if the last column read was SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurred
     */
    public boolean wasNull() throws java.sql.SQLException {
        return wasNullFlag;
    }

    ///////////////////////////////////////////
    //
    // These number conversion routines save
    // a ton of "new()s", especially for the heavily
    // used getInt() and getDouble() methods
    //
    ///////////////////////////////////////////

    /**
     * Converts a string representation of a number to a double. Need a faster
     * way to do this.
     *
     * @param colIndex the 1-based index of the column to retrieve a double
     *        from.
     *
     * @return the double value represented by the string in buf
     *
     * @throws SQLException if an error occurs
     */
    protected double getDoubleInternal(int colIndex) throws SQLException {
        String s = null;

        try {
            s = getString(colIndex);

            if ((s == null) || (s.length() == 0)) {
                return 0;
            }

            double d = Double.parseDouble(s);

            if (this.useStrictFloatingPoint) {
                // Fix endpoint rounding precision loss in MySQL server
                if (d == 2.147483648E9) {
                    // Fix Odd end-point rounding on MySQL
                    d = 2.147483647E9;
                } else if (d == 1.0000000036275E-15) {
                    // Fix odd end-point rounding on MySQL
                    d = 1.0E-15;
                } else if (d == 9.999999869911E14) {
                    d = 9.99999999999999E14;
                } else if (d == 1.4012984643248E-45) {
                    d = 1.4E-45;
                } else if (d == 1.4013E-45) {
                    d = 1.4E-45;
                } else if (d == 3.4028234663853E37) {
                    d = 3.4028235E37;
                } else if (d == -2.14748E9) {
                    d = -2.147483648E9;
                } else if (d == 3.40282E37) {
                    d = 3.4028235E37;
                }
            }

            return d;
        } catch (NumberFormatException e) {
            throw new SQLException("Bad format for number '" + s + "'");
        }
    }

    /**
     * Sets the first character of the query that this result set was created
     * from.
     *
     * @param c the first character of the query...uppercased
     */
    protected void setFirstCharOfQuery(char c) {
        this.firstCharOfQuery = c;
    }

    /**
     * Returns the first character of the query that this result set was
     * created from.
     *
     * @return the first character of the query...uppercased
     */
    protected char getFirstCharOfQuery() {
        return this.firstCharOfQuery;
    }

    /**
     * Sets the concurrency (JDBC2)
     *
     * @param concurrencyFlag CONCUR_UPDATABLE or CONCUR_READONLY
     */
    protected void setResultSetConcurrency(int concurrencyFlag) {
        resultSetConcurrency = concurrencyFlag;
    }

    /**
     * Sets the result set type for (JDBC2)
     *
     * @param typeFlag SCROLL_SENSITIVE or SCROLL_INSENSITIVE (we only support
     *        SCROLL_INSENSITIVE)
     */
    protected void setResultSetType(int typeFlag) {
        resultSetType = typeFlag;
    }

    /**
     * Sets server info (if any)
     *
     * @param info the server info message
     */
    protected void setServerInfo(String info) {
        this.serverInfo = info;
    }

    /**
     * Returns the server info (if any), or null if none.
     *
     * @return server info created for this ResultSet
     */
    protected String getServerInfo() {
        return this.serverInfo;
    }

    /**
     * Builds a hash between column names and their indices for fast retrieval.
     */
    protected void buildIndexMapping() {
        int numFields = fields.length;
        columnNameToIndex = new HashMap();
        fullColumnNameToIndex = new HashMap();

        // We do this in reverse order, so that the 'first' column
        // with a given name ends up as the final mapping in the
        // hashtable...
        //
        // Quoting the JDBC Spec:
        //
		// "Column names used as input to getter
		// methods are case insensitive. When a getter method is called with a column
		// name and several columns have the same name, the value of the first
		// matching column will be returned. "
        //
        
		for (int i =  numFields-1; i>= 0 ; i--) {
            Integer index = new Integer(i);
            String columnName = fields[i].getName();
            String fullColumnName = fields[i].getFullName();

            if (columnName != null) {
                columnNameToIndex.put(columnName, index);
                columnNameToIndex.put(columnName.toUpperCase(), index);
                columnNameToIndex.put(columnName.toLowerCase(), index);
            }

            if (fullColumnName != null) {
                fullColumnNameToIndex.put(fullColumnName, index);
                fullColumnNameToIndex.put(fullColumnName.toUpperCase(), index);
                fullColumnNameToIndex.put(fullColumnName.toLowerCase(), index);
            }
        }

        // set the flag to prevent rebuilding...
        hasBuiltIndexMapping = true;
    }

    /**
     * Ensures that the result set is not closed
     *
     * @throws SQLException if the result set is closed
     */
    protected void checkClosed() throws SQLException {
        if (isClosed) {
            throw new SQLException("Operation not allowed after ResultSet closed",
                SQLError.SQL_STATE_GENERAL_ERROR);
        }
    }

    /**
     * Ensures that the cursor is positioned on a valid row and that the result
     * set is not closed
     *
     * @throws SQLException if the result set is not in a valid state for
     *         traversal
     */
    protected void checkRowPos() throws SQLException {
        checkClosed();

        if (!rowData.isDynamic() && (rowData.size() == 0)) {
            throw new SQLException("Illegal operation on empty result set",
                SQLError.SQL_STATE_GENERAL_ERROR);
        }

        if (rowData.isBeforeFirst()) {
            throw new SQLException("Before start of result set", SQLError.SQL_STATE_GENERAL_ERROR);
        }

        if (rowData.isAfterLast()) {
            throw new SQLException("After end of result set", SQLError.SQL_STATE_GENERAL_ERROR);
        }
    }

    protected void realClose(boolean closeRowData) throws SQLException {
        try {
            if (closeRowData && (this.rowData != null)) {
                rowData.close();
            }
        } finally {
        	this.defaultTimeZone = null;
            this.rowData = null;
            this.isClosed = true;
            this.fields = null;
        }
    }

    void setStatement(com.mysql.jdbc.Statement stmt) {
        owningStatement = stmt;
    }

    long getUpdateCount() {
        return updateCount;
    }

    long getUpdateID() {
        return updateId;
    }

    boolean reallyResult() {
        return reallyResult;
    }

    /**
     * Get the value of a column in the current row as a java.sql.Time object
     * in the given timezone
     *
     * @param columnIndex the first column is 1, the second is 2...
     * @param tz the Timezone to use
     *
     * @return the column value; null if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    private Time getTimeInternal(int columnIndex, TimeZone tz)
        throws java.sql.SQLException {
        int hr = 0;
        int min = 0;
        int sec = 0;

        try {
            String timeAsString = getString(columnIndex);

            if (timeAsString == null) {
                return null;
            } else {
                int length = timeAsString.length();

                if ((length > 0) && (timeAsString.charAt(0) == '0')
                        && (timeAsString.equals("0000-00-00")
                        || timeAsString.equals("0000-00-00 00:00:00")
                        || timeAsString.equals("00000000000000"))) {
                    wasNullFlag = true;

                    return null;
                }

                Field timeColField = fields[columnIndex - 1];

                if (timeColField.getMysqlType() == MysqlDefs.FIELD_TYPE_TIMESTAMP) {
                    // It's a timestamp
                    switch (length) {
                    case 14:
                    case 12: {
                        hr = Integer.parseInt(timeAsString.substring(length - 6,
                                    length - 4));
                        min = Integer.parseInt(timeAsString.substring(length
                                    - 4, length - 2));
                        sec = Integer.parseInt(timeAsString.substring(length
                                    - 2, length));
                    }

                    break;

                    case 10: {
                        hr = Integer.parseInt(timeAsString.substring(6, 8));
                        min = Integer.parseInt(timeAsString.substring(8, 10));
                        sec = 0;
                    }

                    break;

                    default:
                        throw new SQLException(
                            "Timestamp too small to convert to Time value in column "
                            + columnIndex + "(" + fields[columnIndex - 1]
                            + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
                    } /* endswitch */

                    SQLWarning precisionLost = new SQLWarning(
                            "Precision lost converting TIMESTAMP to Time with getTime() on column "
                            + columnIndex + "(" + fields[columnIndex - 1]
                            + ").");

                    if (warningChain == null) {
                        warningChain = precisionLost;
                    } else {
                        warningChain.setNextWarning(precisionLost);
                    }
                } else if (timeColField.getMysqlType() == MysqlDefs.FIELD_TYPE_DATETIME) {
                    hr = Integer.parseInt(timeAsString.substring(11, 13));
                    min = Integer.parseInt(timeAsString.substring(14, 16));
                    sec = Integer.parseInt(timeAsString.substring(17, 19));

                    SQLWarning precisionLost = new SQLWarning(
                            "Precision lost converting DATETIME to Time with getTime() on column "
                            + columnIndex + "(" + fields[columnIndex - 1]
                            + ").");

                    if (warningChain == null) {
                        warningChain = precisionLost;
                    } else {
                        warningChain.setNextWarning(precisionLost);
                    }
                } else {
                    // convert a String to a Time
                    if ((length != 5) && (length != 8)) {
                        throw new SQLException("Bad format for Time '"
                            + timeAsString + "' in column " + columnIndex + "("
                            + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
                    }

                    hr = Integer.parseInt(timeAsString.substring(0, 2));
                    min = Integer.parseInt(timeAsString.substring(3, 5));
                    sec = (length == 5) ? 0
                                        : Integer.parseInt(timeAsString
                            .substring(6));
                }

                return TimeUtil.changeTimezone(this.connection,
                    fastTimeCreate(null, hr, min, sec),
                    connection.getServerTimezone(), tz);
            }
        } catch (Exception ex) {
            throw new java.sql.SQLException(ex.getClass().getName(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    /**
     * Get the value of a column in the current row as a java.sql.Timestamp
     * object in the given timezone
     *
     * @param columnIndex the first column is 1, the second is 2...
     * @param tz the timezone to use
     *
     * @return the column value; null if SQL NULL
     *
     * @exception java.sql.SQLException if a database access error occurs
     */
    private Timestamp getTimestampInternal(int columnIndex, TimeZone tz)
        throws java.sql.SQLException {
        String timestampValue = getString(columnIndex);

        try {
            if (timestampValue == null) {
                return null;
            } else {
                int length = timestampValue.length();

                if ((length > 0) && (timestampValue.charAt(0) == '0')
                        && (timestampValue.equals("0000-00-00")
                        || timestampValue.equals("0000-00-00 00:00:00")
                        || timestampValue.equals("00000000000000")
                        || timestampValue.equals("0"))) {
                    wasNullFlag = true;

                    return null;
                } else if (fields[columnIndex - 1].getMysqlType() == MysqlDefs.FIELD_TYPE_YEAR) {
                    return TimeUtil.changeTimezone(this.connection,
                        fastTimestampCreate(null,
                            Integer.parseInt(timestampValue.substring(0, 4))
                            - 1900, 0, 1, 0, 0, 0, 0),
                        connection.getServerTimezone(), tz);
                } else {
                    // Convert from TIMESTAMP or DATE
                    switch (length) {
                    case 19: {
                        int year = Integer.parseInt(timestampValue.substring(
                                    0, 4));
                        int month = Integer.parseInt(timestampValue.substring(
                                    5, 7));
                        int day = Integer.parseInt(timestampValue.substring(8,
                                    10));
                        int hour = Integer.parseInt(timestampValue.substring(
                                    11, 13));
                        int minutes = Integer.parseInt(timestampValue.substring(
                                    14, 16));
                        int seconds = Integer.parseInt(timestampValue.substring(
                                    17, 19));

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year - 1900, month - 1,
                                day, hour, minutes, seconds, 0),
                            connection.getServerTimezone(), tz);
                    }

                    case 14: {
                        int year = Integer.parseInt(timestampValue.substring(
                                    0, 4));
                        int month = Integer.parseInt(timestampValue.substring(
                                    4, 6));
                        int day = Integer.parseInt(timestampValue.substring(6, 8));
                        int hour = Integer.parseInt(timestampValue.substring(
                                    8, 10));
                        int minutes = Integer.parseInt(timestampValue.substring(
                                    10, 12));
                        int seconds = Integer.parseInt(timestampValue.substring(
                                    12, 14));

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year - 1900, month - 1,
                                day, hour, minutes, seconds, 0),
                            connection.getServerTimezone(), tz);
                    }

                    case 12: {
                        int year = Integer.parseInt(timestampValue.substring(
                                    0, 2));

                        if (year <= 69) {
                            year = (year + 100);
                        }

                        int month = Integer.parseInt(timestampValue.substring(
                                    2, 4));
                        int day = Integer.parseInt(timestampValue.substring(4, 6));
                        int hour = Integer.parseInt(timestampValue.substring(
                                    6, 8));
                        int minutes = Integer.parseInt(timestampValue.substring(
                                    8, 10));
                        int seconds = Integer.parseInt(timestampValue.substring(
                                    10, 12));

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year, month - 1, day,
                                hour, minutes, seconds, 0),
                            connection.getServerTimezone(), tz);
                    }

                    case 10: {
                        int year;
                        int month;
                        int day;
                        int hour;
                        int minutes;

                        if ((this.fields[columnIndex - 1].getMysqlType() == MysqlDefs.FIELD_TYPE_DATE)
                                || (timestampValue.indexOf("-") != -1)) {
                            year = Integer.parseInt(timestampValue.substring(
                                        0, 4)) - 1900;
                            month = Integer.parseInt(timestampValue.substring(
                                        5, 7));
                            day = Integer.parseInt(timestampValue.substring(8,
                                        10));
                            hour = 0;
                            minutes = 0;
                        } else {
                            year = Integer.parseInt(timestampValue.substring(
                                        0, 2));

                            if (year <= 69) {
                                year = (year + 100);
                            }

                            month = Integer.parseInt(timestampValue.substring(
                                        2, 4));
                            day = Integer.parseInt(timestampValue.substring(4, 6));
                            hour = Integer.parseInt(timestampValue.substring(
                                        6, 8));
                            minutes = Integer.parseInt(timestampValue.substring(
                                        8, 10));
                        }

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year, month - 1, day,
                                hour, minutes, 0, 0),
                            connection.getServerTimezone(), tz);
                    }

                    case 8: {
                    	if (timestampValue.indexOf(":") != -1) {
                    		int hour = Integer.parseInt(timestampValue.substring(
                                    0, 2));
                    		int minutes = Integer.parseInt(timestampValue.substring(
                                    3, 5));
                    		int seconds = Integer.parseInt(timestampValue.substring(6, 8));

                    		return TimeUtil.changeTimezone(this.connection,
                    				fastTimestampCreate(null, 70, 0,
                    						1, hour, minutes, seconds, 0),
											connection.getServerTimezone(), tz);
                    		
                    	} else {
                    		int year = Integer.parseInt(timestampValue.substring(
                                    0, 4));
                    		int month = Integer.parseInt(timestampValue.substring(
                                    4, 6));
                    		int day = Integer.parseInt(timestampValue.substring(6, 8));

                    		return TimeUtil.changeTimezone(this.connection,
                    				fastTimestampCreate(null, year - 1900, month - 1,
                    						day, 0, 0, 0, 0),
											connection.getServerTimezone(), tz);
                    	}
                    }

                    case 6: {
                        int year = Integer.parseInt(timestampValue.substring(
                                    0, 2));

                        if (year <= 69) {
                            year = (year + 100);
                        }

                        int month = Integer.parseInt(timestampValue.substring(
                                    2, 4));
                        int day = Integer.parseInt(timestampValue.substring(4, 6));

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year, month - 1, day, 0,
                                0, 0, 0), connection.getServerTimezone(), tz);
                    }

                    case 4: {
                        int year = Integer.parseInt(timestampValue.substring(
                                    0, 2));

                        if (year <= 69) {
                            year = (year + 100);
                        }

                        int month = Integer.parseInt(timestampValue.substring(
                                    2, 4));

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year, month - 1, 1, 0, 0,
                                0, 0), connection.getServerTimezone(), tz);
                    }

                    case 2: {
                        int year = Integer.parseInt(timestampValue.substring(
                                    0, 2));

                        if (year <= 69) {
                            year = (year + 100);
                        }

                        return TimeUtil.changeTimezone(this.connection,
                            fastTimestampCreate(null, year, 0, 1, 0, 0, 0, 0),
                            connection.getServerTimezone(), tz);
                    }

                    default:
                        throw new java.sql.SQLException(
                            "Bad format for Timestamp '" + timestampValue
                            + "' in column " + columnIndex + "("
                            + fields[columnIndex - 1] + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
                    }
                }
            }
        } catch (Exception e) {
            throw new java.sql.SQLException("Cannot convert value '"
                + timestampValue + "' from column " + columnIndex + "("
                + timestampValue + " ) to TIMESTAMP.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
        }
    }

    private synchronized Date fastDateCreate(Calendar cal, int year, int month,
        int day) {
        if (cal == null) {
            if (this.fastDateCal == null) {
                this.fastDateCal = new GregorianCalendar();

                this.fastDateCal.setTimeZone(this.defaultTimeZone);
            }

            cal = this.fastDateCal;
        }

        cal.clear();
        cal.set(year + 1900, month, day, 0, 0, 0);

        long dateAsMillis = 0;

        try {
            dateAsMillis = cal.getTimeInMillis();
        } catch (IllegalAccessError iae) {
            // Must be on JDK-1.3.1 or older....
            dateAsMillis = cal.getTime().getTime();
        }

        return new Date(dateAsMillis);
    }

    private synchronized Time fastTimeCreate(Calendar cal, int hour,
        int minute, int second) {
        if (cal == null) {
            if (this.fastDateCal == null) {
                this.fastDateCal = new GregorianCalendar();

                this.fastDateCal.setTimeZone(this.defaultTimeZone);
            }

            cal = this.fastDateCal;
        }

        cal.clear();

        // Set 'date' to epoch of Jan 1, 1970
        cal.set(1970, 0, 1, hour, minute, second);

        long timeAsMillis = 0;

        try {
            timeAsMillis = cal.getTimeInMillis();
        } catch (IllegalAccessError iae) {
            // Must be on JDK-1.3.1 or older....
            timeAsMillis = cal.getTime().getTime();
        }

        return new Time(timeAsMillis);
    }

    private synchronized Timestamp fastTimestampCreate(Calendar cal, int year,
        int month, int day, int hour, int minute, int seconds, int secondsPart) {
        if (cal == null) {
            if (this.fastDateCal == null) {
                this.fastDateCal = new GregorianCalendar();

                this.fastDateCal.setTimeZone(this.defaultTimeZone);
            }

            cal = this.fastDateCal;
        }

        cal.clear();
        cal.set(year + 1900, month, day, hour, minute, seconds);

        long tsAsMillis = 0;

        try {
            tsAsMillis = cal.getTimeInMillis();
        } catch (IllegalAccessError iae) {
            // Must be on JDK-1.3.1 or older....
            tsAsMillis = cal.getTime().getTime();
        }

        Timestamp ts = new Timestamp(tsAsMillis);
        ts.setNanos(secondsPart);

        return ts;
    }
}
