Package com.mysql.jdbc

Source Code of com.mysql.jdbc.StatementImpl$CancelTask

/*
  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.InputStream;
import java.math.BigInteger;
import java.sql.BatchUpdateException;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;

import com.mysql.jdbc.exceptions.MySQLStatementCancelledException;
import com.mysql.jdbc.exceptions.MySQLTimeoutException;
import com.mysql.jdbc.log.LogUtils;
import com.mysql.jdbc.profiler.ProfilerEvent;
import com.mysql.jdbc.profiler.ProfilerEventHandler;

/**
* A Statement object is used for executing a static SQL statement and obtaining
* the results produced by it.
*
* <p>
* Only one ResultSet per Statement can be open at any point in time. Therefore,
* if the reading of one ResultSet is interleaved with the reading of another,
* each must have been generated by different Statements. All statement execute
* methods implicitly close a statement's current ResultSet if an open one
* exists.
* </p>
*
* @author Mark Matthews
* @version $Id: Statement.java 4624 2005-11-28 14:24:29 -0600 (Mon, 28 Nov
*          2005) mmatthews $
*
* @see java.sql.Statement
* @see ResultSetInternalMethods
*/
public class StatementImpl implements Statement {
        protected static final String PING_MARKER = "/* ping */";
  /**
   * Thread used to implement query timeouts...Eventually we could be more
   * efficient and have one thread with timers, but this is a straightforward
   * and simple way to implement a feature that isn't used all that often.
   */
  class CancelTask extends TimerTask {

    long connectionId = 0;
    String origHost = "";
    SQLException caughtWhileCancelling = null;
    StatementImpl toCancel;
    Properties origConnProps = null;
    String origConnURL = "";
   
    CancelTask(StatementImpl cancellee) throws SQLException {
      connectionId = cancellee.connectionId;
      origHost = connection.getHost();
      toCancel = cancellee;
      origConnProps = new Properties();
     
      Properties props = connection.getProperties();
     
      Enumeration<?> keys = props.propertyNames();
     
      while (keys.hasMoreElements()) {
        String key = keys.nextElement().toString();
        origConnProps.setProperty(key, props.getProperty(key));
      }
     
      origConnURL = connection.getURL();
    }

    public void run() {

      Thread cancelThread = new Thread() {

        public void run() {

          Connection cancelConn = null;
          java.sql.Statement cancelStmt = null;

          try {
            if (connection.getQueryTimeoutKillsConnection()) {
              toCancel.wasCancelled = true;
              toCancel.wasCancelledByTimeout = true;
              connection.realClose(false, false, true,
                  new MySQLStatementCancelledException(Messages.getString("Statement.ConnectionKilledDueToTimeout")));
            } else {
              synchronized (cancelTimeoutMutex) {
                if (origConnURL.equals(connection.getURL())) {
                  //All's fine
                  cancelConn = connection.duplicate();
                  cancelStmt = cancelConn.createStatement();
                  cancelStmt.execute("KILL QUERY " + connectionId);
                } else {
                  try {
                    cancelConn = (Connection) DriverManager.getConnection(origConnURL, origConnProps);
                    cancelStmt = cancelConn.createStatement();
                    cancelStmt.execute("KILL QUERY " + connectionId);
                  } catch (NullPointerException npe){
                    //Log this? "Failed to connect to " + origConnURL + " and KILL query"
                  }
                }
                toCancel.wasCancelled = true;
                toCancel.wasCancelledByTimeout = true;
              }
            }
          } catch (SQLException sqlEx) {
            caughtWhileCancelling = sqlEx;
          } catch (NullPointerException npe) {
            // Case when connection closed while starting to cancel
            // We can't easily synchronize this, because then one thread
            // can't cancel() a running query

            // ignore, we shouldn't re-throw this, because the connection's
            // already closed, so the statement has been timed out.
          } finally {
            if (cancelStmt != null) {
              try {
                cancelStmt.close();
              } catch (SQLException sqlEx) {
                throw new RuntimeException(sqlEx.toString());
              }
            }

            if (cancelConn != null) {
              try {
                cancelConn.close();
              } catch (SQLException sqlEx) {
                throw new RuntimeException(sqlEx.toString());
              }
            }
           
            toCancel = null;
            origConnProps = null;
            origConnURL = null;
          }
        }
      };

      cancelThread.start();
    }
  }

  /** Mutex to prevent race between returning query results and noticing
    that we're timed-out or cancelled. */

  protected Object cancelTimeoutMutex = new Object();

  /** Used to generate IDs when profiling. */
  static int statementCounter = 1;

  public final static byte USES_VARIABLES_FALSE = 0;

  public final static byte USES_VARIABLES_TRUE = 1;

  public final static byte USES_VARIABLES_UNKNOWN = -1;

  protected boolean wasCancelled = false;
  protected boolean wasCancelledByTimeout = false;

  /** Holds batched commands */
  protected List<Object> batchedArgs;

  /** The character converter to use (if available) */
  protected SingleByteCharsetConverter charConverter = null;

  /** The character encoding to use (if available) */
  protected String charEncoding = null;

  /** The connection that created us */
  protected volatile MySQLConnection connection = null;

  protected long connectionId = 0;

  /** The catalog in use */
  protected String currentCatalog = null;

  /** Should we process escape codes? */
  protected boolean doEscapeProcessing = true;

  /** If we're profiling, where should events go to? */
  protected ProfilerEventHandler eventSink = null;

  /** The number of rows to fetch at a time (currently ignored) */
  private int fetchSize = 0;

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

  /** The auto_increment value for the last insert */
  protected long lastInsertId = -1;

  /** The max field size for this statement */
  protected int maxFieldSize = MysqlIO.getMaxBuf();

  /**
   * The maximum number of rows to return for this statement (-1 means _all_
   * rows)
   */
  protected int maxRows = -1;

  /** Has someone changed this for this statement? */
  protected boolean maxRowsChanged = false;

  /** Set of currently-open ResultSets */
  protected Set<ResultSetInternalMethods> openResults = new HashSet<ResultSetInternalMethods>();

  /** Are we in pedantic mode? */
  protected boolean pedantic = false;

  /**
   * Where this statement was created, only used if profileSql or
   * useUsageAdvisor set to true.
   */
  protected String pointOfOrigin;

  /** Should we profile? */
  protected boolean profileSQL = false;

  /** The current results */
  protected ResultSetInternalMethods results = null;

  protected ResultSetInternalMethods generatedKeysResults = null;

  /** The concurrency for this result set (updatable or not) */
  protected int resultSetConcurrency = 0;

  /** The type of this result set (scroll sensitive or in-sensitive) */
  protected int resultSetType = 0;

  /** Used to identify this statement when profiling. */
  protected int statementId;

  /** The timeout for a query */
  protected int timeoutInMillis = 0;

  /** The update count for this statement */
  protected long updateCount = -1;

  /** Should we use the usage advisor? */
  protected boolean useUsageAdvisor = false;

  /** The warnings chain. */
  protected SQLWarning warningChain = null;
 
  /** Has clearWarnings() been called? */
  protected boolean clearWarningsCalled = false;

  /**
   * Should this statement hold results open over .close() irregardless of
   * connection's setting?
   */
  protected boolean holdResultsOpenOverClose = false;

  protected ArrayList<ResultSetRow> batchedGeneratedKeys = null;

  protected boolean retrieveGeneratedKeys = false;

  protected boolean continueBatchOnError = false;

  protected PingTarget pingTarget = null;
 
  protected boolean useLegacyDatetimeCode;
 
  private ExceptionInterceptor exceptionInterceptor;
 
  /** Whether or not the last query was of the form ON DUPLICATE KEY UPDATE */
  protected boolean lastQueryIsOnDupKeyUpdate = false;
 
  /** Are we currently executing a statement? */
  protected final AtomicBoolean statementExecuting = new AtomicBoolean(false);
 
  /**
   * Constructor for a Statement.
   *
   * @param c
   *            the Connection instantation that creates us
   * @param catalog
   *            the database name in use when we were created
   *
   * @throws SQLException
   *             if an error occurs.
   */
  public StatementImpl(MySQLConnection c, String catalog) throws SQLException {
    if ((c == null) || c.isClosed()) {
      throw SQLError.createSQLException(
          Messages.getString("Statement.0"), //$NON-NLS-1$
          SQLError.SQL_STATE_CONNECTION_NOT_OPEN, null); //$NON-NLS-1$ //$NON-NLS-2$
    }

    this.connection = c;
    this.connectionId = this.connection.getId();
    this.exceptionInterceptor = this.connection
        .getExceptionInterceptor();

    this.currentCatalog = catalog;
    this.pedantic = this.connection.getPedantic();
    this.continueBatchOnError = this.connection.getContinueBatchOnError();
    this.useLegacyDatetimeCode = this.connection.getUseLegacyDatetimeCode();
   
    if (!this.connection.getDontTrackOpenResources()) {
      this.connection.registerStatement(this);
    }

    //
    // Adjust, if we know it
    //

    if (this.connection != null) {
      this.maxFieldSize = this.connection.getMaxAllowedPacket();

      int defaultFetchSize = this.connection.getDefaultFetchSize();

      if (defaultFetchSize != 0) {
        setFetchSize(defaultFetchSize);
      }
     
      if (this.connection.getUseUnicode()) {
        this.charEncoding = this.connection.getEncoding();

        this.charConverter = this.connection.getCharsetConverter(this.charEncoding);
      }
     
     

      boolean profiling = this.connection.getProfileSql()
          || this.connection.getUseUsageAdvisor() || this.connection.getLogSlowQueries();

      if (this.connection.getAutoGenerateTestcaseScript() || profiling) {
        this.statementId = statementCounter++;
      }

      if (profiling) {
        this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable());
        this.profileSQL = this.connection.getProfileSql();
        this.useUsageAdvisor = this.connection.getUseUsageAdvisor();
        this.eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);
      }

      int maxRowsConn = this.connection.getMaxRows();

      if (maxRowsConn != -1) {
        setMaxRows(maxRowsConn);
      }
     
      this.holdResultsOpenOverClose = this.connection.getHoldResultsOpenOverStatementClose();
    }
   
    version5013OrNewer = this.connection.versionMeetsMinimum(5, 0, 13);
  }

  /**
   * DOCUMENT ME!
   *
   * @param sql
   *            DOCUMENT ME!
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  public void addBatch(String sql) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.batchedArgs == null) {
        this.batchedArgs = new ArrayList<Object>();
      }
 
      if (sql != null) {
        this.batchedArgs.add(sql);
      }
    }
  }

  /** Get the batched args as added by the addBatch method(s).
   * The list is unmodifiable and might contain any combination of String,
   * BatchParams, or BatchedBindValues depending on how the parameters were
   * batched.
   * @return an unmodifiable List of batched args
   */
  public List<Object> getBatchedArgs() {
    return batchedArgs==null?null:Collections.unmodifiableList(batchedArgs);
  }

  /**
   * Cancels this Statement object if both the DBMS and driver support
   * aborting an SQL statement. This method can be used by one thread to
   * cancel a statement that is being executed by another thread.
   */
  public void cancel() throws SQLException {
    if (!this.statementExecuting.get()) {
      return;
    }
   
    if (!this.isClosed &&
        this.connection != null &&
        this.connection.versionMeetsMinimum(5, 0, 0)) {
      Connection cancelConn = null;
      java.sql.Statement cancelStmt = null;

      try {
        cancelConn = this.connection.duplicate();
        cancelStmt = cancelConn.createStatement();
        cancelStmt.execute("KILL QUERY "
            + this.connection.getIO().getThreadId());
        this.wasCancelled = true;
      } finally {
        if (cancelStmt != null) {
          cancelStmt.close();
        }

        if (cancelConn != null) {
          cancelConn.close();
        }
      }

    }
  }

  // --------------------------JDBC 2.0-----------------------------

  /**
   * Checks if closed() has been called, and throws an exception if so
   *
   * @throws SQLException
   *             if this statement has been closed
   */
  protected MySQLConnection checkClosed() throws SQLException {
    MySQLConnection c = this.connection;
   
    if (c == null) {
      throw SQLError.createSQLException(Messages
          .getString("Statement.49"), //$NON-NLS-1$
          SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); //$NON-NLS-1$
    }
   
    return c;
  }

  /**
   * Checks if the given SQL query with the given first non-ws char is a DML
   * statement. Throws an exception if it is.
   *
   * @param sql
   *            the SQL to check
   * @param firstStatementChar
   *            the UC first non-ws char of the statement
   *
   * @throws SQLException
   *             if the statement contains DML
   */
  protected void checkForDml(String sql, char firstStatementChar)
      throws SQLException {
    if ((firstStatementChar == 'I') || (firstStatementChar == 'U')
        || (firstStatementChar == 'D') || (firstStatementChar == 'A')
        || (firstStatementChar == 'C') || (firstStatementChar == 'T')
        || (firstStatementChar == 'R')) {
      String noCommentSql = StringUtils.stripComments(sql,
          "'\"", "'\"", true, false, true, true);

      if (StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "INSERT") //$NON-NLS-1$
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "UPDATE") //$NON-NLS-1$
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DELETE") //$NON-NLS-1$
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DROP") //$NON-NLS-1$
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "CREATE") //$NON-NLS-1$
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "ALTER")
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "TRUNCATE")
          || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "RENAME")
          ) { //$NON-NLS-1$
        throw SQLError.createSQLException(Messages
            .getString("Statement.57"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }
    }
  }

  /**
   * Method checkNullOrEmptyQuery.
   *
   * @param sql
   *            the SQL to check
   *
   * @throws SQLException
   *             if query is null or empty.
   */
  protected void checkNullOrEmptyQuery(String sql) throws SQLException {
    if (sql == null) {
      throw SQLError.createSQLException(Messages
          .getString("Statement.59"), //$NON-NLS-1$
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$
    }

    if (sql.length() == 0) {
      throw SQLError.createSQLException(Messages
          .getString("Statement.61"), //$NON-NLS-1$
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }

  /**
   * JDBC 2.0 Make the set of commands in the current batch empty. This method
   * is optional.
   *
   * @exception SQLException
   *                if a database-access error occurs, or the driver does not
   *                support batch statements
   */
  public void clearBatch() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.batchedArgs != null) {
        this.batchedArgs.clear();
      }
    }
  }

  /**
   * After this call, getWarnings returns null until a new warning is reported
   * for this Statement.
   *
   * @exception SQLException
   *                if a database access error occurs (why?)
   */
  public void clearWarnings() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      this.clearWarningsCalled = true;
      this.warningChain = null;
    }
  }

  /**
   * In many cases, it is desirable to immediately release a Statement's
   * database and JDBC resources instead of waiting for this to happen when it
   * is automatically closed. The close method provides this immediate
   * release.
   *
   * <p>
   * <B>Note:</B> A Statement is automatically closed when it is garbage
   * collected. When a Statement is closed, its current ResultSet, if one
   * exists, is also closed.
   * </p>
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public void close() throws SQLException {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        realClose(true, true);
      }
    } catch (SQLException sqlEx) {
      if (SQLError.SQL_STATE_CONNECTION_NOT_OPEN.equals(sqlEx.getSQLState())) {
        return;
      }
     
      throw sqlEx;
    }
  }

  /**
   * Close any open result sets that have been 'held open'
   */
  protected void closeAllOpenResults() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.openResults != null) {
        for (ResultSetInternalMethods element : this.openResults) {
          try {
            element.realClose(false);
          } catch (SQLException sqlEx) {
            AssertionFailedException.shouldNotHappen(sqlEx);
          }
        }
 
        this.openResults.clear();
      }
    }
  }

  public void removeOpenResultSet(ResultSet rs) {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        if (this.openResults != null) {
          this.openResults.remove(rs);
        }
      }
    } catch (SQLException e) {
      // we can't break the interface, having this be no-op in case of error is ok
    }
  }
 
  public int getOpenResultSetCount() {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        if (this.openResults != null) {
          return this.openResults.size();
        }
       
        return 0;
      }
    } catch (SQLException e) {
      // we can't break the interface, having this be no-op in case of error is ok
     
      return 0;
    }
  }
 
  /**
   * @param sql
   * @return
   */
  private ResultSetInternalMethods createResultSetUsingServerFetch(String sql)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      java.sql.PreparedStatement pStmt = this.connection.prepareStatement(
          sql, this.resultSetType, this.resultSetConcurrency);
 
      pStmt.setFetchSize(this.fetchSize);
 
      if (this.maxRows > -1) {
        pStmt.setMaxRows(this.maxRows);
      }
 
      statementBegins();
 
      pStmt.execute();
 
      //
      // Need to be able to get resultset irrespective if we issued DML or
      // not to make this work.
      //
      ResultSetInternalMethods rs = ((com.mysql.jdbc.StatementImpl) pStmt)
          .getResultSetInternal();
 
      rs
          .setStatementUsedForFetchingRows((com.mysql.jdbc.PreparedStatement) pStmt);
 
      this.results = rs;
 
      return rs;
    }
  }

  /**
   * We only stream result sets when they are forward-only, read-only, and the
   * fetch size has been set to Integer.MIN_VALUE
   *
   * @return true if this result set should be streamed row at-a-time, rather
   *         than read all at once.
   */
  protected boolean createStreamingResultSet() {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        return ((this.resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY)
            && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this.fetchSize == Integer.MIN_VALUE));
      }
    } catch (SQLException e) {
      // we can't break the interface, having this be no-op in case of error is ok
     
      return false;
    }
  }

  private int originalResultSetType = 0;
  private int originalFetchSize = 0;

  /* (non-Javadoc)
   * @see com.mysql.jdbc.IStatement#enableStreamingResults()
   */
  public void enableStreamingResults() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      this.originalResultSetType = this.resultSetType;
      this.originalFetchSize = this.fetchSize;
 
      setFetchSize(Integer.MIN_VALUE);
      setResultSetType(ResultSet.TYPE_FORWARD_ONLY);
    }
  }

  public void disableStreamingResults() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.fetchSize == Integer.MIN_VALUE &&
          this.resultSetType == ResultSet.TYPE_FORWARD_ONLY) {
        setFetchSize(this.originalFetchSize);
        setResultSetType(this.originalResultSetType);
      }
    }
  }

  /**
   * Execute a SQL statement that may return multiple results. We don't have
   * to worry about this since we do not support multiple ResultSets. You can
   * use getResultSet or getUpdateCount to retrieve the result.
   *
   * @param sql
   *            any SQL statement
   *
   * @return true if the next result is a ResulSet, false if it is an update
   *         count or there are no more results
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public boolean execute(String sql) throws SQLException {
    return execute(sql, false);
  }
 
  private boolean execute(String sql, boolean returnGeneratedKeys) throws SQLException {
    MySQLConnection locallyScopedConn = checkClosed();

    synchronized (locallyScopedConn.getConnectionMutex()) {
      this.retrieveGeneratedKeys = returnGeneratedKeys;
      lastQueryIsOnDupKeyUpdate = false;
      if (returnGeneratedKeys)
        lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyInString(sql);
     
      resetCancelledState();

      checkNullOrEmptyQuery(sql);

      checkClosed();

      char firstNonWsChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql));

      boolean isSelect = true;

      if (firstNonWsChar != 'S') {
        isSelect = false;

        if (locallyScopedConn.isReadOnly()) {
          throw SQLError.createSQLException(Messages
              .getString("Statement.27") //$NON-NLS-1$
              + Messages.getString("Statement.28"), //$NON-NLS-1$
              SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
        }
      }

      boolean doStreaming = createStreamingResultSet();

      try {
        // Adjust net_write_timeout to a higher value if we're
        // streaming result sets. More often than not, someone runs into
        // an issue where they blow net_write_timeout when using this
        // feature, and if they're willing to hold a result set open
        // for 30 seconds or more, one more round-trip isn't going to hurt
        //
        // This is reset by RowDataDynamic.close().
 
        if (doStreaming
            && locallyScopedConn.getNetTimeoutForStreamingResults() > 0) {
          executeSimpleNonQuery(locallyScopedConn, "SET net_write_timeout="
              + locallyScopedConn.getNetTimeoutForStreamingResults());
        }
 
        if (this.doEscapeProcessing) {
          Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
              locallyScopedConn.serverSupportsConvertFn(), locallyScopedConn);
 
          if (escapedSqlResult instanceof String) {
            sql = (String) escapedSqlResult;
          } else {
            sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
          }
        }
 
        if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) {
          if (this.results != null) {
            this.results.realClose(false);
          }
          if (this.generatedKeysResults != null) {
            this.generatedKeysResults.realClose(false);
          }
          closeAllOpenResults();
        }

        if (sql.charAt(0) == '/') {
          if (sql.startsWith(PING_MARKER)) {
            doPingInstead();
         
            return true;
          }
        }
 
        CachedResultSetMetaData cachedMetaData = null;
 
        ResultSetInternalMethods rs = null;
 
        // If there isn't a limit clause in the SQL
        // then limit the number of rows to return in
        // an efficient manner. Only do this if
        // setMaxRows() hasn't been used on any Statements
        // generated from the current Connection (saves
        // a query, and network traffic).
 
        this.batchedGeneratedKeys = null;
 
        if (useServerFetch()) {
          rs = createResultSetUsingServerFetch(sql);
        } else {
          CancelTask timeoutTask = null;
 
          String oldCatalog = null;
 
          try {
            if (locallyScopedConn.getEnableQueryTimeouts() &&
                this.timeoutInMillis != 0
                && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
              timeoutTask = new CancelTask(this);
              locallyScopedConn.getCancelTimer().schedule(timeoutTask,
                  this.timeoutInMillis);
            }

            if (!locallyScopedConn.getCatalog().equals(
                this.currentCatalog)) {
              oldCatalog = locallyScopedConn.getCatalog();
              locallyScopedConn.setCatalog(this.currentCatalog);
            }
 
            //
            // Check if we have cached metadata for this query...
            //
 
            Field[] cachedFields = null;
 
            if (locallyScopedConn.getCacheResultSetMetadata()) {
              cachedMetaData = locallyScopedConn.getCachedMetaData(sql);
 
              if (cachedMetaData != null) {
                cachedFields = cachedMetaData.fields;
              }
            }
 
            //
            // Only apply max_rows to selects
            //
            if (locallyScopedConn.useMaxRows()) {
              int rowLimit = -1;
 
              if (isSelect) {
                if (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1) { //$NON-NLS-1$
                  rowLimit = this.maxRows;
                } else {
                  if (this.maxRows <= 0) {
                    executeSimpleNonQuery(locallyScopedConn,
                        "SET SQL_SELECT_LIMIT=DEFAULT");
                  } else {
                    executeSimpleNonQuery(locallyScopedConn,
                        "SET SQL_SELECT_LIMIT=" + this.maxRows);
                  }
                }
              } else {
                executeSimpleNonQuery(locallyScopedConn,
                    "SET SQL_SELECT_LIMIT=DEFAULT");
              }
 

              statementBegins();

              // Finally, execute the query
              rs = locallyScopedConn.execSQL(this, sql, rowLimit, null,
                  this.resultSetType, this.resultSetConcurrency,
                  doStreaming,
                  this.currentCatalog, cachedFields);
            } else {
              statementBegins();

              rs = locallyScopedConn.execSQL(this, sql, -1, null,
                  this.resultSetType, this.resultSetConcurrency,
                  doStreaming,
                  this.currentCatalog, cachedFields);
            }
 
            if (timeoutTask != null) {
              if (timeoutTask.caughtWhileCancelling != null) {
                throw timeoutTask.caughtWhileCancelling;
              }
 
              timeoutTask.cancel();
              timeoutTask = null;
            }
 
            synchronized (this.cancelTimeoutMutex) {
              if (this.wasCancelled) {
                SQLException cause = null;
               
                if (this.wasCancelledByTimeout) {
                  cause = new MySQLTimeoutException();
                } else {
                  cause = new MySQLStatementCancelledException();
                }
               
                resetCancelledState();
               
                throw cause;
              }
            }
          } finally {
            if (timeoutTask != null) {
              timeoutTask.cancel();
              locallyScopedConn.getCancelTimer().purge();
            }
 
            if (oldCatalog != null) {
              locallyScopedConn.setCatalog(oldCatalog);
            }
          }
        }
 
        if (rs != null) {
          this.lastInsertId = rs.getUpdateID();
 
          this.results = rs;
 
          rs.setFirstCharOfQuery(firstNonWsChar);
 
          if (rs.reallyResult()) {
            if (cachedMetaData != null) {
              locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData,
                  this.results);
            } else {
              if (this.connection.getCacheResultSetMetadata()) {
                locallyScopedConn.initializeResultsMetadataFromCache(sql,
                    null /* will be created */, this.results);
              }
            }
          }
        }
 
        return ((rs != null) && rs.reallyResult());
      } finally {
        this.statementExecuting.set(false);
      }
    }
  }

  protected void statementBegins() {
    this.clearWarningsCalled = false;
    this.statementExecuting.set(true);
  }

  protected void resetCancelledState() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.cancelTimeoutMutex == null) {
        return;
      }
     
      synchronized (this.cancelTimeoutMutex) {
        this.wasCancelled = false;
        this.wasCancelledByTimeout = false;
      }
    }
  }

  /**
   * @see StatementImpl#execute(String, int)
   */
  public boolean execute(String sql, int returnGeneratedKeys)
      throws SQLException {


    if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS) {
      checkClosed();

      MySQLConnection locallyScopedConn = this.connection;

      synchronized (locallyScopedConn.getConnectionMutex()) {
        // If this is a 'REPLACE' query, we need to be able to parse
        // the 'info' message returned from the server to determine
        // the actual number of keys generated.
        boolean readInfoMsgState = this.connection
            .isReadInfoMsgEnabled();
        locallyScopedConn.setReadInfoMsgEnabled(true);

        try {
          return execute(sql, true);
        } finally {
          locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState);
        }
      }
    }

    return execute(sql);
  }

  /**
   * @see StatementImpl#execute(String, int[])
   */
  public boolean execute(String sql, int[] generatedKeyIndices)
      throws SQLException {
    MySQLConnection locallyScopedConn = checkClosed();

    synchronized (locallyScopedConn.getConnectionMutex()) {
      if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) {

        this.retrieveGeneratedKeys = true;
       
        // If this is a 'REPLACE' query, we need to be able to parse
        // the 'info' message returned from the server to determine
        // the actual number of keys generated.
        boolean readInfoMsgState = locallyScopedConn
            .isReadInfoMsgEnabled();
        locallyScopedConn.setReadInfoMsgEnabled(true);

        try {
          return execute(sql, true);
        } finally {
          locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState);
        }
      }
     
      return execute(sql);
   
  }

  /**
   * @see StatementImpl#execute(String, String[])
   */
  public boolean execute(String sql, String[] generatedKeyNames)
      throws SQLException {
    MySQLConnection locallyScopedConn = checkClosed();

    synchronized (locallyScopedConn.getConnectionMutex()) {
      if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) {

        this.retrieveGeneratedKeys = true;
        // If this is a 'REPLACE' query, we need to be able to parse
        // the 'info' message returned from the server to determine
        // the actual number of keys generated.
        boolean readInfoMsgState = this.connection
            .isReadInfoMsgEnabled();
        locallyScopedConn.setReadInfoMsgEnabled(true);

        try {
          return execute(sql, true);
        } finally {
          locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState);
        }
      }

      return execute(sql);
    }
  }

  /**
   * JDBC 2.0 Submit a batch of commands to the database for execution. This
   * method is optional.
   *
   * @return an array of update counts containing one element for each command
   *         in the batch. The array is ordered according to the order in
   *         which commands were inserted into the batch
   *
   * @exception SQLException
   *                if a database-access error occurs, or the driver does not
   *                support batch statements
   * @throws java.sql.BatchUpdateException
   *             DOCUMENT ME!
   */
  public int[] executeBatch() throws SQLException {
    MySQLConnection locallyScopedConn = checkClosed();

    synchronized (locallyScopedConn.getConnectionMutex()) {
      if (locallyScopedConn.isReadOnly()) {
        throw SQLError.createSQLException(Messages
            .getString("Statement.34") //$NON-NLS-1$
            + Messages.getString("Statement.35"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }
 
      if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) {
        if (this.results != null) {
          this.results.realClose(false);
        }
        if (this.generatedKeysResults != null) {
          this.generatedKeysResults.realClose(false);
        }
        closeAllOpenResults();
      }

      if (this.batchedArgs == null || this.batchedArgs.size() == 0) {
                return new int[0];
            }
     
      // we timeout the entire batch, not individual statements
      int individualStatementTimeout = this.timeoutInMillis;
      this.timeoutInMillis = 0;
     
      CancelTask timeoutTask = null;
     
      try {
        resetCancelledState();

        statementBegins();
       
        try {
          this.retrieveGeneratedKeys = true; // The JDBC spec doesn't forbid this, but doesn't provide for it either...we do..
 
          int[] updateCounts = null;
 
         
          if (this.batchedArgs != null) {
            int nbrCommands = this.batchedArgs.size();
 
            this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(this.batchedArgs.size());
 
            boolean multiQueriesEnabled = locallyScopedConn.getAllowMultiQueries();
 
            if (locallyScopedConn.versionMeetsMinimum(4, 1, 1) &&
                (multiQueriesEnabled ||
                (locallyScopedConn.getRewriteBatchedStatements() &&
                    nbrCommands > 4))) {
              return executeBatchUsingMultiQueries(multiQueriesEnabled, nbrCommands, individualStatementTimeout);
            }
 
            if (locallyScopedConn.getEnableQueryTimeouts() &&
                individualStatementTimeout != 0
                && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
              timeoutTask = new CancelTask(this);
              locallyScopedConn.getCancelTimer().schedule(timeoutTask,
                  individualStatementTimeout);
            }
           
            updateCounts = new int[nbrCommands];
 
            for (int i = 0; i < nbrCommands; i++) {
              updateCounts[i] = -3;
            }
 
            SQLException sqlEx = null;
 
            int commandIndex = 0;
 
            for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
              try {
                String sql = (String) this.batchedArgs.get(commandIndex);
                updateCounts[commandIndex] = executeUpdate(sql, true, true);
                // limit one generated key per OnDuplicateKey statement
                getBatchedGeneratedKeys(containsOnDuplicateKeyInString(sql) ? 1 : 0);
              } catch (SQLException ex) {
                updateCounts[commandIndex] = EXECUTE_FAILED;
 
                if (this.continueBatchOnError &&
                    !(ex instanceof MySQLTimeoutException) &&
                    !(ex instanceof MySQLStatementCancelledException) &&
                                      !hasDeadlockOrTimeoutRolledBackTx(ex)) {
                  sqlEx = ex;
                } else {
                  int[] newUpdateCounts = new int[commandIndex];
                 
                  if (hasDeadlockOrTimeoutRolledBackTx(ex)) {
                    for (int i = 0; i < newUpdateCounts.length; i++) {
                      newUpdateCounts[i] = Statement.EXECUTE_FAILED;
                    }
                  } else {
                    System.arraycopy(updateCounts, 0,
                      newUpdateCounts, 0, commandIndex);
                  }
 
                  throw new java.sql.BatchUpdateException(ex
                      .getMessage(), ex.getSQLState(), ex
                      .getErrorCode(), newUpdateCounts);
                }
              }
            }
 
            if (sqlEx != null) {
              throw new java.sql.BatchUpdateException(sqlEx
                  .getMessage(), sqlEx.getSQLState(), sqlEx
                  .getErrorCode(), updateCounts);
            }
          }
 
          if (timeoutTask != null) {
            if (timeoutTask.caughtWhileCancelling != null) {
              throw timeoutTask.caughtWhileCancelling;
            }
 
            timeoutTask.cancel();
           
            locallyScopedConn.getCancelTimer().purge();
            timeoutTask = null;
          }
         
          return (updateCounts != null) ? updateCounts : new int[0];
        } finally {
          this.statementExecuting.set(false);
        }
      } finally {
       
        if (timeoutTask != null) {
          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
        }
       
        resetCancelledState();
       
        this.timeoutInMillis = individualStatementTimeout;

        clearBatch();
      }
    }
  }

  protected final boolean hasDeadlockOrTimeoutRolledBackTx(SQLException ex) {
    int vendorCode = ex.getErrorCode();
   
    switch (vendorCode) {
    case MysqlErrorNumbers.ER_LOCK_DEADLOCK:
    case MysqlErrorNumbers.ER_LOCK_TABLE_FULL:
      return true;
    case MysqlErrorNumbers.ER_LOCK_WAIT_TIMEOUT:
        return !version5013OrNewer;
    default:
      return false;
    }
  }

  /**
   * Rewrites batch into a single query to send to the server. This method
   * will constrain each batch to be shorter than max_allowed_packet on the
   * server.
   *
   * @return update counts in the same manner as executeBatch()
   * @throws SQLException
   */
  private int[] executeBatchUsingMultiQueries(boolean multiQueriesEnabled,
      int nbrCommands, int individualStatementTimeout) throws SQLException {
   
    MySQLConnection locallyScopedConn = checkClosed();

    synchronized (locallyScopedConn.getConnectionMutex()) {
      if (!multiQueriesEnabled) {
        locallyScopedConn.getIO().enableMultiQueries();
      }
 
      java.sql.Statement batchStmt = null;
 
      CancelTask timeoutTask = null;
     
      try {
        int[] updateCounts = new int[nbrCommands];
 
        for (int i = 0; i < nbrCommands; i++) {
          updateCounts[i] = -3;
        }
 
        int commandIndex = 0;
 
        StringBuffer queryBuf = new StringBuffer();
 
        batchStmt = locallyScopedConn.createStatement();
 
        if (locallyScopedConn.getEnableQueryTimeouts() &&
            individualStatementTimeout != 0
            && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
          timeoutTask = new CancelTask((StatementImpl)batchStmt);
          locallyScopedConn.getCancelTimer().schedule(timeoutTask,
              individualStatementTimeout);
        }
       
        int counter = 0;
 
        int numberOfBytesPerChar = 1;
 
        String connectionEncoding = locallyScopedConn.getEncoding();
 
        if (StringUtils.startsWithIgnoreCase(connectionEncoding, "utf")) {
          numberOfBytesPerChar = 3;
        } else if (CharsetMapping.isMultibyteCharset(connectionEncoding)) {
          numberOfBytesPerChar = 2;
        }
 
        int escapeAdjust = 1;
 
        batchStmt.setEscapeProcessing(this.doEscapeProcessing);
       
        if (this.doEscapeProcessing) {
         
          escapeAdjust = 2; /* We assume packet _could_ grow by this amount, as we're not
                               sure how big statement will end up after
                               escape processing */
        }
 
        SQLException sqlEx = null;
       
        int argumentSetsInBatchSoFar = 0;
       
        for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
          String nextQuery = (String) this.batchedArgs.get(commandIndex);
 
          if (((((queryBuf.length() + nextQuery.length())
              * numberOfBytesPerChar) + 1 /* for semicolon */
              + MysqlIO.HEADER_LENGTH) * escapeAdjust+ 32 > this.connection
              .getMaxAllowedPacket()) {
            try {
              batchStmt.execute(queryBuf.toString(), Statement.RETURN_GENERATED_KEYS);
            } catch (SQLException ex) {
              sqlEx = handleExceptionForBatch(commandIndex,
                  argumentSetsInBatchSoFar, updateCounts, ex);
            }
 
            counter = processMultiCountsAndKeys((StatementImpl)batchStmt, counter,
                updateCounts);
 
            queryBuf = new StringBuffer();
            argumentSetsInBatchSoFar = 0;
          }
 
          queryBuf.append(nextQuery);
          queryBuf.append(";");
          argumentSetsInBatchSoFar++;
        }
 
        if (queryBuf.length() > 0) {
          try {
            batchStmt.execute(queryBuf.toString(), Statement.RETURN_GENERATED_KEYS);
          } catch (SQLException ex) {
            sqlEx = handleExceptionForBatch(commandIndex - 1,
                argumentSetsInBatchSoFar, updateCounts, ex);
          }
 
          counter = processMultiCountsAndKeys((StatementImpl)batchStmt, counter,
              updateCounts);
        }
 
        if (timeoutTask != null) {
          if (timeoutTask.caughtWhileCancelling != null) {
            throw timeoutTask.caughtWhileCancelling;
          }
 
          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
         
          timeoutTask = null;
        }
       
        if (sqlEx != null) {
          throw new java.sql.BatchUpdateException(sqlEx
              .getMessage(), sqlEx.getSQLState(), sqlEx
              .getErrorCode(), updateCounts);
        }
       
        return (updateCounts != null) ? updateCounts : new int[0];
      } finally {
        if (timeoutTask != null) {
          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
        }
       
        resetCancelledState();
       
        try {
          if (batchStmt != null) {
            batchStmt.close();
          }
        } finally {
          if (!multiQueriesEnabled) {
            locallyScopedConn.getIO().disableMultiQueries();
          }
        }
      }
    }
  }
 
  protected int processMultiCountsAndKeys(
      StatementImpl batchedStatement,
      int updateCountCounter, int[] updateCounts) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      updateCounts[updateCountCounter++] = batchedStatement.getUpdateCount();
     
      boolean doGenKeys = this.batchedGeneratedKeys != null;
 
      byte[][] row = null;
     
      if (doGenKeys) {
        long generatedKey = batchedStatement.getLastInsertID();
     
        row = new byte[1][];
        row[0] = StringUtils.getBytes(Long.toString(generatedKey));
        this.batchedGeneratedKeys.add(new ByteArrayRow(row, getExceptionInterceptor()));
      }
 
      while (batchedStatement.getMoreResults()
          || batchedStatement.getUpdateCount() != -1) {
        updateCounts[updateCountCounter++] = batchedStatement.getUpdateCount();
       
        if (doGenKeys) {
          long generatedKey = batchedStatement.getLastInsertID();
         
          row = new byte[1][];
          row[0] = StringUtils.getBytes(Long.toString(generatedKey));
          this.batchedGeneratedKeys.add(new ByteArrayRow(row, getExceptionInterceptor()));
        }
      }
     
      return updateCountCounter;
    }
  }
 
  protected SQLException handleExceptionForBatch(int endOfBatchIndex,
      int numValuesPerBatch, int[] updateCounts, SQLException ex)
      throws BatchUpdateException {
    SQLException sqlEx;
 
    for (int j = endOfBatchIndex; j > endOfBatchIndex - numValuesPerBatch; j--) {
      updateCounts[j] = EXECUTE_FAILED;
    }

    if (this.continueBatchOnError &&
        !(ex instanceof MySQLTimeoutException) &&
        !(ex instanceof MySQLStatementCancelledException) &&
        !hasDeadlockOrTimeoutRolledBackTx(ex)) {
      sqlEx = ex;
    } else {
      int[] newUpdateCounts = new int[endOfBatchIndex];
      System.arraycopy(updateCounts, 0,
          newUpdateCounts, 0, endOfBatchIndex);

      BatchUpdateException batchException = new BatchUpdateException(ex
          .getMessage(), ex.getSQLState(), ex
          .getErrorCode(), newUpdateCounts);
      batchException.initCause(ex);
      throw batchException;
    }
   
    return sqlEx;
  }
 
  /**
   * Execute a SQL statement that retruns a single ResultSet
   *
   * @param sql
   *            typically a static SQL SELECT statement
   *
   * @return a ResulSet that contains the data produced by the query
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public java.sql.ResultSet executeQuery(String sql)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      MySQLConnection locallyScopedConn = this.connection;
     
      this.retrieveGeneratedKeys = false;
     
      resetCancelledState();

      checkNullOrEmptyQuery(sql);

      boolean doStreaming = createStreamingResultSet();

      // Adjust net_write_timeout to a higher value if we're
      // streaming result sets. More often than not, someone runs into
      // an issue where they blow net_write_timeout when using this
      // feature, and if they're willing to hold a result set open
      // for 30 seconds or more, one more round-trip isn't going to hurt
      //
      // This is reset by RowDataDynamic.close().

      if (doStreaming
          && this.connection.getNetTimeoutForStreamingResults() > 0) {
        executeSimpleNonQuery(locallyScopedConn, "SET net_write_timeout="
            + this.connection.getNetTimeoutForStreamingResults());
      }

      if (this.doEscapeProcessing) {
        Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
            locallyScopedConn.serverSupportsConvertFn(), this.connection);

        if (escapedSqlResult instanceof String) {
          sql = (String) escapedSqlResult;
        } else {
          sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
        }
      }

      char firstStatementChar = StringUtils.firstNonWsCharUc(sql,
          findStartOfStatement(sql));

      if (sql.charAt(0) == '/') {
        if (sql.startsWith(PING_MARKER)) {
          doPingInstead();
       
          return this.results;
        }
      }
     
      checkForDml(sql, firstStatementChar);

      if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) {
        if (this.results != null) {
          this.results.realClose(false);
        }
        if (this.generatedKeysResults != null) {
          this.generatedKeysResults.realClose(false);
        }
        closeAllOpenResults();
      }

      CachedResultSetMetaData cachedMetaData = null;

      // If there isn't a limit clause in the SQL
      // then limit the number of rows to return in
      // an efficient manner. Only do this if
      // setMaxRows() hasn't been used on any Statements
      // generated from the current Connection (saves
      // a query, and network traffic).

      if (useServerFetch()) {
        this.results = createResultSetUsingServerFetch(sql);

        return this.results;
      }

      CancelTask timeoutTask = null;

      String oldCatalog = null;

      try {
        if (locallyScopedConn.getEnableQueryTimeouts() &&
            this.timeoutInMillis != 0
            && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
          timeoutTask = new CancelTask(this);
          locallyScopedConn.getCancelTimer().schedule(timeoutTask,
              this.timeoutInMillis);
        }

        if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
          oldCatalog = locallyScopedConn.getCatalog();
          locallyScopedConn.setCatalog(this.currentCatalog);
        }

        //
        // Check if we have cached metadata for this query...
        //

        Field[] cachedFields = null;

        if (locallyScopedConn.getCacheResultSetMetadata()) {
          cachedMetaData = locallyScopedConn.getCachedMetaData(sql);

          if (cachedMetaData != null) {
            cachedFields = cachedMetaData.fields;
          }
        }

        if (locallyScopedConn.useMaxRows()) {
          // We need to execute this all together
          // So synchronize on the Connection's mutex (because
          // even queries going through there synchronize
          // on the connection
          if (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1) { //$NON-NLS-1$
            this.results = locallyScopedConn.execSQL(this, sql,
                this.maxRows, null, this.resultSetType,
                this.resultSetConcurrency,
                doStreaming,
                this.currentCatalog, cachedFields);
          } else {
            if (this.maxRows <= 0) {
              executeSimpleNonQuery(locallyScopedConn,
                  "SET SQL_SELECT_LIMIT=DEFAULT");
            } else {
              executeSimpleNonQuery(locallyScopedConn,
                  "SET SQL_SELECT_LIMIT=" + this.maxRows);
            }

            statementBegins();
           
            this.results = locallyScopedConn.execSQL(this, sql, -1,
                null, this.resultSetType,
                this.resultSetConcurrency,
                doStreaming,
                this.currentCatalog, cachedFields);

            if (oldCatalog != null) {
              locallyScopedConn.setCatalog(oldCatalog);
            }
          }
        } else {
          statementBegins();
         
          this.results = locallyScopedConn.execSQL(this, sql, -1, null,
              this.resultSetType, this.resultSetConcurrency,
              doStreaming,
              this.currentCatalog, cachedFields);
        }

        if (timeoutTask != null) {
          if (timeoutTask.caughtWhileCancelling != null) {
            throw timeoutTask.caughtWhileCancelling;
          }

          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
         
          timeoutTask = null;
        }

        synchronized (this.cancelTimeoutMutex) {
          if (this.wasCancelled) {
            SQLException cause = null;
           
            if (this.wasCancelledByTimeout) {
              cause = new MySQLTimeoutException();
            } else {
              cause = new MySQLStatementCancelledException();
            }
           
            resetCancelledState();
           
            throw cause;
          }
        }
      } finally {
        this.statementExecuting.set(false);
       
        if (timeoutTask != null) {
          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
        }

        if (oldCatalog != null) {
          locallyScopedConn.setCatalog(oldCatalog);
        }
      }

      this.lastInsertId = this.results.getUpdateID();

      if (cachedMetaData != null) {
        locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData,
            this.results);
      } else {
        if (this.connection.getCacheResultSetMetadata()) {
          locallyScopedConn.initializeResultsMetadataFromCache(sql,
              null /* will be created */, this.results);
        }
      }

      return this.results;
    }
  }

  protected void doPingInstead() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.pingTarget != null) {
        this.pingTarget.doPing();
      } else {
        this.connection.ping();
      }
 
      ResultSetInternalMethods fakeSelectOneResultSet = generatePingResultSet();
      this.results = fakeSelectOneResultSet;
    }
  }

  protected ResultSetInternalMethods generatePingResultSet() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      Field[] fields = { new Field(null, "1", Types.BIGINT, 1) };
      ArrayList<ResultSetRow> rows = new ArrayList<ResultSetRow>();
      byte[] colVal = new byte[] { (byte) '1' };
 
      rows.add(new ByteArrayRow(new byte[][] { colVal }, getExceptionInterceptor()));
 
      return (ResultSetInternalMethods) DatabaseMetaData.buildResultSet(fields, rows,
          this.connection);
    }
  }
 
  protected void executeSimpleNonQuery(MySQLConnection c, String nonQuery)
      throws SQLException {
    c.execSQL(this, nonQuery,
        -1, null, ResultSet.TYPE_FORWARD_ONLY,
        ResultSet.CONCUR_READ_ONLY, false, this.currentCatalog,
        null, false).close();
  }

  /**
   * Execute a SQL INSERT, UPDATE or DELETE statement. In addition SQL
   * statements that return nothing such as SQL DDL statements can be executed
   * Any IDs generated for AUTO_INCREMENT fields can be retrieved by casting
   * this Statement to org.gjt.mm.mysql.Statement and calling the
   * getLastInsertID() method.
   *
   * @param sql
   *            a SQL statement
   *
   * @return either a row count, or 0 for SQL commands
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public int executeUpdate(String sql) throws SQLException {
    return executeUpdate(sql, false, false);
  }

  protected int executeUpdate(String sql, boolean isBatch, boolean returnGeneratedKeys)
    throws SQLException {
   
    synchronized (checkClosed().getConnectionMutex()) {
      MySQLConnection locallyScopedConn = this.connection;
 
      char firstStatementChar = StringUtils.firstAlphaCharUc(sql,
          findStartOfStatement(sql));
 
      ResultSetInternalMethods rs = null;
   
      this.retrieveGeneratedKeys = returnGeneratedKeys;
     
      resetCancelledState();

      checkNullOrEmptyQuery(sql);

      if (this.doEscapeProcessing) {
        Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
            this.connection.serverSupportsConvertFn(), this.connection);

        if (escapedSqlResult instanceof String) {
          sql = (String) escapedSqlResult;
        } else {
          sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
        }
      }

      if (locallyScopedConn.isReadOnly(false)) {
        throw SQLError.createSQLException(Messages
            .getString("Statement.42") //$NON-NLS-1$
            + Messages.getString("Statement.43"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }

      if (StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) { //$NON-NLS-1$
        throw SQLError.createSQLException(Messages
            .getString("Statement.46"), //$NON-NLS-1$
        "01S03", getExceptionInterceptor()); //$NON-NLS-1$
      }

      if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) {
        if (this.results != null) {
          this.results.realClose(false);
        }
        if (this.generatedKeysResults != null) {
          this.generatedKeysResults.realClose(false);
        }
        closeAllOpenResults();
      }

      // The checking and changing of catalogs
      // must happen in sequence, so synchronize
      // on the same mutex that _conn is using

      CancelTask timeoutTask = null;

      String oldCatalog = null;

      try {
        if (locallyScopedConn.getEnableQueryTimeouts() &&
            this.timeoutInMillis != 0
            && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
          timeoutTask = new CancelTask(this);
          locallyScopedConn.getCancelTimer().schedule(timeoutTask,
              this.timeoutInMillis);
        }

        if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
          oldCatalog = locallyScopedConn.getCatalog();
          locallyScopedConn.setCatalog(this.currentCatalog);
        }

        //
        // Only apply max_rows to selects
        //
        if (locallyScopedConn.useMaxRows()) {
          executeSimpleNonQuery(locallyScopedConn,
              "SET SQL_SELECT_LIMIT=DEFAULT");
        }

        statementBegins();
       
        rs = locallyScopedConn.execSQL(this, sql, -1, null,
            java.sql.ResultSet.TYPE_FORWARD_ONLY,
            java.sql.ResultSet.CONCUR_READ_ONLY, false,
            this.currentCatalog,
            null /* force read of field info on DML */,
            isBatch);

        if (timeoutTask != null) {
          if (timeoutTask.caughtWhileCancelling != null) {
            throw timeoutTask.caughtWhileCancelling;
          }

          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
         
          timeoutTask = null;
        }

        synchronized (this.cancelTimeoutMutex) {
          if (this.wasCancelled) {
            SQLException cause = null;
           
            if (this.wasCancelledByTimeout) {
              cause = new MySQLTimeoutException();
            } else {
              cause = new MySQLStatementCancelledException();
            }
           
            resetCancelledState();
           
            throw cause;
          }
        }
      } finally {
        if (timeoutTask != null) {
          timeoutTask.cancel();
         
          locallyScopedConn.getCancelTimer().purge();
        }

        if (oldCatalog != null) {
          locallyScopedConn.setCatalog(oldCatalog);
        }
       
        if (!isBatch) {
          this.statementExecuting.set(false);
        }
      }
     
      this.results = rs;

      rs.setFirstCharOfQuery(firstStatementChar);

      this.updateCount = rs.getUpdateCount();

      int truncatedUpdateCount = 0;

      if (this.updateCount > Integer.MAX_VALUE) {
        truncatedUpdateCount = Integer.MAX_VALUE;
      } else {
        truncatedUpdateCount = (int) this.updateCount;
      }

      this.lastInsertId = rs.getUpdateID();

      return truncatedUpdateCount;
    }
  }


  /**
   * @see StatementImpl#executeUpdate(String, int)
   */
  public int executeUpdate(String sql, int returnGeneratedKeys)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS) {
        MySQLConnection locallyScopedConn = this.connection;
     
        // If this is a 'REPLACE' query, we need to be able to parse
        // the 'info' message returned from the server to determine
        // the actual number of keys generated.
        boolean readInfoMsgState = locallyScopedConn
            .isReadInfoMsgEnabled();
        locallyScopedConn.setReadInfoMsgEnabled(true);

        try {
          return executeUpdate(sql, false, true);
        } finally {
          locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState);
        }
      }
     
      return executeUpdate(sql);
    }
  }

  /**
   * @see StatementImpl#executeUpdate(String, int[])
   */
  public int executeUpdate(String sql, int[] generatedKeyIndices)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) {
        checkClosed();
 
        MySQLConnection locallyScopedConn = this.connection;
       
        // If this is a 'REPLACE' query, we need to be able to parse
        // the 'info' message returned from the server to determine
        // the actual number of keys generated.
        boolean readInfoMsgState = locallyScopedConn
            .isReadInfoMsgEnabled();
        locallyScopedConn.setReadInfoMsgEnabled(true);

        try {
          return executeUpdate(sql, false, true);
        } finally {
          locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState);
        }
      }
     
      return executeUpdate(sql);
    }
  }

  /**
   * @see StatementImpl#executeUpdate(String, String[])
   */
  public int executeUpdate(String sql, String[] generatedKeyNames)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) {
        MySQLConnection locallyScopedConn = this.connection;
        // If this is a 'REPLACE' query, we need to be able to parse
        // the 'info' message returned from the server to determine
        // the actual number of keys generated.
        boolean readInfoMsgState = this.connection
            .isReadInfoMsgEnabled();
        locallyScopedConn.setReadInfoMsgEnabled(true);

        try {
          return executeUpdate(sql, false, true);
        } finally {
          locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState);
        }
      } 

      return executeUpdate(sql);
    }
  }

  /**
   * Optimization to only use one calendar per-session, or calculate it for
   * each call, depending on user configuration
   */
  protected Calendar getCalendarInstanceForSessionOrNew() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.connection != null) {
        return this.connection.getCalendarInstanceForSessionOrNew();
      }
      // punt, no connection around
      return new GregorianCalendar();
    }
  }

  /**
   * JDBC 2.0 Return the Connection that produced the Statement.
   *
   * @return the Connection that produced the Statement
   *
   * @throws SQLException
   *             if an error occurs
   */
  public java.sql.Connection getConnection() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.connection;
    }
  }

  /**
   * JDBC 2.0 Determine the fetch direction.
   *
   * @return the default fetch direction
   *
   * @exception SQLException
   *                if a database-access error occurs
   */
  public int getFetchDirection() throws SQLException {
    return java.sql.ResultSet.FETCH_FORWARD;
  }

  /**
   * JDBC 2.0 Determine the default fetch size.
   *
   * @return the number of rows to fetch at a time
   *
   * @throws SQLException
   *             if an error occurs
   */
  public int getFetchSize() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.fetchSize;
    }
  }

  /**
   * DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  public java.sql.ResultSet getGeneratedKeys()
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (!this.retrieveGeneratedKeys) {
        throw SQLError.createSQLException(Messages.getString("Statement.GeneratedKeysNotRequested"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
     
      if (this.batchedGeneratedKeys == null) {
        if (lastQueryIsOnDupKeyUpdate) {
          return getGeneratedKeysInternal(1);
        }
        return getGeneratedKeysInternal();
      }
 
      Field[] fields = new Field[1];
      fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17); //$NON-NLS-1$ //$NON-NLS-2$
      fields[0].setConnection(this.connection);

      this.generatedKeysResults = com.mysql.jdbc.ResultSetImpl.getInstance(this.currentCatalog, fields,
          new RowDataStatic(this.batchedGeneratedKeys), this.connection,
          this, false);
     
      return this.generatedKeysResults;
    }
  }

  /*
   * Needed because there's no concept of super.super to get to this
   * implementation from ServerPreparedStatement when dealing with batched
   * updates.
   */
  protected java.sql.ResultSet getGeneratedKeysInternal()
      throws SQLException {
    int numKeys = getUpdateCount();
    return getGeneratedKeysInternal(numKeys);
  }

  protected java.sql.ResultSet getGeneratedKeysInternal(int numKeys)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      Field[] fields = new Field[1];
      fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17); //$NON-NLS-1$ //$NON-NLS-2$
      fields[0].setConnection(this.connection);
      fields[0].setUseOldNameMetadata(true);
 
      ArrayList<ResultSetRow> rowSet = new ArrayList<ResultSetRow>();
 
      long beginAt = getLastInsertID();
     
      if (beginAt < 0) { // looking at an UNSIGNED BIGINT that has overflowed
        fields[0].setUnsigned();
      }
 
      if (this.results != null) {
        String serverInfo = this.results.getServerInfo();
 
        //
        // Only parse server info messages for 'REPLACE'
        // queries
        //
        if ((numKeys > 0) && (this.results.getFirstCharOfQuery() == 'R')
            && (serverInfo != null) && (serverInfo.length() > 0)) {
          numKeys = getRecordCountFromInfo(serverInfo);
        }
 
        if ((beginAt != 0 /* BIGINT UNSIGNED can wrap the protocol representation */) && (numKeys > 0)) {
          for (int i = 0; i < numKeys; i++) {
            byte[][] row = new byte[1][];
            if (beginAt > 0) {
              row[0] = StringUtils.getBytes(Long.toString(beginAt));
            } else {
              byte[] asBytes = new byte[8];
              asBytes[7] = (byte) (beginAt & 0xff);
              asBytes[6] = (byte) (beginAt >>> 8);
              asBytes[5] = (byte) (beginAt >>> 16);
              asBytes[4] = (byte) (beginAt >>> 24);
              asBytes[3] = (byte) (beginAt >>> 32);
              asBytes[2] = (byte) (beginAt >>> 40);
              asBytes[1] = (byte) (beginAt >>> 48);
              asBytes[0] = (byte) (beginAt >>> 56);
             
              BigInteger val = new BigInteger(1, asBytes);
 
              row[0] = val.toString().getBytes();
            }
            rowSet.add(new ByteArrayRow(row, getExceptionInterceptor()));
            beginAt  += this.connection.getAutoIncrementIncrement();
          }
        }
      }
     
      com.mysql.jdbc.ResultSetImpl gkRs = com.mysql.jdbc.ResultSetImpl.getInstance(this.currentCatalog, fields,
          new RowDataStatic(rowSet), this.connection, this, false);
     
      this.openResults.add(gkRs);
     
      return gkRs;
    }
  }

  /**
   * Returns the id used when profiling
   *
   * @return the id used when profiling.
   */
  protected int getId() {
    return this.statementId;
  }

  /**
   * getLastInsertID returns the value of the auto_incremented key after an
   * executeQuery() or excute() call.
   *
   * <p>
   * This gets around the un-threadsafe behavior of "select LAST_INSERT_ID()"
   * which is tied to the Connection that created this Statement, and
   * therefore could have had many INSERTS performed before one gets a chance
   * to call "select LAST_INSERT_ID()".
   * </p>
   *
   * @return the last update ID.
   */
  public long getLastInsertID() {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        return this.lastInsertId;
      }
    } catch (SQLException e) {
      throw new RuntimeException(e); // evolve interface to throw SQLException
    }
  }

  /**
   * getLongUpdateCount returns the current result as an update count, if the
   * result is a ResultSet or there are no more results, -1 is returned. It
   * should only be called once per result.
   *
   * <p>
   * This method returns longs as MySQL server versions newer than 3.22.4
   * return 64-bit values for update counts
   * </p>
   *
   * @return the current update count.
   */
  public long getLongUpdateCount() {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        if (this.results == null) {
          return -1;
        }

        if (this.results.reallyResult()) {
          return -1;
        }

        return this.updateCount;
      }
    } catch (SQLException e) {
      throw new RuntimeException(e); // evolve interface to throw SQLException
    }
  }

  /**
   * The maxFieldSize limit (in bytes) is the maximum amount of data returned
   * for any column value; it only applies to BINARY, VARBINARY,
   * LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR columns. If the limit is
   * exceeded, the excess data is silently discarded.
   *
   * @return the current max column size limit; zero means unlimited
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public int getMaxFieldSize() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.maxFieldSize;
    }
  }

  /**
   * The maxRows limit is set to limit the number of rows that any ResultSet
   * can contain. If the limit is exceeded, the excess rows are silently
   * dropped.
   *
   * @return the current maximum row limit; zero means unlimited
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public int getMaxRows() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.maxRows <= 0) {
        return 0;
      }

      return this.maxRows;
    }
  }

  /**
   * getMoreResults moves to a Statement's next result. If it returns true,
   * this result is a ResulSet.
   *
   * @return true if the next ResultSet is valid
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public boolean getMoreResults() throws SQLException {
    return getMoreResults(CLOSE_CURRENT_RESULT);
  }

  /**
   * @see StatementImpl#getMoreResults(int)
   */
  public boolean getMoreResults(int current) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.results == null) {
        return false;
      }
 
      boolean streamingMode = createStreamingResultSet();
     
      if (streamingMode) {
        if (this.results.reallyResult()) {
          while (this.results.next()); // need to drain remaining rows to get to server status
                       // which tells us whether more results actually exist or not
        }
      }
     
      ResultSetInternalMethods nextResultSet = this.results.getNextResultSet();
 
      switch (current) {
      case java.sql.Statement.CLOSE_CURRENT_RESULT:
 
        if (this.results != null) {
          if (!streamingMode) {
            this.results.close();
          }
         
          this.results.clearNextResult();
        }
 
        break;
 
      case java.sql.Statement.CLOSE_ALL_RESULTS:
 
        if (this.results != null) {
          if (!streamingMode) {
            this.results.close();
          }
         
          this.results.clearNextResult();
        }
 
        closeAllOpenResults();
 
        break;
 
      case java.sql.Statement.KEEP_CURRENT_RESULT:
        if (!this.connection.getDontTrackOpenResources()) {
          this.openResults.add(this.results);
        }
 
        this.results.clearNextResult(); // nobody besides us should
        // ever need this value...
        break;
 
      default:
        throw SQLError.createSQLException(Messages
            .getString("Statement.19"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }
 
      this.results = nextResultSet;
 
      if (this.results == null) {
        this.updateCount = -1;
        this.lastInsertId = -1;
      } else if (this.results.reallyResult()) {
        this.updateCount = -1;
        this.lastInsertId = -1;
      } else {
        this.updateCount = this.results.getUpdateCount();
        this.lastInsertId = this.results.getUpdateID();
      }
 
      return ((this.results != null) && this.results.reallyResult()) ? true
          : false;
    }
  }

  /**
   * The queryTimeout limit is the number of seconds the driver will wait for
   * a Statement to execute. If the limit is exceeded, a SQLException is
   * thrown.
   *
   * @return the current query timeout limit in seconds; 0 = unlimited
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public int getQueryTimeout() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.timeoutInMillis / 1000;
    }
  }

  /**
   * Parses actual record count from 'info' message
   *
   * @param serverInfo
   *            DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   */
  private int getRecordCountFromInfo(String serverInfo) {
    StringBuffer recordsBuf = new StringBuffer();
    int recordsCount = 0;
    int duplicatesCount = 0;

    char c = (char) 0;

    int length = serverInfo.length();
    int i = 0;

    for (; i < length; i++) {
      c = serverInfo.charAt(i);

      if (Character.isDigit(c)) {
        break;
      }
    }

    recordsBuf.append(c);
    i++;

    for (; i < length; i++) {
      c = serverInfo.charAt(i);

      if (!Character.isDigit(c)) {
        break;
      }

      recordsBuf.append(c);
    }

    recordsCount = Integer.parseInt(recordsBuf.toString());

    StringBuffer duplicatesBuf = new StringBuffer();

    for (; i < length; i++) {
      c = serverInfo.charAt(i);

      if (Character.isDigit(c)) {
        break;
      }
    }

    duplicatesBuf.append(c);
    i++;

    for (; i < length; i++) {
      c = serverInfo.charAt(i);

      if (!Character.isDigit(c)) {
        break;
      }

      duplicatesBuf.append(c);
    }

    duplicatesCount = Integer.parseInt(duplicatesBuf.toString());

    return recordsCount - duplicatesCount;
  }

  /**
   * getResultSet returns the current result as a ResultSet. It should only be
   * called once per result.
   *
   * @return the current result set; null if there are no more
   *
   * @exception SQLException
   *                if a database access error occurs (why?)
   */
  public java.sql.ResultSet getResultSet() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return ((this.results != null) && this.results.reallyResult()) ? (java.sql.ResultSet) this.results
          : null;
    }
  }

  /**
   * JDBC 2.0 Determine the result set concurrency.
   *
   * @return CONCUR_UPDATABLE or CONCUR_READONLY
   *
   * @throws SQLException
   *             if an error occurs
   */
  public int getResultSetConcurrency() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.resultSetConcurrency;
    }
  }

  /**
   * @see StatementImpl#getResultSetHoldability()
   */
  public int getResultSetHoldability() throws SQLException {
    return java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
  }

  protected ResultSetInternalMethods getResultSetInternal() {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        return this.results;
      }
    } catch (SQLException e) {
      return this.results; // you end up with the same thing as before, you'll get exception when actually trying to use it
    }
  }

  /**
   * JDBC 2.0 Determine the result set type.
   *
   * @return the ResultSet type (SCROLL_SENSITIVE or SCROLL_INSENSITIVE)
   *
   * @throws SQLException
   *             if an error occurs.
   */
  public int getResultSetType() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.resultSetType;
    }
  }

  /**
   * getUpdateCount returns the current result as an update count, if the
   * result is a ResultSet or there are no more results, -1 is returned. It
   * should only be called once per result.
   *
   * @return the current result as an update count.
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public int getUpdateCount() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.results == null) {
        return -1;
      }
 
      if (this.results.reallyResult()) {
        return -1;
      }
 
      int truncatedUpdateCount = 0;
 
      if (this.results.getUpdateCount() > Integer.MAX_VALUE) {
        truncatedUpdateCount = Integer.MAX_VALUE;
      } else {
        truncatedUpdateCount = (int) this.results.getUpdateCount();
      }
 
      return truncatedUpdateCount;
    }
  }

  /**
   * The first warning reported by calls on this Statement is returned. A
   * Statement's execute methods clear its java.sql.SQLWarning chain.
   * Subsequent Statement warnings will be chained to this
   * java.sql.SQLWarning.
   *
   * <p>
   * The Warning chain is automatically cleared each time a statement is
   * (re)executed.
   * </p>
   *
   * <p>
   * <B>Note:</B> If you are processing a ResultSet then any warnings
   * associated with ResultSet reads will be chained on the ResultSet object.
   * </p>
   *
   * @return the first java.sql.SQLWarning or null
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public java.sql.SQLWarning getWarnings() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (this.clearWarningsCalled) {
        return null;
      }
     
      if (this.connection.versionMeetsMinimum(4, 1, 0)) {
        SQLWarning pendingWarningsFromServer = SQLError
            .convertShowWarningsToSQLWarnings(this.connection);
 
        if (this.warningChain != null) {
          this.warningChain.setNextWarning(pendingWarningsFromServer);
        } else {
          this.warningChain = pendingWarningsFromServer;
        }
 
        return this.warningChain;
      }
 
      return this.warningChain;
    }
  }

  /**
   * Closes this statement, and frees resources.
   *
   * @param calledExplicitly
   *            was this called from close()?
   *
   * @throws SQLException
   *             if an error occurs
   */
  protected void realClose(boolean calledExplicitly, boolean closeOpenResults)
      throws SQLException {
    MySQLConnection locallyScopedConn;
   
    try {
      locallyScopedConn = checkClosed();
    } catch (SQLException sqlEx) {
      return; // already closed
    }
   
    synchronized (locallyScopedConn.getConnectionMutex()) {
 
      if (this.useUsageAdvisor) {
        if (!calledExplicitly) {
          String message = Messages.getString("Statement.63") //$NON-NLS-1$
              + Messages.getString("Statement.64"); //$NON-NLS-1$
 
          this.eventSink.consumeEvent(new ProfilerEvent(
              ProfilerEvent.TYPE_WARN,
              "", //$NON-NLS-1$
              this.currentCatalog, this.connectionId, this.getId(),
              -1, System.currentTimeMillis(), 0,
              Constants.MILLIS_I18N, null, this.pointOfOrigin,
              message));
        }
      }
 
      if (closeOpenResults) {
        closeOpenResults = !this.holdResultsOpenOverClose;
      }
     
      if (closeOpenResults) {
        if (this.results != null) {
         
          try {
            this.results.close();
          } catch (Exception ex) {
            ;
          }
        }

        if (this.generatedKeysResults != null) {
         
          try {
            this.generatedKeysResults.close();
          } catch (Exception ex) {
            ;
          }
        }
       
        closeAllOpenResults();
      }
 
      if (this.connection != null) {
        if (this.maxRowsChanged) {
          this.connection.unsetMaxRows(this);
        }
 
        if (!this.connection.getDontTrackOpenResources()) {
          this.connection.unregisterStatement(this);
        }
      }
 
      this.isClosed = true;
 
      this.results = null;
      this.generatedKeysResults = null;
      this.connection = null;
      this.warningChain = null;
      this.openResults = null;
      this.batchedGeneratedKeys = null;
      this.localInfileInputStream = null;
      this.pingTarget = null;
    }
  }

  /**
   * setCursorName defines the SQL cursor name that will be used by subsequent
   * execute methods. This name can then be used in SQL positioned
   * update/delete statements to identify the current row in the ResultSet
   * generated by this statement. If a database doesn't support positioned
   * update/delete, this method is a no-op.
   *
   * <p>
   * <b>Note:</b> This MySQL driver does not support cursors.
   * </p>
   *
   * @param name
   *            the new cursor name
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public void setCursorName(String name) throws SQLException {
    // No-op
  }

  /**
   * If escape scanning is on (the default), the driver will do escape
   * substitution before sending the SQL to the database.
   *
   * @param enable
   *            true to enable; false to disable
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public void setEscapeProcessing(boolean enable)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      this.doEscapeProcessing = enable;
    }
  }

  /**
   * JDBC 2.0 Give a hint as to the direction in which the rows in a result
   * set will be processed. The hint applies only to result sets created using
   * this Statement object. The default value is ResultSet.FETCH_FORWARD.
   *
   * @param direction
   *            the initial direction for processing rows
   *
   * @exception SQLException
   *                if a database-access error occurs or direction is not one
   *                of ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or
   *                ResultSet.FETCH_UNKNOWN
   */
  public void setFetchDirection(int direction) throws SQLException {
    switch (direction) {
    case java.sql.ResultSet.FETCH_FORWARD:
    case java.sql.ResultSet.FETCH_REVERSE:
    case java.sql.ResultSet.FETCH_UNKNOWN:
      break;

    default:
      throw SQLError.createSQLException(
          Messages.getString("Statement.5"), //$NON-NLS-1$
          SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
    }
  }

  /**
   * JDBC 2.0 Give the JDBC driver a hint as to the number of rows that should
   * be fetched from the database when more rows are needed. The number of
   * rows specified only affects result sets created using this statement. If
   * the value specified is zero, then the hint is ignored. The default value
   * is zero.
   *
   * @param rows
   *            the number of rows to fetch
   *
   * @exception SQLException
   *                if a database-access error occurs, or the condition 0
   *                &lt;= rows &lt;= this.getMaxRows() is not satisfied.
   */
  public void setFetchSize(int rows) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (((rows < 0) && (rows != Integer.MIN_VALUE))
          || ((this.maxRows != 0) && (this.maxRows != -1) && (rows > this
              .getMaxRows()))) {
        throw SQLError.createSQLException(
            Messages.getString("Statement.7"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$
      }
 
      this.fetchSize = rows;
    }
  }

  public void setHoldResultsOpenOverClose(boolean holdResultsOpenOverClose) {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        this.holdResultsOpenOverClose = holdResultsOpenOverClose;
      }
    } catch (SQLException e) {
      // FIXME: can't break interface at this point
    }
  }

  /**
   * Sets the maxFieldSize
   *
   * @param max
   *            the new max column size limit; zero means unlimited
   *
   * @exception SQLException
   *                if size exceeds buffer size
   */
  public void setMaxFieldSize(int max) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (max < 0) {
        throw SQLError.createSQLException(Messages
            .getString("Statement.11"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }
 
      int maxBuf = (this.connection != null) ? this.connection
          .getMaxAllowedPacket() : MysqlIO.getMaxBuf();
 
      if (max > maxBuf) {
        throw SQLError.createSQLException(Messages.getString(
            "Statement.13", //$NON-NLS-1$
            new Object[] { Long.valueOf(maxBuf) }), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }
 
      this.maxFieldSize = max;
    }
  }

  /**
   * Set the maximum number of rows
   *
   * @param max
   *            the new max rows limit; zero means unlimited
   *
   * @exception SQLException
   *                if a database access error occurs
   *
   * @see getMaxRows
   */
  public void setMaxRows(int max) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if ((max > MysqlDefs.MAX_ROWS) || (max < 0)) {
        throw SQLError
            .createSQLException(
                Messages.getString("Statement.15") + max //$NON-NLS-1$
                    + " > " //$NON-NLS-1$ //$NON-NLS-2$
                    + MysqlDefs.MAX_ROWS + ".", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$
      }
 
      if (max == 0) {
        max = -1;
      }
 
      this.maxRows = max;
      this.maxRowsChanged = true;
 
      if (this.maxRows == -1) {
        this.connection.unsetMaxRows(this);
        this.maxRowsChanged = false;
      } else {
        // Most people don't use setMaxRows()
        // so don't penalize them
        // with the extra query it takes
        // to do it efficiently unless we need
        // to.
        this.connection.maxRowsChanged(this);
      }
    }
  }

  /**
   * Sets the queryTimeout limit
   *
   * @param seconds -
   *            the new query timeout limit in seconds
   *
   * @exception SQLException
   *                if a database access error occurs
   */
  public void setQueryTimeout(int seconds) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (seconds < 0) {
        throw SQLError.createSQLException(Messages
            .getString("Statement.21"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
      }
 
      this.timeoutInMillis = seconds * 1000;
    }
  }

  /**
   * Sets the concurrency for result sets generated by this statement
   *
   * @param concurrencyFlag
   *            DOCUMENT ME!
   */
  void setResultSetConcurrency(int concurrencyFlag) {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        this.resultSetConcurrency = concurrencyFlag;
      }
    } catch (SQLException e) {
      // FIXME: Can't break interface atm, we'll get the exception later when
      // you try and do something useful with a closed statement...
    }
  }

  /**
   * Sets the result set type for result sets generated by this statement
   *
   * @param typeFlag
   *            DOCUMENT ME!
   */
  void setResultSetType(int typeFlag) {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        this.resultSetType = typeFlag;
      }
    } catch (SQLException e) {
      // FIXME: Can't break interface atm, we'll get the exception later when
      // you try and do something useful with a closed statement...
    }
  }

  protected void getBatchedGeneratedKeys(java.sql.Statement batchedStatement) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.retrieveGeneratedKeys) {
        java.sql.ResultSet rs = null;
 
        try {
          rs = batchedStatement.getGeneratedKeys();
 
          while (rs.next()) {
            this.batchedGeneratedKeys
                .add(new ByteArrayRow(new byte[][] { rs.getBytes(1) }, getExceptionInterceptor()));
          }
        } finally {
          if (rs != null) {
            rs.close();
          }
        }
      }
    }
  }
 
  protected void getBatchedGeneratedKeys(int maxKeys) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.retrieveGeneratedKeys) {
        java.sql.ResultSet rs = null;
 
        try {
          if (maxKeys == 0)
            rs = getGeneratedKeysInternal();
          else
            rs = getGeneratedKeysInternal(maxKeys);
 
          while (rs.next()) {
            this.batchedGeneratedKeys
                .add(new ByteArrayRow(new byte[][] { rs.getBytes(1) }, getExceptionInterceptor()));
          }
        } finally {
          if (rs != null) {
            rs.close();
          }
        }
      }
    }
  }

  /**
   * @return
   */
  private boolean useServerFetch() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return this.connection.isCursorFetchEnabled() && this.fetchSize > 0
          && this.resultSetConcurrency == ResultSet.CONCUR_READ_ONLY
          && this.resultSetType == ResultSet.TYPE_FORWARD_ONLY;
    }
  }

  public boolean isClosed() throws SQLException {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        return this.isClosed;
      }
    } catch (SQLException sqlEx) {
      if (SQLError.SQL_STATE_CONNECTION_NOT_OPEN.equals(sqlEx.getSQLState())) {
        return true;
      }
     
      throw sqlEx;
    }
  }

  private boolean isPoolable = true;

  public boolean isPoolable() throws SQLException {
    return this.isPoolable;
  }

  public void setPoolable(boolean poolable) throws SQLException {
    this.isPoolable = poolable;
  }

  /**
     * Returns true if this either implements the interface argument or is directly or indirectly a wrapper
     * for an object that does. Returns false otherwise. If this implements the interface then return true,
     * else if this is a wrapper then return the result of recursively calling <code>isWrapperFor</code> on the wrapped
     * object. If this does not implement the interface and is not a wrapper, return false.
     * This method should be implemented as a low-cost operation compared to <code>unwrap</code> so that
     * callers can use this method to avoid expensive <code>unwrap</code> calls that may fail. If this method
     * returns true then calling <code>unwrap</code> with the same argument should succeed.
     *
     * @param interfaces a Class defining an interface.
     * @return true if this implements the interface or directly or indirectly wraps an object that does.
     * @throws java.sql.SQLException  if an error occurs while determining whether this is a wrapper
     * for an object with the given interface.
     * @since 1.6
     */
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    checkClosed();

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

    /**
     * Returns an object that implements the given interface to allow access to non-standard methods,
     * or standard methods not exposed by the proxy.
     * The result may be either the object found to implement the interface or a proxy for that object.
     * If the receiver implements the interface then that is the object. If the receiver is a wrapper
     * and the wrapped object implements the interface then that is the object. Otherwise the object is
     *  the result of calling <code>unwrap</code> recursively on the wrapped object. If the receiver is not a
     * wrapper and does not implement the interface, then an <code>SQLException</code> is thrown.
     *
     * @param iface A Class defining an interface that the result must implement.
     * @return an object that implements the interface. May be a proxy for the actual implementing object.
     * @throws java.sql.SQLException If no object found that implements the interface
     * @since 1.6
     */
  public Object unwrap(Class<?> iface) throws java.sql.SQLException {
      try {
        // This works for classes that aren't actually wrapping
        // anything
            return Util.cast(iface, this);
        } catch (ClassCastException cce) {
            throw SQLError.createSQLException("Unable to unwrap to " + iface.toString(),
                SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }
    }

  protected int findStartOfStatement(String sql) {
    int statementStartPos = 0;

    if (StringUtils.startsWithIgnoreCaseAndWs(sql, "/*")) {
      statementStartPos = sql.indexOf("*/");

      if (statementStartPos == -1) {
        statementStartPos = 0;
      } else {
        statementStartPos += 2;
      }
    } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "--")
      || StringUtils.startsWithIgnoreCaseAndWs(sql, "#")) {
      statementStartPos = sql.indexOf('\n');

      if (statementStartPos == -1) {
        statementStartPos = sql.indexOf('\r');

        if (statementStartPos == -1) {
          statementStartPos = 0;
        }
      }
    }

    return statementStartPos;
  }

  private InputStream localInfileInputStream;

  protected final boolean version5013OrNewer;

    public InputStream getLocalInfileInputStream() {
        return this.localInfileInputStream;
    }

    public void setLocalInfileInputStream(InputStream stream) {
        this.localInfileInputStream = stream;
    }
   
    public void setPingTarget(PingTarget pingTarget) {
    this.pingTarget = pingTarget;
  }
   
    public ExceptionInterceptor getExceptionInterceptor() {
      return this.exceptionInterceptor;
    }
 
  protected boolean containsOnDuplicateKeyInString(String sql) {
    return getOnDuplicateKeyLocation(sql) != -1;
  }
 
  protected int getOnDuplicateKeyLocation(String sql) {
    return StringUtils.indexOfIgnoreCaseRespectMarker(0,
        sql, "ON DUPLICATE KEY UPDATE ", "\"'`", "\"'`", !this.connection.isNoBackslashEscapesSet());
  }
 
  private boolean closeOnCompletion;
 
  public void closeOnCompletion() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      closeOnCompletion = true;
    }
  }
 
  public boolean isCloseOnCompletion() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return closeOnCompletion;
    }
  }
}
TOP

Related Classes of com.mysql.jdbc.StatementImpl$CancelTask

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.