/* Copyright (c) 2002, 2016, 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 FOSS 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.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Hashtable; import java.util.concurrent.Callable; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NameParser; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; import javax.sql.PooledConnection; import javax.sql.XAConnection; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import com.mysql.jdbc.ConnectionProperties; import com.mysql.jdbc.MySQLConnection; import com.mysql.jdbc.NonRegisteringDriver; import com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker; import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource; import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; import com.mysql.jdbc.jdbc2.optional.MysqlDataSourceFactory; import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource; import com.mysql.jdbc.jdbc2.optional.MysqlXid; import testsuite.BaseTestCase; import testsuite.simple.DataSourceTest; /** * Tests fixes for bugs related to datasources. */ public class DataSourceRegressionTest extends BaseTestCase { public final static String DS_DATABASE_PROP_NAME = "com.mysql.jdbc.test.ds.db"; public final static String DS_HOST_PROP_NAME = "com.mysql.jdbc.test.ds.host"; public final static String DS_PASSWORD_PROP_NAME = "com.mysql.jdbc.test.ds.password"; public final static String DS_PORT_PROP_NAME = "com.mysql.jdbc.test.ds.port"; public final static String DS_USER_PROP_NAME = "com.mysql.jdbc.test.ds.user"; private Context ctx; private File tempDir; /** * Creates a new DataSourceRegressionTest suite for the given test name * * @param name * the name of the testcase to run. */ public DataSourceRegressionTest(String name) { super(name); } /** * Runs all test cases in this test suite * * @param args */ public static void main(String[] args) { junit.textui.TestRunner.run(DataSourceTest.class); } /** * Sets up this test, calling registerDataSource() to bind a DataSource into * JNDI, using the FSContext JNDI provider from Sun * * @throws Exception * if an error occurs. */ @Override public void setUp() throws Exception { super.setUp(); createJNDIContext(); } /** * Un-binds the DataSource, and cleans up the filesystem * * @throws Exception * if an error occurs */ @Override public void tearDown() throws Exception { this.ctx.unbind(this.tempDir.getAbsolutePath() + "/test"); this.ctx.unbind(this.tempDir.getAbsolutePath() + "/testNoUrl"); this.ctx.close(); this.tempDir.delete(); super.tearDown(); } /** * Tests fix for BUG#4808- Calling .close() twice on a PooledConnection * causes NPE. * * @throws Exception * if an error occurs. */ public void testBug4808() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); PooledConnection closeMeTwice = ds.getPooledConnection(); closeMeTwice.close(); closeMeTwice.close(); } /** * Tests fix for Bug#3848, port # alone parsed incorrectly * * @throws Exception * ... */ public void testBug3848() throws Exception { String jndiName = "/testBug3848"; String databaseName = System.getProperty(DS_DATABASE_PROP_NAME); String userName = System.getProperty(DS_USER_PROP_NAME); String password = System.getProperty(DS_PASSWORD_PROP_NAME); String port = System.getProperty(DS_PORT_PROP_NAME); // Only run this test if at least one of the above are set if ((databaseName != null) || (userName != null) || (password != null) || (port != null)) { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); if (databaseName != null) { ds.setDatabaseName(databaseName); } if (userName != null) { ds.setUser(userName); } if (password != null) { ds.setPassword(password); } if (port != null) { ds.setPortNumber(Integer.parseInt(port)); } bindDataSource(jndiName, ds); ConnectionPoolDataSource boundDs = null; try { boundDs = (ConnectionPoolDataSource) lookupDatasourceInJNDI(jndiName); assertTrue("Datasource not bound", boundDs != null); Connection dsConn = null; try { dsConn = boundDs.getPooledConnection().getConnection(); } finally { if (dsConn != null) { dsConn.close(); } } } finally { if (boundDs != null) { this.ctx.unbind(jndiName); } } } } /** * Tests that we can get a connection from the DataSource bound in JNDI * during test setup * * @throws Exception * if an error occurs */ public void testBug3920() throws Exception { String jndiName = "/testBug3920"; String databaseName = System.getProperty(DS_DATABASE_PROP_NAME); String userName = System.getProperty(DS_USER_PROP_NAME); String password = System.getProperty(DS_PASSWORD_PROP_NAME); String port = System.getProperty(DS_PORT_PROP_NAME); String serverName = System.getProperty(DS_HOST_PROP_NAME); // Only run this test if at least one of the above are set if ((databaseName != null) || (serverName != null) || (userName != null) || (password != null) || (port != null)) { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); if (databaseName != null) { ds.setDatabaseName(databaseName); } if (userName != null) { ds.setUser(userName); } if (password != null) { ds.setPassword(password); } if (port != null) { ds.setPortNumber(Integer.parseInt(port)); } if (serverName != null) { ds.setServerName(serverName); } bindDataSource(jndiName, ds); ConnectionPoolDataSource boundDs = null; try { boundDs = (ConnectionPoolDataSource) lookupDatasourceInJNDI(jndiName); assertTrue("Datasource not bound", boundDs != null); Connection dsCon = null; Statement dsStmt = null; try { dsCon = boundDs.getPooledConnection().getConnection(); dsStmt = dsCon.createStatement(); dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920"); dsStmt.executeUpdate("CREATE TABLE testBug3920 (field1 varchar(32))"); assertTrue("Connection can not be obtained from data source", dsCon != null); } finally { if (dsStmt != null) { dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920"); dsStmt.close(); } if (dsCon != null) { dsCon.close(); } } } finally { if (boundDs != null) { this.ctx.unbind(jndiName); } } } } /** * Tests fix for BUG#19169 - ConnectionProperties (and thus some * subclasses) are not serializable, even though some J2EE containers * expect them to be. * * @throws Exception * if the test fails. */ public void testBug19169() throws Exception { MysqlDataSource toSerialize = new MysqlDataSource(); toSerialize.setZeroDateTimeBehavior("convertToNull"); boolean testBooleanFlag = !toSerialize.getAllowLoadLocalInfile(); toSerialize.setAllowLoadLocalInfile(testBooleanFlag); int testIntFlag = toSerialize.getBlobSendChunkSize() + 1; toSerialize.setBlobSendChunkSize(String.valueOf(testIntFlag)); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(bOut); objOut.writeObject(toSerialize); objOut.flush(); ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray())); MysqlDataSource thawedDs = (MysqlDataSource) objIn.readObject(); assertEquals("convertToNull", thawedDs.getZeroDateTimeBehavior()); assertEquals(testBooleanFlag, thawedDs.getAllowLoadLocalInfile()); assertEquals(testIntFlag, thawedDs.getBlobSendChunkSize()); } /** * Tests fix for BUG#20242 - MysqlValidConnectionChecker for JBoss doesn't * work with MySQLXADataSources. * * @throws Exception * if the test fails. */ public void testBug20242() throws Exception { if (versionMeetsMinimum(5, 0)) { try { Class.forName("org.jboss.resource.adapter.jdbc.ValidConnectionChecker"); } catch (Exception ex) { System.out.println("The testBug20242() is ignored because required class isn't available:"); ex.printStackTrace(); return; // class not available for testing } MysqlXADataSource xaDs = new MysqlXADataSource(); xaDs.setUrl(dbUrl); MysqlValidConnectionChecker checker = new MysqlValidConnectionChecker(); assertNull(checker.isValidConnection(xaDs.getXAConnection().getConnection())); } } private void bindDataSource(String name, DataSource ds) throws Exception { this.ctx.bind(this.tempDir.getAbsolutePath() + name, ds); } /** * This method is separated from the rest of the example since you normally * would NOT register a JDBC driver in your code. It would likely be * configered into your naming and directory service using some GUI. * * @throws Exception * if an error occurs */ private void createJNDIContext() throws Exception { this.tempDir = File.createTempFile("jnditest", null); this.tempDir.delete(); this.tempDir.mkdir(); this.tempDir.deleteOnExit(); MysqlConnectionPoolDataSource ds; Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); this.ctx = new InitialContext(env); assertTrue("Naming Context not created", this.ctx != null); ds = new MysqlConnectionPoolDataSource(); ds.setUrl(dbUrl); // from BaseTestCase ds.setDatabaseName("test"); this.ctx.bind(this.tempDir.getAbsolutePath() + "/test", ds); } private DataSource lookupDatasourceInJNDI(String jndiName) throws Exception { NameParser nameParser = this.ctx.getNameParser(""); Name datasourceName = nameParser.parse(this.tempDir.getAbsolutePath() + jndiName); Object obj = this.ctx.lookup(datasourceName); DataSource boundDs = null; if (obj instanceof DataSource) { boundDs = (DataSource) obj; } else if (obj instanceof Reference) { // // For some reason, this comes back as a Reference instance under CruiseControl !? // Reference objAsRef = (Reference) obj; ObjectFactory factory = (ObjectFactory) Class.forName(objAsRef.getFactoryClassName()).newInstance(); boundDs = (DataSource) factory.getObjectInstance(objAsRef, datasourceName, this.ctx, new Hashtable<Object, Object>()); } return boundDs; } public void testCSC4616() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); PooledConnection pooledConn = ds.getPooledConnection(); Connection physConn = pooledConn.getConnection(); Statement physStatement = physConn.createStatement(); Method enableStreamingResultsMethodStmt = Class.forName("com.mysql.jdbc.jdbc2.optional.StatementWrapper").getMethod("enableStreamingResults", new Class[0]); enableStreamingResultsMethodStmt.invoke(physStatement, (Object[]) null); this.rs = physStatement.executeQuery("SELECT 1"); try { this.rs = physConn.createStatement().executeQuery("SELECT 2"); fail("Should have caught a streaming exception here"); } catch (SQLException sqlEx) { assertTrue(sqlEx.getMessage() != null && sqlEx.getMessage().indexOf("Streaming") != -1); } finally { if (this.rs != null) { this.rs.close(); this.rs = null; } } PreparedStatement physPrepStmt = physConn.prepareStatement("SELECT 1"); Method enableStreamingResultsMethodPstmt = Class.forName("com.mysql.jdbc.jdbc2.optional.PreparedStatementWrapper").getMethod("enableStreamingResults", (Class[]) null); enableStreamingResultsMethodPstmt.invoke(physPrepStmt, (Object[]) null); this.rs = physPrepStmt.executeQuery(); try { this.rs = physConn.createStatement().executeQuery("SELECT 2"); fail("Should have caught a streaming exception here"); } catch (SQLException sqlEx) { assertTrue(sqlEx.getMessage() != null && sqlEx.getMessage().indexOf("Streaming") != -1); } finally { if (this.rs != null) { this.rs.close(); this.rs = null; } } } /** * Tests fix for BUG#16791 - NullPointerException in MysqlDataSourceFactory * due to Reference containing RefAddrs with null content. * * @throws Exception * if the test fails */ public void testBug16791() throws Exception { MysqlDataSource myDs = new MysqlDataSource(); myDs.setUrl(dbUrl); Reference asRef = myDs.getReference(); System.out.println(asRef); removeFromRef(asRef, "port"); removeFromRef(asRef, NonRegisteringDriver.USER_PROPERTY_KEY); removeFromRef(asRef, NonRegisteringDriver.PASSWORD_PROPERTY_KEY); removeFromRef(asRef, "serverName"); removeFromRef(asRef, "databaseName"); //MysqlDataSource newDs = (MysqlDataSource) new MysqlDataSourceFactory().getObjectInstance(asRef, null, null, null); } private void removeFromRef(Reference ref, String key) { int size = ref.size(); for (int i = 0; i < size; i++) { RefAddr refAddr = ref.get(i); if (refAddr.getType().equals(key)) { ref.remove(i); break; } } } /** * Tests fix for BUG#32101 - When using a connection from our ConnectionPoolDataSource, * some Connection.prepareStatement() methods would return null instead of * a prepared statement. * * @throws Exception */ public void testBug32101() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); PooledConnection pc = ds.getPooledConnection(); assertNotNull(pc.getConnection().prepareStatement("SELECT 1")); assertNotNull(pc.getConnection().prepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS)); assertNotNull(pc.getConnection().prepareStatement("SELECT 1", new int[0])); assertNotNull(pc.getConnection().prepareStatement("SELECT 1", new String[0])); assertNotNull(pc.getConnection().prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)); assertNotNull( pc.getConnection().prepareStatement("SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT)); } public void testBug35810() throws Exception { int defaultConnectTimeout = ((ConnectionProperties) this.conn).getConnectTimeout(); int nonDefaultConnectTimeout = defaultConnectTimeout + 1000 * 2; MysqlConnectionPoolDataSource cpds = new MysqlConnectionPoolDataSource(); String dsUrl = BaseTestCase.dbUrl; if (dsUrl.indexOf("?") == -1) { dsUrl += "?"; } else { dsUrl += "&"; } dsUrl += "connectTimeout=" + nonDefaultConnectTimeout; cpds.setUrl(dsUrl); Connection dsConn = cpds.getPooledConnection().getConnection(); int configuredConnectTimeout = ((ConnectionProperties) dsConn).getConnectTimeout(); assertEquals("Connect timeout spec'd by URL didn't take", nonDefaultConnectTimeout, configuredConnectTimeout); assertFalse("Connect timeout spec'd by URL didn't take", defaultConnectTimeout == configuredConnectTimeout); } public void testBug42267() throws Exception { MysqlDataSource ds = new MysqlDataSource(); ds.setUrl(dbUrl); Connection c = ds.getConnection(); String query = "select 1,2,345"; PreparedStatement ps = c.prepareStatement(query); String psString = ps.toString(); assertTrue("String representation of wrapped ps should contain query string", psString.endsWith(": " + query)); ps.close(); ps.toString(); c.close(); } /** * Tests fix for BUG#72890 - Java jdbc driver returns incorrect return code when it's part of XA transaction * * @throws Exception * if the test fails. */ public void testBug72890() throws Exception { MysqlXADataSource myDs = new MysqlXADataSource(); myDs.setUrl(BaseTestCase.dbUrl); try { final Xid xid = new MysqlXid("72890".getBytes(), "72890".getBytes(), 1); final XAConnection xaConn = myDs.getXAConnection(); final XAResource xaRes = xaConn.getXAResource(); final Connection dbConn = xaConn.getConnection(); final long connId = ((MySQLConnection) ((com.mysql.jdbc.Connection) dbConn).getConnectionMutex()).getId(); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); assertEquals(XAResource.XA_OK, xaRes.prepare(xid)); // Simulate a connection hang and make sure the connection really dies. this.stmt.execute("KILL CONNECTION " + connId); int connAliveChecks = 4; while (connAliveChecks > 0) { this.rs = this.stmt.executeQuery("SHOW PROCESSLIST"); boolean connIsAlive = false; while (!connIsAlive && this.rs.next()) { connIsAlive = this.rs.getInt(1) == connId; } this.rs.close(); if (connIsAlive) { connAliveChecks--; System.out.println("Connection id " + connId + " is still alive. Checking " + connAliveChecks + " more times."); try { Thread.sleep(500); } catch (InterruptedException e) { } } else { connAliveChecks = -1; } } if (connAliveChecks == 0) { fail("Failed to kill the Connection id " + connId + " in a timely manner."); } XAException xaEx = assertThrows(XAException.class, "Undetermined error occurred in the underlying Connection - check your data for consistency", new Callable<Void>() { public Void call() throws Exception { xaRes.commit(xid, false); return null; } }); assertEquals("XAException error code", XAException.XAER_RMFAIL, xaEx.errorCode); dbConn.close(); xaConn.close(); } finally { /* * After MySQL 5.7.7 a prepared XA transaction is no longer rolled back at disconnect. It needs to be rolled back manually to prevent test failures * in subsequent runs. * Other MySQL versions won't have any transactions to recover. */ final XAConnection xaConnRecovery = myDs.getXAConnection(); final XAResource xaResRecovery = xaConnRecovery.getXAResource(); final Xid[] xidsToRecover = xaResRecovery.recover(XAResource.TMSTARTRSCAN); for (Xid xidToRecover : xidsToRecover) { xaResRecovery.rollback(xidToRecover); } xaConnRecovery.close(); } } /** * Tests fix for Bug#72632 - NullPointerException for invalid JDBC URL. */ public void testBug72632() throws Exception { final MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl("bad-connection-string"); assertThrows(SQLException.class, "Failed to get a connection using the URL 'bad-connection-string'.", new Callable<Void>() { public Void call() throws Exception { dataSource.getConnection(); return null; } }); } }