content: /*
   * Copyright (c) 2002, 2021, Oracle and/or its affiliates.
   *
   * 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 testsuite.regression;
  
  import static org.junit.jupiter.api.Assertions.assertEquals;
  import static org.junit.jupiter.api.Assertions.assertFalse;
  import static org.junit.jupiter.api.Assertions.assertNotEquals;
  import static org.junit.jupiter.api.Assertions.assertNull;
  import static org.junit.jupiter.api.Assertions.assertTrue;
  
  import java.io.BufferedInputStream;
  import java.io.FileInputStream;
  import java.sql.Connection;
  import java.sql.PreparedStatement;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.sql.SQLFeatureNotSupportedException;
  import java.sql.SQLNonTransientConnectionException;
  import java.sql.Statement;
  import java.util.Properties;
  import java.util.concurrent.Callable;
  
  import javax.sql.ConnectionEvent;
  import javax.sql.ConnectionEventListener;
  import javax.sql.ConnectionPoolDataSource;
  import javax.sql.PooledConnection;
  
  import org.junit.jupiter.api.AfterEach;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.api.Test;
  
  import com.mysql.cj.NativeSession;
  import com.mysql.cj.ServerVersion;
  import com.mysql.cj.conf.PropertyDefinitions.SslMode;
  import com.mysql.cj.conf.PropertyKey;
  import com.mysql.cj.jdbc.Blob;
  import com.mysql.cj.jdbc.CallableStatementWrapper;
  import com.mysql.cj.jdbc.Clob;
  import com.mysql.cj.jdbc.CommentClientInfoProvider;
  import com.mysql.cj.jdbc.ConnectionImpl;
  import com.mysql.cj.jdbc.ConnectionWrapper;
  import com.mysql.cj.jdbc.DatabaseMetaData;
  import com.mysql.cj.jdbc.DatabaseMetaDataUsingInfoSchema;
  import com.mysql.cj.jdbc.JdbcConnection;
  import com.mysql.cj.jdbc.JdbcPropertySetImpl;
  import com.mysql.cj.jdbc.MysqlConnectionPoolDataSource;
  import com.mysql.cj.jdbc.MysqlSQLXML;
  import com.mysql.cj.jdbc.MysqlXADataSource;
  import com.mysql.cj.jdbc.NClob;
  import com.mysql.cj.jdbc.PreparedStatementWrapper;
  import com.mysql.cj.jdbc.StatementWrapper;
  import com.mysql.cj.jdbc.exceptions.CommunicationsException;
  import com.mysql.cj.jdbc.exceptions.PacketTooBigException;
  
  import testsuite.BaseTestCase;
  
  /**
   * Tests a PooledConnection implementation provided by a JDBC driver. Test case provided by Johnny Macchione from bug database record BUG#884. According to
   * the JDBC 2.0 specification:
   * 
   * <p>
   * "Each call to PooledConnection.getConnection() must return a newly constructed Connection object that exhibits the default Connection behavior. Only the most
   * recent Connection object produced from a particular PooledConnection is open. An existing Connection object is automatically closed, if the getConnection()
   * method of its associated Pooled-Connection is called again, before it has been explicitly closed by the application. This gives the application server a way
   * to 'take away' a Connection from the application if it wishes, and give it out to someone else. This capability will not likely be used frequently in
   * practice."
   * </p>
   * 
   * <p>
   * "When the application calls Connection.close(), an event is triggered that tells the connection pool it can recycle the physical database connection. In
   * other words, the event signals the connection pool that the PooledConnection object which originally produced the Connection object generating the event can
   * be put back in the connection pool."
   * </p>
   * 
   * <p>
   * "A Connection-EventListener will also be notified when a fatal error occurs, so that it can make a note not to put a bad PooledConnection object back in the
   * cache when the application finishes using it. When an error occurs, the ConnectionEventListener is notified by the JDBC driver, just before the driver throws
   * an SQLException to the application to notify it of the same error. Note that automatic closing of a Connection object as discussed in the previous section
   * does not generate a connection close event."
   * </p>
   * The JDBC 3.0 specification states the same in other words:
   * 
   * <p>
   * "The Connection.close method closes the logical handle, but the physical connection is maintained. The connection pool manager is notified that the
   * underlying PooledConnection object is now available for reuse. If the application attempts to reuse the logical handle, the Connection implementation throws
   * an SQLException."
   * </p>
   * 
   * <p>
   * "For a given PooledConnection object, only the most recently produced logical Connection object will be valid. Any previously existing Connection object is
   * automatically closed when the associated PooledConnection.getConnection method is called. Listeners (connection pool managers) are not notified in this case.
   * This gives the application server a way to take a connection away from a client. This is an unlikely scenario but may be useful if the application server is
   * trying to force an orderly shutdown."
   * </p>
   * 
   * <p>
   * "A connection pool manager shuts down a physical connection by calling the method PooledConnection.close. This method is typically called only in certain
   * circumstances: when the application server is undergoing an orderly shutdown, when the connection cache is being reinitialized, or when the application
   * server receives an event indicating that an unrecoverable error has occurred on the connection."
   * </p>
   * Even though the specification isn't clear about it, I think it is no use generating a close event when calling the method PooledConnection.close(), even if a
   * logical Connection is open for this PooledConnection, bc the PooledConnection will obviously not be returned to the pool.
   */
  public final class PooledConnectionRegressionTest extends BaseTestCase {
      private ConnectionPoolDataSource cpds;
  
      // Count nb of closeEvent.
      protected int closeEventCount;
  
      // Count nb of connectionErrorEvent
      protected int connectionErrorEventCount;
  
      /**
       * Set up test case before a test is run.
       * 
       * @throws Exception
       */
      @BeforeEach
      public void setUp() throws Exception {
          // Reset event count.
          this.closeEventCount = 0;
          this.connectionErrorEventCount = 0;
  
          MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
  
          ds.setURL(BaseTestCase.dbUrl);
          ds.<SslMode>getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED);
          ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true);
  
          this.cpds = ds;
      }
  
      /**
       * After the test is run.
       * 
       * @throws Exception
       */
      @AfterEach
      public void tearDown() throws Exception {
          this.cpds = null;
      }
  
      /**
       * Tests fix for BUG#7136 ... Statement.getConnection() returning physical connection instead of logical connection.
       * 
       * @throws Exception
       */
      @Test
      public void testBug7136() throws Exception {
          final ConnectionEventListener conListener = new ConnectionListener();
          PooledConnection pc = null;
          this.closeEventCount = 0;
  
          try {
              pc = this.cpds.getPooledConnection();
  
              pc.addConnectionEventListener(conListener);
  
              Connection _conn = pc.getConnection();
  
              Connection connFromStatement = _conn.createStatement().getConnection();
  
              // This should generate a close event.
  
              connFromStatement.close();
  
              assertEquals(1, this.closeEventCount, "One close event should've been registered");
  
              this.closeEventCount = 0;
  
              _conn = pc.getConnection();
  
              Connection connFromPreparedStatement = _conn.prepareStatement("SELECT 1").getConnection();
  
              // This should generate a close event.
  
              connFromPreparedStatement.close();
  
              assertEquals(1, this.closeEventCount, "One close event should've been registered");
  
          } finally {
              if (pc != null) {
                  pc.close();
              }
          }
      }
  
      /**
       * Test the nb of closeEvents generated when a Connection is reclaimed. No event should be generated in that case.
       * 
       * @throws Exception
       */
      @Test
      public void testConnectionReclaim() throws Exception {
          final ConnectionEventListener conListener = new ConnectionListener();
          PooledConnection pc = null;
          final int NB_TESTS = 5;
  
          try {
              pc = this.cpds.getPooledConnection();
  
              pc.addConnectionEventListener(conListener);
  
              for (int i = 0; i < NB_TESTS; i++) {
                  Connection _conn = pc.getConnection();
  
                  try {
                      // Try to reclaim connection.
                      System.out.println("Before connection reclaim.");
  
                      _conn = pc.getConnection();
  
                      System.out.println("After connection reclaim.");
                  } finally {
                      if (_conn != null) {
                          System.out.println("Before connection.close().");
  
                          // This should generate a close event.
                          _conn.close();
  
                          System.out.println("After connection.close().");
                      }
                  }
              }
          } finally {
              if (pc != null) {
                  System.out.println("Before pooledConnection.close().");
                  // This should not generate a close event.
                  pc.close();
                  System.out.println("After pooledConnection.close().");
              }
          }
  
          assertEquals(NB_TESTS, this.closeEventCount, "Wrong nb of CloseEvents: ");
      }
  
      /**
       * Tests that PacketTooLargeException doesn't clober the connection.
       * 
       * @throws Exception
       */
      @Test
      public void testPacketTooLargeException() throws Exception {
          final ConnectionEventListener conListener = new ConnectionListener();
          PooledConnection pc = null;
  
          pc = this.cpds.getPooledConnection();
  
          pc.addConnectionEventListener(conListener);
  
          createTable("testPacketTooLarge", "(field1 LONGBLOB)");
  
          Connection connFromPool = pc.getConnection();
          PreparedStatement pstmtFromPool = ((ConnectionWrapper) connFromPool).clientPrepare("INSERT INTO testPacketTooLarge VALUES (?)");
  
          this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'");
          this.rs.next();
  
          int maxAllowedPacket = this.rs.getInt(2);
  
          int numChars = (int) (maxAllowedPacket * 1.2);
  
          pstmtFromPool.setBinaryStream(1, new BufferedInputStream(new FileInputStream(newTempBinaryFile("testPacketTooLargeException", numChars))), numChars);
  
          assertThrows(PacketTooBigException.class, () -> {
              pstmtFromPool.executeUpdate();
              return null;
          });
  
          // This should still work okay, even though the last query on the same connection didn't...
          this.rs = connFromPool.createStatement().executeQuery("SELECT 1");
  
          assertEquals(0, this.connectionErrorEventCount);
          assertEquals(0, this.closeEventCount);
      }
  
      /**
       * Test the nb of closeEvents generated by a PooledConnection. A JDBC-compliant driver should only generate 1 closeEvent each time connection.close() is
       * called.
       * 
       * @throws Exception
       */
      @Test
      public void testCloseEvent() throws Exception {
          final ConnectionEventListener conListener = new ConnectionListener();
          PooledConnection pc = null;
          final int NB_TESTS = 5;
  
          try {
              pc = this.cpds.getPooledConnection();
  
              pc.addConnectionEventListener(conListener);
  
              for (int i = 0; i < NB_TESTS; i++) {
                  Connection pConn = pc.getConnection();
  
                  System.out.println("Before connection.close().");
  
                  // This should generate a close event.
                  pConn.close();
  
                  System.out.println("After connection.close().");
              }
          } finally {
              if (pc != null) {
                  try {
                      System.out.println("Before pooledConnection.close().");
  
                      // This should not generate a close event.
                      pc.close();
  
                      System.out.println("After pooledConnection.close().");
                  } catch (SQLException ex) {
                      ex.printStackTrace();
                  }
              }
          }
          assertEquals(NB_TESTS, this.closeEventCount, "Wrong nb of CloseEvents: ");
      }
  
      /**
       * Listener for PooledConnection events.
       */
      protected final class ConnectionListener implements ConnectionEventListener {
          public void connectionClosed(ConnectionEvent event) {
              PooledConnectionRegressionTest.this.closeEventCount++;
              System.out.println(PooledConnectionRegressionTest.this.closeEventCount + " - Connection closed.");
          }
  
          public void connectionErrorOccurred(ConnectionEvent event) {
              PooledConnectionRegressionTest.this.connectionErrorEventCount++;
              System.out.println("Connection error: " + event.getSQLException());
          }
      }
  
      /**
       * Tests fix for BUG#35489 - Prepared statements from pooled connections cause NPE when closed() under JDBC4
       * 
       * @throws Exception
       */
      @Test
      public void testBug35489() throws Exception {
          MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource();
          pds.setUrl(dbUrl);
          pds.<SslMode>getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED);
          pds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true);
          this.pstmt = pds.getPooledConnection().getConnection().prepareStatement("SELECT 1");
          this.pstmt.execute();
          this.pstmt.close();
  
          MysqlXADataSource xads = new MysqlXADataSource();
          xads.setUrl(dbUrl);
          xads.<SslMode>getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED);
          xads.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true);
          this.pstmt = xads.getXAConnection().getConnection().prepareStatement("SELECT 1");
          this.pstmt.execute();
          this.pstmt.close();
  
          xads = new MysqlXADataSource();
          xads.setUrl(dbUrl);
          xads.<SslMode>getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED);
          xads.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true);
          xads.getProperty(PropertyKey.pinGlobalTxToPhysicalConnection).setValue(true);
          this.pstmt = xads.getXAConnection().getConnection().prepareStatement("SELECT 1");
          this.pstmt.execute();
          this.pstmt.close();
      }
  
      @SuppressWarnings("deprecation")
      @Test
      public void testConnectionWrapperMethods() throws Exception {
          PooledConnection pc = null;
          pc = this.cpds.getPooledConnection();
          ConnectionWrapper cw = (ConnectionWrapper) pc.getConnection();
  
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepare("SELECT 1").getClass());
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepare("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepareStatement("SELECT 1").getClass());
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepareStatement("SELECT 1", new int[] { 1 }).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepareStatement("SELECT 1", new String[] { "1" }).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.clientPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY).getClass());
          assertEquals(PreparedStatementWrapper.class,
                  cw.clientPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.serverPrepareStatement("SELECT 1").getClass());
          assertEquals(PreparedStatementWrapper.class, cw.serverPrepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.serverPrepareStatement("SELECT 1", new int[] { 1 }).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.serverPrepareStatement("SELECT 1", new String[] { "1" }).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.serverPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY).getClass());
          assertEquals(PreparedStatementWrapper.class,
                  cw.serverPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.prepareStatement("SELECT 1").getClass());
          assertEquals(PreparedStatementWrapper.class, cw.prepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.prepareStatement("SELECT 1", new int[] { 1 }).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.prepareStatement("SELECT 1", new String[] { "1" }).getClass());
          assertEquals(PreparedStatementWrapper.class, cw.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY).getClass());
          assertEquals(PreparedStatementWrapper.class,
                  cw.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT).getClass());
  
          assertEquals(CallableStatementWrapper.class, cw.prepareCall("SELECT 1").getClass());
          assertEquals(CallableStatementWrapper.class, cw.prepareCall("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY).getClass());
          assertEquals(CallableStatementWrapper.class,
                  cw.prepareCall("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT).getClass());
  
          assertEquals(StatementWrapper.class, cw.createStatement().getClass());
          assertEquals(StatementWrapper.class, cw.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY).getClass());
          assertEquals(StatementWrapper.class,
                  cw.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT).getClass());
  
          assertEquals(26, cw.getActiveStatementCount());
  
          assertThrows(SQLFeatureNotSupportedException.class, new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createArrayOf(String.class.getName(), new Object[] {}).getClass();
                  return null;
              }
          });
          assertThrows(SQLFeatureNotSupportedException.class, new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStruct(String.class.getName(), new Object[] {}).getClass();
                  return null;
              }
          });
  
          assertEquals(Blob.class, cw.createBlob().getClass());
          assertEquals(Clob.class, cw.createClob().getClass());
          assertEquals(NClob.class, cw.createNClob().getClass());
          assertEquals(MysqlSQLXML.class, cw.createSQLXML().getClass());
          assertEquals(ConnectionImpl.class, cw.getActiveMySQLConnection().getClass());
  
          assertEquals(this.conn.getAutoCommit(), cw.getAutoCommit());
          assertEquals(((JdbcConnection) this.conn).getAutoIncrementIncrement(), cw.getAutoIncrementIncrement());
          assertEquals(((JdbcConnection) this.conn).getCatalog(), cw.getCatalog());
          assertEquals(((JdbcConnection) this.conn).getCharacterSetMetadata(), cw.getCharacterSetMetadata());
          assertEquals(((JdbcConnection) this.conn).getHoldability(), cw.getHoldability());
          assertEquals(((JdbcConnection) this.conn).getHost(), cw.getHost());
          assertEquals(((JdbcConnection) this.conn).getHostPortPair(), cw.getHostPortPair());
          assertEquals(Properties.class, cw.getProperties().getClass());
          assertEquals(JdbcPropertySetImpl.class, cw.getPropertySet().getClass());
          assertEquals(((JdbcConnection) this.conn).getSchema(), cw.getSchema());
          assertEquals(((JdbcConnection) this.conn).getServerVersion().toString(), cw.getServerVersion().toString());
          assertEquals(NativeSession.class, cw.getSession().getClass());
          assertEquals(((JdbcConnection) this.conn).getSessionMaxRows(), cw.getSessionMaxRows());
          assertEquals(((JdbcConnection) this.conn).getURL(), cw.getURL());
          assertEquals(((JdbcConnection) this.conn).getUser(), cw.getUser());
          assertFalse(cw.hasTriedMaster());
          assertFalse(cw.isClosed());
          assertFalse(cw.isInGlobalTx());
          assertFalse(cw.isSourceConnection());
          assertFalse(cw.isProxySet());
          assertFalse(cw.isReadOnly());
          assertFalse(cw.isReadOnly(false));
          assertFalse(cw.isReadOnly(true));
          assertEquals(isMysqlRunningLocally(), cw.isServerLocal());
          assertTrue(cw.isValid(10));
          assertTrue(cw.isWrapperFor(Connection.class));
          assertEquals(((JdbcConnection) this.conn).lowerCaseTableNames(), cw.lowerCaseTableNames());
  
          assertEquals(CommentClientInfoProvider.class, cw.getClientInfoProviderImpl().getClass());
          Properties ci1 = new Properties();
          ci1.setProperty("k1", "v1");
          ci1.setProperty("k2", "v2");
          cw.setClientInfo(ci1);
          cw.setClientInfo("k3", "v3");
          Properties ci2 = cw.getClientInfo();
          assertFalse(ci1.equals(ci2));
          assertEquals("v1", cw.getClientInfo("k1"));
          assertEquals("v2", cw.getClientInfo("k2"));
          assertEquals("v3", cw.getClientInfo("k3"));
  
          String comment = cw.getStatementComment();
          assertEquals("k3=v3, k2=v2, k1=v1", comment);
          cw.setStatementComment("Test comment");
          assertNotEquals(((JdbcConnection) this.conn).getStatementComment(), cw.getStatementComment());
  
          assertEquals(ConnectionImpl.class, cw.getConnectionMutex().getClass());
          assertNull(cw.getExceptionInterceptor());
          assertEquals(((JdbcConnection) this.conn).getNetworkTimeout(), cw.getNetworkTimeout());
          assertEquals(((JdbcConnection) this.conn).getTypeMap(), cw.getTypeMap());
          assertNull(cw.getWarnings());
  
          // testsuite is built upon non-SSL default connection with additional useSSL=false&allowPublicKeyRetrieval=true properties
          assertFalse(cw.hasSameProperties((JdbcConnection) this.conn));
  
          assertTrue(cw.isSameResource((JdbcConnection) this.conn));
          assertEquals(((JdbcConnection) this.conn).nativeSQL("SELECT 1"), cw.nativeSQL("SELECT 1"));
  
          assertEquals(cw.getServerVersion().meetsMinimum(new ServerVersion(8, 0, 3)) ? DatabaseMetaDataUsingInfoSchema.class : DatabaseMetaData.class,
                  cw.getMetaData().getClass());
  
          // TODO find a way to test following methods
          //        cw.getId();
          //        cw.getIdleFor();
          //        cw.getMetadataSafeStatement();
          //        cw.getMultiHostSafeProxy();
          //        cw.resetServerState();
  
          cw.setCatalog(this.dbName);
          cw.setFailedOver(false);
          cw.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
          cw.setInGlobalTx(false);
          // TODO find a way to test following methods
          //        cw.setNetworkTimeout(executor, milliseconds);
          //        cw.setProxy(this.conn);
          //        cw.setReadOnly(readOnly);
          //        cw.setReadOnlyInternal(readOnlyFlag);
          //        cw.setSchema(schema);
          //        cw.setSessionMaxRows(max);
          //        cw.setTypeMap(map);
          assertEquals(((JdbcConnection) this.conn).storesLowerCaseTableName(), cw.storesLowerCaseTableName());
  
          // TODO find a way to test following methods
          //        cw.getQueryInterceptorsInstances();
          //        cw.initializeSafeQueryInterceptors();
          //        cw.unSafeQueryInterceptors();
          //        cw.unwrap(iface);
          //        cw.initializeResultsMetadataFromCache(sql, cachedMetaData, resultSet);
          //        cw.getCachedMetaData(sql);
  
          cw.setAutoCommit(false);
          cw.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
          assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, cw.getTransactionIsolation());
          cw.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
          assertEquals(Connection.TRANSACTION_READ_COMMITTED, cw.getTransactionIsolation());
          // TODO find a way to test following methods
          //cw.transactionBegun();
          //cw.transactionCompleted();
          //        cw.commit();
          //        cw.rollback();
          //        cw.rollback(arg0);
          //        cw.setSavepoint();
          //        cw.setSavepoint(arg0);
          //        cw.releaseSavepoint(arg0);
          cw.setAutoCommit(true);
  
          // TODO find a way to test following methods
          //        cw.registerStatement(this.stmt);
          //        cw.unregisterStatement(this.stmt);
          //        cw.decachePreparedStatement(this.pstmt);
          //        cw.recachePreparedStatement(this.pstmt);
  
          // TODO find a way to test following methods
          //        cw.clearHasTriedMaster();
          //        cw.clearWarnings();
          //        cw.ping();
          //        cw.pingInternal(checkForClosedConnection, timeoutMillis);
          //        cw.createNewIO(isForReconnect);
          //        cw.changeUser(userName, newPassword);
          //        cw.checkClosed();
  
          cw.close();
          assertEquals(26, cw.getActiveStatementCount()); // TODO why are they still active? Active statements should be cleaned when connection is returned to pool.
          checkConnectionReturnedToPool(cw);
  
          cw.normalClose();
          assertEquals(0, cw.getActiveStatementCount());
          checkReallyClosedConnection(cw);
  
          // TODO find a way to test following methods
          //        cw.realClose(calledExplicitly, issueRollback, skipLocalTeardown, reason);
          //        cw.cleanup(whyCleanedUp);
          //        cw.abort(executor);
          //        cw.abortInternal();
      }
  
      @SuppressWarnings("deprecation")
      private void checkConnectionReturnedToPool(ConnectionWrapper cw) throws Exception {
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepare("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepare("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", new int[] { 1 });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", new String[] { "1" });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", new int[] { 1 });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", new String[] { "1" });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", new int[] { 1 });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", new String[] { "1" });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareCall("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareCall("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareCall("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStatement();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createArrayOf(String.class.getName(), new Object[] {}).getClass();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStruct(String.class.getName(), new Object[] {}).getClass();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createBlob();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createClob();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createNClob();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createSQLXML();
                  return null;
              }
          });
  
          assertEquals(ConnectionImpl.class, cw.getActiveMySQLConnection().getClass());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getAutoCommit();
                  return null;
              }
          });
  
          assertEquals(((JdbcConnection) this.conn).getAutoIncrementIncrement(), cw.getAutoIncrementIncrement());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getCatalog();
                  return null;
              }
          });
  
          assertEquals(((JdbcConnection) this.conn).getCharacterSetMetadata(), cw.getCharacterSetMetadata());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getHoldability();
                  return null;
              }
          });
          assertEquals(((JdbcConnection) this.conn).getHost(), cw.getHost());
          assertEquals(((JdbcConnection) this.conn).getHostPortPair(), cw.getHostPortPair());
          assertEquals(Properties.class, cw.getProperties().getClass());
          assertEquals(JdbcPropertySetImpl.class, cw.getPropertySet().getClass());
          assertEquals(((JdbcConnection) this.conn).getSchema(), cw.getSchema());
          assertEquals(((JdbcConnection) this.conn).getServerVersion().toString(), cw.getServerVersion().toString());
          assertEquals(NativeSession.class, cw.getSession().getClass());
          assertEquals(((JdbcConnection) this.conn).getSessionMaxRows(), cw.getSessionMaxRows());
          assertEquals(((JdbcConnection) this.conn).getURL(), cw.getURL());
          assertEquals(((JdbcConnection) this.conn).getUser(), cw.getUser());
          assertFalse(cw.hasTriedMaster());
          assertTrue(cw.isClosed());
          assertFalse(cw.isInGlobalTx());
          assertFalse(cw.isSourceConnection());
          assertFalse(cw.isProxySet());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isReadOnly();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isReadOnly(false);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isReadOnly(true);
                  return null;
              }
          });
          assertEquals(isMysqlRunningLocally(), cw.isServerLocal());
          assertTrue(cw.isValid(10));
          assertTrue(cw.isWrapperFor(Connection.class));
          assertEquals(((JdbcConnection) this.conn).lowerCaseTableNames(), cw.lowerCaseTableNames());
  
          assertEquals(CommentClientInfoProvider.class, cw.getClientInfoProviderImpl().getClass());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  Properties ci1 = new Properties();
                  ci1.setProperty("k1", "v1");
                  cw.setClientInfo(ci1);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setClientInfo("k4", "v4");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getClientInfo();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getClientInfo("k1");
                  return null;
              }
          });
  
          assertEquals(ConnectionImpl.class, cw.getConnectionMutex().getClass());
          assertNull(cw.getExceptionInterceptor());
          assertEquals(((JdbcConnection) this.conn).getNetworkTimeout(), cw.getNetworkTimeout());
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getTypeMap();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getWarnings();
                  return null;
              }
          });
  
          // testsuite is built upon non-SSL default connection with additional useSSL=false&allowPublicKeyRetrieval=true properties
          assertFalse(cw.hasSameProperties((JdbcConnection) this.conn));
  
          assertTrue(cw.isSameResource((JdbcConnection) this.conn));
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.nativeSQL("SELECT 1");
                  return null;
              }
          });
  
          String comment = cw.getStatementComment();
          assertEquals("Test comment", comment);
          cw.setStatementComment("Test comment 2");
          assertNotEquals(((JdbcConnection) this.conn).getStatementComment(), cw.getStatementComment());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getMetaData();
                  return null;
              }
          });
  
          // TODO find a way to test following methods
          //        cw.getId();
          //        cw.getIdleFor();
          //        cw.getMetadataSafeStatement();
          //        cw.getMultiHostSafeProxy();
          //        cw.resetServerState();
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              @SuppressWarnings("synthetic-access")
              public Void call() throws Exception {
                  cw.setCatalog(PooledConnectionRegressionTest.this.dbName);
                  return null;
              }
          });
  
          cw.setFailedOver(false);
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
  
          cw.setInGlobalTx(false);
          // TODO find a way to test following methods
          //        cw.setNetworkTimeout(executor, milliseconds);
          //        cw.setProxy(this.conn);
          //        cw.setReadOnly(readOnly);
          //        cw.setReadOnlyInternal(readOnlyFlag);
          //        cw.setSchema(schema);
          //        cw.setSessionMaxRows(max);
          //        cw.setTypeMap(map);
          assertEquals(((JdbcConnection) this.conn).storesLowerCaseTableName(), cw.storesLowerCaseTableName());
  
          // TODO find a way to test following methods
          //        cw.getQueryInterceptorsInstances();
          //        cw.initializeSafeQueryInterceptors();
          //        cw.unSafeQueryInterceptors();
          //        cw.unwrap(iface);
          //        cw.initializeResultsMetadataFromCache(sql, cachedMetaData, resultSet);
          //        cw.getCachedMetaData(sql);
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setAutoCommit(false);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getTransactionIsolation();
                  return null;
              }
          });
          cw.transactionBegun();
          cw.transactionCompleted();
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.commit();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.rollback();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.rollback(null);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setSavepoint();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setSavepoint("SP1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.releaseSavepoint(null);
                  return null;
              }
          });
  
          // TODO find a way to test following methods
          //        cw.registerStatement(this.stmt);
          //        cw.unregisterStatement(this.stmt);
          //        cw.decachePreparedStatement(this.pstmt);
          //        cw.recachePreparedStatement(this.pstmt);
          //        cw.clearHasTriedMaster();
          //        cw.clearWarnings();
          //        cw.ping();
          //        cw.pingInternal(checkForClosedConnection, timeoutMillis);
          //        cw.createNewIO(isForReconnect);
          //        cw.changeUser(userName, newPassword);
          //        cw.checkClosed();
          //        cw.realClose(calledExplicitly, issueRollback, skipLocalTeardown, reason);
          //        cw.cleanup(whyCleanedUp);
          //        cw.abort(executor);
          //        cw.abortInternal();
      }
  
      @SuppressWarnings("deprecation")
      private void checkReallyClosedConnection(ConnectionWrapper cw) throws Exception {
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepare("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepare("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", new int[] { 1 });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", new String[] { "1" });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.clientPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", new int[] { 1 });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", new String[] { "1" });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.serverPrepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", new int[] { 1 });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", new String[] { "1" });
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareCall("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareCall("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.prepareCall("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStatement();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createArrayOf(String.class.getName(), new Object[] {}).getClass();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createStruct(String.class.getName(), new Object[] {}).getClass();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createBlob();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createClob();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createNClob();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.createSQLXML();
                  return null;
              }
          });
  
          assertEquals(ConnectionImpl.class, cw.getActiveMySQLConnection().getClass());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getAutoCommit();
                  return null;
              }
          });
  
          assertEquals(((JdbcConnection) this.conn).getAutoIncrementIncrement(), cw.getAutoIncrementIncrement());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getCatalog();
                  return null;
              }
          });
  
          assertEquals(((JdbcConnection) this.conn).getCharacterSetMetadata(), cw.getCharacterSetMetadata());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getHoldability();
                  return null;
              }
          });
          assertEquals(((JdbcConnection) this.conn).getHost(), cw.getHost());
          assertEquals(((JdbcConnection) this.conn).getHostPortPair(), cw.getHostPortPair());
          assertEquals(Properties.class, cw.getProperties().getClass());
          assertEquals(JdbcPropertySetImpl.class, cw.getPropertySet().getClass());
          assertThrows(SQLNonTransientConnectionException.class, "No operations allowed after connection closed.", new Callable<Void>() { // TODO why message is different?
              public Void call() throws Exception {
                  cw.getSchema();
                  return null;
              }
          });
          assertEquals(((JdbcConnection) this.conn).getServerVersion().toString(), cw.getServerVersion().toString());
          assertEquals(NativeSession.class, cw.getSession().getClass());
          assertEquals(((JdbcConnection) this.conn).getSessionMaxRows(), cw.getSessionMaxRows());
          assertEquals(((JdbcConnection) this.conn).getURL(), cw.getURL());
          assertEquals(((JdbcConnection) this.conn).getUser(), cw.getUser());
          assertFalse(cw.hasTriedMaster());
          assertTrue(cw.isClosed());
          assertFalse(cw.isInGlobalTx());
          assertFalse(cw.isSourceConnection());
          assertFalse(cw.isProxySet());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isReadOnly();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isReadOnly(false);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isReadOnly(true);
                  return null;
              }
          });
          assertThrows(CommunicationsException.class, new Callable<Void>() {
              public Void call() throws Exception {
                  cw.isServerLocal();
                  return null;
              }
          });
          assertFalse(cw.isValid(10));
          assertTrue(cw.isWrapperFor(Connection.class));
          assertEquals(((JdbcConnection) this.conn).lowerCaseTableNames(), cw.lowerCaseTableNames());
  
          assertEquals(CommentClientInfoProvider.class, cw.getClientInfoProviderImpl().getClass());
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  Properties ci1 = new Properties();
                  ci1.setProperty("k1", "v1");
                  cw.setClientInfo(ci1);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setClientInfo("k4", "v4");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getClientInfo();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getClientInfo("k1");
                  return null;
              }
          });
          assertEquals(ConnectionImpl.class, cw.getConnectionMutex().getClass());
          assertNull(cw.getExceptionInterceptor());
  
          String comment = cw.getStatementComment();
          assertEquals("Test comment 2", comment);
          cw.setStatementComment("Test comment 3");
          assertNotEquals(((JdbcConnection) this.conn).getStatementComment(), cw.getStatementComment());
  
          assertThrows(SQLNonTransientConnectionException.class, "No operations allowed after connection closed.", new Callable<Void>() { // TODO why message is different?
              public Void call() throws Exception {
                  cw.getNetworkTimeout();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getTypeMap();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getWarnings();
                  return null;
              }
          });
  
          // testsuite is built upon non-SSL default connection with additional useSSL=false&allowPublicKeyRetrieval=true properties
          assertFalse(cw.hasSameProperties((JdbcConnection) this.conn));
  
          assertTrue(cw.isSameResource((JdbcConnection) this.conn));
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.nativeSQL("SELECT 1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getMetaData();
                  return null;
              }
          });
  
          // TODO find a way to test following methods
          //        cw.getId();
          //        cw.getIdleFor();
          //        cw.getMetadataSafeStatement();
          //        cw.getMultiHostSafeProxy();
          //        cw.resetServerState();
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              @SuppressWarnings("synthetic-access")
              public Void call() throws Exception {
                  cw.setCatalog(PooledConnectionRegressionTest.this.dbName);
                  return null;
              }
          });
  
          cw.setFailedOver(false);
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
                  return null;
              }
          });
  
          cw.setInGlobalTx(false);
          // TODO find a way to test following methods
          //        cw.setNetworkTimeout(executor, milliseconds);
          //        cw.setProxy(this.conn);
          //        cw.setReadOnly(readOnly);
          //        cw.setReadOnlyInternal(readOnlyFlag);
          //        cw.setSchema(schema);
          //        cw.setSessionMaxRows(max);
          //        cw.setTypeMap(map);
          assertEquals(((JdbcConnection) this.conn).storesLowerCaseTableName(), cw.storesLowerCaseTableName());
  
          // TODO find a way to test following methods
          //        cw.getQueryInterceptorsInstances();
          //        cw.initializeSafeQueryInterceptors();
          //        cw.unSafeQueryInterceptors();
          //        cw.unwrap(iface);
          //        cw.initializeResultsMetadataFromCache(sql, cachedMetaData, resultSet);
          //        cw.getCachedMetaData(sql);
  
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setAutoCommit(false);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.getTransactionIsolation();
                  return null;
              }
          });
          cw.transactionBegun();
          cw.transactionCompleted();
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.commit();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.rollback();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.rollback(null);
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setSavepoint();
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.setSavepoint("SP1");
                  return null;
              }
          });
          assertThrows(SQLNonTransientConnectionException.class, "Logical handle no longer valid", new Callable<Void>() {
              public Void call() throws Exception {
                  cw.releaseSavepoint(null);
                  return null;
              }
          });
  
          // TODO find a way to test following methods
          //        cw.registerStatement(this.stmt);
          //        cw.unregisterStatement(this.stmt);
          //        cw.decachePreparedStatement(this.pstmt);
          //        cw.recachePreparedStatement(this.pstmt);
          //        cw.clearHasTriedMaster();
          //        cw.clearWarnings();
          //        cw.ping();
          //        cw.pingInternal(checkForClosedConnection, timeoutMillis);
          //        cw.createNewIO(isForReconnect);
          //        cw.changeUser(userName, newPassword);
          //        cw.checkClosed();
          //        cw.realClose(calledExplicitly, issueRollback, skipLocalTeardown, reason);
          //        cw.cleanup(whyCleanedUp);
          //        cw.abort(executor);
          //        cw.abortInternal();
      }
  }
  