/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception <http://www.mysql.com/about/legal/licensing/foss-exception.html>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package testsuite.regression; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import javax.sql.ConnectionEvent; import javax.sql.ConnectionEventListener; import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; import junit.framework.Test; import junit.framework.TestSuite; import testsuite.BaseTestCase; import com.mysql.jdbc.PacketTooBigException; import com.mysql.jdbc.jdbc2.optional.ConnectionWrapper; import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource; import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource; /** * 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. * * @author fcr */ public final class PooledConnectionRegressionTest extends BaseTestCase { private ConnectionPoolDataSource cpds; // Count nb of closeEvent. protected int closeEventCount; // Count nb of connectionErrorEvent protected int connectionErrorEventCount; /** * Creates a new instance of ProgressPooledConnectionTest * * @param testname * DOCUMENT ME! */ public PooledConnectionRegressionTest(String testname) { super(testname); } /** * Set up test case before a test is run. * * @throws Exception * DOCUMENT ME! */ public void setUp() throws Exception { super.setUp(); // Reset event count. this.closeEventCount = 0; this.connectionErrorEventCount = 0; MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); this.cpds = ds; } /** * Runs all test cases in this test suite * * @param args */ public static void main(String[] args) { junit.textui.TestRunner.run(PooledConnectionRegressionTest.class); } /** * DOCUMENT ME! * * @return a test suite composed of this test case. */ public static Test suite() { TestSuite suite = new TestSuite(PooledConnectionRegressionTest.class); return suite; } /** * After the test is run. */ public void tearDown() throws Exception { this.cpds = null; super.tearDown(); } /** * Tests fix for BUG#7136 ... Statement.getConnection() returning physical * connection instead of logical connection. */ public void testBug7136() { 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("One close event should've been registered", 1, this.closeEventCount); this.closeEventCount = 0; _conn = pc.getConnection(); Connection connFromPreparedStatement = _conn.prepareStatement( "SELECT 1").getConnection(); // This should generate a close event. connFromPreparedStatement.close(); assertEquals("One close event should've been registered", 1, this.closeEventCount); } catch (SQLException ex) { fail(ex.toString()); } finally { if (pc != null) { try { pc.close(); } catch (SQLException ex) { ex.printStackTrace(); } } } } /** * Test the nb of closeEvents generated when a Connection is reclaimed. No * event should be generated in that case. */ public void testConnectionReclaim() { 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()."); } } } } catch (SQLException ex) { ex.printStackTrace(); fail(ex.toString()); } 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(); fail(ex.toString()); } } } assertEquals("Wrong nb of CloseEvents: ", NB_TESTS, this.closeEventCount); } /** * Tests that PacketTooLargeException doesn't clober the connection. * * @throws Exception * if the test fails. */ 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); try { pstmtFromPool.executeUpdate(); fail("Expecting PacketTooLargeException"); } catch (PacketTooBigException ptbe) { // We're expecting this one... } // This should still work okay, even though the last query on the // same // connection didn't... connFromPool.createStatement().executeQuery("SELECT 1"); assertTrue(this.connectionErrorEventCount == 0); assertTrue(this.closeEventCount == 0); } /** * Test the nb of closeEvents generated by a PooledConnection. A * JDBC-compliant driver should only generate 1 closeEvent each time * connection.close() is called. */ public void testCloseEvent() { 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()."); } } catch (SQLException ex) { fail(ex.toString()); } 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("Wrong nb of CloseEvents: ", NB_TESTS, this.closeEventCount); } /** * 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 * if the test fails */ public void testBug35489() throws Exception { MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource(); pds.setUrl(dbUrl); this.pstmt = pds.getPooledConnection().getConnection() .prepareStatement("SELECT 1"); this.pstmt.execute(); this.pstmt.close(); MysqlXADataSource xads = new MysqlXADataSource(); xads.setUrl(dbUrl); this.pstmt = xads.getXAConnection().getConnection() .prepareStatement("SELECT 1"); this.pstmt.execute(); this.pstmt.close(); xads = new MysqlXADataSource(); xads.setUrl(dbUrl); xads.setPinGlobalTxToPhysicalConnection(true); this.pstmt = xads.getXAConnection().getConnection() .prepareStatement("SELECT 1"); this.pstmt.execute(); this.pstmt.close(); } }