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

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

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

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

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



 */
package testsuite.regression;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Array;
import java.sql.BatchUpdateException;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DataTruncation;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;

import testsuite.BaseTestCase;
import testsuite.UnreliableSocketFactory;

import com.mysql.jdbc.CachedResultSetMetaData;
import com.mysql.jdbc.CharsetMapping;
import com.mysql.jdbc.Field;
import com.mysql.jdbc.MySQLConnection;
import com.mysql.jdbc.NonRegisteringDriver;
import com.mysql.jdbc.ParameterBindings;
import com.mysql.jdbc.ResultSetInternalMethods;
import com.mysql.jdbc.SQLError;
import com.mysql.jdbc.ServerPreparedStatement;
import com.mysql.jdbc.StatementImpl;
import com.mysql.jdbc.StatementInterceptor;
import com.mysql.jdbc.StatementInterceptorV2;
import com.mysql.jdbc.exceptions.MySQLTimeoutException;
import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;

/**
 * Regression tests for the Statement class
 * 
 * @author Mark Matthews
 */
public class StatementRegressionTest extends BaseTestCase {
	class PrepareThread extends Thread {
		Connection c;

		PrepareThread(Connection cn) {
			this.c = cn;
		}

		public void run() {
			for (int i = 0; i < 20; i++) // force this to end eventually
			{
				try {
					this.c.prepareStatement("SELECT 1");
					StatementRegressionTest.this.testServerPrepStmtDeadlockCounter++;
					Thread.sleep(400);
				} catch (SQLException sqlEx) {
					throw new RuntimeException(sqlEx);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	static int count = 0;

	static int nextID = 1; // The next ID we expected to generate

	/*
	 * Each row in this table is to be converted into a single REPLACE
	 * statement. If the value is zero, a new record is to be created using then
	 * autoincrement feature. If the value is non-zero, the existing row of that
	 * value is to be replace with, obviously, the same key. I expect one
	 * Generated Key for each zero value - but I would accept one key for each
	 * value, with non-zero values coming back as themselves.
	 */
	static final int[][] tests = { { 0 }, // generate 1
			{ 1, 0, 0 }, // update 1, generate 2, 3
			{ 2, 0, 0, }, // update 2, generate 3, 4
	};

	/**
	 * Runs all test cases in this test suite
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		junit.textui.TestRunner.run(StatementRegressionTest.class);
	}

	protected int testServerPrepStmtDeadlockCounter = 0;

	/**
	 * Constructor for StatementRegressionTest.
	 * 
	 * @param name
	 *            the name of the test to run
	 */
	public StatementRegressionTest(String name) {
		super(name);
	}

	private void addBatchItems(Statement statement, PreparedStatement pStmt,
			String tableName, int i) throws SQLException {
		pStmt.setString(1, "ps_batch_" + i);
		pStmt.setString(2, "ps_batch_" + i);
		pStmt.addBatch();

		statement.addBatch("INSERT INTO " + tableName
				+ " (strdata1, strdata2) VALUES " + "(\"s_batch_" + i
				+ "\",\"s_batch_" + i + "\")");
	}

	private void createGGKTables() throws Exception {
		// Delete and recreate table
		dropGGKTables();
		createTable(
				"testggk",
				"(id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,val INT NOT NULL)",
				"MYISAM");
	}

	private void doGGKTestPreparedStatement(int[] values, boolean useUpdate)
			throws Exception {
		// Generate the the multiple replace command
		StringBuffer cmd = new StringBuffer("REPLACE INTO testggk VALUES ");
		int newKeys = 0;

		for (int i = 0; i < values.length; i++) {
			cmd.append("(");

			if (values[i] == 0) {
				cmd.append("NULL");
				newKeys += 1;
			} else {
				cmd.append(values[i]);
			}

			cmd.append(", ");
			cmd.append(count++);
			cmd.append("), ");
		}

		cmd.setLength(cmd.length() - 2); // trim the final ", "

		// execute and print it
		System.out.println(cmd.toString());

		PreparedStatement pStmt = this.conn.prepareStatement(cmd.toString(),
				Statement.RETURN_GENERATED_KEYS);

		if (useUpdate) {
			pStmt.executeUpdate();
		} else {
			pStmt.execute();
		}

		// print out what actually happened
		System.out.println("Expect " + newKeys
				+ " generated keys, starting from " + nextID);

		this.rs = pStmt.getGeneratedKeys();
		StringBuffer res = new StringBuffer("Got keys");

		int[] generatedKeys = new int[newKeys];
		int i = 0;

		while (this.rs.next()) {
			if (i < generatedKeys.length) {
				generatedKeys[i] = this.rs.getInt(1);
			}

			i++;

			res.append(" " + this.rs.getInt(1));
		}

		int numberOfGeneratedKeys = i;

		assertTrue(
				"Didn't retrieve expected number of generated keys, expected "
						+ newKeys + ", found " + numberOfGeneratedKeys,
				numberOfGeneratedKeys == newKeys);
		assertTrue("Keys didn't start with correct sequence: ",
				generatedKeys[0] == nextID);

		System.out.println(res.toString());

		// Read and print the new state of the table
		this.rs = this.stmt.executeQuery("SELECT id, val FROM testggk");
		System.out.println("New table contents ");

		while (this.rs.next())
			System.out.println("Id " + this.rs.getString(1) + " val "
					+ this.rs.getString(2));

		// Tidy up
		System.out.println("");
		nextID += newKeys;
	}

	private void doGGKTestStatement(int[] values, boolean useUpdate)
			throws Exception {
		// Generate the the multiple replace command
		StringBuffer cmd = new StringBuffer("REPLACE INTO testggk VALUES ");
		int newKeys = 0;

		for (int i = 0; i < values.length; i++) {
			cmd.append("(");

			if (values[i] == 0) {
				cmd.append("NULL");
				newKeys += 1;
			} else {
				cmd.append(values[i]);
			}

			cmd.append(", ");
			cmd.append(count++);
			cmd.append("), ");
		}

		cmd.setLength(cmd.length() - 2); // trim the final ", "

		// execute and print it
		System.out.println(cmd.toString());

		if (useUpdate) {
			this.stmt.executeUpdate(cmd.toString(),
					Statement.RETURN_GENERATED_KEYS);
		} else {
			this.stmt.execute(cmd.toString(), Statement.RETURN_GENERATED_KEYS);
		}

		// print out what actually happened
		System.out.println("Expect " + newKeys
				+ " generated keys, starting from " + nextID);

		this.rs = this.stmt.getGeneratedKeys();
		StringBuffer res = new StringBuffer("Got keys");

		int[] generatedKeys = new int[newKeys];
		int i = 0;

		while (this.rs.next()) {
			if (i < generatedKeys.length) {
				generatedKeys[i] = this.rs.getInt(1);
			}

			i++;

			res.append(" " + this.rs.getInt(1));
		}

		int numberOfGeneratedKeys = i;

		assertTrue(
				"Didn't retrieve expected number of generated keys, expected "
						+ newKeys + ", found " + numberOfGeneratedKeys,
				numberOfGeneratedKeys == newKeys);
		assertTrue("Keys didn't start with correct sequence: ",
				generatedKeys[0] == nextID);

		System.out.println(res.toString());

		// Read and print the new state of the table
		this.rs = this.stmt.executeQuery("SELECT id, val FROM testggk");
		System.out.println("New table contents ");

		while (this.rs.next())
			System.out.println("Id " + this.rs.getString(1) + " val "
					+ this.rs.getString(2));

		// Tidy up
		System.out.println("");
		nextID += newKeys;
	}

	private void dropGGKTables() throws Exception {
		this.stmt.executeUpdate("DROP TABLE IF EXISTS testggk");
	}

	/**
	 * @param pStmt
	 * @param catId
	 * @throws SQLException
	 */
	private void execQueryBug5191(PreparedStatement pStmt, int catId)
			throws SQLException {
		pStmt.setInt(1, catId);

		this.rs = pStmt.executeQuery();

		assertTrue(this.rs.next());
		assertTrue(this.rs.next());
		// assertTrue(rs.next());

		assertFalse(this.rs.next());
	}

	private String getByteArrayString(byte[] ba) {
		StringBuffer buffer = new StringBuffer();
		if (ba != null) {
			for (int i = 0; i < ba.length; i++) {
				buffer.append("0x" + Integer.toHexString(ba[i] & 0xff) + " ");
			}
		} else {
			buffer.append("null");
		}
		return buffer.toString();
	}

	/**
	 * @param continueBatchOnError
	 * @throws SQLException
	 */
	private void innerBug6823(boolean continueBatchOnError) throws SQLException {
		Properties continueBatchOnErrorProps = new Properties();
		continueBatchOnErrorProps.setProperty("continueBatchOnError",
				String.valueOf(continueBatchOnError));
		this.conn = getConnectionWithProps(continueBatchOnErrorProps);
		Statement statement = this.conn.createStatement();

		String tableName = "testBug6823";

		createTable(tableName, "(id int not null primary key auto_increment,"
				+ " strdata1 varchar(255) not null, strdata2 varchar(255),"
				+ " UNIQUE INDEX (strdata1(100)))");

		PreparedStatement pStmt = this.conn.prepareStatement("INSERT INTO "
				+ tableName + " (strdata1, strdata2) VALUES (?,?)");

		int c = 0;
		addBatchItems(statement, pStmt, tableName, ++c);
		addBatchItems(statement, pStmt, tableName, ++c);
		addBatchItems(statement, pStmt, tableName, ++c);
		addBatchItems(statement, pStmt, tableName, c); // duplicate entry
		addBatchItems(statement, pStmt, tableName, ++c);
		addBatchItems(statement, pStmt, tableName, ++c);

		int expectedUpdateCounts = continueBatchOnError ? 6 : 3;

		BatchUpdateException e1 = null;
		BatchUpdateException e2 = null;

		int[] updateCountsPstmt = null;
		try {
			updateCountsPstmt = pStmt.executeBatch();
		} catch (BatchUpdateException e) {
			e1 = e;
			updateCountsPstmt = e1.getUpdateCounts();
		}

		int[] updateCountsStmt = null;
		try {
			updateCountsStmt = statement.executeBatch();
		} catch (BatchUpdateException e) {
			e2 = e;
			updateCountsStmt = e1.getUpdateCounts();
		}

		assertNotNull(e1);
		assertNotNull(e2);

		assertEquals(expectedUpdateCounts, updateCountsPstmt.length);
		assertEquals(expectedUpdateCounts, updateCountsStmt.length);

		if (continueBatchOnError) {
			assertTrue(updateCountsPstmt[3] == Statement.EXECUTE_FAILED);
			assertTrue(updateCountsStmt[3] == Statement.EXECUTE_FAILED);
		}

		int psRows = 0;
		this.rs = this.stmt.executeQuery("SELECT * from " + tableName
				+ " WHERE strdata1 like \"ps_%\"");
		while (this.rs.next()) {
			psRows++;
		}
		assertTrue(psRows > 0);

		int sRows = 0;
		this.rs = this.stmt.executeQuery("SELECT * from " + tableName
				+ " WHERE strdata1 like \"s_%\"");
		while (this.rs.next()) {
			sRows++;
		}
		assertTrue(sRows > 0);

		assertTrue(psRows + "!=" + sRows, psRows == sRows);
	}

	/**
	 * Tests fix for BUG#10155, double quotes not recognized when parsing
	 * client-side prepared statements.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug10155() throws Exception {
		this.conn
				.prepareStatement(
						"SELECT \"Test question mark? Test single quote'\"")
				.executeQuery().close();
	}

	/**
	 * Tests fix for BUG#10630, Statement.getWarnings() fails with NPE if
	 * statement has been closed.
	 */
	public void testBug10630() throws Exception {
		Connection conn2 = null;
		Statement stmt2 = null;

		try {
			conn2 = getConnectionWithProps((Properties) null);
			stmt2 = conn2.createStatement();

			conn2.close();
			stmt2.getWarnings();
			fail("Should've caught an exception here");
		} catch (SQLException sqlEx) {
			assertEquals("08003", sqlEx.getSQLState());
		} finally {
			if (stmt2 != null) {
				stmt2.close();
			}

			if (conn2 != null) {
				conn2.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#11115, Varbinary data corrupted when using server-side
	 * prepared statements.
	 */
	public void testBug11115() throws Exception {
		String tableName = "testBug11115";

		if (versionMeetsMinimum(4, 1, 0)) {

			createTable(tableName,
					"(pwd VARBINARY(30)) DEFAULT CHARACTER SET utf8", "InnoDB");

			byte[] bytesToTest = new byte[] { 17, 120, -1, -73, -5 };

			PreparedStatement insStmt = this.conn
					.prepareStatement("INSERT INTO " + tableName
							+ " (pwd) VALUES (?)");
			insStmt.setBytes(1, bytesToTest);
			insStmt.executeUpdate();

			this.rs = this.stmt.executeQuery("SELECT pwd FROM " + tableName);
			this.rs.next();

			byte[] fromDatabase = this.rs.getBytes(1);

			assertEquals(bytesToTest.length, fromDatabase.length);

			for (int i = 0; i < bytesToTest.length; i++) {
				assertEquals(bytesToTest[i], fromDatabase[i]);
			}

			this.rs = this.conn
					.prepareStatement("SELECT pwd FROM " + tableName)
					.executeQuery();
			this.rs.next();

			fromDatabase = this.rs.getBytes(1);

			assertEquals(bytesToTest.length, fromDatabase.length);

			for (int i = 0; i < bytesToTest.length; i++) {
				assertEquals(bytesToTest[i], fromDatabase[i]);
			}
		}
	}

	public void testBug11540() throws Exception {
		Locale originalLocale = Locale.getDefault();
		Connection thaiConn = null;
		Statement thaiStmt = null;
		PreparedStatement thaiPrepStmt = null;

		try {
			createTable("testBug11540", "(field1 DATE, field2 TIMESTAMP)");
			this.stmt
					.executeUpdate("INSERT INTO testBug11540 VALUES (NOW(), NOW())");
			Locale.setDefault(new Locale("th", "TH"));
			Properties props = new Properties();
			props.setProperty("jdbcCompliantTruncation", "false");

			thaiConn = getConnectionWithProps(props);
			thaiStmt = thaiConn.createStatement();

			this.rs = thaiStmt
					.executeQuery("SELECT field1, field2 FROM testBug11540");
			this.rs.next();

			Date origDate = this.rs.getDate(1);
			Timestamp origTimestamp = this.rs.getTimestamp(1);
			this.rs.close();

			thaiStmt.executeUpdate("TRUNCATE TABLE testBug11540");

			thaiPrepStmt = ((com.mysql.jdbc.Connection) thaiConn)
					.clientPrepareStatement("INSERT INTO testBug11540 VALUES (?,?)");
			thaiPrepStmt.setDate(1, origDate);
			thaiPrepStmt.setTimestamp(2, origTimestamp);
			thaiPrepStmt.executeUpdate();

			this.rs = thaiStmt
					.executeQuery("SELECT field1, field2 FROM testBug11540");
			this.rs.next();

			Date testDate = this.rs.getDate(1);
			Timestamp testTimestamp = this.rs.getTimestamp(1);
			this.rs.close();

			assertEquals(origDate, testDate);
			assertEquals(origTimestamp, testTimestamp);

		} finally {
			Locale.setDefault(originalLocale);
		}
	}

	/**
	 * Tests fix for BUG#11663, autoGenerateTestcaseScript uses bogus parameter
	 * names for server-side prepared statements.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug11663() throws Exception {
		if (versionMeetsMinimum(4, 1, 0)
				&& ((com.mysql.jdbc.Connection) this.conn)
						.getUseServerPreparedStmts()) {
			Connection testcaseGenCon = null;
			PrintStream oldErr = System.err;

			try {
				createTable("testBug11663", "(field1 int)");

				Properties props = new Properties();
				props.setProperty("autoGenerateTestcaseScript", "true");
				testcaseGenCon = getConnectionWithProps(props);
				ByteArrayOutputStream testStream = new ByteArrayOutputStream();
				PrintStream testErr = new PrintStream(testStream);
				System.setErr(testErr);
				this.pstmt = testcaseGenCon
						.prepareStatement("SELECT field1 FROM testBug11663 WHERE field1=?");
				this.pstmt.setInt(1, 1);
				this.pstmt.execute();
				System.setErr(oldErr);
				String testString = new String(testStream.toByteArray());

				int setIndex = testString.indexOf("SET @debug_stmt_param");
				int equalsIndex = testString.indexOf("=", setIndex);
				String paramName = testString.substring(setIndex + 4,
						equalsIndex);

				int usingIndex = testString.indexOf("USING " + paramName,
						equalsIndex);

				assertTrue(usingIndex != -1);
			} finally {
				System.setErr(oldErr);

				if (this.pstmt != null) {
					this.pstmt.close();
					this.pstmt = null;
				}

				if (testcaseGenCon != null) {
					testcaseGenCon.close();
				}

			}
		}
	}

	/**
	 * Tests fix for BUG#11798 - Pstmt.setObject(...., Types.BOOLEAN) throws
	 * exception.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug11798() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid on JDK-1.3.1
		}

		try {
			this.pstmt = this.conn.prepareStatement("SELECT ?");
			this.pstmt.setObject(1, Boolean.TRUE, Types.BOOLEAN);
			this.pstmt.setObject(1, new BigDecimal("1"), Types.BOOLEAN);
			this.pstmt.setObject(1, "true", Types.BOOLEAN);
		} finally {
			if (this.pstmt != null) {
				this.pstmt.close();
				this.pstmt = null;
			}
		}
	}

	/**
	 * Tests fix for BUG#13255 - Reconnect during middle of executeBatch()
	 * should not happen.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug13255() throws Exception {

		createTable("testBug13255", "(field_1 int)");

		Properties props = new Properties();
		props.setProperty("autoReconnect", "true");

		Connection reconnectConn = null;
		Statement reconnectStmt = null;
		PreparedStatement reconnectPStmt = null;

		try {
			reconnectConn = getConnectionWithProps(props);
			reconnectStmt = reconnectConn.createStatement();

			String connectionId = getSingleIndexedValueWithQuery(reconnectConn,
					1, "SELECT CONNECTION_ID()").toString();

			reconnectStmt.addBatch("INSERT INTO testBug13255 VALUES (1)");
			reconnectStmt.addBatch("INSERT INTO testBug13255 VALUES (2)");
			reconnectStmt.addBatch("KILL " + connectionId);

			for (int i = 0; i < 100; i++) {
				reconnectStmt.addBatch("INSERT INTO testBug13255 VALUES (" + i
						+ ")");
			}

			try {
				reconnectStmt.executeBatch();
			} catch (SQLException sqlEx) {
				// We expect this...we killed the connection
			}

			assertEquals(2, getRowCount("testBug13255"));

			this.stmt.executeUpdate("TRUNCATE TABLE testBug13255");

			reconnectConn.close();

			reconnectConn = getConnectionWithProps(props);

			connectionId = getSingleIndexedValueWithQuery(reconnectConn, 1,
					"SELECT CONNECTION_ID()").toString();

			reconnectPStmt = reconnectConn
					.prepareStatement("INSERT INTO testBug13255 VALUES (?)");
			reconnectPStmt.setInt(1, 1);
			reconnectPStmt.addBatch();
			reconnectPStmt.setInt(1, 2);
			reconnectPStmt.addBatch();
			reconnectPStmt.addBatch("KILL " + connectionId);

			for (int i = 3; i < 100; i++) {
				reconnectPStmt.setInt(1, i);
				reconnectPStmt.addBatch();
			}

			try {
				reconnectPStmt.executeBatch();
			} catch (SQLException sqlEx) {
				// We expect this...we killed the connection
			}

			assertEquals(2, getRowCount("testBug13255"));

		} finally {
			if (reconnectStmt != null) {
				reconnectStmt.close();
			}

			if (reconnectConn != null) {
				reconnectConn.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#15024 - Driver incorrectly closes streams passed as
	 * arguments to PreparedStatements.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug15024() throws Exception {
		createTable("testBug15024", "(field1 BLOB)");

		try {
			this.pstmt = this.conn
					.prepareStatement("INSERT INTO testBug15024 VALUES (?)");
			testStreamsForBug15024(false, false);

			Properties props = new Properties();
			props.setProperty("useConfigs", "3-0-Compat");

			Connection compatConn = null;

			try {
				compatConn = getConnectionWithProps(props);

				this.pstmt = compatConn
						.prepareStatement("INSERT INTO testBug15024 VALUES (?)");
				testStreamsForBug15024(true, false);
			} finally {
				if (compatConn != null) {
					compatConn.close();
				}
			}
		} finally {
			if (this.pstmt != null) {
				PreparedStatement toClose = this.pstmt;
				this.pstmt = null;

				toClose.close();
			}
		}
	}

	/**
	 * PreparedStatement should call EscapeProcessor.escapeSQL?
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug15141() throws Exception {
		try {
			createTable("testBug15141", "(field1 VARCHAR(32))");
			this.stmt.executeUpdate("INSERT INTO testBug15141 VALUES ('abc')");

			this.pstmt = this.conn
					.prepareStatement("select {d '1997-05-24'} FROM testBug15141");
			this.rs = this.pstmt.executeQuery();
			assertTrue(this.rs.next());
			assertEquals("1997-05-24", this.rs.getString(1));
			this.rs.close();
			this.rs = null;
			this.pstmt.close();
			this.pstmt = null;

			this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
					.clientPrepareStatement("select {d '1997-05-24'} FROM testBug15141");
			this.rs = this.pstmt.executeQuery();
			assertTrue(this.rs.next());
			assertEquals("1997-05-24", this.rs.getString(1));
			this.rs.close();
			this.rs = null;
			this.pstmt.close();
			this.pstmt = null;
		} finally {
			if (this.rs != null) {
				ResultSet toCloseRs = this.rs;
				this.rs = null;
				toCloseRs.close();
			}

			if (this.pstmt != null) {
				PreparedStatement toClosePstmt = this.pstmt;
				this.pstmt = null;
				toClosePstmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#18041 - Server-side prepared statements don't cause
	 * truncation exceptions to be thrown.
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug18041() throws Exception {
		if (versionMeetsMinimum(4, 1)) {
			createTable("testBug18041", "(`a` tinyint(4) NOT NULL,"
					+ "`b` char(4) default NULL)");

			Properties props = new Properties();
			props.setProperty("jdbcCompliantTruncation", "true");
			props.setProperty("useServerPrepStmts", "true");

			Connection truncConn = null;
			PreparedStatement stm = null;

			try {
				truncConn = getConnectionWithProps(props);

				stm = truncConn
						.prepareStatement("insert into testBug18041 values (?,?)");
				stm.setInt(1, 1000);
				stm.setString(2, "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn");
				stm.executeUpdate();
				fail("Truncation exception should have been thrown");
			} catch (DataTruncation truncEx) {
				// we expect this
			} finally {
				if (truncConn != null) {
					truncConn.close();
				}
			}
		}
	}

	private void testStreamsForBug15024(boolean shouldBeClosedStream,
			boolean shouldBeClosedReader) throws SQLException {
		IsClosedInputStream bIn = new IsClosedInputStream(new byte[4]);
		IsClosedReader readerIn = new IsClosedReader("abcdef");

		this.pstmt.setBinaryStream(1, bIn, 4);
		this.pstmt.execute();
		assertEquals(shouldBeClosedStream, bIn.isClosed());

		this.pstmt.setCharacterStream(1, readerIn, 6);
		this.pstmt.execute();
		assertEquals(shouldBeClosedReader, readerIn.isClosed());

		this.pstmt.close();
	}

	class IsClosedReader extends StringReader {

		boolean isClosed = false;

		public IsClosedReader(String arg0) {
			super(arg0);
		}

		public void close() {
			super.close();

			this.isClosed = true;
		}

		public boolean isClosed() {
			return this.isClosed;
		}

	}

	class IsClosedInputStream extends ByteArrayInputStream {

		boolean isClosed = false;

		public IsClosedInputStream(byte[] arg0, int arg1, int arg2) {
			super(arg0, arg1, arg2);
		}

		public IsClosedInputStream(byte[] arg0) {
			super(arg0);
		}

		public void close() throws IOException {

			super.close();
			this.isClosed = true;
		}

		public boolean isClosed() {
			return this.isClosed;
		}
	}

	/**
	 * Tests fix for BUG#1774 -- Truncated words after double quote
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug1774() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1774");
			this.stmt
					.executeUpdate("CREATE TABLE testBug1774 (field1 VARCHAR(255))");

			PreparedStatement pStmt = this.conn
					.prepareStatement("INSERT INTO testBug1774 VALUES (?)");

			String testString = "The word contains \" character";

			pStmt.setString(1, testString);
			pStmt.executeUpdate();

			this.rs = this.stmt.executeQuery("SELECT * FROM testBug1774");
			this.rs.next();
			assertEquals(this.rs.getString(1), testString);
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1774");
		}
	}

	/**
	 * Tests fix for BUG#1901 -- PreparedStatement.setObject(int, Object, int,
	 * int) doesn't support CLOB or BLOB types.
	 * 
	 * @throws Exception
	 *             if this test fails for any reason
	 */
	public void testBug1901() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1901");
			this.stmt
					.executeUpdate("CREATE TABLE testBug1901 (field1 VARCHAR(255))");
			this.stmt.executeUpdate("INSERT INTO testBug1901 VALUES ('aaa')");

			this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug1901");
			this.rs.next();

			Clob valueAsClob = this.rs.getClob(1);
			Blob valueAsBlob = this.rs.getBlob(1);

			PreparedStatement pStmt = this.conn
					.prepareStatement("INSERT INTO testBug1901 VALUES (?)");
			pStmt.setObject(1, valueAsClob, java.sql.Types.CLOB, 0);
			pStmt.executeUpdate();
			pStmt.setObject(1, valueAsBlob, java.sql.Types.BLOB, 0);
			pStmt.executeUpdate();
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1901");
		}
	}

	/**
	 * Test fix for BUG#1933 -- Driver property 'maxRows' has no effect.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug1933() throws Exception {
		if (versionMeetsMinimum(4, 0)) {
			Connection maxRowsConn = null;
			PreparedStatement maxRowsPrepStmt = null;
			Statement maxRowsStmt = null;

			try {
				Properties props = new Properties();

				props.setProperty("maxRows", "1");

				maxRowsConn = getConnectionWithProps(props);

				maxRowsStmt = maxRowsConn.createStatement();

				assertTrue(maxRowsStmt.getMaxRows() == 1);

				this.rs = maxRowsStmt.executeQuery("SELECT 1 UNION SELECT 2");

				this.rs.next();

				maxRowsPrepStmt = maxRowsConn
						.prepareStatement("SELECT 1 UNION SELECT 2");

				assertTrue(maxRowsPrepStmt.getMaxRows() == 1);

				this.rs = maxRowsPrepStmt.executeQuery();

				this.rs.next();

				assertTrue(!this.rs.next());

				props.setProperty("useServerPrepStmts", "false");

				maxRowsConn = getConnectionWithProps(props);

				maxRowsPrepStmt = maxRowsConn
						.prepareStatement("SELECT 1 UNION SELECT 2");

				assertTrue(maxRowsPrepStmt.getMaxRows() == 1);

				this.rs = maxRowsPrepStmt.executeQuery();

				this.rs.next();

				assertTrue(!this.rs.next());
			} finally {
				if (maxRowsConn != null) {
					maxRowsConn.close();
				}
			}
		}
	}

	/**
	 * Tests the fix for BUG#1934 -- prepareStatement dies silently when
	 * encountering Statement.RETURN_GENERATED_KEY
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug1934() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid on JDK-1.3.1
		}

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1934");
			this.stmt.executeUpdate("CREATE TABLE testBug1934 (field1 INT)");

			System.out.println("Before prepareStatement()");

			this.pstmt = this.conn.prepareStatement(
					"INSERT INTO testBug1934 VALUES (?)",
					java.sql.Statement.RETURN_GENERATED_KEYS);

			assertTrue(this.pstmt != null);

			System.out.println("After prepareStatement() - " + this.pstmt);
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1934");
		}
	}

	/**
	 * Tests fix for BUG#1958 - Improper bounds checking on
	 * PreparedStatement.setFoo().
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug1958() throws Exception {
		PreparedStatement pStmt = null;

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1958");
			this.stmt.executeUpdate("CREATE TABLE testBug1958 (field1 int)");

			pStmt = this.conn
					.prepareStatement("SELECT * FROM testBug1958 WHERE field1 IN (?, ?, ?)");

			try {
				pStmt.setInt(4, 1);
			} catch (SQLException sqlEx) {
				assertTrue(SQLError.SQL_STATE_ILLEGAL_ARGUMENT.equals(sqlEx
						.getSQLState()));
			}
		} finally {
			if (pStmt != null) {
				pStmt.close();
			}

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug1958");
		}
	}

	/**
	 * Tests the fix for BUG#2606, server-side prepared statements not returning
	 * datatype YEAR correctly.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug2606() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug2606");
			this.stmt
					.executeUpdate("CREATE TABLE testBug2606(year_field YEAR)");
			this.stmt.executeUpdate("INSERT INTO testBug2606 VALUES (2004)");

			PreparedStatement yrPstmt = this.conn
					.prepareStatement("SELECT year_field FROM testBug2606");

			this.rs = yrPstmt.executeQuery();

			assertTrue(this.rs.next());

			assertEquals(2004, this.rs.getInt(1));
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug2606");
		}
	}

	/**
	 * Tests the fix for BUG#2671, nulls encoded incorrectly in server-side
	 * prepared statements.
	 * 
	 * @throws Exception
	 *             if an error occurs.
	 */
	public void testBug2671() throws Exception {
		if (versionMeetsMinimum(4, 1)) {
			createTable("test3", "("
					+ " `field1` int(8) NOT NULL auto_increment,"
					+ " `field2` int(8) unsigned zerofill default NULL,"
					+ " `field3` varchar(30) binary NOT NULL default '',"
					+ " `field4` varchar(100) default NULL,"
					+ " `field5` datetime NULL default '0000-00-00 00:00:00',"
					+ " PRIMARY KEY  (`field1`),"
					+ " UNIQUE KEY `unq_id` (`field2`),"
					+ " UNIQUE KEY  (`field3`)"
					+ " )  CHARACTER SET utf8", "InnoDB");

			this.stmt
					.executeUpdate("insert into test3 (field1, field3, field4) values (1,'blewis','Bob Lewis')");

			String query = "              " + "UPDATE                   "
					+ "  test3                  " + "SET                      "
					+ "  field2=?               " + "  ,field3=?          "
					+ "  ,field4=?           " + "  ,field5=?        "
					+ "WHERE                    "
					+ "  field1 = ?                 ";

			java.sql.Date mydate = null;

			this.pstmt = this.conn.prepareStatement(query);

			this.pstmt.setInt(1, 13);
			this.pstmt.setString(2, "abc");
			this.pstmt.setString(3, "def");
			this.pstmt.setDate(4, mydate);
			this.pstmt.setInt(5, 1);

			int retval = this.pstmt.executeUpdate();
			assertTrue(retval == 1);
		}
	}

	/**
	 * Tests fix for BUG#3103 -- java.util.Date not accepted as parameter to
	 * PreparedStatement.setObject().
	 * 
	 * @throws Exception
	 *             if the test fails
	 * 
	 * @deprecated uses deprecated methods of Date class
	 */
	public void testBug3103() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3103");

			if (versionMeetsMinimum(5, 6, 4)) {
				this.stmt.executeUpdate("CREATE TABLE testBug3103 (field1 DATETIME(3))");
			} else {
				this.stmt.executeUpdate("CREATE TABLE testBug3103 (field1 DATETIME)");
			}

			PreparedStatement pStmt = this.conn
					.prepareStatement("INSERT INTO testBug3103 VALUES (?)");

			java.util.Date utilDate = new java.util.Date();

			pStmt.setObject(1, utilDate);
			pStmt.executeUpdate();

			this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug3103");
			this.rs.next();

			java.util.Date retrUtilDate = new java.util.Date(this.rs
					.getTimestamp(1).getTime());

			// We can only compare on the day/month/year hour/minute/second
			// interval, because the timestamp has added milliseconds to the
			// internal date...
			assertTrue(
					"Dates not equal",
					(utilDate.getMonth() == retrUtilDate.getMonth())
							&& (utilDate.getDate() == retrUtilDate.getDate())
							&& (utilDate.getYear() == retrUtilDate.getYear())
							&& (utilDate.getHours() == retrUtilDate.getHours())
							&& (utilDate.getMinutes() == retrUtilDate
									.getMinutes())
							&& (utilDate.getSeconds() == retrUtilDate
									.getSeconds()));
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3103");
		}
	}

	/**
	 * Tests fix for BUG#3520
	 * 
	 * @throws Exception
	 *             ...
	 */
	public void testBug3520() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS t");
			this.stmt.executeUpdate("CREATE TABLE t (s1 int,primary key (s1))");
			this.stmt.executeUpdate("INSERT INTO t VALUES (1)");
			this.stmt.executeUpdate("INSERT INTO t VALUES (1)");
		} catch (SQLException sqlEx) {
			System.out.println(sqlEx.getSQLState());
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS t");
		}
	}

	/**
	 * Test fix for BUG#3557 -- UpdatableResultSet not picking up default values
	 * 
	 * @throws Exception
	 *             if test fails.
	 */
	public void testBug3557() throws Exception {
		boolean populateDefaults = ((com.mysql.jdbc.ConnectionProperties) this.conn)
				.getPopulateInsertRowWithDefaultValues();

		try {
			((com.mysql.jdbc.ConnectionProperties) this.conn)
					.setPopulateInsertRowWithDefaultValues(true);

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3557");

			this.stmt.executeUpdate("CREATE TABLE testBug3557 ( "
					+ "`a` varchar(255) NOT NULL default 'XYZ', "
					+ "`b` varchar(255) default '123', "
					+ "PRIMARY KEY  (`a`(100)))");

			Statement updStmt = this.conn
					.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
							ResultSet.CONCUR_UPDATABLE);
			this.rs = updStmt.executeQuery("SELECT * FROM testBug3557");

			assertTrue(this.rs.getConcurrency() == ResultSet.CONCUR_UPDATABLE);

			this.rs.moveToInsertRow();

			assertEquals("XYZ", this.rs.getObject(1));
			assertEquals("123", this.rs.getObject(2));
		} finally {
			((com.mysql.jdbc.ConnectionProperties) this.conn)
					.setPopulateInsertRowWithDefaultValues(populateDefaults);

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3557");
		}
	}

	/**
	 * Tests fix for BUG#3620 -- Timezone not respected correctly.
	 * 
	 * @throws SQLException
	 *             if the test fails.
	 */
	public void testBug3620() throws SQLException {
		if (isRunningOnJRockit()) {
			// bug with their timezones
			return;
		}

		if (isRunningOnJdk131()) {
			// bug with timezones, no update
			// for new DST in USA
			return;
		}

		// FIXME: This test is sensitive to being in CST/CDT it seems
		if (!TimeZone.getDefault().equals(
				TimeZone.getTimeZone("America/Chicago"))) {
			return;
		}

		long epsillon = 3000; // 3 seconds time difference

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3620");
			this.stmt
					.executeUpdate("CREATE TABLE testBug3620 (field1 TIMESTAMP)");

			PreparedStatement tsPstmt = this.conn
					.prepareStatement("INSERT INTO testBug3620 VALUES (?)");

			Calendar pointInTime = Calendar.getInstance();
			pointInTime.set(2004, 02, 29, 10, 0, 0);

			long pointInTimeOffset = pointInTime.getTimeZone().getRawOffset();

			java.sql.Timestamp ts = new java.sql.Timestamp(pointInTime
					.getTime().getTime());

			tsPstmt.setTimestamp(1, ts);
			tsPstmt.executeUpdate();

			String tsValueAsString = getSingleValue("testBug3620", "field1",
					null).toString();

			System.out.println("Timestamp as string with no calendar: "
					+ tsValueAsString.toString());

			Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));

			this.stmt.executeUpdate("DELETE FROM testBug3620");

			Properties props = new Properties();
			props.put("useTimezone", "true");
			// props.put("serverTimezone", "UTC");

			Connection tzConn = getConnectionWithProps(props);

			Statement tsStmt = tzConn.createStatement();

			tsPstmt = tzConn
					.prepareStatement("INSERT INTO testBug3620 VALUES (?)");

			tsPstmt.setTimestamp(1, ts, cal);
			tsPstmt.executeUpdate();

			tsValueAsString = getSingleValue("testBug3620", "field1", null)
					.toString();

			Timestamp tsValueAsTimestamp = (Timestamp) getSingleValue(
					"testBug3620", "field1", null);

			System.out.println("Timestamp as string with UTC calendar: "
					+ tsValueAsString.toString());
			System.out.println("Timestamp as Timestamp with UTC calendar: "
					+ tsValueAsTimestamp);

			this.rs = tsStmt.executeQuery("SELECT field1 FROM testBug3620");
			this.rs.next();

			Timestamp tsValueUTC = this.rs.getTimestamp(1, cal);

			//
			// We use this testcase with other vendors, JDBC spec
			// requires result set fields can only be read once,
			// although MySQL doesn't require this ;)
			//
			this.rs = tsStmt.executeQuery("SELECT field1 FROM testBug3620");
			this.rs.next();

			Timestamp tsValueStmtNoCal = this.rs.getTimestamp(1);

			System.out
					.println("Timestamp specifying UTC calendar from normal statement: "
							+ tsValueUTC.toString());

			PreparedStatement tsPstmtRetr = tzConn
					.prepareStatement("SELECT field1 FROM testBug3620");

			this.rs = tsPstmtRetr.executeQuery();
			this.rs.next();

			Timestamp tsValuePstmtUTC = this.rs.getTimestamp(1, cal);

			System.out
					.println("Timestamp specifying UTC calendar from prepared statement: "
							+ tsValuePstmtUTC.toString());

			//
			// We use this testcase with other vendors, JDBC spec
			// requires result set fields can only be read once,
			// although MySQL doesn't require this ;)
			//
			this.rs = tsPstmtRetr.executeQuery();
			this.rs.next();

			Timestamp tsValuePstmtNoCal = this.rs.getTimestamp(1);

			System.out
					.println("Timestamp specifying no calendar from prepared statement: "
							+ tsValuePstmtNoCal.toString());

			long stmtDeltaTWithCal = (ts.getTime() - tsValueStmtNoCal.getTime());

			long deltaOrig = Math.abs(stmtDeltaTWithCal - pointInTimeOffset);

			assertTrue(
					"Difference between original timestamp and timestamp retrieved using java.sql.Statement "
							+ "set in database using UTC calendar is not ~= "
							+ epsillon + ", it is actually " + deltaOrig,
					(deltaOrig < epsillon));

			long pStmtDeltaTWithCal = (ts.getTime() - tsValuePstmtNoCal
					.getTime());

			System.out
					.println(Math.abs(pStmtDeltaTWithCal - pointInTimeOffset)
							+ " < "
							+ epsillon
							+ (Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) < epsillon));
			assertTrue(
					"Difference between original timestamp and timestamp retrieved using java.sql.PreparedStatement "
							+ "set in database using UTC calendar is not ~= "
							+ epsillon
							+ ", it is actually "
							+ pStmtDeltaTWithCal,
					(Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) < epsillon));

			System.out
					.println("Difference between original ts and ts with no calendar: "
							+ (ts.getTime() - tsValuePstmtNoCal.getTime())
							+ ", offset should be " + pointInTimeOffset);
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3620");
		}
	}

	/**
	 * Tests that DataTruncation is thrown when data is truncated.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug3697() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3697");
			this.stmt
					.executeUpdate("CREATE TABLE testBug3697 (field1 VARCHAR(255))");

			StringBuffer updateBuf = new StringBuffer(
					"INSERT INTO testBug3697 VALUES ('");

			for (int i = 0; i < 512; i++) {
				updateBuf.append("A");
			}

			updateBuf.append("')");

			try {
				this.stmt.executeUpdate(updateBuf.toString());
			} catch (DataTruncation dtEx) {
				// This is an expected exception....
			}

			SQLWarning warningChain = this.stmt.getWarnings();

			System.out.println(warningChain);
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3697");
		}
	}

	/**
	 * Tests fix for BUG#3804, data truncation on server should throw
	 * DataTruncation exception.
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug3804() throws Exception {
		if (versionMeetsMinimum(4, 1)) {
			try {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3804");
				this.stmt
						.executeUpdate("CREATE TABLE testBug3804 (field1 VARCHAR(5))");

				boolean caughtTruncation = false;

				try {
					this.stmt
							.executeUpdate("INSERT INTO testBug3804 VALUES ('1234567')");
				} catch (DataTruncation truncationEx) {
					caughtTruncation = true;
					System.out.println(truncationEx);
				}

				assertTrue("Data truncation exception should've been thrown",
						caughtTruncation);
			} finally {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3804");
			}
		}
	}

	/**
	 * Tests BUG#3873 - PreparedStatement.executeBatch() not returning all
	 * generated keys (even though that's not JDBC compliant).
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug3873() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid on JDK-1.3.1
		}

		PreparedStatement batchStmt = null;

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3873");
			this.stmt
					.executeUpdate("CREATE TABLE testBug3873 (keyField INT NOT NULL PRIMARY KEY AUTO_INCREMENT, dataField VARCHAR(32))");
			batchStmt = this.conn.prepareStatement(
					"INSERT INTO testBug3873 (dataField) VALUES (?)",
					Statement.RETURN_GENERATED_KEYS);
			batchStmt.setString(1, "abc");
			batchStmt.addBatch();
			batchStmt.setString(1, "def");
			batchStmt.addBatch();
			batchStmt.setString(1, "ghi");
			batchStmt.addBatch();

			@SuppressWarnings("unused")
			int[] updateCounts = batchStmt.executeBatch();

			this.rs = batchStmt.getGeneratedKeys();

			while (this.rs.next()) {
				System.out.println(this.rs.getInt(1));
			}

			this.rs = batchStmt.getGeneratedKeys();
			assertTrue(this.rs.next());
			assertTrue(1 == this.rs.getInt(1));
			assertTrue(this.rs.next());
			assertTrue(2 == this.rs.getInt(1));
			assertTrue(this.rs.next());
			assertTrue(3 == this.rs.getInt(1));
			assertTrue(!this.rs.next());
		} finally {
			if (batchStmt != null) {
				batchStmt.close();
			}

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3873");
		}
	}

	/**
	 * Tests fix for BUG#4119 -- misbehavior in a managed environment from
	 * MVCSoft JDO
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug4119() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4119");
			this.stmt.executeUpdate("CREATE TABLE `testBug4119` ("
					+ "`field1` varchar(255) NOT NULL default '',"
					+ "`field2` bigint(20) default NULL,"
					+ "`field3` int(11) default NULL,"
					+ "`field4` datetime default NULL,"
					+ "`field5` varchar(75) default NULL,"
					+ "`field6` varchar(75) default NULL,"
					+ "`field7` varchar(75) default NULL,"
					+ "`field8` datetime default NULL,"
					+ " PRIMARY KEY  (`field1`(100))" + ")");

			PreparedStatement pStmt = this.conn
					.prepareStatement("insert into testBug4119 (field2, field3,"
							+ "field4, field5, field6, field7, field8, field1) values (?, ?,"
							+ "?, ?, ?, ?, ?, ?)");

			pStmt.setString(1, "0");
			pStmt.setString(2, "0");
			pStmt.setTimestamp(3,
					new java.sql.Timestamp(System.currentTimeMillis()));
			pStmt.setString(4, "ABC");
			pStmt.setString(5, "DEF");
			pStmt.setString(6, "AA");
			pStmt.setTimestamp(7,
					new java.sql.Timestamp(System.currentTimeMillis()));
			pStmt.setString(8, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
			pStmt.executeUpdate();
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4119");
		}
	}

	/**
	 * Tests fix for BUG#4311 - Error in JDBC retrieval of mediumint column when
	 * using prepared statements and binary result sets.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug4311() throws Exception {
		try {
			int lowValue = -8388608;
			int highValue = 8388607;

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4311");
			this.stmt
					.executeUpdate("CREATE TABLE testBug4311 (low MEDIUMINT, high MEDIUMINT)");
			this.stmt.executeUpdate("INSERT INTO testBug4311 VALUES ("
					+ lowValue + ", " + highValue + ")");

			PreparedStatement pStmt = this.conn
					.prepareStatement("SELECT low, high FROM testBug4311");
			this.rs = pStmt.executeQuery();
			assertTrue(this.rs.next());
			assertTrue(this.rs.getInt(1) == lowValue);
			assertTrue(this.rs.getInt(2) == highValue);
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4311");
		}
	}

	/**
	 * Tests fix for BUG#4510 -- Statement.getGeneratedKeys() fails when key >
	 * 32767
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug4510() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid on JDK-1.3.1
		}

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4510");
			this.stmt.executeUpdate("CREATE TABLE testBug4510 ("
					+ "field1 INT NOT NULL PRIMARY KEY AUTO_INCREMENT,"
					+ "field2 VARCHAR(100))");
			this.stmt
					.executeUpdate("INSERT INTO testBug4510 (field1, field2) VALUES (32767, 'bar')");

			PreparedStatement p = this.conn.prepareStatement(
					"insert into testBug4510 (field2) values (?)",
					Statement.RETURN_GENERATED_KEYS);

			p.setString(1, "blah");

			p.executeUpdate();

			ResultSet genKeysRs = p.getGeneratedKeys();
			genKeysRs.next();
			System.out.println("Id: " + genKeysRs.getInt(1));
			genKeysRs.close();
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4510");
		}
	}

	/**
	 * Server doesn't accept everything as a server-side prepared statement, so
	 * by default we scan for stuff it can't handle.
	 * 
	 * @throws SQLException
	 */
	public void testBug4718() throws SQLException {
		if (versionMeetsMinimum(4, 1, 0)
				&& ((com.mysql.jdbc.Connection) this.conn)
						.getUseServerPreparedStmts()) {
			this.pstmt = this.conn.prepareStatement("SELECT 1 LIMIT ?");
			assertTrue(this.pstmt instanceof com.mysql.jdbc.PreparedStatement);

			this.pstmt = this.conn.prepareStatement("SELECT 1 LIMIT 1");
			assertTrue(this.pstmt instanceof com.mysql.jdbc.ServerPreparedStatement);

			this.pstmt = this.conn.prepareStatement("SELECT 1 LIMIT 1, ?");
			assertTrue(this.pstmt instanceof com.mysql.jdbc.PreparedStatement);

			try {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4718");
				this.stmt
						.executeUpdate("CREATE TABLE testBug4718 (field1 char(32))");

				this.pstmt = this.conn
						.prepareStatement("ALTER TABLE testBug4718 ADD INDEX (field1)");
				assertTrue(this.pstmt instanceof com.mysql.jdbc.PreparedStatement);

				this.pstmt = this.conn.prepareStatement("SELECT 1");
				assertTrue(this.pstmt instanceof ServerPreparedStatement);

				this.pstmt = this.conn
						.prepareStatement("UPDATE testBug4718 SET field1=1");
				assertTrue(this.pstmt instanceof ServerPreparedStatement);

				this.pstmt = this.conn
						.prepareStatement("UPDATE testBug4718 SET field1=1 LIMIT 1");
				assertTrue(this.pstmt instanceof ServerPreparedStatement);

				this.pstmt = this.conn
						.prepareStatement("UPDATE testBug4718 SET field1=1 LIMIT ?");
				assertTrue(this.pstmt instanceof com.mysql.jdbc.PreparedStatement);

				this.pstmt = this.conn
						.prepareStatement("UPDATE testBug4718 SET field1='Will we ignore LIMIT ?,?'");
				assertTrue(this.pstmt instanceof ServerPreparedStatement);

			} finally {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4718");
			}
		}
	}

	/**
	 * Tests fix for BUG#5012 -- ServerPreparedStatements dealing with return of
	 * DECIMAL type don't work.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug5012() throws Exception {
		PreparedStatement pStmt = null;
		String valueAsString = "12345.12";

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug5012");
			this.stmt
					.executeUpdate("CREATE TABLE testBug5012(field1 DECIMAL(10,2))");
			this.stmt.executeUpdate("INSERT INTO testBug5012 VALUES ("
					+ valueAsString + ")");

			pStmt = this.conn
					.prepareStatement("SELECT field1 FROM testBug5012");
			this.rs = pStmt.executeQuery();
			assertTrue(this.rs.next());
			assertEquals(new BigDecimal(valueAsString),
					this.rs.getBigDecimal(1));
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug5012");

			if (pStmt != null) {
				pStmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#5133 -- PreparedStatement.toString() doesn't return
	 * correct value if no parameters are present in statement.
	 * 
	 * @throws Exception
	 */
	public void testBug5133() throws Exception {
		String query = "SELECT 1";
		String output = this.conn.prepareStatement(query).toString();
		System.out.println(output);

		assertTrue(output.indexOf(query) != -1);
	}

	/**
	 * Tests for BUG#5191 -- PreparedStatement.executeQuery() gives
	 * OutOfMemoryError
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug5191() throws Exception {
		PreparedStatement pStmt = null;

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug5191Q");
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug5191C");

			this.stmt.executeUpdate("CREATE TABLE testBug5191Q"
					+ "(QuestionId int NOT NULL AUTO_INCREMENT, "
					+ "Text VARCHAR(200), " + "PRIMARY KEY(QuestionId))");

			this.stmt.executeUpdate("CREATE TABLE testBug5191C"
					+ "(CategoryId int, " + "QuestionId int)");

			String[] questions = new String[] { "What is your name?",
					"What is your quest?",
					"What is the airspeed velocity of an unladen swollow?",
					"How many roads must a man walk?", "Where's the tea?", };

			for (int i = 0; i < questions.length; i++) {
				this.stmt.executeUpdate("INSERT INTO testBug5191Q(Text)"
						+ " VALUES (\"" + questions[i] + "\")");
				int catagory = (i < 3) ? 0 : i;

				this.stmt.executeUpdate("INSERT INTO testBug5191C"
						+ "(CategoryId, QuestionId) VALUES (" + catagory + ", "
						+ i + ")");
				/*
				 * this.stmt.executeUpdate("INSERT INTO testBug5191C" +
				 * "(CategoryId, QuestionId) VALUES (" + catagory + ", (SELECT
				 * testBug5191Q.QuestionId" + " FROM testBug5191Q " + "WHERE
				 * testBug5191Q.Text LIKE '" + questions[i] + "'))");
				 */
			}

			pStmt = this.conn.prepareStatement("SELECT qc.QuestionId, q.Text "
					+ "FROM testBug5191Q q, testBug5191C qc "
					+ "WHERE qc.CategoryId = ? "
					+ " AND q.QuestionId = qc.QuestionId");

			int catId = 0;
			for (int i = 0; i < 100; i++) {
				execQueryBug5191(pStmt, catId);
			}

		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug5191Q");
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug5191C");

			if (pStmt != null) {
				pStmt.close();
			}
		}
	}

	public void testBug5235() throws Exception {
		Properties props = new Properties();
		props.setProperty("zeroDateTimeBehavior", "convertToNull");

		Connection convertToNullConn = getConnectionWithProps(props);
		Statement convertToNullStmt = convertToNullConn.createStatement();
		try {
			convertToNullStmt.executeUpdate("DROP TABLE IF EXISTS testBug5235");
			convertToNullStmt
					.executeUpdate("CREATE TABLE testBug5235(field1 DATE)");
			convertToNullStmt
					.executeUpdate("INSERT INTO testBug5235 (field1) VALUES ('0000-00-00')");

			PreparedStatement ps = convertToNullConn
					.prepareStatement("SELECT field1 FROM testBug5235");
			this.rs = ps.executeQuery();

			if (this.rs.next()) {
				Date d = (Date) this.rs.getObject("field1");
				System.out.println("date: " + d);
			}
		} finally {
			convertToNullStmt.executeUpdate("DROP TABLE IF EXISTS testBug5235");
		}
	}

	public void testBug5450() throws Exception {
		if (versionMeetsMinimum(4, 1)) {
			String table = "testBug5450";
			String column = "policyname";

			try {
				Properties props = new Properties();
				props.setProperty("characterEncoding", "utf-8");

				Connection utf8Conn = getConnectionWithProps(props);
				Statement utfStmt = utf8Conn.createStatement();

				this.stmt.executeUpdate("DROP TABLE IF EXISTS " + table);

				this.stmt.executeUpdate("CREATE TABLE " + table
						+ "(policyid int NOT NULL AUTO_INCREMENT, " + column
						+ " VARCHAR(200), "
						+ "PRIMARY KEY(policyid)) DEFAULT CHARACTER SET utf8");

				String pname0 = "inserted \uac00 - foo - \u4e00";

				utfStmt.executeUpdate("INSERT INTO " + table + "(" + column
						+ ")" + " VALUES (\"" + pname0 + "\")");

				this.rs = utfStmt.executeQuery("SELECT " + column + " FROM "
						+ table);

				this.rs.first();
				String pname1 = this.rs.getString(column);

				assertEquals(pname0, pname1);
				byte[] bytes = this.rs.getBytes(column);

				String pname2 = new String(bytes, "utf-8");
				assertEquals(pname1, pname2);

				utfStmt.executeUpdate("delete from " + table + " where "
						+ column + " like 'insert%'");

				PreparedStatement s1 = utf8Conn.prepareStatement("insert into "
						+ table + "(" + column + ") values (?)");

				s1.setString(1, pname0);
				s1.executeUpdate();

				String byteesque = "byte " + pname0;
				byte[] newbytes = byteesque.getBytes("utf-8");

				s1.setBytes(1, newbytes);
				s1.executeUpdate();

				this.rs = utfStmt.executeQuery("select " + column + " from "
						+ table + " where " + column + " like 'insert%'");
				this.rs.first();
				String pname3 = this.rs.getString(column);
				assertEquals(pname0, pname3);

				this.rs = utfStmt.executeQuery("select " + column + " from "
						+ table + " where " + column + " like 'byte insert%'");
				this.rs.first();

				String pname4 = this.rs.getString(column);
				assertEquals(byteesque, pname4);

			} finally {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS " + table);
			}
		}
	}

	public void testBug5510() throws Exception {
		// This is a server bug that should be fixed by 4.1.6
		if (versionMeetsMinimum(4, 1, 6)) {
			createTable(
					"`testBug5510`",
					"("
							+ "`a` bigint(20) NOT NULL auto_increment,"
							+ "`b` varchar(64) default NULL,"
							+ "`c` varchar(64) default NULL,"
							+ "`d` varchar(255) default NULL,"
							+ "`e` int(11) default NULL,"
							+ "`f` varchar(32) default NULL,"
							+ "`g` varchar(32) default NULL,"
							+ "`h` varchar(80) default NULL,"
							+ "`i` varchar(255) default NULL,"
							+ "`j` varchar(255) default NULL,"
							+ "`k` varchar(255) default NULL,"
							+ "`l` varchar(32) default NULL,"
							+ "`m` varchar(32) default NULL,"
							+ "`n` timestamp NOT NULL default CURRENT_TIMESTAMP on update"
							+ " CURRENT_TIMESTAMP,"
							+ "`o` int(11) default NULL,"
							+ "`p` int(11) default NULL,"
							+ "PRIMARY KEY  (`a`)" + ") DEFAULT CHARSET=latin1",
					"InnoDB ");
			PreparedStatement pStmt = this.conn
					.prepareStatement("INSERT INTO testBug5510 (a) VALUES (?)");
			pStmt.setNull(1, 0);
			pStmt.executeUpdate();
		}
	}

	/**
	 * Tests fix for BUG#5874, timezone correction goes in wrong 'direction'
	 * when useTimezone=true and server timezone differs from client timezone.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug5874() throws Exception {
		/*
		 * try { String clientTimezoneName = "America/Los_Angeles"; String
		 * serverTimezoneName = "America/Chicago";
		 * 
		 * TimeZone.setDefault(TimeZone.getTimeZone(clientTimezoneName));
		 * 
		 * long epsillon = 3000; // 3 seconds difference
		 * 
		 * long clientTimezoneOffsetMillis = TimeZone.getDefault()
		 * .getRawOffset(); long serverTimezoneOffsetMillis =
		 * TimeZone.getTimeZone( serverTimezoneName).getRawOffset();
		 * 
		 * long offsetDifference = clientTimezoneOffsetMillis -
		 * serverTimezoneOffsetMillis;
		 * 
		 * Properties props = new Properties(); props.put("useTimezone",
		 * "true"); props.put("serverTimezone", serverTimezoneName);
		 * 
		 * Connection tzConn = getConnectionWithProps(props); Statement tzStmt =
		 * tzConn.createStatement();
		 * tzStmt.executeUpdate("DROP TABLE IF EXISTS timeTest"); tzStmt
		 * .executeUpdate("CREATE TABLE timeTest (tstamp DATETIME, t TIME)");
		 * 
		 * PreparedStatement pstmt = tzConn
		 * .prepareStatement("INSERT INTO timeTest VALUES (?, ?)");
		 * 
		 * long now = System.currentTimeMillis(); // Time in milliseconds //
		 * since 1/1/1970 GMT
		 * 
		 * Timestamp nowTstamp = new Timestamp(now); Time nowTime = new
		 * Time(now);
		 * 
		 * pstmt.setTimestamp(1, nowTstamp); pstmt.setTime(2, nowTime);
		 * pstmt.executeUpdate();
		 * 
		 * this.rs = tzStmt.executeQuery("SELECT * from timeTest");
		 * 
		 * // Timestamps look like this: 2004-11-29 13:43:21 SimpleDateFormat
		 * timestampFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");
		 * SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
		 * 
		 * while (this.rs.next()) { // Driver now converts/checks
		 * DATE/TIME/TIMESTAMP/DATETIME types // when calling getString()...
		 * String retrTimestampString = new String(this.rs.getBytes(1));
		 * Timestamp retrTimestamp = this.rs.getTimestamp(1);
		 * 
		 * java.util.Date timestampOnServer = timestampFormat
		 * .parse(retrTimestampString);
		 * 
		 * long retrievedOffsetForTimestamp = retrTimestamp.getTime() -
		 * timestampOnServer.getTime();
		 * 
		 * assertTrue(
		 * "Difference between original timestamp and timestamp retrieved using client timezone is not "
		 * + offsetDifference, (Math .abs(retrievedOffsetForTimestamp -
		 * offsetDifference) < epsillon));
		 * 
		 * String retrTimeString = new String(this.rs.getBytes(2)); Time
		 * retrTime = this.rs.getTime(2);
		 * 
		 * java.util.Date timeOnServerAsDate = timeFormat
		 * .parse(retrTimeString); Time timeOnServer = new
		 * Time(timeOnServerAsDate.getTime());
		 * 
		 * long retrievedOffsetForTime = retrTime.getTime() -
		 * timeOnServer.getTime();
		 * 
		 * assertTrue(
		 * "Difference between original times and time retrieved using client timezone is not "
		 * + offsetDifference, (Math.abs(retrievedOffsetForTime -
		 * offsetDifference) < epsillon)); } } finally {
		 * this.stmt.executeUpdate("DROP TABLE IF EXISTS timeTest"); }
		 */
	}

	public void testBug6823() throws SQLException {
		innerBug6823(true);
		innerBug6823(false);
	}

	public void testBug7461() throws Exception {
		String tableName = "testBug7461";

		try {
			createTable(tableName, "(field1 varchar(4))");
			File tempFile = File.createTempFile("mysql-test", ".txt");
			tempFile.deleteOnExit();

			FileOutputStream fOut = new FileOutputStream(tempFile);
			fOut.write("abcdefghijklmnop".getBytes());
			fOut.close();

			try {
				this.stmt.executeQuery("LOAD DATA LOCAL INFILE '"
						+ tempFile.toString() + "' INTO TABLE " + tableName);
			} catch (SQLException sqlEx) {
				this.stmt.getWarnings();
			}

		} finally {
			dropTable(tableName);
		}

	}

	public void testBug8181() throws Exception {

		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug8181");
			this.stmt
					.executeUpdate("CREATE TABLE testBug8181(col1 VARCHAR(20),col2 INT)");

			this.pstmt = this.conn
					.prepareStatement("INSERT INTO testBug8181(col1,col2) VALUES(?,?)");

			for (int i = 0; i < 20; i++) {
				this.pstmt.setString(1, "Test " + i);
				this.pstmt.setInt(2, i);
				this.pstmt.addBatch();
			}

			this.pstmt.executeBatch();

		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug8181");

			if (this.pstmt != null) {
				this.pstmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#8487 - PreparedStatements not creating streaming result
	 * sets.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug8487() throws Exception {
		try {
			this.pstmt = this.conn.prepareStatement("SELECT 1",
					ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);

			this.pstmt.setFetchSize(Integer.MIN_VALUE);
			this.rs = this.pstmt.executeQuery();
			try {
				this.conn.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) {
				while (this.rs.next())
					;

				this.rs.close();
			}

			if (this.pstmt != null) {
				this.pstmt.close();
			}
		}
	}

	/**
	 * Tests multiple statement support with fix for BUG#9704.
	 * 
	 * @throws Exception
	 *             DOCUMENT ME!
	 */
	public void testBug9704() throws Exception {
		if (versionMeetsMinimum(4, 1)) {
			Connection multiStmtConn = null;
			Statement multiStmt = null;

			try {
				Properties props = new Properties();
				props.setProperty("allowMultiQueries", "true");

				multiStmtConn = getConnectionWithProps(props);

				multiStmt = multiStmtConn.createStatement();

				multiStmt
						.executeUpdate("DROP TABLE IF EXISTS testMultiStatements");
				multiStmt
						.executeUpdate("CREATE TABLE testMultiStatements (field1 VARCHAR(255), field2 INT, field3 DOUBLE)");
				multiStmt
						.executeUpdate("INSERT INTO testMultiStatements VALUES ('abcd', 1, 2)");

				multiStmt
						.execute("SELECT field1 FROM testMultiStatements WHERE field1='abcd';"
								+ "UPDATE testMultiStatements SET field3=3;"
								+ "SELECT field3 FROM testMultiStatements WHERE field3=3");

				this.rs = multiStmt.getResultSet();

				assertTrue(this.rs.next());

				assertTrue("abcd".equals(this.rs.getString(1)));
				this.rs.close();

				// Next should be an update count...
				assertTrue(!multiStmt.getMoreResults());

				assertTrue("Update count was " + multiStmt.getUpdateCount()
						+ ", expected 1", multiStmt.getUpdateCount() == 1);

				assertTrue(multiStmt.getMoreResults());

				this.rs = multiStmt.getResultSet();

				assertTrue(this.rs.next());

				assertTrue(this.rs.getDouble(1) == 3);

				// End of multi results
				assertTrue(!multiStmt.getMoreResults());
				assertTrue(multiStmt.getUpdateCount() == -1);
			} finally {
				if (multiStmt != null) {
					multiStmt
							.executeUpdate("DROP TABLE IF EXISTS testMultiStatements");

					multiStmt.close();
				}

				if (multiStmtConn != null) {
					multiStmtConn.close();
				}
			}
		}
	}

	/**
	 * Tests that you can close a statement twice without an NPE.
	 * 
	 * @throws Exception
	 *             if an error occurs.
	 */
	public void testCloseTwice() throws Exception {
		Statement closeMe = this.conn.createStatement();
		closeMe.close();
		closeMe.close();
	}

	public void testCsc4194() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid on JDK-1.3.1
		}

		try {
			"".getBytes("Windows-31J");
		} catch (UnsupportedEncodingException ex) {
			return; // test doesn't work on this platform
		}

		Connection sjisConn = null;
		Connection windows31JConn = null;

		try {
			String tableNameText = "testCsc4194Text";
			String tableNameBlob = "testCsc4194Blob";

			createTable(tableNameBlob, "(field1 BLOB)");
			String charset = "";

			if (versionMeetsMinimum(5, 0, 3) || versionMeetsMinimum(4, 1, 12)) {
				charset = " CHARACTER SET cp932";
			} else if (versionMeetsMinimum(4, 1, 0)) {
				charset = " CHARACTER SET sjis";
			}

			createTable(tableNameText, "(field1 TEXT)" + charset);

			Properties windows31JProps = new Properties();
			windows31JProps.setProperty("useUnicode", "true");
			windows31JProps.setProperty("characterEncoding", "Windows-31J");

			windows31JConn = getConnectionWithProps(windows31JProps);
			testCsc4194InsertCheckBlob(windows31JConn, tableNameBlob);

			if (versionMeetsMinimum(4, 1, 0)) {
				testCsc4194InsertCheckText(windows31JConn, tableNameText,
						"Windows-31J");
			}

			Properties sjisProps = new Properties();
			sjisProps.setProperty("useUnicode", "true");
			sjisProps.setProperty("characterEncoding", "sjis");

			sjisConn = getConnectionWithProps(sjisProps);
			testCsc4194InsertCheckBlob(sjisConn, tableNameBlob);

			if (versionMeetsMinimum(5, 0, 3)) {
				testCsc4194InsertCheckText(sjisConn, tableNameText,
						"Windows-31J");
			}

		} finally {

			if (windows31JConn != null) {
				windows31JConn.close();
			}

			if (sjisConn != null) {
				sjisConn.close();
			}
		}
	}

	private void testCsc4194InsertCheckBlob(Connection c, String tableName)
			throws Exception {
		byte[] bArray = new byte[] { (byte) 0xac, (byte) 0xed, (byte) 0x00,
				(byte) 0x05 };

		PreparedStatement testStmt = c.prepareStatement("INSERT INTO "
				+ tableName + " VALUES (?)");
		testStmt.setBytes(1, bArray);
		testStmt.executeUpdate();

		this.rs = c.createStatement().executeQuery(
				"SELECT field1 FROM " + tableName);
		assertTrue(this.rs.next());
		assertEquals(getByteArrayString(bArray),
				getByteArrayString(this.rs.getBytes(1)));
		this.rs.close();
	}

	private void testCsc4194InsertCheckText(Connection c, String tableName,
			String encoding) throws Exception {
		byte[] kabuInShiftJIS = { (byte) 0x87, // a double-byte
				// charater("kabu") in Shift JIS
				(byte) 0x8a, };

		String expected = new String(kabuInShiftJIS, encoding);
		PreparedStatement testStmt = c.prepareStatement("INSERT INTO "
				+ tableName + " VALUES (?)");
		testStmt.setString(1, expected);
		testStmt.executeUpdate();

		this.rs = c.createStatement().executeQuery(
				"SELECT field1 FROM " + tableName);
		assertTrue(this.rs.next());
		assertEquals(expected, this.rs.getString(1));
		this.rs.close();
	}

	/**
	 * Tests all forms of statements influencing getGeneratedKeys().
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testGetGeneratedKeysAllCases() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid on JDK-1.3.1
		}

		System.out.println("Using Statement.executeUpdate()\n");

		try {
			createGGKTables();

			// Do the tests
			for (int i = 0; i < tests.length; i++) {
				doGGKTestStatement(tests[i], true);
			}
		} finally {
			dropGGKTables();
		}

		nextID = 1;
		count = 0;

		System.out.println("Using Statement.execute()\n");

		try {
			createGGKTables();

			// Do the tests
			for (int i = 0; i < tests.length; i++) {
				doGGKTestStatement(tests[i], false);
			}
		} finally {
			dropGGKTables();
		}

		nextID = 1;
		count = 0;

		System.out.println("Using PreparedStatement.executeUpdate()\n");

		try {
			createGGKTables();

			// Do the tests
			for (int i = 0; i < tests.length; i++) {
				doGGKTestPreparedStatement(tests[i], true);
			}
		} finally {
			dropGGKTables();
		}

		nextID = 1;
		count = 0;

		System.out.println("Using PreparedStatement.execute()\n");

		try {
			createGGKTables();

			// Do the tests
			for (int i = 0; i < tests.length; i++) {
				doGGKTestPreparedStatement(tests[i], false);
			}
		} finally {
			dropGGKTables();
		}
	}

	/**
	 * Tests that max_rows and 'limit' don't cause exceptions to be thrown.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testLimitAndMaxRows() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testMaxRowsAndLimit");
			this.stmt
					.executeUpdate("CREATE TABLE testMaxRowsAndLimit(limitField INT)");

			for (int i = 0; i < 500; i++) {
				this.stmt
						.executeUpdate("INSERT INTO testMaxRowsAndLimit VALUES ("
								+ i + ")");
			}

			this.stmt.setMaxRows(250);
			this.stmt
					.executeQuery("SELECT limitField FROM testMaxRowsAndLimit");
		} finally {
			this.stmt.setMaxRows(0);

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testMaxRowsAndLimit");
		}
	}

	/*
	 * public void testBug9595() throws Exception { double[] vals = new double[]
	 * {52.21, 52.22, 52.23, 52.24};
	 * 
	 * createTable("testBug9595", "(field1 DECIMAL(10,2), sortField INT)");
	 * 
	 * this.pstmt = this.conn.prepareStatement("INSERT INTO testBug9595 VALUES
	 * (?, ?)"); // Try setting as doubles for (int i = 0; i < vals.length; i++)
	 * { this.pstmt.setDouble(1, vals[i]); this.pstmt.setInt(2, i);
	 * this.pstmt.executeUpdate(); }
	 * 
	 * this.pstmt = this.conn.prepareStatement("SELECT field1 FROM testBug9595
	 * ORDER BY sortField"); this.rs = this.pstmt.executeQuery();
	 * 
	 * int i = 0;
	 * 
	 * while (this.rs.next()) { double valToTest = vals[i++];
	 * 
	 * assertEquals(this.rs.getDouble(1), valToTest, 0.001);
	 * assertEquals(this.rs.getBigDecimal(1).doubleValue(), valToTest, 0.001); }
	 * 
	 * this.pstmt = this.conn.prepareStatement("INSERT INTO testBug9595 VALUES
	 * (?, ?)");
	 * 
	 * this.stmt.executeUpdate("TRUNCATE TABLE testBug9595"); // Now, as
	 * BigDecimals for (i = 0; i < vals.length; i++) { BigDecimal foo = new
	 * BigDecimal(vals[i]);
	 * 
	 * this.pstmt.setObject(1, foo, Types.DECIMAL, 2); this.pstmt.setInt(2, i);
	 * this.pstmt.executeUpdate(); }
	 * 
	 * this.pstmt = this.conn.prepareStatement("SELECT field1 FROM testBug9595
	 * ORDER BY sortField"); this.rs = this.pstmt.executeQuery();
	 * 
	 * i = 0;
	 * 
	 * while (this.rs.next()) { double valToTest = vals[i++];
	 * System.out.println(this.rs.getString(1));
	 * assertEquals(this.rs.getDouble(1), valToTest, 0.001);
	 * assertEquals(this.rs.getBigDecimal(1).doubleValue(), valToTest, 0.001); }
	 * }
	 */

	/**
	 * Tests that 'LOAD DATA LOCAL INFILE' works
	 * 
	 * @throws Exception
	 *             if any errors occur
	 */
	public void testLoadData() throws Exception {
		try {
			//int maxAllowedPacket = 1048576;

			this.stmt.executeUpdate("DROP TABLE IF EXISTS loadDataRegress");
			this.stmt
					.executeUpdate("CREATE TABLE loadDataRegress (field1 int, field2 int)");

			File tempFile = File.createTempFile("mysql", ".txt");

			// tempFile.deleteOnExit();
			System.out.println(tempFile);

			Writer out = new FileWriter(tempFile);

			int localCount = 0;
			int rowCount = 128; // maxAllowedPacket * 4;

			for (int i = 0; i < rowCount; i++) {
				out.write((localCount++) + "\t" + (localCount++) + "\n");
			}

			out.close();

			StringBuffer fileNameBuf = null;

			if (File.separatorChar == '\\') {
				fileNameBuf = new StringBuffer();

				String fileName = tempFile.getAbsolutePath();
				int fileNameLength = fileName.length();

				for (int i = 0; i < fileNameLength; i++) {
					char c = fileName.charAt(i);

					if (c == '\\') {
						fileNameBuf.append("/");
					} else {
						fileNameBuf.append(c);
					}
				}
			} else {
				fileNameBuf = new StringBuffer(tempFile.getAbsolutePath());
			}

			int updateCount = this.stmt
					.executeUpdate("LOAD DATA LOCAL INFILE '"
							+ fileNameBuf.toString()
							+ "' INTO TABLE loadDataRegress" +
							" CHARACTER SET " + CharsetMapping.getMysqlEncodingForJavaEncoding(((MySQLConnection)this.conn).getEncoding(), (com.mysql.jdbc.Connection) this.conn));
			assertTrue(updateCount == rowCount);
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS loadDataRegress");
		}
	}

	public void testNullClob() throws Exception {
		createTable("testNullClob", "(field1 TEXT NULL)");

		PreparedStatement pStmt = null;

		try {
			pStmt = this.conn
					.prepareStatement("INSERT INTO testNullClob VALUES (?)");
			pStmt.setClob(1, null);
			pStmt.executeUpdate();
		} finally {
			if (pStmt != null) {
				pStmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#1658
	 * 
	 * @throws Exception
	 *             if the fix for parameter bounds checking doesn't work.
	 */
	public void testParameterBoundsCheck() throws Exception {
		try {
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS testParameterBoundsCheck");
			this.stmt
					.executeUpdate("CREATE TABLE testParameterBoundsCheck(f1 int, f2 int, f3 int, f4 int, f5 int)");

			PreparedStatement _pstmt = this.conn
					.prepareStatement("UPDATE testParameterBoundsCheck SET f1=?, f2=?,f3=?,f4=? WHERE f5=?");

			_pstmt.setString(1, "");
			_pstmt.setString(2, "");

			try {
				_pstmt.setString(25, "");
			} catch (SQLException sqlEx) {
				assertTrue(SQLError.SQL_STATE_ILLEGAL_ARGUMENT.equals(sqlEx
						.getSQLState()));
			}
		} finally {
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS testParameterBoundsCheck");
		}
	}

	public void testPStmtTypesBug() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testPStmtTypesBug");
			this.stmt
					.executeUpdate("CREATE TABLE testPStmtTypesBug(field1 INT)");
			this.pstmt = this.conn
					.prepareStatement("INSERT INTO testPStmtTypesBug VALUES (?)");
			this.pstmt.setObject(1, null, Types.INTEGER);
			this.pstmt.addBatch();
			this.pstmt.setInt(1, 1);
			this.pstmt.addBatch();
			this.pstmt.executeBatch();

		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS testPStmtTypesBug");
		}
	}

	/**
	 * Tests fix for BUG#1511
	 * 
	 * @throws Exception
	 *             if the quoteid parsing fix in PreparedStatement doesn't work.
	 */
	public void testQuotedIdRecognition() throws Exception {
		if (!this.versionMeetsMinimum(4, 1)) {
			try {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testQuotedId");
				this.stmt
						.executeUpdate("CREATE TABLE testQuotedId (col1 VARCHAR(32))");

				PreparedStatement pStmt = this.conn
						.prepareStatement("SELECT * FROM testQuotedId WHERE col1='ABC`DEF' or col1=?");
				pStmt.setString(1, "foo");
				pStmt.execute();

				this.stmt.executeUpdate("DROP TABLE IF EXISTS testQuotedId2");
				this.stmt
						.executeUpdate("CREATE TABLE testQuotedId2 (`Works?` INT)");
				pStmt = this.conn
						.prepareStatement("INSERT INTO testQuotedId2 (`Works?`) VALUES (?)");
				pStmt.setInt(1, 1);
				pStmt.executeUpdate();
			} finally {
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testQuotedId");
				this.stmt.executeUpdate("DROP TABLE IF EXISTS testQuotedId2");
			}
		}
	}

	/**
	 * Tests for BUG#9288, parameter index out of range if LIKE, ESCAPE '\'
	 * present in query.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	/*
	 * public void testBug9288() throws Exception { String tableName =
	 * "testBug9288"; PreparedStatement pStmt = null;
	 * 
	 * try { createTable(tableName, "(field1 VARCHAR(32), field2 INT)"); pStmt =
	 * ((com.mysql.jdbc.Connection)this.conn).clientPrepareStatement( "SELECT
	 * COUNT(1) FROM " + tableName + " WHERE " + "field1 LIKE '%' ESCAPE '\\'
	 * AND " + "field2 > ?"); pStmt.setInt(1, 0);
	 * 
	 * this.rs = pStmt.executeQuery(); } finally { if (this.rs != null) {
	 * this.rs.close(); this.rs = null; }
	 * 
	 * if (pStmt != null) { pStmt.close(); } } }
	 */

	/*
	 * public void testBug10999() throws Exception { if (versionMeetsMinimum(5,
	 * 0, 5)) {
	 * 
	 * String tableName = "testBug10999"; String updateTrigName =
	 * "testBug10999Update"; String insertTrigName = "testBug10999Insert"; try {
	 * createTable(tableName, "(pkfield INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
	 * field1 VARCHAR(32))");
	 * 
	 * try { this.stmt.executeUpdate("DROP TRIGGER " + updateTrigName); } catch
	 * (SQLException sqlEx) { // ignore for now }
	 * 
	 * this.stmt.executeUpdate("CREATE TRIGGER " + updateTrigName + " AFTER
	 * UPDATE ON " + tableName + " FOR EACH ROW " + "BEGIN " + "END");
	 * 
	 * try { this.stmt.executeUpdate("DROP TRIGGER " + insertTrigName); } catch
	 * (SQLException sqlEx) { // ignore }
	 * 
	 * this.stmt.executeUpdate("CREATE TRIGGER " + insertTrigName + " AFTER
	 * INSERT ON " + tableName + " FOR EACH ROW " + " BEGIN " + "END");
	 * 
	 * this.conn.setAutoCommit(false);
	 * 
	 * String updateSQL = "INSERT INTO " + tableName + " (field1) VALUES
	 * ('abcdefg')"; int rowCount = this.stmt.executeUpdate(updateSQL,
	 * Statement.RETURN_GENERATED_KEYS);
	 * 
	 * this.rs = stmt.getGeneratedKeys(); if (rs.next()) {
	 * System.out.println(rs.getInt(1)); int id = rs.getInt(1); //if
	 * (log.isDebugEnabled()) // log.debug("Retrieved ID = " + id); } //else {
	 * //log.error("Can't retrieve ID with getGeneratedKeys."); // Retrieve ID
	 * using a SELECT statement instead. // querySQL = "SELECT id from tab1
	 * WHERE ...";
	 * 
	 * //if (log.isDebugEnabled()) // log.debug(querySQL);
	 * 
	 * //rs = stmt.executeQuery(querySQL); this.rs =
	 * this.stmt.executeQuery("SELECT pkfield FROM " + tableName); } finally {
	 * this.conn.setAutoCommit(true);
	 * 
	 * try { this.stmt.executeUpdate("DROP TRIGGER IF EXISTS " +
	 * insertTrigName); } catch (SQLException sqlEx) { // ignore }
	 * 
	 * try { this.stmt.executeUpdate("DROP TRIGGER IF EXISTS " +
	 * updateTrigName); } catch (SQLException sqlEx) { // ignore } } } }
	 */

	/**
	 * Tests that binary dates/times are encoded/decoded correctly.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 * 
	 * @deprecated because we need to use this particular constructor for the
	 *             date class, as Calendar-constructed dates don't pass the
	 *             .equals() test :(
	 */
	public void testServerPrepStmtAndDate() throws Exception {
		try {
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS testServerPrepStmtAndDate");
			this.stmt.executeUpdate("CREATE TABLE testServerPrepStmtAndDate("
					+ "`P_ID` int(10) NOT NULL default '0',"
					+ "`H_ID` int(10) NOT NULL default '0',"
					+ "`R_ID` int(10) NOT NULL default '0',"
					+ "`H_Age` int(10) default NULL,"
					+ "`R_Date` date NOT NULL default '0000-00-00',"
					+ "`Comments` varchar(255) default NULL,"
					+ "`Weight` int(10) default NULL,"
					+ "`HeadGear` char(1) NOT NULL default '',"
					+ "`FinPos` int(10) default NULL,"
					+ "`Jock_ID` int(10) default NULL,"
					+ "`BtnByPrev` double default NULL,"
					+ "`BtnByWinner` double default NULL,"
					+ "`Jock_All` int(10) default NULL,"
					+ "`Draw` int(10) default NULL,"
					+ "`SF` int(10) default NULL,"
					+ "`RHR` int(10) default NULL,"
					+ "`ORating` int(10) default NULL,"
					+ "`Odds` double default NULL,"
					+ "`RaceFormPlus` int(10) default NULL,"
					+ "`PrevPerform` int(10) default NULL,"
					+ "`TrainerID` int(10) NOT NULL default '0',"
					+ "`DaysSinceRun` int(10) default NULL,"
					+ "UNIQUE KEY `P_ID` (`P_ID`),"
					+ "UNIQUE KEY `R_H_ID` (`R_ID`,`H_ID`),"
					+ "KEY `R_Date` (`R_Date`)," + "KEY `H_Age` (`H_Age`),"
					+ "KEY `TrainerID` (`TrainerID`)," + "KEY `H_ID` (`H_ID`)"
					+ ")");

			Date dt = new java.sql.Date(102, 1, 2); // Note, this represents the
			// date 2002-02-02

			PreparedStatement pStmt2 = this.conn
					.prepareStatement("INSERT INTO testServerPrepStmtAndDate (P_ID, R_Date) VALUES (171576, ?)");
			pStmt2.setDate(1, dt);
			pStmt2.executeUpdate();
			pStmt2.close();

			this.rs = this.stmt
					.executeQuery("SELECT R_Date FROM testServerPrepStmtAndDate");
			this.rs.next();

			System.out.println("Date that was stored (as String) "
					+ this.rs.getString(1)); // comes back as 2002-02-02

			PreparedStatement pStmt = this.conn
					.prepareStatement("Select P_ID,R_Date from testServerPrepStmtAndDate Where R_Date = ?   and P_ID = 171576");
			pStmt.setDate(1, dt);

			this.rs = pStmt.executeQuery();

			assertTrue(this.rs.next());

			assertEquals("171576", this.rs.getString(1));

			assertEquals(dt, this.rs.getDate(2));
		} finally {
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS testServerPrepStmtAndDate");
		}
	}

	public void testServerPrepStmtDeadlock() throws Exception {

		Connection c = getConnectionWithProps((Properties) null);

		Thread testThread1 = new PrepareThread(c);
		Thread testThread2 = new PrepareThread(c);
		testThread1.start();
		testThread2.start();
		Thread.sleep(30000);
		assertTrue(this.testServerPrepStmtDeadlockCounter >= 10);
	}

	/**
	 * Tests PreparedStatement.setCharacterStream() to ensure it accepts > 4K
	 * streams
	 * 
	 * @throws Exception
	 *             if an error occurs.
	 */
	public void testSetCharacterStream() throws Exception {
		try {
			((com.mysql.jdbc.Connection) this.conn).setTraceProtocol(true);

			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS charStreamRegressTest");
			this.stmt
					.executeUpdate("CREATE TABLE charStreamRegressTest(field1 text)");

			this.pstmt = this.conn
					.prepareStatement("INSERT INTO charStreamRegressTest VALUES (?)");

			// char[] charBuf = new char[16384];
			char[] charBuf = new char[32];

			for (int i = 0; i < charBuf.length; i++) {
				charBuf[i] = 'A';
			}

			CharArrayReader reader = new CharArrayReader(charBuf);

			this.pstmt.setCharacterStream(1, reader, charBuf.length);
			this.pstmt.executeUpdate();

			this.rs = this.stmt
					.executeQuery("SELECT LENGTH(field1) FROM charStreamRegressTest");

			this.rs.next();

			System.out.println("Character stream length: "
					+ this.rs.getString(1));

			this.rs = this.stmt
					.executeQuery("SELECT field1 FROM charStreamRegressTest");

			this.rs.next();

			String result = this.rs.getString(1);

			assertTrue(result.length() == charBuf.length);

			this.stmt.execute("TRUNCATE TABLE charStreamRegressTest");

			// Test that EOF is not thrown
			reader = new CharArrayReader(charBuf);
			this.pstmt.clearParameters();
			this.pstmt.setCharacterStream(1, reader, charBuf.length);
			this.pstmt.executeUpdate();

			this.rs = this.stmt
					.executeQuery("SELECT LENGTH(field1) FROM charStreamRegressTest");

			this.rs.next();

			System.out.println("Character stream length: "
					+ this.rs.getString(1));

			this.rs = this.stmt
					.executeQuery("SELECT field1 FROM charStreamRegressTest");

			this.rs.next();

			result = this.rs.getString(1);

			assertTrue("Retrieved value of length " + result.length()
					+ " != length of inserted value " + charBuf.length,
					result.length() == charBuf.length);

			// Test single quotes inside identifers
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS `charStream'RegressTest`");
			this.stmt
					.executeUpdate("CREATE TABLE `charStream'RegressTest`(field1 text)");

			this.pstmt = this.conn
					.prepareStatement("INSERT INTO `charStream'RegressTest` VALUES (?)");

			reader = new CharArrayReader(charBuf);
			this.pstmt.setCharacterStream(1, reader, (charBuf.length * 2));
			this.pstmt.executeUpdate();

			this.rs = this.stmt
					.executeQuery("SELECT field1 FROM `charStream'RegressTest`");

			this.rs.next();

			result = this.rs.getString(1);

			assertTrue("Retrieved value of length " + result.length()
					+ " != length of inserted value " + charBuf.length,
					result.length() == charBuf.length);
		} finally {
			((com.mysql.jdbc.Connection) this.conn).setTraceProtocol(false);

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

				this.rs = null;
			}

			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS `charStream'RegressTest`");
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS charStreamRegressTest");
		}
	}

	/**
	 * Tests a bug where Statement.setFetchSize() does not work for values other
	 * than 0 or Integer.MIN_VALUE
	 * 
	 * @throws Exception
	 *             if any errors occur
	 */
	public void testSetFetchSize() throws Exception {
		int oldFetchSize = this.stmt.getFetchSize();

		try {
			this.stmt.setFetchSize(10);
		} finally {
			this.stmt.setFetchSize(oldFetchSize);
		}
	}

	/**
	 * Tests fix for BUG#907
	 * 
	 * @throws Exception
	 *             if an error occurs
	 */
	public void testSetMaxRows() throws Exception {
		Statement maxRowsStmt = null;

		try {
			maxRowsStmt = this.conn.createStatement();
			maxRowsStmt.setMaxRows(1);
			maxRowsStmt.executeQuery("SELECT 1");
		} finally {
			if (maxRowsStmt != null) {
				maxRowsStmt.close();
			}
		}
	}

	/**
	 * Tests for timestamp NPEs occuring in binary-format timestamps.
	 * 
	 * @throws Exception
	 *             DOCUMENT ME!
	 * 
	 * @deprecated yes, we know we are using deprecated methods here :)
	 */
	public void testTimestampNPE() throws Exception {
		try {
			Timestamp ts = new Timestamp(System.currentTimeMillis());

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testTimestampNPE");
			this.stmt
					.executeUpdate("CREATE TABLE testTimestampNPE (field1 TIMESTAMP)");

			this.pstmt = this.conn
					.prepareStatement("INSERT INTO testTimestampNPE VALUES (?)");
			this.pstmt.setTimestamp(1, ts);
			this.pstmt.executeUpdate();

			this.pstmt = this.conn
					.prepareStatement("SELECT field1 FROM testTimestampNPE");

			this.rs = this.pstmt.executeQuery();

			this.rs.next();

			System.out.println(this.rs.getString(1));

			this.rs.getDate(1);

			Timestamp rTs = this.rs.getTimestamp(1);
			assertTrue("Retrieved year of " + rTs.getYear()
					+ " does not match " + ts.getYear(),
					rTs.getYear() == ts.getYear());
			assertTrue("Retrieved month of " + rTs.getMonth()
					+ " does not match " + ts.getMonth(),
					rTs.getMonth() == ts.getMonth());
			assertTrue("Retrieved date of " + rTs.getDate()
					+ " does not match " + ts.getDate(),
					rTs.getDate() == ts.getDate());

			this.stmt.executeUpdate("DROP TABLE IF EXISTS testTimestampNPE");

		} finally {
		}
	}

	public void testTruncationWithChar() throws Exception {
		try {
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS testTruncationWithChar");
			this.stmt
					.executeUpdate("CREATE TABLE testTruncationWithChar (field1 char(2))");

			this.pstmt = this.conn
					.prepareStatement("INSERT INTO testTruncationWithChar VALUES (?)");
			this.pstmt.setString(1, "00");
			this.pstmt.executeUpdate();
		} finally {
			this.stmt
					.executeUpdate("DROP TABLE IF EXISTS testTruncationWithChar");
		}
	}

	/**
	 * Tests fix for updatable streams being supported in updatable result sets.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testUpdatableStream() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS updateStreamTest");
			this.stmt
					.executeUpdate("CREATE TABLE updateStreamTest (keyField INT NOT NULL AUTO_INCREMENT PRIMARY KEY, field1 BLOB)");

			int streamLength = 16385;
			byte[] streamData = new byte[streamLength];

			/* create an updatable statement */
			Statement updStmt = this.conn.createStatement(
					ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_UPDATABLE);

			/* fill the resultset with some values */
			ResultSet updRs = updStmt
					.executeQuery("SELECT * FROM updateStreamTest");

			/* move to insertRow */
			updRs.moveToInsertRow();

			/* update the table */
			updRs.updateBinaryStream("field1", new ByteArrayInputStream(
					streamData), streamLength);

			updRs.insertRow();
		} finally {
			this.stmt.executeUpdate("DROP TABLE IF EXISTS updateStreamTest");
		}
	}

	/**
	 * Tests fix for BUG#15383 - PreparedStatement.setObject() serializes
	 * BigInteger as object, rather than sending as numeric value (and is thus
	 * not complementary to .getObject() on an UNSIGNED LONG type).
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug15383() throws Exception {
		createTable(
				"testBug15383",
				"(id INTEGER UNSIGNED NOT NULL "
						+ "AUTO_INCREMENT,value BIGINT UNSIGNED NULL DEFAULT 0,PRIMARY "
						+ "KEY(id))", "InnoDB");

		this.stmt.executeUpdate("INSERT INTO testBug15383(value) VALUES(1)");

		Statement updatableStmt = this.conn.createStatement(
				ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);

		try {
			this.rs = updatableStmt.executeQuery("SELECT * from testBug15383");

			assertTrue(this.rs.next());

			Object bigIntObj = this.rs.getObject("value");
			assertEquals("java.math.BigInteger", bigIntObj.getClass().getName());

			this.rs.updateObject("value", new BigInteger("3"));
			this.rs.updateRow();

			assertEquals("3", this.rs.getString("value"));
		} finally {
			if (this.rs != null) {
				ResultSet toClose = this.rs;
				this.rs = null;
				toClose.close();
			}

			if (updatableStmt != null) {
				updatableStmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#17099 - Statement.getGeneratedKeys() throws NPE when no
	 * query has been processed.
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug17099() throws Exception {
		if (isRunningOnJdk131()) {
			return; // test not valid
		}

		PreparedStatement pStmt = this.conn.prepareStatement("SELECT 1",
				Statement.RETURN_GENERATED_KEYS);
		assertNotNull(pStmt.getGeneratedKeys());

		if (versionMeetsMinimum(4, 1)) {
			pStmt = ((com.mysql.jdbc.Connection) this.conn)
					.clientPrepareStatement("SELECT 1",
							Statement.RETURN_GENERATED_KEYS);
			assertNotNull(pStmt.getGeneratedKeys());
		}
	}

	/**
	 * Tests fix for BUG#17587 - clearParameters() on a closed prepared
	 * statement causes NPE.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug17587() throws Exception {
		createTable("testBug17857", "(field1 int)");
		PreparedStatement pStmt = null;

		try {
			pStmt = this.conn
					.prepareStatement("INSERT INTO testBug17857 VALUES (?)");
			pStmt.close();
			try {
				pStmt.clearParameters();
			} catch (SQLException sqlEx) {
				assertEquals("08003", sqlEx.getSQLState());
			}

			pStmt = ((com.mysql.jdbc.Connection) this.conn)
					.clientPrepareStatement("INSERT INTO testBug17857 VALUES (?)");
			pStmt.close();
			try {
				pStmt.clearParameters();
			} catch (SQLException sqlEx) {
				assertEquals("08003", sqlEx.getSQLState());
			}

		} finally {
			if (pStmt != null) {
				pStmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#18740 - Data truncation and getWarnings() only returns
	 * last warning in set.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug18740() throws Exception {
		if (!versionMeetsMinimum(5, 0, 2)) {
			createTable("testWarnings", "(field1 smallint(6),"
					+ "field2 varchar(6)," + "UNIQUE KEY field1(field1))");

			try {
				this.stmt.executeUpdate("INSERT INTO testWarnings VALUES "
						+ "(10001, 'data1')," + "(10002, 'data2 foo'),"
						+ "(10003, 'data3')," + "(10004999, 'data4'),"
						+ "(10005, 'data5')");
			} catch (SQLException sqlEx) {
				String sqlStateToCompare = "01004";

				if (isJdbc4()) {
					sqlStateToCompare = "22001";
				}

				assertEquals(sqlStateToCompare, sqlEx.getSQLState());
				assertEquals(sqlStateToCompare, sqlEx.getNextException()
						.getSQLState());

				SQLWarning sqlWarn = this.stmt.getWarnings();
				assertEquals("01000", sqlWarn.getSQLState());
				assertEquals("01000", sqlWarn.getNextWarning().getSQLState());
			}
		}
	}

	protected boolean isJdbc4() {
		boolean isJdbc4;

		try {
			Class.forName("java.sql.Wrapper");
			isJdbc4 = true;
		} catch (Throwable t) {
			isJdbc4 = false;
		}

		return isJdbc4;
	}

	/**
	 * Tests fix for BUG#19615, PreparedStatement.setObject(int, Object, int)
	 * doesn't respect scale of BigDecimals.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug19615() throws Exception {
		createTable("testBug19615", "(field1 DECIMAL(19, 12))");

		BigDecimal dec = new BigDecimal("1.234567");

		this.pstmt = this.conn
				.prepareStatement("INSERT INTO testBug19615 VALUES (?)");
		this.pstmt.setObject(1, dec, Types.DECIMAL);
		this.pstmt.executeUpdate();
		this.pstmt.close();

		this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug19615");
		this.rs.next();
		assertEquals(dec, this.rs.getBigDecimal(1).setScale(6));
		this.rs.close();
		this.stmt.executeUpdate("TRUNCATE TABLE testBug19615");

		this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
				.clientPrepareStatement("INSERT INTO testBug19615 VALUES (?)");
		this.pstmt.setObject(1, dec, Types.DECIMAL);
		this.pstmt.executeUpdate();
		this.pstmt.close();

		this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug19615");
		this.rs.next();
		assertEquals(dec, this.rs.getBigDecimal(1).setScale(6));
		this.rs.close();
	}

	/**
	 * Tests fix for BUG#20029 - NPE thrown from executeBatch().
	 * 
	 * @throws Exception
	 */
	public void testBug20029() throws Exception {
		createTable("testBug20029", ("(field1 int)"));

		long initialTimeout = 20; // may need to raise this depending on
									// environment
									// we try and do this automatically in this
									// testcase

		for (int i = 0; i < 10; i++) {
			final Connection toBeKilledConn = getConnectionWithProps(new Properties());
			final long timeout = initialTimeout;
			PreparedStatement toBeKilledPstmt = null;

			try {
				toBeKilledPstmt = ((com.mysql.jdbc.Connection) toBeKilledConn)
						.clientPrepareStatement("INSERT INTO testBug20029 VALUES (?)");

				for (int j = 0; j < 1000; j++) {
					toBeKilledPstmt.setInt(1, j);
					toBeKilledPstmt.addBatch();
				}

				Thread t = new Thread() {
					public void run() {
						try {
							sleep(timeout);
							toBeKilledConn.close();
						} catch (Throwable thr) {

						}
					}
				};

				t.start();

				try {
					if (!toBeKilledConn.isClosed()) {
						initialTimeout *= 2;
						continue;
					}

					toBeKilledPstmt.executeBatch();
					fail("Should've caught a SQLException for the statement being closed here");
				} catch (BatchUpdateException batchEx) {
					assertEquals("08003", batchEx.getSQLState());
					break;
				} catch (SQLException sqlEx) {
					assertEquals("08003", sqlEx.getSQLState());
					break;
				}

				fail("Connection didn't close while in the middle of PreparedStatement.executeBatch()");
			} finally {
				if (toBeKilledPstmt != null) {
					toBeKilledPstmt.close();
				}

				if (toBeKilledConn != null) {
					toBeKilledConn.close();
				}
			}
		}
	}

	/**
	 * Fixes BUG#20687 - Can't pool server-side prepared statements, exception
	 * raised when re-using them.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug20687() throws Exception {
		if (!isRunningOnJdk131() && versionMeetsMinimum(5, 0)) {
			createTable("testBug20687", "(field1 int)");
			Connection poolingConn = null;

			Properties props = new Properties();
			props.setProperty("cachePrepStmts", "true");
			props.setProperty("useServerPrepStmts", "true");
			PreparedStatement pstmt1 = null;
			PreparedStatement pstmt2 = null;

			try {
				poolingConn = getConnectionWithProps(props);
				pstmt1 = poolingConn
						.prepareStatement("SELECT field1 FROM testBug20687");
				pstmt1.executeQuery();
				pstmt1.close();

				pstmt2 = poolingConn
						.prepareStatement("SELECT field1 FROM testBug20687");
				pstmt2.executeQuery();
				assertTrue(pstmt1 == pstmt2);
				pstmt2.close();
			} finally {
				if (pstmt1 != null) {
					pstmt1.close();
				}

				if (pstmt2 != null) {
					pstmt2.close();
				}

				if (poolingConn != null) {
					poolingConn.close();
				}
			}
		}
	}

	public void testLikeWithBackslashes() throws Exception {
		if (!versionMeetsMinimum(5, 0, 0)) {
			return;
		}

		Connection noBackslashEscapesConn = null;

		try {
			Properties props = new Properties();
			props.setProperty("sessionVariables",
					"sql_mode=NO_BACKSLASH_ESCAPES");

			noBackslashEscapesConn = getConnectionWithProps(props);

			createTable(
					"X_TEST",
					"(userName varchar(32) not null, ivalue integer, CNAME varchar(255), bvalue CHAR(1), svalue varchar(255), ACTIVE CHAR(1), primary key (userName))");

			String insert_sql = "insert into X_TEST (ivalue, CNAME, bvalue, svalue, ACTIVE, userName) values (?, ?, ?, ?, ?, ?)";

			this.pstmt = noBackslashEscapesConn.prepareStatement(insert_sql);
			this.pstmt.setInt(1, 0);
			this.pstmt.setString(2, "c:\\jetson");
			this.pstmt.setInt(3, 1);
			this.pstmt.setString(4, "c:\\jetson");
			this.pstmt.setInt(5, 1);
			this.pstmt.setString(6, "c:\\jetson");
			this.pstmt.execute();

			String select_sql = "select user0_.userName as userName0_0_, user0_.ivalue as ivalue0_0_, user0_.CNAME as CNAME0_0_, user0_.bvalue as bvalue0_0_, user0_.svalue as svalue0_0_, user0_.ACTIVE as ACTIVE0_0_ from X_TEST user0_ where user0_.userName like ?";
			this.pstmt = noBackslashEscapesConn.prepareStatement(select_sql);
			this.pstmt.setString(1, "c:\\j%");
			// if we comment out the previous line and uncomment the following,
			// the like clause matches
			// this.pstmt.setString(1,"c:\\\\j%");
			System.out.println("about to execute query " + select_sql);
			this.rs = this.pstmt.executeQuery();
			assertTrue(this.rs.next());
		} finally {
			if (noBackslashEscapesConn != null) {
				noBackslashEscapesConn.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#20650 - Statement.cancel() causes NullPointerException
	 * if underlying connection has been closed due to server failure.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug20650() throws Exception {
		Connection closedConn = null;
		Statement cancelStmt = null;

		try {
			closedConn = getConnectionWithProps((String) null);
			cancelStmt = closedConn.createStatement();

			closedConn.close();

			cancelStmt.cancel();
		} finally {
			if (cancelStmt != null) {
				cancelStmt.close();
			}

			if (closedConn != null && !closedConn.isClosed()) {
				closedConn.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#20888 - escape of quotes in client-side prepared
	 * statements parsing not respected.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug20888() throws Exception {
		String s = "SELECT 'What do you think about D\\'Artanian''?', \"What do you think about D\\\"Artanian\"\"?\"";
		this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
				.clientPrepareStatement(s);

		this.rs = this.pstmt.executeQuery();
		this.rs.next();
		assertEquals(this.rs.getString(1),
				"What do you think about D'Artanian'?");
		assertEquals(this.rs.getString(2),
				"What do you think about D\"Artanian\"?");
	}

	/**
	 * Tests Bug#21207 - Driver throws NPE when tracing prepared statements that
	 * have been closed (in asSQL()).
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug21207() throws Exception {
		this.pstmt = this.conn.prepareStatement("SELECT 1");
		this.pstmt.close();
		this.pstmt.toString(); // this used to cause an NPE
	}

	/**
	 * Tests BUG#21438, server-side PS fails when using jdbcCompliantTruncation.
	 * If either is set to FALSE (&useServerPrepStmts=false or
	 * &jdbcCompliantTruncation=false) test succedes.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */

	public void testBug21438() throws Exception {
		createTable("testBug21438",
				"(t_id int(10), test_date timestamp NOT NULL,primary key t_pk (t_id));");

		assertEquals(
				1,
				this.stmt
						.executeUpdate("insert into testBug21438 values (1,NOW());"));

		if (this.versionMeetsMinimum(4, 1)) {
			this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
					.serverPrepareStatement("UPDATE testBug21438 SET test_date=ADDDATE(?,INTERVAL 1 YEAR) WHERE t_id=1;");
			Timestamp ts = new Timestamp(System.currentTimeMillis());
			ts.setNanos(999999999);

			this.pstmt.setTimestamp(1, ts);

			assertEquals(1, this.pstmt.executeUpdate());

			Timestamp future = (Timestamp) getSingleIndexedValueWithQuery(1,
					"SELECT test_date FROM testBug21438");
			assertEquals(future.getYear() - ts.getYear(), 1);
		}
	}

	/**
	 * Tests fix for BUG#22359 - Driver was using millis for
	 * Statement.setQueryTimeout() when spec says argument is seconds.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug22359() throws Exception {
		if (versionMeetsMinimum(5, 0)) {
			Statement timeoutStmt = null;

			try {
				timeoutStmt = this.conn.createStatement();
				timeoutStmt.setQueryTimeout(2);

				long begin = System.currentTimeMillis();

				try {
					timeoutStmt.execute("SELECT SLEEP(30)");
					fail("Query didn't time out");
				} catch (MySQLTimeoutException timeoutEx) {
					long end = System.currentTimeMillis();

					assertTrue((end - begin) > 1000);
				}
			} finally {
				if (timeoutStmt != null) {
					timeoutStmt.close();
				}
			}
		}
	}

	/**
	 * Tests fix for BUG#22290 - Driver issues truncation on write exception
	 * when it shouldn't (due to sending big decimal incorrectly to server with
	 * server-side prepared statement).
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug22290() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		createTable(
				"testbug22290",
				"(`id` int(11) NOT NULL default '1',`cost` decimal(10,2) NOT NULL,PRIMARY KEY  (`id`))"
						+ " DEFAULT CHARSET=utf8", "InnoDB");
		assertEquals(
				this.stmt
						.executeUpdate("INSERT INTO testbug22290 (`id`,`cost`) VALUES (1,'1.00')"),
				1);

		Connection configuredConn = null;

		try {
			Properties props = new Properties();
			props.setProperty("sessionVariables",
					"sql_mode='STRICT_TRANS_TABLES'");

			configuredConn = getConnectionWithProps(props);

			this.pstmt = configuredConn
					.prepareStatement("update testbug22290 set cost = cost + ? where id = 1");
			this.pstmt.setBigDecimal(1, new BigDecimal("1.11"));
			assertEquals(this.pstmt.executeUpdate(), 1);

			assertEquals(
					this.stmt
							.executeUpdate("UPDATE testbug22290 SET cost='1.00'"),
					1);
			this.pstmt = ((com.mysql.jdbc.Connection) configuredConn)
					.clientPrepareStatement("update testbug22290 set cost = cost + ? where id = 1");
			this.pstmt.setBigDecimal(1, new BigDecimal("1.11"));
			assertEquals(this.pstmt.executeUpdate(), 1);
		} finally {
			if (configuredConn != null) {
				configuredConn.close();
			}
		}
	}

	public void testClientPreparedSetBoolean() throws Exception {
		this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
				.clientPrepareStatement("SELECT ?");
		this.pstmt.setBoolean(1, false);
		assertEquals(
				"SELECT 0",
				this.pstmt.toString().substring(
						this.pstmt.toString().indexOf("SELECT")));
		this.pstmt.setBoolean(1, true);
		assertEquals(
				"SELECT 1",
				this.pstmt.toString().substring(
						this.pstmt.toString().indexOf("SELECT")));
	}

	/**
	 * Tests fix for BUG#24360 .setFetchSize() breaks prepared SHOW and other
	 * commands.
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug24360() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		Connection c = null;

		Properties props = new Properties();
		props.setProperty("useServerPrepStmts", "true");

		try {
			c = getConnectionWithProps(props);

			this.pstmt = c.prepareStatement("SHOW PROCESSLIST");
			this.pstmt.setFetchSize(5);
			this.pstmt.execute();
		} finally {
			if (c != null) {
				c.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#24344 - useJDBCCompliantTimezoneShift with server-side
	 * prepared statements gives different behavior than when using client-side
	 * prepared statements. (this is now fixed if moving from server-side
	 * prepared statements to client-side prepared statements by setting
	 * "useSSPSCompatibleTimezoneShift" to "true", as the driver can't tell if
	 * this is a new deployment that never used server-side prepared statements,
	 * or if it is an existing deployment that is switching to client-side
	 * prepared statements from server-side prepared statements.
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug24344() throws Exception {

		if (!versionMeetsMinimum(4, 1)) {
			return; // need SSPS
		}

		super.createTable("testBug24344",
				"(i INT AUTO_INCREMENT, t1 DATETIME, PRIMARY KEY (i)) ENGINE = MyISAM");

		Connection conn2 = null;

		try {
			Properties props = new Properties();
			props.setProperty("useServerPrepStmts", "true");
			props.setProperty("useJDBCCompliantTimezoneShift", "true");
			conn2 = super.getConnectionWithProps(props);
			this.pstmt = conn2
					.prepareStatement("INSERT INTO testBug24344 (t1) VALUES (?)");
			Calendar c = Calendar.getInstance();
			this.pstmt.setTimestamp(1, new Timestamp(c.getTime().getTime()));
			this.pstmt.execute();
			this.pstmt.close();
			conn2.close();

			props.setProperty("useServerPrepStmts", "false");
			props.setProperty("useJDBCCompliantTimezoneShift", "true");
			props.setProperty("useSSPSCompatibleTimezoneShift", "true");

			conn2 = super.getConnectionWithProps(props);
			this.pstmt = conn2
					.prepareStatement("INSERT INTO testBug24344 (t1) VALUES (?)");
			this.pstmt.setTimestamp(1, new Timestamp(c.getTime().getTime()));
			this.pstmt.execute();
			this.pstmt.close();
			conn2.close();

			props.setProperty("useServerPrepStmts", "false");
			props.setProperty("useJDBCCompliantTimezoneShift", "false");
			props.setProperty("useSSPSCompatibleTimezoneShift", "false");
			conn2 = super.getConnectionWithProps(props);
			this.pstmt = conn2
					.prepareStatement("INSERT INTO testBug24344 (t1) VALUES (?)");
			this.pstmt.setTimestamp(1, new Timestamp(c.getTime().getTime()));
			this.pstmt.execute();
			this.pstmt.close();

			Statement s = conn2.createStatement();
			this.rs = s
					.executeQuery("SELECT t1 FROM testBug24344 ORDER BY i ASC");

			Timestamp[] dates = new Timestamp[3];

			int i = 0;

			while (rs.next()) {
				dates[i++] = rs.getTimestamp(1);
			}

			assertEquals("Number of rows should be 3.", 3, i);
			assertEquals(dates[0], dates[1]);
			assertTrue(!dates[1].equals(dates[2]));
		} finally {
			if (conn2 != null) {
				conn2.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#25073 - rewriting batched statements leaks internal
	 * statement instances, and causes a memory leak.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug25073() throws Exception {
		if (isRunningOnJdk131()) {
			return;
		}

		Properties props = new Properties();
		props.setProperty("rewriteBatchedStatements", "true");
		Connection multiConn = getConnectionWithProps(props);
		createTable("testBug25073",
				"(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)");
		Statement multiStmt = multiConn.createStatement();
		multiStmt.addBatch("INSERT INTO testBug25073(field1) VALUES (1)");
		multiStmt.addBatch("INSERT INTO testBug25073(field1) VALUES (2)");
		multiStmt.addBatch("INSERT INTO testBug25073(field1) VALUES (3)");
		multiStmt.addBatch("INSERT INTO testBug25073(field1) VALUES (4)");
		multiStmt.addBatch("UPDATE testBug25073 SET field1=5 WHERE field1=1");
		multiStmt
				.addBatch("UPDATE testBug25073 SET field1=6 WHERE field1=2 OR field1=3");

		int beforeOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		multiStmt.executeBatch();

		int afterOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		assertEquals(beforeOpenStatementCount, afterOpenStatementCount);

		createTable("testBug25073",
				"(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)");
		props.clear();
		props.setProperty("rewriteBatchedStatements", "true");
		props.setProperty("maxAllowedPacket", "1024");
		props.setProperty("dumpQueriesOnException", "true");
		props.setProperty("maxQuerySizeToLog", String.valueOf(1024 * 1024 * 2));
		multiConn = getConnectionWithProps(props);
		multiStmt = multiConn.createStatement();

		for (int i = 0; i < 1000; i++) {
			multiStmt.addBatch("INSERT INTO testBug25073(field1) VALUES (" + i
					+ ")");
		}

		beforeOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		multiStmt.executeBatch();

		afterOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		assertEquals(beforeOpenStatementCount, afterOpenStatementCount);

		createTable("testBug25073",
				"(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)");

		props.clear();
		props.setProperty("useServerPrepStmts", "false");
		props.setProperty("rewriteBatchedStatements", "true");
		props.setProperty("dumpQueriesOnException", "true");
		props.setProperty("maxQuerySizeToLog", String.valueOf(1024 * 1024 * 2));
		multiConn = getConnectionWithProps(props);
		PreparedStatement pStmt = multiConn.prepareStatement(
				"INSERT INTO testBug25073(field1) VALUES (?)",
				Statement.RETURN_GENERATED_KEYS);

		for (int i = 0; i < 1000; i++) {
			pStmt.setInt(1, i);
			pStmt.addBatch();
		}

		beforeOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		pStmt.executeBatch();

		afterOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		assertEquals(beforeOpenStatementCount, afterOpenStatementCount);

		createTable("testBug25073",
				"(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)");
		props.setProperty("useServerPrepStmts", "false");
		props.setProperty("rewriteBatchedStatements", "true");
		props.setProperty("maxAllowedPacket", "1024");
		props.setProperty("dumpQueriesOnException", "true");
		props.setProperty("maxQuerySizeToLog", String.valueOf(1024 * 1024 * 2));
		multiConn = getConnectionWithProps(props);
		pStmt = multiConn.prepareStatement(
				"INSERT INTO testBug25073(field1) VALUES (?)",
				Statement.RETURN_GENERATED_KEYS);

		for (int i = 0; i < 1000; i++) {
			pStmt.setInt(1, i);
			pStmt.addBatch();
		}

		beforeOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		pStmt.executeBatch();

		afterOpenStatementCount = ((com.mysql.jdbc.Connection) multiConn)
				.getActiveStatementCount();

		assertEquals(beforeOpenStatementCount, afterOpenStatementCount);
	}

	/**
	 * Tests fix for BUG#25009 - Results from updates not handled correctly in
	 * multi-statement queries.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug25009() throws Exception {
		if (!versionMeetsMinimum(4, 1)) {
			return;
		}

		Properties props = new Properties();
		props.setProperty("allowMultiQueries", "true");

		Connection multiConn = getConnectionWithProps(props);
		createTable("testBug25009", "(field1 INT)");

		try {
			Statement multiStmt = multiConn.createStatement();
			multiStmt
					.execute("SELECT 1;SET @a=1; SET @b=2; SET @c=3; INSERT INTO testBug25009 VALUES (1)");

			assertEquals(-1, multiStmt.getUpdateCount());

			this.rs = multiStmt.getResultSet();
			assertTrue(this.rs.next());
			assertEquals(multiStmt.getMoreResults(), false);

			for (int i = 0; i < 3; i++) {
				assertEquals(0, multiStmt.getUpdateCount());
				assertEquals(multiStmt.getMoreResults(), false);
			}

			assertEquals(1, multiStmt.getUpdateCount());

			this.rs = multiStmt.executeQuery("SELECT field1 FROM testBug25009");
			assertTrue(this.rs.next());
			assertEquals(1, this.rs.getInt(1));

		} finally {
			if (multiConn != null) {
				multiConn.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#25025 - Client-side prepared statement parser gets
	 * confused by in-line (slash-star) comments and therefore can't rewrite
	 * batched statements or reliably detect type of statements when they're
	 * used.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug25025() throws Exception {

		Connection multiConn = null;

		createTable("testBug25025", "(field1 INT)");

		try {
			Properties props = new Properties();
			props.setProperty("rewriteBatchedStatements", "true");
			props.setProperty("useServerPrepStmts", "false");

			multiConn = getConnectionWithProps(props);

			this.pstmt = multiConn
					.prepareStatement("/* insert foo.bar.baz INSERT INTO foo VALUES (?,?,?,?) to trick parser */ INSERT into testBug25025 VALUES (?)");
			this.pstmt.setInt(1, 1);
			this.pstmt.addBatch();
			this.pstmt.setInt(1, 2);
			this.pstmt.addBatch();
			this.pstmt.setInt(1, 3);
			this.pstmt.addBatch();

			int[] counts = this.pstmt.executeBatch();

			assertEquals(3, counts.length);
			assertEquals(3, counts[0]);
			assertEquals(3, counts[1]);
			assertEquals(3, counts[2]);
			assertEquals(true,
					((com.mysql.jdbc.PreparedStatement) this.pstmt)
							.canRewriteAsMultiValueInsertAtSqlLevel());
		} finally {
			if (multiConn != null) {
				multiConn.close();
			}
		}
	}

	public void testBustedGGKWithPSExecute() throws Exception {
		createTable("sequence",
				"(sequence_name VARCHAR(32) NOT NULL PRIMARY KEY, next_val BIGINT NOT NULL)");

		// Populate with the initial value
		stmt.executeUpdate("INSERT INTO sequence VALUES ('test-sequence', 1234)");

		// Atomic operation to increment and return next value
		PreparedStatement pStmt = null;

		try {
			pStmt = this.conn
					.prepareStatement(
							"UPDATE sequence SET next_val=LAST_INSERT_ID(next_val + ?) WHERE sequence_name = ?",
							Statement.RETURN_GENERATED_KEYS);

			pStmt.setInt(1, 4);
			pStmt.setString(2, "test-sequence");
			pStmt.execute();

			this.rs = pStmt.getGeneratedKeys();
			this.rs.next();
			assertEquals(1238, this.rs.getLong(1));
		} finally {
			if (pStmt != null) {
				pStmt.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#28256 - When connection is in read-only mode, queries
	 * that are parentheized incorrectly identified as DML.
	 * 
	 * @throws Exception
	 */
	public void testBug28256() throws Exception {
		try {
			this.conn.setReadOnly(true);
			this.stmt.execute("(SELECT 1) UNION (SELECT 2)");
			this.conn.prepareStatement("(SELECT 1) UNION (SELECT 2)").execute();
			if (versionMeetsMinimum(4, 1)) {
				((com.mysql.jdbc.Connection) this.conn).serverPrepareStatement(
						"(SELECT 1) UNION (SELECT 2)").execute();
			}
		} finally {
			this.conn.setReadOnly(false);
		}
	}

	/**
	 * Tests fix for BUG#28469 - PreparedStatement.getMetaData() for statements
	 * containing leading one-line comments is not returned correctly.
	 * 
	 * As part of this fix, we also overhauled detection of DML for
	 * executeQuery() and SELECTs for executeUpdate() in plain and prepared
	 * statements to be aware of the same types of comments.
	 * 
	 * @throws Exception
	 */
	public void testBug28469() throws Exception {
		PreparedStatement commentStmt = null;

		try {
			String[] statementsToTest = { "-- COMMENT\nSELECT 1",
					"# COMMENT\nSELECT 1", "/* comment */ SELECT 1" };

			for (int i = 0; i < statementsToTest.length; i++) {
				commentStmt = this.conn.prepareStatement(statementsToTest[i]);

				assertNotNull(commentStmt.getMetaData());

				try {
					commentStmt.executeUpdate();
					fail("Should not be able to call executeUpdate() on a SELECT statement!");
				} catch (SQLException sqlEx) {
					// expected
				}

				this.rs = commentStmt.executeQuery();
				this.rs.next();
				assertEquals(1, this.rs.getInt(1));
			}

			createTable("testBug28469", "(field1 INT)");

			String[] updatesToTest = {
					"-- COMMENT\nUPDATE testBug28469 SET field1 = 2",
					"# COMMENT\nUPDATE testBug28469 SET field1 = 2",
					"/* comment */ UPDATE testBug28469 SET field1 = 2" };

			for (int i = 0; i < updatesToTest.length; i++) {
				commentStmt = this.conn.prepareStatement(updatesToTest[i]);

				assertNull(commentStmt.getMetaData());

				try {
					commentStmt.executeQuery();
					fail("Should not be able to call executeQuery() on a SELECT statement!");
				} catch (SQLException sqlEx) {
					// expected
				}

				try {
					this.stmt.executeQuery(updatesToTest[i]);
					fail("Should not be able to call executeQuery() on a SELECT statement!");
				} catch (SQLException sqlEx) {
					// expected
				}
			}
		} finally {
			if (commentStmt != null) {
				commentStmt.close();
			}
		}
	}

	/**
	 * Tests error with slash-star comment at EOL
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testCommentParsing() throws Exception {
		createTable("PERSON", "(NAME VARCHAR(32), PERID VARCHAR(32))");

		this.pstmt = this.conn
				.prepareStatement("SELECT NAME AS name2749_0_, PERID AS perid2749_0_ FROM PERSON WHERE PERID=? /*FOR UPDATE*/");
	}

	/**
	 * Tests fix for BUG#28851 - parser in client-side prepared statements eats
	 * character following '/' if it's not a multi-line comment.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug28851() throws Exception {
		this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
				.clientPrepareStatement("SELECT 1/?");
		this.pstmt.setInt(1, 1);
		this.rs = this.pstmt.executeQuery();

		assertTrue(this.rs.next());

		assertEquals(1, this.rs.getInt(1));

	}

	/**
	 * Tests fix for BUG#28596 - parser in client-side prepared statements runs
	 * to end of statement, rather than end-of-line for '#' comments.
	 * 
	 * Also added support for '--' single-line comments
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug28596() throws Exception {
		String query = "SELECT #\n" + "?, #\n" + "? #?\r\n" + ",-- abcdefg \n"
				+ "?";

		this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
				.clientPrepareStatement(query);
		this.pstmt.setInt(1, 1);
		this.pstmt.setInt(2, 2);
		this.pstmt.setInt(3, 3);

		assertEquals(3, this.pstmt.getParameterMetaData().getParameterCount());
		this.rs = this.pstmt.executeQuery();

		assertTrue(this.rs.next());

		assertEquals(1, this.rs.getInt(1));
		assertEquals(2, this.rs.getInt(2));
		assertEquals(3, this.rs.getInt(3));
	}

	/**
	 * Tests fix for BUG#30550 - executeBatch() on an empty batch when there are
	 * no elements in the batch causes a divide-by-zero error when rewriting is
	 * enabled.
	 * 
	 * @throws Exception
	 *             if the test fails
	 */
	public void testBug30550() throws Exception {
		createTable("testBug30550", "(field1 int)");

		Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true");
		PreparedStatement batchPStmt = null;
		Statement batchStmt = null;

		try {
			batchStmt = rewriteConn.createStatement();
			assertEquals(0, batchStmt.executeBatch().length);

			batchStmt.addBatch("INSERT INTO testBug30550 VALUES (1)");
			int[] counts = batchStmt.executeBatch();
			assertEquals(1, counts.length);
			assertEquals(1, counts[0]);
			assertEquals(0, batchStmt.executeBatch().length);

			batchPStmt = rewriteConn
					.prepareStatement("INSERT INTO testBug30550 VALUES (?)");
			batchPStmt.setInt(1, 1);
			assertEquals(0, batchPStmt.executeBatch().length);
			batchPStmt.addBatch();
			counts = batchPStmt.executeBatch();
			assertEquals(1, counts.length);
			assertEquals(1, counts[0]);
			assertEquals(0, batchPStmt.executeBatch().length);
		} finally {
			if (batchPStmt != null) {
				batchPStmt.close();
			}

			if (batchStmt != null) {
				batchStmt.close();
			}
			if (rewriteConn != null) {
				rewriteConn.close();
			}
		}
	}

	/**
	 * Tests fix for Bug#27412 - cached metadata with
	 * PreparedStatement.execute() throws NullPointerException.
	 * 
	 * @throws Exception
	 */
	public void testBug27412() throws Exception {
		Properties props = new Properties();
		props.put("useServerPrepStmts", "false");
		props.put("cachePreparedStatements", "true");
		props.put("cacheResultSetMetadata", "true");
		Connection conn2 = getConnectionWithProps(props);
		PreparedStatement pstm = conn2.prepareStatement("SELECT 1");
		try {
			assertTrue(pstm.execute());
		} finally {
			pstm.close();
			conn2.close();
		}
	}

	public void testBustedGGKColumnNames() throws Exception {
		createTable("testBustedGGKColumnNames",
				"(field1 int primary key auto_increment)");
		this.stmt.executeUpdate(
				"INSERT INTO testBustedGGKColumnNames VALUES (null)",
				Statement.RETURN_GENERATED_KEYS);
		assertEquals("GENERATED_KEY", this.stmt.getGeneratedKeys()
				.getMetaData().getColumnName(1));

		this.pstmt = this.conn.prepareStatement(
				"INSERT INTO testBustedGGKColumnNames VALUES (null)",
				Statement.RETURN_GENERATED_KEYS);
		this.pstmt.executeUpdate();
		assertEquals("GENERATED_KEY", this.pstmt.getGeneratedKeys()
				.getMetaData().getColumnName(1));

		if (versionMeetsMinimum(4, 1, 0)) {
			this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
					.serverPrepareStatement(
							"INSERT INTO testBustedGGKColumnNames VALUES (null)",
							Statement.RETURN_GENERATED_KEYS);
			this.pstmt.executeUpdate();
			assertEquals("GENERATED_KEY", this.pstmt.getGeneratedKeys()
					.getMetaData().getColumnName(1));
		}

	}

	public void testLancesBitMappingBug() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		createTable("Bit_TabXXX", "( `MAX_VAL` BIT default NULL, "
				+ "`MIN_VAL` BIT default NULL, `NULL_VAL` BIT default NULL) "
				+ "DEFAULT CHARSET=latin1", "InnoDB");

		// add Bit_In_MinXXX procedure
		createProcedure("Bit_In_MinXXX",
				"(MIN_PARAM TINYINT(1)) begin update Bit_TabXXX set MIN_VAL=MIN_PARAM; end");

		createProcedure("Bit_In_MaxXXX",
				"(MAX_PARAM TINYINT(1)) begin update Bit_TabXXX set MAX_VAL=MAX_PARAM; end");

		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		String sPrepStmt = "{call Bit_In_MinXXX(?)}";
		this.pstmt = conn.prepareStatement(sPrepStmt);
		this.pstmt.setObject(1, "true", java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"true",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, "false", java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"false",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, "1", java.sql.Types.BIT); // fails
		this.pstmt.executeUpdate();
		assertEquals(
				"true",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, "0", java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"false",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, Boolean.TRUE, java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"true",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, Boolean.FALSE, java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"false",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, new Boolean(true), java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"true",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, new Boolean(false), java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"false",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, new Byte("1"), java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"true",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
		this.stmt.execute("TRUNCATE TABLE Bit_TabXXX");
		this.stmt.execute("insert into Bit_TabXXX values(null,0,null)");

		this.pstmt.setObject(1, new Byte("0"), java.sql.Types.BIT);
		this.pstmt.executeUpdate();
		assertEquals(
				"false",
				getSingleIndexedValueWithQuery(1,
						"SELECT MIN_VAL FROM Bit_TabXXX").toString());
	}

	/**
	 * Tests fix for BUG#32577 - no way to store two timestamp/datetime values
	 * that happens over the DST switchover, as the hours end up being the same
	 * when sent as the literal that MySQL requires.
	 * 
	 * Note that to get this scenario to work with MySQL (since it doesn't
	 * support per-value timezones), you need to configure your server (or
	 * session) to be in UTC, and tell the driver not to use the legacy
	 * date/time code by setting "useLegacyDatetimeCode" to "false". This will
	 * cause the driver to always convert to/from the server and client timezone
	 * consistently.
	 * 
	 * @throws Exception
	 */
	public void testBug32577() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		createTable("testBug32577",
				"(id INT, field_datetime DATETIME, field_timestamp TIMESTAMP)");
		Properties props = new Properties();
		props.setProperty("useLegacyDatetimeCode", "false");
		props.setProperty("sessionVariables", "time_zone='+0:00'");
		props.setProperty("serverTimezone", "UTC");

		Connection nonLegacyConn = getConnectionWithProps(props);

		try {
			long earlier = 1194154200000L;
			long later = 1194157800000L;

			this.pstmt = nonLegacyConn
					.prepareStatement("INSERT INTO testBug32577 VALUES (?,?,?)");
			Timestamp ts = new Timestamp(earlier);
			this.pstmt.setInt(1, 1);
			this.pstmt.setTimestamp(2, ts);
			this.pstmt.setTimestamp(3, ts);
			this.pstmt.executeUpdate();

			ts = new Timestamp(later);
			this.pstmt.setInt(1, 2);
			this.pstmt.setTimestamp(2, ts);
			this.pstmt.setTimestamp(3, ts);
			this.pstmt.executeUpdate();

			this.rs = nonLegacyConn
					.createStatement()
					.executeQuery(
							"SELECT id, field_datetime, field_timestamp "
									+ ", UNIX_TIMESTAMP(field_datetime), UNIX_TIMESTAMP(field_timestamp) "
									+ "FROM testBug32577 ORDER BY id ASC");

			this.rs.next();

			//java.util.Date date1 = new Date(this.rs.getTimestamp(2).getTime());
			Timestamp ts1 = this.rs.getTimestamp(3);
			long datetimeSeconds1 = rs.getLong(4) * 1000;
			long timestampSeconds1 = rs.getLong(5) * 1000;

			this.rs.next();

			//java.util.Date date2 = new Date(this.rs.getTimestamp(2).getTime());
			Timestamp ts2 = this.rs.getTimestamp(3);
			long datetimeSeconds2 = rs.getLong(4) * 1000;
			long timestampSeconds2 = rs.getLong(5) * 1000;

			assertEquals(later, datetimeSeconds2);
			assertEquals(later, timestampSeconds2);
			assertEquals(earlier, datetimeSeconds1);
			assertEquals(earlier, timestampSeconds1);

			SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm z");
			sdf.setTimeZone(TimeZone.getTimeZone("America/New York"));
			System.out.println(sdf.format(ts2));
			System.out.println(sdf.format(ts1));
		} finally {
			if (nonLegacyConn != null) {
				nonLegacyConn.close();
			}
		}
	}

	/**
	 * Tests fix for BUG#30508 - ResultSet returned by
	 * Statement.getGeneratedKeys() is not closed automatically when statement
	 * that created it is closed.
	 * 
	 * @throws Exception
	 */
	public void testBug30508() throws Exception {
		createTable("testBug30508",
				"(k INT PRIMARY KEY NOT NULL AUTO_INCREMENT, p VARCHAR(32))");
		try {
			Statement ggkStatement = this.conn.createStatement();
			ggkStatement.executeUpdate(
					"INSERT INTO testBug30508 (p) VALUES ('abc')",
					Statement.RETURN_GENERATED_KEYS);

			this.rs = ggkStatement.getGeneratedKeys();
			ggkStatement.close();

			this.rs.next();
			fail("Should've had an exception here");
		} catch (SQLException sqlEx) {
			assertEquals("S1000", sqlEx.getSQLState());
		}

		try {
			this.pstmt = this.conn.prepareStatement("SELECT 1",
					Statement.RETURN_GENERATED_KEYS);
			this.rs = this.pstmt.getGeneratedKeys();
			this.pstmt.close();
			this.rs.next();
			fail("Should've had an exception here");
		} catch (SQLException sqlEx) {
			assertEquals("S1000", sqlEx.getSQLState());
		}

		if (versionMeetsMinimum(5, 0)) {
			createProcedure("testBug30508", "() BEGIN SELECT 1; END");

			try {
				this.pstmt = this.conn.prepareCall("{CALL testBug30508()}");
				this.rs = this.pstmt.getGeneratedKeys();
				this.pstmt.close();
				this.rs.next();
				fail("Should've had an exception here");
			} catch (SQLException sqlEx) {
				assertEquals("S1000", sqlEx.getSQLState());
			}
		}
	}

	public void testMoreLanceBugs() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		createTable("Bit_Tab", "( `MAX_VAL` BIT default NULL, "
				+ "`MIN_VAL` BIT default NULL, `NULL_VAL` BIT default NULL) "
				+ "DEFAULT CHARSET=latin1", "InnoDB");
		// this.stmt.execute("insert into Bit_Tab values(null,0,null)");
		createProcedure(
				"Bit_Proc",
				"(out MAX_PARAM TINYINT, out MIN_PARAM TINYINT, out NULL_PARAM TINYINT) "
						+ "begin select MAX_VAL, MIN_VAL, NULL_VAL  into MAX_PARAM, MIN_PARAM, NULL_PARAM from Bit_Tab; end ");

		Boolean minBooleanVal;
		Boolean oRetVal;
		String Min_Val_Query = "SELECT MIN_VAL from Bit_Tab";
		String Min_Insert = "insert into Bit_Tab values(1,0,null)";
		// System.out.println("Value to insert=" + extractVal(Min_Insert,1));
		CallableStatement cstmt;

		this.stmt.executeUpdate("delete from Bit_Tab");
		this.stmt.executeUpdate(Min_Insert);
		cstmt = this.conn.prepareCall("{call Bit_Proc(?,?,?)}");

		cstmt.registerOutParameter(1, java.sql.Types.BIT);
		cstmt.registerOutParameter(2, java.sql.Types.BIT);
		cstmt.registerOutParameter(3, java.sql.Types.BIT);

		cstmt.executeUpdate();

		boolean bRetVal = cstmt.getBoolean(2);
		oRetVal = new Boolean(bRetVal);
		minBooleanVal = new Boolean("false");
		this.rs = this.stmt.executeQuery(Min_Val_Query);
		assertEquals(minBooleanVal, oRetVal);
	}

	public void testBug33823() {
		new ResultSetInternalMethods() {

			public void buildIndexMapping() throws SQLException {

			}

			public void clearNextResult() {

			}

			public ResultSetInternalMethods copy() throws SQLException {

				return null;
			}

			public char getFirstCharOfQuery() {

				return 0;
			}

			public ResultSetInternalMethods getNextResultSet() {

				return null;
			}

			public Object getObjectStoredProc(int columnIndex,
					int desiredSqlType) throws SQLException {

				return null;
			}

			public Object getObjectStoredProc(int i, Map<Object, Object> map, int desiredSqlType)
					throws SQLException {

				return null;
			}

			public Object getObjectStoredProc(String columnName,
					int desiredSqlType) throws SQLException {

				return null;
			}

			public Object getObjectStoredProc(String colName, Map<Object, Object> map,
					int desiredSqlType) throws SQLException {

				return null;
			}

			public String getServerInfo() {

				return null;
			}

			public long getUpdateCount() {

				return 0;
			}

			public long getUpdateID() {

				return 0;
			}

			public void initializeFromCachedMetaData(
					CachedResultSetMetaData cachedMetaData) {
				cachedMetaData.getFields();

			}

			public void initializeWithMetadata() throws SQLException {

			}

			public void populateCachedMetaData(
					CachedResultSetMetaData cachedMetaData) throws SQLException {

			}

			public void realClose(boolean calledExplicitly) throws SQLException {

			}

			public boolean reallyResult() {

				return false;
			}

			public void redefineFieldsForDBMD(Field[] metadataFields) {

			}

			public void setFirstCharOfQuery(char firstCharUpperCase) {

			}

			public void setOwningStatement(StatementImpl owningStatement) {

			}

			public void setStatementUsedForFetchingRows(
					com.mysql.jdbc.PreparedStatement stmt) {

			}

			public void setWrapperStatement(Statement wrapperStatement) {

			}

			public boolean absolute(int row) throws SQLException {

				return false;
			}

			public void afterLast() throws SQLException {

			}

			public void beforeFirst() throws SQLException {

			}

			public void cancelRowUpdates() throws SQLException {

			}

			public void clearWarnings() throws SQLException {

			}

			public void close() throws SQLException {

			}

			public void deleteRow() throws SQLException {

			}

			public int findColumn(String columnName) throws SQLException {

				return 0;
			}

			public boolean first() throws SQLException {

				return false;
			}

			public Array getArray(int i) throws SQLException {

				return null;
			}

			public Array getArray(String colName) throws SQLException {

				return null;
			}

			public InputStream getAsciiStream(int columnIndex)
					throws SQLException {

				return null;
			}

			public InputStream getAsciiStream(String columnName)
					throws SQLException {

				return null;
			}

			public BigDecimal getBigDecimal(int columnIndex)
					throws SQLException {

				return null;
			}

			public BigDecimal getBigDecimal(String columnName)
					throws SQLException {

				return null;
			}

			public BigDecimal getBigDecimal(int columnIndex, int scale)
					throws SQLException {

				return null;
			}

			public BigDecimal getBigDecimal(String columnName, int scale)
					throws SQLException {

				return null;
			}

			public InputStream getBinaryStream(int columnIndex)
					throws SQLException {

				return null;
			}

			public InputStream getBinaryStream(String columnName)
					throws SQLException {

				return null;
			}

			public Blob getBlob(int i) throws SQLException {

				return null;
			}

			public Blob getBlob(String colName) throws SQLException {

				return null;
			}

			public boolean getBoolean(int columnIndex) throws SQLException {

				return false;
			}

			public boolean getBoolean(String columnName) throws SQLException {

				return false;
			}

			public byte getByte(int columnIndex) throws SQLException {

				return 0;
			}

			public byte getByte(String columnName) throws SQLException {

				return 0;
			}

			public byte[] getBytes(int columnIndex) throws SQLException {

				return null;
			}

			public byte[] getBytes(String columnName) throws SQLException {

				return null;
			}

			public Reader getCharacterStream(int columnIndex)
					throws SQLException {

				return null;
			}

			public Reader getCharacterStream(String columnName)
					throws SQLException {

				return null;
			}

			public Clob getClob(int i) throws SQLException {

				return null;
			}

			public Clob getClob(String colName) throws SQLException {

				return null;
			}

			public int getConcurrency() throws SQLException {

				return 0;
			}

			public String getCursorName() throws SQLException {

				return null;
			}

			public Date getDate(int columnIndex) throws SQLException {

				return null;
			}

			public Date getDate(String columnName) throws SQLException {

				return null;
			}

			public Date getDate(int columnIndex, Calendar cal)
					throws SQLException {

				return null;
			}

			public Date getDate(String columnName, Calendar cal)
					throws SQLException {

				return null;
			}

			public double getDouble(int columnIndex) throws SQLException {

				return 0;
			}

			public double getDouble(String columnName) throws SQLException {

				return 0;
			}

			public int getFetchDirection() throws SQLException {

				return 0;
			}

			public int getFetchSize() throws SQLException {

				return 0;
			}

			public float getFloat(int columnIndex) throws SQLException {

				return 0;
			}

			public float getFloat(String columnName) throws SQLException {

				return 0;
			}

			public int getInt(int columnIndex) throws SQLException {

				return 0;
			}

			public int getInt(String columnName) throws SQLException {

				return 0;
			}

			public long getLong(int columnIndex) throws SQLException {

				return 0;
			}

			public long getLong(String columnName) throws SQLException {

				return 0;
			}

			public ResultSetMetaData getMetaData() throws SQLException {

				return null;
			}

			public Object getObject(int columnIndex) throws SQLException {

				return null;
			}

			public Object getObject(String columnName) throws SQLException {

				return null;
			}

			public Object getObject(int arg0, Map<String,Class<?>> arg1) throws SQLException {

				return null;
			}

			public Object getObject(String arg0, Map<String,Class<?>> arg1) throws SQLException {

				return null;
			}

			public Ref getRef(int i) throws SQLException {

				return null;
			}

			public Ref getRef(String colName) throws SQLException {

				return null;
			}

			public int getRow() throws SQLException {

				return 0;
			}

			public short getShort(int columnIndex) throws SQLException {

				return 0;
			}

			public short getShort(String columnName) throws SQLException {

				return 0;
			}

			public Statement getStatement() throws SQLException {

				return null;
			}

			public String getString(int columnIndex) throws SQLException {

				return null;
			}

			public String getString(String columnName) throws SQLException {

				return null;
			}

			public Time getTime(int columnIndex) throws SQLException {

				return null;
			}

			public Time getTime(String columnName) throws SQLException {

				return null;
			}

			public Time getTime(int columnIndex, Calendar cal)
					throws SQLException {

				return null;
			}

			public Time getTime(String columnName, Calendar cal)
					throws SQLException {

				return null;
			}

			public Timestamp getTimestamp(int columnIndex) throws SQLException {

				return null;
			}

			public Timestamp getTimestamp(String columnName)
					throws SQLException {

				return null;
			}

			public Timestamp getTimestamp(int columnIndex, Calendar cal)
					throws SQLException {

				return null;
			}

			public Timestamp getTimestamp(String columnName, Calendar cal)
					throws SQLException {

				return null;
			}

			public int getType() throws SQLException {

				return 0;
			}

			public URL getURL(int columnIndex) throws SQLException {

				return null;
			}

			public URL getURL(String columnName) throws SQLException {

				return null;
			}

			public InputStream getUnicodeStream(int columnIndex)
					throws SQLException {

				return null;
			}

			public InputStream getUnicodeStream(String columnName)
					throws SQLException {

				return null;
			}

			public SQLWarning getWarnings() throws SQLException {

				return null;
			}

			public void insertRow() throws SQLException {

			}

			public boolean isAfterLast() throws SQLException {

				return false;
			}

			public boolean isBeforeFirst() throws SQLException {

				return false;
			}

			public boolean isFirst() throws SQLException {

				return false;
			}

			public boolean isLast() throws SQLException {

				return false;
			}

			public boolean last() throws SQLException {

				return false;
			}

			public void moveToCurrentRow() throws SQLException {

			}

			public void moveToInsertRow() throws SQLException {

			}

			public boolean next() throws SQLException {

				return false;
			}

			public boolean previous() throws SQLException {

				return false;
			}

			public void refreshRow() throws SQLException {

			}

			public boolean relative(int rows) throws SQLException {

				return false;
			}

			public boolean rowDeleted() throws SQLException {

				return false;
			}

			public boolean rowInserted() throws SQLException {

				return false;
			}

			public boolean rowUpdated() throws SQLException {

				return false;
			}

			public void setFetchDirection(int direction) throws SQLException {

			}

			public void setFetchSize(int rows) throws SQLException {

			}

			public void updateArray(int columnIndex, Array x)
					throws SQLException {

			}

			public void updateArray(String columnName, Array x)
					throws SQLException {

			}

			public void updateAsciiStream(int columnIndex, InputStream x,
					int length) throws SQLException {

			}

			public void updateAsciiStream(String columnName, InputStream x,
					int length) throws SQLException {

			}

			public void updateBigDecimal(int columnIndex, BigDecimal x)
					throws SQLException {

			}

			public void updateBigDecimal(String columnName, BigDecimal x)
					throws SQLException {

			}

			public void updateBinaryStream(int columnIndex, InputStream x,
					int length) throws SQLException {

			}

			public void updateBinaryStream(String columnName, InputStream x,
					int length) throws SQLException {

			}

			public void updateBlob(int columnIndex, Blob x) throws SQLException {

			}

			public void updateBlob(String columnName, Blob x)
					throws SQLException {

			}

			public void updateBoolean(int columnIndex, boolean x)
					throws SQLException {

			}

			public void updateBoolean(String columnName, boolean x)
					throws SQLException {

			}

			public void updateByte(int columnIndex, byte x) throws SQLException {

			}

			public void updateByte(String columnName, byte x)
					throws SQLException {

			}

			public void updateBytes(int columnIndex, byte[] x)
					throws SQLException {

			}

			public void updateBytes(String columnName, byte[] x)
					throws SQLException {

			}

			public void updateCharacterStream(int columnIndex, Reader x,
					int length) throws SQLException {

			}

			public void updateCharacterStream(String columnName, Reader reader,
					int length) throws SQLException {

			}

			public void updateClob(int columnIndex, Clob x) throws SQLException {

			}

			public void updateClob(String columnName, Clob x)
					throws SQLException {

			}

			public void updateDate(int columnIndex, Date x) throws SQLException {

			}

			public void updateDate(String columnName, Date x)
					throws SQLException {

			}

			public void updateDouble(int columnIndex, double x)
					throws SQLException {

			}

			public void updateDouble(String columnName, double x)
					throws SQLException {

			}

			public void updateFloat(int columnIndex, float x)
					throws SQLException {

			}

			public void updateFloat(String columnName, float x)
					throws SQLException {

			}

			public void updateInt(int columnIndex, int x) throws SQLException {

			}

			public void updateInt(String columnName, int x) throws SQLException {

			}

			public void updateLong(int columnIndex, long x) throws SQLException {

			}

			public void updateLong(String columnName, long x)
					throws SQLException {

			}

			public void updateNull(int columnIndex) throws SQLException {

			}

			public void updateNull(String columnName) throws SQLException {

			}

			public void updateObject(int columnIndex, Object x)
					throws SQLException {

			}

			public void updateObject(String columnName, Object x)
					throws SQLException {

			}

			public void updateObject(int columnIndex, Object x, int scale)
					throws SQLException {

			}

			public void updateObject(String columnName, Object x, int scale)
					throws SQLException {

			}

			public void updateRef(int columnIndex, Ref x) throws SQLException {

			}

			public void updateRef(String columnName, Ref x) throws SQLException {

			}

			public void updateRow() throws SQLException {

			}

			public void updateShort(int columnIndex, short x)
					throws SQLException {

			}

			public void updateShort(String columnName, short x)
					throws SQLException {

			}

			public void updateString(int columnIndex, String x)
					throws SQLException {

			}

			public void updateString(String columnName, String x)
					throws SQLException {

			}

			public void updateTime(int columnIndex, Time x) throws SQLException {

			}

			public void updateTime(String columnName, Time x)
					throws SQLException {

			}

			public void updateTimestamp(int columnIndex, Timestamp x)
					throws SQLException {

			}

			public void updateTimestamp(String columnName, Timestamp x)
					throws SQLException {

			}

			public boolean wasNull() throws SQLException {

				return false;
			}

			public int getBytesSize() throws SQLException {
				return 0;
			}
		};
	}

	/**
	 * Tests fix for BUG#34093 - Statements with batched values do not return
	 * correct values for getGeneratedKeys() when "rewriteBatchedStatements" is
	 * set to "true", and the statement has an "ON DUPLICATE KEY UPDATE" clause.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug34093() throws Exception {
		Connection rewriteConn = null;

		rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true");

		checkBug34093(rewriteConn);

		rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true,useServerPrepStmts=true");

		checkBug34093(rewriteConn);
	}

	private void checkBug34093(Connection rewriteConn) throws Exception {
		try {
			String ddl = "(autoIncId INT NOT NULL PRIMARY KEY AUTO_INCREMENT, uniqueTextKey VARCHAR(255), UNIQUE KEY (uniqueTextKey(100)))";

			String[] sequence = { "c", "a", "d", "b" };
			String sql = "insert into testBug30493 (uniqueTextKey) values (?) on duplicate key UPDATE autoIncId = last_insert_id( autoIncId )";
			String tablePrimeSql = "INSERT INTO testBug30493 (uniqueTextKey) VALUES ('a'), ('b'), ('c'), ('d')";

			// setup the rewritten and non-written statements
			Statement stmts[] = new Statement[2];
			PreparedStatement pstmts[] = new PreparedStatement[2];
			stmts[0] = conn.createStatement();
			stmts[1] = rewriteConn.createStatement();
			pstmts[0] = conn.prepareStatement(sql,
					Statement.RETURN_GENERATED_KEYS);
			pstmts[1] = rewriteConn.prepareStatement(sql,
					Statement.RETURN_GENERATED_KEYS);
			for (int i = 0; i < sequence.length; ++i) {
				String sqlLiteral = sql.replaceFirst("\\?", "'" + sequence[i]
						+ "'");
				stmts[0].addBatch(sqlLiteral);
				stmts[1].addBatch(sqlLiteral);
				pstmts[0].setString(1, sequence[i]);
				pstmts[0].addBatch();
				pstmts[1].setString(1, sequence[i]);
				pstmts[1].addBatch();
			}

			// run the test once for Statement, and once for PreparedStatement
			Statement stmtSets[][] = new Statement[2][];
			stmtSets[0] = stmts;
			stmtSets[1] = pstmts;

			for (int stmtSet = 0; stmtSet < 2; ++stmtSet) {
				Statement testStmts[] = stmtSets[stmtSet];
				createTable("testBug30493", ddl);
				this.stmt.executeUpdate(tablePrimeSql);

				int nonRwUpdateCounts[] = testStmts[0].executeBatch();

				ResultSet nonRewrittenRsKeys = testStmts[0].getGeneratedKeys();

				createTable("testBug30493", ddl);
				this.stmt.executeUpdate(tablePrimeSql);
				int expectedUpdateCount = versionMeetsMinimum(5, 1, 0) ? 2 : 1;

				// behavior changed by fix of Bug#46675, affects servers starting from 5.5.16 and 5.6.3
				if (versionMeetsMinimum(5, 5, 16)) {
					expectedUpdateCount = 1;
				}

				int rwUpdateCounts[] = testStmts[1].executeBatch();
				ResultSet rewrittenRsKeys = testStmts[1].getGeneratedKeys();
				for (int i = 0; i < 4; ++i) {
					assertEquals(expectedUpdateCount, nonRwUpdateCounts[i]);
					assertEquals(expectedUpdateCount, rwUpdateCounts[i]);
				}

				assertResultSetLength(nonRewrittenRsKeys, 4);
				assertResultSetLength(rewrittenRsKeys, 4);

				assertResultSetsEqual(nonRewrittenRsKeys, rewrittenRsKeys);
			}
		} finally {
			if (rewriteConn != null) {
				rewriteConn.close();
			}
		}
	}

	public void testBug34093_nonbatch() throws Exception {
		Connection rewriteConn = null;

		try {
			String ddl = "(autoIncId INT NOT NULL PRIMARY KEY AUTO_INCREMENT, uniqueTextKey VARCHAR(255) UNIQUE KEY)";

			String sql = "insert into testBug30493 (uniqueTextKey) values ('c') on duplicate key UPDATE autoIncId = last_insert_id( autoIncId )";
			String tablePrimeSql = "INSERT INTO testBug30493 (uniqueTextKey) VALUES ('a'), ('b'), ('c'), ('d')";

			try {
				createTable("testBug30493", ddl);
			} catch (SQLException sqlEx) {
				if (sqlEx.getMessage().indexOf("max key length") != -1) {
					createTable(
							"testBug30493",
							"(autoIncId INT NOT NULL PRIMARY KEY AUTO_INCREMENT, uniqueTextKey VARCHAR(180) UNIQUE KEY)");
				}
			}
			stmt.executeUpdate(tablePrimeSql);

			Statement stmt1 = conn.createStatement();
			stmt1.execute(sql, Statement.RETURN_GENERATED_KEYS);
			int expectedUpdateCount = versionMeetsMinimum(5, 1, 0) ? 2 : 1;

			// behavior changed by fix of Bug#46675, affects servers starting from 5.5.16 and 5.6.3
			if (versionMeetsMinimum(5, 5, 16)) {
				expectedUpdateCount = 1;
			}

			assertEquals(expectedUpdateCount, stmt1.getUpdateCount());
			ResultSet stmtKeys = stmt1.getGeneratedKeys();
			assertResultSetLength(stmtKeys, 1);

			try {
				createTable("testBug30493", ddl);
			} catch (SQLException sqlEx) {
				if (sqlEx.getMessage().indexOf("max key length") != -1) {
					createTable(
							"testBug30493",
							"(autoIncId INT NOT NULL PRIMARY KEY AUTO_INCREMENT, uniqueTextKey VARCHAR(180) UNIQUE KEY)");
				}
			}
			stmt.executeUpdate(tablePrimeSql);

			pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
			pstmt.execute();
			assertEquals(expectedUpdateCount, pstmt.getUpdateCount());
			ResultSet pstmtKeys = pstmt.getGeneratedKeys();
			assertResultSetLength(pstmtKeys, 1);

			assertResultSetsEqual(stmtKeys, pstmtKeys);
		} finally {
			if (rewriteConn != null) {
				rewriteConn.close();
			}
		}
	}

	public void testBug34518() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		Connection fetchConn = getConnectionWithProps("useCursorFetch=true");
		Statement fetchStmt = fetchConn.createStatement();

		int stmtCount = ((com.mysql.jdbc.Connection) fetchConn)
				.getActiveStatementCount();

		fetchStmt.setFetchSize(100);
		this.rs = fetchStmt.executeQuery("SELECT 1");

		assertEquals(
				((com.mysql.jdbc.Connection) fetchConn)
						.getActiveStatementCount(),
				stmtCount + 1);
		this.rs.close();
		assertEquals(
				((com.mysql.jdbc.Connection) fetchConn)
						.getActiveStatementCount(),
				stmtCount);
	}

	public void testBug35170() throws Exception {
		Statement stt = null;

		try {
			stt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_READ_ONLY);
			stt.setFetchSize(Integer.MIN_VALUE);
			this.rs = stt.executeQuery("select 1");
			this.rs.next();
			while (!this.rs.isAfterLast()) {
				this.rs.getString(1);
				this.rs.next();
			}
		} finally {
			if (stt != null) {
				stt.close();
			}
		}

	}

	/*
	 * public void testBug35307() throws Exception { createTable("testBug35307",
	 * "(`id` int(11) unsigned NOT NULL auto_increment," +
	 * "`field` varchar(20) NOT NULL," + "`date` datetime NOT NULL," +
	 * "PRIMARY KEY  (`id`)" + ") ENGINE=MyISAM DEFAULT CHARSET=latin1");
	 * 
	 * this.stmt.executeUpdate("INSERT INTO testBug35307 (field) values ('works')"
	 * ); }
	 */

	public void testBug35666() throws Exception {
		Connection loggingConn = getConnectionWithProps("logSlowQueries=true");
		this.pstmt = ((com.mysql.jdbc.Connection) loggingConn)
				.serverPrepareStatement("SELECT SLEEP(4)");
		this.pstmt.execute();
	}

	public void testDeadlockBatchBehavior() throws Exception {
		try {
			createTable("t1", "(id INTEGER, x INTEGER)", "INNODB");
			createTable("t2", "(id INTEGER, x INTEGER)", "INNODB");
			this.stmt.executeUpdate("INSERT INTO t1 VALUES (0, 0)");

			this.conn.setAutoCommit(false);
			this.conn.createStatement().executeQuery(
					"SELECT * FROM t1 WHERE id=0 FOR UPDATE");

			final Connection deadlockConn = getConnectionWithProps("includeInnodbStatusInDeadlockExceptions=true");
			deadlockConn.setAutoCommit(false);

			final Statement deadlockStmt = deadlockConn.createStatement();
			deadlockStmt.executeUpdate("INSERT INTO t2 VALUES (1, 0)");
			deadlockStmt.executeQuery("SELECT * FROM t2 WHERE id=0 FOR UPDATE");

			new Thread() {
				public void run() {
					try {
						deadlockStmt.addBatch("INSERT INTO t2 VALUES (1, 0)");
						deadlockStmt.addBatch("INSERT INTO t2 VALUES (2, 0)");
						deadlockStmt.addBatch("UPDATE t1 SET x=2 WHERE id=0");
						deadlockStmt.executeBatch();
					} catch (SQLException sqlEx) {
						sqlEx.printStackTrace();
						try {
							deadlockConn.rollback();
						} catch (SQLException e) {
							e.printStackTrace();
						}
					}
				}
			}.run();

			this.stmt.executeUpdate("INSERT INTO t1 VALUES (0, 0)");

		} catch (BatchUpdateException sqlEx) {
			int[] updateCounts = sqlEx.getUpdateCounts();
			for (int i = 0; i < updateCounts.length; i++) {
				System.out.println(updateCounts[i]);
			}
		} finally {
			this.conn.rollback();
			this.conn.setAutoCommit(true);
		}
	}

	public void testBug39352() throws Exception {
		Connection affectedRowsConn = getConnectionWithProps("useAffectedRows=true");

		try {

			createTable("bug39352", "(id INT PRIMARY KEY, data VARCHAR(100))");
			assertEquals(
					1,
					this.stmt
							.executeUpdate("INSERT INTO bug39352 (id,data) values (1,'a')"));
			int rowsAffected = this.stmt
					.executeUpdate("INSERT INTO bug39352 (id, data) VALUES(2, 'bb') "
							+ "ON DUPLICATE KEY " + "UPDATE data=values(data)");
			assertEquals("First UPD failed", 1, rowsAffected);

			rowsAffected = affectedRowsConn.createStatement().executeUpdate(
					"INSERT INTO bug39352 (id, data) VALUES(2, 'bbb') "
							+ "ON DUPLICATE KEY " + "UPDATE data=values(data)");
			assertEquals("2nd UPD failed", 2, rowsAffected);

			rowsAffected = affectedRowsConn.createStatement().executeUpdate(
					"INSERT INTO bug39352 (id, data) VALUES(2, 'bbb') "
							+ "ON DUPLICATE KEY " + "UPDATE data=values(data)");
			assertEquals("3rd UPD failed", 0, rowsAffected);

		} finally {
			affectedRowsConn.close();
		}
	}

	public void testBug38747() throws Exception {
		try {
			this.conn.setReadOnly(true);
			this.pstmt = this.conn.prepareStatement("SELECT 1",
					ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
			this.pstmt.setFetchSize(Integer.MIN_VALUE);

			this.rs = this.pstmt.executeQuery();

			while (this.rs.next())
				;

			this.rs.close();
			this.pstmt.close();

		} finally {
			this.conn.setReadOnly(false);
		}
	}

	public void testBug39956() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return;
		}

		ResultSet enginesRs = this.conn.createStatement().executeQuery(
				"SHOW ENGINES");

		while (enginesRs.next()) {
			if ("YES".equalsIgnoreCase(enginesRs.getString("Support"))
					|| "DEFAULT".equalsIgnoreCase(enginesRs
							.getString("Support"))) {

				String engineName = enginesRs.getString("Engine");

				if ("CSV".equalsIgnoreCase(engineName)
						|| "BLACKHOLE".equalsIgnoreCase(engineName)
						|| "FEDERATED".equalsIgnoreCase(engineName)
						|| "MRG_MYISAM".equalsIgnoreCase(engineName)
						|| "PARTITION".equalsIgnoreCase(engineName)
						|| "EXAMPLE".equalsIgnoreCase(engineName)
						|| "PERFORMANCE_SCHEMA".equalsIgnoreCase(engineName)
						|| engineName.endsWith("_SCHEMA")) {
					continue; // not supported
				}

				if ("ARCHIVE".equalsIgnoreCase(engineName)
						&& !versionMeetsMinimum(5, 1, 6)) {
					continue;
				}

				String tableName = "testBug39956_" + engineName;

				Connection twoConn = getConnectionWithProps("sessionVariables=auto_increment_increment=2");

				try {
					for (int i = 0; i < 2; i++) {
						createTable(tableName,
								"(k int primary key auto_increment, p varchar(4)) ENGINE="
										+ engineName);

						((com.mysql.jdbc.Connection) twoConn)
								.setRewriteBatchedStatements(i == 1);

						this.pstmt = twoConn.prepareStatement("INSERT INTO "
								+ tableName + " (p) VALUES (?)",
								Statement.RETURN_GENERATED_KEYS);
						this.pstmt.setString(1, "a");
						this.pstmt.addBatch();
						this.pstmt.setString(1, "b");
						this.pstmt.addBatch();
						this.pstmt.executeBatch();

						this.rs = this.pstmt.getGeneratedKeys();

						this.rs.next();
						assertEquals("For engine " + engineName
								+ ((i == 1) ? " rewritten " : " plain "), 1,
								this.rs.getInt(1));
						this.rs.next();
						assertEquals("For engine " + engineName
								+ ((i == 1) ? " rewritten " : " plain "), 3,
								this.rs.getInt(1));

						createTable(tableName,
								"(k int primary key auto_increment, p varchar(4)) ENGINE="
										+ engineName);
						Statement twoStmt = twoConn.createStatement();
						for (int j = 0; j < 10; j++) {
							twoStmt.addBatch("INSERT INTO " + tableName
									+ " (p) VALUES ('" + j + "')");
						}

						twoStmt.executeBatch(); // UGH: No getGeneratedKeys()
												// support in JDBC spec, but we
												// allow it...might have to
												// rewrite test if/when we don't
						this.rs = twoStmt.getGeneratedKeys();

						int key = 1;

						for (int j = 0; j < 10; j++) {
							this.rs.next();
							assertEquals("For engine " + engineName
									+ ((i == 1) ? " rewritten " : " plain "),
									key, this.rs.getInt(1));
							key += 2;
						}
					}
				} finally {
					if (twoConn != null) {
						twoConn.close();
					}
				}
			}
		}
	}

	public void testBug34185() throws Exception {
		this.stmt.executeQuery("SELECT 1");

		try {
			this.stmt.getGeneratedKeys();
			fail("Expected exception");
		} catch (SQLException sqlEx) {
			assertEquals(SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
					sqlEx.getSQLState());
		}

		this.pstmt = this.conn.prepareStatement("SELECT 1");

		try {
			this.pstmt.execute();
			this.pstmt.getGeneratedKeys();
			fail("Expected exception");
		} catch (SQLException sqlEx) {
			assertEquals(SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
					sqlEx.getSQLState());
		}
	}

	public void testBug41161() throws Exception {
		createTable("testBug41161", "(a int, b int)");

		Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true");

		try {
			this.pstmt = rewriteConn
					.prepareStatement("INSERT INTO testBug41161 (a, b) VALUES (?, ?, ?)");
			this.pstmt.setInt(1, 1);
			this.pstmt.setInt(2, 1);

			try {
				this.pstmt.addBatch();
				fail("Should have thrown an exception");
			} catch (SQLException sqlEx) {
				assertEquals("07001", sqlEx.getSQLState());
			}

			this.pstmt.executeBatch(); // NPE when this bug exists
		} finally {
			rewriteConn.close();
		}
	}

	/**
	 * Ensures that cases listed in Bug#41448 actually work - we don't think
	 * there's a bug here right now
	 */

	public void testBug41448() throws Exception {
		createTable("testBug41448",
				"(pk INT PRIMARY KEY AUTO_INCREMENT, field1 VARCHAR(4))");

		stmt.executeUpdate("INSERT INTO testBug41448 (field1) VALUES ('abc')",
				Statement.RETURN_GENERATED_KEYS);
		stmt.getGeneratedKeys();

		stmt.executeUpdate("INSERT INTO testBug41448 (field1) VALUES ('def')",
				new int[] { 1 });
		stmt.getGeneratedKeys();

		stmt.executeUpdate("INSERT INTO testBug41448 (field1) VALUES ('ghi')",
				new String[] { "pk" });
		stmt.getGeneratedKeys();

		stmt.executeUpdate("INSERT INTO testBug41448 (field1) VALUES ('ghi')");

		try {
			stmt.getGeneratedKeys();
			fail("Expected a SQLException here");
		} catch (SQLException sqlEx) {
			// expected
		}

		stmt.execute("INSERT INTO testBug41448 (field1) VALUES ('jkl')",
				Statement.RETURN_GENERATED_KEYS);
		stmt.getGeneratedKeys();

		stmt.execute("INSERT INTO testBug41448 (field1) VALUES ('mno')",
				new int[] { 1 });
		stmt.getGeneratedKeys();

		stmt.execute("INSERT INTO testBug41448 (field1) VALUES ('pqr')",
				new String[] { "pk" });
		stmt.getGeneratedKeys();

		stmt.execute("INSERT INTO testBug41448 (field1) VALUES ('stu')");

		try {
			stmt.getGeneratedKeys();
			fail("Expected a SQLException here");
		} catch (SQLException sqlEx) {
			// expected
		}

		this.pstmt = this.conn.prepareStatement(
				"INSERT INTO testBug41448 (field1) VALUES (?)",
				Statement.RETURN_GENERATED_KEYS);
		this.pstmt.setString(1, "abc");
		this.pstmt.executeUpdate();
		this.pstmt.getGeneratedKeys();
		this.pstmt.execute();
		this.pstmt.getGeneratedKeys();

		this.pstmt = this.conn
				.prepareStatement(
						"INSERT INTO testBug41448 (field1) VALUES (?)",
						new int[] { 1 });
		this.pstmt.setString(1, "abc");
		this.pstmt.executeUpdate();
		this.pstmt.getGeneratedKeys();
		this.pstmt.execute();
		this.pstmt.getGeneratedKeys();

		this.pstmt = this.conn.prepareStatement(
				"INSERT INTO testBug41448 (field1) VALUES (?)",
				new String[] { "pk" });
		this.pstmt.setString(1, "abc");
		this.pstmt.executeUpdate();
		this.pstmt.getGeneratedKeys();
		this.pstmt.execute();
		this.pstmt.getGeneratedKeys();

		this.pstmt = this.conn
				.prepareStatement("INSERT INTO testBug41448 (field1) VALUES (?)");
		this.pstmt.setString(1, "abc");
		this.pstmt.executeUpdate();
		try {
			this.pstmt.getGeneratedKeys();
			fail("Expected a SQLException here");
		} catch (SQLException sqlEx) {
			// expected
		}

		this.pstmt.execute();

		try {
			this.pstmt.getGeneratedKeys();
			fail("Expected a SQLException here");
		} catch (SQLException sqlEx) {
			// expected
		}
	}

	public void testBug48172() throws Exception {
		createTable("testBatchInsert", "(a INT PRIMARY KEY AUTO_INCREMENT)");
		Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true,dumpQueriesOnException=true");
		assertEquals(
				"0",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());

		this.pstmt = rewriteConn
				.prepareStatement("INSERT INTO testBatchInsert VALUES (?)");
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.executeBatch();

		assertEquals(
				"1",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());
		this.pstmt = rewriteConn
				.prepareStatement("INSERT INTO `testBatchInsert`VALUES (?)");
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.executeBatch();

		assertEquals(
				"2",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());

		this.pstmt = rewriteConn
				.prepareStatement("INSERT INTO testBatchInsert VALUES(?)");
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.executeBatch();

		assertEquals(
				"3",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());

		this.pstmt = rewriteConn
				.prepareStatement("INSERT INTO testBatchInsert VALUES\n(?)");
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.setNull(1, java.sql.Types.INTEGER);
		this.pstmt.addBatch();
		this.pstmt.executeBatch();

		assertEquals(
				"4",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());

	}

	/**
	 * Tests fix for Bug#41532 - regression in performance for batched inserts
	 * when using ON DUPLICATE KEY UPDATE
	 */
	public void testBug41532() throws Exception {
		createTable(
				"testBug41532",
				"(ID"
						+ " INTEGER, S1 VARCHAR(100), S2 VARCHAR(100), S3 VARCHAR(100), D1 DATETIME, D2 DATETIME, D3 DATETIME, N1 DECIMAL(28,6), N2 DECIMAL(28,6), N3 DECIMAL(28,6), UNIQUE KEY"
						+ " UNIQUE_KEY_TEST_DUPLICATE (ID) )");

		int numTests = 5000;
		Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true,dumpQueriesOnException=true");

		assertEquals(
				"0",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());
		long batchedTime = timeBatch(rewriteConn, numTests);
		assertEquals(
				"1",
				getSingleIndexedValueWithQuery(rewriteConn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());

		this.stmt.executeUpdate("TRUNCATE TABLE testBug41532");

		assertEquals(
				"0",
				getSingleIndexedValueWithQuery(this.conn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());
		long unbatchedTime = timeBatch(this.conn, numTests);
		assertEquals(
				String.valueOf(numTests),
				getSingleIndexedValueWithQuery(this.conn, 2,
						"SHOW SESSION STATUS LIKE 'Com_insert'").toString());
		assertTrue(batchedTime < unbatchedTime);

		rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true,useCursorFetch=true,defaultFetchSize=10000");
		timeBatch(rewriteConn, numTests);
	}

	private long timeBatch(Connection c, int numberOfRows) throws SQLException {
		this.pstmt = c
				.prepareStatement("INSERT INTO testBug41532(ID, S1, S2, S3, D1,"
						+ "D2, D3, N1, N2, N3) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
						+ " ON DUPLICATE KEY"
						+ " UPDATE S1 = VALUES(S1), S2 = VALUES(S2), S3 = VALUES(S3), D1 = VALUES(D1), D2 ="
						+ " VALUES(D2), D3 = VALUES(D3), N1 = N1 + VALUES(N1), N2 = N2 + VALUES(N2), N2 = N2 +"
						+ " VALUES(N2)");
		c.setAutoCommit(false);
		c.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
		Date d1 = new Date(currentTimeMillis());
		Date d2 = new Date(currentTimeMillis() + 1000000);
		Date d3 = new Date(currentTimeMillis() + 1250000);

		for (int i = 0; i < numberOfRows; i++) {
			this.pstmt.setObject(1, new Integer(i), Types.INTEGER);
			this.pstmt.setObject(2, String.valueOf(i), Types.VARCHAR);
			this.pstmt.setObject(3, String.valueOf(i * 0.1), Types.VARCHAR);
			this.pstmt.setObject(4, String.valueOf(i / 3), Types.VARCHAR);
			this.pstmt.setObject(5, new Timestamp(d1.getTime()),
					Types.TIMESTAMP);
			this.pstmt.setObject(6, new Timestamp(d2.getTime()),
					Types.TIMESTAMP);
			this.pstmt.setObject(7, new Timestamp(d3.getTime()),
					Types.TIMESTAMP);
			this.pstmt.setObject(8, new BigDecimal(i + 0.1), Types.DECIMAL);
			this.pstmt.setObject(9, new BigDecimal(i * 0.1), Types.DECIMAL);
			this.pstmt.setObject(10, new BigDecimal(i / 3), Types.DECIMAL);
			this.pstmt.addBatch();
		}
		long startTime = currentTimeMillis();
		this.pstmt.executeBatch();
		c.commit();
		long stopTime = currentTimeMillis();

		rs = conn.createStatement().executeQuery(
				"SELECT COUNT(*) FROM testBug41532");
		assertTrue(rs.next());
		assertEquals(numberOfRows, rs.getInt(1));

		return stopTime - startTime;
	}

	/**
	 * Tests fix for Bug#44056 - Statement.getGeneratedKeys() retains result set
	 * instances until statement is closed.
	 */

	public void testBug44056() throws Exception {
		createTable("testBug44056",
				"(pk int primary key not null auto_increment)");
		Statement newStmt = this.conn.createStatement();

		try {
			newStmt.executeUpdate("INSERT INTO testBug44056 VALUES (null)",
					Statement.RETURN_GENERATED_KEYS);
			checkOpenResultsFor44056(newStmt);
			this.pstmt = this.conn.prepareStatement(
					"INSERT INTO testBug44056 VALUES (null)",
					Statement.RETURN_GENERATED_KEYS);
			this.pstmt.executeUpdate();
			checkOpenResultsFor44056(this.pstmt);
			this.pstmt = ((com.mysql.jdbc.Connection) this.conn)
					.serverPrepareStatement(
							"INSERT INTO testBug44056 VALUES (null)",
							Statement.RETURN_GENERATED_KEYS);
			this.pstmt.executeUpdate();
			checkOpenResultsFor44056(this.pstmt);
		} finally {
			newStmt.close();
		}
	}

	private void checkOpenResultsFor44056(Statement newStmt)
			throws SQLException {
		this.rs = newStmt.getGeneratedKeys();
		assertEquals(1,
				((com.mysql.jdbc.Statement) newStmt).getOpenResultSetCount());
		this.rs.close();
		assertEquals(0,
				((com.mysql.jdbc.Statement) newStmt).getOpenResultSetCount());
	}

	/**
	 * Bug #41730 - SQL Injection when using U+00A5 and SJIS/Windows-31J
	 */
	public void testBug41730() throws Exception {
		try {
			"".getBytes("sjis");
		} catch (UnsupportedEncodingException ex) {
			return; // test doesn't work on this platform
		}

		Connection conn2 = null;
		PreparedStatement pstmt2 = null;
		try {
			conn2 = getConnectionWithProps("characterEncoding=sjis");
			pstmt2 = conn2.prepareStatement("select ?");
			pstmt2.setString(1, "\u00A5'");
			// this will throw an exception with a syntax error if it fails
			pstmt2.executeQuery();
		} finally {
			try {
				if (pstmt2 != null)
					pstmt2.close();
			} catch (SQLException ex) {
			}
			try {
				if (conn2 != null)
					conn2.close();
			} catch (SQLException ex) {
			}
		}
	}

	public void testBug43196() throws Exception {
		createTable(
				"`bug43196`",
				"(`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, `a` bigint(20) unsigned NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=latin1;");

		Connection conn1 = null;

		try {
			assertEquals(1, this.stmt.executeUpdate(
					"INSERT INTO bug43196 (a) VALUES (1)",
					Statement.RETURN_GENERATED_KEYS));

			this.rs = this.stmt.getGeneratedKeys();

			if (this.rs.next()) {

				Object id = this.rs.getObject(1);// use long

				assertEquals(Long.class, id.getClass());
			}

			this.rs.close();

			this.rs = this.stmt.executeQuery("select id from bug43196");

			if (this.rs.next()) {
				Object id = this.rs.getObject(1);// use BigInteger

				assertEquals(BigInteger.class, id.getClass());
			}

			this.rs.close();

			// insert a id > Long.MAX_VALUE(9223372036854775807)

			assertEquals(
					1,
					this.stmt
							.executeUpdate(
									"insert into bug43196(id,a) values(18446744073709551200,1)",
									Statement.RETURN_GENERATED_KEYS));

			this.rs = this.stmt.getGeneratedKeys();

			this.rs.first();

			assertTrue("No rows returned", this.rs.isFirst());
			assertEquals("18446744073709551200", this.rs.getObject(1)
					.toString());
		} finally {
			if (conn1 != null) {
				conn1.close();
			}
		}
	}

	/**
	 * Bug #42253 - multiple escaped quotes cause exception from
	 * EscapeProcessor.
	 */
	public void testBug42253() throws Exception {
		rs = stmt.executeQuery("select '\\'\\'','{t\\'}'");
		rs.next();
		assertEquals("''", rs.getString(1));
		assertEquals("{t'}", rs.getString(2));
	}

	/**
	 * Bug #41566 - Quotes within comments not correctly ignored by escape
	 * parser
	 */
	public void testBug41566() throws Exception {
		rs = stmt
				.executeQuery("-- this should't change the literal\n select '{1}'");
		rs.next();
		assertEquals("{1}", rs.getString(1));
	}

	/*
	 * Bug #40439 - Error rewriting batched statement if table name ends with
	 * "values".
	 */
	public void testBug40439() throws Exception {
		Connection conn2 = null;
		try {
			createTable("testBug40439VALUES", "(x int)");
			conn2 = getConnectionWithProps("rewriteBatchedStatements=true");
			PreparedStatement ps = conn2
					.prepareStatement("insert into testBug40439VALUES (x) values (?)");
			ps.setInt(1, 1);
			ps.addBatch();
			ps.setInt(1, 2);
			ps.addBatch();
			ps.executeBatch();
		} finally {
			if (conn2 != null)
				try {
					conn2.close();
				} catch (SQLException ex) {
				}
		}
	}

	public static class Bug39426Interceptor implements StatementInterceptor {
		public static List<Integer> vals = new ArrayList<Integer>();
		String prevSql;

		public void destroy() {
		}

		public boolean executeTopLevelOnly() {
			return false;
		}

		public void init(com.mysql.jdbc.Connection conn, Properties props)
				throws SQLException {
		}

		public ResultSetInternalMethods postProcess(String sql,
				com.mysql.jdbc.Statement interceptedStatement,
				ResultSetInternalMethods originalResultSet,
				com.mysql.jdbc.Connection connection) throws SQLException {
			return null;
		}

		public ResultSetInternalMethods preProcess(String sql,
				com.mysql.jdbc.Statement interceptedStatement,
				com.mysql.jdbc.Connection connection) throws SQLException {
			String asSql = sql;

			if (interceptedStatement instanceof com.mysql.jdbc.PreparedStatement) {
				asSql = interceptedStatement.toString();
				int firstColon = asSql.indexOf(":");
				asSql = asSql.substring(firstColon + 2);

				if (asSql.equals(prevSql))
					throw new RuntimeException(
							"Previous statement matched current: " + sql);
				prevSql = asSql;
				ParameterBindings b = ((com.mysql.jdbc.PreparedStatement) interceptedStatement)
						.getParameterBindings();
				vals.add(new Integer(b.getInt(1)));
			}
			return null;
		}
	}

	/**
	 * Bug #39426 - executeBatch passes most recent PreparedStatement params to
	 * StatementInterceptor
	 */
	public void testBug39426() throws Exception {
		Connection c = null;
		try {
			createTable("testBug39426", "(x int)");
			c = getConnectionWithProps("statementInterceptors=testsuite.regression.StatementRegressionTest$Bug39426Interceptor,useServerPrepStmts=false");
			PreparedStatement ps = c
					.prepareStatement("insert into testBug39426 values (?)");
			ps.setInt(1, 1);
			ps.addBatch();
			ps.setInt(1, 2);
			ps.addBatch();
			ps.setInt(1, 3);
			ps.addBatch();
			ps.executeBatch();
			List<Integer> vals = Bug39426Interceptor.vals;
			assertEquals(new Integer(1), vals.get(0));
			assertEquals(new Integer(2), vals.get(1));
			assertEquals(new Integer(3), vals.get(2));
		} finally {
			if (c != null)
				c.close();
		}
	}

	public void testBugDupeKeySingle() throws Exception {
		createTable("testBugDupeKeySingle", "(field1 int not null primary key)");
		Connection conn2 = null;
		try {
			conn2 = getConnectionWithProps("rewriteBatchedStatements=true");

			this.pstmt = conn2
					.prepareStatement("INSERT INTO testBugDupeKeySingle VALUES (?) ON DUPLICATE KEY UPDATE field1=VALUES(field1)");
			this.pstmt.setInt(1, 1);
			this.pstmt.addBatch();
			this.pstmt.executeBatch();

			// this should be a syntax error
			this.pstmt = conn2
					.prepareStatement("INSERT INTO testBugDupeKeySingle VALUES (?) ON DUPLICATE KEY UPDATE");
			this.pstmt.setInt(1, 1);
			this.pstmt.addBatch();
			try {
				this.pstmt.executeBatch();
			} catch (SQLException sqlEx) {
				assertEquals(SQLError.SQL_STATE_SYNTAX_ERROR,
						sqlEx.getSQLState());
			}

			this.pstmt = conn2
					.prepareStatement("INSERT INTO testBugDupeKeySingle VALUES (?)");
			this.pstmt.setInt(1, 2);
			this.pstmt.addBatch();
			this.pstmt.executeBatch();
			this.pstmt.setInt(1, 3);
			this.pstmt.setInt(1, 4);
			this.pstmt.executeBatch();
		} finally {
			if (conn2 != null) {
				conn2.close();
			}
		}
	}

	/**
	 * Bug #37458 - MySQL 5.1 returns generated keys in ascending order
	 */
	public void testBug37458() throws Exception {
		int ids[] = { 13, 1, 8 };
		String vals[] = { "c", "a", "b" };
		createTable("testBug37458",
				"(id int not null auto_increment, val varchar(100), "
						+ "primary key (id), unique (val))");
		stmt.executeUpdate("insert into testBug37458 values (1, 'a'), (8, 'b'), (13, 'c')");
		pstmt = conn.prepareStatement(
				"insert into testBug37458 (val) values (?) "
						+ "on duplicate key update id = last_insert_id(id)",
				PreparedStatement.RETURN_GENERATED_KEYS);
		for (int i = 0; i < ids.length; ++i) {
			pstmt.setString(1, vals[i]);
			pstmt.addBatch();
		}
		pstmt.executeBatch();
		ResultSet keys = pstmt.getGeneratedKeys();
		for (int i = 0; i < ids.length; ++i) {
			assertTrue(keys.next());
			assertEquals(ids[i], keys.getInt(1));
		}
	}

	public void testBug34555() throws Exception {
		if (!versionMeetsMinimum(5, 0)) {
			return; // no KILL QUERY prior to this
		}

		createTable("testBug34555", "(field1 int)", "INNODB");
		this.stmt.executeUpdate("INSERT INTO testBug34555 VALUES (0)");

		final Connection lockerConn = getConnectionWithProps("");
		lockerConn.setAutoCommit(false);
		lockerConn.createStatement().execute(
				"SELECT * FROM testBug34555 WHERE field1=0 FOR UPDATE");

		this.conn.setAutoCommit(false);

		this.pstmt = this.conn
				.prepareStatement("UPDATE testBug34555 SET field1=1 WHERE field1=?");
		this.pstmt.setQueryTimeout(1);
		this.pstmt.setInt(1, 0);
		this.pstmt.addBatch();
		this.pstmt.setInt(1, 2);
		this.pstmt.addBatch();

		try {
			this.pstmt.executeBatch();
		} catch (BatchUpdateException batchEx) {
			assertTrue(batchEx.getMessage().startsWith("Statement cancelled"));
		} finally {
			this.conn.setAutoCommit(true);
			lockerConn.commit();
		}
	}

	public void testBug46788() throws Exception {
		createTable("testBug46788", "(modified varchar(32), id varchar(32))");

		Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true");

		this.pstmt = rewriteConn
				.prepareStatement("insert into testBug46788 (modified,id) values (?,?) ON DUPLICATE KEY UPDATE modified=?");

		this.pstmt.setString(1, "theID");
		this.pstmt.setString(2, "Hello_world_");
		this.pstmt.setString(3, "Hello_world_");

		for (int i = 0; i < 10; i++) {
			this.pstmt.addBatch();
		}

		this.pstmt.executeBatch();
	}

	public void testBug31193() throws Exception {
		createTable("bug31193", "(sometime datetime, junk text)");
		Connection fetchConn = getConnectionWithProps("useCursorFetch=true");
		Statement fetchStmt = fetchConn.createStatement();

		fetchStmt.setFetchSize(10000);

		assertEquals(
				1,
				fetchStmt
						.executeUpdate("INSERT INTO bug31193 (sometime) values ('2007-01-01 12:34:56.7')"));
		this.rs = fetchStmt.executeQuery("SELECT * FROM bug31193");
		this.rs.next();
		String badDatetime = this.rs.getString("sometime");

		this.rs = fetchStmt.executeQuery("SELECT sometime FROM bug31193");
		this.rs.next();
		String goodDatetime = this.rs.getString("sometime");
		assertEquals(goodDatetime, badDatetime);
	}

	public void testBug51666() throws Exception {
		Connection testConn = getConnectionWithProps("statementInterceptors="
				+ IncrementStatementCountInterceptor.class.getName());
		createTable("testStatementInterceptorCount", "(field1 int)");
		this.stmt
				.executeUpdate("INSERT INTO testStatementInterceptorCount VALUES (0)");
		ResultSet testRs = testConn.createStatement().executeQuery(
				"SHOW SESSION STATUS LIKE 'Com_select'");
		testRs.next();
		int s = testRs.getInt(2);
		testConn.createStatement().executeQuery("SELECT 1");
		testRs = testConn.createStatement().executeQuery(
				"SHOW SESSION STATUS LIKE 'Com_select'");
		testRs.next();
		assertEquals(s + 1, testRs.getInt(2));

	}

	public void testBug51776() throws Exception {

		Properties props = new Properties();
		NonRegisteringDriver d = new NonRegisteringDriver();
		this.copyBasePropertiesIntoProps(props, d);
		props.setProperty("socketFactory", "testsuite.UnreliableSocketFactory");
		Properties parsed = d.parseURL(BaseTestCase.dbUrl, props);
		String db = parsed
				.getProperty(NonRegisteringDriver.DBNAME_PROPERTY_KEY);
		String port = parsed
				.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY);
		String host = getPortFreeHostname(props, d);
		UnreliableSocketFactory.flushAllHostLists();
		UnreliableSocketFactory.mapHost("first", host);
		props.remove(NonRegisteringDriver.HOST_PROPERTY_KEY);

		Connection testConn = getConnectionWithProps("jdbc:mysql://first:"
				+ port + "/" + db, props);
		testConn.setAutoCommit(false);
		testConn.createStatement().execute("SELECT 1");
		UnreliableSocketFactory.downHost("first");
		try {
			testConn.rollback();
			fail("Should receive SQLException on rollback().");
		} catch (SQLException e) {

		}

	}

	public static class IncrementStatementCountInterceptor implements
			StatementInterceptorV2 {
		public void destroy() {
		}

		public boolean executeTopLevelOnly() {
			return false;
		}

		public void init(com.mysql.jdbc.Connection conn, Properties props)
				throws SQLException {
		}

		public ResultSetInternalMethods postProcess(String sql,
				com.mysql.jdbc.Statement interceptedStatement,
				ResultSetInternalMethods originalResultSet,
				com.mysql.jdbc.Connection connection, int warningCount,
				boolean noIndexUsed, boolean noGoodIndexUsed,
				SQLException statementException) throws SQLException {
			return null;
		}

		public ResultSetInternalMethods preProcess(String sql,
				com.mysql.jdbc.Statement interceptedStatement,
				com.mysql.jdbc.Connection conn) throws SQLException {
			java.sql.Statement test = conn.createStatement();
			if (sql.equals("SELECT 1")) {
				return (ResultSetInternalMethods) test
						.executeQuery("/* execute this, not the original */ SELECT 1");
			}
			return null;
		}

	}

	public void testReversalOfScanFlags() throws Exception {
		createTable("testReversalOfScanFlags", "(field1 int)");
		this.stmt
				.executeUpdate("INSERT INTO testReversalOfScanFlags VALUES (1),(2),(3)");

		Connection scanningConn = getConnectionWithProps("statementInterceptors="
				+ ScanDetectingInterceptor.class.getName());

		try {
			ScanDetectingInterceptor.watchForScans = true;
			scanningConn.createStatement().executeQuery(
					"SELECT field1 FROM testReversalOfScanFlags");
			assertTrue(ScanDetectingInterceptor.hasSeenScan);
			assertFalse(ScanDetectingInterceptor.hasSeenBadIndex);
		} finally {
			scanningConn.close();
		}

	}

	public static class ScanDetectingInterceptor implements
			StatementInterceptorV2 {
		static boolean watchForScans = false;
		static boolean hasSeenScan = false;
		static boolean hasSeenBadIndex = false;

		public void destroy() {

		}

		public boolean executeTopLevelOnly() {
			return false;
		}

		public void init(com.mysql.jdbc.Connection conn, Properties props)
				throws SQLException {

		}

		public ResultSetInternalMethods postProcess(String sql,
				com.mysql.jdbc.Statement interceptedStatement,
				ResultSetInternalMethods originalResultSet,
				com.mysql.jdbc.Connection connection, int warningCount,
				boolean noIndexUsed, boolean noGoodIndexUsed,
				SQLException statementException) throws SQLException {
			if (watchForScans) {
				if (noIndexUsed) {
					hasSeenScan = true;
				}

				if (noGoodIndexUsed) {
					hasSeenBadIndex = true;
				}
			}

			return null;
		}

		public ResultSetInternalMethods preProcess(String sql,
				com.mysql.jdbc.Statement interceptedStatement,
				com.mysql.jdbc.Connection connection) throws SQLException {
			return null;
		}

	}

	/**
	 * Tests fix for Bug#51704, rewritten batched statements don't honor escape
	 * processing flag of Statement that they are created for
	 */
	public void testBug51704() throws Exception {
		createTable("testBug51704", "(field1 TIMESTAMP)");
		Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true");
		Statement rewriteStmt = rewriteConn.createStatement();

		try {
			rewriteStmt.setEscapeProcessing(false);

			for (int i = 0; i < 20; i++) {
				rewriteStmt
						.addBatch("INSERT INTO testBug51704 VALUES ({tsp '2002-11-12 10:00:00'})");
			}

			rewriteStmt.executeBatch(); // this should pass, because mysqld
										// doesn't validate any escape
										// sequences,
										// it just strips them, where our escape
										// processor validates them

			Statement batchStmt = conn.createStatement();
			batchStmt.setEscapeProcessing(false);
			batchStmt
					.addBatch("INSERT INTO testBug51704 VALUES ({tsp '2002-11-12 10:00:00'})");
			batchStmt.executeBatch(); // same here
		} finally {
			rewriteConn.close();
		}
	}

	public void testBug54175() throws Exception {
		if (!versionMeetsMinimum(5, 5)) {
			return;
		}

		Connection utf8conn = getConnectionWithProps("characterEncoding=utf8");

		createTable("testBug54175", "(a VARCHAR(10)) CHARACTER SET utf8mb4");
		stmt.execute("INSERT INTO testBug54175 VALUES(0xF0AFA6B2)");
		rs = utf8conn.createStatement().executeQuery(
				"SELECT * FROM testBug54175");
		assertTrue(rs.next());
		assertEquals(55422, rs.getString(1).charAt(0));
	}

	/**
	 * Tests fix for Bug#58728, NPE in com.mysql.jdbc.jdbc2.optional.StatementWrappe.getResultSet()
	 * ((com.mysql.jdbc.ResultSetInternalMethods) rs).setWrapperStatement(this);
	 * when rs is null
	 */
	public void testBug58728() throws Exception {
		createTable("testbug58728", "(Id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, txt VARCHAR(50))","InnoDB");
		this.stmt.executeUpdate("INSERT INTO testbug58728 VALUES (NULL, 'Text 1'), (NULL, 'Text 2')");
		
		MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource();
		pds.setUrl(dbUrl);
		Statement stmt1 = pds.getPooledConnection().getConnection().createStatement();
		stmt1.executeUpdate("UPDATE testbug58728 SET txt = 'New text' WHERE Id > 0");
		ResultSet rs1 = stmt1.getResultSet();
		stmt1.close();
		if (rs1 != null) {
			rs1.close();
		}
	}
	
	public void testBug61501() throws Exception {
		createTable("testBug61501", "(id int)");
		stmt.executeUpdate("INSERT INTO testBug61501 VALUES (1)");
		String sql = "SELECT id FROM testBug61501 where id=1";
		pstmt = conn.prepareStatement(sql);
		pstmt.executeQuery();
		pstmt.cancel();
		pstmt.close();
		
		pstmt = conn.prepareStatement(sql);
		rs = pstmt.executeQuery();
		
		stmt.cancel();
		stmt.executeQuery(sql);
		stmt.cancel();
		stmt.execute(sql);
		pstmt = ((com.mysql.jdbc.Connection) conn).serverPrepareStatement(sql);
		pstmt.execute();
		pstmt.cancel();
		pstmt.execute();
		
		sql = "INSERT INTO testBug61501 VALUES (2)";
		pstmt = conn.prepareStatement(sql);
		pstmt.execute();
		assertEquals(1, pstmt.getUpdateCount());
		pstmt.cancel();
		pstmt.close();
		
		pstmt = conn.prepareStatement(sql);
		assertEquals(1, pstmt.executeUpdate());
		
		stmt.cancel();
		assertEquals(1, stmt.executeUpdate(sql));
		stmt.cancel();
		stmt.execute(sql);
		assertEquals(1, stmt.getUpdateCount());
		
		pstmt = ((com.mysql.jdbc.Connection) conn).serverPrepareStatement(sql);
		pstmt.execute();
		assertEquals(1, pstmt.getUpdateCount());
		pstmt.cancel();
		pstmt.close();
		
		pstmt = ((com.mysql.jdbc.Connection) conn).serverPrepareStatement(sql);
		assertEquals(1, pstmt.executeUpdate());
		
		pstmt.cancel();
		pstmt.addBatch();
		pstmt.addBatch();
		pstmt.addBatch();
		int[] counts = pstmt.executeBatch();
		
		for (int i = 0; i < counts.length; i++) {
			assertEquals(1, counts[i]);
		}
		
		pstmt = conn.prepareStatement(sql);
		pstmt.cancel();
		pstmt.addBatch();
		pstmt.addBatch();
		pstmt.addBatch();
		counts = pstmt.executeBatch();
		
		for (int i = 0; i < counts.length; i++) {
			assertEquals(1, counts[i]);
		}
		
		stmt.cancel();
		stmt.addBatch(sql);
		stmt.addBatch(sql);
		stmt.addBatch(sql);

		counts = stmt.executeBatch();
		
		for (int i = 0; i < counts.length; i++) {
			assertEquals(1, counts[i]);
		}
		
		
	}
	
	public void testbug61866() throws Exception {
			
			createProcedure("WARN_PROCEDURE", "() BEGIN	DECLARE l_done INT;	SELECT 1 	INTO l_done	FROM DUAL	WHERE 1=2; END");
			this.pstmt = this.conn.prepareCall("{CALL WARN_PROCEDURE()}");
			this.pstmt.execute();
			assertTrue("No warning when expected", this.pstmt.getWarnings().toString().contentEquals("java.sql.SQLWarning: No data - zero rows fetched, selected, or processed"));
			this.pstmt.clearWarnings();
			assertNull("Warning when not expected", this.pstmt.getWarnings());
	}
	
	public void testbug12565726() throws Exception {
		// Not putting the space between VALUES() and ON DUPLICATE KEY UPDATE
		// causes C/J a) enter rewriting the query altrhough it has ON UPDATE 
		// and b) to generate the wrong query with multiple ON DUPLICATE KEY

		Properties props = new Properties();
        props.put("rewriteBatchedStatements","true");
        props.put("useServerPrepStmts","false");
        props.put("enablePacketDebug","true");
		this.conn = getConnectionWithProps(props);
		this.stmt = this.conn.createStatement();
		
		try {
			createTable("testbug12565726", "(id int primary key, txt1 varchar(32))");
            this.stmt.executeUpdate("INSERT INTO testbug12565726 " +
            	"(id, txt1) VALUES (1, 'something')");
			

            this.pstmt = this.conn.prepareStatement("INSERT INTO " +
                    "testbug12565726 (id, txt1) " +
                    "VALUES (?, ?)ON DUPLICATE KEY UPDATE " +
                    "id=LAST_INSERT_ID(id)+10");            
            
            this.pstmt.setInt(1, 1);
            this.pstmt.setString(2, "something else");
            this.pstmt.addBatch();
            
            this.pstmt.setInt(1, 2);
            this.pstmt.setString(2, "hope it is not error again!");
            this.pstmt.addBatch();

            this.pstmt.executeBatch();
			
		} finally {
		}
		
	}

	public void testBug36478() throws Exception {

		createTable("testBug36478", "(`limit` varchar(255) not null primary key, id_limit INT, limit1 INT, maxlimit2 INT)");
		
		this.stmt.execute("INSERT INTO testBug36478 VALUES ('bahblah',1,1,1)");
		this.stmt.execute("INSERT INTO testBug36478 VALUES ('bahblah2',2,2,2)");
		this.pstmt = this.conn.prepareStatement("select 1 FROM testBug36478");

		this.pstmt.setMaxRows(1);
		this.rs = this.pstmt.executeQuery();
		this.rs.first();
		assertTrue(this.rs.isFirst());
		assertTrue(this.rs.isLast());

		this.pstmt = this.conn.prepareStatement("select `limit`, id_limit, limit1, maxlimit2 FROM testBug36478");
		this.pstmt.setMaxRows(0);
		this.rs = this.pstmt.executeQuery();
		this.rs.first();
		assertTrue(this.rs.isFirst());
		assertFalse(this.rs.isLast());		
		
		//SSPS
		Connection _conn = null;
		PreparedStatement s = null;
		try {
			Properties props = new Properties();
			props.setProperty("useServerPrepStmts", "true");

			_conn = getConnectionWithProps(props);
			s = _conn.prepareStatement("select 1 FROM testBug36478");

			s.setMaxRows(1);
			ResultSet _rs = s.executeQuery();
			_rs.first();
			assertTrue(_rs.isFirst());
			assertTrue(_rs.isLast());

			s = _conn.prepareStatement("select `limit`, id_limit, limit1, maxlimit2 FROM testBug36478");
			s.setMaxRows(0);
			_rs = s.executeQuery();
			_rs.first();
			assertTrue(_rs.isFirst());
			assertFalse(_rs.isLast());		
			
		} finally {
			if (s != null) {
				s.close();
			}
			if (_conn != null) {
				_conn.close();
			}
		}
		
	}

	/**
	 * Tests fix for BUG#40279 - Timestamp values get truncated when passed as prepared statement parameters
	 * (and duplicate BUG#60584 - prepared statements truncate milliseconds)
	 * 
	 *  [13 Sep 2012 21:06] Mark Matthews
	 *  This was fixed with http://bazaar.launchpad.net/~mysql/connectorj/5.1/revision/1107 in 2011,
	 *  it supports MySQL-5.6.4 or later.
	 *  
	 *  But that fix did not cover useLegacyDatetimeCode=true case.
	 * 
	 * @throws Exception
	 */
	public void testBug40279() throws Exception {
		if (!versionMeetsMinimum(5, 6, 4)) {
			return;
		}
		
		createTable("testBug40279", "(f1 int, f2 timestamp(6))");

		Timestamp ts = new Timestamp(1300791248001L);

		Connection ps_conn_legacy = null;
		Connection ps_conn_nolegacy = null;

		Connection ssps_conn_legacy = null;
		Connection ssps_conn_nolegacy = null;

		try {
			Properties props = new Properties();
			props.setProperty("serverTimezone", "UTC");
			props.setProperty("useLegacyDatetimeCode", "true");
			props.setProperty("useServerPrepStmts", "false");
			ps_conn_legacy = getConnectionWithProps(props);
			
			props.setProperty("useLegacyDatetimeCode", "false");
			ps_conn_nolegacy = getConnectionWithProps(props);
			
			props.setProperty("useLegacyDatetimeCode", "true");
			props.setProperty("useServerPrepStmts", "true");
			ssps_conn_legacy = getConnectionWithProps(props);

			props.setProperty("useLegacyDatetimeCode", "false");
			ssps_conn_nolegacy = getConnectionWithProps(props);

			this.pstmt = ps_conn_legacy.prepareStatement("INSERT INTO testBug40279(f1, f2) VALUES (?, ?)");
			this.pstmt.setInt(1, 1);
			this.pstmt.setTimestamp(2, ts);
			this.pstmt.execute();
			this.pstmt.close();

			this.pstmt = ps_conn_nolegacy.prepareStatement("INSERT INTO testBug40279(f1, f2) VALUES (?, ?)");
			this.pstmt.setInt(1, 2);
			this.pstmt.setTimestamp(2, ts);
			this.pstmt.execute();
			this.pstmt.close();

			this.pstmt = ssps_conn_legacy.prepareStatement("INSERT INTO testBug40279(f1, f2) VALUES (?, ?)");
			this.pstmt.setInt(1, 3);
			this.pstmt.setTimestamp(2, ts);
			this.pstmt.execute();
			this.pstmt.close();

			this.pstmt = ssps_conn_nolegacy.prepareStatement("INSERT INTO testBug40279(f1, f2) VALUES (?, ?)");
			this.pstmt.setInt(1, 4);
			this.pstmt.setTimestamp(2, ts);
			this.pstmt.execute();
			this.pstmt.close();

			this.rs = this.stmt.executeQuery("SELECT f2 FROM testBug40279");
			while (this.rs.next()) {
				assertEquals(ts.getNanos(), this.rs.getTimestamp("f2").getNanos());
			}
			
		} finally {
			if (ps_conn_legacy != null) {
				ps_conn_legacy.close();
			}
			if (ps_conn_nolegacy != null) {
				ps_conn_nolegacy.close();
			}
			if (ssps_conn_legacy != null) {
				ssps_conn_legacy.close();
			}
			if (ssps_conn_nolegacy != null) {
				ssps_conn_nolegacy.close();
			}
		}
	
	}

	/**
	 * Tests fix for BUG#35653 - executeQuery() in Statement.java let "TRUNCATE" queries being executed.
	 * "RENAME" is also filtered now.
	 * 
	 * @throws Exception
	 */
	public void testBug35653() throws Exception {
		createTable("testBug35653", "(f1 int)");
		try {
			this.stmt.executeQuery("TRUNCATE testBug35653");
			fail("executeQuery() shouldn't allow TRUNCATE");
		} catch (SQLException e) {
			assertTrue(SQLError.SQL_STATE_ILLEGAL_ARGUMENT == e.getSQLState());
		}

		try {
			this.stmt.executeQuery("RENAME TABLE testBug35653 TO testBug35653_new");
			fail("executeQuery() shouldn't allow RENAME");
		} catch (SQLException e) {
			assertTrue(SQLError.SQL_STATE_ILLEGAL_ARGUMENT == e.getSQLState());
		} finally {
			dropTable("testBug35653_new");
		}
	}

	/**
	 * Tests fix for BUG#64805 - StatementImpl$CancelTask occasionally throws NullPointerExceptions.
	 * 
	 * @throws Exception
	 */
	public void testBug64805() throws Exception {

		try {
			this.stmt.setQueryTimeout(5);
			this.stmt.executeQuery("select sleep(5)");
		} catch (NullPointerException e) {
			e.printStackTrace();
			fail();
		} catch (Exception e) {
			if (e instanceof MySQLTimeoutException) {
				// expected behavior in slow environment
			} else {
				throw e;
			}
		}

	}

	/**
	 * WL#4897 - Add EXPLAIN INSERT/UPDATE/DELETE
	 * 
	 * Added support for EXPLAIN INSERT/REPLACE/UPDATE/DELETE. Connector/J must issue a warning containing the execution
	 * plan for slow queries when connection properties logSlowQueries=true and explainSlowQueries=true are used.
	 * 
	 * @throws SQLException
	 */
	public void testExecutionPlanForSlowQueries() throws Exception {
		// once slow query (with execution plan) warning is sent to System.err, we capture messages sent here to check
		// proper operation.
		final class TestHandler {
			// System.err diversion handling
			PrintStream systemErrBackup = null;
			ByteArrayOutputStream systemErrDetour = null;

			// Connection handling
			Connection testConn = null;

			TestHandler() {
				systemErrBackup = System.err;
				systemErrDetour = new ByteArrayOutputStream(8192);
				System.setErr(new PrintStream(systemErrDetour));
			}

			boolean containsSlowQueryMsg(String lookFor) {
				String errMsg = systemErrDetour.toString();
				boolean found = false;

				if (errMsg.indexOf("Slow query explain results for '" + lookFor + "'") != -1) {
					found = true;
				}
				systemErrDetour.reset();
				// print message in original OutputStream.
				systemErrBackup.print(errMsg);
				return found;
			}

			void undoSystemErrDiversion() throws IOException {
				systemErrBackup.print(systemErrDetour.toString());
				systemErrDetour.close();
				System.setErr(systemErrBackup);
				systemErrDetour = null;
				systemErrBackup = null;
			}

			@SuppressWarnings("synthetic-access")
			Connection getNewConnectionForSlowQueries() throws SQLException {
				releaseConnectionResources();
				testConn = getConnectionWithProps("logSlowQueries=true,explainSlowQueries=true");
				Statement st = testConn.createStatement();
				// execute several fast queries to unlock slow query analysis and lower query execution time mean
				for (int i = 0; i < 25; i++) {
					st.executeQuery("SELECT 1");
				}
				return testConn;
			}

			void releaseConnectionResources() throws SQLException {
				if (testConn != null) {
					testConn.close();
					testConn = null;
				}
			}
		}

		TestHandler testHandler = new TestHandler();
		Statement testStatement = null;
		
		try {
			if (versionMeetsMinimum(5, 6, 3)) {
				createTable("testWL4897", "(f1 INT NOT NULL PRIMARY KEY, f2 CHAR(50))");

				// when executed in the following sequence, each one of these queries take approximately 1 sec.
				final String[] slowQueries = {
						"INSERT INTO testWL4897 VALUES (SLEEP(0.5) + 1, 'MySQL'), (SLEEP(0.5) + 2, 'Connector/J')",
						"SELECT * FROM testWL4897 WHERE f1 + SLEEP(0.5) = f1",
						"REPLACE INTO testWL4897 VALUES (SLEEP(0.33) + 2, 'Database'), (SLEEP(0.33) + 3, 'Connector'), (SLEEP(0.33) + 4, 'Java')",
						"UPDATE testWL4897 SET f1 = f1 * 10 + SLEEP(0.25)",
						"DELETE FROM testWL4897 WHERE f1 + SLEEP(0.25) = f1" };

				for (String query : slowQueries) {
					testStatement = testHandler.getNewConnectionForSlowQueries().createStatement();
					testStatement.execute(query);
					assertTrue("A slow query explain results warning should have been issued for: '" + query + "'.",
							testHandler.containsSlowQueryMsg(query));
					testStatement.close();
				}
			} else {
				// only SELECT is qualified to log slow query explain results warning
				final String query = "SELECT SLEEP(1)";
				
				testStatement = testHandler.getNewConnectionForSlowQueries().createStatement();
				testStatement.execute(query);
				assertTrue("A slow query explain results warning should have been issued for: '" + query + "'.",
						testHandler.containsSlowQueryMsg(query));
				testStatement.close();
			}

		} finally {
			testHandler.releaseConnectionResources();
			testHandler.undoSystemErrDiversion();
		}
	}

	/**
	 * Tests fix for BUG#68562 - Combination rewriteBatchedStatements and useAffectedRows not working as expected
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug68562() throws Exception {

		// 5.1 server returns wrong values for found_rows because Bug#46675 was fixed only for 5.5+
		if (!versionMeetsMinimum(5, 5)) {
			return;
		}

		int batchSize = 3;
		createTable("testBug68562_found", "(id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, version VARCHAR(255)) ENGINE=InnoDB;");
		createTable("testBug68562_affected", "(id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, version VARCHAR(255)) ENGINE=InnoDB;");

		// insert the records (no update)
		int[] foundRows = testBug68562ExecuteBatch(batchSize, false, false, false);
		for(int foundRow : foundRows) {
			assertEquals(1, foundRow);
		}
		int[] affectedRows = testBug68562ExecuteBatch(batchSize, true, false, false);
		for(int affectedRow : affectedRows) {
			assertEquals(1, affectedRow);
		}

		// update the inserted records with same values
		foundRows = testBug68562ExecuteBatch(batchSize, false, false, false);
		for(int foundRow : foundRows) {
			assertEquals(1, foundRow);
		}
		affectedRows = testBug68562ExecuteBatch(batchSize, true, false, false);
		for(int affectedRow : affectedRows) {
			assertEquals(0, affectedRow);
		}

		// update the inserted records with same values REWRITING THE BATCHED STATEMENTS
		foundRows = testBug68562ExecuteBatch(batchSize, false, true, false);
		for(int foundRow : foundRows) {
			assertEquals(batchSize, foundRow);
		}
		affectedRows = testBug68562ExecuteBatch(batchSize, true, true, false);
		for(int affectedRow : affectedRows) {
			assertEquals(0, affectedRow);
		}

		// update the inserted records with NEW values REWRITING THE BATCHED STATEMENTS
		foundRows = testBug68562ExecuteBatch(batchSize, false, true, true);
		for(int foundRow : foundRows) {
			assertEquals(2 * batchSize, foundRow);
		}
		affectedRows = testBug68562ExecuteBatch(batchSize, true, true, true);
		for(int affectedRow : affectedRows) {
			assertEquals(2 * batchSize, affectedRow);
		}
	}	

	private int[] testBug68562ExecuteBatch(int batchSize, boolean useAffectedRows, boolean rewriteBatchedStatements, boolean realUpdate) throws ClassNotFoundException, SQLException{

		String tableName="testBug68562";

		Properties properties = new Properties();
		if (useAffectedRows) {
			properties.put("useAffectedRows", "true");
			tableName += "_affected";
		} else {
			tableName += "_found";
		}
		if(rewriteBatchedStatements) {
			properties.put("rewriteBatchedStatements", "true");
		}
		Connection connection = getConnectionWithProps(properties);    	

		PreparedStatement statement = connection.prepareStatement("INSERT INTO " + tableName +
				"(id, name, version) VALUES(?,?,?) ON DUPLICATE KEY UPDATE version = " +
				(realUpdate ?
						"CONCAT(VALUES(version),'updated'), name = CONCAT(VALUES(name),'updated')" :
						"VALUES(version), name = VALUES(name)"
				));
		for(int i = 0; i < batchSize; i++) {
			statement.setInt(1, i);
			statement.setString(2, "name" + i);
			statement.setString(3, "version" + i);
			statement.addBatch();
		}

		int[] affectedRows = statement.executeBatch();

		statement.close();
		connection.close();

		return affectedRows;

	}

}
