gt:         synchronized (checkClosed().getConnectionMutex()) {
context: /*
   * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
   *
   * This program is free software; you can redistribute it and/or modify it under
   * the terms of the GNU General Public License, version 2.0, as published by the
   * Free Software Foundation.
   *
   * This program is also distributed with certain software (including but not
   * limited to OpenSSL) that is licensed under separate terms, as designated in a
   * particular file or component or in included license documentation. The
   * authors of MySQL hereby grant you an additional permission to link the
   * program and your derivative works with the separately licensed software that
   * they have included with MySQL.
   *
   * Without limiting anything contained in the foregoing, this file, which is
   * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
   * version 1.0, a copy of which can be found at
   * http://oss.oracle.com/licenses/universal-foss-exception.
   *
   * 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, version 2.0,
   * 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.cj.jdbc;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.io.UnsupportedEncodingException;
  import java.net.URL;
  import java.sql.Date;
  import java.sql.ParameterMetaData;
  import java.sql.SQLException;
  import java.sql.Time;
  import java.sql.Timestamp;
  import java.sql.Wrapper;
  import java.util.ArrayList;
  
  import com.mysql.cj.CancelQueryTask;
  import com.mysql.cj.Messages;
  import com.mysql.cj.MysqlType;
  import com.mysql.cj.ParseInfo;
  import com.mysql.cj.PreparedQuery;
  import com.mysql.cj.ServerPreparedQuery;
  import com.mysql.cj.ServerPreparedQueryBindValue;
  import com.mysql.cj.ServerPreparedQueryBindings;
  import com.mysql.cj.conf.PropertyKey;
  import com.mysql.cj.exceptions.CJException;
  import com.mysql.cj.exceptions.ExceptionFactory;
  import com.mysql.cj.exceptions.ExceptionInterceptor;
  import com.mysql.cj.exceptions.MysqlErrorNumbers;
  import com.mysql.cj.exceptions.WrongArgumentException;
  import com.mysql.cj.jdbc.exceptions.MySQLStatementCancelledException;
  import com.mysql.cj.jdbc.exceptions.MySQLTimeoutException;
  import com.mysql.cj.jdbc.exceptions.SQLError;
  import com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping;
  import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
  import com.mysql.cj.jdbc.result.ResultSetMetaData;
  import com.mysql.cj.protocol.ColumnDefinition;
  import com.mysql.cj.protocol.Message;
  
  /**
   * JDBC Interface for MySQL-4.1 and newer server-side PreparedStatements.
   */
  public class ServerPreparedStatement extends ClientPreparedStatement {
  
      private boolean hasOnDuplicateKeyUpdate = false;
  
      /** Has this prepared statement been marked invalid? */
      private boolean invalid = false;
  
      /** If this statement has been marked invalid, what was the reason? */
      private CJException invalidationException;
  
      protected boolean isCached = false;
  
      /**
       * Creates a prepared statement instance
       * 
       * @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.
       * @param resultSetType
       *            ResultSet type
       * @param resultSetConcurrency
       *            ResultSet concurrency
       * @return new ServerPreparedStatement
       * @throws SQLException
       *             If an error occurs
       */
      protected static ServerPreparedStatement getInstance(JdbcConnection conn, String sql, String catalog, int resultSetType, int resultSetConcurrency)
              throws SQLException {
          return new ServerPreparedStatement(conn, sql, catalog, resultSetType, resultSetConcurrency);
      }
  
      /**
       * 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.
       * @param resultSetType
       *            ResultSet type
       * @param resultSetConcurrency
       *            ResultSet concurrency
       * 
       * @throws SQLException
       *             If an error occurs
       */
      protected ServerPreparedStatement(JdbcConnection conn, String sql, String catalog, int resultSetType, int resultSetConcurrency) throws SQLException {
          super(conn, catalog);
  
          checkNullOrEmptyQuery(sql);
          String statementComment = this.session.getProtocol().getQueryComment();
          ((PreparedQuery<?>) this.query).setOriginalSql(statementComment == null ? sql : "/* " + statementComment + " */ " + sql);
          ((PreparedQuery<?>) this.query).setParseInfo(new ParseInfo(((PreparedQuery<?>) this.query).getOriginalSql(), this.session, this.charEncoding));
  
          this.hasOnDuplicateKeyUpdate = ((PreparedQuery<?>) this.query).getParseInfo().getFirstStmtChar() == 'I' && containsOnDuplicateKeyInString(sql);
  
          try {
              serverPrepare(sql);
          } catch (CJException | SQLException sqlEx) {
              realClose(false, true);
  
              throw SQLExceptionsMapping.translateException(sqlEx, this.exceptionInterceptor);
          }
  
          setResultSetType(resultSetType);
          setResultSetConcurrency(resultSetConcurrency);
  
      }
  
      @Override
      protected void initQuery() {
          this.query = ServerPreparedQuery.getInstance(this.session);
      }
  
      @Override
      public String toString() {
          StringBuilder toStringBuf = new StringBuilder();
  
          toStringBuf.append(this.getClass().getName() + "[");
          toStringBuf.append(((ServerPreparedQuery) this.query).getServerStatementId());
          toStringBuf.append("]: ");
  
          try {
              toStringBuf.append(asSql());
          } catch (SQLException sqlEx) {
              toStringBuf.append(Messages.getString("ServerPreparedStatement.6"));
              toStringBuf.append(sqlEx);
          }
  
          return toStringBuf.toString();
      }
  
      @Override
      public void addBatch() throws SQLException {
          synchronized (checkClosed().getConnectionMutex()) {
              this.query.addBatch(((PreparedQuery<?>) this.query).getQueryBindings().clone());
          }
      }
  
      @Override
      public String asSql(boolean quoteStreamsAndUnknowns) throws SQLException {
  
          synchronized (checkClosed().getConnectionMutex()) {
  
              ClientPreparedStatement pStmtForSub = null;
  
              try {
                  pStmtForSub = ClientPreparedStatement.getInstance(this.connection, ((PreparedQuery<?>) this.query).getOriginalSql(), this.getCurrentCatalog());
  
                  int numParameters = ((PreparedQuery<?>) pStmtForSub.query).getParameterCount();
                  int ourNumParameters = ((PreparedQuery<?>) this.query).getParameterCount();
  
                  ServerPreparedQueryBindValue[] parameterBindings = ((ServerPreparedQuery) this.query).getQueryBindings().getBindValues();
  
                  for (int i = 0; (i < numParameters) && (i < ourNumParameters); i++) {
                      if (parameterBindings[i] != null) {
                          if (parameterBindings[i].isNull()) {
                              pStmtForSub.setNull(i + 1, MysqlType.NULL);
                          } else {
                              ServerPreparedQueryBindValue bindValue = parameterBindings[i];
  
                              //
                              // Handle primitives first
                              //
                              switch (bindValue.bufferType) {
  
                                  case MysqlType.FIELD_TYPE_TINY:
                                      pStmtForSub.setByte(i + 1, (byte) bindValue.longBinding);
                                      break;
                                  case MysqlType.FIELD_TYPE_SHORT:
                                      pStmtForSub.setShort(i + 1, (short) bindValue.longBinding);
                                      break;
                                  case MysqlType.FIELD_TYPE_LONG:
                                      pStmtForSub.setInt(i + 1, (int) bindValue.longBinding);
                                      break;
                                  case MysqlType.FIELD_TYPE_LONGLONG:
                                      pStmtForSub.setLong(i + 1, bindValue.longBinding);
                                      break;
                                  case MysqlType.FIELD_TYPE_FLOAT:
                                      pStmtForSub.setFloat(i + 1, bindValue.floatBinding);
                                      break;
                                  case MysqlType.FIELD_TYPE_DOUBLE:
                                      pStmtForSub.setDouble(i + 1, bindValue.doubleBinding);
                                      break;
                                  default:
                                      pStmtForSub.setObject(i + 1, parameterBindings[i].value);
                                      break;
                              }
                          }
                      }
                  }
  
                  return pStmtForSub.asSql(quoteStreamsAndUnknowns);
              } finally {
                  if (pStmtForSub != null) {
                      try {
                          pStmtForSub.close();
                      } catch (SQLException sqlEx) {
                          // ignore
                      }
                  }
              }
          }
      }
  
      @Override
      protected JdbcConnection checkClosed() {
          if (this.invalid) {
              throw this.invalidationException;
          }
  
          return super.checkClosed();
      }
  
      @Override
      public void clearParameters() {
          synchronized (checkClosed().getConnectionMutex()) {
              ((ServerPreparedQuery) this.query).clearParameters(true);
          }
      }
  
      protected void setClosed(boolean flag) {
          this.isClosed = flag;
      }
  
      @Override
      public void close() throws SQLException {
          JdbcConnection locallyScopedConn = this.connection;
  
          if (locallyScopedConn == null) {
              return; // already closed
          }
  
          synchronized (locallyScopedConn.getConnectionMutex()) {
  
              if (this.isCached && isPoolable() && !this.isClosed) {
                  clearParameters();
  
                  this.isClosed = true;
  
                  this.connection.recachePreparedStatement(this);
                  return;
              }
  
              this.isClosed = false;
              realClose(true, true);
          }
      }
  
      @Override
      protected long[] executeBatchSerially(int batchTimeout) throws SQLException {
          synchronized (checkClosed().getConnectionMutex()) {
              JdbcConnection locallyScopedConn = this.connection;
  
              if (locallyScopedConn.isReadOnly()) {
                  throw SQLError.createSQLException(Messages.getString("ServerPreparedStatement.2") + Messages.getString("ServerPreparedStatement.3"),
                          MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
              }
  
              clearWarnings();
  
              // Store this for later, we're going to 'swap' them out
              // as we execute each batched statement...
              ServerPreparedQueryBindValue[] oldBindValues = ((ServerPreparedQuery) this.query).getQueryBindings().getBindValues();
  
              try {
                  long[] updateCounts = null;
  
                  if (this.query.getBatchedArgs() != null) {
                      int nbrCommands = this.query.getBatchedArgs().size();
                      updateCounts = new long[nbrCommands];
  
                      if (this.retrieveGeneratedKeys) {
                          this.batchedGeneratedKeys = new ArrayList<>(nbrCommands);
                      }
  
                      for (int i = 0; i < nbrCommands; i++) {
                          updateCounts[i] = -3;
                      }
  
                      SQLException sqlEx = null;
  
                      int commandIndex = 0;
  
                      ServerPreparedQueryBindValue[] previousBindValuesForBatch = null;
  
                      CancelQueryTask timeoutTask = null;
  
                      try {
                          timeoutTask = startQueryTimer(this, batchTimeout);
  
                          for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
                              Object arg = this.query.getBatchedArgs().get(commandIndex);
  
                              try {
                                  if (arg instanceof String) {
                                      updateCounts[commandIndex] = executeUpdateInternal((String) arg, true, this.retrieveGeneratedKeys);
  
                                      // limit one generated key per OnDuplicateKey statement
                                      getBatchedGeneratedKeys(this.results.getFirstCharOfQuery() == 'I' && containsOnDuplicateKeyInString((String) arg) ? 1 : 0);
                                  } else {
                                      ((ServerPreparedQuery) this.query).setQueryBindings((ServerPreparedQueryBindings) arg);
                                      ServerPreparedQueryBindValue[] parameterBindings = ((ServerPreparedQuery) this.query).getQueryBindings().getBindValues();
  
                                      // 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 < parameterBindings.length; j++) {
                                              if (parameterBindings[j].bufferType != previousBindValuesForBatch[j].bufferType) {
                                                  ((ServerPreparedQuery) this.query).getQueryBindings().getSendTypesToServer().set(true);
  
                                                  break;
                                              }
                                          }
                                      }
  
                                      try {
                                          updateCounts[commandIndex] = executeUpdateInternal(false, true);
                                      } finally {
                                          previousBindValuesForBatch = parameterBindings;
                                      }
  
                                      // limit one generated key per OnDuplicateKey statement
                                      getBatchedGeneratedKeys(containsOnDuplicateKeyUpdateInSQL() ? 1 : 0);
                                  }
                              } catch (SQLException ex) {
                                  updateCounts[commandIndex] = EXECUTE_FAILED;
  
                                  if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException)
                                          && !hasDeadlockOrTimeoutRolledBackTx(ex)) {
                                      sqlEx = ex;
                                  } else {
                                      long[] newUpdateCounts = new long[commandIndex];
                                      System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex);
  
                                      throw SQLError.createBatchUpdateException(ex, newUpdateCounts, this.exceptionInterceptor);
                                  }
                              }
                          }
                      } finally {
                          stopQueryTimer(timeoutTask, false, false);
                          resetCancelledState();
                      }
  
                      if (sqlEx != null) {
                          throw SQLError.createBatchUpdateException(sqlEx, updateCounts, this.exceptionInterceptor);
                      }
                  }
  
                  return (updateCounts != null) ? updateCounts : new long[0];
              } finally {
                  ((ServerPreparedQuery) this.query).getQueryBindings().setBindValues(oldBindValues);
                  ((ServerPreparedQuery) this.query).getQueryBindings().getSendTypesToServer().set(true);
  
                  clearBatch();
              }
          }
      }
  
      private static SQLException appendMessageToException(SQLException sqlEx, String messageToAppend, ExceptionInterceptor interceptor) {
          String sqlState = sqlEx.getSQLState();
          int vendorErrorCode = sqlEx.getErrorCode();
  
          SQLException sqlExceptionWithNewMessage = SQLError.createSQLException(sqlEx.getMessage() + messageToAppend, sqlState, vendorErrorCode, interceptor);
          sqlExceptionWithNewMessage.setStackTrace(sqlEx.getStackTrace());
  
          return sqlExceptionWithNewMessage;
      }
  
      @Override
      protected <M extends Message> com.mysql.cj.jdbc.result.ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, M sendPacket,
              boolean createStreamingResultSet, boolean queryIsSelectOnly, ColumnDefinition metadata, boolean isBatch) throws SQLException {
          synchronized (checkClosed().getConnectionMutex()) {
              ((PreparedQuery<?>) this.query).getQueryBindings()
                      .setNumberOfExecutions(((PreparedQuery<?>) this.query).getQueryBindings().getNumberOfExecutions() + 1);
  
              // We defer to server-side execution
              try {
                  return serverExecute(maxRowsToRetrieve, createStreamingResultSet, metadata);
              } catch (SQLException sqlEx) {
                  // don't wrap SQLExceptions
                  if (this.session.getPropertySet().getBooleanProperty(PropertyKey.enablePacketDebug).getValue()) {
                      this.session.dumpPacketRingBuffer();
                  }
  
                  if (this.dumpQueriesOnException.getValue()) {
                      String extractedSql = toString();
                      StringBuilder messageBuf = new StringBuilder(extractedSql.length() + 32);
                      messageBuf.append("\n\nQuery being executed when exception was thrown:\n");
                      messageBuf.append(extractedSql);
                      messageBuf.append("\n\n");
  
                      sqlEx = appendMessageToException(sqlEx, messageBuf.toString(), this.exceptionInterceptor);
                  }
  
                  throw sqlEx;
              } catch (Exception ex) {
                  if (this.session.getPropertySet().getBooleanProperty(PropertyKey.enablePacketDebug).getValue()) {
                      this.session.dumpPacketRingBuffer();
                  }
  
                  SQLException sqlEx = SQLError.createSQLException(ex.toString(), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, ex, this.exceptionInterceptor);
  
                  if (this.dumpQueriesOnException.getValue()) {
                      String extractedSql = toString();
                      StringBuilder messageBuf = new StringBuilder(extractedSql.length() + 32);
                      messageBuf.append("\n\nQuery being executed when exception was thrown:\n");
                      messageBuf.append(extractedSql);
                      messageBuf.append("\n\n");
  
                      sqlEx = appendMessageToException(sqlEx, messageBuf.toString(), this.exceptionInterceptor);
                  }
  
                  throw sqlEx;
              }
          }
      }
  
      /**
       * 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 {@link ServerPreparedQueryBindValue}
       * @throws SQLException
       *             if a database access error occurs or this method is called on a closed PreparedStatement
       */
      protected ServerPreparedQueryBindValue getBinding(int parameterIndex, boolean forLongData) throws SQLException {
          synchronized (checkClosed().getConnectionMutex()) {
              int i = getCoreParameterIndex(parameterIndex);
              return ((ServerPreparedQuery) this.query).getQueryBindings().getBinding(i, forLongData);
          }
      }
  
      @Override
      public java.sql.ResultSetMetaData getMetaData() throws SQLException {
  