/*
 * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * 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, version 2.0,
 * 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.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.cj.jdbc.result;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Array;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLType;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.format.DateTimeParseException;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;

import com.mysql.cj.Messages;
import com.mysql.cj.MysqlType;
import com.mysql.cj.NativeSession;
import com.mysql.cj.Query;
import com.mysql.cj.Session;
import com.mysql.cj.WarningListener;
import com.mysql.cj.conf.PropertyKey;
import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.exceptions.CJException;
import com.mysql.cj.exceptions.ExceptionFactory;
import com.mysql.cj.exceptions.ExceptionInterceptor;
import com.mysql.cj.exceptions.MysqlErrorNumbers;
import com.mysql.cj.jdbc.Blob;
import com.mysql.cj.jdbc.BlobFromLocator;
import com.mysql.cj.jdbc.Clob;
import com.mysql.cj.jdbc.JdbcConnection;
import com.mysql.cj.jdbc.JdbcPreparedStatement;
import com.mysql.cj.jdbc.JdbcStatement;
import com.mysql.cj.jdbc.MysqlSQLXML;
import com.mysql.cj.jdbc.StatementImpl;
import com.mysql.cj.jdbc.exceptions.NotUpdatable;
import com.mysql.cj.jdbc.exceptions.SQLError;
import com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping;
import com.mysql.cj.log.ProfilerEvent;
import com.mysql.cj.log.ProfilerEventHandler;
import com.mysql.cj.protocol.ColumnDefinition;
import com.mysql.cj.protocol.ResultsetRows;
import com.mysql.cj.protocol.a.result.NativeResultset;
import com.mysql.cj.protocol.a.result.OkPacket;
import com.mysql.cj.result.BigDecimalValueFactory;
import com.mysql.cj.result.BinaryStreamValueFactory;
import com.mysql.cj.result.BooleanValueFactory;
import com.mysql.cj.result.ByteValueFactory;
import com.mysql.cj.result.DoubleValueFactory;
import com.mysql.cj.result.Field;
import com.mysql.cj.result.FloatValueFactory;
import com.mysql.cj.result.IntegerValueFactory;
import com.mysql.cj.result.LocalDateTimeValueFactory;
import com.mysql.cj.result.LocalDateValueFactory;
import com.mysql.cj.result.LocalTimeValueFactory;
import com.mysql.cj.result.LongValueFactory;
import com.mysql.cj.result.ShortValueFactory;
import com.mysql.cj.result.SqlDateValueFactory;
import com.mysql.cj.result.SqlTimeValueFactory;
import com.mysql.cj.result.SqlTimestampValueFactory;
import com.mysql.cj.result.StringValueFactory;
import com.mysql.cj.result.ValueFactory;
import com.mysql.cj.util.LogUtils;
import com.mysql.cj.util.StringUtils;

public class ResultSetImpl extends NativeResultset implements ResultSetInternalMethods, WarningListener {

    /** Counter used to generate IDs for profiling. */
    static int resultCounter = 1;

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

    /** Keep track of columns accessed */
    protected boolean[] columnUsed = null;

    /** The Connection instance that created us */
    protected volatile JdbcConnection connection;

    protected NativeSession session = null;

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

    protected ProfilerEventHandler eventSink = null;

    Calendar fastDefaultCal = null;
    Calendar fastClientCal = null;

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

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

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

    /** The statement that created us */
    private com.mysql.cj.jdbc.StatementImpl owningStatement;

    /**
     * StackTrace generated where ResultSet was created... used when profiling
     */
    private String pointOfOrigin;

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

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

    JdbcPreparedStatement statementUsedForFetchingRows;

    protected boolean useUsageAdvisor = false;
    protected boolean gatherPerfMetrics = false;

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

    protected java.sql.Statement wrapperStatement;

    private boolean padCharsWithSpace = false;

    private boolean useColumnNamesInFindColumn;

    private ExceptionInterceptor exceptionInterceptor;

    private ValueFactory<Boolean> booleanValueFactory;
    private ValueFactory<Byte> byteValueFactory;
    private ValueFactory<Short> shortValueFactory;
    private ValueFactory<Integer> integerValueFactory;
    private ValueFactory<Long> longValueFactory;
    private ValueFactory<Float> floatValueFactory;
    private ValueFactory<Double> doubleValueFactory;
    private ValueFactory<BigDecimal> bigDecimalValueFactory;
    private ValueFactory<InputStream> binaryStreamValueFactory;
    private ValueFactory<Time> defaultTimeValueFactory;
    private ValueFactory<Timestamp> defaultTimestampValueFactory;

    private ValueFactory<LocalDate> defaultLocalDateValueFactory;
    private ValueFactory<LocalDateTime> defaultLocalDateTimeValueFactory;
    private ValueFactory<LocalTime> defaultLocalTimeValueFactory;

    protected RuntimeProperty<Boolean> emulateLocators;
    protected boolean yearIsDateType = true;

    /**
     * Create a result set for an executeUpdate statement.
     * 
     * @param ok
     *            {@link OkPacket}
     * @param conn
     *            the Connection that created us.
     * @param creatorStmt
     *            the Statement that created us.
     */
    public ResultSetImpl(OkPacket ok, JdbcConnection conn, StatementImpl creatorStmt) {
        super(ok);

        this.connection = conn;
        this.owningStatement = creatorStmt;

        if (this.connection != null) {
            this.session = (NativeSession) conn.getSession();
            this.exceptionInterceptor = this.connection.getExceptionInterceptor();

            this.padCharsWithSpace = this.connection.getPropertySet().getBooleanProperty(PropertyKey.padCharsWithSpace).getValue();
        }
    }

    /**
     * Creates a new ResultSet object.
     * 
     * @param tuples
     *            actual row data
     * @param conn
     *            the Connection that created us.
     * @param creatorStmt
     *            the Statement that created us.
     * 
     * @throws SQLException
     *             if an error occurs
     */
    public ResultSetImpl(ResultsetRows tuples, JdbcConnection conn, StatementImpl creatorStmt) throws SQLException {
        this.connection = conn;
        this.session = (NativeSession) conn.getSession();
        // TODO which database to use, from connection or from statement?
        this.db = creatorStmt != null ? creatorStmt.getCurrentDatabase() : conn.getDatabase();
        this.owningStatement = creatorStmt;

        this.exceptionInterceptor = this.connection.getExceptionInterceptor();

        PropertySet pset = this.connection.getPropertySet();
        this.emulateLocators = pset.getBooleanProperty(PropertyKey.emulateLocators);
        this.padCharsWithSpace = pset.getBooleanProperty(PropertyKey.padCharsWithSpace).getValue();
        this.yearIsDateType = pset.getBooleanProperty(PropertyKey.yearIsDateType).getValue();
        this.useUsageAdvisor = pset.getBooleanProperty(PropertyKey.useUsageAdvisor).getValue();
        this.gatherPerfMetrics = pset.getBooleanProperty(PropertyKey.gatherPerfMetrics).getValue();

        this.booleanValueFactory = new BooleanValueFactory(pset);
        this.byteValueFactory = new ByteValueFactory(pset);
        this.shortValueFactory = new ShortValueFactory(pset);
        this.integerValueFactory = new IntegerValueFactory(pset);
        this.longValueFactory = new LongValueFactory(pset);
        this.floatValueFactory = new FloatValueFactory(pset);
        this.doubleValueFactory = new DoubleValueFactory(pset);
        this.bigDecimalValueFactory = new BigDecimalValueFactory(pset);
        this.binaryStreamValueFactory = new BinaryStreamValueFactory(pset);

        this.defaultTimeValueFactory = new SqlTimeValueFactory(pset, null, this.session.getServerSession().getServerTimeZone(), this);
        this.defaultTimestampValueFactory = new SqlTimestampValueFactory(pset, null, this.session.getServerSession().getServerTimeZone());

        this.defaultLocalDateValueFactory = new LocalDateValueFactory(pset, this);
        this.defaultLocalTimeValueFactory = new LocalTimeValueFactory(pset, this);
        this.defaultLocalDateTimeValueFactory = new LocalDateTimeValueFactory(pset);

        this.columnDefinition = tuples.getMetadata();
        this.rowData = tuples;
        this.updateCount = this.rowData.size();

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

        this.rowData.setOwner(this);

        if (this.columnDefinition.getFields() != null) {
            initializeWithMetadata();
        } // else called by Connection.initializeResultsMetadataFromCache() when cached

        this.useColumnNamesInFindColumn = pset.getBooleanProperty(PropertyKey.useColumnNamesInFindColumn).getValue();

        setRowPositionValidity();
    }

    @Override
    public void initializeWithMetadata() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            initRowsWithMetadata();

            if (this.useUsageAdvisor) {
                this.columnUsed = new boolean[this.columnDefinition.getFields().length];
                this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable());
                this.resultId = resultCounter++;
                this.eventSink = this.session.getProfilerEventHandler();
            }

            if (this.gatherPerfMetrics) {
                this.session.getProtocol().getMetricsHolder().incrementNumberOfResultSetsCreated();

                Set<String> tableNamesSet = new HashSet<>();

                for (int i = 0; i < this.columnDefinition.getFields().length; i++) {
                    Field f = this.columnDefinition.getFields()[i];

                    String tableName = f.getOriginalTableName();

                    if (tableName == null) {
                        tableName = f.getTableName();
                    }

                    if (tableName != null) {
                        if (this.connection.lowerCaseTableNames()) {
                            // on windows, table names are not case-sensitive;
                            // adjusting names to hit the same keys in tableNamesSet
                            tableName = tableName.toLowerCase();
                        }

                        tableNamesSet.add(tableName);
                    }
                }

                this.session.getProtocol().getMetricsHolder().reportNumberOfTablesAccessed(tableNamesSet.size());
            }
        }
    }

    @Override
    public boolean absolute(int row) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

            boolean b;

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

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

            setRowPositionValidity();

            return b;
        }
    }

    @Override
    public void afterLast() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

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

            setRowPositionValidity();
        }
    }

    @Override
    public void beforeFirst() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

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

            this.rowData.beforeFirst();
            this.thisRow = null;

            setRowPositionValidity();
        }
    }

    @Override
    public void cancelRowUpdates() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    /**
     * Ensures that the result set is not closed
     * 
     * @return connection
     * 
     * @throws SQLException
     *             if the result set is closed
     */
    protected final JdbcConnection checkClosed() throws SQLException {
        JdbcConnection c = this.connection;

        if (c == null) {
            throw SQLError.createSQLException(Messages.getString("ResultSet.Operation_not_allowed_after_ResultSet_closed_144"),
                    MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        }

        return c;
    }

    /**
     * Checks if columnIndex is within the number of columns in this result set.
     * 
     * @param columnIndex
     *            the index to check
     * 
     * @throws SQLException
     *             if the index is out of bounds
     */
    protected final void checkColumnBounds(int columnIndex) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if ((columnIndex < 1)) {
                throw SQLError.createSQLException(
                        Messages.getString("ResultSet.Column_Index_out_of_range_low",
                                new Object[] { Integer.valueOf(columnIndex), Integer.valueOf(this.columnDefinition.getFields().length) }),
                        MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            } else if ((columnIndex > this.columnDefinition.getFields().length)) {
                throw SQLError.createSQLException(
                        Messages.getString("ResultSet.Column_Index_out_of_range_high",
                                new Object[] { Integer.valueOf(columnIndex), Integer.valueOf(this.columnDefinition.getFields().length) }),
                        MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            }

            if (this.useUsageAdvisor) {
                this.columnUsed[columnIndex - 1] = true;
            }
        }
    }

    /**
     * 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 (!this.onValidRow) {
            throw SQLError.createSQLException(this.invalidRowReason, MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        }
    }

    private boolean onValidRow = false;
    private String invalidRowReason = null;

    private void setRowPositionValidity() throws SQLException {
        if (!this.rowData.isDynamic() && (this.rowData.size() == 0)) {
            this.invalidRowReason = Messages.getString("ResultSet.Illegal_operation_on_empty_result_set");
            this.onValidRow = false;
        } else if (this.rowData.isBeforeFirst()) {
            this.invalidRowReason = Messages.getString("ResultSet.Before_start_of_result_set_146");
            this.onValidRow = false;
        } else if (this.rowData.isAfterLast()) {
            this.invalidRowReason = Messages.getString("ResultSet.After_end_of_result_set_148");
            this.onValidRow = false;
        } else {
            this.onValidRow = true;
            this.invalidRowReason = null;
        }
    }

    @Override
    public void clearWarnings() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            this.warningChain = null;
        }
    }

    @Override
    public void close() throws SQLException {
        realClose(true);
    }

    @Override
    public void populateCachedMetaData(CachedResultSetMetaData cachedMetaData) throws SQLException {
        this.columnDefinition.exportTo(cachedMetaData);
        cachedMetaData.setMetadata(getMetaData());
    }

    @Override
    public void deleteRow() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    /*
     * /**
     * TODO: Required by JDBC spec
     */
    /*
     * protected void finalize() throws Throwable {
     * if (!this.isClosed) {
     * realClose(false);
     * }
     * }
     */

    @Override
    public int findColumn(String columnName) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            Integer index = this.columnDefinition.findColumn(columnName, this.useColumnNamesInFindColumn, 1);

            if (index == -1) {
                throw SQLError.createSQLException(
                        Messages.getString("ResultSet.Column____112") + columnName + Messages.getString("ResultSet.___not_found._113"),
                        MysqlErrorNumbers.SQL_STATE_COLUMN_NOT_FOUND, getExceptionInterceptor());
            }

            return index;
        }
    }

    @Override
    public boolean first() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

            boolean b = true;

            if (this.rowData.isEmpty()) {
                b = false;
            } else {
                this.rowData.beforeFirst();
                this.thisRow = this.rowData.next();
            }

            setRowPositionValidity();

            return b;
        }
    }

    @Override
    public Array getArray(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public Array getArray(String colName) throws SQLException {
        return getArray(findColumn(colName));
    }

    @Override
    public InputStream getAsciiStream(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return getBinaryStream(columnIndex);
    }

    @Override
    public InputStream getAsciiStream(String columnName) throws SQLException {
        return getAsciiStream(findColumn(columnName));
    }

    @Override
    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.bigDecimalValueFactory);
    }

    @Deprecated
    @Override
    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        ValueFactory<BigDecimal> vf = new BigDecimalValueFactory(this.session.getPropertySet(), scale);
        vf.setPropertySet(this.connection.getPropertySet());
        return this.thisRow.getValue(columnIndex - 1, vf);
    }

    @Override
    public BigDecimal getBigDecimal(String columnName) throws SQLException {
        return getBigDecimal(findColumn(columnName));
    }

    @Deprecated
    @Override
    public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException {
        return getBigDecimal(findColumn(columnName), scale);
    }

    @Override
    public InputStream getBinaryStream(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.binaryStreamValueFactory);
    }

    @Override
    public InputStream getBinaryStream(String columnName) throws SQLException {
        return getBinaryStream(findColumn(columnName));
    }

    @Override
    public java.sql.Blob getBlob(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        if (this.thisRow.getNull(columnIndex - 1)) {
            return null;
        }

        if (!this.emulateLocators.getValue()) {
            return new Blob(this.thisRow.getBytes(columnIndex - 1), getExceptionInterceptor());
        }

        return new BlobFromLocator(this, columnIndex, getExceptionInterceptor());
    }

    @Override
    public java.sql.Blob getBlob(String colName) throws SQLException {
        return getBlob(findColumn(colName));
    }

    @Override
    public boolean getBoolean(int columnIndex) throws SQLException {
        Boolean res = getObject(columnIndex, Boolean.TYPE);
        return res == null ? false : res;
    }

    @Override
    public boolean getBoolean(String columnName) throws SQLException {
        return getBoolean(findColumn(columnName));
    }

    @Override
    public byte getByte(int columnIndex) throws SQLException {
        Byte res = getObject(columnIndex, Byte.TYPE);
        return res == null ? (byte) 0 : res;
    }

    @Override
    public byte getByte(String columnName) throws SQLException {
        return getByte(findColumn(columnName));
    }

    @Override
    public byte[] getBytes(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getBytes(columnIndex - 1);
    }

    @Override
    public byte[] getBytes(String columnName) throws SQLException {
        return getBytes(findColumn(columnName));
    }

    @Override
    public Reader getCharacterStream(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        InputStream stream = getBinaryStream(columnIndex);

        if (stream == null) {
            return null;
        }

        Field f = this.columnDefinition.getFields()[columnIndex - 1];
        try {
            return new InputStreamReader(stream, f.getEncoding());
        } catch (UnsupportedEncodingException e) {
            SQLException sqlEx = SQLError.createSQLException("Cannot read value with encoding: " + f.getEncoding(), this.exceptionInterceptor);
            sqlEx.initCause(e);
            throw sqlEx;
        }
    }

    @Override
    public Reader getCharacterStream(String columnName) throws SQLException {
        return getCharacterStream(findColumn(columnName));
    }

    @Override
    public java.sql.Clob getClob(int columnIndex) throws SQLException {
        String asString = getStringForClob(columnIndex);

        if (asString == null) {
            return null;
        }

        return new com.mysql.cj.jdbc.Clob(asString, getExceptionInterceptor());
    }

    @Override
    public java.sql.Clob getClob(String colName) throws SQLException {
        return getClob(findColumn(colName));
    }

    @Override
    public Date getDate(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1,
                new SqlDateValueFactory(this.session.getPropertySet(), null, this.session.getServerSession().getDefaultTimeZone(), this));
    }

    @Override
    public Date getDate(int columnIndex, Calendar cal) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, new SqlDateValueFactory(this.session.getPropertySet(), cal,
                cal != null ? cal.getTimeZone() : this.session.getServerSession().getDefaultTimeZone(), this));
    }

    @Override
    public Date getDate(String columnName) throws SQLException {
        return getDate(findColumn(columnName));
    }

    @Override
    public Date getDate(String columnName, Calendar cal) throws SQLException {
        return getDate(findColumn(columnName), cal);
    }

    @Override
    public double getDouble(int columnIndex) throws SQLException {
        Double res = getObject(columnIndex, Double.TYPE);
        return res == null ? (double) 0 : res;
    }

    @Override
    public double getDouble(String columnName) throws SQLException {
        return getDouble(findColumn(columnName));
    }

    @Override
    public float getFloat(int columnIndex) throws SQLException {
        Float res = getObject(columnIndex, Float.TYPE);
        return res == null ? (float) 0 : res;
    }

    @Override
    public float getFloat(String columnName) throws SQLException {
        return getFloat(findColumn(columnName));
    }

    @Override
    public int getInt(int columnIndex) throws SQLException {
        Integer res = getObject(columnIndex, Integer.TYPE);
        return res == null ? 0 : res;
    }

    @Override
    public BigInteger getBigInteger(int columnIndex) throws SQLException {
        String stringVal = getString(columnIndex);
        if (stringVal == null) {
            return null;
        }
        try {
            return new BigInteger(stringVal);
        } catch (NumberFormatException nfe) {
            throw SQLError.createSQLException(
                    Messages.getString("ResultSet.Bad_format_for_BigInteger", new Object[] { Integer.valueOf(columnIndex), stringVal }),
                    MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }
    }

    @Override
    public int getInt(String columnName) throws SQLException {
        return getInt(findColumn(columnName));
    }

    @Override
    public long getLong(int columnIndex) throws SQLException {
        Long res = getObject(columnIndex, Long.TYPE);
        return res == null ? 0L : res;
    }

    @Override
    public long getLong(String columnName) throws SQLException {
        return getLong(findColumn(columnName));
    }

    @Override
    public short getShort(int columnIndex) throws SQLException {
        Short res = getObject(columnIndex, Short.TYPE);
        return res == null ? (short) 0 : res;
    }

    @Override
    public short getShort(String columnName) throws SQLException {
        return getShort(findColumn(columnName));
    }

    @Override
    public String getString(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        Field f = this.columnDefinition.getFields()[columnIndex - 1];
        ValueFactory<String> vf = new StringValueFactory(this.session.getPropertySet());
        String stringVal = this.thisRow.getValue(columnIndex - 1, vf);

        if (this.padCharsWithSpace && stringVal != null && f.getMysqlTypeId() == MysqlType.FIELD_TYPE_STRING) {
            int maxBytesPerChar = this.session.getServerSession().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding());
            int fieldLength = (int) f.getLength() /* safe, bytes in a CHAR <= 1024 */ / maxBytesPerChar; /* safe, this will never be 0 */
            return StringUtils.padString(stringVal, fieldLength);
        }

        return stringVal;
    }

    @Override
    public String getString(String columnName) throws SQLException {
        return getString(findColumn(columnName));
    }

    private String getStringForClob(int columnIndex) throws SQLException {
        String asString = null;

        String forcedEncoding = this.connection.getPropertySet().getStringProperty(PropertyKey.clobCharacterEncoding).getStringValue();

        if (forcedEncoding == null) {
            asString = getString(columnIndex);
        } else {
            byte[] asBytes = getBytes(columnIndex);

            if (asBytes != null) {
                asString = StringUtils.toString(asBytes, forcedEncoding);
            }
        }

        return asString;
    }

    @Override
    public Time getTime(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.defaultTimeValueFactory);
    }

    @Override
    public Time getTime(int columnIndex, Calendar cal) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        ValueFactory<Time> vf = new SqlTimeValueFactory(this.session.getPropertySet(), cal,
                cal != null ? cal.getTimeZone() : this.session.getServerSession().getServerTimeZone());
        return this.thisRow.getValue(columnIndex - 1, vf);
    }

    @Override
    public Time getTime(String columnName) throws SQLException {
        return getTime(findColumn(columnName));
    }

    @Override
    public Time getTime(String columnName, Calendar cal) throws SQLException {
        return getTime(findColumn(columnName), cal);
    }

    @Override
    public Timestamp getTimestamp(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.defaultTimestampValueFactory);
    }

    public LocalDate getLocalDate(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.defaultLocalDateValueFactory);
    }

    public LocalDateTime getLocalDateTime(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.defaultLocalDateTimeValueFactory);
    }

    public LocalTime getLocalTime(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);
        return this.thisRow.getValue(columnIndex - 1, this.defaultLocalTimeValueFactory);
    }

    /*
     * This method is optimized by saving the configuration for the last-used cal/tz. If it's re-used, we don't need to create a new value factory (and thus
     * calendar, etc) instance
     */
    private TimeZone lastTsCustomTz;
    private ValueFactory<Timestamp> customTsVf;

    @Override
    public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        TimeZone tz = cal != null ? cal.getTimeZone() : this.session.getServerSession().getServerTimeZone();
        if (this.customTsVf != null && tz == this.lastTsCustomTz) {
            return this.thisRow.getValue(columnIndex - 1, this.customTsVf);
        }
        ValueFactory<Timestamp> vf = new SqlTimestampValueFactory(this.session.getPropertySet(), cal, tz);
        this.lastTsCustomTz = tz;
        this.customTsVf = vf;
        return this.thisRow.getValue(columnIndex - 1, vf);
    }

    @Override
    public Timestamp getTimestamp(String columnName) throws SQLException {
        return getTimestamp(findColumn(columnName));
    }

    @Override
    public Timestamp getTimestamp(String columnName, Calendar cal) throws SQLException {
        return getTimestamp(findColumn(columnName), cal);
    }

    @Override
    public Reader getNCharacterStream(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        String fieldEncoding = this.columnDefinition.getFields()[columnIndex - 1].getEncoding();
        if (fieldEncoding == null || !fieldEncoding.equals("UTF-8")) {
            throw new SQLException("Can not call getNCharacterStream() when field's charset isn't UTF-8");
        }
        return getCharacterStream(columnIndex);
    }

    @Override
    public Reader getNCharacterStream(String columnName) throws SQLException {
        return getNCharacterStream(findColumn(columnName));
    }

    @Override
    public NClob getNClob(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        String fieldEncoding = this.columnDefinition.getFields()[columnIndex - 1].getEncoding();
        if (fieldEncoding == null || !fieldEncoding.equals("UTF-8")) {
            throw new SQLException("Can not call getNClob() when field's charset isn't UTF-8");
        }

        String asString = getStringForNClob(columnIndex);

        if (asString == null) {
            return null;
        }

        return new com.mysql.cj.jdbc.NClob(asString, getExceptionInterceptor());
    }

    @Override
    public NClob getNClob(String columnName) throws SQLException {
        return getNClob(findColumn(columnName));
    }

    private String getStringForNClob(int columnIndex) throws SQLException {
        String asString = null;

        String forcedEncoding = "UTF-8";

        try {
            byte[] asBytes = getBytes(columnIndex);

            if (asBytes != null) {
                asString = new String(asBytes, forcedEncoding);
            }
        } catch (UnsupportedEncodingException uee) {
            throw SQLError.createSQLException("Unsupported character encoding " + forcedEncoding, MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT,
                    getExceptionInterceptor());
        }

        return asString;
    }

    @Override
    public String getNString(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        String fieldEncoding = this.columnDefinition.getFields()[columnIndex - 1].getEncoding();
        if (fieldEncoding == null || !fieldEncoding.equals("UTF-8")) {
            throw new SQLException("Can not call getNString() when field's charset isn't UTF-8");
        }
        return getString(columnIndex);
    }

    @Override
    public String getNString(String columnName) throws SQLException {
        return getNString(findColumn(columnName));
    }

    @Override
    public int getConcurrency() throws SQLException {
        return (CONCUR_READ_ONLY);
    }

    @Override
    public String getCursorName() throws SQLException {
        throw SQLError.createSQLException(Messages.getString("ResultSet.Positioned_Update_not_supported"), MysqlErrorNumbers.SQL_STATE_DRIVER_NOT_CAPABLE,
                getExceptionInterceptor());
    }

    @Override
    public int getFetchDirection() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            return this.fetchDirection;
        }
    }

    @Override
    public int getFetchSize() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            return this.fetchSize;
        }
    }

    @Override
    public char getFirstCharOfQuery() {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                return this.firstCharOfQuery;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // FIXME: Need to evolve interface
        }
    }

    @Override
    public java.sql.ResultSetMetaData getMetaData() throws SQLException {
        checkClosed();

        return new ResultSetMetaData(this.session, this.columnDefinition.getFields(),
                this.session.getPropertySet().getBooleanProperty(PropertyKey.useOldAliasMetadataBehavior).getValue(), this.yearIsDateType,
                getExceptionInterceptor());
    }

    @Override
    public Object getObject(int columnIndex) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        int columnIndexMinusOne = columnIndex - 1;

        // we can't completely rely on code below because primitives have default values for null (e.g. int->0)
        if (this.thisRow.getNull(columnIndexMinusOne)) {
            return null;
        }

        Field field = this.columnDefinition.getFields()[columnIndexMinusOne];
        switch (field.getMysqlType()) {
            case BIT:
                // TODO Field sets binary and blob flags if the length of BIT field is > 1; is it needed at all?
                if (field.isBinary() || field.isBlob()) {
                    byte[] data = getBytes(columnIndex);

                    if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.autoDeserialize).getValue()) {
                        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 SQLError.createSQLException(Messages.getString("ResultSet.Class_not_found___91") + cnfe.toString()
                                            + Messages.getString("ResultSet._while_reading_serialized_object_92"), getExceptionInterceptor());
                                } catch (IOException ex) {
                                    obj = data; // not serialized?
                                }
                            } else {
                                return getString(columnIndex);
                            }
                        }

                        return obj;
                    }

                    return data;
                }

                return field.isSingleBit() ? Boolean.valueOf(getBoolean(columnIndex)) : getBytes(columnIndex);

            case BOOLEAN:
                return Boolean.valueOf(getBoolean(columnIndex));

            case TINYINT:
                return Integer.valueOf(getByte(columnIndex));

            case TINYINT_UNSIGNED:
            case SMALLINT:
            case SMALLINT_UNSIGNED:
            case MEDIUMINT:
            case MEDIUMINT_UNSIGNED:
            case INT:
                return Integer.valueOf(getInt(columnIndex));

            case INT_UNSIGNED:
            case BIGINT:
                return Long.valueOf(getLong(columnIndex));

            case BIGINT_UNSIGNED:
                return getBigInteger(columnIndex);

            case DECIMAL:
            case DECIMAL_UNSIGNED:
                String stringVal = getString(columnIndex);

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

                    try {
                        return new BigDecimal(stringVal);
                    } catch (NumberFormatException ex) {
                        throw SQLError.createSQLException(
                                Messages.getString("ResultSet.Bad_format_for_BigDecimal", new Object[] { stringVal, Integer.valueOf(columnIndex) }),
                                MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
                    }
                }
                return null;

            case FLOAT:
            case FLOAT_UNSIGNED:
                return new Float(getFloat(columnIndex));

            case DOUBLE:
            case DOUBLE_UNSIGNED:
                return new Double(getDouble(columnIndex));

            case CHAR:
            case ENUM:
            case SET:
            case VARCHAR:
            case TINYTEXT:
                return getString(columnIndex);

            case TEXT:
            case MEDIUMTEXT:
            case LONGTEXT:
            case JSON:
                return getStringForClob(columnIndex);

            case GEOMETRY:
                return getBytes(columnIndex);

            case BINARY:
            case VARBINARY:
            case TINYBLOB:
            case MEDIUMBLOB:
            case LONGBLOB:
            case BLOB:
                if (field.isBinary() || field.isBlob()) {
                    byte[] data = getBytes(columnIndex);

                    if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.autoDeserialize).getValue()) {
                        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 SQLError.createSQLException(Messages.getString("ResultSet.Class_not_found___91") + cnfe.toString()
                                            + Messages.getString("ResultSet._while_reading_serialized_object_92"), getExceptionInterceptor());
                                } catch (IOException ex) {
                                    obj = data; // not serialized?
                                }
                            } else {
                                return getString(columnIndex);
                            }
                        }

                        return obj;
                    }

                    return data;
                }

                return getBytes(columnIndex);

            case YEAR:
                return this.yearIsDateType ? getDate(columnIndex) : Short.valueOf(getShort(columnIndex));

            case DATE:
                return getDate(columnIndex);

            case TIME:
                return getTime(columnIndex);

            case TIMESTAMP:
            case DATETIME:
                return getTimestamp(columnIndex);

            default:
                return getString(columnIndex);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
        if (type == null) {
            throw SQLError.createSQLException("Type parameter can not be null", MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }

        synchronized (checkClosed().getConnectionMutex()) {
            if (type.equals(String.class)) {
                return (T) getString(columnIndex);

            } else if (type.equals(BigDecimal.class)) {
                return (T) getBigDecimal(columnIndex);

            } else if (type.equals(BigInteger.class)) {
                return (T) getBigInteger(columnIndex);

            } else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.booleanValueFactory);

            } else if (type.equals(Byte.class) || type.equals(Byte.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.byteValueFactory);

            } else if (type.equals(Short.class) || type.equals(Short.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.shortValueFactory);

            } else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.integerValueFactory);

            } else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.longValueFactory);

            } else if (type.equals(Float.class) || type.equals(Float.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.floatValueFactory);

            } else if (type.equals(Double.class) || type.equals(Double.TYPE)) {
                checkRowPos();
                checkColumnBounds(columnIndex);
                return (T) this.thisRow.getValue(columnIndex - 1, this.doubleValueFactory);

            } else if (type.equals(byte[].class)) {
                return (T) getBytes(columnIndex);

            } else if (type.equals(Date.class)) {
                return (T) getDate(columnIndex);

            } else if (type.equals(Time.class)) {
                return (T) getTime(columnIndex);

            } else if (type.equals(Timestamp.class)) {
                return (T) getTimestamp(columnIndex);

            } else if (type.equals(Clob.class)) {
                return (T) getClob(columnIndex);

            } else if (type.equals(Blob.class)) {
                return (T) getBlob(columnIndex);

            } else if (type.equals(Array.class)) {
                return (T) getArray(columnIndex);

            } else if (type.equals(Ref.class)) {
                return (T) getRef(columnIndex);

            } else if (type.equals(URL.class)) {
                return (T) getURL(columnIndex);

            } else if (type.equals(Struct.class)) {
                throw new SQLFeatureNotSupportedException();

            } else if (type.equals(RowId.class)) {
                return (T) getRowId(columnIndex);

            } else if (type.equals(NClob.class)) {
                return (T) getNClob(columnIndex);

            } else if (type.equals(SQLXML.class)) {
                return (T) getSQLXML(columnIndex);

            } else if (type.equals(LocalDate.class)) {
                return (T) getLocalDate(columnIndex);

            } else if (type.equals(LocalDateTime.class)) {
                return (T) getLocalDateTime(columnIndex);

            } else if (type.equals(LocalTime.class)) {
                return (T) getLocalTime(columnIndex);

            } else if (type.equals(OffsetDateTime.class)) {
                try {
                    String odt = getString(columnIndex);
                    return odt == null ? null : (T) OffsetDateTime.parse(odt);
                } catch (DateTimeParseException e) {
                    // Let it continue and try by object deserialization.
                }

            } else if (type.equals(OffsetTime.class)) {
                try {
                    String ot = getString(columnIndex);
                    return ot == null ? null : (T) OffsetTime.parse(getString(columnIndex));
                } catch (DateTimeParseException e) {
                    // Let it continue and try by object deserialization.
                }

            }

            if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.autoDeserialize).getValue()) {
                try {
                    return (T) getObject(columnIndex);
                } catch (ClassCastException cce) {
                    SQLException sqlEx = SQLError.createSQLException("Conversion not supported for type " + type.getName(),
                            MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
                    sqlEx.initCause(cce);

                    throw sqlEx;
                }
            }

            throw SQLError.createSQLException("Conversion not supported for type " + type.getName(), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT,
                    getExceptionInterceptor());
        }
    }

    @Override
    public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
        return getObject(findColumn(columnLabel), type);
    }

    @Override
    public Object getObject(int i, java.util.Map<String, Class<?>> map) throws SQLException {
        return getObject(i);
    }

    @Override
    public Object getObject(String columnName) throws SQLException {
        return getObject(findColumn(columnName));
    }

    @Override
    public Object getObject(String colName, java.util.Map<String, Class<?>> map) throws SQLException {
        return getObject(findColumn(colName), map);
    }

    @Override
    public Object getObjectStoredProc(int columnIndex, int desiredSqlType) throws SQLException {
        checkRowPos();
        checkColumnBounds(columnIndex);

        Object value = this.thisRow.getBytes(columnIndex - 1);

        if (value == null) {
            return null;
        }

        Field field = this.columnDefinition.getFields()[columnIndex - 1];

        MysqlType desiredMysqlType = MysqlType.getByJdbcType(desiredSqlType);

        switch (desiredMysqlType) {
            case BIT:
            case BOOLEAN:
                return Boolean.valueOf(getBoolean(columnIndex));

            case TINYINT:
            case TINYINT_UNSIGNED:
                return Integer.valueOf(getInt(columnIndex));

            case SMALLINT:
            case SMALLINT_UNSIGNED:
                return Integer.valueOf(getInt(columnIndex));

            case INT:
            case INT_UNSIGNED:
            case MEDIUMINT:
            case MEDIUMINT_UNSIGNED:
                if (!field.isUnsigned() || field.getMysqlTypeId() == MysqlType.FIELD_TYPE_INT24) {
                    return Integer.valueOf(getInt(columnIndex));
                }
                return Long.valueOf(getLong(columnIndex));

            case BIGINT:
                return Long.valueOf(getLong(columnIndex));

            case BIGINT_UNSIGNED:
                return getBigInteger(columnIndex);

            case DECIMAL:
            case DECIMAL_UNSIGNED:
                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 SQLError.createSQLException(
                                Messages.getString("ResultSet.Bad_format_for_BigDecimal", new Object[] { stringVal, Integer.valueOf(columnIndex) }),
                                MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
                    }

                    return val;
                }

                return null;

            case FLOAT:
            case FLOAT_UNSIGNED:
                return new Float(getFloat(columnIndex));

            case DOUBLE:
            case DOUBLE_UNSIGNED:
                return new Double(getDouble(columnIndex));

            case CHAR:
            case ENUM:
            case SET:
            case VARCHAR:
            case TINYTEXT:
                return getString(columnIndex);

            case JSON:
            case TEXT:
            case MEDIUMTEXT:
            case LONGTEXT:
                return getStringForClob(columnIndex);

            case BINARY:
            case GEOMETRY:
            case VARBINARY:
            case TINYBLOB:
            case BLOB:
            case MEDIUMBLOB:
            case LONGBLOB:
                return getBytes(columnIndex);

            case YEAR:
            case DATE:
                if (field.getMysqlType() == MysqlType.YEAR && !this.yearIsDateType) {
                    return Short.valueOf(getShort(columnIndex));
                }

                return getDate(columnIndex);

            case TIME:
                return getTime(columnIndex);

            case TIMESTAMP:
                return getTimestamp(columnIndex);

            default:
                return getString(columnIndex);
        }
    }

    @Override
    public Object getObjectStoredProc(int i, java.util.Map<Object, Object> map, int desiredSqlType) throws SQLException {
        return getObjectStoredProc(i, desiredSqlType);
    }

    @Override
    public Object getObjectStoredProc(String columnName, int desiredSqlType) throws SQLException {
        return getObjectStoredProc(findColumn(columnName), desiredSqlType);
    }

    @Override
    public Object getObjectStoredProc(String colName, java.util.Map<Object, Object> map, int desiredSqlType) throws SQLException {
        return getObjectStoredProc(findColumn(colName), map, desiredSqlType);
    }

    @Override
    public java.sql.Ref getRef(int i) throws SQLException {
        checkColumnBounds(i);
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public java.sql.Ref getRef(String colName) throws SQLException {
        return getRef(findColumn(colName));
    }

    @Override
    public int getRow() throws SQLException {
        checkClosed();

        int currentRowNumber = this.rowData.getPosition();
        int row = 0;

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

        return row;
    }

    @Override
    public java.sql.Statement getStatement() throws SQLException {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                if (this.wrapperStatement != null) {
                    return this.wrapperStatement;
                }

                return this.owningStatement;
            }

        } catch (SQLException sqlEx) {
            throw SQLError.createSQLException("Operation not allowed on closed ResultSet.", MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR,
                    getExceptionInterceptor());
        }

    }

    @Override
    public int getType() throws SQLException {
        return this.resultSetType;
    }

    @Deprecated
    @Override
    public InputStream getUnicodeStream(int columnIndex) throws SQLException {
        checkRowPos();

        return getBinaryStream(columnIndex);
    }

    @Deprecated
    @Override
    public InputStream getUnicodeStream(String columnName) throws SQLException {
        return getUnicodeStream(findColumn(columnName));
    }

    @Override
    public URL getURL(int colIndex) throws SQLException {
        String val = getString(colIndex);

        if (val == null) {
            return null;
        }

        try {
            return new URL(val);
        } catch (MalformedURLException mfe) {
            throw SQLError.createSQLException(Messages.getString("ResultSet.Malformed_URL____104") + val + "'", MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT,
                    getExceptionInterceptor());
        }
    }

    @Override
    public URL getURL(String colName) throws SQLException {
        String val = getString(colName);

        if (val == null) {
            return null;
        }

        try {
            return new URL(val);
        } catch (MalformedURLException mfe) {
            throw SQLError.createSQLException(Messages.getString("ResultSet.Malformed_URL____107") + val + "'", MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT,
                    getExceptionInterceptor());
        }
    }

    @Override
    public java.sql.SQLWarning getWarnings() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            return this.warningChain;
        }
    }

    @Override
    public void insertRow() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public boolean isAfterLast() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            boolean b = this.rowData.isAfterLast();

            return b;
        }
    }

    @Override
    public boolean isBeforeFirst() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            return this.rowData.isBeforeFirst();
        }
    }

    @Override
    public boolean isFirst() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            return this.rowData.isFirst();
        }
    }

    @Override
    public boolean isLast() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            return this.rowData.isLast();
        }
    }

    @Override
    public boolean last() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

            boolean b = true;

            if (this.rowData.size() == 0) {
                b = false;
            } else {
                this.rowData.beforeLast();
                this.thisRow = this.rowData.next();
            }

            setRowPositionValidity();

            return b;
        }
    }

    @Override
    public void moveToCurrentRow() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void moveToInsertRow() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public boolean next() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {

            boolean b;

            if (!hasRows()) {
                throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"),
                        MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
            }

            if (this.rowData.size() == 0) {
                b = false;
            } else {
                this.thisRow = this.rowData.next();

                if (this.thisRow == null) {
                    b = false;
                } else {
                    clearWarnings();

                    b = true;

                }
            }

            setRowPositionValidity();

            return b;
        }
    }

    /**
     * The <i>prev</i> 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 {
        synchronized (checkClosed().getConnectionMutex()) {

            int rowIndex = this.rowData.getPosition();

            boolean b = true;

            if ((rowIndex - 1) >= 0) {
                rowIndex--;
                this.rowData.setCurrentRow(rowIndex);
                this.thisRow = this.rowData.get(rowIndex);

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

                b = false;
            } else {
                b = false;
            }

            setRowPositionValidity();

            return b;
        }
    }

    @Override
    public boolean previous() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

            return prev();
        }
    }

    @Override
    public void realClose(boolean calledExplicitly) throws SQLException {
        JdbcConnection locallyScopedConn = this.connection;

        if (locallyScopedConn == null) {
            return; // already closed
        }

        synchronized (locallyScopedConn.getConnectionMutex()) {
            // additional check in case ResultSet was closed while current thread was waiting for lock
            if (this.isClosed) {
                return;
            }

            try {
                if (this.useUsageAdvisor) {

                    if (!calledExplicitly) {
                        this.eventSink.processEvent(ProfilerEvent.TYPE_USAGE, this.session, this.owningStatement, this, 0, new Throwable(),
                                Messages.getString("ResultSet.ResultSet_implicitly_closed_by_driver"));
                    }

                    int resultSetSizeThreshold = locallyScopedConn.getPropertySet().getIntegerProperty(PropertyKey.resultSetSizeThreshold).getValue();
                    if (this.rowData.size() > resultSetSizeThreshold) {
                        this.eventSink.processEvent(ProfilerEvent.TYPE_USAGE, this.session, this.owningStatement, this, 0, new Throwable(),
                                Messages.getString("ResultSet.Too_Large_Result_Set",
                                        new Object[] { Integer.valueOf(this.rowData.size()), Integer.valueOf(resultSetSizeThreshold) }));
                    }

                    if (!isLast() && !isAfterLast() && (this.rowData.size() != 0)) {
                        this.eventSink.processEvent(ProfilerEvent.TYPE_USAGE, this.session, this.owningStatement, this, 0, new Throwable(),
                                Messages.getString("ResultSet.Possible_incomplete_traversal_of_result_set",
                                        new Object[] { Integer.valueOf(getRow()), Integer.valueOf(this.rowData.size()) }));
                    }

                    // Report on any columns that were selected but not referenced
                    if (this.columnUsed.length > 0 && !this.rowData.wasEmpty()) {
                        StringBuilder buf = new StringBuilder();
                        for (int i = 0; i < this.columnUsed.length; i++) {
                            if (!this.columnUsed[i]) {
                                if (buf.length() > 0) {
                                    buf.append(", ");
                                }
                                buf.append(this.columnDefinition.getFields()[i].getFullName());
                            }
                        }
                        if (buf.length() > 0) {
                            this.eventSink.processEvent(ProfilerEvent.TYPE_USAGE, this.session, this.owningStatement, this, 0, new Throwable(),
                                    Messages.getString("ResultSet.The_following_columns_were_never_referenced", new String[] { buf.toString() }));
                        }
                    }
                }
            } finally {
                if (this.owningStatement != null && calledExplicitly) {
                    this.owningStatement.removeOpenResultSet(this);
                }

                SQLException exceptionDuringClose = null;

                if (this.rowData != null) {
                    try {
                        this.rowData.close();
                    } catch (CJException sqlEx) {
                        exceptionDuringClose = SQLExceptionsMapping.translateException(sqlEx);
                    }
                }

                if (this.statementUsedForFetchingRows != null) {
                    try {
                        this.statementUsedForFetchingRows.realClose(true, false);
                    } catch (SQLException sqlEx) {
                        if (exceptionDuringClose != null) {
                            exceptionDuringClose.setNextException(sqlEx);
                        } else {
                            exceptionDuringClose = sqlEx;
                        }
                    }
                }

                this.rowData = null;
                this.columnDefinition = null;
                this.eventSink = null;
                this.warningChain = null;
                this.owningStatement = null;
                this.db = null;
                this.serverInfo = null;
                this.thisRow = null;
                this.fastDefaultCal = null;
                this.fastClientCal = null;
                this.connection = null;
                this.session = null;

                this.isClosed = true;

                if (exceptionDuringClose != null) {
                    throw exceptionDuringClose;
                }
            }
        }
    }

    @Override
    public boolean isClosed() throws SQLException {
        return this.isClosed;
    }

    @Override
    public void refreshRow() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public boolean relative(int rows) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (getType() == ResultSet.TYPE_FORWARD_ONLY) {
                throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly"));
            }

            if (this.rowData.size() == 0) {
                setRowPositionValidity();

                return false;
            }

            this.rowData.moveRowRelative(rows);
            this.thisRow = this.rowData.get(this.rowData.getPosition());

            setRowPositionValidity();

            return (!this.rowData.isAfterLast() && !this.rowData.isBeforeFirst());
        }
    }

    @Override
    public boolean rowDeleted() throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public boolean rowInserted() throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public boolean rowUpdated() throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void setFetchDirection(int direction) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if ((direction != FETCH_FORWARD) && (direction != FETCH_REVERSE) && (direction != FETCH_UNKNOWN)) {
                throw SQLError.createSQLException(Messages.getString("ResultSet.Illegal_value_for_fetch_direction_64"),
                        MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            }

            if (getType() == ResultSet.TYPE_FORWARD_ONLY && direction != FETCH_FORWARD) {
                String constName = direction == ResultSet.FETCH_REVERSE ? "ResultSet.FETCH_REVERSE" : "ResultSet.FETCH_UNKNOWN";
                throw ExceptionFactory.createException(Messages.getString("ResultSet.Unacceptable_value_for_fetch_direction", new Object[] { constName }));
            }

            this.fetchDirection = direction;
        }
    }

    @Override
    public void setFetchSize(int rows) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (rows < 0) { /* || rows > getMaxRows() */
                throw SQLError.createSQLException(Messages.getString("ResultSet.Value_must_be_between_0_and_getMaxRows()_66"),
                        MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            }

            this.fetchSize = rows;
        }
    }

    @Override
    public void setFirstCharOfQuery(char c) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.firstCharOfQuery = c;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // FIXME: Need to evolve public interface
        }
    }

    @Override
    public void setOwningStatement(JdbcStatement owningStatement) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.owningStatement = (StatementImpl) owningStatement;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // FIXME: Need to evolve public interface
        }
    }

    /**
     * Sets the concurrency
     * 
     * @param concurrencyFlag
     *            CONCUR_UPDATABLE or CONCUR_READONLY
     */
    public synchronized void setResultSetConcurrency(int concurrencyFlag) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.resultSetConcurrency = concurrencyFlag;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // TODO: FIXME: Need to evolve public interface
        }
    }

    /**
     * Sets the result set type
     * 
     * @param typeFlag
     *            SCROLL_SENSITIVE or SCROLL_INSENSITIVE (we only support
     *            SCROLL_INSENSITIVE)
     */
    public synchronized void setResultSetType(int typeFlag) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.resultSetType = typeFlag;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // TODO: FIXME: Need to evolve public interface
        }
    }

    /**
     * Sets server info (if any)
     * 
     * @param info
     *            the server info message
     */
    public void setServerInfo(String info) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.serverInfo = info;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // TODO: FIXME: Need to evolve public interface
        }
    }

    @Override
    public synchronized void setStatementUsedForFetchingRows(JdbcPreparedStatement stmt) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.statementUsedForFetchingRows = stmt;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // TODO: FIXME: Need to evolve public interface
        }
    }

    @Override
    public synchronized void setWrapperStatement(java.sql.Statement wrapperStatement) {
        try {
            synchronized (checkClosed().getConnectionMutex()) {
                this.wrapperStatement = wrapperStatement;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e); // TODO: FIXME: Need to evolve public interface
        }
    }

    @Override
    public String toString() {
        return hasRows() ? super.toString() : "Result set representing update count of " + this.updateCount;
    }

    @Override
    public void updateArray(int columnIndex, Array arg1) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateArray(String columnLabel, Array arg1) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateAsciiStream(String columnName, InputStream x, int length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBigDecimal(String columnName, BigDecimal x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBinaryStream(String columnName, InputStream x, int length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBlob(int columnIndex, java.sql.Blob arg1) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBlob(String columnLabel, java.sql.Blob arg1) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));

    }

    @Override
    public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBoolean(int columnIndex, boolean x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBoolean(String columnName, boolean x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateByte(int columnIndex, byte x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateByte(String columnName, byte x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBytes(int columnIndex, byte[] x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateBytes(String columnName, byte[] x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateCharacterStream(String columnName, Reader reader, int length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateClob(int columnIndex, java.sql.Clob arg1) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateClob(String columnName, java.sql.Clob clob) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateClob(int columnIndex, Reader reader) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateClob(String columnLabel, Reader reader) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateDate(int columnIndex, java.sql.Date x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateDate(String columnName, java.sql.Date x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateDouble(int columnIndex, double x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateDouble(String columnName, double x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateFloat(int columnIndex, float x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateFloat(String columnName, float x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateInt(int columnIndex, int x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateInt(String columnName, int x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateLong(int columnIndex, long x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateLong(String columnName, long x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNClob(String columnName, NClob nClob) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNClob(int columnIndex, Reader reader) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNClob(String columnLabel, Reader reader) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNull(int columnIndex) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNull(String columnName) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNString(int columnIndex, String nString) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateNString(String columnLabel, String nString) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(int columnIndex, Object x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(String columnName, Object x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(int columnIndex, Object x, int scale) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(String columnName, Object x, int scale) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(String columnLabel, Object x, SQLType targetSqlType) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateObject(String columnLabel, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateRef(int columnIndex, Ref arg1) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateRef(String columnLabel, Ref arg1) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateRow() throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateRowId(int columnIndex, RowId x) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateRowId(String columnName, RowId x) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public void updateShort(int columnIndex, short x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateShort(String columnName, short x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateString(int columnIndex, String x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateString(String columnName, String x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateTime(int columnIndex, java.sql.Time x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateTime(String columnName, java.sql.Time x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateTimestamp(int columnIndex, java.sql.Timestamp x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public void updateTimestamp(String columnName, java.sql.Timestamp x) throws SQLException {
        throw new NotUpdatable(Messages.getString("NotUpdatable.0"));
    }

    @Override
    public boolean wasNull() throws SQLException {
        return this.thisRow.wasNull();
    }

    protected ExceptionInterceptor getExceptionInterceptor() {
        return this.exceptionInterceptor;
    }

    @Override
    public int getHoldability() throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public RowId getRowId(int columnIndex) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public RowId getRowId(String columnLabel) throws SQLException {
        throw SQLError.createSQLFeatureNotSupportedException();
    }

    @Override
    public SQLXML getSQLXML(int columnIndex) throws SQLException {
        checkColumnBounds(columnIndex);

        return new MysqlSQLXML(this, columnIndex, getExceptionInterceptor());
    }

    @Override
    public SQLXML getSQLXML(String columnLabel) throws SQLException {
        return getSQLXML(findColumn(columnLabel));
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        checkClosed();

        // This works for classes that aren't actually wrapping anything
        return iface.isInstance(this);
    }

    @Override
    public <T> T unwrap(java.lang.Class<T> iface) throws java.sql.SQLException {
        try {
            // This works for classes that aren't actually wrapping anything
            return iface.cast(this);
        } catch (ClassCastException cce) {
            throw SQLError.createSQLException(Messages.getString("Common.UnableToUnwrap", new Object[] { iface.toString() }),
                    MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }
    }

    /**
     * Accumulate internal warnings as the SQLWarning chain.
     */
    @Override
    public synchronized void warningEncountered(String warning) {
        SQLWarning w = new SQLWarning(warning);
        if (this.warningChain == null) {
            this.warningChain = w;
        } else {
            this.warningChain.setNextWarning(w);
        }
    }

    public ColumnDefinition getMetadata() {
        return this.columnDefinition;
    }

    public com.mysql.cj.jdbc.StatementImpl getOwningStatement() {
        return this.owningStatement;
    }

    @Override
    public void closeOwner(boolean calledExplicitly) {
        try {
            realClose(calledExplicitly);
        } catch (SQLException e) {
            throw ExceptionFactory.createException(e.getMessage(), e);
        }
    }

    @Override
    public JdbcConnection getConnection() {
        return this.connection;
    }

    @Override
    public Session getSession() {
        return this.connection != null ? this.connection.getSession() : null;
    }

    @Override
    public String getPointOfOrigin() {
        return this.pointOfOrigin;
    }

    @Override
    public int getOwnerFetchSize() {
        try {
            return getFetchSize();
        } catch (SQLException e) {
            throw ExceptionFactory.createException(e.getMessage(), e);
        }
    }

    @Override
    public Query getOwningQuery() {
        return this.owningStatement;
    }

    @Override
    public int getOwningStatementMaxRows() {
        return this.owningStatement == null ? -1 : this.owningStatement.maxRows;
    }

    @Override
    public int getOwningStatementFetchSize() {
        try {
            return this.owningStatement == null ? 0 : this.owningStatement.getFetchSize();
        } catch (SQLException e) {
            throw ExceptionFactory.createException(e.getMessage(), e);
        }
    }

    @Override
    public long getOwningStatementServerId() {
        return this.owningStatement == null ? 0 : this.owningStatement.getServerStatementId();
    }

    @Override
    public Object getSyncMutex() {
        return this.connection != null ? this.connection.getConnectionMutex() : null;
    }

}
