Package com.mysql.jdbc

Source Code of com.mysql.jdbc.ServerPreparedStatement$BatchedBindValues

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

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

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

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

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



*/
package com.mysql.jdbc;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.ParameterMetaData;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

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

/**
* JDBC Interface for MySQL-4.1 and newer server-side PreparedStatements.
*
* @author Mark Matthews
* @version $Id: ServerPreparedStatement.java,v 1.1.2.2 2005/05/17 14:58:56
*          mmatthews Exp $
*/
public class ServerPreparedStatement extends PreparedStatement {
  private static final Constructor<?> JDBC_4_SPS_CTOR;
 
  static {
    if (Util.isJdbc4()) {
      try {
        JDBC_4_SPS_CTOR = Class.forName("com.mysql.jdbc.JDBC4ServerPreparedStatement")
        .getConstructor(
        new Class[] { MySQLConnection.class, String.class, String.class,
            Integer.TYPE, Integer.TYPE});
      } catch (SecurityException e) {
        throw new RuntimeException(e);
      } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
      }
    } else {
      JDBC_4_SPS_CTOR = null;
    }
  }
 
  protected static final int BLOB_STREAM_READ_BUF_SIZE = 8192;

  public static class BatchedBindValues {
    public BindValue[] batchedParameterValues;

    BatchedBindValues(BindValue[] paramVals) {
      int numParams = paramVals.length;

      this.batchedParameterValues = new BindValue[numParams];

      for (int i = 0; i < numParams; i++) {
        this.batchedParameterValues[i] = new BindValue(paramVals[i]);
      }
    }
  }

  public static class BindValue {

    public long boundBeforeExecutionNum = 0;
   
    public long bindLength; /* Default length of data */

    public int bufferType; /* buffer type */

    public double doubleBinding;

    public float floatBinding;

    public boolean isLongData; /* long data indicator */

    public boolean isNull; /* NULL indicator */

    public boolean isSet = false; /* has this parameter been set? */

    public long longBinding; /* all integral values are stored here */

    public Object value; /* The value to store */

    BindValue() {
    }

    BindValue(BindValue copyMe) {
      this.value = copyMe.value;
      this.isSet = copyMe.isSet;
      this.isLongData = copyMe.isLongData;
      this.isNull = copyMe.isNull;
      this.bufferType = copyMe.bufferType;
      this.bindLength = copyMe.bindLength;
      this.longBinding = copyMe.longBinding;
      this.floatBinding = copyMe.floatBinding;
      this.doubleBinding = copyMe.doubleBinding;
    }

    void reset() {
      this.isSet = false;
      this.value = null;
      this.isLongData = false;

      this.longBinding = 0L;
      this.floatBinding = 0;
      this.doubleBinding = 0D;
    }

    public String toString() {
      return toString(false);
    }

    public String toString(boolean quoteIfNeeded) {
      if (this.isLongData) {
        return "' STREAM DATA '";
      }

      switch (this.bufferType) {
      case MysqlDefs.FIELD_TYPE_TINY:
      case MysqlDefs.FIELD_TYPE_SHORT:
      case MysqlDefs.FIELD_TYPE_LONG:
      case MysqlDefs.FIELD_TYPE_LONGLONG:
        return String.valueOf(longBinding);
      case MysqlDefs.FIELD_TYPE_FLOAT:
        return String.valueOf(floatBinding);
      case MysqlDefs.FIELD_TYPE_DOUBLE:
        return String.valueOf(doubleBinding);
      case MysqlDefs.FIELD_TYPE_TIME:
      case MysqlDefs.FIELD_TYPE_DATE:
      case MysqlDefs.FIELD_TYPE_DATETIME:
      case MysqlDefs.FIELD_TYPE_TIMESTAMP:
      case MysqlDefs.FIELD_TYPE_VAR_STRING:
      case MysqlDefs.FIELD_TYPE_STRING:
      case MysqlDefs.FIELD_TYPE_VARCHAR:
        if (quoteIfNeeded) {
          return "'" + String.valueOf(value) + "'";
        }
        return String.valueOf(value);

      default:
        if (value instanceof byte[]) {
          return "byte data";
        }
        if (quoteIfNeeded) {
          return "'" + String.valueOf(value) + "'";
        }
        return String.valueOf(value);
      }
    }
   
    long getBoundLength() {
      if (isNull) {
        return 0;
      }
     
      if (isLongData) {
        return bindLength;
      }

      switch (bufferType) {

      case MysqlDefs.FIELD_TYPE_TINY:
        return 1;
      case MysqlDefs.FIELD_TYPE_SHORT:
        return 2;
      case MysqlDefs.FIELD_TYPE_LONG:
        return 4;
      case MysqlDefs.FIELD_TYPE_LONGLONG:
        return 8;
      case MysqlDefs.FIELD_TYPE_FLOAT:
        return 4;
      case MysqlDefs.FIELD_TYPE_DOUBLE:
        return 8;
      case MysqlDefs.FIELD_TYPE_TIME:
        return 9;
      case MysqlDefs.FIELD_TYPE_DATE:
        return 7;
      case MysqlDefs.FIELD_TYPE_DATETIME:
      case MysqlDefs.FIELD_TYPE_TIMESTAMP:
        return 11;
      case MysqlDefs.FIELD_TYPE_VAR_STRING:
      case MysqlDefs.FIELD_TYPE_STRING:
      case MysqlDefs.FIELD_TYPE_VARCHAR:
      case MysqlDefs.FIELD_TYPE_DECIMAL:
      case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
        if (value instanceof byte[]) {
          return ((byte[]) value).length;
        }
        return ((String) value).length();

      default:
        return 0;
      }
    }
  }

  /* 1 (length) + 2 (year) + 1 (month) + 1 (day) */
  //private static final byte MAX_DATE_REP_LENGTH = (byte) 5;

  /*
   * 1 (length) + 2 (year) + 1 (month) + 1 (day) + 1 (hour) + 1 (minute) + 1
   * (second) + 4 (microseconds)
   */
  //private static final byte MAX_DATETIME_REP_LENGTH = 12;

  /*
   * 1 (length) + 1 (is negative) + 4 (day count) + 1 (hour) + 1 (minute) + 1
   * (seconds) + 4 (microseconds)
   */
  //private static final byte MAX_TIME_REP_LENGTH = 13;
 
  private boolean hasOnDuplicateKeyUpdate = false;

  private void storeTime(Buffer intoBuf, Time tm) throws SQLException {
   
    intoBuf.ensureCapacity(9);
    intoBuf.writeByte((byte) 8); // length
    intoBuf.writeByte((byte) 0); // neg flag
    intoBuf.writeLong(0); // tm->day, not used

    Calendar sessionCalendar = getCalendarInstanceForSessionOrNew();
   
    synchronized (sessionCalendar) {
      java.util.Date oldTime = sessionCalendar.getTime();
      try {
        sessionCalendar.setTime(tm);
        intoBuf.writeByte((byte) sessionCalendar.get(Calendar.HOUR_OF_DAY));
        intoBuf.writeByte((byte) sessionCalendar.get(Calendar.MINUTE));
        intoBuf.writeByte((byte) sessionCalendar.get(Calendar.SECOND));

        // intoBuf.writeLongInt(0); // tm-second_part
      } finally {
        sessionCalendar.setTime(oldTime);
      }
    }
  }

  /**
   * Flag indicating whether or not the long parameters have been 'switched'
   * back to normal parameters. We can not execute() if clearParameters()
   * hasn't been called in this case.
   */
  private boolean detectedLongParameterSwitch = false;

  /**
   * The number of fields in the result set (if any) for this
   * PreparedStatement.
   */
  private int fieldCount;

  /** Has this prepared statement been marked invalid? */
  private boolean invalid = false;

  /** If this statement has been marked invalid, what was the reason? */
  private SQLException invalidationException;

  private Buffer outByteBuffer;

  /** Bind values for individual fields */
  private BindValue[] parameterBindings;

  /** Field-level metadata for parameters */
  private Field[] parameterFields;

  /** Field-level metadata for result sets. */
  private Field[] resultFields;

  /** Do we need to send/resend types to the server? */
  private boolean sendTypesToServer = false;

  /** The ID that the server uses to identify this PreparedStatement */
  private long serverStatementId;

  /** The type used for string bindings, changes from version-to-version */
  private int stringTypeCode = MysqlDefs.FIELD_TYPE_STRING;

  private boolean serverNeedsResetBeforeEachExecution;

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

  protected static ServerPreparedStatement getInstance(MySQLConnection conn,
      String sql, String catalog, int resultSetType,
      int resultSetConcurrency) throws SQLException {
    if (!Util.isJdbc4()) {
      return new ServerPreparedStatement(conn, sql, catalog,
          resultSetType, resultSetConcurrency);
    }

    try {
      return (ServerPreparedStatement) JDBC_4_SPS_CTOR.newInstance(new Object[] { conn,
          sql, catalog, Integer.valueOf(resultSetType),
          Integer.valueOf(resultSetConcurrency) });
    } catch (IllegalArgumentException e) {
      throw new SQLException(e.toString(), SQLError.SQL_STATE_GENERAL_ERROR);
    } catch (InstantiationException e) {
      throw new SQLException(e.toString(), SQLError.SQL_STATE_GENERAL_ERROR);
    } catch (IllegalAccessException e) {
      throw new SQLException(e.toString(), SQLError.SQL_STATE_GENERAL_ERROR);
    } catch (InvocationTargetException e) {
      Throwable target = e.getTargetException();
     
      if (target instanceof SQLException) {
        throw (SQLException)target;
      }
     
      throw new SQLException(target.toString(), SQLError.SQL_STATE_GENERAL_ERROR);
    }
  }

  /**
   * Creates a new ServerPreparedStatement object.
   *
   * @param conn
   *            the connection creating us.
   * @param sql
   *            the SQL containing the statement to prepare.
   * @param catalog
   *            the catalog in use when we were created.
   *
   * @throws SQLException
   *             If an error occurs
   */
   protected ServerPreparedStatement(MySQLConnection conn, String sql, String catalog,
      int resultSetType, int resultSetConcurrency)
      throws SQLException {
    super(conn, catalog);

    checkNullOrEmptyQuery(sql);

    this.hasOnDuplicateKeyUpdate = containsOnDuplicateKeyInString(sql);
   
    int startOfStatement = findStartOfStatement(sql);
   
    this.firstCharOfStmt = StringUtils.firstAlphaCharUc(sql, startOfStatement);
   
    if (this.connection.versionMeetsMinimum(5, 0, 0)) {
      this.serverNeedsResetBeforeEachExecution =
        !this.connection.versionMeetsMinimum(5, 0, 3);
    } else {
      this.serverNeedsResetBeforeEachExecution =
        !this.connection.versionMeetsMinimum(4, 1, 10);
    }
   
    this.useAutoSlowLog = this.connection.getAutoSlowLog();
    this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23);
    int lim_id = StringUtils.indexOfIgnoreCase(sql, "LIMIT");
    if (lim_id != -1) {
      boolean hasPreviosIdChar = false;
      boolean hasFollowingIdChar = false;
      if (lim_id > 0 && (
          sql.charAt(lim_id - 1) == '`' ||
          StringUtils.isValidIdChar(sql.charAt(lim_id - 1))
          )) {
        hasPreviosIdChar = true;
      }
      if (lim_id + 5 < sql.length() && (
          sql.charAt(lim_id + 5) == '`' ||
          StringUtils.isValidIdChar(sql.charAt(lim_id + 5))
          )) {
        hasFollowingIdChar = true;
      }
      if (!hasPreviosIdChar && !hasFollowingIdChar) {
        this.hasLimitClause = true;
      }
    } else {
      this.hasLimitClause = false;
    }
   
   
    String statementComment = this.connection.getStatementComment();

    this.originalSql = (statementComment == null) ? sql : "/* "
        + statementComment + " */ " + sql;

    if (this.connection.versionMeetsMinimum(4, 1, 2)) {
      this.stringTypeCode = MysqlDefs.FIELD_TYPE_VAR_STRING;
    } else {
      this.stringTypeCode = MysqlDefs.FIELD_TYPE_STRING;
    }

    try {
      serverPrepare(sql);
    } catch (SQLException sqlEx) {
      realClose(false, true);
      // don't wrap SQLExceptions
      throw sqlEx;
    } catch (Exception ex) {
      realClose(false, true);

      SQLException sqlEx = SQLError.createSQLException(ex.toString(),
          SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
      sqlEx.initCause(ex);
     
      throw sqlEx;
    }
   
    setResultSetType(resultSetType);
    setResultSetConcurrency(resultSetConcurrency);
   
    this.parameterTypes = new int[this.parameterCount];
  }

  /**
   * JDBC 2.0 Add a set of parameters to the batch.
   *
   * @exception SQLException
   *                if a database-access error occurs.
   *
   * @see StatementImpl#addBatch
   */
  public void addBatch() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (this.batchedArgs == null) {
        this.batchedArgs = new ArrayList<Object>();
      }
 
      this.batchedArgs.add(new BatchedBindValues(this.parameterBindings));
    }
  }

  public String asSql(boolean quoteStreamsAndUnknowns) throws SQLException {

    synchronized (checkClosed().getConnectionMutex()) {
     
      PreparedStatement pStmtForSub = null;
 
      try {
        pStmtForSub = PreparedStatement.getInstance(this.connection,
            this.originalSql, this.currentCatalog);
 
        int numParameters = pStmtForSub.parameterCount;
        int ourNumParameters = this.parameterCount;
 
        for (int i = 0; (i < numParameters) && (i < ourNumParameters); i++) {
          if (this.parameterBindings[i] != null) {
            if (this.parameterBindings[i].isNull) {
              pStmtForSub.setNull(i + 1, Types.NULL);
            } else {
              BindValue bindValue = this.parameterBindings[i];
 
              //
              // Handle primitives first
              //
              switch (bindValue.bufferType) {
 
              case MysqlDefs.FIELD_TYPE_TINY:
                pStmtForSub.setByte(i + 1, (byte)bindValue.longBinding);
                break;
              case MysqlDefs.FIELD_TYPE_SHORT:
                pStmtForSub.setShort(i + 1, (short)bindValue.longBinding);
                break;
              case MysqlDefs.FIELD_TYPE_LONG:
                pStmtForSub.setInt(i + 1, (int)bindValue.longBinding);
                break;
              case MysqlDefs.FIELD_TYPE_LONGLONG:
                pStmtForSub.setLong(i + 1, bindValue.longBinding);
                break;
              case MysqlDefs.FIELD_TYPE_FLOAT:
                pStmtForSub.setFloat(i + 1, bindValue.floatBinding);
                break;
              case MysqlDefs.FIELD_TYPE_DOUBLE:
                pStmtForSub.setDouble(i + 1,
                    bindValue.doubleBinding);
                break;
              default:
                pStmtForSub.setObject(i + 1,
                    this.parameterBindings[i].value);
                break;
              }
            }
          }
        }
 
        return pStmtForSub.asSql(quoteStreamsAndUnknowns);
      } finally {
        if (pStmtForSub != null) {
          try {
            pStmtForSub.close();
          } catch (SQLException sqlEx) {
            ; // ignore
          }
        }
      }
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.mysql.jdbc.Statement#checkClosed()
   */
  protected MySQLConnection checkClosed() throws SQLException {
    if (this.invalid) {
      throw this.invalidationException;
    }

    return super.checkClosed();
  }

  /**
   * @see java.sql.PreparedStatement#clearParameters()
   */
  public void clearParameters() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      clearParametersInternal(true);
    }
  }

  private void clearParametersInternal(boolean clearServerParameters)
      throws SQLException {
    boolean hadLongData = false;

    if (this.parameterBindings != null) {
      for (int i = 0; i < this.parameterCount; i++) {
        if ((this.parameterBindings[i] != null)
            && this.parameterBindings[i].isLongData) {
          hadLongData = true;
        }

        this.parameterBindings[i].reset();
      }
    }

    if (clearServerParameters && hadLongData) {
      serverResetStatement();

      this.detectedLongParameterSwitch = false;
    }
  }

  protected boolean isCached = false;

  private boolean useAutoSlowLog;

  private Calendar serverTzCalendar;

  private Calendar defaultTzCalendar;

  protected void setClosed(boolean flag) {
    this.isClosed = flag;
  }
 
  /**
   * @see java.sql.Statement#close()
   */
  public void close() throws SQLException {
    try {
      synchronized (checkClosed().getConnectionMutex()) {
        if (this.isCached && !this.isClosed) {
          clearParameters();
         
          this.isClosed = true;
         
          this.connection.recachePreparedStatement(this);
          return;
        }
       
        realClose(true, true);
      }
    } catch (SQLException sqlEx) {
      if (SQLError.SQL_STATE_CONNECTION_NOT_OPEN.equals(sqlEx.getSQLState())) {
        return;
      }
     
      throw sqlEx;
    }
  }

  private void dumpCloseForTestcase() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      StringBuffer buf = new StringBuffer();
      this.connection.generateConnectionCommentBlock(buf);
      buf.append("DEALLOCATE PREPARE debug_stmt_");
      buf.append(this.statementId);
      buf.append(";\n");
 
      this.connection.dumpTestcaseQuery(buf.toString());
    }
  }

  private void dumpExecuteForTestcase() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      StringBuffer buf = new StringBuffer();
 
      for (int i = 0; i < this.parameterCount; i++) {
        this.connection.generateConnectionCommentBlock(buf);
 
        buf.append("SET @debug_stmt_param");
        buf.append(this.statementId);
        buf.append("_");
        buf.append(i);
        buf.append("=");
 
        if (this.parameterBindings[i].isNull) {
          buf.append("NULL");
        } else {
          buf.append(this.parameterBindings[i].toString(true));
        }
 
        buf.append(";\n");
      }
 
      this.connection.generateConnectionCommentBlock(buf);
 
      buf.append("EXECUTE debug_stmt_");
      buf.append(this.statementId);
 
      if (this.parameterCount > 0) {
        buf.append(" USING ");
        for (int i = 0; i < this.parameterCount; i++) {
          if (i > 0) {
            buf.append(", ");
          }
 
          buf.append("@debug_stmt_param");
          buf.append(this.statementId);
          buf.append("_");
          buf.append(i);
 
        }
      }
 
      buf.append(";\n");
 
      this.connection.dumpTestcaseQuery(buf.toString());
    }
  }

  private void dumpPrepareForTestcase() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      StringBuffer buf = new StringBuffer(this.originalSql.length() + 64);
 
      this.connection.generateConnectionCommentBlock(buf);
 
      buf.append("PREPARE debug_stmt_");
      buf.append(this.statementId);
      buf.append(" FROM \"");
      buf.append(this.originalSql);
      buf.append("\";\n");
 
      this.connection.dumpTestcaseQuery(buf.toString());
    }
  }

  protected int[] executeBatchSerially(int batchTimeout) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      MySQLConnection locallyScopedConn = this.connection;
     
     
      if (locallyScopedConn.isReadOnly()) {
        throw SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.2") //$NON-NLS-1$
            + Messages.getString("ServerPreparedStatement.3"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
     
      clearWarnings();

      // Store this for later, we're going to 'swap' them out
      // as we execute each batched statement...
      BindValue[] oldBindValues = this.parameterBindings;

      try {
        int[] updateCounts = null;

        if (this.batchedArgs != null) {
          int nbrCommands = this.batchedArgs.size();
          updateCounts = new int[nbrCommands];

          if (this.retrieveGeneratedKeys) {
            this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(nbrCommands);
          }

          for (int i = 0; i < nbrCommands; i++) {
            updateCounts[i] = -3;
          }

          SQLException sqlEx = null;

          int commandIndex = 0;

          BindValue[] previousBindValuesForBatch = null;

          CancelTask timeoutTask = null;
         
          try {
            if (locallyScopedConn.getEnableQueryTimeouts() &&
                batchTimeout != 0
                && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
              timeoutTask = new CancelTask(this);
              locallyScopedConn.getCancelTimer().schedule(timeoutTask,
                  batchTimeout);
            }
           
            for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
              Object arg = this.batchedArgs.get(commandIndex);
 
              if (arg instanceof String) {
                updateCounts[commandIndex] = executeUpdate((String) arg);
              } else {
                this.parameterBindings = ((BatchedBindValues) arg).batchedParameterValues;
 
                try {
                  // We need to check types each time, as
                  // the user might have bound different
                  // types in each addBatch()
 
                  if (previousBindValuesForBatch != null) {
                    for (int j = 0; j < this.parameterBindings.length; j++) {
                      if (this.parameterBindings[j].bufferType != previousBindValuesForBatch[j].bufferType) {
                        this.sendTypesToServer = true;
 
                        break;
                      }
                    }
                  }
 
                  try {
                    updateCounts[commandIndex] = executeUpdate(false, true);
                  } finally {
                    previousBindValuesForBatch = this.parameterBindings;
                  }
 
                  if (this.retrieveGeneratedKeys) {
                    java.sql.ResultSet rs = null;
 
                    try {
                      // we don't want to use our version,
                      // because we've altered the behavior of
                      // ours to support batch updates
                      // (catch-22)
                      // Ideally, what we need here is
                      // super.super.getGeneratedKeys()
                      // but that construct doesn't exist in
                      // Java, so that's why there's
                      // this kludge.
                      rs = getGeneratedKeysInternal();
 
                      while (rs.next()) {
                        this.batchedGeneratedKeys
                            .add(new ByteArrayRow(new byte[][] { rs
                                .getBytes(1) }, getExceptionInterceptor()));
                      }
                    } finally {
                      if (rs != null) {
                        rs.close();
                      }
                    }
                  }
                } 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];
                    System.arraycopy(updateCounts, 0,
                        newUpdateCounts, 0, commandIndex);
     
                    throw new java.sql.BatchUpdateException(ex
                        .getMessage(), ex.getSQLState(), ex
                        .getErrorCode(), newUpdateCounts);
                  }
                }
              }
            }
          } finally {
            if (timeoutTask != null) {
              timeoutTask.cancel();
             
              locallyScopedConn.getCancelTimer().purge();
            }
           
            resetCancelledState();
          }

          if (sqlEx != null) {
            throw new java.sql.BatchUpdateException(sqlEx
                .getMessage(), sqlEx.getSQLState(), sqlEx
                .getErrorCode(), updateCounts);
          }
        }

        return (updateCounts != null) ? updateCounts : new int[0];
      } finally {
        this.parameterBindings = oldBindValues;
        this.sendTypesToServer = true;

        clearBatch();
      }
    }
  }

  /**
   * @see com.mysql.jdbc.PreparedStatement#executeInternal(int,
   *      com.mysql.jdbc.Buffer, boolean, boolean)
   */
  protected com.mysql.jdbc.ResultSetInternalMethods executeInternal(int maxRowsToRetrieve,
      Buffer sendPacket, boolean createStreamingResultSet,
      boolean queryIsSelectOnly, Field[] metadataFromCache,
      boolean isBatch)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      this.numberOfExecutions++;
 
      // We defer to server-side execution
      try {
        return serverExecute(maxRowsToRetrieve, createStreamingResultSet,
            metadataFromCache);
      } catch (SQLException sqlEx) {
        // don't wrap SQLExceptions
        if (this.connection.getEnablePacketDebug()) {
          this.connection.getIO().dumpPacketRingBuffer();
        }
 
        if (this.connection.getDumpQueriesOnException()) {
          String extractedSql = toString();
          StringBuffer messageBuf = new StringBuffer(extractedSql
              .length() + 32);
          messageBuf
              .append("\n\nQuery being executed when exception was thrown:\n");
          messageBuf.append(extractedSql);
              messageBuf.append("\n\n");
 
          sqlEx = ConnectionImpl.appendMessageToException(sqlEx, messageBuf
              .toString(), getExceptionInterceptor());
        }
 
        throw sqlEx;
      } catch (Exception ex) {
        if (this.connection.getEnablePacketDebug()) {
          this.connection.getIO().dumpPacketRingBuffer();
        }
 
        SQLException sqlEx = SQLError.createSQLException(ex.toString(),
            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
 
        if (this.connection.getDumpQueriesOnException()) {
          String extractedSql = toString();
          StringBuffer messageBuf = new StringBuffer(extractedSql
              .length() + 32);
          messageBuf
              .append("\n\nQuery being executed when exception was thrown:\n");
          messageBuf.append(extractedSql);
              messageBuf.append("\n\n");
 
          sqlEx = ConnectionImpl.appendMessageToException(sqlEx, messageBuf
              .toString(), getExceptionInterceptor());
        }
 
        sqlEx.initCause(ex);
       
        throw sqlEx;
      }
    }
  }

  /**
   * @see com.mysql.jdbc.PreparedStatement#fillSendPacket()
   */
  protected Buffer fillSendPacket() throws SQLException {
    return null; // we don't use this type of packet
  }

  /**
   * @see com.mysql.jdbc.PreparedStatement#fillSendPacket(byte,
   *      java.io.InputStream, boolean, int)
   */
  protected Buffer fillSendPacket(byte[][] batchedParameterStrings,
      InputStream[] batchedParameterStreams, boolean[] batchedIsStream,
      int[] batchedStreamLengths) throws SQLException {
    return null; // we don't use this type of packet
  }

  /**
   * Returns the structure representing the value that (can be)/(is)
   * bound at the given parameter index.
   *
   * @param parameterIndex 1-based
   * @param forLongData is this for a stream?
   * @return
   * @throws SQLException
   */
  protected  BindValue getBinding(int parameterIndex, boolean forLongData)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
   
      if (this.parameterBindings.length == 0) {
        throw SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.8"), //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
 
      parameterIndex--;
 
      if ((parameterIndex < 0)
          || (parameterIndex >= this.parameterBindings.length)) {
        throw SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.9") //$NON-NLS-1$
            + (parameterIndex + 1)
            + Messages.getString("ServerPreparedStatement.10") //$NON-NLS-1$
            + this.parameterBindings.length,
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
 
      if (this.parameterBindings[parameterIndex] == null) {
        this.parameterBindings[parameterIndex] = new BindValue();
      } else {
        if (this.parameterBindings[parameterIndex].isLongData
            && !forLongData) {
          this.detectedLongParameterSwitch = true;
        }
      }
 
      this.parameterBindings[parameterIndex].isSet = true;
      this.parameterBindings[parameterIndex].boundBeforeExecutionNum = this.numberOfExecutions;
 
      return this.parameterBindings[parameterIndex];
    }
  }

  /**
   * Return current bind values for use by Statement Interceptors.
   * @return the bind values as set by setXXX and stored by addBatch
   * @see #executeBatch()
   * @see #addBatch()
   */
  public BindValue[] getParameterBindValues() {
    return parameterBindings;
  }

  /**
   * @see com.mysql.jdbc.PreparedStatement#getBytes(int)
   */
  byte[] getBytes(int parameterIndex) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      BindValue bindValue = getBinding(parameterIndex, false);
 
      if (bindValue.isNull) {
        return null;
      } else if (bindValue.isLongData) {
        throw SQLError.notImplemented();
      } else {
        if (this.outByteBuffer == null) {
          this.outByteBuffer = new Buffer(this.connection
              .getNetBufferLength());
        }
 
        this.outByteBuffer.clear();
 
        int originalPosition = this.outByteBuffer.getPosition();
 
        storeBinding(this.outByteBuffer, bindValue, this.connection.getIO());
 
        int newPosition = this.outByteBuffer.getPosition();
 
        int length = newPosition - originalPosition;
 
        byte[] valueAsBytes = new byte[length];
 
        System.arraycopy(this.outByteBuffer.getByteBuffer(),
            originalPosition, valueAsBytes, 0, length);
 
        return valueAsBytes;
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#getMetaData()
   */
  public java.sql.ResultSetMetaData getMetaData() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (this.resultFields == null) {
        return null;
      }
 
      return new ResultSetMetaData(this.resultFields,
          this.connection.getUseOldAliasMetadataBehavior(), getExceptionInterceptor());
    }
  }

  /**
   * @see java.sql.PreparedStatement#getParameterMetaData()
   */
  public ParameterMetaData getParameterMetaData() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
   
      if (this.parameterMetaData == null) {
        this.parameterMetaData = new MysqlParameterMetadata(
            this.parameterFields, this.parameterCount, getExceptionInterceptor());
      }
     
      return this.parameterMetaData;
    }
  }

  /**
   * @see com.mysql.jdbc.PreparedStatement#isNull(int)
   */
  boolean isNull(int paramIndex) {
    throw new IllegalArgumentException(Messages
        .getString("ServerPreparedStatement.7")); //$NON-NLS-1$
  }

  /**
   * Closes this connection and frees all 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.connection != null) {
        if (this.connection.getAutoGenerateTestcaseScript()) {
          dumpCloseForTestcase();
        }
 
        //
        // Don't communicate with the server if we're being
        // called from the finalizer...
        //
        // This will leak server resources, but if we don't do this,
        // we'll deadlock (potentially, because there's no guarantee
        // when, what order, and what concurrency finalizers will be
        // called with). Well-behaved programs won't rely on finalizers
        // to clean up their statements.
        //
 
        SQLException exceptionDuringClose = null;
 
        if (calledExplicitly && !this.connection.isClosed()) {
          synchronized (this.connection.getConnectionMutex()) {
            try {
 
              MysqlIO mysql = this.connection.getIO();
 
              Buffer packet = mysql.getSharedSendPacket();
 
              packet.writeByte((byte) MysqlDefs.COM_CLOSE_STATEMENT);
              packet.writeLong(this.serverStatementId);
 
              mysql.sendCommand(MysqlDefs.COM_CLOSE_STATEMENT, null,
                  packet, true, null, 0);
            } catch (SQLException sqlEx) {
              exceptionDuringClose = sqlEx;
            }
          }
        }
 
        super.realClose(calledExplicitly, closeOpenResults);
 
        clearParametersInternal(false);
        this.parameterBindings = null;
 
        this.parameterFields = null;
        this.resultFields = null;
 
        if (exceptionDuringClose != null) {
          throw exceptionDuringClose;
        }
      }
    }
  }

  /**
   * Used by Connection when auto-reconnecting to retrieve 'lost' prepared
   * statements.
   *
   * @throws SQLException
   *             if an error occurs.
   */
  protected void rePrepare() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      this.invalidationException = null;
 
      try {
        serverPrepare(this.originalSql);
      } catch (SQLException sqlEx) {
        // don't wrap SQLExceptions
        this.invalidationException = sqlEx;
      } catch (Exception ex) {
        this.invalidationException = SQLError.createSQLException(ex.toString(),
            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        this.invalidationException.initCause(ex);
      }
 
      if (this.invalidationException != null) {
        this.invalid = true;
 
        this.parameterBindings = null;
 
        this.parameterFields = null;
        this.resultFields = null;
 
        if (this.results != null) {
          try {
            this.results.close();
          } catch (Exception ex) {
            ;
          }
        }

        if (this.generatedKeysResults != null) {
          try {
            this.generatedKeysResults.close();
          } catch (Exception ex) {
            ;
          }
        }

        try {
          closeAllOpenResults();
        } catch (Exception e) {
          ;
        }
       
        if (this.connection != null) {
          if (this.maxRowsChanged) {
            this.connection.unsetMaxRows(this);
          }
 
          if (!this.connection.getDontTrackOpenResources()) {
            this.connection.unregisterStatement(this);
          }
        }
      }
    }
  }

  /**
   * Tells the server to execute this prepared statement with the current
   * parameter bindings.
   *
   * <pre>
   *
   *
   *    -   Server gets the command 'COM_EXECUTE' to execute the
   *        previously         prepared query. If there is any param markers;
   *  then client will send the data in the following format:
   *
   *  [COM_EXECUTE:1]
   *  [STMT_ID:4]
   *  [NULL_BITS:(param_count+7)/8)]
   *  [TYPES_SUPPLIED_BY_CLIENT(0/1):1]
   *  [[length]data]
   *  [[length]data] .. [[length]data].
   *
   *  (Note: Except for string/binary types; all other types will not be
   *  supplied with length field)
   *
   * 
   * </pre>
   *
   * @param maxRowsToRetrieve
   *            DOCUMENT ME!
   * @param createStreamingResultSet
   *            DOCUMENT ME!
   *
   * @return DOCUMENT ME!
   *
   * @throws SQLException
   */
  private com.mysql.jdbc.ResultSetInternalMethods serverExecute(int maxRowsToRetrieve,
      boolean createStreamingResultSet,
      Field[] metadataFromCache) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      MysqlIO mysql = this.connection.getIO();

      if (mysql.shouldIntercept()) {
        ResultSetInternalMethods interceptedResults =
            mysql.invokeStatementInterceptorsPre(this.originalSql, this, true);
 
          if (interceptedResults != null) {
            return interceptedResults;
          }
      }
       
      if (this.detectedLongParameterSwitch) {
        // Check when values were bound
        boolean firstFound = false;
        long boundTimeToCheck = 0;
       
        for (int i = 0; i < this.parameterCount - 1; i++) {
          if (this.parameterBindings[i].isLongData) {
            if (firstFound && boundTimeToCheck !=
              this.parameterBindings[i].boundBeforeExecutionNum) {          
              throw SQLError.createSQLException(Messages
                  .getString("ServerPreparedStatement.11") //$NON-NLS-1$
                  + Messages.getString("ServerPreparedStatement.12"), //$NON-NLS-1$
                  SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
            }
            firstFound = true;
            boundTimeToCheck = this.parameterBindings[i].boundBeforeExecutionNum;
          }
        }
       
        // Okay, we've got all "newly"-bound streams, so reset
        // server-side state to clear out previous bindings
       
        serverResetStatement();
      }


      // Check bindings
      for (int i = 0; i < this.parameterCount; i++) {
        if (!this.parameterBindings[i].isSet) {
          throw SQLError.createSQLException(Messages
              .getString("ServerPreparedStatement.13") + (i + 1) //$NON-NLS-1$
              + Messages.getString("ServerPreparedStatement.14"),
              SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
        }
      }

      //
      // Send all long data
      //
      for (int i = 0; i < this.parameterCount; i++) {
        if (this.parameterBindings[i].isLongData) {
          serverLongData(i, this.parameterBindings[i]);
        }
      }

      if (this.connection.getAutoGenerateTestcaseScript()) {
        dumpExecuteForTestcase();
      }

      //
      // store the parameter values
      //

      Buffer packet = mysql.getSharedSendPacket();

      packet.clear();
      packet.writeByte((byte) MysqlDefs.COM_EXECUTE);
      packet.writeLong(this.serverStatementId);

//      boolean usingCursor = false;

      if (this.connection.versionMeetsMinimum(4, 1, 2)) {
        // we only create cursor-backed result sets if
        // a) The query is a SELECT
        // b) The server supports it
        // c) We know it is forward-only (note this doesn't
        // preclude updatable result sets)
        // d) The user has set a fetch size
        if (this.resultFields != null &&
            this.connection.isCursorFetchEnabled()
            && getResultSetType() == ResultSet.TYPE_FORWARD_ONLY
            && getResultSetConcurrency() == ResultSet.CONCUR_READ_ONLY
            && getFetchSize() > 0) {
          packet.writeByte(MysqlDefs.OPEN_CURSOR_FLAG);
//          usingCursor = true;
        } else {
          packet.writeByte((byte) 0); // placeholder for flags
        }

        packet.writeLong(1); // placeholder for parameter
                             // iterations
      }

      /* Reserve place for null-marker bytes */
      int nullCount = (this.parameterCount + 7) / 8;

      // if (mysql.versionMeetsMinimum(4, 1, 2)) {
      // nullCount = (this.parameterCount + 9) / 8;
      // }
      int nullBitsPosition = packet.getPosition();

      for (int i = 0; i < nullCount; i++) {
        packet.writeByte((byte) 0);
      }

      byte[] nullBitsBuffer = new byte[nullCount];

      /* In case if buffers (type) altered, indicate to server */
      packet.writeByte(this.sendTypesToServer ? (byte) 1 : (byte) 0);

      if (this.sendTypesToServer) {
        /*
         * Store types of parameters in first in first package that is
         * sent to the server.
         */
        for (int i = 0; i < this.parameterCount; i++) {
          packet.writeInt(this.parameterBindings[i].bufferType);
        }
      }

      //
      // store the parameter values
      //
      for (int i = 0; i < this.parameterCount; i++) {
        if (!this.parameterBindings[i].isLongData) {
          if (!this.parameterBindings[i].isNull) {
            storeBinding(packet, this.parameterBindings[i], mysql);
          } else {
            nullBitsBuffer[i / 8] |= (1 << (i & 7));
          }
        }
      }

      //
      // Go back and write the NULL flags
      // to the beginning of the packet
      //
      int endPosition = packet.getPosition();
      packet.setPosition(nullBitsPosition);
      packet.writeBytesNoNull(nullBitsBuffer);
      packet.setPosition(endPosition);

      long begin = 0;

      boolean logSlowQueries = this.connection.getLogSlowQueries();
      boolean gatherPerformanceMetrics = this.connection
          .getGatherPerformanceMetrics();

      if (this.profileSQL || logSlowQueries || gatherPerformanceMetrics) {
        begin = mysql.getCurrentTimeNanosOrMillis();
      }

      resetCancelledState();
     
      CancelTask timeoutTask = null;

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

        Buffer resultPacket = mysql.sendCommand(MysqlDefs.COM_EXECUTE,
          null, packet, false, null, 0);
       
        long queryEndTime = 0L;
       
        if (logSlowQueries || gatherPerformanceMetrics || this.profileSQL) {
          queryEndTime = mysql.getCurrentTimeNanosOrMillis();
        }
       
        if (timeoutTask != null) {
          timeoutTask.cancel();
         
          this.connection.getCancelTimer().purge();
         
          if (timeoutTask.caughtWhileCancelling != null) {
            throw timeoutTask.caughtWhileCancelling;
          }
         
          timeoutTask = null;
        }
       
        synchronized (this.cancelTimeoutMutex) {
          if (this.wasCancelled) {
            SQLException cause = null;
           
            if (this.wasCancelledByTimeout) {
              cause = new MySQLTimeoutException();
            } else {
              cause = new MySQLStatementCancelledException();
            }
           
            resetCancelledState();
           
            throw cause;
          }
        }

        boolean queryWasSlow = false;
       
        if (logSlowQueries || gatherPerformanceMetrics) {
          long elapsedTime = queryEndTime - begin;

          if (logSlowQueries) {
              if (this.useAutoSlowLog) {
                queryWasSlow = elapsedTime > this.connection.getSlowQueryThresholdMillis();
              } else {
                queryWasSlow = this.connection.isAbonormallyLongQuery(elapsedTime);
               
                this.connection.reportQueryTime(elapsedTime);
              }
          }

          if (queryWasSlow) {
           
            StringBuffer mesgBuf = new StringBuffer(
                48 + this.originalSql.length());
            mesgBuf.append(Messages
                .getString("ServerPreparedStatement.15")); //$NON-NLS-1$
            mesgBuf.append(mysql.getSlowQueryThreshold());
            mesgBuf.append(Messages
                .getString("ServerPreparedStatement.15a")); //$NON-NLS-1$
            mesgBuf.append(elapsedTime);
            mesgBuf.append(Messages
                .getString("ServerPreparedStatement.16")); //$NON-NLS-1$

            mesgBuf.append("as prepared: ");
            mesgBuf.append(this.originalSql);
            mesgBuf.append("\n\n with parameters bound:\n\n");
            mesgBuf.append(asSql(true));

            this.eventSink
                .consumeEvent(new ProfilerEvent(
                    ProfilerEvent.TYPE_SLOW_QUERY,
                    "", this.currentCatalog, this.connection.getId(), //$NON-NLS-1$
                    getId(), 0, System.currentTimeMillis(),
                    elapsedTime, mysql
                        .getQueryTimingUnits(), null,
                        LogUtils.findCallingClassAndMethod(new Throwable()), mesgBuf.toString()));
          }

          if (gatherPerformanceMetrics) {
            this.connection.registerQueryExecutionTime(elapsedTime);
          }
        }

        this.connection.incrementNumberOfPreparedExecutes();

        if (this.profileSQL) {
          this.eventSink = ProfilerEventHandlerFactory
              .getInstance(this.connection);

          this.eventSink.consumeEvent(new ProfilerEvent(
              ProfilerEvent.TYPE_EXECUTE,
              "", this.currentCatalog, //$NON-NLS-1$
              this.connectionId, this.statementId, -1, System
                  .currentTimeMillis(), mysql
                  .getCurrentTimeNanosOrMillis() - begin,
              mysql.getQueryTimingUnits(), null, LogUtils.findCallingClassAndMethod(new Throwable()),
              truncateQueryToLog(asSql(true))));
        }
 
        com.mysql.jdbc.ResultSetInternalMethods rs = mysql.readAllResults(this,
            maxRowsToRetrieve, this.resultSetType,
            this.resultSetConcurrency, createStreamingResultSet,
            this.currentCatalog, resultPacket, true, this.fieldCount,
            metadataFromCache);
       
        if (mysql.shouldIntercept()) {
          ResultSetInternalMethods interceptedResults =
              mysql.invokeStatementInterceptorsPost(this.originalSql, this, rs, true, null);
   
            if (interceptedResults != null) {
              rs = interceptedResults;
            }
        }
       
        if (this.profileSQL) {
          long fetchEndTime = mysql.getCurrentTimeNanosOrMillis();

          this.eventSink.consumeEvent(new ProfilerEvent(
              ProfilerEvent.TYPE_FETCH,
              "", this.currentCatalog, this.connection.getId(), //$NON-NLS-1$
              getId(), 0 /* FIXME rs.resultId */, System.currentTimeMillis(),
              (fetchEndTime - queryEndTime), mysql
                  .getQueryTimingUnits(), null,
                  LogUtils.findCallingClassAndMethod(new Throwable()), null));
        }
 
        if (queryWasSlow && this.connection.getExplainSlowQueries()) {
          String queryAsString = asSql(true);

          mysql.explainSlowQuery(StringUtils.getBytes(queryAsString),
              queryAsString);
        }
       
        if (!createStreamingResultSet &&
            this.serverNeedsResetBeforeEachExecution) {
          serverResetStatement(); // clear any long data...
        }
 
       
        this.sendTypesToServer = false;
        this.results = rs;
 
        if (mysql.hadWarnings()) {
                mysql.scanForAndThrowDataTruncation();
            }
       
        return rs;
      } catch (SQLException sqlEx) {
        if (mysql.shouldIntercept()) {
          mysql.invokeStatementInterceptorsPost(this.originalSql, this, null, true, sqlEx);
        }
       
        throw sqlEx;
      } finally {
        this.statementExecuting.set(false);
       
        if (timeoutTask != null) {
          timeoutTask.cancel();
          this.connection.getCancelTimer().purge();
        }
      }
    }
  }

  /**
   * Sends stream-type data parameters to the server.
   *
   * <pre>
   *
   *  Long data handling:
   *
   *  - Server gets the long data in pieces with command type 'COM_LONG_DATA'.
   *  - The packet recieved will have the format as:
   *    [COM_LONG_DATA:     1][STMT_ID:4][parameter_number:2][type:2][data]
   *  - Checks if the type is specified by client, and if yes reads the type,
   *    and  stores the data in that format.
   *  - It's up to the client to check for read data ended. The server doesn't
   *    care;  and also server doesn't notify to the client that it got the
   *    data  or not; if there is any error; then during execute; the error
   *    will  be returned
   * 
   * </pre>
   *
   * @param parameterIndex
   *            DOCUMENT ME!
   * @param longData
   *            DOCUMENT ME!
   *
   * @throws SQLException
   *             if an error occurs.
   */
  private void serverLongData(int parameterIndex, BindValue longData)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      MysqlIO mysql = this.connection.getIO();

      Buffer packet = mysql.getSharedSendPacket();

      Object value = longData.value;

      if (value instanceof byte[]) {
        packet.clear();
        packet.writeByte((byte) MysqlDefs.COM_LONG_DATA);
        packet.writeLong(this.serverStatementId);
        packet.writeInt((parameterIndex));

        packet.writeBytesNoNull((byte[]) longData.value);

        mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true,
            null, 0);
      } else if (value instanceof InputStream) {
        storeStream(mysql, parameterIndex, packet, (InputStream) value);
      } else if (value instanceof java.sql.Blob) {
        storeStream(mysql, parameterIndex, packet,
            ((java.sql.Blob) value).getBinaryStream());
      } else if (value instanceof Reader) {
        storeReader(mysql, parameterIndex, packet, (Reader) value);
      } else {
        throw SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.18") //$NON-NLS-1$
            + value.getClass().getName() + "'", //$NON-NLS-1$
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
      }
    }
  }

  private void serverPrepare(String sql) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      MysqlIO mysql = this.connection.getIO();

      if (this.connection.getAutoGenerateTestcaseScript()) {
        dumpPrepareForTestcase();
      }

      try {
        long begin = 0;

        if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { //$NON-NLS-1$
          this.isLoadDataQuery = true;
        } else {
          this.isLoadDataQuery = false;
        }

        if (this.connection.getProfileSql()) {
          begin = System.currentTimeMillis();
        }

        String characterEncoding = null;
        String connectionEncoding = this.connection.getEncoding();

        if (!this.isLoadDataQuery && this.connection.getUseUnicode()
            && (connectionEncoding != null)) {
          characterEncoding = connectionEncoding;
        }

        Buffer prepareResultPacket = mysql.sendCommand(
            MysqlDefs.COM_PREPARE, sql, null, false,
            characterEncoding, 0);

        if (this.connection.versionMeetsMinimum(4, 1, 1)) {
          // 4.1.1 and newer use the first byte
          // as an 'ok' or 'error' flag, so move
          // the buffer pointer past it to
          // start reading the statement id.
          prepareResultPacket.setPosition(1);
        } else {
          // 4.1.0 doesn't use the first byte as an
          // 'ok' or 'error' flag
          prepareResultPacket.setPosition(0);
        }

        this.serverStatementId = prepareResultPacket.readLong();
        this.fieldCount = prepareResultPacket.readInt();
        this.parameterCount = prepareResultPacket.readInt();
        this.parameterBindings = new BindValue[this.parameterCount];

        for (int i = 0; i < this.parameterCount; i++) {
          this.parameterBindings[i] = new BindValue();
        }

        this.connection.incrementNumberOfPrepares();

        if (this.profileSQL) {
          this.eventSink.consumeEvent(new ProfilerEvent(
              ProfilerEvent.TYPE_PREPARE,
              "", this.currentCatalog, //$NON-NLS-1$
              this.connectionId, this.statementId, -1,
              System.currentTimeMillis(),
              mysql.getCurrentTimeNanosOrMillis() - begin,
              mysql.getQueryTimingUnits(), null,
              LogUtils.findCallingClassAndMethod(new Throwable()), truncateQueryToLog(sql)));
        }

        if (this.parameterCount > 0) {
          if (this.connection.versionMeetsMinimum(4, 1, 2)
              && !mysql.isVersion(5, 0, 0)) {
            this.parameterFields = new Field[this.parameterCount];

            Buffer metaDataPacket = mysql.readPacket();

            int i = 0;

            while (!metaDataPacket.isLastDataPacket()
                && (i < this.parameterCount)) {
              this.parameterFields[i++] = mysql.unpackField(
                  metaDataPacket, false);
              metaDataPacket = mysql.readPacket();
            }
          }
        }

        if (this.fieldCount > 0) {
          this.resultFields = new Field[this.fieldCount];

          Buffer fieldPacket = mysql.readPacket();

          int i = 0;

          // Read in the result set column information
          while (!fieldPacket.isLastDataPacket()
              && (i < this.fieldCount)) {
            this.resultFields[i++] = mysql.unpackField(fieldPacket,
                false);
            fieldPacket = mysql.readPacket();
          }
        }
      } catch (SQLException sqlEx) {
        if (this.connection.getDumpQueriesOnException()) {
          StringBuffer messageBuf = new StringBuffer(this.originalSql
              .length() + 32);
          messageBuf
              .append("\n\nQuery being prepared when exception was thrown:\n\n");
          messageBuf.append(this.originalSql);

          sqlEx = ConnectionImpl.appendMessageToException(sqlEx,
              messageBuf.toString(), getExceptionInterceptor());
        }

        throw sqlEx;
      } finally {
        // Leave the I/O channel in a known state...there might be
        // packets out there
        // that we're not interested in
        this.connection.getIO().clearInputStream();
      }
    }
  }

  private String truncateQueryToLog(String sql) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      String query = null;
     
      if (sql.length() > this.connection.getMaxQuerySizeToLog()) {
        StringBuffer queryBuf = new StringBuffer(
            this.connection.getMaxQuerySizeToLog() + 12);
        queryBuf.append(sql.substring(0, this.connection.getMaxQuerySizeToLog()));
        queryBuf.append(Messages.getString("MysqlIO.25"));
       
        query = queryBuf.toString();
      } else {
        query = sql;
      }
     
      return query;
    }
  }

  private void serverResetStatement() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      MysqlIO mysql = this.connection.getIO();

      Buffer packet = mysql.getSharedSendPacket();

      packet.clear();
      packet.writeByte((byte) MysqlDefs.COM_RESET_STMT);
      packet.writeLong(this.serverStatementId);

      try {
        mysql.sendCommand(MysqlDefs.COM_RESET_STMT, null, packet,
            !this.connection.versionMeetsMinimum(4, 1, 2), null, 0);
      } catch (SQLException sqlEx) {
        throw sqlEx;
      } catch (Exception ex) {
        SQLException sqlEx = SQLError.createSQLException(ex.toString(),
            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        sqlEx.initCause(ex);
       
        throw sqlEx;
      } finally {
        mysql.clearInputStream();
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#setArray(int, java.sql.Array)
   */
  public void setArray(int i, Array x) throws SQLException {
    throw SQLError.notImplemented();
  }

  /**
   * @see java.sql.PreparedStatement#setAsciiStream(int, java.io.InputStream,
   *      int)
   */
  public void setAsciiStream(int parameterIndex, InputStream x, int length)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (x == null) {
        setNull(parameterIndex, java.sql.Types.BINARY);
      } else {
        BindValue binding = getBinding(parameterIndex, true);
        setType(binding, MysqlDefs.FIELD_TYPE_BLOB);
 
        binding.value = x;
        binding.isNull = false;
        binding.isLongData = true;
 
        if (this.connection.getUseStreamLengthsInPrepStmts()) {
          binding.bindLength = length;
        } else {
          binding.bindLength = -1;
        }
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#setBigDecimal(int, java.math.BigDecimal)
   */
  public void setBigDecimal(int parameterIndex, BigDecimal x)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (x == null) {
        setNull(parameterIndex, java.sql.Types.DECIMAL);
      } else {
     
        BindValue binding = getBinding(parameterIndex, false);
 
        if (this.connection.versionMeetsMinimum(5, 0, 3)) {
          setType(binding, MysqlDefs.FIELD_TYPE_NEW_DECIMAL);
        } else {
          setType(binding, this.stringTypeCode);
        }
 
        binding.value = StringUtils
          .fixDecimalExponent(StringUtils.consistentToString(x));
        binding.isNull = false;
        binding.isLongData = false;
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#setBinaryStream(int, java.io.InputStream,
   *      int)
   */
  public void setBinaryStream(int parameterIndex, InputStream x, int length)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (x == null) {
        setNull(parameterIndex, java.sql.Types.BINARY);
      } else {
        BindValue binding = getBinding(parameterIndex, true);
        setType(binding, MysqlDefs.FIELD_TYPE_BLOB);
 
        binding.value = x;
        binding.isNull = false;
        binding.isLongData = true;
 
        if (this.connection.getUseStreamLengthsInPrepStmts()) {
          binding.bindLength = length;
        } else {
          binding.bindLength = -1;
        }
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#setBlob(int, java.sql.Blob)
   */
  public void setBlob(int parameterIndex, Blob x) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (x == null) {
        setNull(parameterIndex, java.sql.Types.BINARY);
      } else {
        BindValue binding = getBinding(parameterIndex, true);
        setType(binding, MysqlDefs.FIELD_TYPE_BLOB);
 
        binding.value = x;
        binding.isNull = false;
        binding.isLongData = true;
 
        if (this.connection.getUseStreamLengthsInPrepStmts()) {
          binding.bindLength = x.length();
        } else {
          binding.bindLength = -1;
        }
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#setBoolean(int, boolean)
   */
  public void setBoolean(int parameterIndex, boolean x) throws SQLException {
    setByte(parameterIndex, (x ? (byte) 1 : (byte) 0));
  }

  /**
   * @see java.sql.PreparedStatement#setByte(int, byte)
   */
  public void setByte(int parameterIndex, byte x) throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);
    setType(binding, MysqlDefs.FIELD_TYPE_TINY);

    binding.value = null;
    binding.longBinding = x;
    binding.isNull = false;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setBytes(int, byte)
   */
  public void setBytes(int parameterIndex, byte[] x) throws SQLException {
    checkClosed();

    if (x == null) {
      setNull(parameterIndex, java.sql.Types.BINARY);
    } else {
      BindValue binding = getBinding(parameterIndex, false);
      setType(binding, MysqlDefs.FIELD_TYPE_VAR_STRING);

      binding.value = x;
      binding.isNull = false;
      binding.isLongData = false;
    }
  }

  /**
   * @see java.sql.PreparedStatement#setCharacterStream(int, java.io.Reader,
   *      int)
   */
  public void setCharacterStream(int parameterIndex, Reader reader, int length)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (reader == null) {
        setNull(parameterIndex, java.sql.Types.BINARY);
      } else {
        BindValue binding = getBinding(parameterIndex, true);
        setType(binding, MysqlDefs.FIELD_TYPE_BLOB);
 
        binding.value = reader;
        binding.isNull = false;
        binding.isLongData = true;
 
        if (this.connection.getUseStreamLengthsInPrepStmts()) {
          binding.bindLength = length;
        } else {
          binding.bindLength = -1;
        }
      }
    }
  }

  /**
   * @see java.sql.PreparedStatement#setClob(int, java.sql.Clob)
   */
  public void setClob(int parameterIndex, Clob x) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (x == null) {
        setNull(parameterIndex, java.sql.Types.BINARY);
      } else {
        BindValue binding = getBinding(parameterIndex, true);
        setType(binding, MysqlDefs.FIELD_TYPE_BLOB);
 
        binding.value = x.getCharacterStream();
        binding.isNull = false;
        binding.isLongData = true;
 
        if (this.connection.getUseStreamLengthsInPrepStmts()) {
          binding.bindLength = x.length();
        } else {
          binding.bindLength = -1;
        }
      }
    }
  }

  /**
   * Set a parameter to a java.sql.Date value. The driver converts this to a
   * SQL DATE value when it sends it to the database.
   *
   * @param parameterIndex
   *            the first parameter is 1, the second is 2, ...
   * @param x
   *            the parameter value
   *
   * @exception SQLException
   *                if a database-access error occurs.
   */
  public void setDate(int parameterIndex, Date x) throws SQLException {
    setDate(parameterIndex, x, null);
  }

  /**
   * Set a parameter to a java.sql.Date value. The driver converts this to a
   * SQL DATE value when it sends it to the database.
   *
   * @param parameterIndex
   *            the first parameter is 1, the second is 2, ...
   * @param x
   *            the parameter value
   * @param cal
   *            the calendar to interpret the date with
   *
   * @exception SQLException
   *                if a database-access error occurs.
   */
  public void setDate(int parameterIndex, Date x, Calendar cal)
      throws SQLException {
    if (x == null) {
      setNull(parameterIndex, java.sql.Types.DATE);
    } else {
      BindValue binding = getBinding(parameterIndex, false);
      setType(binding, MysqlDefs.FIELD_TYPE_DATE);

      binding.value = x;
      binding.isNull = false;
      binding.isLongData = false;
    }
  }

  /**
   * @see java.sql.PreparedStatement#setDouble(int, double)
   */
  public void setDouble(int parameterIndex, double x) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {

      if (!this.connection.getAllowNanAndInf()
          && (x == Double.POSITIVE_INFINITY
              || x == Double.NEGATIVE_INFINITY || Double.isNaN(x))) {
        throw SQLError.createSQLException("'" + x
            + "' is not a valid numeric or approximate numeric value",
            SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
 
      }
 
      BindValue binding = getBinding(parameterIndex, false);
      setType(binding, MysqlDefs.FIELD_TYPE_DOUBLE);
 
      binding.value = null;
      binding.doubleBinding = x;
      binding.isNull = false;
      binding.isLongData = false;
    }
  }

  /**
   * @see java.sql.PreparedStatement#setFloat(int, float)
   */
  public void setFloat(int parameterIndex, float x) throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);
    setType(binding, MysqlDefs.FIELD_TYPE_FLOAT);

    binding.value = null;
    binding.floatBinding = x;
    binding.isNull = false;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setInt(int, int)
   */
  public void setInt(int parameterIndex, int x) throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);
    setType(binding, MysqlDefs.FIELD_TYPE_LONG);

    binding.value = null;
    binding.longBinding = x;
    binding.isNull = false;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setLong(int, long)
   */
  public void setLong(int parameterIndex, long x) throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);
    setType(binding, MysqlDefs.FIELD_TYPE_LONGLONG);

    binding.value = null;
    binding.longBinding = x;
    binding.isNull = false;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setNull(int, int)
   */
  public void setNull(int parameterIndex, int sqlType) throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);

    //
    // Don't re-set types, but use something if this
    // parameter was never specified
    //
    if (binding.bufferType == 0) {
      setType(binding, MysqlDefs.FIELD_TYPE_NULL);
    }

    binding.value = null;
    binding.isNull = true;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setNull(int, int, java.lang.String)
   */
  public void setNull(int parameterIndex, int sqlType, String typeName)
      throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);

    //
    // Don't re-set types, but use something if this
    // parameter was never specified
    //
    if (binding.bufferType == 0) {
      setType(binding, MysqlDefs.FIELD_TYPE_NULL);
    }

    binding.value = null;
    binding.isNull = true;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setRef(int, java.sql.Ref)
   */
  public void setRef(int i, Ref x) throws SQLException {
    throw SQLError.notImplemented();
  }

  /**
   * @see java.sql.PreparedStatement#setShort(int, short)
   */
  public void setShort(int parameterIndex, short x) throws SQLException {
    checkClosed();

    BindValue binding = getBinding(parameterIndex, false);
    setType(binding, MysqlDefs.FIELD_TYPE_SHORT);

    binding.value = null;
    binding.longBinding = x;
    binding.isNull = false;
    binding.isLongData = false;
  }

  /**
   * @see java.sql.PreparedStatement#setString(int, java.lang.String)
   */
  public void setString(int parameterIndex, String x) throws SQLException {
    checkClosed();

    if (x == null) {
      setNull(parameterIndex, java.sql.Types.CHAR);
    } else {
      BindValue binding = getBinding(parameterIndex, false);

      setType(binding, this.stringTypeCode);

      binding.value = x;
      binding.isNull = false;
      binding.isLongData = false;
    }
  }

  /**
   * Set a parameter to a java.sql.Time value.
   *
   * @param parameterIndex
   *            the first parameter is 1...));
   * @param x
   *            the parameter value
   *
   * @throws SQLException
   *             if a database access error occurs
   */
  public void setTime(int parameterIndex, java.sql.Time x)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      setTimeInternal(parameterIndex, x, null, this.connection.getDefaultTimeZone(), false);
    }
  }

  /**
   * Set a parameter to a java.sql.Time value. The driver converts this to a
   * SQL TIME value when it sends it to the database, using the given
   * timezone.
   *
   * @param parameterIndex
   *            the first parameter is 1...));
   * @param x
   *            the parameter value
   * @param cal
   *            the timezone to use
   *
   * @throws SQLException
   *             if a database access error occurs
   */
  public void setTime(int parameterIndex, java.sql.Time x, Calendar cal)
      throws SQLException {
    setTimeInternal(parameterIndex, x, cal, cal.getTimeZone(), true);
  }

  /**
   * Set a parameter to a java.sql.Time value. The driver converts this to a
   * SQL TIME value when it sends it to the database, using the given
   * timezone.
   *
   * @param parameterIndex
   *            the first parameter is 1...));
   * @param x
   *            the parameter value
   * @param tz
   *            the timezone to use
   *
   * @throws SQLException
   *             if a database access error occurs
   */
  protected void setTimeInternal(int parameterIndex, java.sql.Time x,
      Calendar targetCalendar,
      TimeZone tz, boolean rollForward) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (x == null) {
        setNull(parameterIndex, java.sql.Types.TIME);
      } else {
        BindValue binding = getBinding(parameterIndex, false);
        setType(binding, MysqlDefs.FIELD_TYPE_TIME);
 
        if (!this.useLegacyDatetimeCode) {
          binding.value = x;
        } else {
          Calendar sessionCalendar = getCalendarInstanceForSessionOrNew();

          binding.value = TimeUtil.changeTimezone(this.connection,
              sessionCalendar,
              targetCalendar,
              x, tz,
              this.connection.getServerTimezoneTZ(),
              rollForward);
        }
       
        binding.isNull = false;
        binding.isLongData = false;
      }
    }
  }

  /**
   * Set a parameter to a java.sql.Timestamp value. The driver converts this
   * to a SQL TIMESTAMP value when it sends it to the database.
   *
   * @param parameterIndex
   *            the first parameter is 1, the second is 2, ...
   * @param x
   *            the parameter value
   *
   * @throws SQLException
   *             if a database-access error occurs.
   */
  public void setTimestamp(int parameterIndex, java.sql.Timestamp x)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      setTimestampInternal(parameterIndex, x, null, this.connection.getDefaultTimeZone(), false);
    }
  }

  /**
   * Set a parameter to a java.sql.Timestamp value. The driver converts this
   * to a SQL TIMESTAMP value when it sends it to the database.
   *
   * @param parameterIndex
   *            the first parameter is 1, the second is 2, ...
   * @param x
   *            the parameter value
   * @param cal
   *            the timezone to use
   *
   * @throws SQLException
   *             if a database-access error occurs.
   */
  public void setTimestamp(int parameterIndex, java.sql.Timestamp x,
      Calendar cal) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true);
    }
  }

  protected void setTimestampInternal(int parameterIndex,
      java.sql.Timestamp x, Calendar targetCalendar,
      TimeZone tz, boolean rollForward)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (x == null) {
        setNull(parameterIndex, java.sql.Types.TIMESTAMP);
      } else {
        BindValue binding = getBinding(parameterIndex, false);
        setType(binding, MysqlDefs.FIELD_TYPE_DATETIME);
 
        if (!this.useLegacyDatetimeCode) {
          binding.value = x;
        } else {
          Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ?
              this.connection.getUtcCalendar() :
                getCalendarInstanceForSessionOrNew();
         
          binding.value = TimeUtil.changeTimezone(this.connection,
              sessionCalendar,
              targetCalendar,
              x, tz,
              this.connection.getServerTimezoneTZ(),
              rollForward);

          binding.isNull = false;
          binding.isLongData = false;
        }
      }
    }
  }

  protected void setType(BindValue oldValue, int bufferType) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (oldValue.bufferType != bufferType) {
        this.sendTypesToServer = true;
      }
 
      oldValue.bufferType = bufferType;
    }
  }

  /**
   * DOCUMENT ME!
   *
   * @param parameterIndex
   *            DOCUMENT ME!
   * @param x
   *            DOCUMENT ME!
   * @param length
   *            DOCUMENT ME!
   *
   * @throws SQLException
   *             DOCUMENT ME!
   * @throws NotImplemented
   *             DOCUMENT ME!
   *
   * @see java.sql.PreparedStatement#setUnicodeStream(int,
   *      java.io.InputStream, int)
   * @deprecated
   */
  public void setUnicodeStream(int parameterIndex, InputStream x, int length)
      throws SQLException {
    checkClosed();

    throw SQLError.notImplemented();
  }

  /**
   * @see java.sql.PreparedStatement#setURL(int, java.net.URL)
   */
  public void setURL(int parameterIndex, URL x) throws SQLException {
    checkClosed();

    setString(parameterIndex, x.toString());
  }

  /**
   * Method storeBinding.
   *
   * @param packet
   * @param bindValue
   * @param mysql
   *            DOCUMENT ME!
   *
   * @throws SQLException
   *             DOCUMENT ME!
   */
  private void storeBinding(Buffer packet, BindValue bindValue, MysqlIO mysql)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      try {
        Object value = bindValue.value;
 
        //
        // Handle primitives first
        //
        switch (bindValue.bufferType) {
 
        case MysqlDefs.FIELD_TYPE_TINY:
          packet.writeByte((byte)bindValue.longBinding);
          return;
        case MysqlDefs.FIELD_TYPE_SHORT:
          packet.ensureCapacity(2);
          packet.writeInt((int)bindValue.longBinding);
          return;
        case MysqlDefs.FIELD_TYPE_LONG:
          packet.ensureCapacity(4);
          packet.writeLong((int)bindValue.longBinding);
          return;
        case MysqlDefs.FIELD_TYPE_LONGLONG:
          packet.ensureCapacity(8);
          packet.writeLongLong(bindValue.longBinding);
          return;
        case MysqlDefs.FIELD_TYPE_FLOAT:
          packet.ensureCapacity(4);
          packet.writeFloat(bindValue.floatBinding);
          return;
        case MysqlDefs.FIELD_TYPE_DOUBLE:
          packet.ensureCapacity(8);
          packet.writeDouble(bindValue.doubleBinding);
          return;
        case MysqlDefs.FIELD_TYPE_TIME:
          storeTime(packet, (Time) value);
          return;
        case MysqlDefs.FIELD_TYPE_DATE:
        case MysqlDefs.FIELD_TYPE_DATETIME:
        case MysqlDefs.FIELD_TYPE_TIMESTAMP:
          storeDateTime(packet, (java.util.Date) value, mysql, bindValue.bufferType);
          return;
        case MysqlDefs.FIELD_TYPE_VAR_STRING:
        case MysqlDefs.FIELD_TYPE_STRING:
        case MysqlDefs.FIELD_TYPE_VARCHAR:
        case MysqlDefs.FIELD_TYPE_DECIMAL:
        case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
          if (value instanceof byte[]) {
            packet.writeLenBytes((byte[]) value);
          } else if (!this.isLoadDataQuery) {
            packet.writeLenString((String) value, this.charEncoding,
                this.connection.getServerCharacterEncoding(),
                this.charConverter, this.connection
                    .parserKnowsUnicode(),
                    this.connection);
          } else {
            packet.writeLenBytes(StringUtils.getBytes((String) value));
          }
 
          return;
        }
 
       
      } catch (UnsupportedEncodingException uEE) {
        throw SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.22") //$NON-NLS-1$
            + this.connection.getEncoding() + "'", //$NON-NLS-1$
            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
      }
    }
  }

  private void storeDateTime412AndOlder(Buffer intoBuf, java.util.Date dt, int bufferType)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      Calendar sessionCalendar = null;
     
      if (!this.useLegacyDatetimeCode) {
        if (bufferType == MysqlDefs.FIELD_TYPE_DATE) {
          sessionCalendar = getDefaultTzCalendar();
        } else {
          sessionCalendar = getServerTzCalendar();
        }
      } else {
        sessionCalendar = (dt instanceof Timestamp &&
          this.connection.getUseJDBCCompliantTimezoneShift()) ?
          this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew();
      }

      java.util.Date oldTime = sessionCalendar.getTime();
     
      try {
        intoBuf.ensureCapacity(8);
        intoBuf.writeByte((byte) 7); // length
 
        sessionCalendar.setTime(dt);
       
        int year = sessionCalendar.get(Calendar.YEAR);
        int month = sessionCalendar.get(Calendar.MONTH) + 1;
        int date = sessionCalendar.get(Calendar.DATE);

        intoBuf.writeInt(year);
        intoBuf.writeByte((byte) month);
        intoBuf.writeByte((byte) date);
   
        if (dt instanceof java.sql.Date) {
          intoBuf.writeByte((byte) 0);
          intoBuf.writeByte((byte) 0);
          intoBuf.writeByte((byte) 0);
        } else {
          intoBuf.writeByte((byte) sessionCalendar
              .get(Calendar.HOUR_OF_DAY));
          intoBuf.writeByte((byte) sessionCalendar
              .get(Calendar.MINUTE));
          intoBuf.writeByte((byte) sessionCalendar
              .get(Calendar.SECOND));
        }
      } finally {
        sessionCalendar.setTime(oldTime);
      }
    }
  }

  /**
   *
   * @param intoBuf
   * @param dt
   * @param mysql
   * @param bufferType
   * @throws SQLException
   */
  private void storeDateTime(Buffer intoBuf, java.util.Date dt, MysqlIO mysql, int bufferType)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.connection.versionMeetsMinimum(4, 1, 3)) {
        storeDateTime413AndNewer(intoBuf, dt, bufferType);
      } else {
        storeDateTime412AndOlder(intoBuf, dt, bufferType);
      }
    }
  }

  private void storeDateTime413AndNewer(Buffer intoBuf, java.util.Date dt, int bufferType)
      throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      Calendar sessionCalendar = null;
     
      if (!this.useLegacyDatetimeCode) {
        if (bufferType == MysqlDefs.FIELD_TYPE_DATE) {
          sessionCalendar = getDefaultTzCalendar();
        } else {
          sessionCalendar = getServerTzCalendar();
        }
      } else {
        sessionCalendar = (dt instanceof Timestamp &&
          this.connection.getUseJDBCCompliantTimezoneShift()) ?
          this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew();
      }
     

      java.util.Date oldTime = sessionCalendar.getTime();

      try {
        sessionCalendar.setTime(dt);
       
        if (dt instanceof java.sql.Date) {
          sessionCalendar.set(Calendar.HOUR_OF_DAY, 0);
          sessionCalendar.set(Calendar.MINUTE, 0);
          sessionCalendar.set(Calendar.SECOND, 0);
        }

        byte length = (byte) 7;

        if (dt instanceof java.sql.Timestamp) {
          length = (byte) 11;
        }

        intoBuf.ensureCapacity(length);
       
        intoBuf.writeByte(length); // length

        int year = sessionCalendar.get(Calendar.YEAR);
        int month = sessionCalendar.get(Calendar.MONTH) + 1;
        int date = sessionCalendar.get(Calendar.DAY_OF_MONTH);
       
        intoBuf.writeInt(year);
        intoBuf.writeByte((byte) month);
        intoBuf.writeByte((byte) date);

        if (dt instanceof java.sql.Date) {
          intoBuf.writeByte((byte) 0);
          intoBuf.writeByte((byte) 0);
          intoBuf.writeByte((byte) 0);
        } else {
          intoBuf.writeByte((byte) sessionCalendar
              .get(Calendar.HOUR_OF_DAY));
          intoBuf.writeByte((byte) sessionCalendar
              .get(Calendar.MINUTE));
          intoBuf.writeByte((byte) sessionCalendar
              .get(Calendar.SECOND));
        }

        if (length == 11) {
          //  MySQL expects microseconds, not nanos
          intoBuf.writeLong(((java.sql.Timestamp) dt).getNanos() / 1000);
        }
     
      } finally {
        sessionCalendar.setTime(oldTime);
      }
    }
  }

  private Calendar getServerTzCalendar() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (serverTzCalendar == null) {
        serverTzCalendar = new GregorianCalendar(this.connection.getServerTimezoneTZ());
      }
     
      return this.serverTzCalendar;
    }
  }
 
  private Calendar getDefaultTzCalendar() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (defaultTzCalendar == null) {
        defaultTzCalendar = new GregorianCalendar(TimeZone.getDefault());
      }
     
      return this.defaultTzCalendar;
    }
  }

  //
  // TO DO: Investigate using NIO to do this faster
  //
  private void storeReader(MysqlIO mysql, int parameterIndex, Buffer packet,
      Reader inStream) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      String forcedEncoding = this.connection.getClobCharacterEncoding();
     
      String clobEncoding =
        (forcedEncoding == null ? this.connection.getEncoding() : forcedEncoding);
     
      int maxBytesChar = 2;
       
      if (clobEncoding != null) {
        if (!clobEncoding.equals("UTF-16")) {
          maxBytesChar = this.connection.getMaxBytesPerChar(clobEncoding);
         
          if (maxBytesChar == 1) {
            maxBytesChar = 2; // for safety
          }
        } else {
          maxBytesChar = 4;
        }
      }
       
      char[] buf = new char[BLOB_STREAM_READ_BUF_SIZE / maxBytesChar];
     
      int numRead = 0;
 
      int bytesInPacket = 0;
      int totalBytesRead = 0;
      int bytesReadAtLastSend = 0;
      int packetIsFullAt = this.connection.getBlobSendChunkSize();
     
     
     
      try {
        packet.clear();
        packet.writeByte((byte) MysqlDefs.COM_LONG_DATA);
        packet.writeLong(this.serverStatementId);
        packet.writeInt((parameterIndex));
 
        boolean readAny = false;
       
        while ((numRead = inStream.read(buf)) != -1) {
          readAny = true;
       
          byte[] valueAsBytes = StringUtils.getBytes(buf, null,
              clobEncoding, this.connection
                  .getServerCharacterEncoding(), 0, numRead,
              this.connection.parserKnowsUnicode(), getExceptionInterceptor());
 
          packet.writeBytesNoNull(valueAsBytes, 0, valueAsBytes.length);
 
          bytesInPacket += valueAsBytes.length;
          totalBytesRead += valueAsBytes.length;
 
          if (bytesInPacket >= packetIsFullAt) {
            bytesReadAtLastSend = totalBytesRead;
 
            mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet,
                true, null, 0);
 
            bytesInPacket = 0;
            packet.clear();
            packet.writeByte((byte) MysqlDefs.COM_LONG_DATA);
            packet.writeLong(this.serverStatementId);
            packet.writeInt((parameterIndex));
          }
        }
 
        if (totalBytesRead != bytesReadAtLastSend) {
          mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true,
              null, 0);
        }
       
        if (!readAny) {
          mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true,
              null, 0);
        }
      } catch (IOException ioEx) {
        SQLException sqlEx = SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.24") //$NON-NLS-1$
            + ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        sqlEx.initCause(ioEx);
       
        throw sqlEx;
      } finally {
        if (this.connection.getAutoClosePStmtStreams()) {
          if (inStream != null) {
            try {
              inStream.close();
            } catch (IOException ioEx) {
              ; // ignore
            }
          }
        }
      }
    }
  }

  private void storeStream(MysqlIO mysql, int parameterIndex, Buffer packet,
      InputStream inStream) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      byte[] buf = new byte[BLOB_STREAM_READ_BUF_SIZE];
 
      int numRead = 0;
     
      try {
        int bytesInPacket = 0;
        int totalBytesRead = 0;
        int bytesReadAtLastSend = 0;
        int packetIsFullAt = this.connection.getBlobSendChunkSize();
 
        packet.clear();
        packet.writeByte((byte) MysqlDefs.COM_LONG_DATA);
        packet.writeLong(this.serverStatementId);
        packet.writeInt((parameterIndex));
 
        boolean readAny = false;
       
        while ((numRead = inStream.read(buf)) != -1) {
 
          readAny = true;
         
          packet.writeBytesNoNull(buf, 0, numRead);
          bytesInPacket += numRead;
          totalBytesRead += numRead;
 
          if (bytesInPacket >= packetIsFullAt) {
            bytesReadAtLastSend = totalBytesRead;
 
            mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet,
                true, null, 0);
 
            bytesInPacket = 0;
            packet.clear();
            packet.writeByte((byte) MysqlDefs.COM_LONG_DATA);
            packet.writeLong(this.serverStatementId);
            packet.writeInt((parameterIndex));
          }
        }
 
        if (totalBytesRead != bytesReadAtLastSend) {
          mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true,
              null, 0);
        }
       
        if (!readAny) {
          mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true,
              null, 0);
        }
      } catch (IOException ioEx) {
        SQLException sqlEx = SQLError.createSQLException(Messages
            .getString("ServerPreparedStatement.25") //$NON-NLS-1$
            + ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        sqlEx.initCause(ioEx);
       
        throw sqlEx;
      } finally {
        if (this.connection.getAutoClosePStmtStreams()) {
          if (inStream != null) {
            try {
              inStream.close();
            } catch (IOException ioEx) {
              ; // ignore
            }
          }
        }
      }
    }
  }

  /**
   * @see java.lang.Object#toString()
   */
  public String toString() {
    StringBuffer toStringBuf = new StringBuffer();

    toStringBuf.append("com.mysql.jdbc.ServerPreparedStatement["); //$NON-NLS-1$
    toStringBuf.append(this.serverStatementId);
    toStringBuf.append("] - "); //$NON-NLS-1$

    try {
      toStringBuf.append(asSql());
    } catch (SQLException sqlEx) {
      toStringBuf.append(Messages.getString("ServerPreparedStatement.6")); //$NON-NLS-1$
      toStringBuf.append(sqlEx);
    }

    return toStringBuf.toString();
  }

  protected long getServerStatementId() {
    return serverStatementId;
  }

  private boolean hasCheckedRewrite = false;
  private boolean canRewrite = false;
 
  public boolean canRewriteAsMultiValueInsertAtSqlLevel() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (!hasCheckedRewrite) {
        this.hasCheckedRewrite = true;
        this.canRewrite = canRewrite(this.originalSql, isOnDuplicateKeyUpdate(), getLocationOfOnDuplicateKeyUpdate(), 0);
        // We need to client-side parse this to get the VALUES clause, etc.
        this.parseInfo = new ParseInfo(this.originalSql, this.connection, this.connection.getMetaData(), this.charEncoding, this.charConverter);
      }
     
      return this.canRewrite;
    }
  }

 
  public boolean canRewriteAsMultivalueInsertStatement() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (!canRewriteAsMultiValueInsertAtSqlLevel()) {
        return false;
      }
   
      BindValue[] currentBindValues = null;
      BindValue[] previousBindValues = null;
   
      int nbrCommands = this.batchedArgs.size();
   
      // Can't have type changes between sets of bindings for this to work...
   
      for (int commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
        Object arg = this.batchedArgs.get(commandIndex);
   
        if (!(arg instanceof String)) {
   
          currentBindValues = ((BatchedBindValues) arg).batchedParameterValues;
   
          // We need to check types each time, as
          // the user might have bound different
          // types in each addBatch()
   
          if (previousBindValues != null) {
            for (int j = 0; j < this.parameterBindings.length; j++) {
              if (currentBindValues[j].bufferType != previousBindValues[j].bufferType) {
                return false;
              }
            }
          }
        }
      }
     
     
     
      return true;
    }
  }
 
  private int locationOfOnDuplicateKeyUpdate = -2;
 
  protected int getLocationOfOnDuplicateKeyUpdate() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      if (this.locationOfOnDuplicateKeyUpdate == -2) {
        this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(this.originalSql);
      }
     
      return this.locationOfOnDuplicateKeyUpdate;
    }
  }
 
  protected boolean isOnDuplicateKeyUpdate() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      return getLocationOfOnDuplicateKeyUpdate() != -1;
    }
  }

 
 

  /**
   *  Computes the maximum parameter set size, and entire batch size given
   *  the number of arguments in the batch.
   * @throws SQLException
   */
  protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      long sizeOfEntireBatch = 1 + /* com_execute */ + 4 /* stmt id */ + 1 /* flags */ + 4 /* batch count padding */;
      long maxSizeOfParameterSet = 0;
     
      for (int i = 0; i < numBatchedArgs; i++) {
        BindValue[] paramArg = ((BatchedBindValues) this.batchedArgs.get(i)).batchedParameterValues;
   
        long sizeOfParameterSet = 0;
       
        sizeOfParameterSet += (this.parameterCount + 7) / 8; // for isNull
       
        sizeOfParameterSet += this.parameterCount * 2; // have to send types
       
        for (int j = 0; j < this.parameterBindings.length; j++) {
          if (!paramArg[j].isNull) {
   
            long size = paramArg[j].getBoundLength();
           
            if (paramArg[j].isLongData) {
              if (size != -1) {
                sizeOfParameterSet += size;
              }
            } else {
              sizeOfParameterSet += size;
            }
          }
        }
       
        sizeOfEntireBatch += sizeOfParameterSet;
       
        if (sizeOfParameterSet > maxSizeOfParameterSet) {
          maxSizeOfParameterSet = sizeOfParameterSet;
        }
      } 
     
      return new long[] {maxSizeOfParameterSet, sizeOfEntireBatch};
    }
  }

  protected int setOneBatchedParameterSet(
      java.sql.PreparedStatement batchedStatement, int batchedParamIndex,
      Object paramSet) throws SQLException {
    BindValue[] paramArg = ((BatchedBindValues) paramSet).batchedParameterValues;
 
    for (int j = 0; j < paramArg.length; j++) {
      if (paramArg[j].isNull) {
        batchedStatement.setNull(batchedParamIndex++, Types.NULL);
      } else {
        if (paramArg[j].isLongData) {
          Object value = paramArg[j].value;
 
          if (value instanceof InputStream) {
            batchedStatement.setBinaryStream(batchedParamIndex++,
                (InputStream) value,
                (int) paramArg[j].bindLength);
          } else {
            batchedStatement.setCharacterStream(
                batchedParamIndex++, (Reader) value,
                (int) paramArg[j].bindLength);
          }
        } else {
 
          switch (paramArg[j].bufferType) {
 
          case MysqlDefs.FIELD_TYPE_TINY:
            batchedStatement.setByte(batchedParamIndex++,
                (byte)paramArg[j].longBinding);
            break;
          case MysqlDefs.FIELD_TYPE_SHORT:
            batchedStatement.setShort(batchedParamIndex++,
                (short)paramArg[j].longBinding);
            break;
          case MysqlDefs.FIELD_TYPE_LONG:
            batchedStatement.setInt(batchedParamIndex++,
                (int)paramArg[j].longBinding);
            break;
          case MysqlDefs.FIELD_TYPE_LONGLONG:
            batchedStatement.setLong(batchedParamIndex++,
                paramArg[j].longBinding);
            break;
          case MysqlDefs.FIELD_TYPE_FLOAT:
            batchedStatement.setFloat(batchedParamIndex++,
                paramArg[j].floatBinding);
            break;
          case MysqlDefs.FIELD_TYPE_DOUBLE:
            batchedStatement.setDouble(batchedParamIndex++,
                paramArg[j].doubleBinding);
            break;
          case MysqlDefs.FIELD_TYPE_TIME:
            batchedStatement.setTime(batchedParamIndex++,
                (Time) paramArg[j].value);
            break;
          case MysqlDefs.FIELD_TYPE_DATE:
            batchedStatement.setDate(batchedParamIndex++,
                (Date) paramArg[j].value);
            break;
          case MysqlDefs.FIELD_TYPE_DATETIME:
          case MysqlDefs.FIELD_TYPE_TIMESTAMP:
            batchedStatement.setTimestamp(batchedParamIndex++,
                (Timestamp) paramArg[j].value);
            break;
          case MysqlDefs.FIELD_TYPE_VAR_STRING:
          case MysqlDefs.FIELD_TYPE_STRING:
          case MysqlDefs.FIELD_TYPE_VARCHAR:
          case MysqlDefs.FIELD_TYPE_DECIMAL:
          case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
            Object value = paramArg[j].value;
 
            if (value instanceof byte[]) {
              batchedStatement.setBytes(batchedParamIndex,
                  (byte[]) value);
            } else {
              batchedStatement.setString(batchedParamIndex,
                  (String) value);
            }
 
            // If we ended up here as a multi-statement, we're not working with a server prepared statement
           
            if (batchedStatement instanceof ServerPreparedStatement) {
              BindValue asBound = ((ServerPreparedStatement) batchedStatement)
                  .getBinding(
                      batchedParamIndex,
                      false);
              asBound.bufferType = paramArg[j].bufferType;
            }
 
            batchedParamIndex++;
 
            break;
          default:
            throw new IllegalArgumentException(
                "Unknown type when re-binding parameter into batched statement for parameter index "
                    + batchedParamIndex);
          }
        }
      }
    }

    return batchedParamIndex;
  }

  protected boolean containsOnDuplicateKeyUpdateInSQL() {
    return this.hasOnDuplicateKeyUpdate;
  }
 
  protected PreparedStatement prepareBatchedInsertSQL(MySQLConnection localConn, int numBatches) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
      try {
        PreparedStatement pstmt = new ServerPreparedStatement(localConn, this.parseInfo.getSqlForBatch(numBatches), this.currentCatalog, this.resultSetConcurrency, this.resultSetType);
        pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys);
       
        return pstmt;
      } catch (UnsupportedEncodingException e) {
        SQLException sqlEx = SQLError.createSQLException("Unable to prepare batch statement", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        sqlEx.initCause(e);
       
        throw sqlEx;
      }
    }
  }
}
TOP

Related Classes of com.mysql.jdbc.ServerPreparedStatement$BatchedBindValues

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.