/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */

package com.pivotal.gemfirexd.thrift.internal;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import com.gemstone.gemfire.internal.shared.ReverseListIterator;
import com.gemstone.gnu.trove.TObjectIntHashMap;
import com.pivotal.gemfirexd.internal.shared.common.reference.SQLState;
import com.pivotal.gemfirexd.thrift.ColumnDescriptor;
import com.pivotal.gemfirexd.thrift.GFXDException;
import com.pivotal.gemfirexd.thrift.GFXDExceptionData;
import com.pivotal.gemfirexd.thrift.GFXDType;
import com.pivotal.gemfirexd.thrift.Row;
import com.pivotal.gemfirexd.thrift.RowSet;
import com.pivotal.gemfirexd.thrift.StatementAttrs;
import com.pivotal.gemfirexd.thrift.gfxdConstants;
import com.pivotal.gemfirexd.thrift.common.ColumnValueConverter;
import com.pivotal.gemfirexd.thrift.common.Converters;
import com.pivotal.gemfirexd.thrift.common.ThriftExceptionUtil;

/**
 * @author swale
 * @since gfxd 1.1
 */
public final class ClientResultSet extends ClientFetchColumnValue implements
    ResultSet {

  private final ClientStatement statement;
  private final StatementAttrs attrs;
  private int cursorId;
  private RowSet rowSet;
  private int numColumns;
  private ListIterator<Row> rowsIter;
  private Row currentRow;
  private TObjectIntHashMap columnNameToIndex;
  private int fetchDirection;
  private int fetchSize;
  private Row changedRow;
  private Row insertRow;
  private BitSet changedColumns;
  private byte cursorOperationType;

  ClientResultSet(ClientConnection conn, StatementAttrs attrs, RowSet rs) {
    this(conn, null, attrs, rs);
  }

  ClientResultSet(ClientConnection conn, ClientStatement statement, RowSet rs) {
    this(conn, statement, statement.attrs, rs);
  }

  private ClientResultSet(ClientConnection conn, ClientStatement statement,
      StatementAttrs attrs, RowSet rs) {
    super(conn.clientService, rs.cursorId != gfxdConstants.INVALID_ID
        ? gfxdConstants.BULK_CLOSE_RESULTSET : gfxdConstants.INVALID_ID);
    this.statement = statement;
    this.attrs = attrs;
    this.rowsIter = rs.rows.listIterator();
    this.fetchDirection = attrs.fetchReverse ? FETCH_REVERSE : FETCH_FORWARD;
    this.fetchSize = attrs.batchSize;
    initRowSet(rs);
  }

  private final void clearForPositioning() {
    this.currentRow = null;
    super.reset();
  }

  private final void initRowSet(RowSet rs) {
    // copy metadata if not set
    if (rs.metadata == null) {
      rs.setMetadata(this.rowSet.metadata);
    }
    this.rowSet = rs;
    this.numColumns = rs.metadata.size();
    this.cursorId = rs.cursorId;
    setCurrentSource(gfxdConstants.BULK_CLOSE_RESULTSET, rs.cursorId, rs);
  }

  /**
   * Fetch a row set at given position in terms of number of rows having
   * relative or absolute position. If the position is relative, then fetch the
   * row set starting at the given number of rows relative to the current
   * position of the local cursor (and not the remote cursor).
   * <p>
   * This method assumes that caller has already searched in the current row
   * set, and invoking this means that the rows at given position is not present
   * locally and has to be fetched from server.
   */
  private final boolean fetchRowSet(boolean isAbsolute, int rowPosition,
      boolean fetchReverse) throws SQLException {
    assert rowPosition != 0;

    RowSet rs;
    try {
      final int offset = fetchReverse ? (rowPosition + 1) : (rowPosition - 1);
      rs = this.service.scrollCursor(getLobSource(true, "scrollCursor"),
          this.cursorId, offset, isAbsolute, fetchReverse, this.fetchSize);
    } catch (GFXDException gfxde) {
      throw ThriftExceptionUtil.newSQLException(gfxde);
    }
    if (rs != null) {
      if (fetchReverse) {
        this.rowsIter = new ReverseListIterator<Row>(rs.rows, 0);
      }
      else {
        this.rowsIter = rs.rows.listIterator();
      }
      initRowSet(rs);
      return true;
    }
    else {
      // position before first/last row as per JDBC requirement
      if (rowPosition > 0) {
        this.rowsIter = this.rowSet.rows
            .listIterator(this.rowSet.rows.size());
      }
      else {
        this.rowsIter = this.rowSet.rows.listIterator();
      }
      return false;
    }
  }

  final void checkClosed() throws SQLException {
    if (this.service.isOpen) {
      if (this.rowSet != null) {
        return;
      }
      else {
        throw ThriftExceptionUtil
            .newSQLException(SQLState.CLIENT_RESULT_SET_NOT_OPEN);
      }
    }
    else {
      this.rowSet = null;
      this.rowsIter = null;
      this.currentRow = null;
      throw ThriftExceptionUtil
          .newSQLException(SQLState.NO_CURRENT_CONNECTION);
    }
  }

  final void checkOnRow() throws SQLException {
    if (this.currentRow != null) {
      return;
    }
    else {
      throw nullRowException();
    }
  }

  final SQLException nullRowException() {
    if (this.rowSet == null) {
      return ThriftExceptionUtil
          .newSQLException(SQLState.CLIENT_RESULT_SET_NOT_OPEN);
    }
    else {
      return ThriftExceptionUtil.newSQLException(SQLState.NO_CURRENT_ROW);
    }
  }

  final Row checkValidColumn(int columnIndex) throws SQLException {
    final Row currentRow = this.currentRow;
    if (currentRow != null) {
      if ((columnIndex >= 1) & (columnIndex <= this.numColumns)) {
        return currentRow;
      }
      else {
        throw ThriftExceptionUtil.newSQLException(
            SQLState.LANG_INVALID_COLUMN_POSITION, null, columnIndex,
            this.numColumns);
      }
    }
    else {
      throw nullRowException();
    }
  }

  final void checkScrollable() throws SQLException {
    if (this.attrs.resultSetType != gfxdConstants.RESULTSET_TYPE_FORWARD_ONLY) {
      return;
    }
    else {
      throw ThriftExceptionUtil
          .newSQLException(SQLState.CURSOR_MUST_BE_SCROLLABLE);
    }
  }

  final void checkUpdatable(String operation) throws SQLException {
    if (this.attrs.updatable && this.cursorId != gfxdConstants.INVALID_ID) {
      checkOnRow();
    }
    else {
      throw ThriftExceptionUtil.newSQLException(
          SQLState.UPDATABLE_RESULTSET_API_DISALLOWED, null, operation);
    }
  }

  final GFXDType getSQLType(int columnIndex) {
    return this.rowSet.metadata.get(columnIndex - 1).type;
  }

  final int getColumnIndex(final String columnName) throws SQLException {
    if (columnName != null) {
      if (this.columnNameToIndex == null) {
        checkClosed();
        this.columnNameToIndex = buildColumnNameToIndex(this.rowSet.metadata);
      }
      int index = this.columnNameToIndex.get(columnName);
      if (index > 0) {
        return index;
      }
      else {
        throw ThriftExceptionUtil.newSQLException(SQLState.COLUMN_NOT_FOUND,
            null, columnName);
      }
    }
    else {
      throw ThriftExceptionUtil.newSQLException(SQLState.NULL_COLUMN_NAME);
    }
  }

  final static TObjectIntHashMap buildColumnNameToIndex(
      final List<ColumnDescriptor> metadata) {
    final int size = metadata.size();
    final TObjectIntHashMap columnNameToIndex = new TObjectIntHashMap(size);
    ListIterator<ColumnDescriptor> itr = metadata.listIterator(size);
    int index = 1, dotIndex;
    // doing reverse iteration to prefer column names at front in case of
    // column name clashes
    ColumnDescriptor desc;
    String name, tableName;
    while (itr.hasPrevious()) {
      desc = itr.previous();
      name = desc.getName();
      if (name == null || name.isEmpty()) {
        continue;
      }
      tableName = desc.getFullTableName();
      // allow looking up using name, table.name and schema.table.name
      columnNameToIndex.put(name, index);
      if (tableName != null && !tableName.isEmpty()) {
        columnNameToIndex.put(tableName + '.' + name, index);
        dotIndex = tableName.indexOf('.');
        if (dotIndex > 0) {
          columnNameToIndex.put(tableName.substring(dotIndex + 1) + '.'
              + name, index);
        }
      }
      index++;
    }
    return columnNameToIndex;
  }

  private final boolean moveNext() {
    if (this.rowsIter.hasNext()) {
      setCurrentRow(this.rowsIter.next());
      return true;
    }
    else {
      return false;
    }
  }

  private final boolean movePrevious() {
    if (this.rowsIter.hasPrevious()) {
      setCurrentRow(this.rowsIter.previous());
      return true;
    }
    else {
      return false;
    }
  }

  private final void setCurrentRow(Row row) {
    this.currentRow = row;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean next() throws SQLException {
    checkClosed();
    clearForPositioning();

    if (moveNext()) {
      return true;
    }
    else if ((this.rowSet.flags & gfxdConstants.ROWSET_LAST_BATCH) == 0) {
      if (fetchRowSet(false, 1, false)) {
        return moveNext();
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void close() throws SQLException {
    // get the source before clearing the finalizer
    final HostConnection source = getLobSource(false, "closeResultSet");
    clearFinalizer();
    checkClosed();

    this.rowSet = null;
    this.rowsIter = null;
    this.currentRow = null;
    try {
      this.service.closeResultSet(source, this.cursorId, 0);
    } catch (GFXDException gfxde) {
      throw ThriftExceptionUtil.newSQLException(gfxde);
    } finally {
      reset();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean wasNull() throws SQLException {
    return this.wasNull;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String getString(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getString(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean getBoolean(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getBoolean(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final byte getByte(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getByte(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final short getShort(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getShort(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final int getInt(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getInt(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final long getLong(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getLong(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final float getFloat(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getFloat(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final double getDouble(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getDouble(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final BigDecimal getBigDecimal(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getBigDecimal(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final BigDecimal getBigDecimal(int columnIndex, int scale)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getBigDecimal(columnIndex, scale, getSQLType(columnIndex),
        currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final byte[] getBytes(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getBytes(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Date getDate(int columnIndex) throws SQLException {
    return getDate(columnIndex, null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Time getTime(int columnIndex) throws SQLException {
    return getTime(columnIndex, null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Timestamp getTimestamp(int columnIndex) throws SQLException {
    return getTimestamp(columnIndex, null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Date getDate(int columnIndex, Calendar cal)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getDate(columnIndex, cal, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Time getTime(int columnIndex, Calendar cal)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getTime(columnIndex, cal, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Timestamp getTimestamp(int columnIndex, Calendar cal)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getTimestamp(columnIndex, cal, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Object getObject(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getObject(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object getObject(int columnIndex, Map<String, Class<?>> map)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getObject(columnIndex, map, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final InputStream getAsciiStream(int columnIndex)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getAsciiStream(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final InputStream getUnicodeStream(int columnIndex)
      throws SQLException {
    throw ThriftExceptionUtil.notImplemented("getUnicodeStream");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final InputStream getBinaryStream(int columnIndex)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getBinaryStream(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Reader getCharacterStream(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getCharacterStream(columnIndex, getSQLType(columnIndex),
        currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Blob getBlob(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getBlob(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Clob getClob(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getClob(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Ref getRef(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getRef(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Array getArray(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getArray(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public URL getURL(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getURL(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public RowId getRowId(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getRowId(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public NClob getNClob(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getNClob(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public SQLXML getSQLXML(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getSQLXML(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getNString(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getNString(columnIndex, getSQLType(columnIndex), currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Reader getNCharacterStream(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getNCharacterStream(columnIndex, getSQLType(columnIndex),
        currentRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String getString(String columnLabel) throws SQLException {
    return getString(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean getBoolean(String columnLabel) throws SQLException {
    return getBoolean(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final byte getByte(String columnLabel) throws SQLException {
    return getByte(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final short getShort(String columnLabel) throws SQLException {
    return getShort(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final int getInt(String columnLabel) throws SQLException {
    return getInt(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final long getLong(String columnLabel) throws SQLException {
    return getLong(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final float getFloat(String columnLabel) throws SQLException {
    return getFloat(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final double getDouble(String columnLabel) throws SQLException {
    return getDouble(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final BigDecimal getBigDecimal(String columnLabel)
      throws SQLException {
    return getBigDecimal(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final BigDecimal getBigDecimal(String columnLabel, int scale)
      throws SQLException {
    return getBigDecimal(getColumnIndex(columnLabel), scale);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final byte[] getBytes(String columnLabel) throws SQLException {
    return getBytes(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Date getDate(String columnLabel) throws SQLException {
    return getDate(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Time getTime(String columnLabel) throws SQLException {
    return getTime(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Timestamp getTimestamp(String columnLabel) throws SQLException {
    return getTimestamp(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Date getDate(String columnLabel, Calendar cal)
      throws SQLException {
    return getDate(getColumnIndex(columnLabel), cal);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Time getTime(String columnLabel, Calendar cal)
      throws SQLException {
    return getTime(getColumnIndex(columnLabel), cal);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Timestamp getTimestamp(String columnLabel, Calendar cal)
      throws SQLException {
    return getTimestamp(getColumnIndex(columnLabel), cal);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final InputStream getAsciiStream(String columnLabel)
      throws SQLException {
    return getAsciiStream(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final InputStream getUnicodeStream(String columnLabel)
      throws SQLException {
    return getUnicodeStream(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final InputStream getBinaryStream(String columnLabel)
      throws SQLException {
    return getBinaryStream(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Object getObject(String columnLabel) throws SQLException {
    return getObject(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Reader getCharacterStream(String columnLabel)
      throws SQLException {
    return getCharacterStream(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Object getObject(String columnLabel, Map<String, Class<?>> map)
      throws SQLException {
    return getObject(getColumnIndex(columnLabel), map);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Ref getRef(String columnLabel) throws SQLException {
    return getRef(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Blob getBlob(String columnLabel) throws SQLException {
    return getBlob(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Clob getClob(String columnLabel) throws SQLException {
    return getClob(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Array getArray(String columnLabel) throws SQLException {
    return getArray(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final URL getURL(String columnLabel) throws SQLException {
    return getURL(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final RowId getRowId(String columnLabel) throws SQLException {
    return getRowId(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final NClob getNClob(String columnLabel) throws SQLException {
    return getNClob(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final SQLXML getSQLXML(String columnLabel) throws SQLException {
    return getSQLXML(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String getNString(String columnLabel) throws SQLException {
    return getNString(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final Reader getNCharacterStream(String columnLabel)
      throws SQLException {
    return getNCharacterStream(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public SQLWarning getWarnings() throws SQLException {
    checkClosed();

    GFXDExceptionData warnings = this.rowSet.getWarnings();
    if (warnings != null) {
      return ThriftExceptionUtil.newSQLWarning(warnings, null);
    }
    else {
      return null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void clearWarnings() throws SQLException {
    checkClosed();

    this.rowSet.setWarnings(null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getCursorName() throws SQLException {
    checkClosed();

    return this.rowSet.cursorName;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final ResultSetMetaData getMetaData() throws SQLException {
    checkClosed();

    return new ClientRSMetaData(this.rowSet.metadata);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final int findColumn(String columnLabel) throws SQLException {
    return getColumnIndex(columnLabel);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean isBeforeFirst() throws SQLException {
    checkClosed();

    return this.rowSet.offset == 0 && !this.rowsIter.hasPrevious();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean isAfterLast() throws SQLException {
    checkClosed();

    return !this.rowsIter.hasNext()
        && (this.rowSet.flags & gfxdConstants.ROWSET_LAST_BATCH) != 0;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean isFirst() throws SQLException {
    checkClosed();

    return this.rowSet.offset == 0 && this.rowsIter.previousIndex() == 0;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean isLast() throws SQLException {
    checkClosed();

    return this.rowsIter.nextIndex() == this.rowSet.rows.size()
        && (this.rowSet.flags & gfxdConstants.ROWSET_LAST_BATCH) != 0;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void beforeFirst() throws SQLException {
    checkClosed();
    checkScrollable();
    clearForPositioning();

    if (this.rowSet.offset == 0) {
      this.rowsIter = this.rowSet.rows.listIterator();
    }
    else {
      fetchRowSet(true, 1, false);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void afterLast() throws SQLException {
    checkClosed();
    checkScrollable();
    clearForPositioning();

    if ((this.rowSet.flags & gfxdConstants.ROWSET_LAST_BATCH) != 0) {
      this.rowsIter = this.rowSet.rows.listIterator(this.rowSet.rows.size());
    }
    else {
      fetchRowSet(true, -1, true);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean first() throws SQLException {
    beforeFirst();
    if (this.rowsIter.hasNext()) {
      setCurrentRow(this.rowsIter.next());
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean last() throws SQLException {
    afterLast();
    if (this.rowsIter.hasPrevious()) {
      setCurrentRow(this.rowsIter.previous());
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final int getRow() throws SQLException {
    this.service.checkClosedConnection();
    if (this.rowSet != null) {
      int currentOffset = this.rowsIter.nextIndex();
      return (this.rowSet.offset + currentOffset);
    }
    else {
      return 0;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean absolute(int rows) throws SQLException {
    checkClosed();
    checkScrollable();
    clearForPositioning();

    // check if row is already available in current RowSet
    if (rows > 0) {
      if (rows > this.rowSet.offset
          && rows <= (this.rowSet.offset + this.rowSet.rows.size())) {
        this.rowsIter = this.rowSet.rows.listIterator(rows
            - this.rowSet.offset - 1);
        if (moveNext()) {
          return true;
        }
      }
    }
    else if (rows == 0) {
      if (this.rowSet.offset == 0) {
        this.rowsIter = this.rowSet.rows.listIterator();
        return true;
      }
    }
    else if ((this.rowSet.flags & gfxdConstants.ROWSET_LAST_BATCH) != 0) {
      if ((-rows) <= this.rowSet.rows.size()) {
        this.rowsIter = this.rowSet.rows.listIterator(this.rowSet.rows.size()
            + rows);
        if (moveNext()) {
          return true;
        }
      }
    }
    // for absolute position we will rely on fetchDirection to determine
    // whether to fetch in reverse or forward direction
    if (fetchRowSet(true, rows,
        (rows > 1 && this.fetchDirection == FETCH_REVERSE) || (rows == -1))) {
      return moveNext();
    }
    else {
      return false;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean relative(int rows) throws SQLException {
    switch (rows) {
      case 1:
        return next();
      case -1:
        return previous();
      case 0:
        return true; // no-op
      default:
        checkClosed();
        checkScrollable();
        clearForPositioning();

        // check if row is already available in current RowSet
        int nextIndex = this.rowsIter.nextIndex();
        final boolean moveForward = (rows > 0);
        if (moveForward) {
          int available = (this.rowSet.rows.size() - nextIndex);
          if (rows <= available) {
            this.rowsIter = this.rowSet.rows.listIterator(nextIndex + rows
                - 1);
            return moveNext();
          }
          else {
            // adjust by the number available in current batch
            rows -= available;
          }
        }
        else { // rows < 0
          if ((rows + nextIndex) >= 0) {
            this.rowsIter = this.rowSet.rows.listIterator(nextIndex + rows
                + 1);
            return movePrevious();
          }
          else {
            // adjust by the number available in current batch
            rows += nextIndex;
          }
        }
        // for relative position just rely on the sign of the position for
        // fetchReverse flag
        if (fetchRowSet(false, rows, !moveForward)) {
          return moveForward ? moveNext() : movePrevious();
        }
        else {
          return false;
        }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean previous() throws SQLException {
    checkClosed();
    checkScrollable();
    clearForPositioning();

    if (movePrevious()) {
      return true;
    }
    else if (this.rowSet.offset != 0) {
      if (fetchRowSet(false, -1, true)) {
        return movePrevious();
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setFetchDirection(int direction) throws SQLException {
    checkClosed();
    if (direction != FETCH_FORWARD) {
      checkScrollable();
    }

    switch (direction) {
      case FETCH_FORWARD:
      case FETCH_REVERSE:
      case FETCH_UNKNOWN:
        this.fetchDirection = direction;
        break;
      default:
        throw ThriftExceptionUtil.newSQLException(
            SQLState.INVALID_FETCH_DIRECTION, null, direction);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getFetchDirection() throws SQLException {
    checkClosed();

    return this.fetchDirection;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void setFetchSize(int rows) throws SQLException {
    checkClosed();

    final int maxRows;
    if (rows >= 0
        && ((maxRows = this.statement.getMaxRows()) == 0 || rows <= maxRows)) {
      this.fetchSize = rows;
    }
    else {
      throw ThriftExceptionUtil.newSQLException(SQLState.INVALID_FETCH_SIZE,
          null, rows);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final int getFetchSize() throws SQLException {
    checkClosed();

    return this.fetchSize;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getType() throws SQLException {
    checkClosed();

    return Converters.getJdbcResultSetType(this.attrs.resultSetType);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getConcurrency() throws SQLException {
    checkClosed();

    return this.attrs.updatable ? CONCUR_UPDATABLE : CONCUR_READ_ONLY;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean rowUpdated() throws SQLException {
    checkClosed();

    return this.cursorOperationType == gfxdConstants.CURSOR_UPDATE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean rowInserted() throws SQLException {
    checkClosed();

    return this.cursorOperationType == gfxdConstants.CURSOR_INSERT;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean rowDeleted() throws SQLException {
    checkClosed();

    return this.cursorOperationType == gfxdConstants.CURSOR_DELETE;
  }

  private final void initRowUpdate(String operation) throws SQLException {
    checkUpdatable(operation);

    if (this.changedColumns == null) {
      this.changedColumns = new BitSet(this.currentRow.size());
    }
    if (this.changedRow == null) {
      this.changedRow = new Row(this.currentRow);
      setCurrentRow(this.changedRow);
    }
    else if (this.currentRow != this.changedRow) {
      setCurrentRow(this.changedRow);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNull(int columnIndex) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateNull");
    final int index = columnIndex - 1;
    currentRow.setNull(index);
    this.changedColumns.set(index);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBoolean(int columnIndex, boolean x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateBoolean");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "boolean");
    cvc.setBoolean(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateByte(int columnIndex, byte x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateByte");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "byte");
    cvc.setByte(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateShort(int columnIndex, short x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateShort");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "short");
    cvc.setShort(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateInt(int columnIndex, int x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateInt");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "int");
    cvc.setInteger(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateLong(int columnIndex, long x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateLong");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "long");
    cvc.setLong(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateFloat(int columnIndex, float x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateFloat");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "float");
    cvc.setFloat(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateDouble(int columnIndex, double x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateDouble");
    ColumnValueConverter cvc = Converters.getConverter(
        getSQLType(columnIndex), "double");
    cvc.setDouble(currentRow, columnIndex, x);
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBigDecimal(int columnIndex, BigDecimal x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateBigDecimal");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "BigDecimal");
      cvc.setBigDecimal(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateString(int columnIndex, String x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateString");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "String");
      cvc.setString(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBytes(int columnIndex, byte[] x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateBytes");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "byte[]");
      cvc.setBytes(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateDate(int columnIndex, Date x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateDate");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "Date");
      cvc.setDate(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateTime(int columnIndex, Time x) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateTime");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "Time");
      cvc.setTime(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateTimestamp(int columnIndex, Timestamp x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateTimestamp");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "Timestamp");
      cvc.setTimestamp(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateObject(int columnIndex, Object x, int scaleOrLength)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateObject");
    if (x != null) {
      if (x instanceof BigDecimal) {
        BigDecimal bd = (BigDecimal)x;
        if (bd.scale() != scaleOrLength) {
          // rounding as per server side EmbedResultSet20
          bd = new BigDecimal(bd.unscaledValue(), bd.scale());
          bd.setScale(scaleOrLength, BigDecimal.ROUND_HALF_DOWN);
        }
        updateBigDecimal(columnIndex, bd);
      }
      else if (x instanceof InputStream) {
        updateBinaryStream(columnIndex, (InputStream)x, scaleOrLength);
      }
      else if (x instanceof Reader) {
        updateCharacterStream(columnIndex, (Reader)x, scaleOrLength);
      }
      else {
        ColumnValueConverter cvc = Converters.getConverter(
            getSQLType(columnIndex), "Object");
        cvc.setObject(currentRow, columnIndex, x);
      }
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateObject(int columnIndex, Object x)
      throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);

    initRowUpdate("updateObject");
    if (x != null) {
      ColumnValueConverter cvc = Converters.getConverter(
          getSQLType(columnIndex), "Object");
      cvc.setObject(currentRow, columnIndex, x);
    }
    else {
      currentRow.setNull(columnIndex - 1);
    }
    this.changedColumns.set(columnIndex - 1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNull(String columnLabel) throws SQLException {
    updateNull(getColumnIndex(columnLabel));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBoolean(String columnLabel, boolean x)
      throws SQLException {
    updateBoolean(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateByte(String columnLabel, byte x)
      throws SQLException {
    updateByte(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateShort(String columnLabel, short x)
      throws SQLException {
    updateShort(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateInt(String columnLabel, int x) throws SQLException {
    updateInt(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateLong(String columnLabel, long x)
      throws SQLException {
    updateLong(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateFloat(String columnLabel, float x)
      throws SQLException {
    updateFloat(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateDouble(String columnLabel, double x)
      throws SQLException {
    updateDouble(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBigDecimal(String columnLabel, BigDecimal x)
      throws SQLException {
    updateBigDecimal(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateString(String columnLabel, String x)
      throws SQLException {
    updateString(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBytes(String columnLabel, byte[] x)
      throws SQLException {
    updateBytes(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateDate(String columnLabel, Date x)
      throws SQLException {
    updateDate(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateTime(String columnLabel, Time x)
      throws SQLException {
    updateTime(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateTimestamp(String columnLabel, Timestamp x)
      throws SQLException {
    updateTimestamp(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateObject(String columnLabel, Object x,
      int scaleOrLength) throws SQLException {
    updateObject(getColumnIndex(columnLabel), x, scaleOrLength);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateObject(String columnLabel, Object x)
      throws SQLException {
    updateObject(getColumnIndex(columnLabel), x);
  }

  private ArrayList<Integer> getChangedColumns() {
    ArrayList<Integer> changedColumns = new ArrayList<Integer>(
        this.changedColumns.cardinality());
    for (int index = this.changedColumns.nextSetBit(0); index >= 0;
        index = this.changedColumns.nextSetBit(index + 1)) {
      changedColumns.add(index + 1);
    }
    return changedColumns;
  }

  private void resetCurrentRow() {
    int currentIndex = this.rowsIter.nextIndex() - 1;
    if (currentIndex >= 0) {
      setCurrentRow(this.rowSet.rows.get(currentIndex));
    }
    else {
      this.currentRow = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void insertRow() throws SQLException {
    checkClosed();
    checkUpdatable("insertRow");

    // TODO: handle SCROLL_SENSITIVE for insertRow/updateRow/deleteRow
    // but first need to add that to server-side

    if (this.insertRow != null && this.changedColumns != null
        && this.currentRow == this.insertRow) {
      this.insertRow.setChangedColumns(this.changedColumns);
      try {
        this.service.executeCursorUpdate(getLobSource(true, "insertRow"),
            this.cursorId, gfxdConstants.CURSOR_INSERT, insertRow,
            getChangedColumns(), this.rowsIter.nextIndex() - 1);
      } catch (GFXDException gfxde) {
        throw ThriftExceptionUtil.newSQLException(gfxde);
      } finally {
        this.currentRow.setChangedColumns(null);
      }
      this.changedColumns.clear();
      this.cursorOperationType = gfxdConstants.CURSOR_INSERT;
      this.insertRow = null;
    }
    else {
      throw ThriftExceptionUtil
          .newSQLException(SQLState.CURSOR_NOT_POSITIONED_ON_INSERT_ROW);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateRow() throws SQLException {
    checkClosed();
    checkUpdatable("updateRow");

    if (this.changedColumns != null) {
      if (this.currentRow == this.insertRow) {
        throw ThriftExceptionUtil.newSQLException(SQLState.NO_CURRENT_ROW);
      }
      this.currentRow.setChangedColumns(this.changedColumns);
      try {
        this.service.executeCursorUpdate(getLobSource(true, "updateRow"),
            this.cursorId, gfxdConstants.CURSOR_UPDATE, this.currentRow,
            getChangedColumns(), this.rowsIter.nextIndex() - 1);
      } catch (GFXDException gfxde) {
        throw ThriftExceptionUtil.newSQLException(gfxde);
      } finally {
        this.currentRow.setChangedColumns(null);
      }
      this.changedColumns.clear();
      this.cursorOperationType = gfxdConstants.CURSOR_UPDATE;
    }
    else {
      throw ThriftExceptionUtil.newSQLException(
          SQLState.CURSOR_INVALID_OPERATION_AT_CURRENT_POSITION);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void deleteRow() throws SQLException {
    checkClosed();
    checkUpdatable("deleteRow");

    if (this.currentRow == this.insertRow) {
      throw ThriftExceptionUtil.newSQLException(SQLState.NO_CURRENT_ROW);
    }
    try {
      this.service.executeCursorUpdate(getLobSource(true, "deleteRow"),
          this.cursorId, gfxdConstants.CURSOR_DELETE, null, null,
          this.rowsIter.nextIndex() - 1);
    } catch (GFXDException gfxde) {
      throw ThriftExceptionUtil.newSQLException(gfxde);
    }
    if (this.changedColumns != null) {
      this.changedColumns.clear();
    }
    this.cursorOperationType = gfxdConstants.CURSOR_DELETE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void refreshRow() throws SQLException {
    // not implemented
    throw ThriftExceptionUtil.notImplemented("refreshRow");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void cancelRowUpdates() throws SQLException {
    checkClosed();
    checkUpdatable("cancelRowUpdates");

    if (this.changedColumns != null) {
      this.changedColumns.clear();
      this.changedRow = new Row(this.currentRow);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void moveToInsertRow() throws SQLException {
    checkClosed();
    checkUpdatable("moveToInsertRow");

    if (this.changedColumns == null) {
      this.changedColumns = new BitSet(this.currentRow.size());
    }
    if (this.insertRow == null) {
      this.insertRow = new Row(this.rowSet.metadata);
    }
    setCurrentRow(this.insertRow);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void moveToCurrentRow() throws SQLException {
    checkClosed();
    checkUpdatable("moveToCurrentRow");

    resetCurrentRow();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ClientStatement getStatement() throws SQLException {
    return this.statement;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateRef(int columnIndex, Ref x) throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateRef");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateRef(String columnLabel, Ref x) throws SQLException {
    updateRef(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateArray(int columnIndex, Array x) throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateArray");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateArray(String columnLabel, Array x)
      throws SQLException {
    updateArray(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateRowId(int columnIndex, RowId x) throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateRowId");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateRowId(String columnLabel, RowId x)
      throws SQLException {
    updateRowId(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getHoldability() throws SQLException {
    checkClosed();

    return this.attrs.holdCursorsOverCommit ? HOLD_CURSORS_OVER_COMMIT
        : CLOSE_CURSORS_AT_COMMIT;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isClosed() throws SQLException {
    return this.rowSet == null || !this.service.isOpen;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateSQLXML(int columnIndex, SQLXML xmlObject)
      throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateSQLXML");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateSQLXML(String columnLabel, SQLXML xmlObject)
      throws SQLException {
    updateSQLXML(getColumnIndex(columnLabel), xmlObject);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateBinaryStream(int columnIndex, InputStream x, long length)
      throws SQLException {
    // TODO Auto-generated method stub
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBinaryStream(int columnIndex, InputStream x)
      throws SQLException {
    updateBinaryStream(columnIndex, x, -1L);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBinaryStream(int columnIndex, InputStream x,
      int length) throws SQLException {
    updateBinaryStream(columnIndex, x, (long)length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBinaryStream(String columnLabel, InputStream x,
      long length) throws SQLException {
    updateBinaryStream(getColumnIndex(columnLabel), x, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBinaryStream(String columnLabel, InputStream x)
      throws SQLException {
    updateBinaryStream(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBinaryStream(String columnLabel, InputStream x,
      int length) throws SQLException {
    updateBinaryStream(getColumnIndex(columnLabel), x, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateCharacterStream(int columnIndex, Reader x, long length)
      throws SQLException {
    // TODO Auto-generated method stub
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateCharacterStream(int columnIndex, Reader x)
      throws SQLException {
    updateCharacterStream(columnIndex, x, -1L);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateCharacterStream(int columnIndex, Reader x,
      int length) throws SQLException {
    updateCharacterStream(columnIndex, x, (long)length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateCharacterStream(String columnLabel, Reader reader,
      long length) throws SQLException {
    updateCharacterStream(getColumnIndex(columnLabel), reader, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateCharacterStream(String columnLabel, Reader reader)
      throws SQLException {
    updateCharacterStream(getColumnIndex(columnLabel), reader);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateCharacterStream(String columnLabel, Reader reader,
      int length) throws SQLException {
    updateCharacterStream(getColumnIndex(columnLabel), reader, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateAsciiStream(int columnIndex, InputStream x, long length)
      throws SQLException {
    // TODO Auto-generated method stub
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateAsciiStream(int columnIndex, InputStream x)
      throws SQLException {
    updateAsciiStream(columnIndex, x, -1L);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateAsciiStream(int columnIndex, InputStream x,
      int length) throws SQLException {
    updateAsciiStream(columnIndex, x, (long)length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateAsciiStream(String columnLabel, InputStream x,
      long length) throws SQLException {
    updateAsciiStream(getColumnIndex(columnLabel), x, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateAsciiStream(String columnLabel, InputStream x)
      throws SQLException {
    updateAsciiStream(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateAsciiStream(String columnLabel, InputStream x,
      int length) throws SQLException {
    updateAsciiStream(getColumnIndex(columnLabel), x, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateBlob(int columnIndex, InputStream inputStream, long length)
      throws SQLException {
    // TODO Auto-generated method stub

  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBlob(int columnIndex, InputStream inputStream)
      throws SQLException {
    updateBlob(columnIndex, inputStream, -1L);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBlob(int columnIndex, Blob x) throws SQLException {
    // TODO Auto-generated method stub

  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBlob(String columnLabel, InputStream inputStream,
      long length) throws SQLException {
    updateBlob(getColumnIndex(columnLabel), inputStream, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBlob(String columnLabel, InputStream inputStream)
      throws SQLException {
    updateBlob(getColumnIndex(columnLabel), inputStream);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateBlob(String columnLabel, Blob x)
      throws SQLException {
    updateBlob(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateClob(int columnIndex, Reader reader, long length)
      throws SQLException {
    // TODO Auto-generated method stub

  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateClob(int columnIndex, Reader reader)
      throws SQLException {
    updateClob(columnIndex, reader, -1);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateClob(int columnIndex, Clob x) throws SQLException {
    // TODO Auto-generated method stub

  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateClob(String columnLabel, Reader reader, long length)
      throws SQLException {
    updateClob(getColumnIndex(columnLabel), reader, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateClob(String columnLabel, Reader reader)
      throws SQLException {
    updateClob(getColumnIndex(columnLabel), reader);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateClob(String columnLabel, Clob x)
      throws SQLException {
    updateClob(getColumnIndex(columnLabel), x);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateNString(int columnIndex, String nString)
      throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateNString");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNString(String columnLabel, String nString)
      throws SQLException {
    updateNString(getColumnIndex(columnLabel), nString);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateNCharacterStream(int columnIndex, Reader x, long length)
      throws SQLException {
    throw ThriftExceptionUtil
        .notImplemented("ResultSet.updateNCharacterStream");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateNCharacterStream(int columnIndex, Reader x)
      throws SQLException {
    throw ThriftExceptionUtil
        .notImplemented("ResultSet.updateNCharacterStream");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNCharacterStream(String columnLabel, Reader reader,
      long length) throws SQLException {
    updateNCharacterStream(getColumnIndex(columnLabel), reader, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNCharacterStream(String columnLabel, Reader reader)
      throws SQLException {
    updateNCharacterStream(getColumnIndex(columnLabel), reader);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateNClob(int columnIndex, Reader reader, long length)
      throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateNClob");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNClob(int columnIndex, Reader reader)
      throws SQLException {
    updateNClob(columnIndex, reader, -1L);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
    throw ThriftExceptionUtil.notImplemented("ResultSet.updateNClob");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNClob(String columnLabel, Reader reader, long length)
      throws SQLException {
    updateNClob(getColumnIndex(columnLabel), reader, length);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNClob(String columnLabel, Reader reader)
      throws SQLException {
    updateNClob(getColumnIndex(columnLabel), reader);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final void updateNClob(String columnLabel, NClob nClob)
      throws SQLException {
    updateNClob(getColumnIndex(columnLabel), nClob);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    try {
      return iface.cast(this);
    } catch (ClassCastException cce) {
      throw ThriftExceptionUtil.newSQLException(SQLState.UNABLE_TO_UNWRAP,
          cce, iface);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    return iface.isInstance(this);
  }

  // JDBC 4.1 methods

  @Override
  public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
    final Row currentRow = checkValidColumn(columnIndex);
    return getObject(columnIndex, type, getSQLType(columnIndex), currentRow);
  }

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