Package com.mysql.jdbc

Source Code of com.mysql.jdbc.ConnectionImpl

/*
  Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

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

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

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA
*/
package com.mysql.jdbc;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.sql.Blob;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLPermission;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Stack;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TreeMap;
import java.util.concurrent.Executor;

import com.mysql.jdbc.PreparedStatement.ParseInfo;
import com.mysql.jdbc.log.Log;
import com.mysql.jdbc.log.LogFactory;
import com.mysql.jdbc.log.LogUtils;
import com.mysql.jdbc.log.NullLogger;
import com.mysql.jdbc.profiler.ProfilerEvent;
import com.mysql.jdbc.profiler.ProfilerEventHandler;
import com.mysql.jdbc.util.LRUCache;

/**
* A Connection represents a session with a specific database. Within the
* context of a Connection, SQL statements are executed and results are
* returned.
* <P>
* 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 obtained with the getMetaData method.
* </p>
*
* @author Mark Matthews
* @version $Id$
* @see java.sql.Connection
*/
public class ConnectionImpl extends ConnectionPropertiesImpl implements
    MySQLConnection {

  private static final long serialVersionUID = 2877471301981509474L;

  private static final SQLPermission SET_NETWORK_TIMEOUT_PERM = new SQLPermission("setNetworkTimeout");
 
  private static final SQLPermission ABORT_PERM = new SQLPermission("abort");

  private static final String JDBC_LOCAL_CHARACTER_SET_RESULTS = "jdbc.local.character_set_results";

  public String getHost() {
    return host;
  }

  private MySQLConnection proxy = null;

  private InvocationHandler realProxy = null;

  public boolean isProxySet(){
    return this.proxy != null;
  }

  public void setProxy(MySQLConnection proxy) {
    this.proxy = proxy;
  }

  public void setRealProxy(InvocationHandler proxy) {
    this.realProxy = proxy;
  }

  // We have to proxy ourselves when we're load balanced so that
  // statements get routed to the right physical connection
  // (when load balanced, we're a "logical" connection)
  private MySQLConnection getProxy() {
    return (proxy != null) ? proxy : (MySQLConnection) this;
  }

  public MySQLConnection getLoadBalanceSafeProxy() {
    return this.getProxy();
  }

  public Object getConnectionMutex() {
    return (this.realProxy != null) ? this.realProxy : this;
  }

  class ExceptionInterceptorChain implements ExceptionInterceptor {
    List<Extension> interceptors;
   
    ExceptionInterceptorChain(String interceptorClasses) throws SQLException {
      interceptors = Util.loadExtensions(ConnectionImpl.this, props, interceptorClasses, "Connection.BadExceptionInterceptor"this);
    }
   
    void addRingZero(ExceptionInterceptor interceptor) throws SQLException
      interceptors.add(0, interceptor);
    }
   
    public SQLException interceptException(SQLException sqlEx, Connection conn) {
      if (interceptors != null) {
        Iterator<Extension> iter = interceptors.iterator();
       
        while (iter.hasNext()) {
          sqlEx = ((ExceptionInterceptor)iter.next()).interceptException(sqlEx, ConnectionImpl.this);
        }
      }
     
      return sqlEx;
    }

    public void destroy() {
      if (interceptors != null) {
        Iterator<Extension> iter = interceptors.iterator();
       
        while (iter.hasNext()) {
          ((ExceptionInterceptor)iter.next()).destroy();
        }
      }
     
    }

    public void init(Connection conn, Properties properties) throws SQLException {
      if (interceptors != null) {
        Iterator<Extension> iter = interceptors.iterator();
       
        while (iter.hasNext()) {
          ((ExceptionInterceptor)iter.next()).init(conn, properties);
        }
      }
    }
  }
 
  /**
   * Used as a key for caching callable statements which (may) depend on
   * current catalog...In 5.0.x, they don't (currently), but stored procedure
   * names soon will, so current catalog is a (hidden) component of the name.
   */
  static class CompoundCacheKey {
    String componentOne;

    String componentTwo;

    int hashCode;

    CompoundCacheKey(String partOne, String partTwo) {
      this.componentOne = partOne;
      this.componentTwo = partTwo;

      // Handle first component (in most cases, currentCatalog)
      // being NULL....
      this.hashCode = (((this.componentOne != null) ? this.componentOne
          : "") + this.componentTwo).hashCode();
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
      if (obj instanceof CompoundCacheKey) {
        CompoundCacheKey another = (CompoundCacheKey) obj;

        boolean firstPartEqual = false;

        if (this.componentOne == null) {
          firstPartEqual = (another.componentOne == null);
        } else {
          firstPartEqual = this.componentOne
              .equals(another.componentOne);
        }

        return (firstPartEqual && this.componentTwo
            .equals(another.componentTwo));
      }

      return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
      return this.hashCode;
    }
  }

  /**
   * Marker for character set converter not being available (not written,
   * multibyte, etc) Used to prevent multiple instantiation requests.
   */
  private static final Object CHARSET_CONVERTER_NOT_AVAILABLE_MARKER = new Object();

  /**
   * The mapping between MySQL charset names and Java charset names.
   * Initialized by loadCharacterSetMapping()
   */
  public static Map<?, ?> charsetMap;

  /** Default logger class name */
  protected static final String DEFAULT_LOGGER_CLASS = "com.mysql.jdbc.log.StandardLogger";

  private final static int HISTOGRAM_BUCKETS = 20;

  /** Logger instance name */
  private static final String LOGGER_INSTANCE_NAME = "MySQL";

  /**
   * Map mysql transaction isolation level name to
   * java.sql.Connection.TRANSACTION_XXX
   */
  private static Map<String, Integer> mapTransIsolationNameToValue = null;

  /** Null logger shared by all connections at startup */
  private static final Log NULL_LOGGER = new NullLogger(LOGGER_INSTANCE_NAME);

  protected static Map<?, ?> roundRobinStatsMap;

  private static final Map<String, Map<Long, String>> serverCollationByUrl = new HashMap<String, Map<Long,String>>();

  /**
   * Map for Java charsets of user defined charsets. We can't map them statically, because
   * they can be different for different server URLs.
   */
  private static final Map<String, Map<Integer, String>> serverJavaCharsetByUrl = new HashMap<String, Map<Integer,String>>();
  /**
   * Map for user defined charsets. We can't map them statically, because
   * they can be different for different server URLs.
   */
  private static final Map<String, Map<Integer, String>> serverCustomCharsetByUrl = new HashMap<String, Map<Integer,String>>();
  /**
   * Map for user defined charsets. We can't map them statically, because
   * they can be different for different server URLs.
   */
  private static final Map<String, Map<String, Integer>> serverCustomMblenByUrl = new HashMap<String, Map<String, Integer>>();
 
  private CacheAdapter<String, Map<String, String>> serverConfigCache;

  private long queryTimeCount;
  private double queryTimeSum;
  private double queryTimeSumSquares;
  private double queryTimeMean;
 
  private transient Timer cancelTimer;
 
  private List<Extension> connectionLifecycleInterceptors;
 
  private static final Constructor<?> JDBC_4_CONNECTION_CTOR;
 
  private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;
 
  private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;
 
  static {
    mapTransIsolationNameToValue = new HashMap<String, Integer>(8);
    mapTransIsolationNameToValue.put("READ-UNCOMMITED", TRANSACTION_READ_UNCOMMITTED);
    mapTransIsolationNameToValue.put("READ-UNCOMMITTED", TRANSACTION_READ_UNCOMMITTED);
    mapTransIsolationNameToValue.put("READ-COMMITTED", TRANSACTION_READ_COMMITTED);
    mapTransIsolationNameToValue.put("REPEATABLE-READ", TRANSACTION_REPEATABLE_READ);
    mapTransIsolationNameToValue.put("SERIALIZABLE", TRANSACTION_SERIALIZABLE);

    if (Util.isJdbc4()) {
      try {
        JDBC_4_CONNECTION_CTOR = Class.forName(
            "com.mysql.jdbc.JDBC4Connection").getConstructor(
            new Class[] { String.class, Integer.TYPE,
                Properties.class, String.class, String.class });
      } catch (SecurityException e) {
        throw new RuntimeException(e);
      } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
      }
    } else {
      JDBC_4_CONNECTION_CTOR = null;
    }
  }

  protected static SQLException appendMessageToException(SQLException sqlEx,
      String messageToAppend, ExceptionInterceptor interceptor) {
    String origMessage = sqlEx.getMessage();
    String sqlState = sqlEx.getSQLState();
    int vendorErrorCode = sqlEx.getErrorCode();

    StringBuffer messageBuf = new StringBuffer(origMessage.length()
        + messageToAppend.length());
    messageBuf.append(origMessage);
    messageBuf.append(messageToAppend);

    SQLException sqlExceptionWithNewMessage = SQLError.createSQLException(messageBuf
        .toString(), sqlState, vendorErrorCode, interceptor);

    //
    // Try and maintain the original stack trace,
    // only works on JDK-1.4 and newer
    //

    try {
      // Have to do this with reflection, otherwise older JVMs croak
      Method getStackTraceMethod = null;
      Method setStackTraceMethod = null;
      Object theStackTraceAsObject = null;

      Class<?> stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
      Class<?> stackTraceElementArrayClass = Array.newInstance(
          stackTraceElementClass, new int[] { 0 }).getClass();

      getStackTraceMethod = Throwable.class.getMethod("getStackTrace",
          new Class[] {});

      setStackTraceMethod = Throwable.class.getMethod("setStackTrace",
          new Class[] { stackTraceElementArrayClass });

      if (getStackTraceMethod != null && setStackTraceMethod != null) {
        theStackTraceAsObject = getStackTraceMethod.invoke(sqlEx,
            new Object[0]);
        setStackTraceMethod.invoke(sqlExceptionWithNewMessage,
            new Object[] { theStackTraceAsObject });
      }
    } catch (NoClassDefFoundError noClassDefFound) {

    } catch (NoSuchMethodException noSuchMethodEx) {

    } catch (Throwable catchAll) {

    }

    return sqlExceptionWithNewMessage;
  }

  public Timer getCancelTimer() {
    synchronized (getConnectionMutex()) {
      if (cancelTimer == null) {
        boolean createdNamedTimer = false;
       
        // Use reflection magic to try this on JDK's 1.5 and newer, fallback to non-named
        // timer on older VMs.
        try {
          Constructor<Timer> ctr = Timer.class.getConstructor(new Class[] {String.class, Boolean.TYPE});
         
          cancelTimer = ctr.newInstance(new Object[] { "MySQL Statement Cancellation Timer", Boolean.TRUE});
          createdNamedTimer = true;
        } catch (Throwable t) {
          createdNamedTimer = false;
        }
       
        if (!createdNamedTimer) {
          cancelTimer = new Timer(true);
        }
      }
     
      return cancelTimer;
    }
  }

 
  /**
   * Creates a connection instance -- We need to provide factory-style methods
   * so we can support both JDBC3 (and older) and JDBC4 runtimes, otherwise
   * the class verifier complains when it tries to load JDBC4-only interface
   * classes that are present in JDBC4 method signatures.
   */

  protected static Connection getInstance(String hostToConnectTo,
      int portToConnectTo, Properties info, String databaseToConnectTo,
      String url) throws SQLException {
    if (!Util.isJdbc4()) {
      return new ConnectionImpl(hostToConnectTo, portToConnectTo, info,
          databaseToConnectTo, url);
    }

    return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR,
        new Object[] {
              hostToConnectTo, Integer.valueOf(portToConnectTo), info,
              databaseToConnectTo, url }, null);
  }

  private static final Random random = new Random();
 
  /**
   *
   * @param url
   * @param hostList
   * @return
   */
  protected static synchronized int getNextRoundRobinHostIndex(String url,
      List<?> hostList) {
    // we really do "random" here, because you don't get even
    // distribution when this is coupled with connection pools
   
    int indexRange = hostList.size();
   
    int index = random.nextInt(indexRange);
   
    return index;
  }

  private static boolean nullSafeCompare(String s1, String s2) {
    if (s1 == null && s2 == null) {
      return true;
    }

    if (s1 == null && s2 != null) {
      return false;
    }

    return s1 != null && s1.equals(s2);
  }

  /** Are we in autoCommit mode? */
  private boolean autoCommit = true;

  /** A cache of SQL to parsed prepared statement parameters. */
  private CacheAdapter<String, ParseInfo> cachedPreparedStatementParams;

  /**
   * For servers > 4.1.0, what character set is the metadata returned in?
   */
  private String characterSetMetadata = null;

  /**
   * The character set we want results and result metadata returned in (null ==
   * results in any charset, metadata in UTF-8).
   */
  private String characterSetResultsOnServer = null;

  /**
   * Holds cached mappings to charset converters to avoid static
   * synchronization and at the same time save memory (each charset converter
   * takes approx 65K of static data).
   */
  private Map<String, Object> charsetConverterMap = new HashMap<String, Object>(CharsetMapping
      .getNumberOfCharsetsConfigured());

  /** The point in time when this connection was created */
  private long connectionCreationTimeMillis = 0;

  /** ID used when profiling */
  private long connectionId;

  /** The database we're currently using (called Catalog in JDBC terms). */
  private String database = null;

  /** Internal DBMD to use for various database-version specific features */
  private DatabaseMetaData dbmd = null;

  private TimeZone defaultTimeZone;

  /** The event sink to use for profiling */
  private ProfilerEventHandler eventSink;

  /** Why was this connection implicitly closed, if known? (for diagnostics) */
  private Throwable forceClosedReason;

  /** Does the server suuport isolation levels? */
  private boolean hasIsolationLevels = false;

  /** Does this version of MySQL support quoted identifiers? */
  private boolean hasQuotedIdentifiers = false;

  /** The hostname we're connected to */
  private String host = null;
 
  /**
   * We need this 'bootstrapped', because 4.1 and newer will send fields back
   * with this even before we fill this dynamically from the server.
   */
  public Map<Integer, String> indexToJavaCharset = new HashMap<Integer, String>();

  public Map<Integer, String> indexToCustomMysqlCharset = new HashMap<Integer, String>();

  private Map<String, Integer> mysqlCharsetToCustomMblen = new HashMap<String, Integer>();
 
  /** The I/O abstraction interface (network conn to MySQL server */
  private transient MysqlIO io = null;
 
  private boolean isClientTzUTC = false;

  /** Has this connection been closed? */
  private boolean isClosed = true;

  /** Is this connection associated with a global tx? */
  private boolean isInGlobalTx = false;

  /** Is this connection running inside a JDK-1.3 VM? */
  private boolean isRunningOnJDK13 = false;

  /** isolation level */
  private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED;

  private boolean isServerTzUTC = false;

  /** When did the last query finish? */
  private long lastQueryFinishedTime = 0;

  /** The logger we're going to use */
  private transient Log log = NULL_LOGGER;

  /**
   * If gathering metrics, what was the execution time of the longest query so
   * far ?
   */
  private long longestQueryTimeMs = 0;

  /** Is the server configured to use lower-case table names only? */
  private boolean lowerCaseTableNames = false;

  /** When did the master fail? */
//  private long masterFailTimeMillis = 0L;

  private long maximumNumberTablesAccessed = 0;

  /** Has the max-rows setting been changed from the default? */
  private boolean maxRowsChanged = false;

  /** When was the last time we reported metrics? */
  private long metricsLastReportedMs;

  private long minimumNumberTablesAccessed = Long.MAX_VALUE;

  /** The JDBC URL we're using */
  private String myURL = null;

  /** Does this connection need to be tested? */
  private boolean needsPing = false;

  private int netBufferLength = 16384;

  private boolean noBackslashEscapes = false;

  private long numberOfPreparedExecutes = 0;

  private long numberOfPrepares = 0;

  private long numberOfQueriesIssued = 0;

  private long numberOfResultSetsCreated = 0;

  private long[] numTablesMetricsHistBreakpoints;

  private int[] numTablesMetricsHistCounts;

  private long[] oldHistBreakpoints = null;

  private int[] oldHistCounts = null;

  /** A map of currently open statements */
  private Map<Statement, Statement> openStatements;

  private LRUCache parsedCallableStatementCache;

  private boolean parserKnowsUnicode = false;

  /** The password we used */
  private String password = null;

  private long[] perfMetricsHistBreakpoints;

  private int[] perfMetricsHistCounts;

  /** Point of origin where this Connection was created */
  private String pointOfOrigin;

  /** The port number we're connected to (defaults to 3306) */
  private int port = 3306;

  /** Properties for this connection specified by user */
  protected Properties props = null;

  /** Should we retrieve 'info' messages from the server? */
  private boolean readInfoMsg = false;

  /** Are we in read-only mode? */
  private boolean readOnly = false;

  /** Cache of ResultSet metadata */
  protected LRUCache resultSetMetadataCache;
 
  /** The timezone of the server */
  private TimeZone serverTimezoneTZ = null;

  /** The map of server variables that we retrieve at connection init. */
  private Map<String, String> serverVariables = null;

  private long shortestQueryTimeMs = Long.MAX_VALUE;

  /** A map of statements that have had setMaxRows() called on them */
  private Map<Statement, Statement> statementsUsingMaxRows;

  private double totalQueryTimeMs = 0;

  /** Are transactions supported by the MySQL server we are connected to? */
  private boolean transactionsSupported = false;

  /**
   * The type map for UDTs (not implemented, but used by some third-party
   * vendors, most notably IBM WebSphere)
   */
  private Map<String,Class<?>> typeMap;

  /** Has ANSI_QUOTES been enabled on the server? */
  private boolean useAnsiQuotes = false;

  /** The user we're connected as */
  private String user = null;
 
  /**
   * Should we use server-side prepared statements? (auto-detected, but can be
   * disabled by user)
   */
  private boolean useServerPreparedStmts = false;

  private LRUCache serverSideStatementCheckCache;
  private LRUCache serverSideStatementCache;
  private Calendar sessionCalendar;
 
  private Calendar utcCalendar;
 
  private String origHostToConnectTo;

  // we don't want to be able to publicly clone this...
 
  private int origPortToConnectTo;

  private String origDatabaseToConnectTo;

  private String errorMessageEncoding = "Cp1252"; // to begin with, changes after we talk to the server
 
  private boolean usePlatformCharsetConverters;
 
  /*
   * For testing failover scenarios
   */
  private boolean hasTriedMasterFlag = false;

  /**
   * The comment (if any) that we'll prepend to all statements
   * sent to the server (to show up in "SHOW PROCESSLIST")
   */
  private String statementComment = null;

  private boolean storesLowerCaseTableName;

  private List<StatementInterceptorV2> statementInterceptors;
 
  /**
   * If a CharsetEncoder is required for escaping. Needed for SJIS and related
   * problems with \u00A5.
   */
  private boolean requiresEscapingEncoder;

  private String hostPortPair;
 
  /**'
   * For the delegate only
   */
  protected ConnectionImpl() { 
  }
 
  /**
   * Creates a connection to a MySQL Server.
   *
   * @param hostToConnectTo
   *            the hostname of the database server
   * @param portToConnectTo
   *            the port number the server is listening on
   * @param info
   *            a Properties[] list holding the user and password
   * @param databaseToConnectTo
   *            the database to connect to
   * @param url
   *            the URL of the connection
   * @param d
   *            the Driver instantation of the connection
   * @exception SQLException
   *                if a database access error occurs
   */
  protected ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info,
      String databaseToConnectTo, String url)
      throws SQLException {
 
    this.connectionCreationTimeMillis = System.currentTimeMillis();
   
    if (databaseToConnectTo == null) {
      databaseToConnectTo = "";
    }

    // Stash away for later, used to clone this connection for Statement.cancel
    // and Statement.setQueryTimeout().
    //
   
    this.origHostToConnectTo = hostToConnectTo;
    this.origPortToConnectTo = portToConnectTo;
    this.origDatabaseToConnectTo = databaseToConnectTo;

    try {
      Blob.class.getMethod("truncate", new Class[] {Long.TYPE});
     
      this.isRunningOnJDK13 = false;
    } catch (NoSuchMethodException nsme) {
      this.isRunningOnJDK13 = true;
    }
   
    this.sessionCalendar = new GregorianCalendar();
    this.utcCalendar = new GregorianCalendar();
    this.utcCalendar.setTimeZone(TimeZone.getTimeZone("GMT"));
   
    //
    // Normally, this code would be in initializeDriverProperties,
    // but we need to do this as early as possible, so we can start
    // logging to the 'correct' place as early as possible...this.log
    // points to 'NullLogger' for every connection at startup to avoid
    // NPEs and the overhead of checking for NULL at every logging call.
    //
    // We will reset this to the configured logger during properties
    // initialization.
    //
    this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor());

    // We store this per-connection, due to static synchronization
    // issues in Java's built-in TimeZone class...
    this.defaultTimeZone = Util.getDefaultTimeZone();
   
    if ("GMT".equalsIgnoreCase(this.defaultTimeZone.getID())) {
      this.isClientTzUTC = true;
    } else {
      this.isClientTzUTC = false;
    }

    this.openStatements = new HashMap<Statement, Statement>();
   
    if (NonRegisteringDriver.isHostPropertiesList(hostToConnectTo)) {
      Properties hostSpecificProps = NonRegisteringDriver.expandHostKeyValues(hostToConnectTo);
     
      Enumeration<?> propertyNames = hostSpecificProps.propertyNames();
     
      while (propertyNames.hasMoreElements()) {
        String propertyName = propertyNames.nextElement().toString();
        String propertyValue = hostSpecificProps.getProperty(propertyName);
       
        info.setProperty(propertyName, propertyValue);
      }
    } else {
   
      if (hostToConnectTo == null) {
        this.host = "localhost";
        this.hostPortPair = this.host + ":" + portToConnectTo;
      } else {
        this.host = hostToConnectTo;
       
        if (hostToConnectTo.indexOf(":") == -1) {
          this.hostPortPair = this.host + ":" + portToConnectTo;
        } else {
          this.hostPortPair = this.host;
        }
      }
    }

    this.port = portToConnectTo;

    this.database = databaseToConnectTo;
    this.myURL = url;
    this.user = info.getProperty(NonRegisteringDriver.USER_PROPERTY_KEY);
    this.password = info
        .getProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY);

    if ((this.user == null) || this.user.equals("")) {
      this.user = "";
    }

    if (this.password == null) {
      this.password = "";
    }

    this.props = info;
   
   
   
    initializeDriverProperties(info);

    if (getUseUsageAdvisor()) {
      this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable());
    } else {
      this.pointOfOrigin = "";
    }
   
    try {
      this.dbmd = getMetaData(false, false);
      initializeSafeStatementInterceptors();
      createNewIO(false);
      unSafeStatementInterceptors();
    } catch (SQLException ex) {
      cleanup(ex);

      // don't clobber SQL exceptions
      throw ex;
    } catch (Exception ex) {
      cleanup(ex);

      StringBuffer mesg = new StringBuffer(128);

      if (!getParanoid()) {
        mesg.append("Cannot connect to MySQL server on ");
        mesg.append(this.host);
        mesg.append(":");
        mesg.append(this.port);
        mesg.append(".\n\n");
        mesg.append("Make sure that there is a MySQL server ");
        mesg.append("running on the machine/port you are trying ");
        mesg
            .append("to connect to and that the machine this software is "
                + "running on ");
        mesg.append("is able to connect to this host/port "
            + "(i.e. not firewalled). ");
        mesg
            .append("Also make sure that the server has not been started "
                + "with the --skip-networking ");
        mesg.append("flag.\n\n");
      } else {
        mesg.append("Unable to connect to database.");
      }

      SQLException sqlEx = SQLError.createSQLException(mesg.toString(),
          SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor());
     
      sqlEx.initCause(ex);
     
      throw sqlEx;
    }
   
    NonRegisteringDriver.trackConnection(this);
  }

    public void unSafeStatementInterceptors() throws SQLException {
     
      ArrayList<StatementInterceptorV2> unSafedStatementInterceptors = new ArrayList<StatementInterceptorV2>(this.statementInterceptors.size());

      for (int i = 0; i < this.statementInterceptors.size(); i++) {
        NoSubInterceptorWrapper wrappedInterceptor = (NoSubInterceptorWrapper) this.statementInterceptors.get(i);
       
        unSafedStatementInterceptors.add(wrappedInterceptor.getUnderlyingInterceptor());
      }
     
      this.statementInterceptors = unSafedStatementInterceptors;
     
      if (this.io != null) {
        this.io.setStatementInterceptors(this.statementInterceptors);
      }
  }
   
    public void initializeSafeStatementInterceptors() throws SQLException {
      this.isClosed = false;
     
      List<Extension> unwrappedInterceptors = Util.loadExtensions(this, this.props,
        getStatementInterceptors(),
        "MysqlIo.BadStatementInterceptor", getExceptionInterceptor());
     
      this.statementInterceptors = new ArrayList<StatementInterceptorV2>(unwrappedInterceptors.size());

      for (int i = 0; i < unwrappedInterceptors.size(); i++) {
        Extension interceptor = unwrappedInterceptors.get(i);
       
        // adapt older versions of statement interceptors, handle the case where something wants v2
        // functionality but wants to run with an older driver
        if (interceptor instanceof StatementInterceptor) {
          if (ReflectiveStatementInterceptorAdapter.getV2PostProcessMethod(interceptor.getClass()) != null) {
            this.statementInterceptors.add(new NoSubInterceptorWrapper(new ReflectiveStatementInterceptorAdapter((StatementInterceptor) interceptor)));
          } else {
            this.statementInterceptors.add(new NoSubInterceptorWrapper(new V1toV2StatementInterceptorAdapter((StatementInterceptor) interceptor)));
          }
        } else {
          this.statementInterceptors.add(new NoSubInterceptorWrapper((StatementInterceptorV2)interceptor));
        }
      }
     
     
    }
   
    public List<StatementInterceptorV2> getStatementInterceptorsInstances() {
      return this.statementInterceptors;
    }
   
  private void addToHistogram(int[] histogramCounts,
      long[] histogramBreakpoints, long value, int numberOfTimes,
      long currentLowerBound, long currentUpperBound) {
    if (histogramCounts == null) {
      createInitialHistogram(histogramBreakpoints,
          currentLowerBound, currentUpperBound);
    } else {
      for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
        if (histogramBreakpoints[i] >= value) {
          histogramCounts[i] += numberOfTimes;
 
          break;
        }
      }
    }
  }

  private void addToPerformanceHistogram(long value, int numberOfTimes) {
    checkAndCreatePerformanceHistogram();

    addToHistogram(this.perfMetricsHistCounts,
        this.perfMetricsHistBreakpoints, value, numberOfTimes,
        this.shortestQueryTimeMs == Long.MAX_VALUE ? 0
            : this.shortestQueryTimeMs, this.longestQueryTimeMs);
  }

  private void addToTablesAccessedHistogram(long value, int numberOfTimes) {
    checkAndCreateTablesAccessedHistogram();

    addToHistogram(this.numTablesMetricsHistCounts,
        this.numTablesMetricsHistBreakpoints, value, numberOfTimes,
        this.minimumNumberTablesAccessed == Long.MAX_VALUE ? 0
            : this.minimumNumberTablesAccessed,
        this.maximumNumberTablesAccessed);
  }
 
  /**
   * Builds the map needed for 4.1.0 and newer servers that maps field-level
   * charset/collation info to a java character encoding name.
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void buildCollationMapping() throws SQLException {

    HashMap<Integer, String> javaCharset = null;

    if (versionMeetsMinimum(4, 1, 0)) {

      TreeMap<Long, String> sortedCollationMap = null;
      HashMap<Integer, String> customCharset = null;
      HashMap<String, Integer> customMblen = null;

      if (getCacheServerConfiguration()) {
        synchronized (serverCollationByUrl) {
          sortedCollationMap = (TreeMap<Long, String>) serverCollationByUrl.get(getURL());
          javaCharset = (HashMap<Integer, String>) serverJavaCharsetByUrl.get(getURL());
          customCharset = (HashMap<Integer, String>) serverCustomCharsetByUrl.get(getURL());
          customMblen = (HashMap<String, Integer>) serverCustomMblenByUrl.get(getURL());
        }
      }

      java.sql.Statement stmt = null;
      java.sql.ResultSet results = null;

      try {
        if (sortedCollationMap == null) {
          sortedCollationMap = new TreeMap<Long, String>();
          javaCharset = new HashMap<Integer, String>();
          customCharset = new HashMap<Integer, String>();
          customMblen = new HashMap<String, Integer>();

          stmt = getMetadataSafeStatement();

          try {
            results = stmt.executeQuery("SHOW COLLATION");
            if (versionMeetsMinimum(5, 0, 0)) {
              Util.resultSetToMap(sortedCollationMap, results, 3, 2);
            } else {
              while (results.next()) {
                sortedCollationMap.put(results.getLong(3), results.getString(2));
              }
            }
          } catch (SQLException ex) {
            if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
              throw ex;
            }
          }

          for (Iterator<Map.Entry<Long, String>> indexIter = sortedCollationMap.entrySet().iterator(); indexIter.hasNext();) {
            Map.Entry<Long, String> indexEntry = indexIter.next();

            int collationIndex = indexEntry.getKey().intValue();
            String charsetName = indexEntry.getValue();

            javaCharset.put(collationIndex, getJavaEncodingForMysqlEncoding(charsetName));

            // if no static map for charsetIndex
            // or server has a different mapping then our static map,
            // adding it to custom map
            if (collationIndex >= CharsetMapping.MAP_SIZE ||
              !charsetName.equals(CharsetMapping.STATIC_INDEX_TO_MYSQL_CHARSET_MAP.get(collationIndex))) {
              customCharset.put(collationIndex, charsetName);
            }

            // if no static map for charsetName adding to custom map
            if (!CharsetMapping.STATIC_CHARSET_TO_NUM_BYTES_MAP.containsKey(charsetName) &&
              !CharsetMapping.STATIC_4_0_CHARSET_TO_NUM_BYTES_MAP.containsKey(charsetName)) {
              customMblen.put(charsetName, null);
            }
          }
         
          // if there is a number of custom charsets we should execute SHOW CHARACTER SET to know theirs mblen
          if (customMblen.size() > 0) {
            try {
              results = stmt.executeQuery("SHOW CHARACTER SET");
              while (results.next()) {
                String charsetName = results.getString("Charset");
                if (customMblen.containsKey(charsetName)) {
                  customMblen.put(charsetName, results.getInt("Maxlen"));
                }
              }
            } catch (SQLException ex) {
              if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
                throw ex;
              }
            }
          }

          if (getCacheServerConfiguration()) {
            synchronized (serverCollationByUrl) {
              serverCollationByUrl.put(getURL(), sortedCollationMap);
              serverJavaCharsetByUrl.put(getURL(), javaCharset);
              serverCustomCharsetByUrl.put(getURL(), customCharset);
              serverCustomMblenByUrl.put(getURL(), customMblen);
            }
          }

        }

        this.indexToJavaCharset = Collections.unmodifiableMap(javaCharset);
        this.indexToCustomMysqlCharset = Collections.unmodifiableMap(customCharset);
        this.mysqlCharsetToCustomMblen = Collections.unmodifiableMap(customMblen);
       
      } catch (SQLException ex) {
        throw ex;
      } catch (RuntimeException ex) {
        SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
        sqlEx.initCause(ex);
        throw sqlEx;
      } finally {
        if (results != null) {
          try {
            results.close();
          } catch (java.sql.SQLException sqlE) {
            // ignore
          }
        }

        if (stmt != null) {
          try {
            stmt.close();
          } catch (java.sql.SQLException sqlE) {
            // ignore
          }
        }
      }
    } else {
      javaCharset = new HashMap<Integer, String>();
      for (int i = 0; i < CharsetMapping.INDEX_TO_CHARSET.length; i++) {
        javaCharset.put(i, CharsetMapping.INDEX_TO_CHARSET[i]);
      }
      this.indexToJavaCharset = Collections.unmodifiableMap(javaCharset);
    }
  }

  public String getJavaEncodingForMysqlEncoding(String mysqlEncoding) throws SQLException {
   
    if (versionMeetsMinimum(4, 1, 0) && "latin1".equalsIgnoreCase(mysqlEncoding)) {
      return "Cp1252";
    }
   
    return CharsetMapping.MYSQL_TO_JAVA_CHARSET_MAP.get(mysqlEncoding);
  }

  private boolean canHandleAsServerPreparedStatement(String sql)
    throws SQLException {
    if (sql == null || sql.length() == 0) {
      return true;
    }

    if (!this.useServerPreparedStmts) {
      return false;
    }
   
    if (getCachePreparedStatements()) {
      synchronized (this.serverSideStatementCheckCache) {
        Boolean flag = (Boolean)this.serverSideStatementCheckCache.get(sql);
       
        if (flag != null) {
          return flag.booleanValue();
        }
         
        boolean canHandle = canHandleAsServerPreparedStatementNoCache(sql);
       
        if (sql.length() < getPreparedStatementCacheSqlLimit()) {
          this.serverSideStatementCheckCache.put(sql,
              canHandle ? Boolean.TRUE : Boolean.FALSE);
        }
         
        return canHandle;
      }
    }
   
    return canHandleAsServerPreparedStatementNoCache(sql);
  }

  private boolean canHandleAsServerPreparedStatementNoCache(String sql)
    throws SQLException {
   
    // Can't use server-side prepare for CALL
    if (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "CALL")) {
      return false;
    }
   
    boolean canHandleAsStatement = true;
   
    if (!versionMeetsMinimum(5, 0, 7) &&
        (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "SELECT")
        || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
            "DELETE")
        || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
            "INSERT")
        || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
            "UPDATE")
        || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
            "REPLACE"))) {

      // check for limit ?[,?]

      /*
       * The grammar for this (from the server) is: ULONG_NUM | ULONG_NUM
       * ',' ULONG_NUM | ULONG_NUM OFFSET_SYM ULONG_NUM
       */

      int currentPos = 0;
      int statementLength = sql.length();
      int lastPosToLook = statementLength - 7; // "LIMIT ".length()
      boolean allowBackslashEscapes = !this.noBackslashEscapes;
      char quoteChar = this.useAnsiQuotes ? '"' : '\'';
      boolean foundLimitWithPlaceholder = false;

      while (currentPos < lastPosToLook) {
        int limitStart = StringUtils.indexOfIgnoreCaseRespectQuotes(
            currentPos, sql, "LIMIT ", quoteChar,
            allowBackslashEscapes);

        if (limitStart == -1) {
          break;
        }

        currentPos = limitStart + 7;

        while (currentPos < statementLength) {
          char c = sql.charAt(currentPos);

          //
          // Have we reached the end
          // of what can be in a LIMIT clause?
          //

          if (!Character.isDigit(c) && !Character.isWhitespace(c)
              && c != ',' && c != '?') {
            break;
          }

          if (c == '?') {
            foundLimitWithPlaceholder = true;
            break;
          }

          currentPos++;
        }
      }

      canHandleAsStatement = !foundLimitWithPlaceholder;
    } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "CREATE TABLE")) {
      canHandleAsStatement = false;
    } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "DO")) {
      canHandleAsStatement = false;
    } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "SET")) {
      canHandleAsStatement = false;
    }

   
   
    return canHandleAsStatement;
  }

  /**
   * Changes the user on this connection by performing a re-authentication. If
   * authentication fails, the connection will remain under the context of the
   * current user.
   *
   * @param userName
   *            the username to authenticate with
   * @param newPassword
   *            the password to authenticate with
   * @throws SQLException
   *             if authentication fails, or some other error occurs while
   *             performing the command.
   */
  public void changeUser(String userName, String newPassword)
      throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
     
      if ((userName == null) || userName.equals("")) {
        userName = "";
      }
 
      if (newPassword == null) {
        newPassword = "";
      }

      try {
        this.io.changeUser(userName, newPassword, this.database);
      } catch(SQLException ex) {
        if (versionMeetsMinimum(5, 6, 13) && "28000".equals(ex.getSQLState())) {
          cleanup(ex);
        }
        throw ex;
      }
      this.user = userName;
      this.password = newPassword;
 
      if (versionMeetsMinimum(4, 1, 0)) {
        configureClientCharacterSet(true);
      }
     
      setSessionVariables();
     
      setupServerForTruncationChecks();
    }
  }

  private boolean characterSetNamesMatches(String mysqlEncodingName) {
    // set names is equivalent to character_set_client ..._results and ..._connection,
    // but we set _results later, so don't check it here.
    return (mysqlEncodingName != null &&
        mysqlEncodingName.equalsIgnoreCase(this.serverVariables.get("character_set_client")) &&
        mysqlEncodingName.equalsIgnoreCase(this.serverVariables.get("character_set_connection")));
  }

  private void checkAndCreatePerformanceHistogram() {
    if (this.perfMetricsHistCounts == null) {
      this.perfMetricsHistCounts = new int[HISTOGRAM_BUCKETS];
    }

    if (this.perfMetricsHistBreakpoints == null) {
      this.perfMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS];
    }
  }

  private void checkAndCreateTablesAccessedHistogram() {
    if (this.numTablesMetricsHistCounts == null) {
      this.numTablesMetricsHistCounts = new int[HISTOGRAM_BUCKETS];
    }

    if (this.numTablesMetricsHistBreakpoints == null) {
      this.numTablesMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS];
    }
  }

  public void checkClosed() throws SQLException {
    if (this.isClosed) {
      throwConnectionClosedException();
    }
  }

  public void throwConnectionClosedException() throws SQLException {
    StringBuffer messageBuf = new StringBuffer(
        "No operations allowed after connection closed.");

    SQLException ex = SQLError.createSQLException(messageBuf.toString(),
        SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
   
    if (this.forceClosedReason != null) {
      ex.initCause(this.forceClosedReason);
    }

    throw ex;
  }

  /**
   * If useUnicode flag is set and explicit client character encoding isn't
   * specified then assign encoding from server if any.
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void checkServerEncoding() throws SQLException {
    if (getUseUnicode() && (getEncoding() != null)) {
      // spec'd by client, don't map
      return;
    }

    String serverEncoding = this.serverVariables.get("character_set");

    if (serverEncoding == null) {
      // must be 4.1.1 or newer?
      serverEncoding = this.serverVariables.get("character_set_server");
    }

    String mappedServerEncoding = null;

    if (serverEncoding != null) {
      try {
        mappedServerEncoding = getJavaEncodingForMysqlEncoding(serverEncoding
              .toUpperCase(Locale.ENGLISH));
      } catch (SQLException ex) {
        throw ex;
      } catch (RuntimeException ex) {
        SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
        sqlEx.initCause(ex);
        throw sqlEx;
      }
    }

    //
    // First check if we can do the encoding ourselves
    //
    if (!getUseUnicode() && (mappedServerEncoding != null)) {
      SingleByteCharsetConverter converter = getCharsetConverter(mappedServerEncoding);

      if (converter != null) { // we know how to convert this ourselves
        setUseUnicode(true); // force the issue
        setEncoding(mappedServerEncoding);

        return;
      }
    }

    //
    // Now, try and find a Java I/O converter that can do
    // the encoding for us
    //
    if (serverEncoding != null) {
      if (mappedServerEncoding == null) {
        // We don't have a mapping for it, so try
        // and canonicalize the name....
        if (Character.isLowerCase(serverEncoding.charAt(0))) {
          char[] ach = serverEncoding.toCharArray();
          ach[0] = Character.toUpperCase(serverEncoding.charAt(0));
          setEncoding(new String(ach));
        }
      }

      if (mappedServerEncoding == null) {
        throw SQLError.createSQLException("Unknown character encoding on server '"
            + serverEncoding
            + "', use 'characterEncoding=' property "
            + " to provide correct mapping",
            SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
      }

      //
      // Attempt to use the encoding, and bail out if it
      // can't be used
      //
      try {
        StringUtils.getBytes("abc",mappedServerEncoding);
        setEncoding(mappedServerEncoding);
        setUseUnicode(true);
      } catch (UnsupportedEncodingException UE) {
        throw SQLError.createSQLException(
            "The driver can not map the character encoding '"
                + getEncoding()
                + "' that your server is using "
                + "to a character encoding your JVM understands. You "
                + "can specify this mapping manually by adding \"useUnicode=true\" "
                + "as well as \"characterEncoding=[an_encoding_your_jvm_understands]\" "
                + "to your JDBC URL.", "0S100", getExceptionInterceptor());
      }
    }
  }

  /**
   * Set transaction isolation level to the value received from server if any.
   * Is called by connectionInit(...)
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void checkTransactionIsolationLevel() throws SQLException {
    String txIsolationName = null;

    if (versionMeetsMinimum(4, 0, 3)) {
      txIsolationName = "tx_isolation";
    } else {
      txIsolationName = "transaction_isolation";
    }

    String s = this.serverVariables.get(txIsolationName);

    if (s != null) {
      Integer intTI = mapTransIsolationNameToValue.get(s);

      if (intTI != null) {
        this.isolationLevel = intTI.intValue();
      }
    }
  }

  /**
   * Clobbers the physical network connection and marks
   * this connection as closed.
   *
   * @throws SQLException
   */
  public void abortInternal() throws SQLException {
    if (this.io != null) {
      try {
        this.io.forceClose();
      } catch (Throwable t) {
        // can't do anything about it, and we're forcibly aborting
      }
      this.io.releaseResources();
      this.io = null;
    }
   
    this.isClosed = true;
  }
 
  /**
   * Destroys this connection and any underlying resources
   *
   * @param fromWhere
   *            DOCUMENT ME!
   * @param whyCleanedUp
   *            DOCUMENT ME!
   */
  private void cleanup(Throwable whyCleanedUp) {
    try {
      if (this.io != null) {
        if (isClosed()) {
          this.io.forceClose();
        } else {
          realClose(false, false, false, whyCleanedUp);
        }
      }
    } catch (SQLException sqlEx) {
      // ignore, we're going away.
      ;
    }

    this.isClosed = true;
  }

  public void clearHasTriedMaster() {
    this.hasTriedMasterFlag = false;
  }
 
  /**
   * After this call, getWarnings returns null until a new warning is reported
   * for this connection.
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public void clearWarnings() throws SQLException {
    // firstWarning = null;
  }

  /**
   * DOCUMENT ME!
   *
   * @param sql
   *            DOCUMENT ME!
   * @return DOCUMENT ME!
   * @throws SQLException
   *             DOCUMENT ME!
   */
  public java.sql.PreparedStatement clientPrepareStatement(String sql)
      throws SQLException {
    return clientPrepareStatement(sql,
        DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY);
  }

  /**
   * @see Connection#prepareStatement(String, int)
   */
  public java.sql.PreparedStatement clientPrepareStatement(String sql,
      int autoGenKeyIndex) throws SQLException {
    java.sql.PreparedStatement pStmt = clientPrepareStatement(sql);

    ((com.mysql.jdbc.PreparedStatement) pStmt)
        .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);

    return pStmt;
  }

  /**
   * DOCUMENT ME!
   *
   * @param sql
   *            DOCUMENT ME!
   * @param resultSetType
   *            DOCUMENT ME!
   * @param resultSetConcurrency
   *            DOCUMENT ME!
   * @return DOCUMENT ME!
   * @throws SQLException
   *             DOCUMENT ME!
   */
  public java.sql.PreparedStatement clientPrepareStatement(String sql,
      int resultSetType, int resultSetConcurrency) throws SQLException {
    return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true);
  }


 
  public java.sql.PreparedStatement clientPrepareStatement(String sql,
      int resultSetType, int resultSetConcurrency,
      boolean processEscapeCodesIfNeeded) throws SQLException {
    checkClosed();

    String nativeSql = processEscapeCodesIfNeeded && getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
   
    PreparedStatement pStmt = null;

    if (getCachePreparedStatements()) {
      PreparedStatement.ParseInfo pStmtInfo = this.cachedPreparedStatementParams.get(nativeSql);
      if (pStmtInfo == null) {
        pStmt = com.mysql.jdbc.PreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
            this.database);

        this.cachedPreparedStatementParams.put(nativeSql, pStmt
              .getParseInfo());
      } else {
        pStmt = new com.mysql.jdbc.PreparedStatement(getLoadBalanceSafeProxy(), nativeSql,
            this.database, pStmtInfo);
      }
    } else {
      pStmt = com.mysql.jdbc.PreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
          this.database);
    }

    pStmt.setResultSetType(resultSetType);
    pStmt.setResultSetConcurrency(resultSetConcurrency);

    return pStmt;
  }
 
  /**
   * @see java.sql.Connection#prepareStatement(String, int[])
   */
  public java.sql.PreparedStatement clientPrepareStatement(String sql,
      int[] autoGenKeyIndexes) throws SQLException {
   
    PreparedStatement pStmt = (PreparedStatement) clientPrepareStatement(sql);
   
    pStmt
        .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
            && (autoGenKeyIndexes.length > 0));

    return pStmt;
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, String[])
   */
  public java.sql.PreparedStatement clientPrepareStatement(String sql,
      String[] autoGenKeyColNames) throws SQLException {
    PreparedStatement pStmt = (PreparedStatement) clientPrepareStatement(sql);

    pStmt
        .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
            && (autoGenKeyColNames.length > 0));

    return pStmt;
  }

  public java.sql.PreparedStatement clientPrepareStatement(String sql,
      int resultSetType, int resultSetConcurrency,
      int resultSetHoldability) throws SQLException {
    return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true);
  }
 
  // --------------------------JDBC 2.0-----------------------------

  /**
   * In some cases, it is desirable to immediately release a Connection's
   * database and JDBC resources instead of waiting for them to be
   * automatically released (cant think why off the top of my head) <B>Note:</B>
   * A Connection is automatically closed when it is garbage collected.
   * Certain fatal errors also result in a closed connection.
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public void close() throws SQLException {
    synchronized (getConnectionMutex()) {
      if (this.connectionLifecycleInterceptors != null) {
        new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
          void forEach(Extension each) throws SQLException {
            ((ConnectionLifecycleInterceptor)each).close();
          }
        }.doForAll();
      }
   
      realClose(true, true, false, null);
    }
  }

  /**
   * Closes all currently open statements.
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void closeAllOpenStatements() throws SQLException {
    SQLException postponedException = null;

    if (this.openStatements != null) {
      List<Statement> currentlyOpenStatements = new ArrayList<Statement>(); // we need this to
      // avoid
      // ConcurrentModificationEx

      for (Iterator<Statement> iter = this.openStatements.keySet().iterator(); iter.hasNext();) {
        currentlyOpenStatements.add(iter.next());
      }

      int numStmts = currentlyOpenStatements.size();

      for (int i = 0; i < numStmts; i++) {
        StatementImpl stmt = (StatementImpl) currentlyOpenStatements.get(i);

        try {
          stmt.realClose(false, true);
        } catch (SQLException sqlEx) {
          postponedException = sqlEx; // throw it later, cleanup all
          // statements first
        }
      }

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

  private void closeStatement(java.sql.Statement stmt) {
    if (stmt != null) {
      try {
        stmt.close();
      } catch (SQLException sqlEx) {
        ; // ignore
      }

      stmt = null;
    }
  }

  /**
   * The method commit() makes all changes made since the previous
   * commit/rollback permanent and releases any database locks currently held
   * by the Connection. This method should only be used when auto-commit has
   * been disabled.
   * <p>
   * <b>Note:</b> MySQL does not support transactions, so this method is a
   * no-op.
   * </p>
   *
   * @exception SQLException
   *                if a database access error occurs
   * @see setAutoCommit
   */
  public void commit() throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
 
      try {
        if (this.connectionLifecycleInterceptors != null) {
          IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
            void forEach(Extension each) throws SQLException {
              if (!((ConnectionLifecycleInterceptor)each).commit()) {
                this.stopIterating = true;
              }
            }
          };
         
          iter.doForAll();
         
          if (!iter.fullIteration()) {
            return;
          }
        }
       
        // no-op if _relaxAutoCommit == true
        if (this.autoCommit && !getRelaxAutoCommit()) {
          throw SQLError.createSQLException("Can't call commit when autocommit=true", getExceptionInterceptor());
        } else if (this.transactionsSupported) {
          if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) {
            if (!this.io.inTransactionOnServer()) {
              return; // effectively a no-op
            }
          }
 
          execSQL(null, "commit", -1, null,
              DEFAULT_RESULT_SET_TYPE,
              DEFAULT_RESULT_SET_CONCURRENCY, false,
              this.database, null,
              false);
        }
      } catch (SQLException sqlException) {
        if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
            .equals(sqlException.getSQLState())) {
          throw SQLError
              .createSQLException(
                  "Communications link failure during commit(). Transaction resolution unknown.",
                  SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN,
                  getExceptionInterceptor());
        }
 
        throw sqlException;
      } finally {
        this.needsPing = this.getReconnectAtTxEnd();
      }
    }
    return;
  }
 
  /**
   * Configures client-side properties for character set information.
   *
   * @throws SQLException
   *             if unable to configure the specified character set.
   */
  private void configureCharsetProperties() throws SQLException {
    if (getEncoding() != null) {
      // Attempt to use the encoding, and bail out if it
      // can't be used
      try {
        String testString = "abc";
        StringUtils.getBytes(testString, getEncoding());
      } catch (UnsupportedEncodingException UE) {
        // Try the MySQL character encoding, then....
        String oldEncoding = getEncoding();

        try {
          setEncoding(getJavaEncodingForMysqlEncoding(oldEncoding));
        } catch (SQLException ex) {
          throw ex;
        } catch (RuntimeException ex) {
          SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
          sqlEx.initCause(ex);
          throw sqlEx;
        }

        if (getEncoding() == null) {
          throw SQLError.createSQLException(
              "Java does not support the MySQL character encoding "
                  + " " + "encoding '" + oldEncoding + "'.",
              SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
        }

        try {
          String testString = "abc";
          StringUtils.getBytes(testString, getEncoding());
        } catch (UnsupportedEncodingException encodingEx) {
          throw SQLError.createSQLException("Unsupported character "
              + "encoding '" + getEncoding() + "'.",
              SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
        }
      }
    }
  }

  /**
   * Sets up client character set for MySQL-4.1 and newer if the user This
   * must be done before any further communication with the server!
   *
   * @return true if this routine actually configured the client character
   *         set, or false if the driver needs to use 'older' methods to
   *         detect the character set, as it is connected to a MySQL server
   *         older than 4.1.0
   * @throws SQLException
   *             if an exception happens while sending 'SET NAMES' to the
   *             server, or the server sends character set information that
   *             the client doesn't know about.
   */
  private boolean configureClientCharacterSet(boolean dontCheckServerMatch) throws SQLException {
    String realJavaEncoding = getEncoding();
    boolean characterSetAlreadyConfigured = false;

    try {
      if (versionMeetsMinimum(4, 1, 0)) {
        characterSetAlreadyConfigured = true;

        setUseUnicode(true);

        configureCharsetProperties();
        realJavaEncoding = getEncoding(); // we need to do this again
        // to grab this for
        // versions > 4.1.0

        try {

          // Fault injection for testing server character set indices
               
                if (props != null && props.getProperty("com.mysql.jdbc.faultInjection.serverCharsetIndex") != null) {
                  this.io.serverCharsetIndex = Integer.parseInt(
                      props.getProperty(
                          "com.mysql.jdbc.faultInjection.serverCharsetIndex"))
                }
               
          String serverEncodingToSet =
            CharsetMapping.INDEX_TO_CHARSET[this.io.serverCharsetIndex];
         
          if (serverEncodingToSet == null || serverEncodingToSet.length() == 0) {
            if (realJavaEncoding != null) {
              // user knows best, try it
              setEncoding(realJavaEncoding);
            } else {
              throw SQLError.createSQLException(
                  "Unknown initial character set index '"
                      + this.io.serverCharsetIndex
                      + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.",
                  SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
            }
          }
         
          // "latin1" on MySQL-4.1.0+ is actually CP1252, not ISO8859_1
          if (versionMeetsMinimum(4, 1, 0) &&
              "ISO8859_1".equalsIgnoreCase(serverEncodingToSet)) {
            serverEncodingToSet = "Cp1252";
          }
          if ("UnicodeBig".equalsIgnoreCase(serverEncodingToSet) ||
              "UTF-16".equalsIgnoreCase(serverEncodingToSet) ||
              "UTF-16LE".equalsIgnoreCase(serverEncodingToSet) ||
              "UTF-32".equalsIgnoreCase(serverEncodingToSet)
              ) {
            serverEncodingToSet = "UTF-8";
          }

          setEncoding(serverEncodingToSet);
       
        } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) {
          if (realJavaEncoding != null) {
            // user knows best, try it
            setEncoding(realJavaEncoding);
          } else {
            throw SQLError.createSQLException(
                "Unknown initial character set index '"
                    + this.io.serverCharsetIndex
                    + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.",
                SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
          }
        } catch (SQLException ex) {
          throw ex;
        } catch (RuntimeException ex) {
          SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
          sqlEx.initCause(ex);
          throw sqlEx;
        }

        if (getEncoding() == null) {
          // punt?
          setEncoding("ISO8859_1");
        }

        //
        // Has the user has 'forced' the character encoding via
        // driver properties?
        //
        if (getUseUnicode()) {
          if (realJavaEncoding != null) {

            //
            // Now, inform the server what character set we
            // will be using from now-on...
            //
            if (realJavaEncoding.equalsIgnoreCase("UTF-8")
                || realJavaEncoding.equalsIgnoreCase("UTF8")) {
              // charset names are case-sensitive

              boolean utf8mb4Supported = versionMeetsMinimum(5, 5, 2);
              boolean useutf8mb4 = false;
             
              if (utf8mb4Supported) {
                useutf8mb4 = (this.io.serverCharsetIndex == 45);
              }
             
              if (!getUseOldUTF8Behavior()) {
                if (dontCheckServerMatch || !characterSetNamesMatches("utf8")
                    || (utf8mb4Supported && !characterSetNamesMatches("utf8mb4"))) {
                  execSQL(null, "SET NAMES " + (useutf8mb4 ? "utf8mb4" : "utf8"), -1, null,
                      DEFAULT_RESULT_SET_TYPE,
                      DEFAULT_RESULT_SET_CONCURRENCY,
                      false, this.database, null, false);
                }
              } else {
                execSQL(null, "SET NAMES latin1", -1, null,
                    DEFAULT_RESULT_SET_TYPE,
                    DEFAULT_RESULT_SET_CONCURRENCY,
                    false, this.database, null, false);
              }

              setEncoding(realJavaEncoding);
            } /* not utf-8 */else {
              String mysqlEncodingName = CharsetMapping
                  .getMysqlEncodingForJavaEncoding(
                      realJavaEncoding
                          .toUpperCase(Locale.ENGLISH),
                      this);

              /*
               * if ("koi8_ru".equals(mysqlEncodingName)) { //
               * This has a _different_ name in 4.1...
               * mysqlEncodingName = "ko18r"; } else if
               * ("euc_kr".equals(mysqlEncodingName)) { //
               * Different name in 4.1 mysqlEncodingName =
               * "euckr"; }
               */

              if (mysqlEncodingName != null) {
               
                if (dontCheckServerMatch || !characterSetNamesMatches(mysqlEncodingName)) {
                  execSQL(null, "SET NAMES " + mysqlEncodingName,
                    -1, null,
                    DEFAULT_RESULT_SET_TYPE,
                    DEFAULT_RESULT_SET_CONCURRENCY,
                    false, this.database, null, false);
                }
              }

              // Switch driver's encoding now, since the server
              // knows what we're sending...
              //
              setEncoding(realJavaEncoding);
            }
          } else if (getEncoding() != null) {
            // Tell the server we'll use the server default charset
            // to send our
            // queries from now on....
            String mysqlEncodingName = getServerCharacterEncoding();
           
            if(getUseOldUTF8Behavior()){
              mysqlEncodingName = "latin1";
            }

            boolean ucs2 = false;
            if "ucs2".equalsIgnoreCase(mysqlEncodingName) ||
                "utf16".equalsIgnoreCase(mysqlEncodingName) ||
                "utf16le".equalsIgnoreCase(mysqlEncodingName) ||
                "utf32".equalsIgnoreCase(mysqlEncodingName)) {
              mysqlEncodingName = "utf8";
              ucs2 = true;
              if (getCharacterSetResults() == null) {
                setCharacterSetResults("UTF-8");
              }
            }

            if (dontCheckServerMatch || !characterSetNamesMatches(mysqlEncodingName) || ucs2) {
              try {
                execSQL(null, "SET NAMES " + mysqlEncodingName, -1,
                    null, DEFAULT_RESULT_SET_TYPE,
                    DEFAULT_RESULT_SET_CONCURRENCY, false,
                    this.database, null, false);
              } catch (SQLException ex) {
                if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
                  throw ex;
                }
              }
            }

            realJavaEncoding = getEncoding();
          }

        }

        //
        // We know how to deal with any charset coming back from
        // the database, so tell the server not to do conversion
        // if the user hasn't 'forced' a result-set character set
        //

        String onServer = null;
        boolean isNullOnServer = false;
       
        if (this.serverVariables != null) {
          onServer = this.serverVariables.get("character_set_results");
         
          isNullOnServer = onServer == null || "NULL".equalsIgnoreCase(onServer) || onServer.length() == 0;
        }
       
        if (getCharacterSetResults() == null) {
         
          //
          // Only send if needed, if we're caching server variables
          // we -have- to send, because we don't know what it was
          // before we cached them.
          //
          if (!isNullOnServer) {
            try {
              execSQL(null, "SET character_set_results = NULL", -1, null,
                  DEFAULT_RESULT_SET_TYPE,
                  DEFAULT_RESULT_SET_CONCURRENCY, false,
                  this.database, null,
                  false);
            } catch (SQLException ex) {
              if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
                throw ex;
              }
            }
            if (!this.usingCachedConfig) {
              this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, null);
            }
          } else {
            if (!this.usingCachedConfig) {
              this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, onServer);
            }
          }
        } else {

          if(getUseOldUTF8Behavior()){
            try {
              execSQL(null, "SET NAMES " + "latin1", -1,
                null, DEFAULT_RESULT_SET_TYPE,
                DEFAULT_RESULT_SET_CONCURRENCY, false,
                this.database, null, false);
            } catch (SQLException ex) {
              if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
                throw ex;
              }
            }
          }
          String charsetResults = getCharacterSetResults();
          String mysqlEncodingName = null;

          if ("UTF-8".equalsIgnoreCase(charsetResults)
              || "UTF8".equalsIgnoreCase(charsetResults)) {
            mysqlEncodingName = "utf8";
          } else if ("null".equalsIgnoreCase(charsetResults)) {
            mysqlEncodingName = "NULL";
          } else {
            mysqlEncodingName = CharsetMapping
                .getMysqlEncodingForJavaEncoding(charsetResults
                    .toUpperCase(Locale.ENGLISH), this);
          }

          //
          // Only change the value if needed
          //
         
          if (mysqlEncodingName == null) {
            throw SQLError.createSQLException(
                "Can't map "+charsetResults+" given for characterSetResults to a supported MySQL encoding.",
                SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
          }

          if (!mysqlEncodingName.equalsIgnoreCase(
              this.serverVariables.get("character_set_results"))) {
            StringBuffer setBuf = new StringBuffer(
                "SET character_set_results = ".length()
                    + mysqlEncodingName.length());
            setBuf.append("SET character_set_results = ").append(
                mysqlEncodingName);
 
            try {
              execSQL(null, setBuf.toString(), -1, null,
                  DEFAULT_RESULT_SET_TYPE,
                  DEFAULT_RESULT_SET_CONCURRENCY, false,
                  this.database, null, false);
            } catch (SQLException ex) {
              if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
                throw ex;
              }
            }
           
            if (!this.usingCachedConfig) {
              this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS,
                mysqlEncodingName);
            }

            // We have to set errorMessageEncoding according to new value
            // of charsetResults for server version 5.5 and higher
            if (versionMeetsMinimum(5, 5, 0)) {
              this.errorMessageEncoding = charsetResults;
            }

          } else {
            if (!this.usingCachedConfig) {
              this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, onServer);
            }
          }
        }

        if (getConnectionCollation() != null) {
          StringBuffer setBuf = new StringBuffer(
              "SET collation_connection = ".length()
                  + getConnectionCollation().length());
          setBuf.append("SET collation_connection = ").append(
              getConnectionCollation());

          try {
            execSQL(null, setBuf.toString(), -1, null,
                DEFAULT_RESULT_SET_TYPE,
                DEFAULT_RESULT_SET_CONCURRENCY, false,
                this.database, null, false);
          } catch (SQLException ex) {
            if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
              throw ex;
            }
          }
        }
      } else {
        // Use what the server has specified
        realJavaEncoding = getEncoding(); // so we don't get
        // swapped out in the finally
        // block....
      }
    } finally {
      // Failsafe, make sure that the driver's notion of character
      // encoding matches what the user has specified.
      //
      setEncoding(realJavaEncoding);
    }
   
    /**
     * Check if we need a CharsetEncoder for escaping codepoints that are
     * transformed to backslash (0x5c) in the connection encoding.
     */
    try {
      CharsetEncoder enc = Charset.forName(getEncoding()).newEncoder();
      CharBuffer cbuf = CharBuffer.allocate(1);
      ByteBuffer bbuf = ByteBuffer.allocate(1);

      cbuf.put("\u00a5");
      cbuf.position(0);
      enc.encode(cbuf, bbuf, true);
      if(bbuf.get(0) == '\\') {
        requiresEscapingEncoder = true;
      } else {
        cbuf.clear();
        bbuf.clear();
       
        cbuf.put("\u20a9");
        cbuf.position(0);
        enc.encode(cbuf, bbuf, true);
        if(bbuf.get(0) == '\\') {
          requiresEscapingEncoder = true;
        }
      }
    } catch(java.nio.charset.UnsupportedCharsetException ucex) {
      // fallback to String API - for Java 1.4
      try {
        byte bbuf[] = StringUtils.getBytes("\u00a5", getEncoding());
        if (bbuf[0] == '\\') {
          requiresEscapingEncoder = true;
        } else {
          bbuf = StringUtils.getBytes("\u20a9", getEncoding());
          if (bbuf[0] == '\\') {
            requiresEscapingEncoder = true;
          }
        }
      } catch(UnsupportedEncodingException ueex) {
        throw SQLError.createSQLException("Unable to use encoding: " + getEncoding(),
            SQLError.SQL_STATE_GENERAL_ERROR, ueex,
            getExceptionInterceptor());
      }
    }

    return characterSetAlreadyConfigured;
  }

  /**
   * Configures the client's timezone if required.
   *
   * @throws SQLException
   *             if the timezone the server is configured to use can't be
   *             mapped to a Java timezone.
   */
  private void configureTimezone() throws SQLException {
    String configuredTimeZoneOnServer = this.serverVariables.get("timezone");

    if (configuredTimeZoneOnServer == null) {
      configuredTimeZoneOnServer = this.serverVariables.get("time_zone");

      if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
        configuredTimeZoneOnServer = this.serverVariables.get("system_time_zone");
      }
    }

    String canoncicalTimezone = getServerTimezone();
   
    if ((getUseTimezone() || !getUseLegacyDatetimeCode()) && configuredTimeZoneOnServer != null) {
      // user can override this with driver properties, so don't detect if that's the case
      if (canoncicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canoncicalTimezone)) {
        try {
          canoncicalTimezone = TimeUtil
              .getCanoncialTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());

          if (canoncicalTimezone == null) {
            throw SQLError.createSQLException("Can't map timezone '"
                + configuredTimeZoneOnServer + "' to "
                + " canonical timezone.",
                SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
          }
        } catch (IllegalArgumentException iae) {
          throw SQLError.createSQLException(iae.getMessage(),
              SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        }
      }
    } else {
      canoncicalTimezone = getServerTimezone();
    }
   
    if (canoncicalTimezone != null && canoncicalTimezone.length() > 0) {
        this.serverTimezoneTZ = TimeZone.getTimeZone(canoncicalTimezone);

      //
      // The Calendar class has the behavior of mapping
      // unknown timezones to 'GMT' instead of throwing an
      // exception, so we must check for this...
      //
      if (!canoncicalTimezone.equalsIgnoreCase("GMT")
          && this.serverTimezoneTZ.getID().equals("GMT")) {
        throw SQLError.createSQLException("No timezone mapping entry for '"
            + canoncicalTimezone + "'",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }

      if ("GMT".equalsIgnoreCase(this.serverTimezoneTZ.getID())) {
        this.isServerTzUTC = true;
      } else {
        this.isServerTzUTC = false;
      }
    }
  }

  private void createInitialHistogram(long[] breakpoints,
      long lowerBound, long upperBound) {

    double bucketSize = (((double) upperBound - (double) lowerBound) / HISTOGRAM_BUCKETS) * 1.25;

    if (bucketSize < 1) {
      bucketSize = 1;
    }

    for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
      breakpoints[i] = lowerBound;
      lowerBound += bucketSize;
    }
  }

  /**
   * Creates an IO channel to the server
   *
   * @param isForReconnect
   *            is this request for a re-connect
   * @return a new MysqlIO instance connected to a server
   * @throws SQLException
   *             if a database access error occurs
   * @throws CommunicationsException
   *             DOCUMENT ME!
   */
  public void createNewIO(boolean isForReconnect)
      throws SQLException {
    synchronized (getConnectionMutex()) {
      // Synchronization Not needed for *new* connections, but defintely for
      // connections going through fail-over, since we might get the
      // new connection up and running *enough* to start sending
      // cached or still-open server-side prepared statements over
      // to the backend before we get a chance to re-prepare them...
     
 
      Properties mergedProps  = exposeAsProperties(this.props);
 
      if (!getHighAvailability()) {
        connectOneTryOnly(isForReconnect, mergedProps);
       
        return;
      }
 
      connectWithRetries(isForReconnect, mergedProps);
    }   
  }

  private void connectWithRetries(boolean isForReconnect,
      Properties mergedProps) throws SQLException {
    double timeout = getInitialTimeout();
    boolean connectionGood = false;

    Exception connectionException = null;

    for (int attemptCount = 0; (attemptCount < getMaxReconnects())
        && !connectionGood; attemptCount++) {
      try {
        if (this.io != null) {
          this.io.forceClose();
        }

        coreConnect(mergedProps);
        pingInternal(false, 0);
       
        boolean oldAutoCommit;
        int oldIsolationLevel;
        boolean oldReadOnly;
        String oldCatalog;
       
        synchronized (getConnectionMutex()) {
          this.connectionId = this.io.getThreadId();
          this.isClosed = false;

          // save state from old connection
          oldAutoCommit = getAutoCommit();
          oldIsolationLevel = this.isolationLevel;
          oldReadOnly = isReadOnly(false);
          oldCatalog = getCatalog();
 
          this.io.setStatementInterceptors(this.statementInterceptors);
        }
       
        // Server properties might be different
        // from previous connection, so initialize
        // again...
        initializePropsFromServer();

        if (isForReconnect) {
          // Restore state from old connection
          setAutoCommit(oldAutoCommit);

          if (this.hasIsolationLevels) {
            setTransactionIsolation(oldIsolationLevel);
          }

          setCatalog(oldCatalog);
          setReadOnly(oldReadOnly);
        }

        connectionGood = true;

        break;
      } catch (Exception EEE) {
        connectionException = EEE;
        connectionGood = false;
      }

        if (connectionGood) {
          break;
        }

        if (attemptCount > 0) {
          try {
            Thread.sleep((long) timeout * 1000);
          } catch (InterruptedException IE) {
            // ignore
          }
        }
      } // end attempts for a single host

    if (!connectionGood) {
      // We've really failed!
      SQLException chainedEx = SQLError.createSQLException(
          Messages.getString("Connection.UnableToConnectWithRetries",
              new Object[] {Integer.valueOf(getMaxReconnects())}),
          SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
      chainedEx.initCause(connectionException);
     
      throw chainedEx;
    }

    if (getParanoid() && !getHighAvailability()) {
      this.password = null;
      this.user = null;
    }

    if (isForReconnect) {
      //
      // Retrieve any 'lost' prepared statements if re-connecting
      //
      Iterator<Statement> statementIter = this.openStatements.values().iterator();

      //
      // We build a list of these outside the map of open statements,
      // because
      // in the process of re-preparing, we might end up having to
      // close
      // a prepared statement, thus removing it from the map, and
      // generating
      // a ConcurrentModificationException
      //
      Stack<Statement> serverPreparedStatements = null;

      while (statementIter.hasNext()) {
        Statement statementObj = statementIter.next();

        if (statementObj instanceof ServerPreparedStatement) {
          if (serverPreparedStatements == null) {
            serverPreparedStatements = new Stack<Statement>();
          }

          serverPreparedStatements.add(statementObj);
        }
      }

      if (serverPreparedStatements != null) {
        while (!serverPreparedStatements.isEmpty()) {
          ((ServerPreparedStatement) serverPreparedStatements
              .pop()).rePrepare();
        }
      }
    }
  }

  private void coreConnect(Properties mergedProps) throws SQLException,
      IOException {
    int newPort = 3306;
    String newHost = "localhost";
   
    String protocol = mergedProps.getProperty(NonRegisteringDriver.PROTOCOL_PROPERTY_KEY);
   
    if (protocol != null) {
      // "new" style URL

      if ("tcp".equalsIgnoreCase(protocol)) {
        newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY));
        newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306"));
      } else if ("pipe".equalsIgnoreCase(protocol)) {
        setSocketFactoryClassName(NamedPipeSocketFactory.class.getName());
       
        String path = mergedProps.getProperty(NonRegisteringDriver.PATH_PROPERTY_KEY);
       
        if (path != null) {
          mergedProps.setProperty(NamedPipeSocketFactory.NAMED_PIPE_PROP_NAME, path);
        }
      } else {
        // normalize for all unknown protocols
        newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY));
        newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306"));
      }
    } else {
   
      String[] parsedHostPortPair = NonRegisteringDriver
          .parseHostPortPair(this.hostPortPair);
      newHost = parsedHostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];

      newHost = normalizeHost(newHost);

      if (parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {
        newPort = parsePortNumber(parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);
      }
    }

    this.port = newPort;
    this.host = newHost;
   
    this.io = new MysqlIO(newHost, newPort,
        mergedProps, getSocketFactoryClassName(),
        getProxy(), getSocketTimeout(),
        this.largeRowSizeThreshold.getValueAsInt());
    this.io.doHandshake(this.user, this.password,
        this.database);
  }

  private String normalizeHost(String hostname) {
    if (hostname == null || StringUtils.isEmptyOrWhitespaceOnly(hostname)) {
      return "localhost";
    }
   
    return hostname;
  }
  private int parsePortNumber(String portAsString)
      throws SQLException {
    int portNumber = 3306;
    try {
      portNumber = Integer
          .parseInt(portAsString);
    } catch (NumberFormatException nfe) {
      throw SQLError.createSQLException(
          "Illegal connection port value '"
              + portAsString
              + "'",
          SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
    }
    return portNumber;
  }

  private void connectOneTryOnly(boolean isForReconnect,
      Properties mergedProps) throws SQLException {
    Exception connectionNotEstablishedBecause = null;

    try {
     
      coreConnect(mergedProps);
      this.connectionId = this.io.getThreadId();
      this.isClosed = false;

      // save state from old connection
      boolean oldAutoCommit = getAutoCommit();
      int oldIsolationLevel = this.isolationLevel;
      boolean oldReadOnly = isReadOnly(false);
      String oldCatalog = getCatalog();

      this.io.setStatementInterceptors(this.statementInterceptors);
     
      // Server properties might be different
      // from previous connection, so initialize
      // again...
      initializePropsFromServer();

      if (isForReconnect) {
        // Restore state from old connection
        setAutoCommit(oldAutoCommit);

        if (this.hasIsolationLevels) {
          setTransactionIsolation(oldIsolationLevel);
        }

        setCatalog(oldCatalog);
       
        setReadOnly(oldReadOnly);
      }
      return;
     
    } catch (Exception EEE) {

      if (EEE instanceof SQLException
          && ((SQLException)EEE).getErrorCode() == MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD
          && !getDisconnectOnExpiredPasswords()) {
        return;
      }

      if (this.io != null) {
        this.io.forceClose();
      }

      connectionNotEstablishedBecause = EEE;

      if (EEE instanceof SQLException) {
        throw (SQLException)EEE;
      }
     
      SQLException chainedEx = SQLError.createSQLException(
          Messages.getString("Connection.UnableToConnect"),
          SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
      chainedEx.initCause(connectionNotEstablishedBecause);
     
      throw chainedEx;
    }
  }

  private void createPreparedStatementCaches() throws SQLException {
    synchronized (getConnectionMutex()) {
      int cacheSize = getPreparedStatementCacheSize();
     
      try {
        Class<?> factoryClass;
       
        factoryClass = Class.forName(getParseInfoCacheFactory());
       
        @SuppressWarnings("unchecked")
        CacheAdapterFactory<String, ParseInfo> cacheFactory = ((CacheAdapterFactory<String, ParseInfo>)factoryClass.newInstance());
       
        this.cachedPreparedStatementParams = cacheFactory.getInstance(this, myURL, getPreparedStatementCacheSize(), getPreparedStatementCacheSqlLimit(), props);
       
      } catch (ClassNotFoundException e) {
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.CantFindCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}),
            getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      } catch (InstantiationException e) {
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}),
            getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      } catch (IllegalAccessException e) {
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}),
            getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      }
 
      if (getUseServerPreparedStmts()) {
        this.serverSideStatementCheckCache = new LRUCache(cacheSize);
       
        this.serverSideStatementCache = new LRUCache(cacheSize) {
 
          private static final long serialVersionUID = 7692318650375988114L;
 
          protected boolean removeEldestEntry(java.util.Map.Entry<Object, Object> eldest) {
            if (this.maxElements <= 1) {
              return false;
            }
           
            boolean removeIt = super.removeEldestEntry(eldest);
           
            if (removeIt) {
              ServerPreparedStatement ps =
                (ServerPreparedStatement)eldest.getValue();
              ps.isCached = false;
              ps.setClosed(false);
             
              try {
                ps.close();
              } catch (SQLException sqlEx) {
                // punt
              }
            }
           
            return removeIt;
          }
        };
      }
    }
  }

  /**
   * SQL statements without parameters are normally executed using Statement
   * objects. If the same SQL statement is executed many times, it is more
   * efficient to use a PreparedStatement
   *
   * @return a new Statement object
   * @throws SQLException
   *             passed through from the constructor
   */
  public java.sql.Statement createStatement() throws SQLException {
    return createStatement(DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY);
  }

  /**
   * JDBC 2.0 Same as createStatement() above, but allows the default result
   * set type and result set concurrency type to be overridden.
   *
   * @param resultSetType
   *            a result set type, see ResultSet.TYPE_XXX
   * @param resultSetConcurrency
   *            a concurrency type, see ResultSet.CONCUR_XXX
   * @return a new Statement object
   * @exception SQLException
   *                if a database-access error occurs.
   */
  public java.sql.Statement createStatement(int resultSetType,
      int resultSetConcurrency) throws SQLException {
    checkClosed();

    StatementImpl stmt = new StatementImpl(getLoadBalanceSafeProxy(), this.database);
    stmt.setResultSetType(resultSetType);
    stmt.setResultSetConcurrency(resultSetConcurrency);

    return stmt;
  }

  /**
   * @see Connection#createStatement(int, int, int)
   */
  public java.sql.Statement createStatement(int resultSetType,
      int resultSetConcurrency, int resultSetHoldability)
      throws SQLException {
    if (getPedantic()) {
      if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
        throw SQLError.createSQLException(
            "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
    }

    return createStatement(resultSetType, resultSetConcurrency);
  }

  public void dumpTestcaseQuery(String query) {
    System.err.println(query);
  }

  public Connection duplicate() throws SQLException {
    return new ConnectionImplthis.origHostToConnectTo,
        this.origPortToConnectTo,
        this.props,
        this.origDatabaseToConnectTo,
        this.myURL);
  }

  /**
   * Send a query to the server. Returns one of the ResultSet objects. This is
   * synchronized, so Statement's queries will be serialized.
   *
   * @param callingStatement
   *            DOCUMENT ME!
   * @param sql
   *            the SQL statement to be executed
   * @param maxRows
   *            DOCUMENT ME!
   * @param packet
   *            DOCUMENT ME!
   * @param resultSetType
   *            DOCUMENT ME!
   * @param resultSetConcurrency
   *            DOCUMENT ME!
   * @param streamResults
   *            DOCUMENT ME!
   * @param queryIsSelectOnly
   *            DOCUMENT ME!
   * @param catalog
   *            DOCUMENT ME!
   * @param unpackFields
   *            DOCUMENT ME!
   * @return a ResultSet holding the results
   * @exception SQLException
   *                if a database error occurs
   */

  // ResultSet execSQL(Statement callingStatement, String sql,
  // int maxRowsToRetreive, String catalog) throws SQLException {
  // return execSQL(callingStatement, sql, maxRowsToRetreive, null,
  // java.sql.ResultSet.TYPE_FORWARD_ONLY,
  // DEFAULT_RESULT_SET_CONCURRENCY, catalog);
  // }
  // ResultSet execSQL(Statement callingStatement, String sql, int maxRows,
  // int resultSetType, int resultSetConcurrency, boolean streamResults,
  // boolean queryIsSelectOnly, String catalog, boolean unpackFields) throws
  // SQLException {
  // return execSQL(callingStatement, sql, maxRows, null, resultSetType,
  // resultSetConcurrency, streamResults, queryIsSelectOnly, catalog,
  // unpackFields);
  // }
  public ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows,
      Buffer packet, int resultSetType, int resultSetConcurrency,
      boolean streamResults, String catalog,
      Field[] cachedMetadata) throws SQLException {
    return execSQL(callingStatement, sql, maxRows, packet, resultSetType,
        resultSetConcurrency, streamResults,
        catalog, cachedMetadata, false);
  }

  public ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows,
      Buffer packet, int resultSetType, int resultSetConcurrency,
      boolean streamResults, String catalog,
      Field[] cachedMetadata,
      boolean isBatch) throws SQLException {
    synchronized (getConnectionMutex()) {
      //
      // Fall-back if the master is back online if we've
      // issued queriesBeforeRetryMaster queries since
      // we failed over
      //
 
      long queryStartTime = 0;
 
      int endOfQueryPacketPosition = 0;
 
      if (packet != null) {
        endOfQueryPacketPosition = packet.getPosition();
      }
 
      if (getGatherPerformanceMetrics()) {
        queryStartTime = System.currentTimeMillis();
      }
 
      this.lastQueryFinishedTime = 0; // we're busy!
 
      if ((getHighAvailability())
          && (this.autoCommit || getAutoReconnectForPools())
          && this.needsPing && !isBatch) {
        try {
          pingInternal(false, 0);
 
          this.needsPing = false;
        } catch (Exception Ex) {
          createNewIO(true);
        }
      }
 
      try {
        if (packet == null) {
          String encoding = null;
 
          if (getUseUnicode()) {
            encoding = getEncoding();
          }
 
          return this.io.sqlQueryDirect(callingStatement, sql,
              encoding, null, maxRows, resultSetType,
              resultSetConcurrency, streamResults, catalog,
              cachedMetadata);
        }
 
        return this.io.sqlQueryDirect(callingStatement, null, null,
            packet, maxRows, resultSetType,
            resultSetConcurrency, streamResults, catalog,
            cachedMetadata);
      } catch (java.sql.SQLException sqlE) {
        // don't clobber SQL exceptions
 
        if (getDumpQueriesOnException()) {
          String extractedSql = extractSqlFromPacket(sql, packet,
              endOfQueryPacketPosition);
          StringBuffer messageBuf = new StringBuffer(extractedSql
              .length() + 32);
          messageBuf
              .append("\n\nQuery being executed when exception was thrown:\n");
          messageBuf.append(extractedSql);
          messageBuf.append("\n\n");
 
          sqlE = appendMessageToException(sqlE, messageBuf.toString(), getExceptionInterceptor());
        }
 
        if ((getHighAvailability())) {
          this.needsPing = true;
        } else {
          String sqlState = sqlE.getSQLState();
 
          if ((sqlState != null)
              && sqlState
                  .equals(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)) {
            cleanup(sqlE);
          }
        }
 
        throw sqlE;
      } catch (Exception ex) {
        if (getHighAvailability()) {
          this.needsPing = true;
        } else if (ex instanceof IOException) {
          cleanup(ex);
        }
 
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.UnexpectedException"),
            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        sqlEx.initCause(ex);
       
        throw sqlEx;
      } finally {
        if (getMaintainTimeStats()) {
          this.lastQueryFinishedTime = System.currentTimeMillis();
        }
 
 
        if (getGatherPerformanceMetrics()) {
          long queryTime = System.currentTimeMillis()
              - queryStartTime;
 
          registerQueryExecutionTime(queryTime);
        }
      }
    }
  }

  public String extractSqlFromPacket(String possibleSqlQuery,
      Buffer queryPacket, int endOfQueryPacketPosition)
      throws SQLException {

    String extractedSql = null;

    if (possibleSqlQuery != null) {
      if (possibleSqlQuery.length() > getMaxQuerySizeToLog()) {
        StringBuffer truncatedQueryBuf = new StringBuffer(
            possibleSqlQuery.substring(0, getMaxQuerySizeToLog()));
        truncatedQueryBuf.append(Messages.getString("MysqlIO.25"));
        extractedSql = truncatedQueryBuf.toString();
      } else {
        extractedSql = possibleSqlQuery;
      }
    }

    if (extractedSql == null) {
      // This is probably from a client-side prepared
      // statement

      int extractPosition = endOfQueryPacketPosition;

      boolean truncated = false;

      if (endOfQueryPacketPosition > getMaxQuerySizeToLog()) {
        extractPosition = getMaxQuerySizeToLog();
        truncated = true;
      }

      extractedSql = StringUtils.toString(queryPacket.getByteBuffer(), 5,
          (extractPosition - 5));

      if (truncated) {
        extractedSql += Messages.getString("MysqlIO.25"); //$NON-NLS-1$
      }
    }

    return extractedSql;

  }

  public StringBuffer generateConnectionCommentBlock(StringBuffer buf) {
    buf.append("/* conn id ");
    buf.append(getId());
    buf.append(" clock: ");
    buf.append(System.currentTimeMillis());
    buf.append(" */ ");

    return buf;
  }

  public int getActiveStatementCount() {
    // Might not have one of these if
    // not tracking open resources
    if (this.openStatements != null) {
      synchronized (this.openStatements) {
        return this.openStatements.size();
      }
    }

    return 0;
  }

  /**
   * Gets the current auto-commit state
   *
   * @return Current state of auto-commit
   * @exception SQLException
   *                if an error occurs
   * @see setAutoCommit
   */
  public boolean getAutoCommit() throws SQLException {
    synchronized (getConnectionMutex()) {
      return this.autoCommit;
    }
  }

  /**
   * Optimization to only use one calendar per-session, or calculate it for
   * each call, depending on user configuration
   */
  public Calendar getCalendarInstanceForSessionOrNew() {
    if (getDynamicCalendars()) {
      return Calendar.getInstance();
    }

    return getSessionLockedCalendar();
  }

  /**
   * Return the connections current catalog name, or null if no catalog name
   * is set, or we dont support catalogs.
   * <p>
   * <b>Note:</b> MySQL's notion of catalogs are individual databases.
   * </p>
   *
   * @return the current catalog name or null
   * @exception SQLException
   *                if a database access error occurs
   */
  public String getCatalog() throws SQLException {
    synchronized (getConnectionMutex()) {
      return this.database;
    }
  }

  /**
   * @return Returns the characterSetMetadata.
   */
  public String getCharacterSetMetadata() {
    synchronized (getConnectionMutex()) {
      return this.characterSetMetadata;
    }
  }

  /**
   * Returns the locally mapped instance of a charset converter (to avoid
   * overhead of static synchronization).
   *
   * @param javaEncodingName
   *            the encoding name to retrieve
   * @return a character converter, or null if one couldn't be mapped.
   */
  public SingleByteCharsetConverter getCharsetConverter(
      String javaEncodingName) throws SQLException {
    if (javaEncodingName == null) {
      return null;
    }

    if (this.usePlatformCharsetConverters) {
      return null; // we'll use Java's built-in routines for this
                   // they're finally fast enough
    }
   
    SingleByteCharsetConverter converter = null;
   
    synchronized (this.charsetConverterMap) {
      Object asObject = this.charsetConverterMap
      .get(javaEncodingName);

      if (asObject == CHARSET_CONVERTER_NOT_AVAILABLE_MARKER) {
        return null;
      }
     
      converter = (SingleByteCharsetConverter)asObject;
     
      if (converter == null) {
        try {
          converter = SingleByteCharsetConverter.getInstance(
              javaEncodingName, this);

          if (converter == null) {
            this.charsetConverterMap.put(javaEncodingName,
                CHARSET_CONVERTER_NOT_AVAILABLE_MARKER);
          } else {
            this.charsetConverterMap.put(javaEncodingName, converter);
          }
        } catch (UnsupportedEncodingException unsupEncEx) {
          this.charsetConverterMap.put(javaEncodingName,
              CHARSET_CONVERTER_NOT_AVAILABLE_MARKER);

          converter = null;
        }
      }
    }

    return converter;
  }

  /**
   * Returns the Java character encoding name for the given MySQL server
   * charset index
   *
   * @param charsetIndex
   * @return the Java character encoding name for the given MySQL server
   *         charset index
   * @throws SQLException
   *             if the character set index isn't known by the driver
   */
  public String getCharsetNameForIndex(int charsetIndex)
      throws SQLException {
    String charsetName = null;

    if (getUseOldUTF8Behavior()) {
      return getEncoding();
    }

    if (charsetIndex != MysqlDefs.NO_CHARSET_INFO) {
      try {
        charsetName = this.indexToJavaCharset.get(charsetIndex);
        // checking against static maps if no custom charset found
        if (charsetName==null) charsetName = CharsetMapping.INDEX_TO_CHARSET[charsetIndex];

        if (this.characterEncodingIsAliasForSjis) {
          if ("sjis".equalsIgnoreCase(charsetName) ||
              "MS932".equalsIgnoreCase(charsetName) /* for JDK6 */) {
            // Use our encoding so that code pages like Cp932 work
            charsetName = getEncoding();
          }
        }
      } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) {
        throw SQLError.createSQLException(
            "Unknown character set index for field '"
                + charsetIndex + "' received from server.",
            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
      } catch (RuntimeException ex) {
        SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
        sqlEx.initCause(ex);
        throw sqlEx;
      }

      // Punt
      if (charsetName == null) {
        charsetName = getEncoding();
      }
    } else {
      charsetName = getEncoding();
    }

    return charsetName;
  }

  /**
   * DOCUMENT ME!
   *
   * @return Returns the defaultTimeZone.
   */
  public TimeZone getDefaultTimeZone() {
    return this.defaultTimeZone;
  }

  public String getErrorMessageEncoding() {
    return errorMessageEncoding;
  }

  /**
   * @see Connection#getHoldability()
   */
  public int getHoldability() throws SQLException {
    return java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT;
  }

  public long getId() {
    return this.connectionId;
  }

  /**
   * NOT JDBC-Compliant, but clients can use this method to determine how long
   * this connection has been idle. This time (reported in milliseconds) is
   * updated once a query has completed.
   *
   * @return number of ms that this connection has been idle, 0 if the driver
   *         is busy retrieving results.
   */
  public long getIdleFor() {
    synchronized (getConnectionMutex()) {
      if (this.lastQueryFinishedTime == 0) {
        return 0;
      }
 
      long now = System.currentTimeMillis();
      long idleTime = now - this.lastQueryFinishedTime;
 
      return idleTime;
    }
  }

  /**
   * Returns the IO channel to the server
   *
   * @return the IO channel to the server
   * @throws SQLException
   *             if the connection is closed.
   */
  public MysqlIO getIO() throws SQLException {
    if ((this.io == null) || this.isClosed) {
      throw SQLError.createSQLException(
          "Operation not allowed on closed connection",
          SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
    }

    return this.io;
  }

  /**
   * Returns the log mechanism that should be used to log information from/for
   * this Connection.
   *
   * @return the Log instance to use for logging messages.
   * @throws SQLException
   *             if an error occurs
   */
  public Log getLog() throws SQLException {
    return this.log;
  }

  public int getMaxBytesPerChar(String javaCharsetName) throws SQLException {
    return getMaxBytesPerChar(null, javaCharsetName);
  }
 
  public int getMaxBytesPerChar(Integer charsetIndex, String javaCharsetName) throws SQLException {
   
    String charset = null;
   
    try {
      // if we can get it by charsetIndex just doing it

      // getting charset name from dynamic maps in connection;
      // we do it before checking against static maps because custom charset on server
      // can be mapped to index from our static map key's diapason
      charset = this.indexToCustomMysqlCharset.get(charsetIndex);
      // checking against static maps if no custom charset found
      if (charset==null) charset = CharsetMapping.STATIC_INDEX_TO_MYSQL_CHARSET_MAP.get(charsetIndex);

      // if we didn't find charset name by index
      if (charset == null) {
        charset = CharsetMapping.getMysqlEncodingForJavaEncoding(javaCharsetName, this);
        if ((this.io.serverCharsetIndex == 33) && (versionMeetsMinimum(5, 5, 3)) && (javaCharsetName.equalsIgnoreCase("UTF-8"))) {
          //Avoid UTF8mb4
          charset = "utf8";
        }
      }
 
      // checking against dynamic maps in connection
      Integer mblen = this.mysqlCharsetToCustomMblen.get(charset);

      // checking against static maps
      if (mblen == null) mblen = CharsetMapping.STATIC_CHARSET_TO_NUM_BYTES_MAP.get(charset);
      if (mblen == null) mblen = CharsetMapping.STATIC_4_0_CHARSET_TO_NUM_BYTES_MAP.get(charset);
 
      if (mblen != null) return mblen.intValue();
    } catch (SQLException ex) {
      throw ex;
    } catch (RuntimeException ex) {
      SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
      sqlEx.initCause(ex);
      throw sqlEx;
    }

    return 1; // we don't know
  }

  /**
   * 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 DatabaseMetaData object for this connection
   * @exception SQLException
   *                if a database access error occurs
   */
  public java.sql.DatabaseMetaData getMetaData() throws SQLException {
    return getMetaData(true, true);
  }
 
  private java.sql.DatabaseMetaData getMetaData(boolean checkClosed, boolean checkForInfoSchema) throws SQLException {
    if (checkClosed) {
      checkClosed()
    }
   
    return com.mysql.jdbc.DatabaseMetaData.getInstance(getLoadBalanceSafeProxy(), this.database, checkForInfoSchema);
  }

  public java.sql.Statement getMetadataSafeStatement() throws SQLException {
    java.sql.Statement stmt = createStatement();

    if (stmt.getMaxRows() != 0) {
      stmt.setMaxRows(0);
    }

    stmt.setEscapeProcessing(false);
   
    if (stmt.getFetchSize() != 0) {
      stmt.setFetchSize(0);
    }

    return stmt;
  }

  /**
   * Returns the packet buffer size the MySQL server reported upon connection
   *
   * @return DOCUMENT ME!
   */
  public int getNetBufferLength() {
    return this.netBufferLength;
  }

  /**
   * Returns the server's character set
   *
   * @return the server's character set.
   */
  public String getServerCharacterEncoding() {
    if (this.io.versionMeetsMinimum(4, 1, 0)) {
      String charset = this.indexToCustomMysqlCharset.get(this.io.serverCharsetIndex);
      if (charset == null) charset = CharsetMapping.STATIC_INDEX_TO_MYSQL_CHARSET_MAP.get(this.io.serverCharsetIndex);
      return charset != null ? charset : this.serverVariables.get("character_set_server");
    }
    return this.serverVariables.get("character_set");
  }

  public int getServerMajorVersion() {
    return this.io.getServerMajorVersion();
  }

  public int getServerMinorVersion() {
    return this.io.getServerMinorVersion();
  }

  public int getServerSubMinorVersion() {
    return this.io.getServerSubMinorVersion();
  }

  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  public TimeZone getServerTimezoneTZ() {
    return this.serverTimezoneTZ;
  }
 
 
  public String getServerVariable(String variableName) {
    if (this.serverVariables != null) {
      return this.serverVariables.get(variableName);
    }

    return null;
  }

  public String getServerVersion() {
    return this.io.getServerVersion();
  }

  public Calendar getSessionLockedCalendar() {
 
    return this.sessionCalendar;
  }

  /**
   * Get this Connection's current transaction isolation mode.
   *
   * @return the current TRANSACTION_ mode value
   * @exception SQLException
   *                if a database access error occurs
   */
  public int getTransactionIsolation() throws SQLException {

    synchronized (getConnectionMutex()) {
      if (this.hasIsolationLevels && !getUseLocalSessionState()) {
        java.sql.Statement stmt = null;
        java.sql.ResultSet rs = null;
 
        try {
          stmt = getMetadataSafeStatement();
 
          String query = null;
 
          int offset = 0;
         
          if (versionMeetsMinimum(4, 0, 3)) {
            query = "SELECT @@session.tx_isolation";
            offset = 1;
          } else {
            query = "SHOW VARIABLES LIKE 'transaction_isolation'";
            offset = 2;
          }
 
          rs = stmt.executeQuery(query);
 
          if (rs.next()) {
            String s = rs.getString(offset);
 
            if (s != null) {
              Integer intTI = mapTransIsolationNameToValue.get(s);
 
              if (intTI != null) {
                return intTI.intValue();
              }
            }
 
            throw SQLError.createSQLException(
                "Could not map transaction isolation '" + s
                    + " to a valid JDBC level.",
                SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
          }
 
          throw SQLError.createSQLException(
              "Could not retrieve transaction isolation level from server",
              SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
 
        } finally {
          if (rs != null) {
            try {
              rs.close();
            } catch (Exception ex) {
              // ignore
              ;
            }
 
            rs = null;
          }
 
          if (stmt != null) {
            try {
              stmt.close();
            } catch (Exception ex) {
              // ignore
              ;
            }
 
            stmt = null;
          }
        }
      }
 
      return this.isolationLevel;
    }
  }

  /**
   * JDBC 2.0 Get the type-map object associated with this connection. By
   * default, the map returned is empty.
   *
   * @return the type map
   * @throws SQLException
   *             if a database error occurs
   */
  public java.util.Map<String,Class<?>> getTypeMap() throws SQLException {
    synchronized (getConnectionMutex()) {
      if (this.typeMap == null) {
        this.typeMap = new HashMap<String,Class<?>>();
      }
 
      return this.typeMap;
    }
  }

  public String getURL() {
    return this.myURL;
  }

  public String getUser() {
    return this.user;
  }

  public Calendar getUtcCalendar() {
    return this.utcCalendar;
  }
 
  /**
   * The first warning reported by calls on this Connection is returned.
   * <B>Note:</B> Sebsequent warnings will be changed to this
   * java.sql.SQLWarning
   *
   * @return the first java.sql.SQLWarning or null
   * @exception SQLException
   *                if a database access error occurs
   */
  public SQLWarning getWarnings() throws SQLException {
    return null;
  }

  public boolean hasSameProperties(Connection c) {
    return this.props.equals(c.getProperties());
  }

  public Properties getProperties() {
    return this.props;
  }
 
  public boolean hasTriedMaster() {
    return this.hasTriedMasterFlag;
  }

  public void incrementNumberOfPreparedExecutes() {
    if (getGatherPerformanceMetrics()) {
      this.numberOfPreparedExecutes++;

      // We need to increment this, because
      // server-side prepared statements bypass
      // any execution by the connection itself...
      this.numberOfQueriesIssued++;
    }
  }

  public void incrementNumberOfPrepares() {
    if (getGatherPerformanceMetrics()) {
      this.numberOfPrepares++;
    }
  }

  public void incrementNumberOfResultSetsCreated() {
    if (getGatherPerformanceMetrics()) {
      this.numberOfResultSetsCreated++;
    }
  }

  /**
   * Initializes driver properties that come from URL or properties passed to
   * the driver manager.
   *
   * @param info
   *            DOCUMENT ME!
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void initializeDriverProperties(Properties info)
      throws SQLException {
    initializeProperties(info);
   
    String exceptionInterceptorClasses = getExceptionInterceptors();
   
    if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) {
      this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses);
      this.exceptionInterceptor.init(this, info);
    }
   
    this.usePlatformCharsetConverters = getUseJvmCharsetConverters();

    this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor());

    if (getProfileSql() || getUseUsageAdvisor()) {
      this.eventSink = ProfilerEventHandlerFactory.getInstance(getLoadBalanceSafeProxy());
    }

    if (getCachePreparedStatements()) {
      createPreparedStatementCaches();   
    }

    if (getNoDatetimeStringSync() && getUseTimezone()) {
      throw SQLError.createSQLException(
          "Can't enable noDatetimeStringSync and useTimezone configuration "
              + "properties at the same time",
          SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
    }
   
    if (getCacheCallableStatements()) {
      this.parsedCallableStatementCache = new LRUCache(
          getCallableStatementCacheSize());
    }
   
    if (getAllowMultiQueries()) {
      setCacheResultSetMetadata(false); // we don't handle this yet
    }
   
    if (getCacheResultSetMetadata()) {
      this.resultSetMetadataCache = new LRUCache(
          getMetadataCacheSize());
    }
  }

  /**
   * Sets varying properties that depend on server information. Called once we
   * have connected to the server.
   *
   * @param info
   *            DOCUMENT ME!
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void initializePropsFromServer() throws SQLException {
    String connectionInterceptorClasses = getConnectionLifecycleInterceptors();
   
    this.connectionLifecycleInterceptors = null;
   
    if (connectionInterceptorClasses != null) {
      this.connectionLifecycleInterceptors = Util.loadExtensions(this, this.props,
          connectionInterceptorClasses,
          "Connection.badLifecycleInterceptor", getExceptionInterceptor());
    }
   
    setSessionVariables();

    //
    // the "boolean" type didn't come along until MySQL-4.1
    //

    if (!versionMeetsMinimum(4, 1, 0)) {
      setTransformedBitIsBoolean(false);
    }

    this.parserKnowsUnicode = versionMeetsMinimum(4, 1, 0);

    //
    // Users can turn off detection of server-side prepared statements
    //
    if (getUseServerPreparedStmts() && versionMeetsMinimum(4, 1, 0)) {
      this.useServerPreparedStmts = true;

      if (versionMeetsMinimum(5, 0, 0) && !versionMeetsMinimum(5, 0, 3)) {
        this.useServerPreparedStmts = false; // 4.1.2+ style prepared
        // statements
        // don't work on these versions
      }
    }
   
    //
    // If version is greater than 3.21.22 get the server
    // variables.
    if (versionMeetsMinimum(3, 21, 22)) {
      loadServerVariables();

      if (versionMeetsMinimum(5, 0, 2)) {
        this.autoIncrementIncrement = getServerVariableAsInt("auto_increment_increment", 1);
      } else {
        this.autoIncrementIncrement = 1;
      }
     
      buildCollationMapping();

      LicenseConfiguration.checkLicenseType(this.serverVariables);

      String lowerCaseTables = this.serverVariables.get("lower_case_table_names");

      this.lowerCaseTableNames = "on".equalsIgnoreCase(lowerCaseTables)
          || "1".equalsIgnoreCase(lowerCaseTables)
          || "2".equalsIgnoreCase(lowerCaseTables);

      this.storesLowerCaseTableName = "1".equalsIgnoreCase(lowerCaseTables) ||
          "on".equalsIgnoreCase(lowerCaseTables);

      configureTimezone();

      if (this.serverVariables.containsKey("max_allowed_packet")) {
        int serverMaxAllowedPacket = getServerVariableAsInt("max_allowed_packet", -1);
        // use server value if maxAllowedPacket hasn't been given, or max_allowed_packet is smaller
        if (serverMaxAllowedPacket != -1 && (serverMaxAllowedPacket < getMaxAllowedPacket() ||
            getMaxAllowedPacket() <= 0))
          setMaxAllowedPacket(serverMaxAllowedPacket);
        else if (serverMaxAllowedPacket == -1 && getMaxAllowedPacket() == -1)
          setMaxAllowedPacket(65535);
       
        int preferredBlobSendChunkSize = getBlobSendChunkSize();
       
        int allowedBlobSendChunkSize = Math.min(preferredBlobSendChunkSize,
            getMaxAllowedPacket()) -
            ServerPreparedStatement.BLOB_STREAM_READ_BUF_SIZE
            - 11 /* LONG_DATA and MySQLIO packet header size */;
       
        setBlobSendChunkSize(String.valueOf(allowedBlobSendChunkSize));
      }

      if (this.serverVariables.containsKey("net_buffer_length")) {
        this.netBufferLength = getServerVariableAsInt("net_buffer_length", 16 * 1024);
      }

      checkTransactionIsolationLevel();
     
      if (!versionMeetsMinimum(4, 1, 0)) {
        checkServerEncoding();
      }

      this.io.checkForCharsetMismatch();

      if (this.serverVariables.containsKey("sql_mode")) {
        int sqlMode = 0;

        String sqlModeAsString = this.serverVariables.get("sql_mode");
        try {
          sqlMode = Integer.parseInt(sqlModeAsString);
        } catch (NumberFormatException nfe) {
          // newer versions of the server has this as a string-y
          // list...
          sqlMode = 0;

          if (sqlModeAsString != null) {
            if (sqlModeAsString.indexOf("ANSI_QUOTES") != -1) {
              sqlMode |= 4;
            }

            if (sqlModeAsString.indexOf("NO_BACKSLASH_ESCAPES") != -1) {
              this.noBackslashEscapes = true;
            }
          }
        }

        if ((sqlMode & 4) > 0) {
          this.useAnsiQuotes = true;
        } else {
          this.useAnsiQuotes = false;
        }
      }
    }
   
    try {
      this.errorMessageEncoding =
        CharsetMapping.getCharacterEncodingForErrorMessages(this);
    } catch (SQLException ex) {
      throw ex;
    } catch (RuntimeException ex) {
      SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
      sqlEx.initCause(ex);
      throw sqlEx;
    }
   
   
    boolean overrideDefaultAutocommit = isAutoCommitNonDefaultOnServer();
 
    configureClientCharacterSet(false);

    if (versionMeetsMinimum(3, 23, 15)) {
      this.transactionsSupported = true;
     
      if (!overrideDefaultAutocommit) {
        try {
          setAutoCommit(true); // to override anything
          // the server is set to...reqd
          // by JDBC spec.
        } catch (SQLException ex) {
          if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
            throw ex;
          }
        }
      }
    } else {
      this.transactionsSupported = false;
    }
   

    if (versionMeetsMinimum(3, 23, 36)) {
      this.hasIsolationLevels = true;
    } else {
      this.hasIsolationLevels = false;
    }

    this.hasQuotedIdentifiers = versionMeetsMinimum(3, 23, 6);

    this.io.resetMaxBuf();

    //
    // If we're using MySQL 4.1.0 or newer, we need to figure
    // out what character set metadata will be returned in,
    // and then map that to a Java encoding name.
    //
    // We've already set it, and it might be different than what
    // was originally on the server, which is why we use the
    // "special" key to retrieve it
    if (this.io.versionMeetsMinimum(4, 1, 0)) {
      String characterSetResultsOnServerMysql = this.serverVariables.get(JDBC_LOCAL_CHARACTER_SET_RESULTS);

      if (characterSetResultsOnServerMysql == null
          || StringUtils.startsWithIgnoreCaseAndWs(
              characterSetResultsOnServerMysql, "NULL")
          || characterSetResultsOnServerMysql.length() == 0) {
        String defaultMetadataCharsetMysql = this.serverVariables.get("character_set_system");
        String defaultMetadataCharset = null;

        if (defaultMetadataCharsetMysql != null) {
          defaultMetadataCharset = getJavaEncodingForMysqlEncoding(defaultMetadataCharsetMysql);
        } else {
          defaultMetadataCharset = "UTF-8";
        }

        this.characterSetMetadata = defaultMetadataCharset;
      } else {
        this.characterSetResultsOnServer = getJavaEncodingForMysqlEncoding(characterSetResultsOnServerMysql);
        this.characterSetMetadata = this.characterSetResultsOnServer;
      }
    } else {
      this.characterSetMetadata = getEncoding();
    }

    //
    // Query cache is broken wrt. multi-statements before MySQL-4.1.10
    //

    if (versionMeetsMinimum(4, 1, 0)
        && !this.versionMeetsMinimum(4, 1, 10)
        && getAllowMultiQueries()) {
      if (isQueryCacheEnabled()) {
        setAllowMultiQueries(false);
      }
    }
   
    if (versionMeetsMinimum(5, 0, 0) &&
        (getUseLocalTransactionState() || getElideSetAutoCommits()) &&
        isQueryCacheEnabled() && !versionMeetsMinimum(6, 0, 10)) {
      // Can't trust the server status flag on the wire if query cache is enabled,
      // due to Bug#36326
      setUseLocalTransactionState(false);
      setElideSetAutoCommits(false);
    }
   
    //
    // Server can do this more efficiently for us
    //
   
    setupServerForTruncationChecks();
  }

  private boolean isQueryCacheEnabled() {
    return "ON".equalsIgnoreCase(this.serverVariables.get("query_cache_type"))
        && !"0".equalsIgnoreCase(this.serverVariables.get("query_cache_size"));
  }

  private int getServerVariableAsInt(String variableName, int fallbackValue)
      throws SQLException {
    try {
      return Integer.parseInt(this.serverVariables.get(variableName));
    } catch (NumberFormatException nfe) {
      getLog().logWarn(Messages.getString("Connection.BadValueInServerVariables", new Object[] {variableName,
          this.serverVariables.get(variableName), Integer.valueOf(fallbackValue)}));
     
      return fallbackValue;
    }
  }

  /**
   * Has the default autocommit value of 0 been changed on the server
   * via init_connect?
   *
   * @return true if autocommit is not the default of '0' on the server.
   *
   * @throws SQLException
   */
  private boolean isAutoCommitNonDefaultOnServer() throws SQLException {
    boolean overrideDefaultAutocommit = false;
   
    String initConnectValue = this.serverVariables.get("init_connect");

    if (versionMeetsMinimum(4, 1, 2) && initConnectValue != null
        && initConnectValue.length() > 0) {
      if (!getElideSetAutoCommits()) {
        // auto-commit might have changed
        java.sql.ResultSet rs = null;
        java.sql.Statement stmt = null;
       
        try {
          stmt = getMetadataSafeStatement();
         
          rs = stmt.executeQuery("SELECT @@session.autocommit");
         
          if (rs.next()) {
            this.autoCommit = rs.getBoolean(1);
            if (this.autoCommit != true) {
              overrideDefaultAutocommit = true;
            }
          }
         
        } finally {
          if (rs != null) {
            try {
              rs.close();
            } catch (SQLException sqlEx) {
              // do nothing
            }
          }
         
          if (stmt != null) {
            try {
              stmt.close();
            } catch (SQLException sqlEx) {
              // do nothing
            }
          }
        }
      } else {
        if (this.getIO().isSetNeededForAutoCommitMode(true)) {
          // we're not in standard autocommit=true mode
          this.autoCommit = false;
          overrideDefaultAutocommit = true;
        }
      }
    }
   
    return overrideDefaultAutocommit;
  }

  public boolean isClientTzUTC() {
    return this.isClientTzUTC;
  }

  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  public boolean isClosed() {
    return this.isClosed;
  }

  public boolean isCursorFetchEnabled() throws SQLException {
    return (versionMeetsMinimum(5, 0, 2) && getUseCursorFetch());
  }

  public boolean isInGlobalTx() {
    return this.isInGlobalTx;
  }

  /**
   * Is this connection connected to the first host in the list if
   * there is a list of servers in the URL?
   *
   * @return true if this connection is connected to the first in
   * the list.
   */
  public boolean isMasterConnection() {
    synchronized (getConnectionMutex()) {
      return false; // handled higher up
    }
  }

  /**
   * Is the server in a sql_mode that doesn't allow us to use \\ to escape
   * things?
   *
   * @return Returns the noBackslashEscapes.
   */
  public boolean isNoBackslashEscapesSet() {
    return this.noBackslashEscapes;
  }

  public boolean isReadInfoMsgEnabled() {
    return this.readInfoMsg;
  }

  /**
   * Tests to see if the connection is in Read Only Mode. Note that prior to 5.6,
   * we cannot really put the database in read only mode, but we pretend we can by
   * returning the value of the readOnly flag
   *
   * @return true if the connection is read only
   * @exception SQLException if a database access error occurs
   */
  public boolean isReadOnly() throws SQLException {
    return isReadOnly(true);
  }

  /**
   * Tests to see if the connection is in Read Only Mode. Note that prior to 5.6,
   * we cannot really put the database in read only mode, but we pretend we can by
   * returning the value of the readOnly flag
   *
   * @param useSessionStatus in some cases, for example when restoring connection with autoReconnect=true,
   * we can rely only on saved readOnly state, so use useSessionStatus=false in that case
   *
   * @return true if the connection is read only
   * @exception SQLException if a database access error occurs
   */
  public boolean isReadOnly(boolean useSessionStatus) throws SQLException {
    if (useSessionStatus && !this.isClosed && versionMeetsMinimum(5, 6, 5) && !getUseLocalSessionState()) {
      java.sql.Statement stmt = null;
      java.sql.ResultSet rs = null;

      try {
        try {
          stmt = getMetadataSafeStatement();

          rs = stmt.executeQuery("select @@session.tx_read_only");
          if (rs.next()) {
            return rs.getInt(1) != 0; // mysql has a habit of tri+ state booleans
          }
        } catch (SQLException ex1) {
          if (ex1.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
            throw SQLError.createSQLException(
                "Could not retrieve transation read-only status server",
                SQLError.SQL_STATE_GENERAL_ERROR, ex1, getExceptionInterceptor());
          }
        }

      } finally {
        if (rs != null) {
          try {
            rs.close();
          } catch (Exception ex) {
            // ignore
          }

          rs = null;
        }

        if (stmt != null) {
          try {
            stmt.close();
          } catch (Exception ex) {
            // ignore
          }

          stmt = null;
        }
      }
    }

    return this.readOnly;
  }

  public boolean isRunningOnJDK13() {
    return this.isRunningOnJDK13;
  }

  public boolean isSameResource(Connection otherConnection) {
    synchronized (getConnectionMutex()) {
      if (otherConnection == null) {
        return false;
      }
     
      boolean directCompare = true;
     
      String otherHost = ((ConnectionImpl)otherConnection).origHostToConnectTo;
      String otherOrigDatabase = ((ConnectionImpl)otherConnection).origDatabaseToConnectTo;
      String otherCurrentCatalog = ((ConnectionImpl)otherConnection).database;
     
      if (!nullSafeCompare(otherHost, this.origHostToConnectTo)) {
        directCompare = false;
      } else if (otherHost != null && otherHost.indexOf(',') == -1 &&
          otherHost.indexOf(':') == -1) {
        // need to check port numbers
        directCompare = (((ConnectionImpl)otherConnection).origPortToConnectTo ==
          this.origPortToConnectTo);
      }
     
      if (directCompare) {
        if (!nullSafeCompare(otherOrigDatabase, this.origDatabaseToConnectTo)) {      directCompare = false;
          directCompare = false;
        } else if (!nullSafeCompare(otherCurrentCatalog, this.database)) {
          directCompare = false;
        }
      }
 
      if (directCompare) {
        return true;
      }
     
      // Has the user explicitly set a resourceId?
      String otherResourceId = ((ConnectionImpl)otherConnection).getResourceId();
      String myResourceId = getResourceId();
     
      if (otherResourceId != null || myResourceId != null) {
        directCompare = nullSafeCompare(otherResourceId, myResourceId);
       
        if (directCompare) {
          return true;
        }
      }
     
      return false;
    }
  }

  public boolean isServerTzUTC() {
    return this.isServerTzUTC;
  }

 
  private boolean usingCachedConfig = false;

  private void createConfigCacheIfNeeded() throws SQLException {
    synchronized (getConnectionMutex()) {
      if (this.serverConfigCache != null) {
        return;
      }
     
      try {
        Class<?> factoryClass;
       
        factoryClass = Class.forName(getServerConfigCacheFactory());
       
        @SuppressWarnings("unchecked")
        CacheAdapterFactory<String, Map<String, String>> cacheFactory = ((CacheAdapterFactory<String, Map<String, String>>)factoryClass.newInstance());
       
        this.serverConfigCache = cacheFactory.getInstance(this, myURL, Integer.MAX_VALUE, Integer.MAX_VALUE, props);
       
        ExceptionInterceptor evictOnCommsError = new ExceptionInterceptor() {
 
          public void init(Connection conn, Properties config)
              throws SQLException {
          }
 
          public void destroy() {
          }
 
          @SuppressWarnings("synthetic-access")
          public SQLException interceptException(SQLException sqlEx,
              Connection conn) {
            if (sqlEx.getSQLState() != null && sqlEx.getSQLState().startsWith("08")) {
              serverConfigCache.invalidate(getURL());
            }
            return null;
          }};
       
        if (this.exceptionInterceptor == null) {
          this.exceptionInterceptor = evictOnCommsError;
        } else {
          ((ExceptionInterceptorChain)this.exceptionInterceptor).addRingZero(evictOnCommsError);
        }
      } catch (ClassNotFoundException e) {
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.CantFindCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}),
            getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      } catch (InstantiationException e) {
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}),
            getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      } catch (IllegalAccessException e) {
        SQLException sqlEx = SQLError.createSQLException(
            Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}),
            getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      }
    }
  }
 
  private final static String SERVER_VERSION_STRING_VAR_NAME = "server_version_string";
 
  /**
   * Loads the result of 'SHOW VARIABLES' into the serverVariables field so
   * that the driver can configure itself.
   *
   * @throws SQLException
   *             if the 'SHOW VARIABLES' query fails for any reason.
   */
  private void loadServerVariables() throws SQLException {

    if (getCacheServerConfiguration()) {
      createConfigCacheIfNeeded();
     
      Map<String, String> cachedVariableMap = serverConfigCache.get(getURL());

      if (cachedVariableMap != null) {
        String cachedServerVersion = cachedVariableMap.get(SERVER_VERSION_STRING_VAR_NAME);
       
        if (cachedServerVersion != null && this.io.getServerVersion() != null
            && cachedServerVersion.equals(this.io.getServerVersion())) {
          this.serverVariables = cachedVariableMap;
          this.usingCachedConfig = true;
 
          return;
        }
       
        serverConfigCache.invalidate(getURL());
      }
    }

    java.sql.Statement stmt = null;
    java.sql.ResultSet results = null;

    try {
      stmt = getMetadataSafeStatement();
     
      String version = this.dbmd.getDriverVersion();
     
      if (version != null && version.indexOf('*') != -1) {
        StringBuffer buf = new StringBuffer(version.length() + 10);

        for (int i = 0; i < version.length(); i++) {
          char c = version.charAt(i);

          if (c == '*') {
            buf.append("[star]");
          } else {
            buf.append(c);
          }
        }

        version = buf.toString();
      }

      String versionComment = (this.getParanoid() || version == null) ? ""
          : "/* " + version + " */";
     
      String query = versionComment + "SHOW VARIABLES";
     
      if (versionMeetsMinimum(5, 0, 3)) {
        query = versionComment + "SHOW VARIABLES WHERE Variable_name ='language'"
          + " OR Variable_name = 'net_write_timeout'"
          + " OR Variable_name = 'interactive_timeout'"
          + " OR Variable_name = 'wait_timeout'"
          + " OR Variable_name = 'character_set_client'"
          + " OR Variable_name = 'character_set_connection'"
          + " OR Variable_name = 'character_set'"
          + " OR Variable_name = 'character_set_server'"
          + " OR Variable_name = 'tx_isolation'"
          + " OR Variable_name = 'transaction_isolation'"
          + " OR Variable_name = 'character_set_results'"
          + " OR Variable_name = 'timezone'"
          + " OR Variable_name = 'time_zone'"
          + " OR Variable_name = 'system_time_zone'"
          + " OR Variable_name = 'lower_case_table_names'"
          + " OR Variable_name = 'max_allowed_packet'"
          + " OR Variable_name = 'net_buffer_length'"
          + " OR Variable_name = 'sql_mode'"
          + " OR Variable_name = 'query_cache_type'"
          + " OR Variable_name = 'query_cache_size'"
          + " OR Variable_name = 'init_connect'";
      }
     
      this.serverVariables = new HashMap<String, String>();

      try {
        results = stmt.executeQuery(query);
       
        while (results.next()) {
          this.serverVariables.put(results.getString(1), results
              .getString(2));
        }
 
        results.close();
        results = null;
      } catch (SQLException ex) {
        if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
          throw ex;
        }
      }
     
      if (versionMeetsMinimum(5, 0, 2)) {
        try {
          results = stmt.executeQuery(versionComment + "SELECT @@session.auto_increment_increment");
         
          if (results.next()) {
            this.serverVariables.put("auto_increment_increment", results.getString(1));
          }
        } catch (SQLException ex) {
          if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) {
            throw ex;
          }
        }
      }
     
      if (getCacheServerConfiguration()) {
        this.serverVariables.put(SERVER_VERSION_STRING_VAR_NAME, this.io.getServerVersion());
       
        serverConfigCache.put(getURL(), this.serverVariables);
       
        this.usingCachedConfig = true;
      }
    } catch (SQLException e) {
      throw e;
    } finally {
      if (results != null) {
        try {
          results.close();
        } catch (SQLException sqlE) {
          ;
        }
      }

      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException sqlE) {
          ;
        }
      }
    }
  }

  private int autoIncrementIncrement = 0;
 
  public int getAutoIncrementIncrement() {
    return this.autoIncrementIncrement;
  }
 
  /**
   * Is the server configured to use lower-case table names only?
   *
   * @return true if lower_case_table_names is 'on'
   */
  public boolean lowerCaseTableNames() {
    return this.lowerCaseTableNames;
  }

  /**
   * Has the maxRows value changed?
   *
   * @param stmt
   *            DOCUMENT ME!
   */
  public void maxRowsChanged(Statement stmt) {
    synchronized (getConnectionMutex()) {
      if (this.statementsUsingMaxRows == null) {
        this.statementsUsingMaxRows = new HashMap<Statement, Statement>();
      }
 
      this.statementsUsingMaxRows.put(stmt, stmt);
 
      this.maxRowsChanged = true;
    }
  }

  /**
   * A driver may convert the JDBC sql grammar into its system's native SQL
   * grammar prior to sending it; nativeSQL returns the native form of the
   * statement that the driver would have sent.
   *
   * @param sql
   *            a SQL statement that may contain one or more '?' parameter
   *            placeholders
   * @return the native form of this statement
   * @exception SQLException
   *                if a database access error occurs
   */
  public String nativeSQL(String sql) throws SQLException {
    if (sql == null) {
      return null;
    }

    Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
                                                          serverSupportsConvertFn(),
                                                          getLoadBalanceSafeProxy());

    if (escapedSqlResult instanceof String) {
      return (String) escapedSqlResult;
    }

    return ((EscapeProcessorResult) escapedSqlResult).escapedSql;
  }

  private CallableStatement parseCallableStatement(String sql)
      throws SQLException {
    Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
        serverSupportsConvertFn(), getLoadBalanceSafeProxy());

    boolean isFunctionCall = false;
    String parsedSql = null;

    if (escapedSqlResult instanceof EscapeProcessorResult) {
      parsedSql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
      isFunctionCall = ((EscapeProcessorResult) escapedSqlResult).callingStoredFunction;
    } else {
      parsedSql = (String) escapedSqlResult;
      isFunctionCall = false;
    }

    return CallableStatement.getInstance(getLoadBalanceSafeProxy(), parsedSql, this.database,
        isFunctionCall);
  }

  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  public boolean parserKnowsUnicode() {
    return this.parserKnowsUnicode;
  }

  /**
   * Detect if the connection is still good
   *
   * @throws SQLException
   *             if the ping fails
   */
  public void ping() throws SQLException {
    pingInternal(true, 0);
  }

  public void pingInternal(boolean checkForClosedConnection, int timeoutMillis)
      throws SQLException {
    if (checkForClosedConnection) {
      checkClosed();
    }

    long pingMillisLifetime = getSelfDestructOnPingSecondsLifetime();
    int pingMaxOperations = getSelfDestructOnPingMaxOperations();

    if ((pingMillisLifetime > 0 && (System.currentTimeMillis() - this.connectionCreationTimeMillis) > pingMillisLifetime)
        || (pingMaxOperations > 0 && pingMaxOperations <= this.io
            .getCommandCount())) {

      close();

      throw SQLError.createSQLException(Messages
          .getString("Connection.exceededConnectionLifetime"),
          SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor());
    }
    // Need MySQL-3.22.1, but who uses anything older!?
    this.io.sendCommand(MysqlDefs.PING, null, null, false, null, timeoutMillis);
  }

  /**
   * DOCUMENT ME!
   *
   * @param sql
   *            DOCUMENT ME!
   * @return DOCUMENT ME!
   * @throws SQLException
   *             DOCUMENT ME!
   */
  public java.sql.CallableStatement prepareCall(String sql)
      throws SQLException {

    return prepareCall(sql, DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY);
  }

  /**
   * JDBC 2.0 Same as prepareCall() above, but allows the default result set
   * type and result set concurrency type to be overridden.
   *
   * @param sql
   *            the SQL representing the callable statement
   * @param resultSetType
   *            a result set type, see ResultSet.TYPE_XXX
   * @param resultSetConcurrency
   *            a concurrency type, see ResultSet.CONCUR_XXX
   * @return a new CallableStatement object containing the pre-compiled SQL
   *         statement
   * @exception SQLException
   *                if a database-access error occurs.
   */
  public java.sql.CallableStatement prepareCall(String sql,
      int resultSetType, int resultSetConcurrency) throws SQLException {
    if (versionMeetsMinimum(5, 0, 0)) {
      CallableStatement cStmt = null;

      if (!getCacheCallableStatements()) {

        cStmt = parseCallableStatement(sql);
      } else {
        synchronized (this.parsedCallableStatementCache) {
          CompoundCacheKey key = new CompoundCacheKey(getCatalog(), sql);
 
          CallableStatement.CallableStatementParamInfo cachedParamInfo = (CallableStatement.CallableStatementParamInfo) this.parsedCallableStatementCache
              .get(key);
 
          if (cachedParamInfo != null) {
            cStmt = CallableStatement.getInstance(getLoadBalanceSafeProxy(), cachedParamInfo);
          } else {
            cStmt = parseCallableStatement(sql);
 
            synchronized (cStmt) {
              cachedParamInfo = cStmt.paramInfo;
            }
 
            this.parsedCallableStatementCache.put(key, cachedParamInfo);
          }
        }
      }

      cStmt.setResultSetType(resultSetType);
      cStmt.setResultSetConcurrency(resultSetConcurrency);

      return cStmt;
    }

    throw SQLError.createSQLException("Callable statements not " + "supported.",
        SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
  }

  /**
   * @see Connection#prepareCall(String, int, int, int)
   */
  public java.sql.CallableStatement prepareCall(String sql,
      int resultSetType, int resultSetConcurrency,
      int resultSetHoldability) throws SQLException {
    if (getPedantic()) {
      if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
        throw SQLError.createSQLException(
            "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
    }

    CallableStatement cStmt = (com.mysql.jdbc.CallableStatement) prepareCall(
        sql, resultSetType, resultSetConcurrency);

    return cStmt;
  }

  /**
   * A SQL statement with or without IN parameters can be pre-compiled and
   * stored in a PreparedStatement 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. In this case, the statement is not sent to the database
   * until the PreparedStatement is executed. This has no direct effect on
   * users; however it does affect which method throws certain
   * java.sql.SQLExceptions
   * </p>
   * <p>
   * MySQL does not support precompilation of statements, so they are handled
   * by the driver.
   * </p>
   *
   * @param sql
   *            a SQL statement that may contain one or more '?' IN parameter
   *            placeholders
   * @return a new PreparedStatement object containing the pre-compiled
   *         statement.
   * @exception SQLException
   *                if a database access error occurs.
   */
  public java.sql.PreparedStatement prepareStatement(String sql)
      throws SQLException {
    return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY);
  }

  /**
   * @see Connection#prepareStatement(String, int)
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int autoGenKeyIndex) throws SQLException {
    java.sql.PreparedStatement pStmt = prepareStatement(sql);

    ((com.mysql.jdbc.PreparedStatement) pStmt)
        .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);

    return pStmt;
  }

  /**
   * JDBC 2.0 Same as prepareStatement() above, but allows the default result
   * set type and result set concurrency type to be overridden.
   *
   * @param sql
   *            the SQL query containing place holders
   * @param resultSetType
   *            a result set type, see ResultSet.TYPE_XXX
   * @param resultSetConcurrency
   *            a concurrency type, see ResultSet.CONCUR_XXX
   * @return a new PreparedStatement object containing the pre-compiled SQL
   *         statement
   * @exception SQLException
   *                if a database-access error occurs.
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int resultSetType, int resultSetConcurrency) throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
 
      //
      // FIXME: Create warnings if can't create results of the given
      // type or concurrency
      //
      PreparedStatement pStmt = null;
     
      boolean canServerPrepare = true;
     
      String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
     
      if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
        canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
      }
     
      if (this.useServerPreparedStmts && canServerPrepare) {
        if (this.getCachePreparedStatements()) {
          synchronized (this.serverSideStatementCache) {
            pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);
           
            if (pStmt != null) {
              ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);
              pStmt.clearParameters();
            }
 
            if (pStmt == null) {
              try {
                pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
                    this.database, resultSetType, resultSetConcurrency);
                if (sql.length() < getPreparedStatementCacheSqlLimit()) {
                  ((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true;
                }
               
                pStmt.setResultSetType(resultSetType);
                pStmt.setResultSetConcurrency(resultSetConcurrency);
              } catch (SQLException sqlEx) {
                // Punt, if necessary
                if (getEmulateUnsupportedPstmts()) {
                  pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                 
                  if (sql.length() < getPreparedStatementCacheSqlLimit()) {
                    this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
                  }
                } else {
                  throw sqlEx;
                }
              }
            }
          }
        } else {
          try {
            pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,
                this.database, resultSetType, resultSetConcurrency);
           
            pStmt.setResultSetType(resultSetType);
            pStmt.setResultSetConcurrency(resultSetConcurrency);
          } catch (SQLException sqlEx) {
            // Punt, if necessary
            if (getEmulateUnsupportedPstmts()) {
              pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
            } else {
              throw sqlEx;
            }
          }
        }
      } else {
        pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
      }
     
      return pStmt;
    }
  }

  /**
   * @see Connection#prepareStatement(String, int, int, int)
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int resultSetType, int resultSetConcurrency,
      int resultSetHoldability) throws SQLException {
    if (getPedantic()) {
      if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
        throw SQLError.createSQLException(
            "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
    }

    return prepareStatement(sql, resultSetType, resultSetConcurrency);
  }

  /**
   * @see Connection#prepareStatement(String, int[])
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      int[] autoGenKeyIndexes) throws SQLException {
    java.sql.PreparedStatement pStmt = prepareStatement(sql);

    ((com.mysql.jdbc.PreparedStatement) pStmt)
        .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
            && (autoGenKeyIndexes.length > 0));

    return pStmt;
  }

  /**
   * @see Connection#prepareStatement(String, String[])
   */
  public java.sql.PreparedStatement prepareStatement(String sql,
      String[] autoGenKeyColNames) throws SQLException {
    java.sql.PreparedStatement pStmt = prepareStatement(sql);

    ((com.mysql.jdbc.PreparedStatement) pStmt)
        .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
            && (autoGenKeyColNames.length > 0));

    return pStmt;
  }

  /**
   * Closes connection and frees resources.
   *
   * @param calledExplicitly
   *            is this being called from close()
   * @param issueRollback
   *            should a rollback() be issued?
   * @throws SQLException
   *             if an error occurs
   */
  public void realClose(boolean calledExplicitly, boolean issueRollback,
      boolean skipLocalTeardown, Throwable reason) throws SQLException {
    SQLException sqlEx = null;

    if (this.isClosed()) {
      return;
    }
   
    this.forceClosedReason = reason;
   
    try {
      if (!skipLocalTeardown) {
        if (!getAutoCommit() && issueRollback) {
          try {
            rollback();
          } catch (SQLException ex) {
            sqlEx = ex;
          }
        }

        reportMetrics();

        if (getUseUsageAdvisor()) {
          if (!calledExplicitly) {
            String message = "Connection implicitly closed by Driver. You should call Connection.close() from your code to free resources more efficiently and avoid resource leaks.";

            this.eventSink.consumeEvent(new ProfilerEvent(
                ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$
                this.getCatalog(), this.getId(), -1, -1, System
                    .currentTimeMillis(), 0, Constants.MILLIS_I18N,
                    null,
                this.pointOfOrigin, message));
          }

          long connectionLifeTime = System.currentTimeMillis()
              - this.connectionCreationTimeMillis;

          if (connectionLifeTime < 500) {
            String message = "Connection lifetime of < .5 seconds. You might be un-necessarily creating short-lived connections and should investigate connection pooling to be more efficient.";

            this.eventSink.consumeEvent(new ProfilerEvent(
                ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$
                this.getCatalog(), this.getId(), -1, -1, System
                    .currentTimeMillis(), 0, Constants.MILLIS_I18N,
                    null,
                this.pointOfOrigin, message));
          }
        }

        try {
          closeAllOpenStatements();
        } catch (SQLException ex) {
          sqlEx = ex;
        }

        if (this.io != null) {
          try {
            this.io.quit();
          } catch (Exception e) {
            ;
          }

        }
      } else {
        this.io.forceClose();
      }
     
        if (this.statementInterceptors != null) {
          for (int i = 0; i < this.statementInterceptors.size(); i++) {
            this.statementInterceptors.get(i).destroy();
          }
        }
       
        if (this.exceptionInterceptor != null) {
          this.exceptionInterceptor.destroy();
        }
    } finally {
      this.openStatements = null;
      if (this.io != null) {
        this.io.releaseResources();
        this.io = null;
      }
      this.statementInterceptors = null;
      this.exceptionInterceptor = null;
      ProfilerEventHandlerFactory.removeInstance(this);
     
      synchronized (getConnectionMutex()) {
        if (this.cancelTimer != null) {
          this.cancelTimer.cancel();
        }
      }
     
      this.isClosed = true;
    }

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

  }

  public void recachePreparedStatement(ServerPreparedStatement pstmt) throws SQLException {
    synchronized (getConnectionMutex()) {
      if (pstmt.isPoolable()) {
        synchronized (this.serverSideStatementCache) {
          this.serverSideStatementCache.put(pstmt.originalSql, pstmt);
        }
      }
    }
  }

  /**
   * DOCUMENT ME!
   *
   * @param queryTimeMs
   */
  public void registerQueryExecutionTime(long queryTimeMs) {
    if (queryTimeMs > this.longestQueryTimeMs) {
      this.longestQueryTimeMs = queryTimeMs;

      repartitionPerformanceHistogram();
    }

    addToPerformanceHistogram(queryTimeMs, 1);

    if (queryTimeMs < this.shortestQueryTimeMs) {
      this.shortestQueryTimeMs = (queryTimeMs == 0) ? 1 : queryTimeMs;
    }

    this.numberOfQueriesIssued++;

    this.totalQueryTimeMs += queryTimeMs;
  }

  /**
   * Register a Statement instance as open.
   *
   * @param stmt
   *            the Statement instance to remove
   */
  public void registerStatement(Statement stmt) {
    synchronized (this.openStatements) {
      this.openStatements.put(stmt, stmt);
    }
  }

  /**
   * @see Connection#releaseSavepoint(Savepoint)
   */
  public void releaseSavepoint(Savepoint arg0) throws SQLException {
    // this is a no-op
  }

  private void repartitionHistogram(int[] histCounts, long[] histBreakpoints,
      long currentLowerBound, long currentUpperBound) {

    if (oldHistCounts == null) {
      oldHistCounts = new int[histCounts.length];
      oldHistBreakpoints = new long[histBreakpoints.length];
    }

    System.arraycopy(histCounts, 0, oldHistCounts, 0, histCounts.length);
   
    System.arraycopy(histBreakpoints, 0, oldHistBreakpoints, 0,
        histBreakpoints.length);
 
    createInitialHistogram(histBreakpoints, currentLowerBound,
        currentUpperBound);

    for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
      addToHistogram(histCounts, histBreakpoints, oldHistBreakpoints[i],
          oldHistCounts[i], currentLowerBound, currentUpperBound);
    }
  }

  private void repartitionPerformanceHistogram() {
    checkAndCreatePerformanceHistogram();

    repartitionHistogram(this.perfMetricsHistCounts,
        this.perfMetricsHistBreakpoints,
        this.shortestQueryTimeMs == Long.MAX_VALUE ? 0
            : this.shortestQueryTimeMs, this.longestQueryTimeMs);
  }

  private void repartitionTablesAccessedHistogram() {
    checkAndCreateTablesAccessedHistogram();

    repartitionHistogram(this.numTablesMetricsHistCounts,
        this.numTablesMetricsHistBreakpoints,
        this.minimumNumberTablesAccessed == Long.MAX_VALUE ? 0
            : this.minimumNumberTablesAccessed,
        this.maximumNumberTablesAccessed);
  }

  private void reportMetrics() {
    if (getGatherPerformanceMetrics()) {
      StringBuffer logMessage = new StringBuffer(256);

      logMessage.append("** Performance Metrics Report **\n");
      logMessage.append("\nLongest reported query: "
          + this.longestQueryTimeMs + " ms");
      logMessage.append("\nShortest reported query: "
          + this.shortestQueryTimeMs + " ms");
      logMessage
          .append("\nAverage query execution time: "
              + (this.totalQueryTimeMs / this.numberOfQueriesIssued)
              + " ms");
      logMessage.append("\nNumber of statements executed: "
          + this.numberOfQueriesIssued);
      logMessage.append("\nNumber of result sets created: "
          + this.numberOfResultSetsCreated);
      logMessage.append("\nNumber of statements prepared: "
          + this.numberOfPrepares);
      logMessage.append("\nNumber of prepared statement executions: "
          + this.numberOfPreparedExecutes);

      if (this.perfMetricsHistBreakpoints != null) {
        logMessage.append("\n\n\tTiming Histogram:\n");
        int maxNumPoints = 20;
        int highestCount = Integer.MIN_VALUE;

        for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) {
          if (this.perfMetricsHistCounts[i] > highestCount) {
            highestCount = this.perfMetricsHistCounts[i];
          }
        }

        if (highestCount == 0) {
          highestCount = 1; // avoid DIV/0
        }

        for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) {

          if (i == 0) {
            logMessage.append("\n\tless than "
                + this.perfMetricsHistBreakpoints[i + 1]
                + " ms: \t" + this.perfMetricsHistCounts[i]);
          } else {
            logMessage.append("\n\tbetween "
                + this.perfMetricsHistBreakpoints[i] + " and "
                + this.perfMetricsHistBreakpoints[i + 1]
                + " ms: \t" + this.perfMetricsHistCounts[i]);
          }

          logMessage.append("\t");

          int numPointsToGraph = (int) (maxNumPoints * ((double) this.perfMetricsHistCounts[i] / (double) highestCount));

          for (int j = 0; j < numPointsToGraph; j++) {
            logMessage.append("*");
          }

          if (this.longestQueryTimeMs < this.perfMetricsHistCounts[i + 1]) {
            break;
          }
        }

        if (this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this.longestQueryTimeMs) {
          logMessage.append("\n\tbetween ");
          logMessage
              .append(this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]);
          logMessage.append(" and ");
          logMessage
              .append(this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]);
          logMessage.append(" ms: \t");
          logMessage
              .append(this.perfMetricsHistCounts[HISTOGRAM_BUCKETS - 1]);
        }
      }

      if (this.numTablesMetricsHistBreakpoints != null) {
        logMessage.append("\n\n\tTable Join Histogram:\n");
        int maxNumPoints = 20;
        int highestCount = Integer.MIN_VALUE;

        for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) {
          if (this.numTablesMetricsHistCounts[i] > highestCount) {
            highestCount = this.numTablesMetricsHistCounts[i];
          }
        }

        if (highestCount == 0) {
          highestCount = 1; // avoid DIV/0
        }

        for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) {

          if (i == 0) {
            logMessage.append("\n\t"
                + this.numTablesMetricsHistBreakpoints[i + 1]
                + " tables or less: \t\t"
                + this.numTablesMetricsHistCounts[i]);
          } else {
            logMessage.append("\n\tbetween "
                + this.numTablesMetricsHistBreakpoints[i]
                + " and "
                + this.numTablesMetricsHistBreakpoints[i + 1]
                + " tables: \t"
                + this.numTablesMetricsHistCounts[i]);
          }

          logMessage.append("\t");

          int numPointsToGraph = (int) (maxNumPoints * ((double) this.numTablesMetricsHistCounts[i] / (double) highestCount));

          for (int j = 0; j < numPointsToGraph; j++) {
            logMessage.append("*");
          }

          if (this.maximumNumberTablesAccessed < this.numTablesMetricsHistBreakpoints[i + 1]) {
            break;
          }
        }

        if (this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this.maximumNumberTablesAccessed) {
          logMessage.append("\n\tbetween ");
          logMessage
              .append(this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]);
          logMessage.append(" and ");
          logMessage
              .append(this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]);
          logMessage.append(" tables: ");
          logMessage
              .append(this.numTablesMetricsHistCounts[HISTOGRAM_BUCKETS - 1]);
        }
      }

      this.log.logInfo(logMessage);

      this.metricsLastReportedMs = System.currentTimeMillis();
    }
  }

  /**
   * Reports currently collected metrics if this feature is enabled and the
   * timeout has passed.
   */
  protected void reportMetricsIfNeeded() {
    if (getGatherPerformanceMetrics()) {
      if ((System.currentTimeMillis() - this.metricsLastReportedMs) > getReportMetricsIntervalMillis()) {
        reportMetrics();
      }
    }
  }

  public void reportNumberOfTablesAccessed(int numTablesAccessed) {
    if (numTablesAccessed < this.minimumNumberTablesAccessed) {
      this.minimumNumberTablesAccessed = numTablesAccessed;
    }

    if (numTablesAccessed > this.maximumNumberTablesAccessed) {
      this.maximumNumberTablesAccessed = numTablesAccessed;

      repartitionTablesAccessedHistogram();
    }

    addToTablesAccessedHistogram(numTablesAccessed, 1);
  }

  /**
   * Resets the server-side state of this connection. Doesn't work for MySQL
   * versions older than 4.0.6 or if isParanoid() is set (it will become a
   * no-op in these cases). Usually only used from connection pooling code.
   *
   * @throws SQLException
   *             if the operation fails while resetting server state.
   */
  public void resetServerState() throws SQLException {
    if (!getParanoid()
        && ((this.io != null) && versionMeetsMinimum(4, 0, 6))) {
      changeUser(this.user, this.password);
    }
  }

  /**
   * The method rollback() drops all changes made since the previous
   * commit/rollback and releases any database locks currently held by the
   * Connection.
   *
   * @exception SQLException
   *                if a database access error occurs
   * @see commit
   */
  public void rollback() throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
 
      try {
        if (this.connectionLifecycleInterceptors != null) {
          IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
            void forEach(Extension each) throws SQLException {
              if (!((ConnectionLifecycleInterceptor)each).rollback()) {
                this.stopIterating = true;
              }
            }
          };
         
          iter.doForAll();
         
          if (!iter.fullIteration()) {
            return;
          }
        }
        // no-op if _relaxAutoCommit == true
        if (this.autoCommit && !getRelaxAutoCommit()) {
          throw SQLError.createSQLException(
              "Can't call rollback when autocommit=true",
              SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
        } else if (this.transactionsSupported) {
          try {
            rollbackNoChecks();
          } catch (SQLException sqlEx) {
            // We ignore non-transactional tables if told to do so
            if (getIgnoreNonTxTables()
                && (sqlEx.getErrorCode() == SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
              return;
            }
            throw sqlEx;
 
          }
        }
      } catch (SQLException sqlException) {
        if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
            .equals(sqlException.getSQLState())) {
          throw SQLError.createSQLException(
              "Communications link failure during rollback(). Transaction resolution unknown.",
              SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor());
        }
 
        throw sqlException;
      } finally {
        this.needsPing = this.getReconnectAtTxEnd();
      }
    }
  }

  /**
   * @see Connection#rollback(Savepoint)
   */
  public void rollback(final Savepoint savepoint) throws SQLException {

    synchronized (getConnectionMutex()) {
      if (versionMeetsMinimum(4, 0, 14) || versionMeetsMinimum(4, 1, 1)) {
        checkClosed();
   
        try {
          if (this.connectionLifecycleInterceptors != null) {
            IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
              void forEach(Extension each) throws SQLException {
                if (!((ConnectionLifecycleInterceptor)each).rollback(savepoint)) {
                  this.stopIterating = true;
                }
              }
            };
           
            iter.doForAll();
           
            if (!iter.fullIteration()) {
              return;
            }
          }
         
          StringBuffer rollbackQuery = new StringBuffer(
              "ROLLBACK TO SAVEPOINT ");
          rollbackQuery.append('`');
          rollbackQuery.append(savepoint.getSavepointName());
          rollbackQuery.append('`');
 
          java.sql.Statement stmt = null;
 
          try {
            stmt = getMetadataSafeStatement();
 
            stmt.executeUpdate(rollbackQuery.toString());
          } catch (SQLException sqlEx) {
            int errno = sqlEx.getErrorCode();
 
            if (errno == 1181) {
              String msg = sqlEx.getMessage();
 
              if (msg != null) {
                int indexOfError153 = msg.indexOf("153");
 
                if (indexOfError153 != -1) {
                  throw SQLError.createSQLException("Savepoint '"
                      + savepoint.getSavepointName()
                      + "' does not exist",
                      SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
                      errno, getExceptionInterceptor());
                }
              }
            }
 
            // We ignore non-transactional tables if told to do so
            if (getIgnoreNonTxTables()
                && (sqlEx.getErrorCode() != SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
              throw sqlEx;
            }
 
            if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
                .equals(sqlEx.getSQLState())) {
              throw SQLError.createSQLException(
                  "Communications link failure during rollback(). Transaction resolution unknown.",
                  SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor());
            }
 
            throw sqlEx;
          } finally {
            closeStatement(stmt);
          }
        } finally {
          this.needsPing = this.getReconnectAtTxEnd();
        }
      } else {
        throw SQLError.notImplemented();
      }
    }
  }

  private void rollbackNoChecks() throws SQLException {
    if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) {
      if (!this.io.inTransactionOnServer()) {
        return; // effectively a no-op
      }
    }
   
    execSQL(null, "rollback", -1, null,
        DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY, false,
        this.database, null, false);
  }

  /**
   * @see java.sql.Connection#prepareStatement(String)
   */
  public java.sql.PreparedStatement serverPrepareStatement(String sql)
    throws SQLException {

    String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;

    return ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.getCatalog(),
        DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY);
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, int)
   */
  public java.sql.PreparedStatement serverPrepareStatement(String sql,
      int autoGenKeyIndex) throws SQLException {
    String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;

    PreparedStatement pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.getCatalog(),
        DEFAULT_RESULT_SET_TYPE,
        DEFAULT_RESULT_SET_CONCURRENCY);
   
    pStmt.setRetrieveGeneratedKeys(
        autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);

    return pStmt;
  }
 
  /**
   * @see java.sql.Connection#prepareStatement(String, int, int)
   */
  public java.sql.PreparedStatement serverPrepareStatement(String sql,
      int resultSetType, int resultSetConcurrency) throws SQLException {
    String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;

    return ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.getCatalog(),
        resultSetType,
        resultSetConcurrency);
  }
 
  /**
   * @see java.sql.Connection#prepareStatement(String, int, int, int)
   */
  public java.sql.PreparedStatement serverPrepareStatement(String sql,
      int resultSetType, int resultSetConcurrency,
      int resultSetHoldability) throws SQLException {
    if (getPedantic()) {
      if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
        throw SQLError.createSQLException(
            "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
    }

    return serverPrepareStatement(sql, resultSetType, resultSetConcurrency);
  }
 
  /**
   * @see java.sql.Connection#prepareStatement(String, int[])
   */
  public java.sql.PreparedStatement serverPrepareStatement(String sql,
      int[] autoGenKeyIndexes) throws SQLException {
   
    PreparedStatement pStmt = (PreparedStatement) serverPrepareStatement(sql);
   
    pStmt
        .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
            && (autoGenKeyIndexes.length > 0));

    return pStmt;
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, String[])
   */
  public java.sql.PreparedStatement serverPrepareStatement(String sql,
      String[] autoGenKeyColNames) throws SQLException {
    PreparedStatement pStmt = (PreparedStatement) serverPrepareStatement(sql);

    pStmt
        .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
            && (autoGenKeyColNames.length > 0));

    return pStmt;
  }
 
  public boolean serverSupportsConvertFn() throws SQLException {
    return versionMeetsMinimum(4, 0, 2);
  }

  /**
   * If a connection is in auto-commit mode, than all its SQL statements will
   * be executed and committed as individual transactions. Otherwise, its SQL
   * statements are grouped into transactions that are terminated by either
   * commit() or rollback(). By default, new connections are in auto- commit
   * mode. The commit occurs when the statement completes or the next execute
   * occurs, whichever comes first. In the case of statements returning a
   * ResultSet, the statement completes when the last row of the ResultSet has
   * been retrieved or the ResultSet has been closed. In advanced cases, a
   * single statement may return multiple results as well as output parameter
   * values. Here the commit occurs when all results and output param values
   * have been retrieved.
   * <p>
   * <b>Note:</b> MySQL does not support transactions, so this method is a
   * no-op.
   * </p>
   *
   * @param autoCommitFlag -
   *            true enables auto-commit; false disables it
   * @exception SQLException
   *                if a database access error occurs
   */
  public void setAutoCommit(final boolean autoCommitFlag) throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
     
      if (this.connectionLifecycleInterceptors != null) {
        IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
          void forEach(Extension each) throws SQLException {
            if (!((ConnectionLifecycleInterceptor)each).setAutoCommit(autoCommitFlag)) {
              this.stopIterating = true;
            }
          }
        };
       
        iter.doForAll();
       
        if (!iter.fullIteration()) {
          return;
        }
      }
 
      if (getAutoReconnectForPools()) {
        setHighAvailability(true);
      }
 
      try {
        if (this.transactionsSupported) {
 
          boolean needsSetOnServer = true;
 
          if (this.getUseLocalSessionState()
              && this.autoCommit == autoCommitFlag) {
            needsSetOnServer = false;
          } else if (!this.getHighAvailability()) {
            needsSetOnServer = this.getIO()
                .isSetNeededForAutoCommitMode(autoCommitFlag);
          }
 
          // this internal value must be set first as failover depends on
          // it
          // being set to true to fail over (which is done by most
          // app servers and connection pools at the end of
          // a transaction), and the driver issues an implicit set
          // based on this value when it (re)-connects to a server
          // so the value holds across connections
          this.autoCommit = autoCommitFlag;
 
          if (needsSetOnServer) {
            execSQL(null, autoCommitFlag ? "SET autocommit=1"
                : "SET autocommit=0", -1, null,
                DEFAULT_RESULT_SET_TYPE,
                DEFAULT_RESULT_SET_CONCURRENCY, false,
                this.database, null, false);
          }
 
        } else {
          if ((autoCommitFlag == false) && !getRelaxAutoCommit()) {
            throw SQLError.createSQLException("MySQL Versions Older than 3.23.15 "
                + "do not support transactions",
                SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
          }
 
          this.autoCommit = autoCommitFlag;
        }
      } finally {
        if (this.getAutoReconnectForPools()) {
          setHighAvailability(false);
        }
      }
     
      return;
    }
  }

  /**
   * A sub-space of this Connection's database may be selected by setting a
   * catalog name. If the driver does not support catalogs, it will silently
   * ignore this request
   * <p>
   * <b>Note:</b> MySQL's notion of catalogs are individual databases.
   * </p>
   *
   * @param catalog
   *            the database for this connection to use
   * @throws SQLException
   *             if a database access error occurs
   */
  public void setCatalog(final String catalog) throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
 
      if (catalog == null) {
        throw SQLError.createSQLException("Catalog can not be null",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
     
      if (this.connectionLifecycleInterceptors != null) {
        IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
          void forEach(Extension each) throws SQLException {
            if (!((ConnectionLifecycleInterceptor)each).setCatalog(catalog)) {
              this.stopIterating = true;
            }
          }
        };
       
        iter.doForAll();
       
        if (!iter.fullIteration()) {
          return;
        }
      }
     
      if (getUseLocalSessionState()) {
        if (this.lowerCaseTableNames) {
          if (this.database.equalsIgnoreCase(catalog)) {
            return;
          }
        } else {
          if (this.database.equals(catalog)) {
            return;
          }
        }
      }
     
      String quotedId = this.dbmd.getIdentifierQuoteString();
 
      if ((quotedId == null) || quotedId.equals(" ")) {
        quotedId = "";
      }
 
      StringBuffer query = new StringBuffer("USE ");
      query.append(quotedId);
      query.append(catalog);
      query.append(quotedId);
 
      execSQL(null, query.toString(), -1, null,
          DEFAULT_RESULT_SET_TYPE,
          DEFAULT_RESULT_SET_CONCURRENCY, false,
          this.database, null, false);
     
      this.database = catalog;
    }
  }

  /**
   * @param failedOver
   *            The failedOver to set.
   */
  public void setFailedOver(boolean flag) {
    synchronized (getConnectionMutex()) {
      // handled higher up
    }
  }

  /**
   * @see Connection#setHoldability(int)
   */
  public void setHoldability(int arg0) throws SQLException {
    // do nothing
  }

  public void setInGlobalTx(boolean flag) {
    this.isInGlobalTx = flag;
  }

  // exposed for testing
  /**
   * @param preferSlaveDuringFailover
   *            The preferSlaveDuringFailover to set.
   */
  public void setPreferSlaveDuringFailover(boolean flag) {
    // no-op, handled further up in the wrapper
  }

  public void setReadInfoMsgEnabled(boolean flag) {
    this.readInfoMsg = flag;
  }

  /**
   * You can put a connection in read-only mode as a hint to enable database
   * optimizations <B>Note:</B> setReadOnly cannot be called while in the
   * middle of a transaction
   *
   * @param readOnlyFlag -
   *            true enables read-only mode; false disables it
   * @exception SQLException
   *                if a database access error occurs
   */
  public void setReadOnly(boolean readOnlyFlag) throws SQLException {
    checkClosed();

    setReadOnlyInternal(readOnlyFlag);
  }
 
  public void setReadOnlyInternal(boolean readOnlyFlag) throws SQLException {
    // note this this is safe even inside a transaction
    if (versionMeetsMinimum(5, 6, 5)) {
      if (!getUseLocalSessionState() || (readOnlyFlag != this.readOnly)) {
        execSQL(null, "set session transaction " + (readOnlyFlag ? "read only" : "read write"), -1, null,
            DEFAULT_RESULT_SET_TYPE,
            DEFAULT_RESULT_SET_CONCURRENCY, false,
            this.database, null,
            false);
      }
    }
   
    this.readOnly = readOnlyFlag;
  }
 
  /**
   * @see Connection#setSavepoint()
   */
  public java.sql.Savepoint setSavepoint() throws SQLException {
    MysqlSavepoint savepoint = new MysqlSavepoint(getExceptionInterceptor());

    setSavepoint(savepoint);

    return savepoint;
  }

  private void setSavepoint(MysqlSavepoint savepoint) throws SQLException {

    synchronized (getConnectionMutex()) {
      if (versionMeetsMinimum(4, 0, 14) || versionMeetsMinimum(4, 1, 1)) {
        checkClosed();
 
        StringBuffer savePointQuery = new StringBuffer("SAVEPOINT ");
        savePointQuery.append('`');
        savePointQuery.append(savepoint.getSavepointName());
        savePointQuery.append('`');
 
        java.sql.Statement stmt = null;
 
        try {
          stmt = getMetadataSafeStatement();
 
          stmt.executeUpdate(savePointQuery.toString());
        } finally {
          closeStatement(stmt);
        }
      } else {
        throw SQLError.notImplemented();
      }
    }
  }
 
  /**
   * @see Connection#setSavepoint(String)
   */
  public java.sql.Savepoint setSavepoint(String name) throws SQLException {
    synchronized (getConnectionMutex()) {
      MysqlSavepoint savepoint = new MysqlSavepoint(name, getExceptionInterceptor());
 
      setSavepoint(savepoint);
 
      return savepoint;
    }
  }
 
  /**
   *
   */
  private void setSessionVariables() throws SQLException {
    if (this.versionMeetsMinimum(4, 0, 0) && getSessionVariables() != null) {
      List<String> variablesToSet = StringUtils.split(getSessionVariables(), ",", "\"'", "\"'",
          false);

      int numVariablesToSet = variablesToSet.size();

      java.sql.Statement stmt = null;

      try {
        stmt = getMetadataSafeStatement();

        for (int i = 0; i < numVariablesToSet; i++) {
          String variableValuePair = variablesToSet.get(i);

          if (variableValuePair.startsWith("@")) {
            stmt.executeUpdate("SET " + variableValuePair);
          } else {
            stmt.executeUpdate("SET SESSION " + variableValuePair);
          }
        }
      } finally {
        if (stmt != null) {
          stmt.close();
        }
      }
    }

  }
 
  /**
   * DOCUMENT ME!
   *
   * @param level
   *            DOCUMENT ME!
   * @throws SQLException
   *             DOCUMENT ME!
   */
  public void setTransactionIsolation(int level) throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
 
      if (this.hasIsolationLevels) {
        String sql = null;
 
        boolean shouldSendSet = false;
 
        if (getAlwaysSendSetIsolation()) {
          shouldSendSet = true;
        } else {
          if (level != this.isolationLevel) {
            shouldSendSet = true;
          }
        }
 
        if (getUseLocalSessionState()) {
          shouldSendSet = this.isolationLevel != level;
        }
 
        if (shouldSendSet) {
          switch (level) {
          case java.sql.Connection.TRANSACTION_NONE:
            throw SQLError.createSQLException("Transaction isolation level "
                + "NONE not supported by MySQL", getExceptionInterceptor());
 
          case java.sql.Connection.TRANSACTION_READ_COMMITTED:
            sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED";
 
            break;
 
          case java.sql.Connection.TRANSACTION_READ_UNCOMMITTED:
            sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
 
            break;
 
          case java.sql.Connection.TRANSACTION_REPEATABLE_READ:
            sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
 
            break;
 
          case java.sql.Connection.TRANSACTION_SERIALIZABLE:
            sql = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE";
 
            break;
 
          default:
            throw SQLError.createSQLException("Unsupported transaction "
                + "isolation level '" + level + "'",
                SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
          }
 
          execSQL(null, sql, -1, null,
              DEFAULT_RESULT_SET_TYPE,
              DEFAULT_RESULT_SET_CONCURRENCY,false,
              this.database, null, false);
 
          this.isolationLevel = level;
        }
      } else {
        throw SQLError.createSQLException("Transaction Isolation Levels are "
            + "not supported on MySQL versions older than 3.23.36.",
            SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
      }
    }
  }
 
  /**
   * JDBC 2.0 Install a type-map object as the default type-map for this
   * connection
   *
   * @param map
   *            the type mapping
   * @throws SQLException
   *             if a database error occurs.
   */
  public void setTypeMap(java.util.Map<String,Class<?>> map) throws SQLException {
    synchronized (getConnectionMutex()) {
      this.typeMap = map;
    }
  }
 
  private void setupServerForTruncationChecks() throws SQLException {
    if (getJdbcCompliantTruncation()) {
      if (versionMeetsMinimum(5, 0, 2)) {
        String currentSqlMode =
          this.serverVariables.get("sql_mode");
       
        boolean strictTransTablesIsSet = StringUtils.indexOfIgnoreCase(currentSqlMode, "STRICT_TRANS_TABLES") != -1;
       
        if (currentSqlMode == null ||
            currentSqlMode.length() == 0 || !strictTransTablesIsSet) {
          StringBuffer commandBuf = new StringBuffer("SET sql_mode='");
         
          if (currentSqlMode != null && currentSqlMode.length() > 0) {
            commandBuf.append(currentSqlMode);
            commandBuf.append(",");
          }
         
          commandBuf.append("STRICT_TRANS_TABLES'");
         
          execSQL(null,  commandBuf.toString(), -1, null,
              DEFAULT_RESULT_SET_TYPE,
              DEFAULT_RESULT_SET_CONCURRENCY, false,
              this.database, null, false);
         
          setJdbcCompliantTruncation(false); // server's handling this for us now
        } else if (strictTransTablesIsSet) {
          // We didn't set it, but someone did, so we piggy back on it
          setJdbcCompliantTruncation(false); // server's handling this for us now
        }
       
      }
    }
  }
 
 
  /**
   * Used by MiniAdmin to shutdown a MySQL server
   *
   * @throws SQLException
   *             if the command can not be issued.
   */
  public void shutdownServer() throws SQLException {
    try {
      this.io.sendCommand(MysqlDefs.SHUTDOWN, null, null, false, null, 0);
    } catch (Exception ex) {
      SQLException sqlEx = SQLError.createSQLException(
          Messages.getString("Connection.UnhandledExceptionDuringShutdown"),
          SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
     
      sqlEx.initCause(ex);
     
      throw sqlEx;
    }
  }

  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  public boolean supportsIsolationLevel() {
    return this.hasIsolationLevels;
  }
 
  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  public boolean supportsQuotedIdentifiers() {
    return this.hasQuotedIdentifiers;
  }

  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  public boolean supportsTransactions() {
    return this.transactionsSupported;
  }

  /**
   * Remove the given statement from the list of open statements
   *
   * @param stmt
   *            the Statement instance to remove
   */
  public void unregisterStatement(Statement stmt) {
    if (this.openStatements != null) {
      synchronized (this.openStatements) {
        this.openStatements.remove(stmt);
      }
    }
  }

  /**
   * Called by statements on their .close() to let the connection know when it
   * is safe to set the connection back to 'default' row limits.
   *
   * @param stmt
   *            the statement releasing it's max-rows requirement
   * @throws SQLException
   *             if a database error occurs issuing the statement that sets
   *             the limit default.
   */
  public void unsetMaxRows(Statement stmt) throws SQLException {
    synchronized (getConnectionMutex()) {
      if (this.statementsUsingMaxRows != null) {
        Object found = this.statementsUsingMaxRows.remove(stmt);
 
        if ((found != null)
            && (this.statementsUsingMaxRows.size() == 0)) {
          execSQL(null, "SET SQL_SELECT_LIMIT=DEFAULT", -1,
              null, DEFAULT_RESULT_SET_TYPE,
              DEFAULT_RESULT_SET_CONCURRENCY, false,
              this.database, null, false);
 
          this.maxRowsChanged = false;
        }
      }
    }
  }
 
  public boolean useAnsiQuotedIdentifiers() {
    synchronized (getConnectionMutex()) {
      return this.useAnsiQuotes;
    }
  }
 
  /**
   * Has maxRows() been set?
   *
   * @return DOCUMENT ME!
   */
  public boolean useMaxRows() {
    synchronized (getConnectionMutex()) {
      return this.maxRowsChanged;
    }
  }
 
  public boolean versionMeetsMinimum(int major, int minor, int subminor)
      throws SQLException {
    checkClosed();

    return this.io.versionMeetsMinimum(major, minor, subminor);
  }

  /**
   * Returns cached metadata (or null if not cached) for the given query,
   * which must match _exactly_.  
   * 
   * This method is synchronized by the caller on getMutex(), so if
   * calling this method from internal code in the driver, make sure it's
   * synchronized on the mutex that guards communication with the server.
   *
   * @param sql
   *            the query that is the key to the cache
   *
   * @return metadata cached for the given SQL, or none if it doesn't
   *                  exist.
   */
  public CachedResultSetMetaData getCachedMetaData(String sql) {
    if (this.resultSetMetadataCache != null) {
      synchronized (this.resultSetMetadataCache) {
        return (CachedResultSetMetaData) this.resultSetMetadataCache
            .get(sql);
      }
    }

    return null; // no cache exists
  }

  /**
   * Caches CachedResultSetMetaData that has been placed in the cache using
   * the given SQL as a key.
   *
   * This method is synchronized by the caller on getMutex(), so if
   * calling this method from internal code in the driver, make sure it's
   * synchronized on the mutex that guards communication with the server.
   *
   * @param sql the query that the metadata pertains too.
   * @param cachedMetaData metadata (if it exists) to populate the cache.
   * @param resultSet the result set to retreive metadata from, or apply to.
   *
   * @throws SQLException
   */
  public void initializeResultsMetadataFromCache(String sql,
      CachedResultSetMetaData cachedMetaData, ResultSetInternalMethods resultSet)
      throws SQLException {

    if (cachedMetaData == null) {
     
      // read from results
      cachedMetaData = new CachedResultSetMetaData();

      // assume that users will use named-based
      // lookups
      resultSet.buildIndexMapping();
      resultSet.initializeWithMetadata();

      if (resultSet instanceof UpdatableResultSet) {
        ((UpdatableResultSet)resultSet).checkUpdatability();
      }

      resultSet.populateCachedMetaData(cachedMetaData);

      this.resultSetMetadataCache.put(sql, cachedMetaData);
    } else {
      resultSet.initializeFromCachedMetaData(cachedMetaData);
      resultSet.initializeWithMetadata();
     
      if (resultSet instanceof UpdatableResultSet) {
        ((UpdatableResultSet)resultSet).checkUpdatability();
      }
    }
  }
 
  /**
   * Returns the comment that will be prepended to all statements
   * sent to the server.
   *
   * @return the comment that will be prepended to all statements
   * sent to the server.
   */
  public String getStatementComment() {
    return this.statementComment;
  }

  /**
   * Sets the comment that will be prepended to all statements
   * sent to the server. Do not use slash-star or star-slash tokens
   * in the comment as these will be added by the driver itself.
   *
   * @param comment  the comment that will be prepended to all statements
   * sent to the server.
   */
  public void setStatementComment(String comment) {
    this.statementComment = comment;
  }
 
  public void reportQueryTime(long millisOrNanos) {
    synchronized (getConnectionMutex()) {
      this.queryTimeCount++;
      this.queryTimeSum += millisOrNanos;
      this.queryTimeSumSquares += (millisOrNanos * millisOrNanos);
      this.queryTimeMean = ((this.queryTimeMean * (this.queryTimeCount - 1)) + millisOrNanos)
          / this.queryTimeCount;
    }
  }
 
  public boolean isAbonormallyLongQuery(long millisOrNanos) {
    synchronized (getConnectionMutex()) {
      if (this.queryTimeCount < 15) {
        return false; // need a minimum amount for this to make sense
      }
     
      double stddev = Math.sqrt((this.queryTimeSumSquares - ((this.queryTimeSum*this.queryTimeSum) / this.queryTimeCount)) / (this.queryTimeCount - 1));
     
      return millisOrNanos > (this.queryTimeMean + 5 * stddev);
    }
  }

  public void initializeExtension(Extension ex) throws SQLException {
    ex.init(this, this.props);
  }
 
  public void transactionBegun() throws SQLException {
    synchronized (getConnectionMutex()) {
      if (this.connectionLifecycleInterceptors != null) {
        IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
          void forEach(Extension each) throws SQLException {
            ((ConnectionLifecycleInterceptor)each).transactionBegun();
          }
        };
       
        iter.doForAll();
      }
    }
  }
 
  public void transactionCompleted() throws SQLException {
    synchronized (getConnectionMutex()) {
      if (this.connectionLifecycleInterceptors != null) {
        IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
 
          void forEach(Extension each) throws SQLException {
            ((ConnectionLifecycleInterceptor)each).transactionCompleted();
          }
        };
       
        iter.doForAll();
      }
    }
  }
 
  public boolean storesLowerCaseTableName() {
    return storesLowerCaseTableName;
  }
 
  private ExceptionInterceptor exceptionInterceptor;
 
  public ExceptionInterceptor getExceptionInterceptor() {
    return this.exceptionInterceptor;
  }
 
  public boolean getRequiresEscapingEncoder() {
    return requiresEscapingEncoder;
  }
 
  public boolean isServerLocal() throws SQLException {
    synchronized (getConnectionMutex()) {
      SocketFactory factory = getIO().socketFactory;
     
      if (factory instanceof SocketMetadata) {
        return ((SocketMetadata)factory).isLocallyConnected(this);
      }
      getLog().logWarn(Messages.getString("Connection.NoMetadataOnSocketFactory"));
      return false;
    }
  }
 
  // JDBC-4.1
  // until we flip catalog/schema, this is a no-op
  public void setSchema(String schema) throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
    }
  }
 
  // JDBC-4.1
  public String getSchema() throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
     
      return null;
    }
  }
 
  /**
     * Terminates an open connection.  Calling <code>abort</code> results in:
     * <ul>
     * <li>The connection marked as closed
     * <li>Closes any physical connection to the database
     * <li>Releases resources used by the connection
     * <li>Insures that any thread that is currently accessing the connection
     * will either progress to completion or throw an <code>SQLException</code>.
     * </ul>
     * <p>
     * Calling <code>abort</code> marks the connection closed and releases any
     * resources. Calling <code>abort</code> on a closed connection is a
     * no-op.
     * <p>
     * It is possible that the aborting and releasing of the resources that are
     * held by the connection can take an extended period of time.  When the
     * <code>abort</code> method returns, the connection will have been marked as
     * closed and the <code>Executor</code> that was passed as a parameter to abort
     * may still be executing tasks to release resources.
     * <p>
     * This method checks to see that there is an <code>SQLPermission</code>
     * object before allowing the method to proceed.  If a
     * <code>SecurityManager</code> exists and its
     * <code>checkPermission</code> method denies calling <code>abort</code>,
     * this method throws a
     * <code>java.lang.SecurityException</code>.
     * @param executor  The <code>Executor</code>  implementation which will
     * be used by <code>abort</code>.
     * @throws java.sql.SQLException if a database access error occurs or
     * the {@code executor} is {@code null},
     * @throws java.lang.SecurityException if a security manager exists and its
     *    <code>checkPermission</code> method denies calling <code>abort</code>
     * @see SecurityManager#checkPermission
     * @see Executor
     * @since 1.7
     */
  public void abort(Executor executor) throws SQLException {
    SecurityManager sec = System.getSecurityManager();
   
    if (sec != null) {
        sec.checkPermission(ABORT_PERM);
    }
   
    if (executor == null) {
      throw SQLError.createSQLException("Executor can not be null", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
    }
   
    executor.execute(new Runnable() {

      public void run() {
        try {
          abortInternal();
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }
    });
  }
 
  // JDBC-4.1
  public void setNetworkTimeout(Executor executor, final int milliseconds) throws SQLException {
    synchronized (getConnectionMutex()) {
      SecurityManager sec = System.getSecurityManager();
     
      if (sec != null) {
          sec.checkPermission(SET_NETWORK_TIMEOUT_PERM);
      }
     
      if (executor == null) {
        throw SQLError.createSQLException("Executor can not be null", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
     
      checkClosed();
      final MysqlIO mysqlIo = this.io;
     
      executor.execute(new Runnable() {
 
        public void run() {
          try {
            setSocketTimeout(milliseconds); // for re-connects
            mysqlIo.setSocketTimeout(milliseconds);
          } catch (SQLException e) {
            throw new RuntimeException(e);
          }
        }});
    }
  }
 
  // JDBC-4.1
  public int getNetworkTimeout() throws SQLException {
    synchronized (getConnectionMutex()) {
      checkClosed();
      return getSocketTimeout();
    }
  }
}
TOP

Related Classes of com.mysql.jdbc.ConnectionImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.