/*
  Copyright (c) 2013, 2014, 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.jdbc4;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.mysql.jdbc.CallableStatement;
import com.mysql.jdbc.PreparedStatement;
import com.mysql.jdbc.StatementImpl;

import testsuite.BaseTestCase;

public class StatementRegressionTest extends BaseTestCase {
	/**
	 * Creates a new StatementRegressionTest.
	 * 
	 * @param name
	 *            the name of the test
	 */
	public StatementRegressionTest(String name) {
		super(name);
	}

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

	/**
	 * Tests fix for BUG#68916 - closeOnCompletion doesn't work.
	 * 
	 * @throws Exception
	 *             if the test fails.
	 */
	public void testBug68916() throws Exception {
		// Prepare common test objects
		createProcedure("testBug68916_proc", "() BEGIN SELECT 1; SELECT 2; SELECT 3; END");
		createTable("testBug68916_tbl", "(fld1 INT NOT NULL AUTO_INCREMENT, fld2 INT, PRIMARY KEY(fld1))");

		// STEP 1: Test using standard connection (no properties)
		subTestBug68916ForStandardConnection();

		// STEP 2: Test using connection property holdResultsOpenOverStatementClose=true
		subTestBug68916ForHoldResultsOpenOverStatementClose();

		// STEP 3: Test using connection property dontTrackOpenResources=true
		subTestBug68916ForDontTrackOpenResources();

		// STEP 4: Test using connection property allowMultiQueries=true
		subTestBug68916ForAllowMultiQueries();

		// STEP 5: Test concurrent Statement/ResultSet sharing same Connection
		subTestBug68916ForConcurrency();
	}

	private void subTestBug68916ForStandardConnection() throws Exception {
		Connection testConnection = conn;
		String testStep;
		ResultSet testResultSet1, testResultSet2, testResultSet3;

		// We are testing against code that was compiled with Java 6, so methods isCloseOnCompletion() and
		// closeOnCompletion() aren't available in the Statement interface. We need to test directly our
		// implementations.
		StatementImpl testStatement = null;
		PreparedStatement testPrepStatement = null;
		CallableStatement testCallStatement = null;

		/*
		 * Testing with standard connection (no properties)
		 */
		testStep = "Standard Connection";

		/*
		 * SUB-STEP 0: The basics (connection without properties)
		 */
		// **testing Statement**
		// ResultSets should be closed when owning Statement is closed
		testStatement = (StatementImpl) testConnection.createStatement();

		assertFalse(testStep + ".ST:0. Statement.isCloseOnCompletion(): false by default.",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());

		// test Statement.close()
		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.close();

		assertTrue(testStep + ".ST:0. ResultSet.isClosed(): true after Statement.Close().", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:0. Statement.isClosed(): true after Statement.Close().", testStatement.isClosed());

		// **testing PreparedStatement**
		// ResultSets should be closed when owning PreparedStatement is closed
		testPrepStatement = (com.mysql.jdbc.PreparedStatement) testConnection.prepareStatement("SELECT 1");

		assertFalse(testStep + ".PS:0. PreparedStatement.isCloseOnCompletion(): false by default.",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());

		// test PreparedStatement.close()
		testPrepStatement.execute();
		testResultSet1 = testPrepStatement.getResultSet();

		assertFalse(testStep + ".PS:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.close();

		assertTrue(testStep + ".PS:0. ResultSet.isClosed(): true after PreparedStatement.close().",
				testResultSet1.isClosed());
		assertTrue(testStep + ".PS:0. PreparedStatement.isClosed(): true after PreparedStatement.close().",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 1: One ResultSet (connection without properties)
		 */
		// **testing Statement**
		// Statement using closeOnCompletion should be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset close, keeping statement open, when following with an executeBatch()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.addBatch("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)");
		testStatement.executeBatch();

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true after executeBatch() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset close keeping statement open, when following with an executeUpdate()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				com.mysql.jdbc.Statement.RETURN_GENERATED_KEYS);

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true after executeUpdate() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing PreparedStatement**
		// PreparedStatement using closeOnCompletion should be closed when last ResultSet is closed
		testPrepStatement = (PreparedStatement) testConnection.prepareStatement("SELECT 1");
		testPrepStatement.closeOnCompletion();

		testResultSet1 = testPrepStatement.executeQuery();

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".PS:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".PS:1. PreparedStatement.isClosed(): true when last ResultSet is closed.",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 2: Multiple ResultSets, sequentially (connection without properties)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testResultSet2 = testStatement.executeQuery("SELECT 2"); // closes testResultSet1

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true after 2nd Statement.executeQuery().",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet2.next()) {
		}

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet3 = testStatement.executeQuery("SELECT 3"); // closes testResultSet2

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true after 3rd Statement.executeQuery().",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet3.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet3.isClosed());
		assertTrue(testStep + ".ST:2. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		/*
		 * SUB-STEP 3: Multiple ResultSets, returned at once (connection without properties)
		 */
		// **testing Statement**
		// Statement using closeOnCompletion should be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:3. There should be some ResultSets.",
				testStatement.execute("CALL testBug68916_proc"));
		testResultSet1 = testStatement.getResultSet();

		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testStatement.getResultSet();

		assertFalse(testStep
				+ ".ST:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testStatement.getResultSet();

		assertTrue(testStep
				+ ".ST:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertTrue(testStep
				+ ".ST:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		// no more ResultSets, must close Statement
		assertFalse(testStep + ".ST:3. There should be no more ResultSets.", testStatement.getMoreResults());

		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertTrue(testStep + ".ST:3. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing CallableStatement**
		// CallableStatement using closeOnCompletion should be closed when last ResultSet is closed
		testCallStatement = (CallableStatement) testConnection.prepareCall("CALL testBug68916_proc");
		testCallStatement.closeOnCompletion();

		assertTrue(testStep + ".CS:3. There should be some ResultSets.", testCallStatement.execute());
		testResultSet1 = testCallStatement.getResultSet();

		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testCallStatement.getResultSet();

		assertFalse(testStep
				+ ".CS:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testCallStatement.getResultSet();

		assertTrue(testStep
				+ ".CS:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertTrue(testStep
				+ ".CS:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		// no more ResultSets, must close Statement
		assertFalse(testStep + ".CS:3. There should be no more ResultSets.", testCallStatement.getMoreResults());

		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertTrue(testStep + ".CS:3. CallableStatement.isClosed(): true when last ResultSet is closed.",
				testCallStatement.isClosed());

		/*
		 * SUB-STEP 4: Generated Keys ResultSet (connection without properties)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test again and combine with simple query
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (4), (5), (6)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		testResultSet2 = testStatement.executeQuery("SELECT 2");

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true after executeQuery() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet2.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:4. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());
	}

	private void subTestBug68916ForHoldResultsOpenOverStatementClose() throws Exception {
		Connection testConnection;
		String testStep;
		ResultSet testResultSet1, testResultSet2, testResultSet3;

		// We are testing against code that was compiled with Java 6, so methods isCloseOnCompletion() and
		// closeOnCompletion() aren't available in the Statement interface. We need to test directly our
		// implementations.
		StatementImpl testStatement = null;
		PreparedStatement testPrepStatement = null;
		CallableStatement testCallStatement = null;

		/*
		 * Testing with connection property holdResultsOpenOverStatementClose=true
		 */
		testStep = "Conn. Prop. 'holdResultsOpenOverStatementClose'";
		testConnection = getConnectionWithProps("holdResultsOpenOverStatementClose=true");

		/*
		 * SUB-STEP 0: The basics (holdResultsOpenOverStatementClose=true)
		 */
		// **testing Statement**
		// ResultSets should stay open when owning Statement is closed
		testStatement = (StatementImpl) testConnection.createStatement();

		assertFalse(testStep + ".ST:0. Statement.isCloseOnCompletion(): false dy default.",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());

		// test Statement.close()
		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.close();

		assertFalse(testStep + ".ST:0. ResultSet.isClosed(): false after Statement.Close().", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:0. Statement.isClosed(): true after Statement.Close().", testStatement.isClosed());

		// **testing PreparedStatement**
		// ResultSets should stay open when owning PreparedStatement is closed
		testPrepStatement = (com.mysql.jdbc.PreparedStatement) testConnection.prepareStatement("SELECT 1");

		assertFalse(testStep + ".PS:0. PreparedStatement.isCloseOnCompletion(): false by default.",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());

		// test PreparedStatement.close()
		testPrepStatement.execute();
		testResultSet1 = testPrepStatement.getResultSet();

		assertFalse(testStep + ".PS:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.close();

		assertFalse(testStep + ".PS:0. ResultSet.isClosed(): false after PreparedStatement.close().",
				testResultSet1.isClosed());
		assertTrue(testStep + ".PS:0. PreparedStatement.isClosed(): true after PreparedStatement.close().",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 1: One ResultSet (holdResultsOpenOverStatementClose=true)
		 */
		// **testing Statement**
		// Statement using closeOnCompletion should be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset close keeping statement open, when following with an executeBatch()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.addBatch("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)");
		testStatement.executeBatch();

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after executeBatch() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset close keeping statement open, when following with an executeUpdate()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				Statement.RETURN_GENERATED_KEYS);

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after executeUpdate() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing PreparedStatement**
		// PreparedStatement using closeOnCompletion should be closed when last ResultSet is closed
		testPrepStatement = (PreparedStatement) testConnection.prepareStatement("SELECT 1");
		testPrepStatement.closeOnCompletion();

		testResultSet1 = testPrepStatement.executeQuery();

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".PS:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".PS:1. PreparedStatement.isClosed(): true when last ResultSet is closed.",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 2: Multiple ResultSets, sequentially (holdResultsOpenOverStatementClose=true)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testResultSet2 = testStatement.executeQuery("SELECT 2"); // mustn't close testResultSet1

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after 2nd Statement.executeQuery().",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet2.next()) {
		}

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet3 = testStatement.executeQuery("SELECT 3"); // mustn't close testResultSet1 nor testResultSet2

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after 3rd Statement.executeQuery().",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet2.close();

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close();
		testResultSet3.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet3.isClosed());
		assertTrue(testStep + ".ST:2. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		/*
		 * SUB-STEP 3: Multiple ResultSets, returned at once (holdResultsOpenOverStatementClose=true)
		 */
		// **testing Statement**
		// Statement using closeOnCompletion should be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:3. There should be some ResultSets.",
				testStatement.execute("CALL testBug68916_proc"));
		testResultSet1 = testStatement.getResultSet();

		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testStatement.getResultSet();

		assertFalse(testStep
				+ ".ST:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testStatement.getResultSet();

		assertTrue(testStep
				+ ".ST:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertTrue(testStep
				+ ".ST:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		// no more ResultSets, must close Statement
		assertFalse(testStep + ".ST:3. There should be no more ResultSets.", testStatement.getMoreResults());

		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertTrue(testStep + ".ST:3. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing CallableStatement**
		// CallableStatement using closeOnCompletion should be closed when last ResultSet is closed
		testCallStatement = (CallableStatement) testConnection.prepareCall("CALL testBug68916_proc");
		testCallStatement.closeOnCompletion();

		assertTrue(testStep + ".CS:3. There should be some ResultSets.", testCallStatement.execute());
		testResultSet1 = testCallStatement.getResultSet();

		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testCallStatement.getResultSet();

		assertFalse(testStep
				+ ".CS:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testCallStatement.getResultSet();

		assertTrue(testStep
				+ ".CS:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertTrue(testStep
				+ ".CS:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		// no more ResultSets, must close Statement
		assertFalse(testStep + ".CS:3. There should be no more ResultSets.", testCallStatement.getMoreResults());

		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertTrue(testStep + ".CS:3. CallableStatement.isClosed(): true when last ResultSet is closed.",
				testCallStatement.isClosed());

		/*
		 * SUB-STEP 4: Generated Keys ResultSet (holdResultsOpenOverStatementClose=true)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test again and combine with simple query
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (4), (5), (6)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		testResultSet2 = testStatement.executeQuery("SELECT 2");

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false after executeQuery() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet2.close();

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false when last ResultSet is closed (still one open).",
				testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:4. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		testConnection.close();
	}

	private void subTestBug68916ForDontTrackOpenResources() throws Exception {
		Connection testConnection;
		String testStep;
		ResultSet testResultSet1, testResultSet2, testResultSet3;

		// We are testing against code that was compiled with Java 6, so methods isCloseOnCompletion() and
		// closeOnCompletion() aren't available in the Statement interface. We need to test directly our
		// implementations.
		StatementImpl testStatement = null;
		PreparedStatement testPrepStatement = null;
		CallableStatement testCallStatement = null;

		/*
		 * Testing with connection property dontTrackOpenResources=true
		 */
		testStep = "Conn. Prop. 'dontTrackOpenResources'";
		testConnection = getConnectionWithProps("dontTrackOpenResources=true");

		/*
		 * SUB-STEP 0: The basics (dontTrackOpenResources=true)
		 */
		// **testing Statement**
		// ResultSets should stay open when owning Statement is closed
		testStatement = (StatementImpl) testConnection.createStatement();

		assertFalse(testStep + ".ST:0. Statement.isCloseOnCompletion(): false by default.",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());

		// test Statement.close()
		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.close();

		assertFalse(testStep + ".ST:0. ResultSet.isClosed(): false after Statement.Close().", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:0. Statement.isClosed(): true after Statement.Close().", testStatement.isClosed());

		// **testing PreparedStatement**
		// ResultSets should stay open when owning PreparedStatement is closed
		testPrepStatement = (com.mysql.jdbc.PreparedStatement) testConnection.prepareStatement("SELECT 1");

		assertFalse(testStep + ".PS:0. PreparedStatement.isCloseOnCompletion(): false by default.",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());

		// test PreparedStatement.close()
		testPrepStatement.execute();
		testResultSet1 = testPrepStatement.getResultSet();

		assertFalse(testStep + ".PS:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.close();

		assertFalse(testStep + ".PS:0. ResultSet.isClosed(): false after PreparedStatement.close().",
				testResultSet1.isClosed());
		assertTrue(testStep + ".PS:0. PreparedStatement.isClosed(): true after PreparedStatement.close().",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 1: One ResultSet (dontTrackOpenResources=true)
		 */
		// **testing Statement**
		// Statement, although using closeOnCompletion, shouldn't be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset (not) close, keeping statement open, when following with an executeBatch()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.addBatch("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)");
		testStatement.executeBatch();

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after executeBatch() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset (not) close keeping statement open, when following with an executeUpdate()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				Statement.RETURN_GENERATED_KEYS);

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after executeUpdate() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing PreparedStatement**
		// PreparedStatement, although using closeOnCompletion, shouldn't be closed when last ResultSet is closed
		testPrepStatement = (PreparedStatement) testConnection.prepareStatement("SELECT 1");
		testPrepStatement.closeOnCompletion();

		testResultSet1 = testPrepStatement.executeQuery();

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testResultSet1.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".PS:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false when last ResultSet is closed.",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 2: Multiple ResultSets, sequentially (dontTrackOpenResources=true)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testResultSet2 = testStatement.executeQuery("SELECT 2"); // mustn't close testResultSet1

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after 2nd Statement.executeQuery().",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet2.next()) {
		}

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet3 = testStatement.executeQuery("SELECT 3"); // mustn't close testResultSet1 nor testResultSet2

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after 3rd Statement.executeQuery().",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet2.close();

		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close();
		testResultSet3.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		/*
		 * SUB-STEP 3: Multiple ResultSets, returned at once (dontTrackOpenResources=true)
		 */
		// **testing Statement**
		// Statement, although using closeOnCompletion, shouldn't be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:3. There should be some ResultSets.",
				testStatement.execute("CALL testBug68916_proc"));
		testResultSet1 = testStatement.getResultSet();

		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testStatement.getResultSet();

		assertFalse(testStep
				+ ".ST:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testStatement.getResultSet();

		assertFalse(testStep
				+ ".ST:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertFalse(testStep
				+ ".ST:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertFalse(testStep + ".ST:3. There should be no more ResultSets.", testStatement.getMoreResults());

		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false after last Satement.getMoreResults().",
				testStatement.isClosed());

		// since open ResultSets aren't tracked, we need to close all manually
		testResultSet1.close();
		testResultSet2.close();
		testResultSet3.close();
		// although there are no more ResultSets, Statement mustn't be closed

		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing CallableStatement**
		// CallableStatement, although using closeOnCompletion, shouldn't be closed when last ResultSet is closed
		testCallStatement = (CallableStatement) testConnection.prepareCall("CALL testBug68916_proc");
		testCallStatement.closeOnCompletion();

		assertTrue(testStep + ".CS:3. There should be some ResultSets.", testCallStatement.execute());
		testResultSet1 = testCallStatement.getResultSet();

		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testCallStatement.getResultSet();

		assertFalse(testStep
				+ ".CS:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testCallStatement.getResultSet();

		assertFalse(testStep
				+ ".CS:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertFalse(testStep
				+ ".CS:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertFalse(testStep + ".CS:3. There should be no more ResultSets.", testCallStatement.getMoreResults());

		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false after last Satement.getMoreResults().",
				testCallStatement.isClosed());

		// since open ResultSets aren't tracked, we need to close all manually
		testResultSet1.close();
		testResultSet2.close();
		testResultSet3.close();
		// although there are no more ResultSets, Statement mustn't be closed

		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet3.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false when last ResultSet is closed.",
				testCallStatement.isClosed());

		/*
		 * SUB-STEP 4: Generated Keys ResultSet (dontTrackOpenResources=true)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		// test again and combine with simple query
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (4), (5), (6)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		testResultSet2 = testStatement.executeQuery("SELECT 2");

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false after executeQuery() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet2.close();

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false when last ResultSet is closed (still one open).",
				testStatement.isClosed());

		testResultSet1.close(); // although it's last open ResultSet, Statement mustn't be closed

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false when last ResultSet is closed.",
				testStatement.isClosed());

		testConnection.close();
	}

	private void subTestBug68916ForAllowMultiQueries() throws Exception {
		Connection testConnection;
		String testStep;
		ResultSet testResultSet1, testResultSet2, testResultSet3;

		// We are testing against code that was compiled with Java 6, so methods isCloseOnCompletion() and
		// closeOnCompletion() aren't available in the Statement interface. We need to test directly our
		// implementations.
		StatementImpl testStatement = null;
		PreparedStatement testPrepStatement = null;
		CallableStatement testCallStatement = null;

		/*
		 * Testing with connection property allowMultiQueries=true
		 */
		testStep = "Conn. Prop. 'allowMultiQueries'";
		testConnection = getConnectionWithProps("allowMultiQueries=true");

		/*
		 * SUB-STEP 0: The basics (allowMultiQueries=true)
		 */
		// **testing Statement**
		// ResultSets should be closed when owning Statement is closed
		testStatement = (StatementImpl) testConnection.createStatement();

		assertFalse(testStep + ".ST:0. Statement.isCloseOnCompletion(): false by default.",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.closeOnCompletion();

		assertTrue(testStep + ".ST:0. Statement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testStatement.isCloseOnCompletion());

		// test Statement.close()
		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:0. Statement.isClosed(): false.", testStatement.isClosed());

		testStatement.close();

		assertTrue(testStep + ".ST:0. ResultSet.isClosed(): true after Statement.Close().", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:0. Statement.isClosed(): true after Statement.Close().", testStatement.isClosed());

		// **testing PreparedStatement**
		// ResultSets should be closed when owning PreparedStatement is closed
		testPrepStatement = (com.mysql.jdbc.PreparedStatement) testConnection.prepareStatement("SELECT 1");

		assertFalse(testStep + ".PS:0. PreparedStatement.isCloseOnCompletion(): false by default.",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.closeOnCompletion();

		assertTrue(testStep
				+ ".PS:0. PreparedStatement.isCloseOnCompletion(): true after 2nd Statement.closeOnCompletion().",
				testPrepStatement.isCloseOnCompletion());

		// test PreparedStatement.close()
		testPrepStatement.execute();
		testResultSet1 = testPrepStatement.getResultSet();

		assertFalse(testStep + ".PS:0. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:0. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testPrepStatement.close();

		assertTrue(testStep + ".PS:0. ResultSet.isClosed(): true after PreparedStatement.close().",
				testResultSet1.isClosed());
		assertTrue(testStep + ".PS:0. PreparedStatement.isClosed(): true after PreparedStatement.close().",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 1: One ResultSet (allowMultiQueries=true)
		 */
		// **testing Statement**
		// Statement using closeOnCompletion should be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".ST:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset close, keeping statement open, when following with an executeBatch()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.addBatch("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)");
		testStatement.executeBatch();

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true after executeBatch() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test implicit resultset close keeping statement open, when following with an executeUpdate()
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3)",
				com.mysql.jdbc.Statement.RETURN_GENERATED_KEYS);

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true after executeUpdate() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:1. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1 = testStatement.getGeneratedKeys();
		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:1. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing PreparedStatement**
		// PreparedStatement using closeOnCompletion should be closed when last ResultSet is closed
		testPrepStatement = (PreparedStatement) testConnection.prepareStatement("SELECT 1");
		testPrepStatement.closeOnCompletion();

		testResultSet1 = testPrepStatement.executeQuery();

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		while (testResultSet1.next()) {
		}

		assertFalse(testStep + ".PS:1. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".PS:1. PreparedStatement.isClosed(): false.", testPrepStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".PS:1. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".PS:1. PreparedStatement.isClosed(): true when last ResultSet is closed.",
				testPrepStatement.isClosed());

		/*
		 * SUB-STEP 2: Multiple ResultSets, sequentially (allowMultiQueries=true)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1");
		testResultSet2 = testStatement.executeQuery("SELECT 2; SELECT 3"); // closes testResultSet1

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true after 2nd Statement.executeQuery().",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		while (testResultSet2.next()) {
		}

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false after ResultSet have reached the end.",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.", testStatement.getMoreResults()); // closes
																											// testResultSet2
		testResultSet3 = testStatement.getResultSet();

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true after Statement.getMoreResults().",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:2. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:2. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet3.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:2. ResultSet.isClosed(): true.", testResultSet3.isClosed());
		assertTrue(testStep + ".ST:2. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		/*
		 * SUB-STEP 3: Multiple ResultSets, returned at once (allowMultiQueries=true)
		 */
		// **testing Statement**
		// Statement using closeOnCompletion should be closed when last ResultSet is closed
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testResultSet1 = testStatement.executeQuery("SELECT 1; SELECT 2; SELECT 3");

		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testStatement.getResultSet();

		assertFalse(testStep
				+ ".ST:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		assertTrue(testStep + ".ST:3. There should be more ResultSets.",
				testStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testStatement.getResultSet();

		assertTrue(testStep
				+ ".ST:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertTrue(testStep
				+ ".ST:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".ST:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".ST:3. Statement.isClosed(): false.", testStatement.isClosed());

		// no more ResultSets, must close Statement
		assertFalse(testStep + ".ST:3. There should be no more ResultSets.", testStatement.getMoreResults());

		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".ST:3. ResultSet.isClosed(): true after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertTrue(testStep + ".ST:3. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// **testing CallableStatement**
		// CallableStatement using closeOnCompletion should be closed when last ResultSet is closed
		testCallStatement = (CallableStatement) testConnection.prepareCall("CALL testBug68916_proc");
		testCallStatement.closeOnCompletion();

		assertTrue(testStep + ".CS:3. There should be some ResultSets.", testCallStatement.execute());
		testResultSet1 = testCallStatement.getResultSet();

		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.KEEP_CURRENT_RESULT));
		testResultSet2 = testCallStatement.getResultSet();

		assertFalse(testStep
				+ ".CS:3. ResultSet.isClosed(): false after Statement.getMoreResults(Statement.KEEP_CURRENT_RESULT).",
				testResultSet1.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		assertTrue(testStep + ".CS:3. There should be more ResultSets.",
				testCallStatement.getMoreResults(Statement.CLOSE_ALL_RESULTS));
		testResultSet3 = testCallStatement.getResultSet();

		assertTrue(testStep
				+ ".CS:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet1.isClosed());
		assertTrue(testStep
				+ ".CS:3. ResultSet.isClosed(): true after Statement.getMoreResults(Statement.CLOSE_ALL_RESULTS).",
				testResultSet2.isClosed());
		assertFalse(testStep + ".CS:3. ResultSet.isClosed(): false.", testResultSet3.isClosed());
		assertFalse(testStep + ".CS:3. CallableStatement.isClosed(): false.", testCallStatement.isClosed());

		// no more ResultSets, must close Statement
		assertFalse(testStep + ".CS:3. There should be no more ResultSets.", testCallStatement.getMoreResults());

		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertTrue(testStep + ".CS:3. ResultSet.isClosed(): true after last Satement.getMoreResults().",
				testResultSet3.isClosed());
		assertTrue(testStep + ".CS:3. CallableStatement.isClosed(): true when last ResultSet is closed.",
				testCallStatement.isClosed());

		/*
		 * SUB-STEP 4: Generated Keys ResultSet (allowMultiQueries=true)
		 */
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (1), (2), (3); "
				+ "INSERT INTO testBug68916_tbl (fld2) VALUES (4), (5), (6)", Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		testResultSet1.close(); // last open ResultSet, must close Statement

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. Statement.isClosed(): true when last ResultSet is closed.",
				testStatement.isClosed());

		// test again and combine with simple query
		testStatement = (StatementImpl) testConnection.createStatement();
		testStatement.closeOnCompletion();

		testStatement.executeUpdate("INSERT INTO testBug68916_tbl (fld2) VALUES (4), (5), (6)",
				Statement.RETURN_GENERATED_KEYS);

		testResultSet1 = testStatement.getGeneratedKeys();
		assertTrue(testStep + ".ST:4. Statement.getGeneratedKeys(): should return some values.", testResultSet1.next());

		testResultSet2 = testStatement.executeQuery("SELECT 2; SELECT 3");

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true after executeQuery() in same Statement.",
				testResultSet1.isClosed());
		assertFalse(testStep + ".ST:4. ResultSet.isClosed(): false.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): false.", testStatement.isClosed());

		// last open ResultSet won't close the Statement
		// because we didn't fetch the next one (SELECT 3)
		testResultSet2.close();

		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet1.isClosed());
		assertTrue(testStep + ".ST:4. ResultSet.isClosed(): true.", testResultSet2.isClosed());
		assertFalse(testStep + ".ST:4. Statement.isClosed(): true when last ResultSet is closed.",
					testStatement.isClosed());
		testStatement.close();

		testConnection.close();
	}

	private void subTestBug68916ForConcurrency() throws Exception {
		ExecutorService executor = Executors.newCachedThreadPool();
		CompletionService<String> complService = new ExecutorCompletionService<String>(executor);

		String[] connectionProperties = new String[] { "", "holdResultsOpenOverStatementClose=true",
				"dontTrackOpenResources=true" };
		// overridesCloseOnCompletion[n] refers to the effect of connectionProperties[n] on
		// Statement.closeOnCompletion()
		boolean[] overridesCloseOnCompletion = new boolean[] { false, false, true };
		String[] sampleQueries = new String[] {
				"SELECT * FROM mysql.help_topic",
				"SELECT SLEEP(1)",
				"SELECT * FROM mysql.time_zone tz "
						+ "INNER JOIN mysql.time_zone_name tzn ON tz.time_zone_id = tzn.time_zone_id "
						+ "INNER JOIN mysql.time_zone_transition tzt ON tz.time_zone_id = tzt.time_zone_id "
						+ "INNER JOIN mysql.time_zone_transition_type tztt ON tzt.time_zone_id = tztt.time_zone_id "
						+ "AND tzt.transition_type_id = tztt.transition_type_id "
						+ "ORDER BY tzn.name , tztt.abbreviation , tzt.transition_time", "SELECT 1" };
		int threadCount = sampleQueries.length;

		for (int c = 0; c < connectionProperties.length; c++) {
			System.out.println("Test Connection with property '" + connectionProperties[c] + "'");
			Connection testConnection = getConnectionWithProps(connectionProperties[c]);

			for (int t = 0; t < threadCount; t++) {
				complService.submit(new subTestBug68916ConcurrentTask(testConnection, sampleQueries[t],
						overridesCloseOnCompletion[c]));
			}

			for (int t = 0; t < threadCount; t++) {
				try {
					System.out.println("   " + complService.take().get());
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				} catch (ExecutionException ex) {
					if (ex.getCause() instanceof Error) {
						// let JUnit try to report as Failure instead of Error
						throw (Error) ex.getCause();
					}
				}
			}

			testConnection.close();
		}
	}

	private class subTestBug68916ConcurrentTask implements Callable<String> {
		Connection testConnection = null;
		String query = null;
		boolean closeOnCompletionIsOverriden = false;

		subTestBug68916ConcurrentTask(Connection testConnection, String query, boolean closeOnCompletionIsOverriden) {
			this.testConnection = testConnection;
			this.query = query;
			this.closeOnCompletionIsOverriden = closeOnCompletionIsOverriden;
		}

		public String call() throws Exception {
			String threadName = Thread.currentThread().getName();
			long startTime = System.currentTimeMillis();
			long stopTime = startTime;
			StatementImpl testStatement = null;
			int count = 0;

			try {
				testStatement = (StatementImpl) testConnection.createStatement();
				testStatement.closeOnCompletion();

				System.out.println(threadName + " is executing: " + query);
				ResultSet testResultSet = testStatement.executeQuery(query);
				while (testResultSet.next()) {
					count++;
				}
				assertTrue(threadName + ": Query should return some values.", count > 0);
				assertFalse(threadName + ": Statement shouldn't be closed.", testStatement.isClosed());

				testResultSet.close(); // should close statement if not closeOnCompletionIsOverriden
				if (closeOnCompletionIsOverriden) {
					assertFalse(threadName + ": Statement shouldn't be closed.", testStatement.isClosed());
				} else {
					assertTrue(threadName + ": Statement should be closed.", testStatement.isClosed());
				}

			} catch (SQLException e) {
				e.printStackTrace();
				fail(threadName + ": Something went wrong, maybe Connection or Statement was closed before its time.");

			} finally {
				try {
					testStatement.close();
				} catch (SQLException e) {
				}
				stopTime = System.currentTimeMillis();
			}
			return threadName + ": processed " + count + " rows in " + (stopTime - startTime) + " milliseconds.";
		}
	}
}
