Connection.java :  » Database-JDBC-Connection-Pool » sequoia-2.10.9 » org » continuent » sequoia » driver » Java Open Source

Java Open Source » Database JDBC Connection Pool » sequoia 2.10.9 
sequoia 2.10.9 » org » continuent » sequoia » driver » Connection.java
/**
 * Sequoia: Database clustering technology.
 * Copyright (C) 2002-2004 French National Institute For Research In Computer
 * Science And Control (INRIA).
 * Copyright (C) 2005-2006 Continuent, Inc.
 * Contact: sequoia@continuent.org
 * 
 * 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. 
 *
 * Initial developer(s): Emmanuel Cecchet. 
 * Contributor(s): Julie Marguerite, Guillaume Bort, Duncan Smith, Vadim Kassin,
 * Nicolas Modrzyk, Jaco Swart,  Jean-Bernard van Zuylen
 * Completely refactored by Marc Herbert to remove the use of Java serialization.
 */

package org.continuent.sequoia.driver;

import java.io.IOException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.continuent.sequoia.common.exceptions.AuthenticationException;
import org.continuent.sequoia.common.exceptions.NoMoreBackendException;
import org.continuent.sequoia.common.exceptions.NoMoreControllerException;
import org.continuent.sequoia.common.exceptions.NotImplementedException;
import org.continuent.sequoia.common.exceptions.ProtocolException;
import org.continuent.sequoia.common.exceptions.driver.DriverIOException;
import org.continuent.sequoia.common.exceptions.driver.DriverSQLException;
import org.continuent.sequoia.common.exceptions.driver.VirtualDatabaseUnavailableException;
import org.continuent.sequoia.common.exceptions.driver.protocol.BackendDriverException;
import org.continuent.sequoia.common.exceptions.driver.protocol.ControllerCoreException;
import org.continuent.sequoia.common.exceptions.driver.protocol.SerializableException;
import org.continuent.sequoia.common.protocol.Commands;
import org.continuent.sequoia.common.protocol.SQLDataSerialization;
import org.continuent.sequoia.common.protocol.TypeTag;
import org.continuent.sequoia.common.protocol.SQLDataSerialization.Serializer;
import org.continuent.sequoia.common.sql.Request;
import org.continuent.sequoia.common.sql.RequestWithResultSetParameters;
import org.continuent.sequoia.common.stream.DriverBufferedInputStream;
import org.continuent.sequoia.common.stream.DriverBufferedOutputStream;

/**
 * This class implements the communication protocol to the Controller.
 * <p>
 * Connection.java was inspired from the PostgreSQL JDBC driver by Peter T.
 * Mount.
 * 
 * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
 * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
 * @author <a href="mailto:vadim@kase.kz">Vadim Kassin </a>
 * @author <a href="mailto:duncan@mightybot.com">Duncan Smith </a>
 * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
 * @author <a href="mailto:jaco.swart@iblocks.co.uk">Jaco Swart </a>
 * @author <a href="mailto:Marc.Herbert@emicnetworks.com">Marc Herbert </a>
 * @author <a href="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
 *         </a>
 * @version 2.0
 */
public class Connection implements java.sql.Connection
{
  /** Status of the connection. */
  protected boolean                    isClosed                            = false;

  protected String                     escapeChar;

  /** Sequoia controller we are connected to */
  protected ControllerInfo             controllerInfo                      = null;

  // ConnectionClosingThread
  /** Driver that created us. */
  protected Driver                     driver                              = null;

  /** Connection with the controller. */
  protected Socket                     socket;
  /** Socket input stream. */
  protected DriverBufferedInputStream  socketInput;
  /** Socket output stream. */
  protected DriverBufferedOutputStream socketOutput;

  /** @see org.continuent.sequoia.controller.core.ControllerConstants#SQL_SHORT_FORM_LENGTH */
  public static final int              ABBREV_REQUEST_LENGTH               = 40;

  // used by Statement (and maybe also by some others _below_)
  static final String                  LINE_SEPARATOR                      = System
                                                                               .getProperty("line.separator");

  // Member variables describing the state of the connection

  /** Commit mode of the connection (<code>true</code>= automatic). */
  protected boolean                    autoCommit                          = true;

  /** Is the connection in read-only mode ? */
  protected boolean                    readOnly                            = false;

  /** Has a write request been executed in the current transaction? */
  boolean                              writeExecutedInTransaction          = false;

  /** Default transaction isolation level if the user has not enforced one */
  public static final int              DEFAULT_TRANSACTION_ISOLATION_LEVEL = -1;

  /** Current transaction isolation level. */
  protected int                        isolationLevel                      = DEFAULT_TRANSACTION_ISOLATION_LEVEL;

  /** transaction identifier. */
  protected long                       transactionId                       = 0;

  /** List of <code>Warnings</code> for this connection. */
  protected SQLWarning                 firstWarning                        = null;

  /** ResultSet holdability (JDBC 3) */
  protected int                        holdability                         = HOLDABILITY_NOT_SET;
  private static final int             HOLDABILITY_NOT_SET                 = -1;

  /** Meta-data of Sequoia connections. */
  protected DatabaseMetaData           metaData                            = null;

  /** Parsed URL to the database. */
  private final SequoiaUrl             sequoiaUrl;

  /** Virtual database user used for this connection. */
  protected String                     vdbUser                             = null;
  protected String                     vdbPassword                         = null;

  private boolean                      connectionPooling;

  // Escape processing tuning
  protected boolean                    escapeBackslash;
  protected boolean                    escapeSingleQuote;

  // flag to check if a new transaction must be
  // started before executing any statement
  private boolean                      mustBeginTransaction                = false;

  // True if the connection must be persisted on all cluster backends even when
  // autoCommit=true
  private boolean                      persistentConnection;

  // Persistent connection identifier if persistentConnection is true
  private long                         persistentConnectionId;

  // Do we want SQL Warnings ?
  private boolean                      retrieveSQLWarnings                 = false;

  // Do we force generated keys to be retrieved?
  private boolean                      alwaysGetGeneratedKeys              = false;

  /*****************************************************************************
   * *************** * Constructor and get/set methods * ***********************
   * ****************************************************************************
   */

  /**
   * Creates a new <code>Connection</code> instance.
   * 
   * @param driver calling driver
   * @param socket connection with the controller
   * @param in socket input stream
   * @param out socket output stream
   * @param sequoiaUrl Sequoia URL of the database
   * @param controller controller we are connected to
   * @param userName user login
   * @param password user password
   * @throws AuthenticationException login error
   * @throws IOException stream error
   * @throws SQLException if the virtual database is not available on the
   *           controller
   */
  Connection(Driver driver, Socket socket, DriverBufferedInputStream in,
      DriverBufferedOutputStream out, SequoiaUrl sequoiaUrl,
      ControllerInfo controller, String userName, String password)
      throws AuthenticationException, IOException,
      VirtualDatabaseUnavailableException
  {
    this.driver = driver;
    this.socket = socket;
    this.socketInput = in;
    this.socketOutput = out;
    this.sequoiaUrl = sequoiaUrl;
    this.controllerInfo = controller;
    this.vdbUser = userName;
    this.vdbPassword = password;

    escapeBackslash = driver.getEscapeBackslash();
    escapeChar = driver.getEscapeChar();
    escapeSingleQuote = driver.getEscapeSingleQuote();
    connectionPooling = driver.getConnectionPooling();
    persistentConnection = driver.getPersistentConnection();
    retrieveSQLWarnings = driver.getRetrieveSQLWarnings();
    alwaysGetGeneratedKeys = driver.getRetrieveGeneratedKeys();

    // Is virtual database available?
    if (!in.readBoolean()) // failed
      throw new VirtualDatabaseUnavailableException(in.readLongUTF());

    // Is authentication successful?
    if (!in.readBoolean()) // failed
      throw new AuthenticationException(in.readLongUTF());

    setUrlParametersOptionsOnConnection(sequoiaUrl);

    out.writeLongUTF(LINE_SEPARATOR);
    out.writeBoolean(persistentConnection);
    out.flush();

    if (persistentConnection)
    {
      if (in.readBoolean())
        persistentConnectionId = in.readLong();
      else
        throw new AuthenticationException(
            "No more persistent connections available for virtual database "
                + sequoiaUrl.getDatabaseName() + "[url=" + sequoiaUrl + "]");
    }

    out.writeBoolean(retrieveSQLWarnings);
    out.flush();

    if (sequoiaUrl.isDebugEnabled())
      System.out.println("New connection:" + this.toString());
  }

  /**
   * Set SequoiaUrl parameters options on connection.
   * 
   * @param sequoiaUrl the Sequoia URL to use
   */
  private void setUrlParametersOptionsOnConnection(SequoiaUrl sequoiaUrl)
  {
    HashMap sequoiaUrlParameters = sequoiaUrl.getParameters();

    String escapeBaskslash = (String) sequoiaUrlParameters
        .get(Driver.ESCAPE_BACKSLASH_PROPERTY);
    if (escapeBaskslash != null)
      setEscapeBackslash(new Boolean(escapeBaskslash).booleanValue());

    String escapeQuote = (String) sequoiaUrlParameters
        .get(Driver.ESCAPE_SINGLE_QUOTE_PROPERTY);
    if (escapeQuote != null)
      setEscapeSingleQuote(new Boolean(escapeQuote).booleanValue());

    String escapeCharacter = (String) sequoiaUrlParameters
        .get(Driver.ESCAPE_CHARACTER_PROPERTY);
    if (escapeCharacter != null)
      setEscapeChar(escapeCharacter);

    // true if transparent connection pooling must be used
    String connPool = (String) sequoiaUrlParameters
        .get(Driver.CONNECTION_POOLING_PROPERTY);
    if (connPool != null)
      this.connectionPooling = "true".equals(connPool);

    String persistentConn = (String) sequoiaUrlParameters
        .get(Driver.PERSISTENT_CONNECTION_PROPERTY);
    if (persistentConn != null)
      this.persistentConnection = "true".equals(persistentConn);

    String retSQLWarns = (String) sequoiaUrlParameters
        .get(Driver.RETRIEVE_SQL_WARNINGS_PROPERTY);
    if (retSQLWarns != null)
      this.retrieveSQLWarnings = "true".equals(retSQLWarns);

    String retGeneratedKeys = (String) sequoiaUrlParameters
        .get(Driver.ALWAYS_RETRIEVE_GENERATED_KEYS_PROPERTY);
    if (retGeneratedKeys != null)
      this.alwaysGetGeneratedKeys = "true".equals(retGeneratedKeys);

    if (sequoiaUrl.isDebugEnabled())
    {
      // Give a warning for unrecognized driver options in the URL
      // (only in the URL: unknown properties have been filtered out)
      for (Iterator iter = sequoiaUrlParameters.entrySet().iterator(); iter
          .hasNext();)
      {
        Map.Entry e = (Map.Entry) iter.next();
        String param = (String) e.getKey();
        if (!Driver.driverPropertiesNames.contains(param))
          System.out.println("Unrecognized driver parameter: " + param + " = "
              + (String) e.getValue());
      }
    }
  }

  /**
   * Get the information about the controller we are connected to
   * 
   * @return <code>ControllerInfo</code> object of the controller
   */
  public ControllerInfo getControllerInfo()
  {
    return controllerInfo;
  }

  /**
   * Gets the password used to login to the database.
   * 
   * @return password
   */
  public String getPassword()
  {
    return vdbPassword;
  }

  /**
   * Gets the untouched String URL that was passed by the client application
   * 
   * @return value of url.
   */
  public String getUrl()
  {
    return sequoiaUrl.getUrl();
  }

  /**
   * Gets the parsed Sequoia URL, including merged properties
   * 
   * @return the parsed Sequoia URL
   */
  SequoiaUrl getSequoiaUrl()
  {
    return sequoiaUrl;
  }

  /**
   * Gets the user name used to login to the database.
   * 
   * @return login name
   */
  public String getUserName()
  {
    return vdbUser;
  }

  /**
   * Returns the escapeBackslash value.
   * 
   * @return Returns the escapeBackslash.
   */
  public boolean isEscapeBackslash()
  {
    return escapeBackslash;
  }

  /**
   * Sets the escapeBackslash value.
   * 
   * @param escapeBackslash The escapeBackslash to set.
   */
  public void setEscapeBackslash(boolean escapeBackslash)
  {
    this.escapeBackslash = escapeBackslash;
  }

  /**
   * Returns the escapeSingleQuote value.
   * 
   * @return Returns the escapeSingleQuote.
   */
  public boolean isEscapeSingleQuote()
  {
    return escapeSingleQuote;
  }

  /**
   * Sets the escapeSingleQuote value.
   * 
   * @param escapeSingleQuote The escapeSingleQuote to set.
   */
  public void setEscapeSingleQuote(boolean escapeSingleQuote)
  {
    this.escapeSingleQuote = escapeSingleQuote;
  }

  /**
   * Sets the escapeCharacter value
   * 
   * @param escapeChar the escapeChar value to set
   */
  public void setEscapeChar(String escapeChar)
  {
    this.escapeChar = escapeChar;
  }

  /**
   * @return Returns the escapeChar.
   */
  public String getEscapeChar()
  {
    return escapeChar;
  }

  /**
   * Returns the connectionPooling value.
   * 
   * @return Returns the connectionPooling.
   */
  public boolean isConnectionPooling()
  {
    return connectionPooling;
  }

  /**
   * Sets the connectionPooling value.
   * 
   * @param connectionPooling The connectionPooling to set.
   */
  public void setConnectionPooling(boolean connectionPooling)
  {
    this.connectionPooling = connectionPooling;
  }

  /**
   * Returns the alwaysGetGeneratedKeys value.
   * 
   * @return Returns the alwaysGetGeneratedKeys.
   */
  boolean isAlwaysGettingGeneratedKeys()
  {
    return alwaysGetGeneratedKeys;
  }
  
  //
  // java.sql.Connection implementation
  //

  /**
   * After this call, <code>getWarnings()</code> returns <code>null</code>
   * until a new call to getWarnings() on this connection.
   * 
   * @exception SQLException if a database access error occurs
   */
  public void clearWarnings() throws SQLException
  {
    if (!persistentConnection || !retrieveSQLWarnings)
      // nop
      return;
    if (isClosed)
    {
      // on a closed connection, just reset the warnings
      // jdbc spec doesn't ask to throw SQLException
      firstWarning = null;
      return;
    }

    try
    {
      sendCommand(Commands.ConnectionClearWarnings);
      socketOutput.writeLong(persistentConnectionId);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName());
      // forget the ack, we just need to know if an exception occured
      receiveBooleanOrException();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      { // Connection failed, try to reconnect and re-send command
        reconnect();
        clearWarnings();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while clearWarnings() and automatic reconnect failed("
                + e1 + ")", e1);
      }
    }

  }

  /**
   * Releases the connection. In fact, the connection is marked to be released
   * but will be effectively closed by the <code>ConnectionClosingThread</code>
   * if the connection has not been reused before.
   * 
   * @exception DriverSQLException if an error occurs
   */
  public void close() throws DriverSQLException
  {
    synchronized (this) // Wait until other methods/Commands are done
    {
      // Spec says:
      // Calling the method close on a Connection object that is already closed
      // is a no-op.
      if (isClosed)
        return;
      isClosed = true;
      /*
       * All JDBC entry points (methods) of this Connection have to
       * throwSQLExceptionIfClosed(). Relaxed: at least every JDBC method _with
       * some side-effect_ has to throwSQLExceptionIfClosed(). So now we are
       * safe and can leave the lock, since they will fail anyway.
       */
    }

    if (connectionPooling && !persistentConnection)
    { // Try to pool the connection for later reuse
      // Persistent connections are not pooled to free resources right away
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Resetting connection and adding it to the pool");
      autoCommit = true;
      mustBeginTransaction = false;
      readOnly = false;
      writeExecutedInTransaction = false;
      isolationLevel = DEFAULT_TRANSACTION_ISOLATION_LEVEL;
      try
      {
        sendCommand(Commands.Reset);
        socketOutput.flush();
        if (socketInput != null)
        {
          // Wait for the controller to receive the reset, in order to have
          // an exception thrown if there are no more controllers
          receiveBooleanOrException();
        }
      }
      catch (Exception ignored)
      {
        // Try to reconnect to inform other controllers that we are reseting
        // this connection. Do it only once to avoid endless loops
        reconnect();
        try
        {
          sendCommand(Commands.Reset);
          socketOutput.flush();
          if (socketInput != null)
          {
            // Wait for the controller to receive the reset, in order to have
            // an exception thrown if there are no more controllers
            receiveBooleanOrException();
          }
        }
        catch (Exception e)
        {
          // Ok, no way to reset connection on the controller side, let's throw
          // the exception
          throw new DriverSQLException("Error while closing the connection\n"
              + e.getLocalizedMessage(), e);
        }
      }

      // only one (Connection) accessing the pool at a time
      synchronized (driver.pendingConnectionClosing)
      {
        if (!driver.connectionClosingThreadisAlive)
        { // First connection to close, start a new closing thread
          if (sequoiaUrl.isDebugEnabled())
            System.out.println("Starting a new connection closing thread");
          ConnectionClosingThread t = new ConnectionClosingThread(driver);
          t.start();
        }
        // Add to the list
        driver.pendingConnectionClosing.add(this);
      }
    }
    else
    { // Close connection
      try
      {
        // driver = null; // probably useless since we use now
        // throwSQLExceptionIfClosed(), but
        // harmless anyway
        if (socketOutput != null)
        {
          if (sequoiaUrl.isDebugEnabled())
            System.out.println("Closing connection");
          sendCommand(Commands.Close);
          socketOutput.flush();
          if (socketInput != null)
          { // Wait for the controller to receive the connection and close the
            // stream. If we do not wait for the controller ack, the connection
            // is closed on the controller before the closing is handled which
            // results in an ugly warning message on the controller side. We are
            // not in a hurry when closing the connection so let do the things
            // nicely!
            receiveBooleanOrException();
            socketInput.close();
          }
          socketOutput.close();
        }

      }
      catch (Exception ignore)
      {
        // we attempt to reconnect to another controller:
        // since the connection was persistent, controllers
        // must be informed of the close operation so that
        // they can clean up their resources properly
        reconnect();
        close();
      }
    }
  }

  /**
   * Makes all changes made since the previous commit/rollback permanent and
   * releases any database locks currently held by the <code>Connection</code>.
   * This method should only be used when auto-commit has been disabled. (If
   * <code>autoCommit</code>== <code>true</code>, then we throw a
   * DriverSQLException).
   * 
   * @exception DriverSQLException if a database access error occurs or the
   *              connection is in autocommit mode
   * @see Connection#setAutoCommit(boolean)
   */
  public synchronized void commit() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    if (autoCommit)
    {
      if (isCommitInAutoCommitAllowed())
      {
        return; // nothing to do
      }
      else
      {
        throw new DriverSQLException(
            "Trying to commit a connection in autocommit mode");
      }
    }
    // Check if we are not committing an empty transaction (not started yet)
    if (mustBeginTransaction)
      return;

    doCommit();
  }

  private void doCommit() throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.Commit);
      socketOutput.flush();

      // Commit acknowledgement
      long acknowledgedTransactionId = receiveLongOrException();
      // sanity check
      if (acknowledgedTransactionId != transactionId)
      {
        throw new DriverSQLException(
            "Protocol error during commit (acknowledge transaction ID = "
                + acknowledgedTransactionId + ", expected transaction ID = "
                + transactionId + ")");
      }
      mustBeginTransaction = true; // lazy begin
      writeExecutedInTransaction = false;
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the commit
      try
      {
        // this should resend transactionId (among others)
        reconnect();

        // get an ack
        long acknowledgedTransactionId = retrieveCommitResult();
        // sanity check
        if (acknowledgedTransactionId != transactionId)
        {
          throw new DriverSQLException(
              "Protocol error during commit (acknowledge transaction ID = "
                  + acknowledgedTransactionId + ", expected transaction ID = "
                  + transactionId + ")");
        }
        mustBeginTransaction = true;
        writeExecutedInTransaction = false;

        // The controller will automatically redo the commit if it was not done
        // earlier so we can safely return here, this is a success.
        return;
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost during commit of transaction '" + transactionId

            + "' and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  /**
   * Determines whether or not commit should be allowed when autocommit is
   * enabled.
   * 
   * @return true when commit is allowed with autocommit
   */
  private boolean isCommitInAutoCommitAllowed()
  {
    String allowed = (String) sequoiaUrl.getParameters().get(
        Driver.ALLOW_COMMIT_WITH_AUTOCOMMIT_PROPERTY);

    return allowed != null && allowed.toLowerCase().equals("true");
  }

  /**
   * SQL statements without parameters are normally executed using
   * <code>Statement</code> objects. If the same SQL statement is executed
   * many times, it is more efficient to use a <code>PreparedStatement</code>.
   * The <code>ResultSet</code> will be
   * <code>TYPE_FORWARD_ONLY</cde>/<code>CONCUR_READ_ONLY</code>.
   *    *
   * @return a new <code>Statement</code> object
   * @exception DriverSQLException passed through from the constructor
   */
  public java.sql.Statement createStatement() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    return new Statement(this, driver);
  }

  /**
   * SQL statements without parameters are normally executed using
   * <code>Statement</code> objects. If the same SQL statement is executed
   * many times, it is more efficient to use a <code>PreparedStatement</code>.
   * 
   * @param resultSetType resultSetType to use
   * @param resultSetConcurrency resultSetConcurrency to use
   * @return a new <code>Statement</code> object
   * @exception SQLException passed through from the constructor
   */
  public java.sql.Statement createStatement(int resultSetType,
      int resultSetConcurrency) throws SQLException
  {
    throwSQLExceptionIfClosed();
    Statement s = new Statement(this, driver);
    s.setResultSetType(resultSetType);
    s.setResultSetConcurrency(resultSetConcurrency);
    return s;
  }

  /**
   * Creates a <code>Statement</code> object that will generate
   * <code>ResultSet</code> objects with the given type, concurrency, and
   * holdability.
   * <p>
   * This method is the same as the <code>createStatement</code> method above,
   * but it allows the default result set type, concurrency, and holdability to
   * be overridden.
   * 
   * @param resultSetType one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
   *          <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
   *          <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
   * @param resultSetConcurrency one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
   *          <code>ResultSet.CONCUR_UPDATABLE</code>
   * @param resultSetHoldability one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
   *          <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
   * @return a new <code>Statement</code> object that will generate
   *         <code>ResultSet</code> objects with the given type, concurrency,
   *         and holdability
   * @exception SQLException if a database access error occurs or the given
   *              parameters are not <code>ResultSet</code> constants
   *              indicating type, concurrency, and holdability
   * @see ResultSet
   * @since JDK 1.4
   */
  public java.sql.Statement createStatement(int resultSetType,
      int resultSetConcurrency, int resultSetHoldability) throws SQLException
  {
    throw new NotImplementedException(getCurrentMethodName());
  }

  /**
   * Gets the current auto-commit state.
   * 
   * @return current state of the auto-commit mode
   * @exception DriverSQLException is connection is closed
   * @see Connection#setAutoCommit
   */
  public boolean getAutoCommit() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    return this.autoCommit;
  }

  /**
   * A connection's database is able to provide information describing its
   * tables, its supported SQL grammar, its stored procedures, the capabilities
   * of this connection, etc. This information is made available through a
   * DatabaseMetaData object.
   * 
   * @return a <code>DatabaseMetaData</code> object for this connection
   * @exception DriverSQLException if connection is closed
   */
  public java.sql.DatabaseMetaData getMetaData() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    if (metaData == null)
    {
      metaData = new DatabaseMetaData(this);
    }
    return metaData;
  }

  /**
   * Return current catalog name.
   * 
   * @return name of the current <code>VirtualDatabase</code>
   * @throws DriverSQLException if any error occurs
   * @see Connection#getCatalog()
   */
  public synchronized String getCatalog() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.ConnectionGetCatalog);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName());

      return receiveStringOrException();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getCatalog();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * getCatalogs definition.
   * 
   * @return instace of <code>ResultSet<code>
   * @throws DriverSQLException if fails (include ANY exception that can be thrown in the code)
   */
  public synchronized ResultSet getCatalogs() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.ConnectionGetCatalogs);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName());

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getCatalogs();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost and automatic reconnect failed while executing "
                + getCurrentMethodName(), e1);
      }
    }
  }

  /**
   * Retrieves the current holdability of <code>ResultSet</code> objects
   * created using this <code>Connection</code> object. If the value was not
   * set using setHoldability(), the default value of the database is returned.
   * 
   * @return the holdability, one of
   *         <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
   *         <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
   * @throws SQLException if a database access occurs
   * @see #setHoldability
   * @see ResultSet
   * @since JDK 1.4
   */
  public int getHoldability() throws SQLException
  {
    if (holdability == HOLDABILITY_NOT_SET)
      holdability = getMetaData().getResultSetHoldability();
    return holdability;
  }

  /**
   * Gets this Connection's current transaction isolation mode. If the
   * transaction isolation has not been set using setTransactionIsolation, this
   * method will return by default
   * java.sql.Connection.TRANSACTION_READ_UNCOMMITTED whatever transaction
   * isolation is really used by the cluster nodes. If you want to enfore
   * TRANSACTION_READ_UNCOMMITTED, you have to explicitely call
   * setTransactionIsolation(java.sql.Connection.TRANSACTION_READ_UNCOMMITTED)
   * 
   * @return the current <code>TRANSACTION_*</code> mode value
   * @exception DriverSQLException if a database access error occurs
   * @see #setTransactionIsolation(int)
   */
  public int getTransactionIsolation() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    // Warning, here we assume that if no transaction isolation is set the
    // database will provide READ_UNCOMMITED.
    if (isolationLevel == DEFAULT_TRANSACTION_ISOLATION_LEVEL)
      return driver.getDefaultTransactionIsolationLevel();
    return isolationLevel;
  }

  /**
   * Sequoia does NOT support type map.
   * 
   * @return an exception
   * @exception SQLException not supported
   */
  public java.util.Map getTypeMap() throws SQLException
  {
    throw new NotImplementedException(getCurrentMethodName());
  }

  /**
   * Returns the first warning reported by calls on this connection. Subsequent
   * warnings will be chained to this SQLWarning<br>
   * <B>Note: </B> If the 'persistent connections' option is set to false, this
   * function will always return null.
   * 
   * @return the first SQLWarning or null
   * @exception DriverSQLException if a database access error occurs or this
   *              method is called on a closed connection
   */
  public SQLWarning getWarnings() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    if (!persistentConnection || !retrieveSQLWarnings)
      return firstWarning;

    try
    {
      sendCommand(Commands.ConnectionGetWarnings);
      socketOutput.writeLong(persistentConnectionId);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName());
      return receiveSQLWarnings();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      { // Connection failed, try to reconnect and re-send command
        reconnect();
        return getWarnings();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while getting SQL Warnings and automatic reconnect failed("
                + e1 + ")", e1);
      }
    }
  }

  /**
   * Returns <code>true</code> if the connection has been closed by the user
   * (but Sequoia may leave it open underneath, unknown to the user).
   * 
   * @return <code>true</code> if connection has never been opened or
   *         <code>close()</code> has been called
   */
  public boolean isClosed()
  {
    return isClosed;
  }

  /**
   * Tests to see if the connection is in read only Mode. Note that we cannot
   * really put the database in read only mode, but we pretend we can by
   * returning the value of the <code>readOnly</code> flag.
   * 
   * @return <code>true</code> if the connection is read only
   */
  public boolean isReadOnly()
  {
    return readOnly;
  }

  /**
   * As we can't know for sure which database will execute this request (now or
   * later), we can't translate it in the native query language of the
   * underlying DBMS. Therefore the query is returned unchanged.
   * 
   * @param query the query to change
   * @return the original query
   */
  public String nativeSQL(String query)
  {
    return query;
  }

  /**
   * A SQL statement with or without <code>IN</code> parameters can be
   * pre-compiled and stored in a PreparedStatement object. This object can then
   * be used to efficiently execute this statement multiple times.
   * 
   * @param sql a SQL statement that may contain one or more '?' IN * parameter
   *          placeholders
   * @return a new <code>PreparedStatement</code> object containing the
   *         pre-compiled statement.
   * @exception SQLException if a database access error occurs.
   */
  public java.sql.PreparedStatement prepareStatement(String sql)
      throws SQLException
  {
    throwSQLExceptionIfClosed();
    return new PreparedStatement(this, sql, driver);
  }

  /**
   * Creates a default <code>PreparedStatement</code> object that has the
   * capability to retrieve auto-generated keys. The given constant tells the
   * driver whether it should make auto-generated keys available for retrieval.
   * This parameter is ignored if the SQL statement is not an
   * <code>INSERT</code> statement.
   * <p>
   * <b>Note: </b> This method is optimized for handling parametric SQL
   * statements that benefit from precompilation. If the driver supports
   * precompilation, the method <code>prepareStatement</code> will send the
   * statement to the database for precompilation. Some drivers may not support
   * precompilation. In this case, the statement may not be sent to the database
   * until the <code>PreparedStatement</code> object is executed. This has no
   * direct effect on users; however, it does affect which methods throw certain
   * SQLExceptions.
   * <p>
   * Result sets created using the returned <code>PreparedStatement</code>
   * object will by default be type <code>TYPE_FORWARD_ONLY</code> and have a
   * concurrency level of <code>CONCUR_READ_ONLY</code>.
   * 
   * @param sql an SQL statement that may contain one or more '?' IN parameter
   *          placeholders
   * @param autoGeneratedKeys a flag indicating whether auto-generated keys
   *          should be returned; one of
   *          <code>Statement.RETURN_GENERATED_KEYS</code> or
   *          <code>Statement.NO_GENERATED_KEYS</code>
   * @return a new <code>PreparedStatement</code> object, containing the
   *         pre-compiled SQL statement, that will have the capability of
   *         returning auto-generated keys
   * @exception SQLException if a database access error occurs or the given
   *              parameter is not a <code>Statement</code> constant
   *              indicating whether auto-generated keys should be returned
   * @since JDK 1.4
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int autoGeneratedKeys) throws SQLException
  {
    throwSQLExceptionIfClosed();
    PreparedStatement ps = new PreparedStatement(this, sql, driver,
        autoGeneratedKeys);
    return ps;
  }

  /**
   * Creates a default <code>PreparedStatement</code> object capable of
   * returning the auto-generated keys designated by the given array. This array
   * contains the indexes of the columns in the target table that contain the
   * auto-generated keys that should be made available. This array is ignored if
   * the SQL statement is not an <code>INSERT</code> statement.
   * <p>
   * An SQL statement with or without IN parameters can be pre-compiled and
   * stored in a <code>PreparedStatement</code> object. This object can then
   * be used to efficiently execute this statement multiple times.
   * <p>
   * <b>Note: </b> This method is optimized for handling parametric SQL
   * statements that benefit from precompilation. If the driver supports
   * precompilation, the method <code>prepareStatement</code> will send the
   * statement to the database for precompilation. Some drivers may not support
   * precompilation. In this case, the statement may not be sent to the database
   * until the <code>PreparedStatement</code> object is executed. This has no
   * direct effect on users; however, it does affect which methods throw certain
   * SQLExceptions.
   * <p>
   * Result sets created using the returned <code>PreparedStatement</code>
   * object will by default be type <code>TYPE_FORWARD_ONLY</code> and have a
   * concurrency level of <code>CONCUR_READ_ONLY</code>.
   * 
   * @param sql an SQL statement that may contain one or more '?' IN parameter
   *          placeholders
   * @param columnIndexes an array of column indexes indicating the columns that
   *          should be returned from the inserted row or rows
   * @return a new <code>PreparedStatement</code> object, containing the
   *         pre-compiled statement, that is capable of returning the
   *         auto-generated keys designated by the given array of column indexes
   * @exception SQLException if a database access error occurs
   * @since JDK 1.4
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int[] columnIndexes) throws SQLException
  {
    throwSQLExceptionIfClosed();
    PreparedStatement ps = new PreparedStatement(this, sql, driver,
        PreparedStatement.RETURN_GENERATED_KEYS);
    return ps;
  }

  /**
   * Creates a default <code>PreparedStatement</code> object capable of
   * returning the auto-generated keys designated by the given array. This array
   * contains the names of the columns in the target table that contain the
   * auto-generated keys that should be returned. This array is ignored if the
   * SQL statement is not an <code>INSERT</code> statement.
   * <p>
   * An SQL statement with or without IN parameters can be pre-compiled and
   * stored in a <code>PreparedStatement</code> object. This object can then
   * be used to efficiently execute this statement multiple times.
   * <p>
   * <b>Note: </b> This method is optimized for handling parametric SQL
   * statements that benefit from precompilation. If the driver supports
   * precompilation, the method <code>prepareStatement</code> will send the
   * statement to the database for precompilation. Some drivers may not support
   * precompilation. In this case, the statement may not be sent to the database
   * until the <code>PreparedStatement</code> object is executed. This has no
   * direct effect on users; however, it does affect which methods throw certain
   * <code>SQLExceptions</code>.
   * <p>
   * Result sets created using the returned <code>PreparedStatement</code>
   * object will by default be type <code>TYPE_FORWARD_ONLY</code> and have a
   * concurrency level of <code>CONCUR_READ_ONLY</code>.
   * 
   * @param sql an SQL statement that may contain one or more '?' IN parameter
   *          placeholders
   * @param columnNames an array of column names indicating the columns that
   *          should be returned from the inserted row or rows
   * @return a new <code>PreparedStatement</code> object, containing the
   *         pre-compiled statement, that is capable of returning the
   *         auto-generated keys designated by the given array of column names
   * @exception SQLException if a database access error occurs
   * @since JDK 1.4
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      String[] columnNames) throws SQLException
  {
    throwSQLExceptionIfClosed();
    PreparedStatement ps = new PreparedStatement(this, sql, driver,
        PreparedStatement.RETURN_GENERATED_KEYS);
    return ps;
  }

  /**
   * A SQL statement with or without IN parameters can be pre-compiled and
   * stored in a <code>PreparedStatement</code> object. This object can then
   * be used to efficiently execute this statement multiple times.
   * 
   * @param sql a SQL statement that may contain one or more '?' IN
   * @param resultSetType <code>ResultSetType</code> to use
   * @param resultSetConcurrency <code>ResultSetConcurrency</code> to use
   * @return a new <code>PreparedStatement</code> object
   * @exception SQLException passed through from the constructor
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int resultSetType, int resultSetConcurrency) throws SQLException
  {
    throwSQLExceptionIfClosed();
    PreparedStatement s = new PreparedStatement(this, sql, driver);
    s.setResultSetType(resultSetType);
    s.setResultSetConcurrency(resultSetConcurrency);
    return s;
  }

  /**
   * Creates a <code>PreparedStatement</code> object that will generate
   * <code>ResultSet</code> objects with the given type, concurrency, and
   * holdability.
   * <p>
   * This method is the same as the <code>prepareStatement</code> method
   * above, but it allows the default result set type, concurrency, and
   * holdability to be overridden.
   * 
   * @param sql a <code>String</code> object that is the SQL statement to be
   *          sent to the database; may contain one or more ? IN parameters
   * @param resultSetType one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
   *          <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
   *          <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
   * @param resultSetConcurrency one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
   *          <code>ResultSet.CONCUR_UPDATABLE</code>
   * @param resultSetHoldability one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
   *          <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
   * @return a new <code>PreparedStatement</code> object, containing the
   *         pre-compiled SQL statement, that will generate
   *         <code>ResultSet</code> objects with the given type, concurrency,
   *         and holdability
   * @exception SQLException if a database access error occurs or the given
   *              parameters are not <code>ResultSet</code> constants
   *              indicating type, concurrency, and holdability
   * @see ResultSet
   * @since JDK 1.4
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int resultSetType, int resultSetConcurrency, int resultSetHoldability)
      throws SQLException
  {
    throwSQLExceptionIfClosed();
    PreparedStatement ps = new PreparedStatement(this, sql, driver);
    ps.setResultSetType(resultSetType);
    ps.setResultSetConcurrency(resultSetConcurrency);
    setHoldability(resultSetHoldability);
    return ps;
  }

  /**
   * Creates a CallableStatement that contains sql and produces a ResultSet that
   * is TYPE_SCROLL_INSENSITIVE and CONCUR_READ_ONLY.
   * 
   * @param sql SQL request
   * @return a CallableStatement
   * @exception SQLException not supported
   */
  public java.sql.CallableStatement prepareCall(String sql) throws SQLException
  {
    throwSQLExceptionIfClosed();
    return prepareCall(sql, java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE,
        java.sql.ResultSet.CONCUR_READ_ONLY);
  }

  /**
   * @see java.sql.Connection#prepareCall(java.lang.String, int, int)
   */
  public java.sql.CallableStatement prepareCall(String sql, int resultSetType,
      int resultSetConcurrency) throws SQLException
  {
    throwSQLExceptionIfClosed();
    CallableStatement c = new CallableStatement(this, sql, driver);
    c.setResultSetType(resultSetType);
    c.setResultSetConcurrency(resultSetConcurrency);
    return c;
  }

  /**
   * Creates a <code>CallableStatement</code> object that will generate
   * <code>ResultSet</code> objects with the given type and concurrency. This
   * method is the same as the <code>prepareCall</code> method above, but it
   * allows the default result set type, result set concurrency type and
   * holdability to be overridden.
   * 
   * @param sql a <code>String</code> object that is the SQL statement to be
   *          sent to the database; may contain on or more ? parameters
   * @param resultSetType one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
   *          <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
   *          <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
   * @param resultSetConcurrency one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
   *          <code>ResultSet.CONCUR_UPDATABLE</code>
   * @param resultSetHoldability one of the following <code>ResultSet</code>
   *          constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
   *          <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
   * @return a new <code>CallableStatement</code> object, containing the
   *         pre-compiled SQL statement, that will generate
   *         <code>ResultSet</code> objects with the given type, concurrency,
   *         and holdability
   * @exception SQLException if a database access error occurs or the given
   *              parameters are not <code>ResultSet</code> constants
   *              indicating type, concurrency, and holdability
   * @see ResultSet
   * @since JDK 1.4
   */
  public java.sql.CallableStatement prepareCall(String sql, int resultSetType,
      int resultSetConcurrency, int resultSetHoldability) throws SQLException
  {
    throwSQLExceptionIfClosed();
    CallableStatement cs = new CallableStatement(this, sql, driver);
    cs.setResultSetType(resultSetType);
    cs.setResultSetConcurrency(resultSetConcurrency);
    setHoldability(resultSetHoldability);
    return cs;
  }

  /**
   * Removes the given <code>Savepoint</code> object from the current
   * transaction. Any reference to the savepoint after it have been removed will
   * cause an <code>SQLException</code> to be thrown.
   * 
   * @param savepoint the <code>Savepoint</code> object to be removed
   * @exception DriverSQLException if a database access error occurs or the
   *              given <code>Savepoint</code> object is not a valid savepoint
   *              in the current transaction
   * @since JDK 1.4
   */
  public void releaseSavepoint(Savepoint savepoint) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    if (savepoint == null)
      throw new DriverSQLException("Savepoint cannot be null");

    if (autoCommit)
      throw new DriverSQLException(
          "Trying to release a savepoint in autocommit mode");

    if (driver == null)
      throw new DriverSQLException("No driver to release a savepoint");

    try
    {
      sendCommand(Commands.ReleaseSavepoint);
      savepointOnStream(savepoint);
      socketOutput.flush();

      this.receiveBooleanOrException();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        // Connection failed, try to reconnect and release the savepoint again
        reconnect();
        boolean done = retrieveReleaseSavepoint(savepoint);
        if (!done)
        {
          releaseSavepoint(savepoint);
        }
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while releasing savepoint '" + savepoint
                + "' and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  /**
   * Drops all changes made since the previous commit/rollback and releases any
   * database locks currently held by this connection. If the connection was in
   * autocommit mode, we throw a DriverSQLException.
   * 
   * @exception DriverSQLException if a database access error occurs or the
   *              connection is in autocommit mode
   * @see Connection#commit()
   */
  public synchronized void rollback() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    if (autoCommit)
      throw new DriverSQLException(
          "Trying to rollback a connection in autocommit mode");

    // Check if we are not rollbacking an empty transaction (not started yet)
    if (mustBeginTransaction)
      return;

    try
    {
      sendCommand(Commands.Rollback);
      socketOutput.flush();

      // rollback acknowledgement
      long acknowledgedTransactionId = receiveLongOrException();
      if (acknowledgedTransactionId != transactionId)
      {
        throw new DriverSQLException(
            "Protocol error during rollback (acknowledge transaction ID = "
                + acknowledgedTransactionId + ", expected transaction ID = "
                + transactionId + ")");
      }
      mustBeginTransaction = true;
      writeExecutedInTransaction = false;
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the rollback
      try
      {
        reconnect();

        long acknowledgedTransactionId = retrieveRollbackResult();
        if (acknowledgedTransactionId != transactionId)
        {
          throw new DriverSQLException(
              "Protocol error during rollback failover (acknowledge transaction ID = "
                  + acknowledgedTransactionId + ", expected transaction ID = "
                  + transactionId + ")");
        }
        mustBeginTransaction = true;

        // The controller will automatically redo the rollback if it was not
        // done earlier so we can safely return here, this is a success.
        return;
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost during rollback of transaction '" + transactionId

            + "' and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  /**
   * Undoes all changes made after the given <code>Savepoint</code> object was
   * set.
   * <p>
   * This method should be used only when auto-commit has been disabled.
   * 
   * @param savepoint the <code>Savepoint</code> object to roll back to
   * @exception DriverSQLException if a database access error occurs, the
   *              <code>Savepoint</code> object is no longer valid, or this
   *              <code>Connection</code> object is currently in auto-commit
   *              mode
   * @see Savepoint
   * @see #rollback()
   * @since JDK 1.4
   */
  public void rollback(Savepoint savepoint) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    if (savepoint == null)
      throw new DriverSQLException("Savepoint cannot be null");

    if (autoCommit)
      throw new DriverSQLException(
          "Trying to rollback to a savepoint in autocommit mode");

    if (driver == null)
      throw new DriverSQLException("No driver to rollback to savepoint");

    try
    {
      sendCommand(Commands.RollbackToSavepoint);
      savepointOnStream(savepoint);
      socketOutput.flush();

      this.receiveBooleanOrException();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        // Connection failed, try to reconnect and rollback again if checkpoint
        // still exists
        reconnect();
        boolean isCheckpointRemoved = retrieveReleaseSavepoint(savepoint);
        if (!isCheckpointRemoved)
          rollback(savepoint);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while rollbacking to savepoint '" + savepoint
                + "' and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  private void begin() throws DriverSQLException, ProtocolException
  {
    try
    {
      sendCommand(Commands.Begin);
      socketOutput.flush();

      transactionId = receiveLongOrException();

      if (sequoiaUrl.isDebugEnabled())
        System.out
            .println("Transaction " + transactionId + " has been started");
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      // Connection failed, try to reconnect and re-exec the query
      if (sequoiaUrl.isInfoEnabled())
        System.out.println("I/O Error while trying to disable autocommit\n"
            + e.getLocalizedMessage());
      reconnect();
      begin();
    }
  }

  /**
   * To keep track of active transactions, sequoia uses the old BEGIN modeless
   * model. Backend's connections on the controller are always maintained/reset
   * in autocommit mode = true, except of course when in the middle of an active
   * transaction. So part of our job here is to translate the new ODBC/JDBC
   * autoCommit mode concept to the old BEGIN model.
   * 
   * @see Commands#SetAutoCommit
   * @see java.sql.Connection#setAutoCommit(boolean)
   * @param autoCommitArg <code>true</code> enables auto-commit;
   *          <code>false</code> disables it
   * @exception DriverSQLException if a database access error occurs
   * @throws DriverIOException if an IO error occured with the controller
   */
  public synchronized void setAutoCommit(boolean autoCommitArg)
      throws DriverSQLException, DriverIOException
  {
    throwSQLExceptionIfClosed();

    // Do nothing if already in the right state
    if (this.autoCommit == autoCommitArg)
      return;

    // true -> false (send nothing, lazy begin)
    if (this.autoCommit)
    {
      this.autoCommit = false;
      this.mustBeginTransaction = true;
      return;
    }

    // false -> true
    if (mustBeginTransaction)
    { // Transaction has NOT yet begun
      // Just cancel the (lazy, not yet done) begin
      mustBeginTransaction = false;
      this.autoCommit = true;
      return;
    }
    else
    {
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Setting connection in autocommit mode");
      doCommit();
      this.autoCommit = true;
    }
  }

  /**
   * Change the current catalog
   * 
   * @param catalog a <code>String</code> value
   * @exception SQLException if fails or if catalog name is invalid
   */
  public synchronized void setCatalog(String catalog) throws SQLException
  {
    throwSQLExceptionIfClosed();
    if (catalog == null)
      throw new DriverSQLException("Invalid Catalog");
    sequoiaUrl.setUrl(driver.changeDatabaseName(sequoiaUrl.getUrl(), catalog));

    try
    {
      sendCommand(Commands.ConnectionSetCatalog);
      socketOutput.writeLongUTF(catalog);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with catalog '" + catalog + "'");

      if (!receiveBooleanOrException())
        throw new DriverSQLException("Invalid Catalog");

    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        // Connection failed, try to reconnect and re-set the catalog
        reconnect();
        setCatalog(catalog);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while setting the catalog '" + catalog
                + "' and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  /**
   * Changes the holdability of <code>ResultSet</code> objects created using
   * this <code>Connection</code> object to the given holdability.
   * 
   * @param holdability a <code>ResultSet</code> holdability constant; one of
   *          <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
   *          <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
   * @throws SQLException if a database access occurs, the given parameter is
   *           not a <code>ResultSet</code> constant indicating holdability,
   *           or the given holdability is not supported
   * @see #getHoldability
   * @see ResultSet
   * @since JDK 1.4
   */
  public void setHoldability(int holdability) throws SQLException
  {
    if ((holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT)
        && (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT))
      throw new SQLException("Invalid holdaibility value " + holdability);
    this.holdability = holdability;
  }

  /**
   * You can put a connection in read-only mode as a hint to enable database
   * optimizations
   * 
   * @param readOnly <code>true</code> enables read-only mode;
   *          <code>false</code> disables it
   * @exception DriverSQLException if a database access error occurs
   */
  public void setReadOnly(boolean readOnly) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();

    try
    {
      sendCommand(Commands.SetReadOnly);
      socketOutput.writeBoolean(readOnly);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Setting connection to read-only=" + readOnly);

      receiveBooleanOrException();
      // Success
      this.readOnly = readOnly;
      return;

    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException ioe)
    {
      try
      {
        // Connection failed, try to reconnect and re-set the transaction
        // isolation level
        reconnect();
        setReadOnly(readOnly);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while setting the connection to read-only="
                + readOnly + " and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  private static final int SAVEPOINT_NOT_SET = -1;

  /**
   * Creates an unnamed savepoint in the current transaction and returns the new
   * <code>Savepoint</code> object that represents it.
   * 
   * @return the new <code>Savepoint</code> object
   * @exception DriverSQLException if a database access error occurs or this
   *              <code>Connection</code> object is currently in auto-commit
   *              mode
   * @see Savepoint
   * @since JDK 1.4
   */
  public Savepoint setSavepoint() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    beginTransactionIfNeeded();
    if (autoCommit)
      throw new DriverSQLException(
          "Trying to set a savepoint in autocommit mode");

    if (driver == null)
      throw new DriverSQLException("No driver to set a savepoint");

    int savepointId = SAVEPOINT_NOT_SET;
    try
    {
      sendCommand(Commands.SetUnnamedSavepoint);
      socketOutput.flush();

      savepointId = receiveIntOrException();
      return new org.continuent.sequoia.driver.Savepoint(savepointId);
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException ioe)
    {
      try
      {
        // Connection failed, try to reconnect and re-set the savepoint if it
        // was not set before the failure
        reconnect();
        org.continuent.sequoia.driver.Savepoint savepoint = new org.continuent.sequoia.driver.Savepoint(
            savepointId);
        boolean checkpointDoesNotExit = (savepointId == SAVEPOINT_NOT_SET)
            || retrieveReleaseSavepoint(savepoint);
        if (checkpointDoesNotExit)
          return this.setSavepoint(); // retry, did not work the first time
        else
          return savepoint; // ok, already set
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while setting an unnamed savepoint and automatic reconnect failed("
                + e1 + ")", e1);
      }
    }
  }

  /**
   * Creates a savepoint with the given name in the current transaction and
   * returns the new <code>Savepoint</code> object that represents it.
   * 
   * @param name a <code>String</code> containing the name of the savepoint
   * @return the new <code>Savepoint</code> object
   * @exception DriverSQLException if a database access error occurs or this
   *              <code>Connection</code> object is currently in auto-commit
   *              mode
   * @see Savepoint
   * @since JDK 1.4
   */
  public Savepoint setSavepoint(String name) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    beginTransactionIfNeeded();
    if (name == null)
      throw new IllegalArgumentException("Savepoint name cannot be null");

    if (autoCommit)
      throw new DriverSQLException(
          "Trying to set a savepoint in autocommit mode");

    if (driver == null)
      throw new DriverSQLException("No driver to set a savepoint");

    try
    {
      sendCommand(Commands.SetNamedSavepoint);
      socketOutput.writeLongUTF(name);
      socketOutput.flush();

      this.receiveBooleanOrException();
      return new org.continuent.sequoia.driver.Savepoint(name);
    }
    catch (SerializableException se)
    {
      throw new DriverSQLException(se);
    }
    catch (IOException e)
    {
      try
      {
        // Connection failed, try to reconnect and re-set the savepoint if it
        // was not set before the failure
        reconnect();
        org.continuent.sequoia.driver.Savepoint savepoint = new org.continuent.sequoia.driver.Savepoint(
            name);
        boolean checkpointDoesNotExit = retrieveReleaseSavepoint(savepoint);
        if (checkpointDoesNotExit)
          return setSavepoint(name);
        else
          return savepoint;
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while setting the savepoint '" + name
                + "' and automatic reconnect failed(" + e1 + ")", e1);
      }
    }
  }

  /**
   * You can call this method to try to change the transaction isolation level
   * using one of the TRANSACTION_* values.
   * <p>
   * <B>Note: </B> this method cannot be called while in the middle of a
   * transaction. The JDBC spec says it should trigger a commit. We should
   * probably let the backend handle this, not trying to add our own complexity.
   * 
   * @param level one of the TRANSACTION_* isolation values with * the exception
   *          of TRANSACTION_NONE; some databases may * not support other values
   * @exception DriverSQLException if a database access error occurs
   * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel
   */
  public synchronized void setTransactionIsolation(int level)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    // Check if we are in a transaction or not. We have no trace on the driver
    // side if a read query has already been executed or not in the current
    // transaction (if any). We let the controller check for this (we only check
    // for writes here) as well as if the underlying databases support the
    // transaction isolation level. If this is not supported, the driver will
    // send back an exception.
    if ((autoCommit == false) && writeExecutedInTransaction)
      throw new DriverSQLException(
          getCurrentMethodName()
              + " cannot be called in a transaction that has executed write requests.");

    if (level != isolationLevel)
    { // Only try to change if there is a new value
      if ((level == TRANSACTION_READ_COMMITTED)
          || (level == TRANSACTION_READ_UNCOMMITTED)
          || (level == TRANSACTION_REPEATABLE_READ)
          || (level == TRANSACTION_SERIALIZABLE))
      {
        try
        {
          sendCommand(Commands.SetTransactionIsolation);
          socketOutput.writeInt(level);
          socketOutput.flush();

          if (sequoiaUrl.isDebugEnabled())
            System.out.println("Setting transaction isolation level to "
                + level);

          receiveBooleanOrException();
          // Success
          isolationLevel = level;
          return;

        }
        catch (SerializableException e)
        {
          throw new DriverSQLException(e);
        }
        catch (IOException ioe)
        {
          try
          {
            // Connection failed, try to reconnect and re-set the transaction
            // isolation level
            reconnect();
            setTransactionIsolation(level);
          }
          catch (DriverSQLException e1)
          {
            throw new DriverSQLException(
                "Connection lost while setting the transaction isolation level '"
                    + level + "' and automatic reconnect failed(" + e1 + ")",
                e1);
          }
        }
      }
      else
        throw new DriverSQLException("Invalid transaction isolation level "
            + level);
    } // we were already in that level; do nothing.
  }

  /**
   * Sequoia does NOT support type map.
   * 
   * @param map ignored
   * @exception SQLException not supported
   */
  public void setTypeMap(java.util.Map map) throws SQLException
  {
    throw new NotImplementedException(getCurrentMethodName());
  }

  /*
   * Connection Sequoia internals
   */

  /**
   * Begins a new transaction if needed (<code>mustBeginTransaction</code> is
   * set to <code>true</code>).
   * 
   * @throws DriverSQLException if begin fails
   */
  private void beginTransactionIfNeeded() throws DriverSQLException
  {
    if (!mustBeginTransaction)
      return;

    begin();
    this.mustBeginTransaction = false;
  }

  /**
   * Fetch multiple results from a query executed with Statement.execute()
   * 
   * @return the list of results
   */
  private List fetchMultipleResultsFromStream(String callerName)
      throws IOException, SerializableException, ProtocolException
  {
    boolean hasResult;
    int updateCount = 0;
    LinkedList results = new LinkedList();
    do
    {
      hasResult = receiveBooleanOrException();
      if (hasResult)
      {
        DriverResultSet rs = receiveResultSet(callerName);
        if (rs == null)
        {
          // This happens during transparent failover when the controller's
          // request cache hasn't found a result for a given request.
          return null;
        }
        else
        {
          results.addLast(rs);
        }
      }
      else
      {
        updateCount = receiveIntOrException();
        results.addLast(new Integer(updateCount));
      }
    }
    while (hasResult || updateCount != -1);
    return results;
  }

  /**
   * Fetch named parameters from the stream.
   * 
   * @return HashMap of <ParameterName,ParameterValue> or null
   * @throws ProtocolException if a protocol error occured
   * @throws IOException if an error with the socket occured
   * @throws SerializableException if a problem occured deserializing an object
   */
  private HashMap fetchNamedParameters() throws ProtocolException, IOException,
      SerializableException
  {
    if (sequoiaUrl.isDebugEnabled())
      System.out.println("Retrieving named parameters");
    String paramName = receiveStringOrException();
    if ("0".equals(paramName))
      return null;
    HashMap params = new HashMap();
    while (!"0".equals(paramName))
    {
      Object value = receiveObject();
      params.put(paramName, value);
      paramName = receiveStringOrException();
    }
    return params;
  }

  /**
   * Fetch OUT parameters from the stream.
   * 
   * @return HashMap of <ParameterName,ParameterValue> or null
   * @throws ProtocolException if a protocol error occured
   * @throws IOException if an error with the socket occured
   * @throws SerializableException if a problem occured deserializing an object
   */
  private HashMap fetchOutParameters() throws ProtocolException, IOException,
      SerializableException
  {
    if (sequoiaUrl.isDebugEnabled())
      System.out.println("Retrieving out parameters");

    int index = receiveIntOrException();
    if (index == 0)
      return null;
    HashMap params = new HashMap();
    while (index != 0)
    {
      Object value = receiveObject();
      params.put(new Integer(index), value);
      index = receiveIntOrException();
    }
    return params;
  }

  /**
   * Set the autocommit mode and read-only status on this request.
   * 
   * @param request The request to set
   */
  private void setConnectionParametersOnRequest(Request request)
  {
    request.setIsAutoCommit(autoCommit);
  }

  /**
   * Receive an object from the stream by fetching a tag first and then the
   * value from the proper serializer.
   * 
   * @return the deserialized object (can be null)
   * @throws IOException if a socket error occurs
   * @throws ProtocolException if a protocol corruption is detected
   */
  private Object receiveObject() throws IOException, ProtocolException
  {
    TypeTag tag = new TypeTag(socketInput);

    // Handle the null specific case
    if (TypeTag.JAVA_NULL.equals(tag))
      return null;

    // We have a real object to de-serialize
    try
    {
      Serializer serializer = SQLDataSerialization.getSerializer(tag);
      return serializer.receiveFromStream(socketInput);
    }
    catch (IllegalArgumentException iae)
    {
      ProtocolException pe = new ProtocolException(
          "Protocol corruption: received unknown TypeTag " + tag
              + " when receiving object.");
      pe.initCause(iae);
      throw pe;
    }
  }

  /**
   * Returns a DriverResultSet read from the stream or throws the
   * SerializableException that came instead
   * 
   * @param callerName used for error messages. Is this really useful?
   * @return received ResultSet
   * @throws IOException stream or protocol error
   * @throws SerializableException received from the controller
   */
  private DriverResultSet receiveResultSet(String callerName)
      throws IOException, ProtocolException, SerializableException
  {
    TypeTag tag = new TypeTag(socketInput);

    if (TypeTag.NULL_RESULTSET.equals(tag))
      return null;

    if (TypeTag.RESULTSET.equals(tag))
    {
      DriverResultSet drs = new DriverResultSet(this);
      return drs;
    }

    if (TypeTag.EXCEPTION.equals(tag))
      throw receiveException();

    throw new ProtocolException(callerName
        + ": expected a resultset, received unexpected tag: " + tag);
  }

  /**
   * Deserialize SQL warnings from the stream: converts BackendDriverException
   * to an SQLWarning chain
   * 
   * @return the deserialized warning chain
   * @throws IOException stream error
   * @throws ProtocolException protocol error
   * @throws SerializableException
   */
  private SQLWarning receiveSQLWarnings() throws IOException,
      ProtocolException, SerializableException
  {
    if (!receiveBooleanOrException())
      // no warning
      return null;
    // Receive the warning as a BackendDriverException
    SerializableException e = receiveException();
    if (!(e instanceof BackendDriverException))
      throw new ProtocolException(
          "Unknown exception received instead of SQLWarning");
    return convertToSQLWarnings(e);
  }

  /**
   * Deserialize an exception from the stream: converts explicit protocol typing
   * into java types.
   * 
   * @return the deserialized exception read from the stream
   * @throws IOException stream error
   * @throws ProtocolException protocol error
   */
  private SerializableException receiveException() throws IOException,
      ProtocolException
  {
    TypeTag exceptionType = new TypeTag(socketInput);

    if (TypeTag.BACKEND_EXCEPTION.equals(exceptionType))
      return new BackendDriverException(socketInput);
    if (TypeTag.CORE_EXCEPTION.equals(exceptionType))
      return new ControllerCoreException(socketInput);

    throw new ProtocolException("received unknown exception type");
  }

  /**
   * Returns a String read from the stream or throws the SerializableException
   * that came instead.
   * 
   * @throws IOException stream or protocol error
   * @throws SerializableException coming from the controller
   * @throws ProtocolException protocol error
   */
  private String receiveStringOrException() throws IOException,
      SerializableException, ProtocolException
  {
    TypeTag tag = new TypeTag(socketInput);
    if (TypeTag.NOT_EXCEPTION.equals(tag))
    {
      String answer = socketInput.readLongUTF();
      return answer;
    }

    throw receiveException();
  }

  /**
   * Returns a boolean read from the stream or throws the SerializableException
   * that came instead.
   * 
   * @throws IOException stream or protocol error
   * @throws SerializableException coming from the controller
   * @throws ProtocolException protocol error
   */
  private boolean receiveBooleanOrException() throws IOException,
      SerializableException, ProtocolException
  {
    TypeTag tag = new TypeTag(socketInput);
    if (TypeTag.NOT_EXCEPTION.equals(tag))
    {
      boolean answer = socketInput.readBoolean();
      return answer;
    }

    throw receiveException();
  }

  /**
   * Returns a int read from the stream or throws the SerializableException that
   * came instead.
   * 
   * @throws IOException stream or protocol error
   * @throws SerializableException coming from the controller
   * @throws ProtocolException protocol error
   */
  private int receiveIntOrException() throws IOException,
      SerializableException, ProtocolException
  {
    TypeTag tag = new TypeTag(socketInput);
    if (TypeTag.NOT_EXCEPTION.equals(tag))
    {
      int answer = socketInput.readInt();
      return answer;
    }

    throw receiveException();
  }

  /**
   * Returns a long read from the stream or throws the SerializableException
   * that came instead.
   * 
   * @throws IOException stream or protocol error
   * @throws SerializableException coming from the controller
   * @throws ProtocolException protocol error
   */
  private long receiveLongOrException() throws IOException,
      SerializableException, ProtocolException
  {
    TypeTag tag = new TypeTag(socketInput);
    if (TypeTag.NOT_EXCEPTION.equals(tag))
    {
      long answer = socketInput.readLong();
      return answer;
    }

    throw receiveException();
  }

  /**
   * Serialize a savepoint on the output stream by sending only the needed
   * parameters to reconstruct it on the controller
   * 
   * @param savepoint the savepoint to send
   * @throws IOException if fails
   */
  private void savepointOnStream(Savepoint savepoint) throws IOException
  {
    writeExecutedInTransaction = true;

    try
    {
      socketOutput.writeLongUTF(savepoint.getSavepointName());
      return;
    }
    catch (SQLException ignore)
    {
      // Ignoring because we are dealing with an un-named savepoint
    }

    try
    {
      socketOutput.writeLongUTF(String.valueOf(savepoint.getSavepointId()));
      return;
    }
    catch (SQLException ignore)
    {
      // We should never get here
    }
  }

  /**
   * Check if the given release savepoint has been successfully performed.
   * 
   * @return true if the release savepoint has been successfully performed
   * @throws DriverSQLException if an error occured
   */
  private boolean retrieveReleaseSavepoint(Savepoint savepoint)
      throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveReleaseSavepoint);
      socketOutput.writeLongUTF(savepoint.getSavepointName());
      socketOutput.flush();
      return receiveBooleanOrException();
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given commit has been successfully performed.
   * 
   * @return the transaction id if the commit has been successfully performed
   * @throws DriverSQLException if an error occured on the commit
   */
  private long retrieveCommitResult() throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveCommitResult);
      socketOutput.flush();
      return receiveLongOrException();
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given rollback has been successfully performed.
   * 
   * @return the transaction id if the rollback has been successfully performed
   * @throws DriverSQLException if an error occured on the rollback
   */
  private long retrieveRollbackResult() throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveRollbackResult);
      socketOutput.flush();
      return receiveLongOrException();
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given query already executed or not on the controller we are
   * currently connected to.
   * 
   * @param request the stored procedure to check
   * @return null if not found or a List composed of a
   *         <code>java.sql.ResultSet</code> value, an <code>ArrayList</code>
   *         of OUT parameters, and a <code>HashMap</code> of named parameters
   *         result objects.
   * @throws DriverSQLException if an error occurs
   */
  private ResultAndWarnings retrieveExecuteQueryResultWithParameters(
      Request request) throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveExecuteQueryResultWithParameters);
      socketOutput.writeLong(request.getId());
      socketOutput.flush();
      SQLWarning sqlw = receiveSQLWarnings();
      DriverResultSet drs = receiveResultSet(getCurrentMethodName());
      if (drs == null)
        return null;
      List result = new ArrayList(3);
      result.add(drs);
      result.add(fetchOutParameters());
      result.add(fetchNamedParameters());
      return new ResultAndWarnings(result, sqlw);
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given query already executed or not on the controller we are
   * currently connected to.
   * 
   * @param request the request to check
   * @return -1 if not found or a List composed of an <code>Integer</code>
   *         (number of updated rows), an <code>ArrayList</code> of OUT
   *         parameters, and a <code>HashMap</code> of named parameters result
   *         objects.
   * @throws DriverSQLException if an error occurs
   */
  private ResultAndWarnings retrieveExecuteUpdateResultWithParameters(
      Request request) throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveExecuteUpdateResultWithParameters);
      socketOutput.writeLong(request.getId());
      socketOutput.flush();
      SQLWarning sqlw = receiveSQLWarnings();
      int updateCount = receiveIntOrException();
      if (updateCount == -1)
        return null; // No result found in failover
      List result = new ArrayList(3);
      result.add(new Integer(updateCount));
      result.add(fetchOutParameters());
      result.add(fetchNamedParameters());
      return new ResultAndWarnings(result, sqlw);
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given query already executed or not on the controller we are
   * currently connected to.
   * 
   * @param request the request to check
   * @return null if not found or a List composed of 1. a <code>List</code> of
   *         results <code>java.sql.ResultSet</code> value, 2. an
   *         <code>ArrayList</code> of OUT parameters, and 3. a
   *         <code>HashMap</code> of named parameters result objects.
   * @throws DriverSQLException if an error occurs
   */
  private ResultAndWarnings retrieveExecuteResultWithParameters(Request request)
      throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveExecuteResultWithParameters);
      socketOutput.writeLong(request.getId());
      socketOutput.flush();
      SQLWarning statementWarnings = receiveSQLWarnings();
      List results = fetchMultipleResultsFromStream(getCurrentMethodName());
      if (results == null)
        return null; // No result found in failover
      List result = new ArrayList(3);
      result.add(results);
      result.add(fetchOutParameters());
      result.add(fetchNamedParameters());
      return new ResultAndWarnings(result, statementWarnings);
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given query already executed or not on the controller we are
   * currently connected to.
   * 
   * @param request the request to check
   * @return int the number of updated rows or -1 if not found
   * @throws DriverSQLException if an error occurs
   */
  private ResultAndWarnings retrieveExecuteUpdateResult(Request request)
      throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveExecuteUpdateResult);
      socketOutput.writeLong(request.getId());
      socketOutput.flush();
      SQLWarning sqlw = receiveSQLWarnings();
      int uc = receiveIntOrException();
      return new ResultAndWarnings(uc, sqlw);
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given query already executed or not on the controller we are
   * currently connected to.
   * 
   * @param request the request to check
   * @return int the number of updated rows or -1 if not found
   * @throws DriverSQLException if an error occurs
   */
  private DriverGeneratedKeysResult retrieveExecuteUpdateWithKeysResult(
      Request request) throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveExecuteUpdateWithKeysResult);
      socketOutput.writeLong(request.getId());
      socketOutput.flush();

      SQLWarning sqlw = receiveSQLWarnings();
      int updateCount = receiveIntOrException();
      if (updateCount == -1)
        return null;

      // Fetch the ResultSet containing the autogenerated keys.
      TypeTag tag = new TypeTag(socketInput);
      if (TypeTag.RESULTSET.equals(tag))
      {
        DriverResultSet drs = new DriverResultSet(this);
        return new DriverGeneratedKeysResult(drs, updateCount, sqlw);
      }

      if (TypeTag.NULL_RESULTSET.equals(tag))
        return new DriverGeneratedKeysResult(null, updateCount, sqlw);

      // Error, unexpected answer
      throw new ProtocolException(getCurrentMethodName()
          + ": protocol corruption for request "
          + request.getSqlShortForm(ABBREV_REQUEST_LENGTH));
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  /**
   * Check if the given query already executed or not on the controller we are
   * currently connected to.
   * 
   * @param request the request to check
   * @return a <code>List</code> of results or null if not found
   * @throws DriverSQLException if an error occurs
   */
  private ResultAndWarnings retrieveExecuteResult(Request request)
      throws DriverSQLException
  {
    try
    {
      sendCommand(Commands.RetrieveExecuteResult);
      socketOutput.writeLong(request.getId());
      socketOutput.flush();
      SQLWarning statementWarnings = receiveSQLWarnings();
      List resList = fetchMultipleResultsFromStream(getCurrentMethodName());
      return new ResultAndWarnings(resList, statementWarnings);
    }
    catch (Throwable e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + " failed on new controller (" + e + ")");
    }
  }

  void reallyClose() throws IOException, DriverSQLException
  {
    sendCommand(Commands.Close);
  }

  /**
   * Try to reconnect to the next controller chosen according to the
   * policy specified in the JDBC URL of this connection.
   * 
   * @throws DriverSQLException if an error occured during reconnect
   */
  private synchronized void reconnect() throws DriverSQLException,
      VirtualDatabaseUnavailableException
  {
    // Get rid of current connection
    try
    {
      this.socket.close();
    }
    catch (IOException ignore)
    {
    }
    try
    {
      this.socketInput.close();
    }
    catch (IOException ignore)
    {
    }
    try
    {
      this.socketOutput.close();
    }
    catch (IOException ignore)
    {
    }
    // only one (Connection) accessing the pool at a time
    synchronized (driver.pendingConnectionClosing)
    {
      if (driver.pendingConnectionClosing.remove(this))
        System.out.println("Warning! Closed call before reconnect");
    }

    SequoiaUrl tempUrl = sequoiaUrl;
    if (persistentConnection)
    {
      /**
       * We do not want to create a persistent connection on the new connection,
       * because we will just close it during the restore operation. We need to
       * make a copy of sequoiaUrl, because it is shared with other connections.
       */
      try
      {
        tempUrl = sequoiaUrl.getTemporaryCloneForReconnection(vdbUser,
            vdbPassword);
      }
      catch (SQLException e)
      {
        // if we could not get a new sequoiaUrl we will just use the original
      }
    }

    Connection newconn = null;
    // At this point, the current controller is down and we have to try a
    // new one that will be allocated by the policy specified in the URL.
    try
    {
      newconn = driver.getConnectionToNewController(tempUrl);
      if (newconn != null)
        controllerInfo = newconn.getControllerInfo();
    }
    catch (AuthenticationException e)
    {
      // Should not happen, this probably mean an inconsistency in controller
      // configuration but safely ignore (see below)
      String msg = "Warning! Authentication exception received on connection retry, controller configuration might be inconsistent";
      if (sequoiaUrl.isInfoEnabled())
        System.out.println(msg);
      throw new DriverSQLException(msg, e);
    }
    catch (NoMoreControllerException nmc)
    {
      throw new DriverSQLException(nmc);
    }
    catch (GeneralSecurityException gse)
    {
      String msg = "Fatal General Security Exception received while trying to reconnect";
      if (sequoiaUrl.isInfoEnabled())
        System.out.println(msg);
      throw new DriverSQLException(msg, gse);
    }

    // newconn cannot be null here else an excepection would have been thrown
    // earlier.

    // Success: let's use the new connection for ourselves
    this.socket = newconn.socket;
    this.socketInput = newconn.socketInput;
    this.socketOutput = newconn.socketOutput;
    this.controllerInfo = newconn.controllerInfo;
    this.isClosed = false;
    try
    {
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Restoring connection state on controller "
            + controllerInfo);
      sendCommand(Commands.RestoreConnectionState);
      socketOutput.writeBoolean(writeExecutedInTransaction);
      if (mustBeginTransaction)
      { // Say that we are in autoCommit, begin will be done later
        // Fixes SEQUOIA-522
        socketOutput.writeBoolean(true);
      }
      else
      {
        socketOutput.writeBoolean(autoCommit);
        if (!autoCommit)
          socketOutput.writeLong(transactionId);
      }
      socketOutput.writeBoolean(persistentConnection);
      if (persistentConnection)
        socketOutput.writeLong(persistentConnectionId);
      socketOutput.writeBoolean(retrieveSQLWarnings);
      socketOutput.flush();
      // Read ack. We won't do anything with it but reading this it is the only
      // way to dectect that the controller is down (see SEQUOIA-632)
      socketInput.readBoolean();

      // Restore read-only state (not part of RestoreConnectionState command for
      // backward compatibility)
      setReadOnly(readOnly);
    }
    catch (IOException e)
    {
      reconnect();
    }
  }

  /**
   * Before sending a command code, checks that the controller is
   * synchronized/ready to accept it. Then sends it.
   * 
   * @param command to send
   * @throws IOException on socket error
   * @throws DriverSQLException on protocol corruption
   */
  private void sendCommand(int command) throws IOException, DriverSQLException
  {
    if (socketInput.readInt() != Commands.ControllerPrompt)
      throw new DriverSQLException(
          "Protocol corruption while trying to send command: " + command
              + ". Check the previous command");
    socketOutput.writeInt(Commands.CommandPrefix);
    socketOutput.writeInt(command);
  }

  /**
   * @see #close()
   */
  private void throwSQLExceptionIfClosed(String message)
      throws DriverSQLException
  {
    if (isClosed)
      throw new DriverSQLException(message);
  }

  /**
   * @see #close()
   */
  private void throwSQLExceptionIfClosed() throws DriverSQLException
  {
    // default message
    throwSQLExceptionIfClosed("Tried to operate on a closed Connection");
  }

  private DriverSQLException wrapIOExceptionInDriverSQLException(
      String callerName, IOException ioe)
  {
    return new DriverSQLException("I/O Error on method " + callerName + "():\n"
        + ioe.getLocalizedMessage(), ioe);
  }

  /**
   * Utility function to convert the given chain of backendException to a chain
   * of SQLWarnings
   * 
   * @param toConvert exception chain to convert
   */
  protected SQLWarning convertToSQLWarnings(SerializableException toConvert)
  {
    if (toConvert == null)
      return null;
    SQLWarning sqlw = new SQLWarning(toConvert.getMessage(), toConvert
        .getSQLState(), toConvert.getErrorCode());
    Throwable t = toConvert.getCause();
    if (t != null)
    {
      if (t instanceof SerializableException)
        sqlw.setNextWarning(convertToSQLWarnings((SerializableException) t));
      else if (sequoiaUrl.isDebugEnabled())
        System.out.println("Unexpected exception type " + t.getClass()
            + "while converting warning chain");
    }
    return sqlw;
  }

  /**
   * Performs a read request and return the reply.
   * 
   * @param request the read request to execute
   * @return a <code>java.sql.ResultSet</code> value
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized DriverResultSet statementExecuteQuery(
      RequestWithResultSetParameters request) throws DriverSQLException,
      NotImplementedException
  {
    throwSQLExceptionIfClosed("Closed connection cannot process request '"
        + request.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    try
    {
      setConnectionParametersOnRequest(request);
      sendCommand(Commands.StatementExecuteQuery);
      request.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with request " + request);

      SQLWarning statementWarnings = receiveSQLWarnings();
      TypeTag tag = new TypeTag(socketInput);

      // First case, we received our ResultSet, let's fetch it.
      if (TypeTag.RESULTSET.equals(tag))
      {
        try
        {
          DriverResultSet drs = new DriverResultSet(this);
          drs.setStatementWarnings(statementWarnings);
          return drs;
        }
        catch (ProtocolException e)
        {
          throw new DriverSQLException("Protocol corruption in "
              + getCurrentMethodName() + " with request "
              + request.getSqlShortForm(ABBREV_REQUEST_LENGTH), e);
        }
        catch (IOException e)
        { // Error while reading, retry
          if (sequoiaUrl.isInfoEnabled())
            System.out.println("IOException occured trying to reconnect ("
                + e.getLocalizedMessage() + ")");
          reconnect();
          return statementExecuteQuery(request);
        }
      }

      if (TypeTag.NULL_RESULTSET.equals(tag))
        return null;

      // From this point on, we had an exception
      if (TypeTag.EXCEPTION.equals(tag))
      {
        Exception recvEx = null;
        recvEx = receiveException();
        // dirty hack until cleanup
        if (recvEx instanceof ControllerCoreException)
          recvEx = ((ControllerCoreException) recvEx)
              .compatibilityWrapperHack();

        if (recvEx instanceof NoMoreBackendException)
        {
          if (sequoiaUrl.isInfoEnabled())
            System.out.println("No more backend available on controller");
          throw new DriverSQLException(recvEx);
        }
        else if (recvEx instanceof IOException)
        {
          // We shouldn't have been able to receive this IOE because it means
          // that the socket died (at the controller side)
          throw new ProtocolException("Received exception of unexpected type ("
              + ((IOException) recvEx).getLocalizedMessage() + ")");
        }
        else if (recvEx instanceof BackendDriverException)
        {
          // TODO: temporary fix until DriverSQLException is fixed
          throw new DriverSQLException((SerializableException) recvEx);
        }
        else if (recvEx instanceof NotImplementedException)
        {
          // incredibly ugly.
          throw (NotImplementedException) recvEx;
        }
        else if (recvEx instanceof SQLException)
        {
          throw new DriverSQLException((SQLException) recvEx);
        }
      }

      // Error, unexpected answer
      throw new ProtocolException("Protocol corruption in "
          + getCurrentMethodName() + " for request "
          + request.getSqlShortForm(ABBREV_REQUEST_LENGTH));
    }
    catch (SerializableException se)
    {
      throw new DriverSQLException(se);
    }
    catch (RuntimeException e)
    {
      e.printStackTrace();
      throw new DriverSQLException(getCurrentMethodName()
          + ": error occured while request '"
          + request.getSqlShortForm(ABBREV_REQUEST_LENGTH)
          + "' was processed by Sequoia Controller", e);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        if (sequoiaUrl.isInfoEnabled())
          System.out.println("IOException occured trying to reconnect ("
              + e.getMessage() + ")");
        reconnect();
        return statementExecuteQuery(request);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " with request '"
            + request.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
  }

  /**
   * Performs a write request and return the number of rows affected.
   * 
   * @param request the write request to execute
   * @return number of rows affected
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized ResultAndWarnings statementExecuteUpdate(
      Request request) throws DriverSQLException
  {
    throwSQLExceptionIfClosed("Closed connection cannot process request '"
        + request.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    boolean requestIdIsSet = false;
    try
    {
      setConnectionParametersOnRequest(request);
      sendCommand(Commands.StatementExecuteUpdate);
      request.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with request " + request);

      request.setId(receiveLongOrException());
      requestIdIsSet = true;
      if (!autoCommit)
        writeExecutedInTransaction = true;

      SQLWarning statementWarnings = receiveSQLWarnings();
      int uc = receiveIntOrException();
      return new ResultAndWarnings(uc, statementWarnings);

    }
    catch (SerializableException se)
    {
      throw new DriverSQLException(se);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        reconnect();
        if (requestIdIsSet)
        { // Controller handled the query, check if it was executed
          ResultAndWarnings result = retrieveExecuteUpdateResult(request);
          if (result != null && result.getUpdateCount() != -1)
          {
            return result;
          }
        }
        // At this point the query failed before any controller succeeded in
        // executing the query

        return statementExecuteUpdate(request);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " with request '"
            + request.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
  }

  /**
   * Call a request that returns a list of results.
   * 
   * @param request the request to execute
   * @return a <code>List</code> of results
   * @throws DriverSQLException if an error occurs
   */
  protected synchronized ResultAndWarnings statementExecute(
      RequestWithResultSetParameters request) throws DriverSQLException
  {
    throwSQLExceptionIfClosed("Closed Connection cannot process request '"
        + request.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    boolean requestIdIsSet = false;
    try
    {
      setConnectionParametersOnRequest(request);
      sendCommand(Commands.StatementExecute);
      request.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with request" + request);

      request.setId(receiveLongOrException());
      requestIdIsSet = true;
      if (!autoCommit)
        writeExecutedInTransaction = true;

      SQLWarning sqlw = receiveSQLWarnings();
      List res = fetchMultipleResultsFromStream(getCurrentMethodName());
      return new ResultAndWarnings(res, sqlw);
    }
    catch (RuntimeException e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + ": Error occured while request '"
          + request.getSqlShortForm(ABBREV_REQUEST_LENGTH)
          + "' was processed by Sequoia Controller", e);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        reconnect();
        if (requestIdIsSet)
        { // Controller handled the query, check if it was executed
          ResultAndWarnings rww = retrieveExecuteResult(request);
          if (rww != null && rww.getResultList() != null)
            return rww;
        }
        // At this point the query failed before any controller succeeded in
        // executing the query

        return statementExecute(request);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " with request '"
            + request.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
  }

  /**
   * Performs a write request and returns the auto-generated keys
   * 
   * @param request the write request to execute
   * @return auto generated keys
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized DriverGeneratedKeysResult statementExecuteUpdateWithKeys(
      Request request) throws DriverSQLException
  {
    throwSQLExceptionIfClosed("Closed Connection cannot process request '"
        + request.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    boolean requestIdIsSet = false;
    try
    {
      setConnectionParametersOnRequest(request);
      sendCommand(Commands.StatementExecuteUpdateWithKeys);
      request.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with request: " + request);

      try
      {
        request.setId(receiveLongOrException());
        requestIdIsSet = true;
        if (!autoCommit)
          writeExecutedInTransaction = true;

        // Receive the warnings
        SQLWarning sqlw = receiveSQLWarnings();

        // Receive the update count or an exception
        int updateCount = receiveIntOrException();

        // Fetch the ResultSet containing the autogenerated keys.
        TypeTag tag = new TypeTag(socketInput);
        if (TypeTag.RESULTSET.equals(tag))
        {
          try
          {
            DriverResultSet drs = new DriverResultSet(this);
            return new DriverGeneratedKeysResult(drs, updateCount, sqlw);
          }
          catch (ProtocolException e)
          {
            throw new DriverSQLException("Protocol corruption in "
                + getCurrentMethodName() + " with request "
                + request.getSqlShortForm(ABBREV_REQUEST_LENGTH), e);
          }
          catch (IOException e)
          { // Error while reading, retry
            if (sequoiaUrl.isInfoEnabled())
              System.out.println("IOException occured trying to reconnect ("
                  + e.getLocalizedMessage() + ")");
            reconnect();
            return statementExecuteUpdateWithKeys(request);
          }
        }

        if (TypeTag.NULL_RESULTSET.equals(tag))
          return new DriverGeneratedKeysResult(null, updateCount, sqlw);

        // Error, unexpected answer
        throw new ProtocolException("Protocol corruption in "
            + getCurrentMethodName() + " for request "
            + request.getSqlShortForm(ABBREV_REQUEST_LENGTH));
      }
      catch (SerializableException e)
      {
        throw new DriverSQLException(e);
      }
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        reconnect();
        if (requestIdIsSet)
        { // Controller handled the query, check if it was executed
          DriverGeneratedKeysResult result = retrieveExecuteUpdateWithKeysResult(request);
          if (result != null)
            return result;
        }
        // At this point the query failed before any controller succeeded in
        // executing the query
        return statementExecuteUpdateWithKeys(request);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " with request '"
            + request.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
  }

  /**
   * Call a stored procedure (with IN/OUT and/or named parameters) that returns
   * a ResultSet.
   * 
   * @param proc the stored procedure call
   * @return a List composed of a <code>java.sql.ResultSet</code> value, an
   *         <code>ArrayList</code> of OUT parameters, and a
   *         <code>HashMap</code> of named parameters result objects.
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized ResultAndWarnings callableStatementExecuteQuery(
      RequestWithResultSetParameters proc) throws DriverSQLException
  {
    throwSQLExceptionIfClosed("Closed Connection cannot process request '"
        + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    boolean procIdIsSet = false;
    try
    {
      setConnectionParametersOnRequest(proc);
      sendCommand(Commands.CallableStatementExecuteQueryWithParameters);
      proc.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with procedure '" + proc + "'");

      proc.setId(receiveLongOrException());
      procIdIsSet = true;
      if (!autoCommit)
        writeExecutedInTransaction = true;

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Received unique id " + proc.getId()
            + " for procedure '" + proc + "'");

      SQLWarning sqlw = receiveSQLWarnings();

      Exception recvEx = null;
      TypeTag tag = new TypeTag(socketInput);

      DriverResultSet drs = null;

      /*
       * TODO: the code below is a complete mess. One reason is it's still half
       * legacy design from old protocol, half from the new procotol. It could
       * easily be made much simpler. We should use #receiveResultSet() TODO:
       * test NoMoreBackendException
       */
      if (!TypeTag.NULL_RESULTSET.equals(tag))
      {
        if (TypeTag.EXCEPTION.equals(tag))
        {
          recvEx = receiveException();
          // dirty hack until cleanup
          if (recvEx instanceof ControllerCoreException)
            recvEx = ((ControllerCoreException) recvEx)
                .compatibilityWrapperHack();

          if (recvEx instanceof DriverSQLException)
            throw (DriverSQLException) recvEx;

          throw new DriverSQLException(recvEx);
        }
        else if (!TypeTag.RESULTSET.equals(tag))
          throw new DriverSQLException(getCurrentMethodName()
              + ": Unexpected response for request "
              + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH), recvEx);

        // Ok, we have a real non-null ResultSet, fetch it
        drs = new DriverResultSet(this);
        if (sequoiaUrl.isDebugEnabled())
          System.out.println("Retrieved ResultSet: " + drs);
      }

      // Now fetch the OUT parameters
      HashMap outParameters = fetchOutParameters();

      // Now fetch the named parameters
      HashMap namedParameters = fetchNamedParameters();

      List result = new ArrayList(3);
      result.add(drs);
      result.add(outParameters);
      result.add(namedParameters);
      return new ResultAndWarnings(result, sqlw);
    }
    catch (RuntimeException e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + ": Error occured while request '"
          + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH)
          + "' was processed by Sequoia Controller", e);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        reconnect();
        if (procIdIsSet)
        { // Controller handled the query, check if it was executed
          ResultAndWarnings result = retrieveExecuteQueryResultWithParameters(proc);
          if (result != null && result.getResultList() != null)
            return result;
        }
        // At this point the query failed before any controller succeeded in
        // executing the query

        return callableStatementExecuteQuery(proc);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " on procedure request '"
            + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
    catch (SerializableException se)
    {
      throw new DriverSQLException(se);
    }
  }

  /**
   * Call a stored procedure that performs an update.
   * 
   * @param proc the stored procedure call
   * @return a List composed of an Integer (number of updated rows), an
   *         <code>ArrayList</code> of OUT parameters, and a
   *         <code>HashMap</code> of named parameters result objects.
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized ResultAndWarnings callableStatementExecuteUpdate(
      Request proc) throws DriverSQLException
  {
    throwSQLExceptionIfClosed("Closed Connection cannot process request '"
        + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    boolean procIdIsSet = false;
    try
    {
      setConnectionParametersOnRequest(proc);
      sendCommand(Commands.CallableStatementExecuteUpdateWithParameters);
      proc.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with procedure '" + proc + "'");

      proc.setId(receiveLongOrException());
      procIdIsSet = true;
      if (!autoCommit)
        writeExecutedInTransaction = true;

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Received unique id " + proc.getId()
            + " for procedure '" + proc + "'");

      // Receive SQLWarnings
      SQLWarning sqlw = receiveSQLWarnings();

      // Receive update count
      Integer updateCount = new Integer(receiveIntOrException());

      // Now fetch the OUT parameters
      HashMap outParameters = fetchOutParameters();

      // Now fetch the named parameters
      HashMap namedParameters = fetchNamedParameters();

      List result = new ArrayList(3);
      result.add(updateCount);
      result.add(outParameters);
      result.add(namedParameters);
      return new ResultAndWarnings(result, sqlw);

    }
    catch (SerializableException se)
    {
      throw new DriverSQLException(se);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        reconnect();
        if (procIdIsSet)
        { // Controller handled the query, check if it was executed
          ResultAndWarnings result = retrieveExecuteUpdateResultWithParameters(proc);
          if (result != null && result.getResultList() != null)
            return result;
        }
        // At this point the query failed before any controller succeeded in
        // executing the query

        return callableStatementExecuteUpdate(proc);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " on procedure request '"
            + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
  }

  /**
   * Call a stored procedure that returns a list of results.
   * 
   * @param proc the stored procedure call
   * @return a List composed of 1. <code>List</code> of results, 2.
   *         <code>ArrayList</code> of OUT parameters, and 3.
   *         <code>HashMap</code> of named parameters result objects.
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized ResultAndWarnings callableStatementExecute(
      RequestWithResultSetParameters proc) throws DriverSQLException
  {
    throwSQLExceptionIfClosed("Closed Connection cannot process request '"
        + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH) + "'");
    beginTransactionIfNeeded();

    boolean procIdIsSet = false;
    try
    {
      setConnectionParametersOnRequest(proc);
      sendCommand(Commands.CallableStatementExecuteWithParameters);
      proc.sendToStream(socketOutput);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName()
            + " with procedure '" + proc + "'");

      proc.setId(receiveLongOrException());
      procIdIsSet = true;
      if (!autoCommit)
        writeExecutedInTransaction = true;

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Received unique id " + proc.getId()
            + " for procedure '" + proc + "'");

      // Fetch warnings
      SQLWarning sqlw = receiveSQLWarnings();

      // Fetch results
      List resultList = fetchMultipleResultsFromStream(getCurrentMethodName());

      // Now fetch the OUT parameters
      HashMap outParameters = fetchOutParameters();

      // Now fetch the named parameters
      HashMap namedParameters = fetchNamedParameters();

      List result = new ArrayList(3);
      result.add(resultList);
      result.add(outParameters);
      result.add(namedParameters);
      return new ResultAndWarnings(result, sqlw);
    }
    catch (RuntimeException e)
    {
      throw new DriverSQLException(getCurrentMethodName()
          + ": Error occured while request '"
          + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH)
          + "' was processed by Sequoia Controller", e);
    }
    catch (IOException e)
    { // Connection failed, try to reconnect and re-exec the query
      try
      {
        reconnect();
        if (procIdIsSet)
        { // Controller handled the query, check if it was executed
          ResultAndWarnings rww = retrieveExecuteResultWithParameters(proc);
          if (rww != null && rww.getResultList() != null)
            return rww;
        }
        // At this point the query failed before any controller succeeded in
        // executing the query

        return callableStatementExecute(proc);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " with procedure request'"
            + proc.getSqlShortForm(ABBREV_REQUEST_LENGTH)
            + "' and automatic reconnect failed (" + e1 + ")", e1);
      }
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
  }

  //
  // Database Metadata methods
  //

  /**
   * Closes the remote ResultSet given its cursor name.
   * 
   * @param cursorName cursor name of the ResultSet to close.
   * @throws SQLException if an error occurs
   */
  protected synchronized void closeRemoteResultSet(String cursorName)
      throws SQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.CloseRemoteResultSet);
      socketOutput.writeLongUTF(cursorName);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Closing remote ResultSet");

      receiveBooleanOrException();
    }
    catch (SerializableException se)
    {
      throw new DriverSQLException(se);
    }
    catch (IOException e)
    {
      throw wrapIOExceptionInDriverSQLException(getCurrentMethodName(), e);
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getAttributes(java.lang.String,
   *      java.lang.String, java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getAttributes(String catalog,
      String schemaPattern, String typeNamePattern, String attributeNamePattern)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetAttributes);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(typeNamePattern);
      socketOutput.writeLongUTF(attributeNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + typeNamePattern + ","
            + attributeNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getAttributes(catalog, schemaPattern, typeNamePattern,
            attributeNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getBestRowIdentifier(java.lang.String,
   *      java.lang.String, java.lang.String, int, boolean)
   */
  protected synchronized ResultSet getBestRowIdentifier(String catalog,
      String schema, String table, int scope, boolean nullable)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetBestRowIdentifier);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schema);
      socketOutput.writeLongUTF(table);
      socketOutput.writeInt(scope);
      socketOutput.writeBoolean(nullable);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schema + "," + table + "," + scope + "," + nullable + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getBestRowIdentifier(catalog, schema, table, scope, nullable);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getColumnPrivileges(java.lang.String,
   *      java.lang.String, java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getColumnPrivileges(String catalog,
      String schemaPattern, String tableName, String columnNamePattern)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetColumnPrivileges);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(tableName);
      socketOutput.writeLongUTF(columnNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + tableName + "," + columnNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getColumnPrivileges(catalog, schemaPattern, tableName,
            columnNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getColumns(java.lang.String,
   *      java.lang.String, java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getColumns(String catalog,
      String schemaPattern, String tableNamePattern, String columnNamePattern)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetColumns);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(tableNamePattern);
      socketOutput.writeLongUTF(columnNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + tableNamePattern + "," + columnNamePattern
            + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getColumns(catalog, schemaPattern, tableNamePattern,
            columnNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * Get the Sequoia controller version number.
   * 
   * @return a String containing the controller version
   * @exception DriverSQLException if an error occurs
   */
  protected synchronized String getControllerVersionNumber()
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.GetControllerVersionNumber);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Executing " + getCurrentMethodName());

      return receiveStringOrException();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getControllerVersionNumber();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getCrossReference(java.lang.String,
   *      java.lang.String, java.lang.String, java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getCrossReference(String primaryCatalog,
      String primarySchema, String primaryTable, String foreignCatalog,
      String foreignSchema, String foreignTable) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetCrossReference);
      socketOutput.writeLongUTF(primaryCatalog);
      socketOutput.writeLongUTF(primarySchema);
      socketOutput.writeLongUTF(primaryTable);
      socketOutput.writeLongUTF(foreignCatalog);
      socketOutput.writeLongUTF(foreignSchema);
      socketOutput.writeLongUTF(foreignTable);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + primaryCatalog + ","
            + primarySchema + "," + primaryTable + "," + foreignCatalog + ","
            + foreignSchema + "," + foreignTable + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getCrossReference(primaryCatalog, primarySchema, primaryTable,
            foreignCatalog, foreignSchema, foreignTable);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see DatabaseMetaData#getDatabaseProductName()
   */
  protected synchronized String getDatabaseProductName()
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetDatabaseProductName);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName());

      return receiveStringOrException();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getDatabaseProductName();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getExportedKeys(java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getExportedKeys(String catalog,
      String schema, String table) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetExportedKeys);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schema);
      socketOutput.writeLongUTF(table);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schema + "," + table + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getExportedKeys(catalog, schema, table);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getImportedKeys(java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getImportedKeys(String catalog,
      String schema, String table) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetImportedKeys);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schema);
      socketOutput.writeLongUTF(table);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schema + "," + table + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getImportedKeys(catalog, schema, table);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getIndexInfo(java.lang.String,
   *      java.lang.String, java.lang.String, boolean, boolean)
   */
  protected synchronized ResultSet getIndexInfo(String catalog, String schema,
      String table, boolean unique, boolean approximate)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetIndexInfo);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schema);
      socketOutput.writeLongUTF(table);
      socketOutput.writeBoolean(unique);
      socketOutput.writeBoolean(approximate);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schema + "," + table + "," + unique + "," + approximate + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getIndexInfo(catalog, schema, table, unique, approximate);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @param sqlTemplate sql template of the PreparedStatement
   * @see java.sql.PreparedStatement#getMetaData()
   */
  protected synchronized ResultSetMetaData preparedStatementGetMetaData(
      String sqlTemplate) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.PreparedStatementGetMetaData);
      socketOutput.writeLongUTF(sqlTemplate);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "()");

      return receiveResultSet(getCurrentMethodName()).getMetaData();
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (SQLException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return preparedStatementGetMetaData(sqlTemplate);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getPrimaryKeys(java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getPrimaryKeys(String catalog,
      String schemaPattern, String tableNamePattern) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetPrimaryKeys);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(tableNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + tableNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getPrimaryKeys(catalog, schemaPattern, tableNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  protected synchronized java.sql.ResultSet getProcedures(String catalog,
      String schemaPattern, String procedureNamePattern)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetProcedures);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(procedureNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + procedureNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getProcedures(catalog, schemaPattern, procedureNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  protected synchronized java.sql.ResultSet getProcedureColumns(String catalog,
      String schemaPattern, String procedureNamePattern,
      String columnNamePattern) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetProcedureColumns);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(procedureNamePattern);
      socketOutput.writeLongUTF(columnNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + procedureNamePattern + ","
            + columnNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getProcedureColumns(catalog, schemaPattern,
            procedureNamePattern, columnNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getSchemas()
   */
  protected synchronized ResultSet getSchemas() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();

    try
    {
      sendCommand(Commands.DatabaseMetaDataGetSchemas);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName());

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getSchemas();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getSuperTables(java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getSuperTables(String catalog,
      String schemaPattern, String tableNamePattern) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetSuperTables);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(tableNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + tableNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getSuperTables(catalog, schemaPattern, tableNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getSuperTypes(java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getSuperTypes(String catalog,
      String schemaPattern, String typeNamePattern) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetSuperTypes);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(typeNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + typeNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getSuperTypes(catalog, schemaPattern, typeNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException(
            "Connection lost while executing getSuperTypes and automatic reconnect failed ",
            e1);
      }
    }
  }

  /**
   * Retrieve a static metadata from the controller.
   * 
   * @param key the "getXXX(Y,Z,...)" hash key of the metadata query
   * @return an Object that will be an <tt>Integer</tt> or <tt>Boolean</tt>
   *         or <tt>String</tt>
   * @throws DriverSQLException if fails
   * @see org.continuent.sequoia.controller.virtualdatabase.VirtualDatabaseWorkerThread#databaseStaticMetadata()
   * @see org.continuent.sequoia.controller.backend.DatabaseBackendMetaData#retrieveDatabaseMetadata()
   */
  protected synchronized Object getStaticMetadata(String key)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseStaticMetadata);
      socketOutput.writeLongUTF(key);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out.println("Getting " + key + " metadata");

      TypeTag tag = new TypeTag(socketInput);

      if (TypeTag.EXCEPTION.equals(tag))
        throw new DriverSQLException(receiveException());
      else
      {
        tag = new TypeTag(socketInput);
        Object result = SQLDataSerialization.getSerializer(tag)
            .receiveFromStream(socketInput);
        return result;
      }
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getStaticMetadata(key);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * Gets a description of the access rights for each table available in a
   * catalog. Note that a table privilege applies to one or more columns in the
   * table. It would be wrong to assume that this priviledge applies to all
   * columns (this may be true for some systems but is not true for all.) Only
   * privileges matching the schema and table name criteria are returned. They
   * are ordered by TABLE_SCHEM, TABLE_NAME, and PRIVILEGE.
   * 
   * @param catalog a catalog name; "" retrieves those without a catalog; null
   *          means drop catalog name from the selection criteria
   * @param schemaPattern a schema name pattern; "" retrieves those without a
   *          schema
   * @param tableNamePattern a table name pattern
   * @return <code>ResultSet</code> each row is a table privilege description
   * @throws DriverSQLException if a database access error occurs
   */
  protected synchronized ResultSet getTablePrivileges(String catalog,
      String schemaPattern, String tableNamePattern) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetTablePrivileges);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(tableNamePattern);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + tableNamePattern + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getTablePrivileges(catalog, schemaPattern, tableNamePattern);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see org.continuent.sequoia.driver.DatabaseMetaData#getTables(String,
   *      String, String, String[])
   */
  protected synchronized ResultSet getTables(String catalog,
      String schemaPattern, String tableNamePattern, String[] types)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetTables);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(tableNamePattern);

      if (null == types)
        socketOutput.writeBoolean(false);
      else
      {
        socketOutput.writeBoolean(true);
        socketOutput.writeInt(types.length);
        for (int i = 0; i < types.length; i++)
          socketOutput.writeLongUTF(types[i]);
      }
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + tableNamePattern + "," + types + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getTables(catalog, schemaPattern, tableNamePattern, types);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * Gets the table types available in this database. The results are ordered by
   * table type.
   * 
   * @return <code>ResultSet</code> each row has a single String column that
   *         is a catalog name
   * @throws SQLException if a database error occurs
   */
  protected synchronized ResultSet getTableTypes() throws SQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetTableTypes);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName());

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getTableTypes();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getTypeInfo()
   */
  protected synchronized ResultSet getTypeInfo() throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetTypeInfo);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "()");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getTypeInfo();
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getUDTs(java.lang.String, java.lang.String,
   *      java.lang.String, int[])
   */
  protected synchronized ResultSet getUDTs(String catalog,
      String schemaPattern, String typeNamePattern, int[] types)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetUDTs);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schemaPattern);
      socketOutput.writeLongUTF(typeNamePattern);

      if (null == types)
        socketOutput.writeBoolean(false);
      else
      {
        socketOutput.writeBoolean(true);
        socketOutput.writeInt(types.length);
        for (int i = 0; i < types.length; i++)
          socketOutput.writeInt(types[i]);
      }
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schemaPattern + "," + typeNamePattern + "," + types + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getUDTs(catalog, schemaPattern, typeNamePattern, types);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * @see java.sql.DatabaseMetaData#getVersionColumns(java.lang.String,
   *      java.lang.String, java.lang.String)
   */
  protected synchronized ResultSet getVersionColumns(String catalog,
      String schema, String table) throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.DatabaseMetaDataGetVersionColumns);
      socketOutput.writeLongUTF(catalog);
      socketOutput.writeLongUTF(schema);
      socketOutput.writeLongUTF(table);
      socketOutput.flush();

      if (sequoiaUrl.isDebugEnabled())
        System.out.println(getCurrentMethodName() + "(" + catalog + ","
            + schema + "," + table + ")");

      return receiveResultSet(getCurrentMethodName());
    }
    catch (SerializableException e)
    {
      throw new DriverSQLException(e);
    }
    catch (IOException e)
    {
      try
      {
        reconnect();
        return getVersionColumns(catalog, schema, table);
      }
      catch (DriverSQLException e1)
      {
        throw new DriverSQLException("Connection lost while executing "
            + getCurrentMethodName() + " and automatic reconnect failed ", e1);
      }
    }
  }

  /**
   * Send "FetchNextResultSetRows" command to controller. Throws an SQL
   * exception if controller returns an error; else returns void and lets the
   * caller receive its new rows by itself.
   * 
   * @see org.continuent.sequoia.controller.virtualdatabase.VirtualDatabaseWorkerThread#fetchNextResultSetRows()
   * @param cursorName name of the ResultSet cursor
   * @param fetchSize number of rows to fetch
   * @throws DriverSQLException if an error occurs
   */
  protected synchronized void tryFetchNext(String cursorName, int fetchSize)
      throws DriverSQLException
  {
    throwSQLExceptionIfClosed();
    try
    {
      sendCommand(Commands.FetchNextResultSetRows);
      socketOutput.writeLongUTF(cursorName);
      socketOutput.writeInt(fetchSize);
      socketOutput.flush();
      if (sequoiaUrl.isDebugEnabled())
        System.out
            .println("Fetching next " + fetchSize + " from " + cursorName);

      TypeTag tag = new TypeTag(socketInput);

      if (TypeTag.EXCEPTION.equals(tag))
        throw new DriverSQLException(receiveException());

      if (!TypeTag.NOT_EXCEPTION.equals(tag))
        throw new ProtocolException();

      // success, now we can let the DriverResultSet caller receive its data.

    }
    catch (IOException e)
    {
      throw wrapIOExceptionInDriverSQLException(getCurrentMethodName(), e);
    }
  }

  /**
   * Gets the caller's method name.
   * 
   * @return method name of the calling function
   */
  static String getCurrentMethodName()
  {
    return new Throwable().getStackTrace()[1].getMethodName();
  }

  /**
   * @see java.lang.Object#toString()
   */
  public String toString()
  {
    // we could use println() instead of LINE_SEPARATOR here
    return "url:"
        + getUrl()
        + LINE_SEPARATOR
        + socket
        + LINE_SEPARATOR
        + ((sequoiaUrl != null) && (sequoiaUrl.getParameters() != null)
            ? "properties:" + sequoiaUrl.getParameters() + LINE_SEPARATOR
            : "") + "user:" + getUserName() + LINE_SEPARATOR
        + "connection pooling:" + connectionPooling + LINE_SEPARATOR
        + "escape backslash:" + escapeBackslash + LINE_SEPARATOR
        + "escape char:" + escapeChar + LINE_SEPARATOR + "escape single quote:"
        + escapeSingleQuote + LINE_SEPARATOR;
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.