/*
 * Copyright (c) 2002, 2023, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package testsuite.regression;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.JdbcRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import com.mysql.cj.Messages;
import com.mysql.cj.MysqlConnection;
import com.mysql.cj.MysqlType;
import com.mysql.cj.conf.DefaultPropertySet;
import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm;
import com.mysql.cj.conf.PropertyDefinitions.SslMode;
import com.mysql.cj.conf.PropertyKey;
import com.mysql.cj.exceptions.CJCommunicationsException;
import com.mysql.cj.exceptions.ExceptionInterceptor;
import com.mysql.cj.exceptions.ExceptionInterceptorChain;
import com.mysql.cj.exceptions.MysqlErrorNumbers;
import com.mysql.cj.jdbc.JdbcConnection;
import com.mysql.cj.jdbc.MysqlSQLXML;
import com.mysql.cj.jdbc.ServerPreparedStatement;
import com.mysql.cj.jdbc.StatementImpl;
import com.mysql.cj.jdbc.exceptions.CommunicationsException;
import com.mysql.cj.jdbc.exceptions.MysqlDataTruncation;
import com.mysql.cj.jdbc.exceptions.NotUpdatable;
import com.mysql.cj.jdbc.result.ResultSetImpl;
import com.mysql.cj.jdbc.result.UpdatableResultSet;
import com.mysql.cj.log.Log;
import com.mysql.cj.protocol.InternalDate;
import com.mysql.cj.protocol.a.result.NativeResultset;
import com.mysql.cj.protocol.a.result.ResultsetRowsCursor;
import com.mysql.cj.protocol.a.result.ResultsetRowsStreaming;
import com.mysql.cj.result.SqlDateValueFactory;
import com.mysql.cj.util.StringUtils;
import com.mysql.cj.util.TimeUtil;
import com.mysql.cj.util.Util;

import testsuite.BaseTestCase;
import testsuite.BufferingLogger;

/**
 * Regression test cases for the ResultSet class.
 */
public class ResultSetRegressionTest extends BaseTestCase {

    /**
     * Tests fix for BUG#2359 -- Numeric types and server-side prepared statements incorrectly detect nulls.
     *
     * @throws Exception
     */
    @Test
    public void testBug2359() throws Exception {
        /*
         * this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug2359");
         * this.stmt.executeUpdate("CREATE TABLE testBug2359 (field1 INT)
         * TYPE=InnoDB"); this.stmt.executeUpdate("INSERT INTO testBug2359
         * VALUES (null), (1)");
         *
         * this.pstmt = this.conn.prepareStatement("SELECT field1 FROM
         * testBug2359 WHERE field1 IS NULL"); this.rs =
         * this.pstmt.executeQuery();
         *
         * assertTrue(this.rs.next());
         *
         * assertTrue(this.rs.getByte(1) == 0); assertTrue(this.rs.wasNull());
         *
         * assertTrue(this.rs.getShort(1) == 0); assertTrue(this.rs.wasNull());
         *
         * assertTrue(this.rs.getInt(1) == 0); assertTrue(this.rs.wasNull());
         *
         * assertTrue(this.rs.getLong(1) == 0); assertTrue(this.rs.wasNull());
         *
         * assertTrue(this.rs.getFloat(1) == 0); assertTrue(this.rs.wasNull());
         *
         * assertTrue(this.rs.getDouble(1) == 0); assertTrue(this.rs.wasNull());
         *
         * assertTrue(this.rs.getBigDecimal(1) == null);
         * assertTrue(this.rs.wasNull());
         *
         * this.rs.close();
         *
         * this.pstmt = this.conn.prepareStatement("SELECT max(field1) FROM
         * testBug2359 WHERE field1 IS NOT NULL"); this.rs =
         * this.pstmt.executeQuery(); assertTrue(this.rs.next());
         *
         * assertTrue(this.rs.getByte(1) == 1); assertTrue(!this.rs.wasNull());
         *
         * assertTrue(this.rs.getShort(1) == 1); assertTrue(!this.rs.wasNull());
         *
         * assertTrue(this.rs.getInt(1) == 1); assertTrue(!this.rs.wasNull());
         *
         * assertTrue(this.rs.getLong(1) == 1); assertTrue(!this.rs.wasNull());
         *
         * assertTrue(this.rs.getFloat(1) == 1); assertTrue(!this.rs.wasNull());
         *
         * assertTrue(this.rs.getDouble(1) == 1);
         * assertTrue(!this.rs.wasNull());
         *
         * assertTrue(this.rs.getBigDecimal(1) != null);
         * assertTrue(!this.rs.wasNull());
         */
        createTable("testBug2359_1", "(id INT)", "InnoDB");
        this.stmt.executeUpdate("INSERT INTO testBug2359_1 VALUES (1)");

        this.pstmt = this.conn.prepareStatement("SELECT max(id) FROM testBug2359_1");
        this.rs = this.pstmt.executeQuery();

        if (this.rs.next()) {
            assertTrue(this.rs.getInt(1) != 0);
            this.rs.close();
        }

        this.rs.close();
    }

    /**
     * Tests fix for BUG#2643, ClassCastException when using this.rs.absolute() and server-side prepared statements.
     *
     * @throws Exception
     */
    @Test
    public void testBug2623() throws Exception {
        PreparedStatement pStmt = null;

        try {
            pStmt = this.conn.prepareStatement("SELECT NOW()", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);

            this.rs = pStmt.executeQuery();

            this.rs.absolute(1);
        } finally {
            if (this.rs != null) {
                this.rs.close();
            }

            this.rs = null;

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

    /**
     * Tests fix for BUG#2654, "Column 'column.table' not found" when "order by" in query"
     *
     * @throws Exception
     */
    @Test
    public void testBug2654() throws Exception {
        createTable("foo", "(id tinyint(3) default NULL, data varchar(255) default NULL) DEFAULT CHARSET=latin1", "MyISAM ");
        this.stmt.executeUpdate("INSERT INTO foo VALUES (2,'male'), (1,'female') ");

        createTable("bar", "(id tinyint(3) unsigned default NULL, data char(3) default '0') DEFAULT CHARSET=latin1", "MyISAM ");

        this.stmt.executeUpdate("INSERT INTO bar VALUES (1,'no'), (2,'yes')");

        String statement = "select foo.id, foo.data, bar.data from foo, bar	where foo.id = bar.id order by foo.id";

        this.rs = this.stmt.executeQuery(statement);

        ResultSetMetaData rsmd = this.rs.getMetaData();
        assertEquals("foo", rsmd.getTableName(1));
        assertEquals("id", rsmd.getColumnName(1));

        this.rs.next();
        assertEquals("female", this.rs.getString("foo.data"));
        assertEquals("no", this.rs.getString("bar.data"));

        this.rs.next();
        assertEquals("male", this.rs.getString("foo.data"));
        assertEquals("yes", this.rs.getString("bar.data"));
    }

    /**
     * Tests for fix to BUG#1130
     *
     * @throws Exception
     */
    @Test
    public void testClobTruncate() throws Exception {
        createTable("testClobTruncate", "(field1 TEXT)");
        this.stmt.executeUpdate("INSERT INTO testClobTruncate VALUES ('abcdefg')");

        this.rs = this.stmt.executeQuery("SELECT * FROM testClobTruncate");
        this.rs.next();

        Clob clob = this.rs.getClob(1);
        clob.truncate(3);

        Reader reader = clob.getCharacterStream();
        char[] buf = new char[8];
        int charsRead = reader.read(buf);

        String clobAsString = new String(buf, 0, charsRead);

        assertTrue(clobAsString.equals("abc"));
    }

    /**
     * Tests that streaming result sets are registered correctly.
     *
     * @throws Exception
     */
    @Test
    public void testClobberStreamingRS() throws Exception {
        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.clobberStreamingResults.getKeyName(), "true");

            Connection clobberConn = getConnectionWithProps(props);

            Statement clobberStmt = clobberConn.createStatement();

            clobberStmt.executeUpdate("DROP TABLE IF EXISTS StreamingClobber");
            clobberStmt.executeUpdate("CREATE TABLE StreamingClobber ( DUMMYID INTEGER NOT NULL, DUMMYNAME VARCHAR(32),PRIMARY KEY (DUMMYID) )");
            clobberStmt.executeUpdate("INSERT INTO StreamingClobber (DUMMYID, DUMMYNAME) VALUES (0, NULL)");
            clobberStmt.executeUpdate("INSERT INTO StreamingClobber (DUMMYID, DUMMYNAME) VALUES (1, 'nro 1')");
            clobberStmt.executeUpdate("INSERT INTO StreamingClobber (DUMMYID, DUMMYNAME) VALUES (2, 'nro 2')");
            clobberStmt.executeUpdate("INSERT INTO StreamingClobber (DUMMYID, DUMMYNAME) VALUES (3, 'nro 3')");

            Statement streamStmt = null;

            try {
                streamStmt = clobberConn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
                streamStmt.setFetchSize(Integer.MIN_VALUE);

                this.rs = streamStmt.executeQuery("SELECT DUMMYID, DUMMYNAME FROM StreamingClobber ORDER BY DUMMYID");

                this.rs.next();

                // This should proceed normally, after the driver clears the input stream
                ResultSet rs2 = clobberStmt.executeQuery("SHOW VARIABLES");
                rs2.next();
                this.rs.close();
            } finally {
                if (streamStmt != null) {
                    streamStmt.close();
                }
            }
        } finally {
            this.stmt.executeUpdate("DROP TABLE IF EXISTS StreamingClobber");
        }
    }

    @Test
    public void testEmptyResultSetGet() throws Exception {
        try {
            this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'foo'");
            System.out.println(this.rs.getInt(1));
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()), "Correct exception not thrown");
        }
    }

    /**
     * Checks fix for BUG#1592 -- cross-database updatable result sets are not checked for updatability correctly.
     *
     * @throws Exception
     */
    @Test
    public void testBug1592() throws Exception {
        Statement updatableStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

        try {
            updatableStmt.execute("SELECT * FROM mysql.user");

            this.rs = updatableStmt.getResultSet();
        } catch (SQLException sqlEx) {
            String message = sqlEx.getMessage();

            if (message != null && message.indexOf("denied") != -1) {
                System.err.println("WARN: Can't complete testFixForBug1592(), access to 'mysql' database not allowed");
            } else {
                throw sqlEx;
            }
        }
    }

    /**
     * Tests fix for BUG#2006, where 2 columns with same name in a result set are returned via findColumn() in the wrong order...The JDBC spec states, that the
     * _first_ matching column should be returned.
     *
     * @throws Exception
     */
    @Test
    public void testBug2006() throws Exception {
        createTable("testFixForBug2006_1", "(key_field INT NOT NULL)");
        createTable("testFixForBug2006_2", "(key_field INT NULL)");
        this.stmt.executeUpdate("INSERT INTO testFixForBug2006_1 VALUES (1)");

        this.rs = this.stmt.executeQuery(
                "SELECT testFixForBug2006_1.key_field, testFixForBug2006_2.key_field FROM testFixForBug2006_1 LEFT JOIN testFixForBug2006_2 USING(key_field)");

        ResultSetMetaData rsmd = this.rs.getMetaData();

        assertTrue(rsmd.getColumnName(1).equals(rsmd.getColumnName(2)));
        assertTrue(rsmd.isNullable(this.rs.findColumn("key_field")) == ResultSetMetaData.columnNoNulls);
        assertTrue(rsmd.isNullable(2) == ResultSetMetaData.columnNullable);
        assertTrue(this.rs.next());
        assertTrue(this.rs.getObject(1) != null);
        assertTrue(this.rs.getObject(2) == null);
    }

    /**
     * Tests that ResultSet.getLong() does not truncate values.
     *
     * @throws Exception
     */
    @Test
    public void testGetLongBug() throws Exception {
        createTable("getLongBug", "(int_col int, bigint_col bigint)");

        int intVal = 123456;
        long longVal1 = 123456789012345678L;
        long longVal2 = -2079305757640172711L;
        this.stmt.executeUpdate("INSERT INTO getLongBug (int_col, bigint_col) VALUES (" + intVal + ", " + longVal1 + "), (" + intVal + ", " + longVal2 + ")");

        this.rs = this.stmt.executeQuery("SELECT int_col, bigint_col FROM getLongBug ORDER BY bigint_col DESC");
        this.rs.next();
        assertTrue(this.rs.getInt(1) == intVal && this.rs.getLong(2) == longVal1, "Values not decoded correctly");
        this.rs.next();
        assertTrue(this.rs.getInt(1) == intVal && this.rs.getLong(2) == longVal2, "Values not decoded correctly");
    }

    @Test
    public void testGetTimestampWithDate() throws Exception {
        createTable("testGetTimestamp", "(d date)");
        this.stmt.executeUpdate("INSERT INTO testGetTimestamp values (now())");

        this.rs = this.stmt.executeQuery("SELECT * FROM testGetTimestamp");
        this.rs.next();
        System.out.println(this.rs.getTimestamp(1));
    }

    /**
     * Tests a bug where ResultSet.isBefireFirst() would return true when the result set was empty (which is incorrect)
     *
     * @throws Exception
     */
    @Test
    public void testIsBeforeFirstOnEmpty() throws Exception {
        // Query with valid rows: isBeforeFirst() correctly returns True
        this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'version'");
        assertTrue(this.rs.isBeforeFirst(), "Non-empty search should return true");

        // Query with empty result: isBeforeFirst() falsely returns True. Sun's documentation says it should return false
        this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'garbage'");
        assertTrue(!this.rs.isBeforeFirst(), "Empty search should return false ");
    }

    /**
     * Tests a bug where ResultSet.isBefireFirst() would return true when the result set was empty (which is incorrect)
     *
     * @throws Exception
     */
    @Test
    public void testMetaDataIsWritable() throws Exception {
        // Query with valid rows
        this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'version'");

        ResultSetMetaData rsmd = this.rs.getMetaData();

        int numColumns = rsmd.getColumnCount();

        for (int i = 1; i <= numColumns; i++) {
            assertTrue(rsmd.isWritable(i) != rsmd.isReadOnly(i), "rsmd.isWritable() should != rsmd.isReadOnly()");
        }
    }

    /**
     * Tests fix for bug # 496
     *
     * @throws Exception
     */
    @Test
    public void testNextAndPrevious() throws Exception {
        createTable("testNextAndPrevious", "(field1 int)");
        this.stmt.executeUpdate("INSERT INTO testNextAndPrevious VALUES (1)");

        this.rs = this.stmt.executeQuery("SELECT * from testNextAndPrevious");

        System.out.println("Currently at row " + this.rs.getRow());
        this.rs.next();
        System.out.println("Value at row " + this.rs.getRow() + " is " + this.rs.getString(1));

        this.rs.previous();

        assertThrows("Should not be able to retrieve values with invalid cursor.", SQLException.class, "Before start.*", () -> {
            System.out.println("Value at row " + this.rs.getRow() + " is " + this.rs.getString(1));
            return null;
        });

        this.rs.next();

        this.rs.next();

        assertThrows("Should not be able to retrieve values with invalid cursor.", SQLException.class, "After end.*", () -> {
            System.out.println("Value at row " + this.rs.getRow() + " is " + this.rs.getString(1));
            return null;
        });
    }

    /**
     * Tests fix for BUG#1630 (not updatable exception turning into NPE on second updateFoo() method call.
     *
     * @throws Exception
     */
    @Test
    public void testNotUpdatable() throws Exception {
        this.rs = null;

        String sQuery = "SHOW VARIABLES";
        this.pstmt = this.conn.prepareStatement(sQuery, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

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

        if (this.rs.next()) {
            this.rs.absolute(1);

            try {
                this.rs.updateInt(1, 1);
            } catch (SQLException sqlEx) {
                assertTrue(sqlEx instanceof NotUpdatable);
            }

            try {
                this.rs.updateString(1, "1");
            } catch (SQLException sqlEx) {
                assertTrue(sqlEx instanceof NotUpdatable);
            }
        }
    }

    /**
     * Tests that streaming result sets are registered correctly.
     *
     * @throws Exception
     */
    @Test
    public void testStreamingRegBug() throws Exception {
        createTable("StreamingRegBug", "( DUMMYID INTEGER NOT NULL, DUMMYNAME VARCHAR(32),PRIMARY KEY (DUMMYID) )");
        this.stmt.executeUpdate("INSERT INTO StreamingRegBug (DUMMYID, DUMMYNAME) VALUES (0, NULL)");
        this.stmt.executeUpdate("INSERT INTO StreamingRegBug (DUMMYID, DUMMYNAME) VALUES (1, 'nro 1')");
        this.stmt.executeUpdate("INSERT INTO StreamingRegBug (DUMMYID, DUMMYNAME) VALUES (2, 'nro 2')");
        this.stmt.executeUpdate("INSERT INTO StreamingRegBug (DUMMYID, DUMMYNAME) VALUES (3, 'nro 3')");

        PreparedStatement streamStmt = null;

        try {
            streamStmt = this.conn.prepareStatement("SELECT DUMMYID, DUMMYNAME FROM StreamingRegBug ORDER BY DUMMYID", java.sql.ResultSet.TYPE_FORWARD_ONLY,
                    java.sql.ResultSet.CONCUR_READ_ONLY);
            streamStmt.setFetchSize(Integer.MIN_VALUE);

            this.rs = streamStmt.executeQuery();

            while (this.rs.next()) {
                this.rs.getString(1);
            }

            this.rs.close(); // error occurs here
        } catch (SQLException sqlEx) {

        } finally {
            if (streamStmt != null) {
                try {
                    streamStmt.close();
                } catch (SQLException exWhileClose) {
                    exWhileClose.printStackTrace();
                }
            }
        }
    }

    /**
     * Tests that result sets can be updated when all parameters are correctly set.
     *
     * @throws Exception
     */
    @Test
    public void testUpdatability() throws Exception {
        this.rs = null;

        createTable("updatabilityBug",
                "(id int(10) unsigned NOT NULL auto_increment, field1 varchar(32) NOT NULL default '',"
                        + " field2 varchar(128) NOT NULL default '', field3 varchar(128) default NULL, field4 varchar(128) default NULL,"
                        + " field5 varchar(64) default NULL, field6 int(10) unsigned default NULL, field7 varchar(64) default NULL, PRIMARY KEY  (id)) ",
                "InnoDB");
        this.stmt.executeUpdate("insert into updatabilityBug (id) values (1)");

        String sQuery = " SELECT * FROM updatabilityBug WHERE id = ? ";
        this.pstmt = this.conn.prepareStatement(sQuery, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        this.conn.setAutoCommit(false);
        this.pstmt.setInt(1, 1);
        this.rs = this.pstmt.executeQuery();

        if (this.rs.next()) {
            this.rs.absolute(1);
            this.rs.updateInt("id", 1);
            this.rs.updateString("field1", "1");
            this.rs.updateString("field2", "1");
            this.rs.updateString("field3", "1");
            this.rs.updateString("field4", "1");
            this.rs.updateString("field5", "1");
            this.rs.updateInt("field6", 1);
            this.rs.updateString("field7", "1");
            this.rs.updateRow();
        }

        this.conn.commit();
        this.conn.setAutoCommit(true);
    }

    /**
     * Test fixes for BUG#1071
     *
     * @throws Exception
     */
    @Test
    public void testUpdatabilityAndEscaping() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.characterEncoding.getKeyName(), "big5");

        Connection updConn = getConnectionWithProps(props);
        Statement updStmt = updConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

        try {
            updStmt.executeUpdate("DROP TABLE IF EXISTS testUpdatesWithEscaping");
            updStmt.executeUpdate("CREATE TABLE testUpdatesWithEscaping (field1 INT PRIMARY KEY, field2 VARCHAR(64))");
            updStmt.executeUpdate("INSERT INTO testUpdatesWithEscaping VALUES (1, null)");

            String stringToUpdate = "\" \\ '";

            this.rs = updStmt.executeQuery("SELECT * from testUpdatesWithEscaping");

            this.rs.next();
            this.rs.updateString(2, stringToUpdate);
            this.rs.updateRow();

            assertTrue(stringToUpdate.equals(this.rs.getString(2)));
        } finally {
            updStmt.executeUpdate("DROP TABLE IF EXISTS testUpdatesWithEscaping");
            updStmt.close();
            updConn.close();
        }
    }

    /**
     * Tests the fix for BUG#661 ... refreshRow() fails when primary key values have escaped data in them.
     *
     * @throws Exception
     */
    @Test
    public void testUpdatabilityWithQuotes() throws Exception {
        Statement updStmt = null;

        try {
            createTable("testUpdWithQuotes", "(keyField CHAR(32) PRIMARY KEY NOT NULL, field2 int)");

            PreparedStatement pStmt = this.conn.prepareStatement("INSERT INTO testUpdWithQuotes VALUES (?, ?)");
            pStmt.setString(1, "Abe's");
            pStmt.setInt(2, 1);
            pStmt.executeUpdate();

            updStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

            this.rs = updStmt.executeQuery("SELECT * FROM testUpdWithQuotes");
            this.rs.next();
            this.rs.updateInt(2, 2);
            this.rs.updateRow();
        } finally {
            if (updStmt != null) {
                updStmt.close();
            }

            updStmt = null;
        }
    }

    /**
     * Checks whether or not ResultSet.updateClob() is implemented
     *
     * @throws Exception
     */
    @Test
    public void testUpdateClob() throws Exception {
        Statement updatableStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        createTable("testUpdateClob", "(intField INT NOT NULL PRIMARY KEY, clobField TEXT)");
        this.stmt.executeUpdate("INSERT INTO testUpdateClob VALUES (1, 'foo')");

        this.rs = updatableStmt.executeQuery("SELECT intField, clobField FROM testUpdateClob");
        this.rs.next();

        Clob clob = this.rs.getClob(2);

        clob.setString(1, "bar");

        this.rs.updateClob("clobField", clob);
        this.rs.updateRow();

        this.rs.moveToInsertRow();

        clob.setString(1, "baz");
        this.rs.updateInt(1, 2);
        this.rs.updateClob(2, clob);
        this.rs.insertRow();

        clob.setString(1, "bat");
        this.rs.updateInt(1, 3);
        this.rs.updateClob(2, clob);
        this.rs.insertRow();

        this.rs.close();

        this.rs = this.stmt.executeQuery("SELECT intField, clobField FROM testUpdateClob ORDER BY intField");

        this.rs.next();
        assertTrue(this.rs.getInt(1) == 1 && this.rs.getString(2).equals("bar"));

        this.rs.next();
        assertTrue(this.rs.getInt(1) == 2 && this.rs.getString(2).equals("baz"));

        this.rs.next();
        assertTrue(this.rs.getInt(1) == 3 && this.rs.getString(2).equals("bat"));
    }

    /**
     * Tests fix for BUG#4482, ResultSet.getObject() returns wrong type for
     * strings when using prepared statements.
     *
     * @throws Exception
     */
    @Test
    public void testBug4482() throws Exception {
        this.rs = this.conn.prepareStatement("SELECT 'abcdef'").executeQuery();
        assertTrue(this.rs.next());
        assertTrue(this.rs.getObject(1) instanceof String);
    }

    /**
     * Test fix for BUG#4689 - WasNull not getting set correctly for binary result sets.
     *
     * @throws Exception
     */
    @Test
    public void testBug4689() throws Exception {
        createTable("testBug4689", "(tinyintField tinyint, tinyintFieldNull tinyint, intField int, intFieldNull int, "
                + "bigintField bigint, bigintFieldNull bigint, shortField smallint, shortFieldNull smallint, doubleField double, doubleFieldNull double)");

        this.stmt.executeUpdate("INSERT INTO testBug4689 VALUES (1, null, 1, null, 1, null, 1, null, 1, null)");

        PreparedStatement pStmt = this.conn.prepareStatement("SELECT tinyintField, tinyintFieldNull, intField, intFieldNull, "
                + "bigintField, bigintFieldNull, shortField, shortFieldNull, doubleField, doubleFieldNull FROM testBug4689");
        this.rs = pStmt.executeQuery();
        assertTrue(this.rs.next());

        assertTrue(this.rs.getByte(1) == 1);
        assertTrue(!this.rs.wasNull());
        assertTrue(this.rs.getByte(2) == 0);
        assertTrue(this.rs.wasNull());

        assertTrue(this.rs.getInt(3) == 1);
        assertTrue(!this.rs.wasNull());
        assertTrue(this.rs.getInt(4) == 0);
        assertTrue(this.rs.wasNull());

        assertTrue(this.rs.getInt(5) == 1);
        assertTrue(!this.rs.wasNull());
        assertTrue(this.rs.getInt(6) == 0);
        assertTrue(this.rs.wasNull());

        assertTrue(this.rs.getShort(7) == 1);
        assertTrue(!this.rs.wasNull());
        assertTrue(this.rs.getShort(8) == 0);
        assertTrue(this.rs.wasNull());

        assertTrue(this.rs.getDouble(9) == 1);
        assertTrue(!this.rs.wasNull());
        assertTrue(this.rs.getDouble(10) == 0);
        assertTrue(this.rs.wasNull());
    }

    /**
     * Tests fix for BUG#5032 -- ResultSet.getObject() doesn't return type Boolean for pseudo-bit types from prepared statements on 4.1.x.
     *
     * @throws Exception
     */
    @Test
    public void testBug5032() throws Exception {
        createTable("testBug5032", "(field1 BIT)");
        this.stmt.executeUpdate("INSERT INTO testBug5032 VALUES (1)");

        this.pstmt = this.conn.prepareStatement("SELECT field1 FROM testBug5032");
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        assertTrue(this.rs.getObject(1) instanceof Boolean);
    }

    /**
     * Tests fix for BUG#5069 -- ResultSet.getMetaData() should not return incorrectly-initialized metadata if the result set has been closed, but should
     * instead throw a SQLException. Also tests fix for getRow() and getWarnings() and traversal methods.
     *
     * @throws Exception
     */
    @Test
    public void testBug5069() throws Exception {
        this.rs = this.stmt.executeQuery("SELECT 1");
        this.rs.close();

        try {
            @SuppressWarnings("unused")
            ResultSetMetaData md = this.rs.getMetaData();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.getRow();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.getWarnings();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.first();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.beforeFirst();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.last();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.afterLast();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.relative(0);
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.next();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.previous();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.isBeforeFirst();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.isFirst();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.isAfterLast();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }

        try {
            this.rs.isLast();
        } catch (NullPointerException npEx) {
            fail("Should not catch NullPointerException here");
        } catch (SQLException sqlEx) {
            assertTrue(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR.equals(sqlEx.getSQLState()));
        }
    }

    /**
     * Tests for BUG#5136, GEOMETRY types getting corrupted, turns out to be a server bug.
     *
     * @throws Exception
     */
    @Test
    public void testBug5136() throws Exception {
        boolean useSTfunctions = versionMeetsMinimum(5, 6);

        PreparedStatement toGeom = this.conn.prepareStatement(useSTfunctions ? "select ST_GeomFromText(?)" : "select GeomFromText(?)");
        PreparedStatement toText = this.conn.prepareStatement(useSTfunctions ? "select ST_AsText(?)" : "select AsText(?)");

        String inText = "POINT(146.67596278 -36.54368233)";

        // First assert that the problem is not at the server end
        this.rs = this.stmt
                .executeQuery(useSTfunctions ? "select ST_AsText(ST_GeomFromText('" + inText + "'))" : "select AsText(GeomFromText('" + inText + "'))");
        this.rs.next();

        String outText = this.rs.getString(1);
        this.rs.close();
        assertTrue(inText.equals(outText), "Server side only\n In: " + inText + "\nOut: " + outText);

        // Now bring a binary geometry object to the client and send it back
        toGeom.setString(1, inText);
        this.rs = toGeom.executeQuery();
        this.rs.next();

        // Return a binary geometry object from the WKT
        Object geom = this.rs.getObject(1);
        this.rs.close();
        toText.setObject(1, geom);
        this.rs = toText.executeQuery();
        this.rs.next();

        // Return WKT from the binary geometry
        outText = this.rs.getString(1);
        this.rs.close();
        assertTrue(inText.equals(outText), "Server to client and back\n In: " + inText + "\nOut: " + outText);
    }

    /**
     * Tests fix for BUG#5664, ResultSet.updateByte() when on insert row throws ArrayOutOfBoundsException.
     *
     * @throws Exception
     */
    @Test
    public void testBug5664() throws Exception {
        createTable("testBug5664", "(pkfield int PRIMARY KEY NOT NULL, field1 SMALLINT)");
        this.stmt.executeUpdate("INSERT INTO testBug5664 VALUES (1, 1)");

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

        this.rs = updatableStmt.executeQuery("SELECT pkfield, field1 FROM testBug5664");
        this.rs.next();
        this.rs.moveToInsertRow();
        this.rs.updateInt(1, 2);
        this.rs.updateByte(2, (byte) 2);
    }

    @Test
    public void testBogusTimestampAsString() throws Exception {
        ResultSet rs1 = this.stmt.executeQuery("SELECT '2004-08-13 13:21:17.'");
        rs1.next();

        // We're only checking for an exception being thrown here as the bug
        assertThrows(SQLDataException.class, "Cannot convert string '2004-08-13 13:21:17.' to java.sql.Timestamp value", () -> {
            rs1.getTimestamp(1);
            return null;
        });
    }

    /**
     * Tests our ability to reject NaN and +/- INF in PreparedStatement.setDouble();
     *
     * @throws Exception
     */
    @Test
    public void testBug5717() throws Exception {
        createTable("testBug5717", "(field1 DOUBLE)");
        this.pstmt = this.conn.prepareStatement("INSERT INTO testBug5717 VALUES (?)");

        assertThrows(Exception.class, () -> {
            this.pstmt.setDouble(1, Double.NEGATIVE_INFINITY);
            return null;
        });
        assertThrows(Exception.class, () -> {
            this.pstmt.setDouble(1, Double.POSITIVE_INFINITY);
            return null;
        });
        assertThrows(Exception.class, () -> {
            this.pstmt.setDouble(1, Double.NaN);
            return null;
        });
    }

    /**
     * Tests fix for server issue that drops precision on aggregate operations on DECIMAL types, because they come back as DOUBLEs.
     *
     * @throws Exception
     */
    @Test
    @SuppressWarnings("deprecation")
    public void testBug6537() throws Exception {
        String tableName = "testBug6537";

        createTable(tableName, "(`id` int(11) NOT NULL default '0', `value` decimal(10,2) NOT NULL default '0.00', `stringval` varchar(10),"
                + "PRIMARY KEY  (`id`)) DEFAULT CHARSET=latin1", "MyISAM");
        this.stmt.executeUpdate("INSERT INTO " + tableName + "(id, value, stringval) VALUES (1, 100.00, '100.00'), (2, 200, '200')");

        String sql = "SELECT SUM(value) as total FROM " + tableName + " WHERE id = ? ";
        PreparedStatement pStmt = this.conn.prepareStatement(sql);
        pStmt.setInt(1, 1);
        this.rs = pStmt.executeQuery();
        assertTrue(this.rs.next());

        assertTrue("100.00".equals(this.rs.getBigDecimal("total").toString()));

        sql = "SELECT stringval as total FROM " + tableName + " WHERE id = ? ";
        pStmt = this.conn.prepareStatement(sql);
        pStmt.setInt(1, 2);
        this.rs = pStmt.executeQuery();
        assertTrue(this.rs.next());

        assertEquals("200.00", this.rs.getBigDecimal("total", 2).toString());
    }

    /**
     * Tests fix for BUG#6231, ResultSet.getTimestamp() on a column with TIME in it fails.
     *
     * @throws Exception
     */
    @Test
    public void testBug6231() throws Exception {
        createTable("testBug6231", "(field1 TIME)");
        this.stmt.executeUpdate("INSERT INTO testBug6231 VALUES ('09:16:00')");

        this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug6231");
        this.rs.next();
        long asMillis = this.rs.getTimestamp(1).getTime();
        Calendar cal = Calendar.getInstance();

        cal.setTimeInMillis(asMillis);

        assertEquals(9, cal.get(Calendar.HOUR));
        assertEquals(16, cal.get(Calendar.MINUTE));
        assertEquals(0, cal.get(Calendar.SECOND));
    }

    @Test
    public void testBug6619() throws Exception {
        createTable("testBug6619", "(field1 int)");
        this.stmt.executeUpdate("INSERT INTO testBug6619 VALUES (1), (2)");

        PreparedStatement pStmt = this.conn.prepareStatement("SELECT SUM(field1) FROM testBug6619");

        this.rs = pStmt.executeQuery();
        this.rs.next();
        System.out.println(this.rs.getString(1));
    }

    @Test
    public void testBug6743() throws Exception {
        // 0x835C U+30BD # KATAKANA LETTER SO
        String katakanaStr = "\u30BD";

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS");

        Connection sjisConn = null;
        Statement sjisStmt = null;

        try {
            sjisConn = getConnectionWithProps(props);
            sjisStmt = sjisConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

            sjisStmt.executeUpdate("DROP TABLE IF EXISTS testBug6743");
            StringBuilder queryBuf = new StringBuilder("CREATE TABLE testBug6743 (pkField INT NOT NULL PRIMARY KEY, field1 VARCHAR(32)");

            queryBuf.append(" CHARACTER SET SJIS");

            queryBuf.append(")");
            sjisStmt.executeUpdate(queryBuf.toString());
            sjisStmt.executeUpdate("INSERT INTO testBug6743 VALUES (1, 'abc')");

            this.rs = sjisStmt.executeQuery("SELECT pkField, field1 FROM testBug6743");
            this.rs.next();
            this.rs.updateString(2, katakanaStr);
            this.rs.updateRow();

            String retrString = this.rs.getString(2);
            assertTrue(katakanaStr.equals(retrString));

            this.rs = sjisStmt.executeQuery("SELECT pkField, field1 FROM testBug6743");
            this.rs.next();

            retrString = this.rs.getString(2);
            assertTrue(katakanaStr.equals(retrString));
        } finally {
            this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug6743");

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

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

    /**
     * Tests for presence of BUG#6561, NPE thrown when dealing with 0 dates and non-unpacked result sets.
     *
     * @throws Exception
     */
    @Test
    public void testBug6561() throws Exception {
        Connection testConn = this.conn;
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL");
        Connection zeroConn = getConnectionWithProps(props);
        try {
            if (versionMeetsMinimum(5, 7, 4)) {
                Properties props2 = new Properties();
                props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
                props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
                props2.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false");
                if (versionMeetsMinimum(5, 7, 5)) {
                    String sqlMode = getMysqlVariable("sql_mode");
                    if (sqlMode.contains("STRICT_TRANS_TABLES")) {
                        sqlMode = removeSqlMode("STRICT_TRANS_TABLES", sqlMode);
                        props2.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'");
                    }
                }
                testConn = getConnectionWithProps(props2);
                this.stmt = testConn.createStatement();
            }

            createTable("testBug6561", "(ofield int, field1 DATE, field2 integer, field3 integer)");
            this.stmt.executeUpdate("INSERT INTO testBug6561 (ofield, field1,field2,field3)	VALUES (1, 0,NULL,0)");
            this.stmt.executeUpdate("INSERT INTO testBug6561 (ofield, field1,field2,field3) VALUES (2, '2004-11-20',NULL,0)");

            PreparedStatement ps = zeroConn.prepareStatement("SELECT field1,field2,field3 FROM testBug6561 ORDER BY ofield");
            this.rs = ps.executeQuery();

            assertTrue(this.rs.next());
            assertNull(this.rs.getObject("field1"));
            assertNull(this.rs.getObject("field2"));
            assertEquals(0, this.rs.getInt("field3"));

            assertTrue(this.rs.next());
            assertEquals("2004-11-20", this.rs.getString("field1"));
            assertNull(this.rs.getObject("field2"));
            assertEquals(0, this.rs.getInt("field3"));

            ps.close();
        } finally {
            zeroConn.close();
            if (testConn != this.conn) {
                testConn.close();
            }
        }
    }

    @Test
    public void testBug7686() throws SQLException {
        String tableName = "testBug7686";
        createTable(tableName, "(id1 int(10) unsigned NOT NULL, id2 DATETIME, field1 varchar(128) NOT NULL default '', PRIMARY KEY  (id1, id2))", "InnoDB;");

        this.stmt.executeUpdate("insert into " + tableName + " (id1, id2, field1) values (1, '2005-01-05 13:59:20', 'foo')");

        String sQuery = " SELECT * FROM " + tableName + " WHERE id1 = ? AND id2 = ?";
        this.pstmt = this.conn.prepareStatement(sQuery, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

        TimeZone serverTz = ((MysqlConnection) this.conn).getSession().getServerSession().getSessionTimeZone();

        this.conn.setAutoCommit(false);
        this.pstmt.setInt(1, 1);
        GregorianCalendar cal = new GregorianCalendar(serverTz);
        cal.clear();
        cal.set(2005, 00, 05, 13, 59, 20);

        Timestamp jan5before2pm = new java.sql.Timestamp(cal.getTimeInMillis());

        this.pstmt.setTimestamp(2, jan5before2pm);
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        this.rs.absolute(1);
        this.rs.updateString("field1", "bar");
        this.rs.updateRow();
        this.conn.commit();
        this.conn.setAutoCommit(true);
    }

    /**
     * Tests fix for BUG#7715 - Timestamps converted incorrectly to strings with SSPS and Upd. Result Sets.
     *
     * @throws Exception
     */
    @Test
    public void testBug7715() throws Exception {
        PreparedStatement pStmt = null;

        createTable("testConvertedBinaryTimestamp", "(field1 VARCHAR(32), field2 VARCHAR(32), field3 VARCHAR(32), field4 TIMESTAMP)");
        this.stmt.executeUpdate("INSERT INTO testConvertedBinaryTimestamp VALUES ('abc', 'def', 'ghi', NOW())");

        pStmt = this.conn.prepareStatement("SELECT field1, field2, field3, field4 FROM testConvertedBinaryTimestamp", ResultSet.TYPE_SCROLL_SENSITIVE,
                ResultSet.CONCUR_UPDATABLE);

        this.rs = pStmt.executeQuery();
        assertTrue(this.rs.next());

        this.rs.getObject(4); // fails if bug exists
    }

    /**
     * Tests fix for BUG#8428 - getString() doesn't maintain format stored on server.
     *
     * @throws Exception
     */
    @Test
    public void testBug8428() throws Exception {
        Connection noSyncConn = null;

        createTable("testBug8428", "(field1 YEAR, field2 DATETIME)");
        this.stmt.executeUpdate("INSERT INTO testBug8428 VALUES ('1999', '2005-02-11 12:54:41')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.noDatetimeStringSync.getKeyName(), "true");
        props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true");
        props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "false");

        noSyncConn = getConnectionWithProps(props);

        this.rs = noSyncConn.createStatement().executeQuery("SELECT field1, field2 FROM testBug8428");
        this.rs.next();
        assertEquals("1999", this.rs.getString(1));
        assertEquals("2005-02-11 12:54:41", this.rs.getString(2));

        this.rs = noSyncConn.prepareStatement("SELECT field1, field2 FROM testBug8428").executeQuery();
        this.rs.next();
        assertEquals("1999", this.rs.getString(1));
        assertEquals("2005-02-11 12:54:41", this.rs.getString(2));
    }

    /**
     * Tests fix for Bug#8868, DATE_FORMAT() queries returned as BLOBs from getObject().
     *
     * @throws Exception
     */
    @Test
    public void testBug8868() throws Exception {
        createTable("testBug8868", "(field1 DATE, field2 VARCHAR(32) CHARACTER SET BINARY)");
        this.stmt.executeUpdate("INSERT INTO testBug8868 VALUES (NOW(), 'abcd')");
        this.rs = this.stmt.executeQuery("SELECT DATE_FORMAT(field1,'%b-%e %l:%i%p') as fmtddate, field2 FROM testBug8868");
        this.rs.next();
        assertEquals("java.lang.String", this.rs.getObject(1).getClass().getName());
    }

    /**
     * Tests fix for BUG#9098 - Server doesn't give us info to distinguish between CURRENT_TIMESTAMP and 'CURRENT_TIMESTAMP' for default values.
     *
     * @throws Exception
     */
    @Test
    public void testBug9098() throws Exception {
        Statement updatableStmt = null;

        createTable("testBug9098", "(pkfield INT PRIMARY KEY NOT NULL AUTO_INCREMENT, \n"
                + "tsfield TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, tsfield2 TIMESTAMP NOT NULL DEFAULT '2005-12-25 12:20:52', charfield VARCHAR(4) NOT NULL DEFAULT 'abcd')");

        for (boolean populateWithDefaults : new boolean[] { false, true }) {
            try {
                ((JdbcConnection) this.conn).getPropertySet().getBooleanProperty(PropertyKey.populateInsertRowWithDefaultValues).setValue(populateWithDefaults);
                updatableStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
                this.rs = updatableStmt.executeQuery("SELECT pkfield, tsfield, tsfield2, charfield FROM testBug9098");
                this.rs.moveToInsertRow();
                this.rs.insertRow();
            } finally {
                this.stmt.execute("TRUNCATE TABLE testBug9098");
            }
        }
    }

    /**
     * Tests fix for BUG#9236, a continuation of BUG#8868, where functions used in queries that should return non-string types when resolved by temporary tables
     * suddenly become opaque binary strings (work-around for server limitation)
     *
     * @throws Exception
     */
    @Test
    public void testBug9236() throws Exception {
        Connection testConn = this.conn;
        try {
            if (versionMeetsMinimum(5, 7, 4)) {
                Properties props = new Properties();
                props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false");
                if (versionMeetsMinimum(5, 7, 5)) {
                    String sqlMode = getMysqlVariable("sql_mode");
                    if (sqlMode.contains("STRICT_TRANS_TABLES")) {
                        sqlMode = removeSqlMode("STRICT_TRANS_TABLES", sqlMode);
                        props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'");
                    }
                }
                testConn = getConnectionWithProps(props);
                this.stmt = testConn.createStatement();
            }

            createTable("testBug9236",
                    "(field_1 int(18) NOT NULL auto_increment, field_2 varchar(50) NOT NULL default '',"
                            + "field_3 varchar(12) default NULL, field_4 int(18) default NULL, field_5 int(18) default NULL,"
                            + "field_6 datetime default NULL, field_7 varchar(30) default NULL, field_8 varchar(50) default NULL,"
                            + "field_9 datetime default NULL, field_10 int(18) NOT NULL default '0', field_11 int(18) default NULL,"
                            + "field_12 datetime NOT NULL default '0000-00-00 00:00:00', PRIMARY KEY  (field_1), KEY (field_4), KEY (field_2),"
                            + "KEY (field_3), KEY (field_7,field_1), KEY (field_5), KEY (field_6,field_10,field_9), KEY (field_11,field_10),"
                            + "KEY (field_12,field_10)) DEFAULT CHARSET=latin1",
                    "InnoDB");

            this.stmt.executeUpdate("INSERT INTO testBug9236 VALUES "
                    + "(1,'0',NULL,-1,0,'0000-00-00 00:00:00','123456789','-1','2004-03-13 14:21:38',0,NULL,'2004-03-13 14:21:38'),"
                    + "(2,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'1',NULL,0,NULL,'2004-07-13 14:29:52'),"
                    + "(3,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'2',NULL,0,NULL,'2004-07-16 13:20:51'),"
                    + "(4,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'3','2004-07-16 13:43:39',0,NULL,'2004-07-16 13:22:01'),"
                    + "(5,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'4','2004-07-16 13:23:48',0,NULL,'2004-07-16 13:23:01'),"
                    + "(6,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'5',NULL,0,NULL,'2004-07-16 14:41:07'),"
                    + "(7,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'6',NULL,0,NULL,'2004-07-16 14:41:34'),"
                    + "(8,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'7',NULL,0,NULL,'2004-07-16 14:41:54'),"
                    + "(9,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'8',NULL,0,NULL,'2004-07-16 14:42:42'),"
                    + "(10,'0','PI',1,0,'0000-00-00 00:00:00',NULL,'9',NULL,0,NULL,'2004-07-18 10:51:30'),"
                    + "(11,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'10','2004-07-23 17:23:06',0,NULL,'2004-07-23 17:18:19'),"
                    + "(12,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'11','2004-07-23 17:24:45',0,NULL,'2004-07-23 17:23:57'),"
                    + "(13,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'12','2004-07-23 17:30:51',0,NULL,'2004-07-23 17:30:15'),"
                    + "(14,'0',NULL,1,0,'0000-00-00 00:00:00',NULL,'13','2004-07-26 17:50:19',0,NULL,'2004-07-26 17:49:38'),"
                    + "(15,'0','FRL',1,0,'0000-00-00 00:00:00',NULL,'1',NULL,0,NULL,'2004-08-19 18:29:18'),"
                    + "(16,'0','FRL',1,0,'0000-00-00 00:00:00',NULL,'15',NULL,0,NULL,'2005-03-16 12:08:28')");

            createTable("testBug9236_1", "(field1 CHAR(2) CHARACTER SET BINARY)");
            this.stmt.executeUpdate("INSERT INTO testBug9236_1 VALUES ('ab')");
            this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug9236_1");

            ResultSetMetaData rsmd = this.rs.getMetaData();
            assertEquals("[B", rsmd.getColumnClassName(1));
            assertTrue(this.rs.next());
            Object asObject = this.rs.getObject(1);
            assertEquals("[B", asObject.getClass().getName());

            this.rs = this.stmt.executeQuery(
                    "select DATE_FORMAT(field_12, '%Y-%m-%d') as date, count(*) as count from testBug9236 where field_10 = 0 and field_3 = 'FRL' and field_12 >= '2005-03-02 00:00:00' and field_12 <= '2005-03-17 00:00:00' group by date");
            rsmd = this.rs.getMetaData();
            assertEquals("java.lang.String", rsmd.getColumnClassName(1));
            this.rs.next();
            asObject = this.rs.getObject(1);
            assertEquals("java.lang.String", asObject.getClass().getName());

            this.rs.close();

            createTable("testBug8868_2", "(field1 CHAR(4) CHARACTER SET BINARY)");
            this.stmt.executeUpdate("INSERT INTO testBug8868_2 VALUES ('abc')");
            this.rs = this.stmt.executeQuery("SELECT field1 FROM testBug8868_2");

            rsmd = this.rs.getMetaData();
            assertEquals("[B", rsmd.getColumnClassName(1));
            this.rs.next();
            asObject = this.rs.getObject(1);
            assertEquals("[B", asObject.getClass().getName());
        } finally {
            if (testConn != this.conn) {
                testConn.close();
            }
        }
    }

    /**
     * Tests fix for BUG#9437, IF() returns type of [B or java.lang.String depending on platform. Fixed earlier, but in here to catch if it ever regresses.
     *
     * @throws Exception
     */
    @Test
    public void testBug9437() throws Exception {
        String tableName = "testBug9437";

        createTable(tableName,
                "(languageCode char(2) NOT NULL default '', countryCode char(2) NOT NULL default '',"
                        + "supported enum('no','yes') NOT NULL default 'no', ordering int(11) default NULL,"
                        + "createDate datetime NOT NULL default '1000-01-01 00:00:03', modifyDate timestamp NOT NULL default CURRENT_TIMESTAMP on update"
                        + " CURRENT_TIMESTAMP, PRIMARY KEY  (languageCode,countryCode), KEY languageCode (languageCode),"
                        + "KEY countryCode (countryCode), KEY ordering (ordering), KEY modifyDate (modifyDate)) DEFAULT CHARSET=utf8",
                "InnoDB");

        this.stmt.executeUpdate("INSERT INTO " + tableName + " (languageCode) VALUES ('en')");

        String alias = "someLocale";
        String sql = "select if ( languageCode = ?, ?, ? ) as " + alias + " from " + tableName;
        this.pstmt = this.conn.prepareStatement(sql);

        int count = 1;
        this.pstmt.setObject(count++, "en");
        this.pstmt.setObject(count++, "en_US");
        this.pstmt.setObject(count++, "en_GB");

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

        assertTrue(this.rs.next());

        Object object = this.rs.getObject(alias);

        if (object != null) {
            assertEquals("java.lang.String", object.getClass().getName());
            assertEquals("en_US", object.toString());
        }
    }

    @Test
    public void testBug9684() throws Exception {
        String tableName = "testBug9684";

        createTable(tableName, "(sourceText text character set utf8 collate utf8_bin)");
        this.stmt.executeUpdate("INSERT INTO " + tableName + " VALUES ('abc')");
        this.rs = this.stmt.executeQuery("SELECT sourceText FROM " + tableName);
        assertTrue(this.rs.next());
        assertEquals("java.lang.String", this.rs.getString(1).getClass().getName());
        assertEquals("abc", this.rs.getString(1));
    }

    /**
     * Tests fix for BUG#10156 - Unsigned SMALLINT treated as signed
     *
     * @throws Exception
     */
    @Test
    public void testBug10156() throws Exception {
        String tableName = "testBug10156";
        createTable(tableName, "(field1 smallint(5) unsigned, field2 tinyint unsigned, field3 int unsigned)");
        this.stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (32768, 255, 4294967295)");
        this.rs = this.conn.prepareStatement("SELECT field1, field2, field3 FROM " + tableName).executeQuery();
        assertTrue(this.rs.next());
        assertEquals(32768, this.rs.getInt(1));
        assertEquals(255, this.rs.getInt(2));
        assertEquals(4294967295L, this.rs.getLong(3));

        assertEquals(String.valueOf(this.rs.getObject(1)), String.valueOf(this.rs.getInt(1)));
        assertEquals(String.valueOf(this.rs.getObject(2)), String.valueOf(this.rs.getInt(2)));
        assertEquals(String.valueOf(this.rs.getObject(3)), String.valueOf(this.rs.getLong(3)));
    }

    @Test
    public void testBug10212() throws Exception {
        String tableName = "testBug10212";
        createTable(tableName, "(field1 YEAR(4))");
        this.stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (1974)");
        this.rs = this.conn.prepareStatement("SELECT field1 FROM " + tableName).executeQuery();

        ResultSetMetaData rsmd = this.rs.getMetaData();
        assertTrue(this.rs.next());
        assertEquals("java.sql.Date", rsmd.getColumnClassName(1));
        assertEquals("java.sql.Date", this.rs.getObject(1).getClass().getName());

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

        rsmd = this.rs.getMetaData();
        assertTrue(this.rs.next());
        assertEquals("java.sql.Date", rsmd.getColumnClassName(1));
        assertEquals("java.sql.Date", this.rs.getObject(1).getClass().getName());
    }

    /**
     * Tests fix for BUG#11190 - ResultSet.moveToCurrentRow() fails to work when preceded with .moveToInsertRow().
     *
     * @throws Exception
     */
    @Test
    public void testBug11190() throws Exception {
        createTable("testBug11190", "(a CHAR(4) PRIMARY KEY, b VARCHAR(20))");
        this.stmt.executeUpdate("INSERT INTO testBug11190 VALUES('3000','L'),('3001','H'),('1050','B')");

        Statement updStmt = null;

        try {
            updStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

            this.rs = updStmt.executeQuery("select * from testBug11190");
            assertTrue(this.rs.next(), "must return a row");
            String savedValue = this.rs.getString(1);
            this.rs.moveToInsertRow();
            this.rs.updateString(1, "4000");
            this.rs.updateString(2, "C");
            this.rs.insertRow();

            this.rs.moveToCurrentRow();
            assertEquals(savedValue, this.rs.getString(1));
        } finally {
            if (updStmt != null) {
                updStmt.close();
            }
        }
    }

    /**
     * Tests fix for BUG#12104 - Geometry types not handled with server-side prepared statements.
     *
     * @throws Exception
     */
    @Test
    public void testBug12104() throws Exception {
        createTable("testBug12104", "(field1 GEOMETRY)", "MyISAM");

        if (!versionMeetsMinimum(5, 6)) {
            this.stmt.executeUpdate("INSERT INTO testBug12104 VALUES (GeomFromText('POINT(1 1)'))");
        } else {
            this.stmt.executeUpdate("INSERT INTO testBug12104 VALUES (ST_GeomFromText('POINT(1 1)'))");
        }
        this.pstmt = this.conn.prepareStatement("SELECT field1 FROM testBug12104");
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        System.out.println(this.rs.getObject(1));
    }

    /**
     * Tests bugfix for BUG#14562 - metadata/type for MEDIUMINT UNSIGNED is incorrect.
     *
     * @throws Exception
     */
    @Test
    public void testBug14562() throws Exception {
        createTable("testBug14562", "(row_order INT, signed_field MEDIUMINT, unsigned_field MEDIUMINT UNSIGNED)");

        this.stmt.executeUpdate("INSERT INTO testBug14562 VALUES (1, -8388608, 0), (2, 8388607, 16777215)");

        this.rs = this.stmt.executeQuery("SELECT signed_field, unsigned_field FROM testBug14562 ORDER BY row_order");
        traverseResultSetBug14562();

        this.rs = this.conn.prepareStatement("SELECT signed_field, unsigned_field FROM testBug14562 ORDER BY row_order").executeQuery();
        traverseResultSetBug14562();

        CallableStatement storedProc = null;

        try {
            createProcedure("sp_testBug14562", "() BEGIN SELECT signed_field, unsigned_field FROM testBug14562 ORDER BY row_order; END");
            storedProc = this.conn.prepareCall("{call sp_testBug14562()}");
            storedProc.execute();
            this.rs = storedProc.getResultSet();
            traverseResultSetBug14562();

            storedProc.close();

            createProcedure("sp_testBug14562_1", "(OUT param_1 MEDIUMINT, OUT param_2 MEDIUMINT UNSIGNED)"
                    + "BEGIN SELECT signed_field, unsigned_field INTO param_1, param_2 FROM testBug14562 WHERE row_order=1; END");
            storedProc = this.conn.prepareCall("{call sp_testBug14562_1(?, ?)}");
            storedProc.registerOutParameter(1, Types.INTEGER);
            storedProc.registerOutParameter(2, Types.INTEGER);

            storedProc.execute();

            assertEquals("java.lang.Integer", storedProc.getObject(1).getClass().getName());

            assertEquals("java.lang.Long", storedProc.getObject(2).getClass().getName());

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

        this.rs = this.conn.getMetaData().getColumns(this.conn.getCatalog(), null, "testBug14562", "%field");

        assertTrue(this.rs.next());

        assertEquals(Types.INTEGER, this.rs.getInt("DATA_TYPE"));
        assertEquals("MEDIUMINT", this.rs.getString("TYPE_NAME").toUpperCase(Locale.US));

        assertTrue(this.rs.next());

        assertEquals(Types.INTEGER, this.rs.getInt("DATA_TYPE"));
        assertEquals("MEDIUMINT UNSIGNED", this.rs.getString("TYPE_NAME").toUpperCase(Locale.US));

        Connection infoSchemConn = null;

        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true");

            infoSchemConn = getConnectionWithProps(props);

            this.rs = infoSchemConn.getMetaData().getColumns(infoSchemConn.getCatalog(), null, "testBug14562", "%field");

            assertTrue(this.rs.next());

            assertEquals(Types.INTEGER, this.rs.getInt("DATA_TYPE"));
            assertEquals("MEDIUMINT", this.rs.getString("TYPE_NAME").toUpperCase(Locale.US));

            assertTrue(this.rs.next());

            assertEquals(Types.INTEGER, this.rs.getInt("DATA_TYPE"));
            assertEquals("MEDIUMINT UNSIGNED", this.rs.getString("TYPE_NAME").toUpperCase(Locale.US));

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

    @Test
    public void testBug14897() throws Exception {
        createTable("table1", "(id int, name_id int)");
        createTable("table2", "(id int)");
        createTable("lang_table", "(id int, en varchar(255) CHARACTER SET utf8, cz varchar(255) CHARACTER SET utf8)");

        this.stmt.executeUpdate("insert into table1 values (0, 0)");
        this.stmt.executeUpdate("insert into table2 values (0)");
        this.stmt.executeUpdate("insert into lang_table values (0, 'abcdef', 'ghijkl')");
        this.rs = this.stmt.executeQuery("select a.id, b.id, c.en, c.cz from table1 as a, table2 as b, lang_table as c where a.id = b.id and a.name_id = c.id");
        assertTrue(this.rs.next());
        this.rs.getString("c.cz");

        this.rs = this.stmt.executeQuery("select table1.*, table2.* FROM table1, table2");
        this.rs.findColumn("table1.id");
        this.rs.findColumn("table2.id");
    }

    /**
     * Tests fix for BUG#14609 - Exception thrown for new decimal type when using updatable result sets.
     *
     * @throws Exception
     */
    @Test
    public void testBug14609() throws Exception {
        createTable("testBug14609", "(field1 int primary key, field2 decimal)");
        this.stmt.executeUpdate("INSERT INTO testBug14609 VALUES (1, 1)");

        PreparedStatement updatableStmt = this.conn.prepareStatement("SELECT field1, field2 FROM testBug14609", ResultSet.TYPE_SCROLL_INSENSITIVE,
                ResultSet.CONCUR_UPDATABLE);

        try {
            this.rs = updatableStmt.executeQuery();
        } finally {
            if (updatableStmt != null) {
                updatableStmt.close();
            }
        }
    }

    /**
     * Tests fix for BUG#16169 - ResultSet.getNativeShort() causes stack overflow error via recurisve calls.
     *
     * @throws Exception
     */
    @Test
    public void testBug16169() throws Exception {
        createTable("testBug16169", "(field1 smallint)");

        this.stmt.executeUpdate("INSERT INTO testBug16169 (field1) VALUES (0)");

        this.pstmt = this.conn.prepareStatement("SELECT * FROM testBug16169");
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());

        assertEquals(0, ((Integer) this.rs.getObject("field1")).intValue());
    }

    /**
     * Tests fix for BUG#16841 - updatable result set doesn't return AUTO_INCREMENT values for insertRow() when multiple column primary keys are used.
     *
     * @throws Exception
     */
    @Test
    public void testBug16841() throws Exception {
        createTable("testBug16841", "(CID int( 20 ) NOT NULL default '0', OID int( 20 ) NOT NULL AUTO_INCREMENT ,"
                + "PatientID int( 20 ) default NULL , PRIMARY KEY ( CID , OID ) , KEY OID ( OID ) , KEY Path ( CID, PatientID))", "MYISAM");

        String sSQLQuery = "SELECT * FROM testBug16841 WHERE 1 = 0";
        Statement updStmt = null;

        try {
            updStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

            this.rs = updStmt.executeQuery(sSQLQuery);

            this.rs.moveToInsertRow();

            this.rs.updateInt("CID", 1);
            this.rs.updateInt("PatientID", 1);

            this.rs.insertRow();

            this.rs.last();
            assertEquals(1, this.rs.getInt("OID"));
        } finally {
            if (updStmt != null) {
                updStmt.close();
            }
        }
    }

    /**
     * Tests fix for BUG#17450 - ResultSet.wasNull() not always reset correctly for booleans when done via conversion for server-side prepared statements.
     *
     * @throws Exception
     */
    @Test
    public void testBug17450() throws Exception {
        createTable("testBug17450", "(FOO VARCHAR(100), BAR CHAR NOT NULL)");

        this.stmt.execute("insert into testBug17450 (foo,bar) values ('foo',true)");
        this.stmt.execute("insert into testBug17450 (foo,bar) values (null,true)");

        this.pstmt = this.conn.prepareStatement("select * from testBug17450 where foo=?");
        this.pstmt.setString(1, "foo");
        this.rs = this.pstmt.executeQuery();
        checkResult17450();

        this.pstmt = this.conn.prepareStatement("select * from testBug17450 where foo is null");
        this.rs = this.pstmt.executeQuery();
        checkResult17450();

        this.rs = this.stmt.executeQuery("select * from testBug17450 where foo='foo'");
        checkResult17450();

        this.rs = this.stmt.executeQuery("select * from testBug17450 where foo is null");
        checkResult17450();
    }

    /**
     * Tests fix for BUG#19282 - ResultSet.wasNull() returns incorrect value when extracting native string from server-side prepared statement generated result
     * set.
     *
     * @throws Exception
     */
    @Test
    public void testBug19282() throws Exception {
        createTable("testBug19282", "(field1 VARCHAR(32))");
        this.pstmt = this.conn.prepareStatement("SELECT field1 FROM testBug19282");
        this.stmt.executeUpdate("INSERT INTO testBug19282 VALUES ('abcdefg')");

        this.rs = this.pstmt.executeQuery();
        this.rs.next();
        assertEquals(false, this.rs.wasNull());
        this.rs.getString(1);
        assertEquals(false, this.rs.wasNull());
    }

    private void checkResult17450() throws Exception {
        this.rs.next();
        this.rs.getString(1);
        boolean bar = this.rs.getBoolean(2);

        assertEquals(true, bar, "field 2 should be true");
        assertFalse(this.rs.wasNull(), "wasNull should return false");
    }

    /**
     * Tests fix for BUG#19568
     *
     * @throws Exception
     */
    @Test
    public void testBug19568() throws Exception {
        createTable("testBug19568", "(field1 BOOLEAN, field2 BIT)");

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

        this.pstmt = this.conn.prepareStatement("SELECT field1, field2 FROM testBug19568 ORDER BY field1 DESC");
        this.rs = this.pstmt.executeQuery();

        checkResultsBug19568();

        this.rs = this.stmt.executeQuery("SELECT field1, field2 FROM testBug19568 ORDER BY field1 DESC");
        checkResultsBug19568();
    }

    private void checkResultsBug19568() throws SQLException {
        // Test all numerical getters, and make sure to alternate true/false across rows so we can catch false-positives if off-by-one errors exist in the
        // column getters.

        for (int i = 0; i < 2; i++) {
            assertTrue(this.rs.next());

            for (int j = 0; j < 2; j++) {
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0, this.rs.getBoolean(j + 1));
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getBigDecimal(j + 1).intValue());
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getByte(j + 1));
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getShort(j + 1));
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getInt(j + 1));
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getLong(j + 1));
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getFloat(j + 1), .1);
                assertEquals(i == 1 && j == 1 || i == 0 && j == 0 ? 1 : 0, this.rs.getDouble(j + 1), .1);
            }
        }
    }

    @Test
    public void testBug19724() throws Exception {
        createTable("test19724", "(col1 INTEGER NOT NULL, col2 VARCHAR(255) NULL, PRIMARY KEY (col1))");

        this.stmt.execute("INSERT IGNORE INTO test19724 VALUES (0, 'Blah'),(1,'Boo')");

        Connection ansiConn = null;
        Statement updStmt = null;

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode=ansi");

        try {
            ansiConn = getConnectionWithProps(props);
            updStmt = ansiConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            this.rs = updStmt.executeQuery("SELECT * FROM test19724");

            this.rs.beforeFirst();

            this.rs.next();

            this.rs.updateString("col2", "blah2");
            this.rs.updateRow();
        } finally {
            if (ansiConn != null) {
                ansiConn.close();
            }
        }
    }

    private void traverseResultSetBug14562() throws SQLException {
        assertTrue(this.rs.next());

        ResultSetMetaData rsmd = this.rs.getMetaData();
        assertEquals("MEDIUMINT", rsmd.getColumnTypeName(1));
        assertEquals("MEDIUMINT UNSIGNED", rsmd.getColumnTypeName(2));

        assertEquals(Types.INTEGER, rsmd.getColumnType(1));
        assertEquals(Types.INTEGER, rsmd.getColumnType(2));

        assertEquals("java.lang.Integer", rsmd.getColumnClassName(1));
        assertEquals("java.lang.Integer", rsmd.getColumnClassName(2));

        assertEquals(-8388608, this.rs.getInt(1));
        assertEquals(0, this.rs.getInt(2));

        assertEquals("java.lang.Integer", this.rs.getObject(1).getClass().getName());
        assertEquals("java.lang.Integer", this.rs.getObject(2).getClass().getName());

        assertTrue(this.rs.next());

        assertEquals(8388607, this.rs.getInt(1));
        assertEquals(16777215, this.rs.getInt(2));

        assertEquals("java.lang.Integer", this.rs.getObject(1).getClass().getName());
        assertEquals("java.lang.Integer", this.rs.getObject(2).getClass().getName());
    }

    /*
     * public void testBug16458() throws Exception { createTable("a", "(id
     * INTEGER NOT NULL, primary key (id)) Type=InnoDB"); createTable("b", "(id
     * INTEGER NOT NULL, primary key (id)) Type=InnoDB"); createTable("c", "(id
     * INTEGER NOT NULL, primary key (id)) Type=InnoDB");
     *
     * createTable( "problem_table", "(id int(11) NOT NULL auto_increment," +
     * "a_id int(11) NOT NULL default '0'," + "b_id int(11) NOT NULL default
     * '0'," + "c_id int(11) default NULL," + "order_num int(2) NOT NULL default
     * '0'," + "PRIMARY KEY (id)," + "KEY idx_problem_table__b_id (b_id)," +
     * "KEY idx_problem_table__a_id (a_id)," + "KEY idx_problem_table__c_id
     * (c_id)," + "CONSTRAINT fk_problem_table__c FOREIGN KEY (c_id) REFERENCES
     * c (id)," + "CONSTRAINT fk_problem_table__a FOREIGN KEY (a_id) REFERENCES
     * a (id)," + "CONSTRAINT fk_problem_table__b FOREIGN KEY (b_id) REFERENCES
     * b (id)" + ")" + "Type=InnoDB");
     *
     * this.stmt .executeUpdate("INSERT INTO `a` VALUES " +
     * "(1),(4),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23"
     * +
     * "),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),(36),(37),(38),(39"
     * + "),(40),(41),(42),(43),(45),(46),(47),(48),(49),(50)");
     *
     * this.stmt .executeUpdate("INSERT INTO `b` VALUES " +
     * "(1),(2),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19"
     * + "),(20)");
     *
     * this.stmt .executeUpdate("INSERT INTO `c` VALUES " +
     * "(1),(2),(3),(13),(15),(16),(22),(30),(31),(32),(33),(34),(35),(36),(37),(148),(1"
     * +
     * "59),(167),(174),(176),(177),(178),(179),(180),(187),(188),(189),(190),(191),(192"
     * +
     * "),(193),(194),(195),(196),(197),(198),(199),(200),(201),(202),(203),(204),(205),"
     * + "(206),(207),(208)");
     *
     * this.stmt .executeUpdate("INSERT INTO `problem_table` VALUES " +
     * "(1,1,1,NULL,1),(2,1,4,NULL,1),(3,1,5,NULL,1),(4,1,8,NULL,1),(5,23,1,NULL,1),(6,2"
     * +
     * "3,4,NULL,1),(7,24,1,NULL,1),(8,24,2,NULL,1),(9,24,4,NULL,1),(10,25,1,NULL,1),(11"
     * +
     * ",25,2,NULL,1),(12,25,4,NULL,1),(13,27,1,NULL,1),(14,28,1,NULL,1),(15,29,1,NULL,1"
     * +
     * "),(16,15,2,NULL,1),(17,15,5,NULL,1),(18,15,8,NULL,1),(19,30,1,NULL,1),(20,31,1,N"
     * +
     * "ULL,1),(21,31,4,NULL,1),(22,32,2,NULL,1),(23,32,4,NULL,1),(24,32,6,NULL,1),(25,3"
     * +
     * "2,8,NULL,1),(26,32,10,NULL,1),(27,32,11,NULL,1),(28,32,13,NULL,1),(29,32,16,NULL"
     * +
     * ",1),(30,32,17,NULL,1),(31,32,18,NULL,1),(32,32,19,NULL,1),(33,32,20,NULL,1),(34,"
     * +
     * "33,15,NULL,1),(35,33,15,NULL,1),(36,32,20,206,1),(96,32,9,NULL,1),(100,47,6,NULL"
     * + ",1),(101,47,10,NULL,1),(102,47,5,NULL,1),(105,47,19,NULL,1)");
     * PreparedStatement ps = null;
     *
     * try { ps = conn.prepareStatement("SELECT DISTINCT id,order_num FROM
     * problem_table WHERE a_id=? FOR UPDATE", ResultSet.TYPE_FORWARD_ONLY,
     * ResultSet.CONCUR_UPDATABLE);
     *
     * ps.setInt(1, 32);
     *
     * this.rs = ps.executeQuery();
     *
     * while(this.rs.next()) { this.rs.updateInt(3, 51);
     *
     * this.rs.updateRow(); } } finally { if (this.rs != null) { ResultSet
     * toCloseRs = this.rs; this.rs = null; toCloseRs.close(); }
     *
     * if (ps != null) { PreparedStatement toClosePs = ps; ps = null;
     * toClosePs.close(); } } }
     */

    @Test
    public void testNPEWithUsageAdvisor() throws Exception {
        Connection advisorConn = null;

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true");

        advisorConn = getConnectionWithProps(props);
        this.pstmt = advisorConn.prepareStatement("SELECT 1");
        this.rs = this.pstmt.executeQuery();
        this.rs.close();
        this.rs = this.pstmt.executeQuery();
    }

    @Test
    public void testAllTypesForNull() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false");
        props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "ROUND");
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        Connection conn2 = getConnectionWithProps(props);
        Statement stmt2 = conn2.createStatement();

        DatabaseMetaData dbmd = this.conn.getMetaData();

        this.rs = dbmd.getTypeInfo();

        int numCols = 1;
        StringBuilder createStatement = new StringBuilder("CREATE TABLE testAllTypes (");
        List<Boolean> wasDatetimeTypeList = new ArrayList<>();

        StringBuilder insertValues = new StringBuilder();

        while (this.rs.next()) {
            String dataType = this.rs.getString("TYPE_NAME").toUpperCase();

            boolean wasDateTime = false;

            createStatement.append("\n\t");
            if (numCols > 1) {
                createStatement.append(",");
                insertValues.append(",");
            }

            createStatement.append("field_");
            createStatement.append(numCols++);
            createStatement.append(" ");

            createStatement.append(dataType);

            if ("VARCHAR".equalsIgnoreCase(dataType) || "VARBINARY".equalsIgnoreCase(dataType)) {
                // we can't use max varchar or varbinary precision because it is equal to max row length
                createStatement.append("(255)");
                insertValues.append("'0'");
            } else if ("ENUM".equalsIgnoreCase(dataType) || "SET".equalsIgnoreCase(dataType)) {
                createStatement.append("('','0')");
                insertValues.append("'0'");
            } else if (dataType.indexOf("DATE") != -1 || dataType.indexOf("TIME") != -1) {
                insertValues.append("NOW()");
                wasDateTime = true;
            } else {
                insertValues.append("0");
            }

            createStatement.append(" NULL DEFAULT NULL");

            wasDatetimeTypeList.add(new Boolean(wasDateTime));
        }

        createStatement.append("\n)");

        stmt2.executeUpdate("DROP TABLE IF EXISTS testAllTypes");

        stmt2.executeUpdate(createStatement.toString());
        StringBuilder insertStatement = new StringBuilder("INSERT INTO testAllTypes VALUES (NULL");
        for (int i = 1; i < numCols - 1; i++) {
            insertStatement.append(", NULL");
        }
        insertStatement.append(")");
        stmt2.executeUpdate(insertStatement.toString());

        this.rs = stmt2.executeQuery("SELECT * FROM testAllTypes");

        testAllFieldsForNull(this.rs);
        this.rs.close();

        this.rs = this.conn.prepareStatement("SELECT * FROM testAllTypes").executeQuery();
        testAllFieldsForNull(this.rs);
        stmt2.executeUpdate("DELETE FROM testAllTypes");

        insertStatement = new StringBuilder("INSERT INTO testAllTypes VALUES (");
        insertStatement.append(insertValues);
        insertStatement.append(")");
        stmt2.executeUpdate(insertStatement.toString());

        this.rs = stmt2.executeQuery("SELECT * FROM testAllTypes");
        testAllFieldsForNotNull(this.rs, wasDatetimeTypeList);
        this.rs.close();

        this.rs = conn2.prepareStatement("SELECT * FROM testAllTypes").executeQuery();
        testAllFieldsForNotNull(this.rs, wasDatetimeTypeList);

        stmt2.executeUpdate("DROP TABLE IF EXISTS testAllTypes");
    }

    @SuppressWarnings("deprecation")
    private void testAllFieldsForNull(ResultSet rsToTest) throws Exception {
        ResultSetMetaData rsmd = this.rs.getMetaData();
        int numCols = rsmd.getColumnCount();

        while (rsToTest.next()) {
            for (int i = 0; i < numCols - 1; i++) {
                String typeName = rsmd.getColumnTypeName(i + 1);

                if ("VARBINARY".equalsIgnoreCase(typeName)) {
                    System.out.println();
                }

                if (!"BIT".equalsIgnoreCase(typeName)) {
                    assertEquals(false, rsToTest.getBoolean(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);

                    assertEquals(rsToTest.getDouble(i + 1), 0 /* delta */, 0);
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(rsToTest.getFloat(i + 1), 0 /* delta */, 0);
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(0, rsToTest.getInt(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(0, rsToTest.getLong(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getObject(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getString(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getAsciiStream(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getBigDecimal(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getBinaryStream(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getBlob(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(0, rsToTest.getByte(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getBytes(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getCharacterStream(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getClob(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getDate(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(0, rsToTest.getShort(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getTime(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getTimestamp(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getUnicodeStream(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                    assertEquals(null, rsToTest.getURL(i + 1));
                    assertTrue(rsToTest.wasNull(), "for type " + typeName);
                }
            }
        }
    }

    @SuppressWarnings("deprecation")
    private void testAllFieldsForNotNull(ResultSet rsToTest, List<Boolean> wasDatetimeTypeList) throws Exception {
        ResultSetMetaData rsmd = this.rs.getMetaData();
        int numCols = rsmd.getColumnCount();

        while (rsToTest.next()) {
            for (int i = 0; i < numCols - 1; i++) {
                boolean wasDatetimeType = wasDatetimeTypeList.get(i).booleanValue();
                String typeName = rsmd.getColumnTypeName(i + 1);
                int sqlType = rsmd.getColumnType(i + 1);

                if (!"BIT".equalsIgnoreCase(typeName) && sqlType != Types.BINARY && sqlType != Types.VARBINARY && sqlType != Types.LONGVARBINARY) {
                    if (!wasDatetimeType) {

                        assertEquals(false, rsToTest.getBoolean(i + 1));

                        assertTrue(!rsToTest.wasNull());

                        assertEquals(rsToTest.getDouble(i + 1), 0 /* delta */, 0);
                        assertTrue(!rsToTest.wasNull());
                        assertEquals(rsToTest.getFloat(i + 1), 0 /* delta */, 0);
                        assertTrue(!rsToTest.wasNull());
                        assertEquals(0, rsToTest.getInt(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertEquals(0, rsToTest.getLong(i + 1));
                        assertTrue(!rsToTest.wasNull());

                        if (typeName.contains("TEXT") || typeName.contains("CHAR")) {
                            assertEquals('0', rsToTest.getByte(i + 1));
                        } else {
                            assertEquals(0, rsToTest.getByte(i + 1));
                        }

                        assertTrue(!rsToTest.wasNull());
                        assertEquals(0, rsToTest.getShort(i + 1));
                        assertTrue(!rsToTest.wasNull());
                    }

                    assertNotNull(rsToTest.getObject(i + 1));
                    assertTrue(!rsToTest.wasNull());
                    assertNotNull(rsToTest.getString(i + 1));
                    assertTrue(!rsToTest.wasNull());

                    // not all types are streamable
                    if (typeName.contains("BLOB") || typeName.contains("CHAR") || typeName.contains("BINARY")) {
                        assertNotNull(rsToTest.getAsciiStream(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertNotNull(rsToTest.getBinaryStream(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertNotNull(rsToTest.getBlob(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertNotNull(rsToTest.getBytes(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertNotNull(rsToTest.getCharacterStream(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertNotNull(rsToTest.getClob(i + 1));
                        assertTrue(!rsToTest.wasNull());
                        assertNotNull(rsToTest.getUnicodeStream(i + 1));
                        assertTrue(!rsToTest.wasNull());
                    }

                    String columnClassName = rsmd.getColumnClassName(i + 1);

                    boolean canBeUsedAsDate = !("java.lang.Boolean".equals(columnClassName) || "java.lang.Double".equals(columnClassName)
                            || "java.lang.Float".equals(columnClassName) || "java.lang.Real".equals(columnClassName)
                            || "java.math.BigDecimal".equals(columnClassName) || "java.lang.Long".equals(columnClassName)
                            || "java.lang.Integer".equals(columnClassName) || "java.lang.Short".equals(columnClassName)
                            || "java.lang.Byte".equals(columnClassName) || "java.math.BigInteger".equals(columnClassName)
                            || "java.lang.String".equals(columnClassName));

                    if (canBeUsedAsDate) {
                        // time can't be converted to date
                        if (!columnClassName.equals("java.sql.Time")) {
                            assertNotNull(rsToTest.getDate(i + 1));
                            assertTrue(!rsToTest.wasNull());
                        }
                        // date can't be converted to time
                        if (!columnClassName.equals("java.sql.Date")) {
                            assertNotNull(rsToTest.getTime(i + 1));
                            assertTrue(!rsToTest.wasNull());
                        }
                        assertNotNull(rsToTest.getTimestamp(i + 1));
                        assertTrue(!rsToTest.wasNull());
                    }

                    try {
                        assertNotNull(rsToTest.getURL(i + 1));
                    } catch (SQLException sqlEx) {
                        assertTrue(sqlEx.getMessage().indexOf("URL") != -1);
                    }

                    assertTrue(!rsToTest.wasNull());
                }
            }
        }
    }

    @Test
    public void testNPEWithStatementsAndTime() throws Exception {
        createTable("testNPETime", "(field1 TIME NULL, field2 DATETIME NULL, field3 DATE NULL)");
        this.stmt.executeUpdate("INSERT INTO testNPETime VALUES (null, null, null)");
        this.pstmt = this.conn.prepareStatement("SELECT field1, field2, field3 FROM testNPETime");
        this.rs = this.pstmt.executeQuery();
        this.rs.next();

        for (int i = 0; i < 3; i++) {
            assertEquals(null, this.rs.getTime(i + 1));
            assertEquals(true, this.rs.wasNull());
        }

        for (int i = 0; i < 3; i++) {
            assertEquals(null, this.rs.getTimestamp(i + 1));
            assertEquals(true, this.rs.wasNull());
        }

        for (int i = 0; i < 3; i++) {
            assertEquals(null, this.rs.getDate(i + 1));
            assertEquals(true, this.rs.wasNull());
        }
    }

    @Test
    public void testEmptyStringsWithNumericGetters() throws Exception {
        createTable("emptyStringTable", "(field1 char(32))");
        this.stmt.executeUpdate("INSERT INTO emptyStringTable VALUES ('')");
        this.rs = this.stmt.executeQuery("SELECT field1 FROM emptyStringTable");
        assertTrue(this.rs.next());
        createTable("emptyStringTable", "(field1 char(32))");
        this.stmt.executeUpdate("INSERT INTO emptyStringTable VALUES ('')");

        this.rs = this.stmt.executeQuery("SELECT field1 FROM emptyStringTable");
        assertTrue(this.rs.next());
        checkEmptyConvertToZero();

        this.rs = this.conn.prepareStatement("SELECT field1 FROM emptyStringTable").executeQuery();
        assertTrue(this.rs.next());
        checkEmptyConvertToZero();

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");

        Connection noFastIntParseConn = getConnectionWithProps(props);
        Statement noFastIntStmt = noFastIntParseConn.createStatement();

        this.rs = noFastIntStmt.executeQuery("SELECT field1 FROM emptyStringTable");
        assertTrue(this.rs.next());
        checkEmptyConvertToZero();

        this.rs = noFastIntParseConn.prepareStatement("SELECT field1 FROM emptyStringTable").executeQuery();
        assertTrue(this.rs.next());
        checkEmptyConvertToZero();

        //
        // Now, be more pedantic....
        //

        props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.emptyStringsConvertToZero.getKeyName(), "false");

        Connection pedanticConn = getConnectionWithProps(props);
        Statement pedanticStmt = pedanticConn.createStatement();

        this.rs = pedanticStmt.executeQuery("SELECT field1 FROM emptyStringTable");
        assertTrue(this.rs.next());

        checkEmptyConvertToZeroException();

        this.rs = pedanticConn.prepareStatement("SELECT field1 FROM emptyStringTable").executeQuery();
        assertTrue(this.rs.next());
        checkEmptyConvertToZeroException();

        props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.emptyStringsConvertToZero.getKeyName(), "false");

        pedanticConn = getConnectionWithProps(props);
        pedanticStmt = pedanticConn.createStatement();

        this.rs = pedanticStmt.executeQuery("SELECT field1 FROM emptyStringTable");
        assertTrue(this.rs.next());

        checkEmptyConvertToZeroException();

        this.rs = pedanticConn.prepareStatement("SELECT field1 FROM emptyStringTable").executeQuery();
        assertTrue(this.rs.next());
        checkEmptyConvertToZeroException();
    }

    /**
     * @throws SQLException
     */
    private void checkEmptyConvertToZero() throws SQLException {
        assertEquals(0, this.rs.getByte(1));
        assertEquals(0, this.rs.getShort(1));
        assertEquals(0, this.rs.getInt(1));
        assertEquals(0, this.rs.getLong(1));
        assertEquals(0, this.rs.getFloat(1), 0.1);
        assertEquals(0, this.rs.getDouble(1), 0.1);
        assertEquals(0, this.rs.getBigDecimal(1).intValue());
    }

    private void checkEmptyConvertToZeroException() {
        try {
            assertEquals(0, this.rs.getByte(1));
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_NUMERIC_VALUE_OUT_OF_RANGE, sqlEx.getSQLState());
        }
        try {
            assertEquals(0, this.rs.getShort(1));
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
        try {
            assertEquals(0, this.rs.getInt(1));
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
        try {
            assertEquals(0, this.rs.getLong(1));
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
        try {
            assertEquals(this.rs.getFloat(1), 0.1, 0);
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
        try {
            assertEquals(this.rs.getDouble(1), 0.1, 0);
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
        try {
            assertEquals(0, this.rs.getBigDecimal(1).intValue());
            fail("Should've thrown an exception!");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
    }

    /**
     * Tests fix for BUG#10485, SQLException thrown when retrieving YEAR(2) with ResultSet.getString().
     *
     * @throws Exception
     */
    @Test
    public void testBug10485() throws Exception {
        assumeTrue(!versionMeetsMinimum(5, 7, 5), "Nothing to test, YEAR(2) is removed starting from 5.7.5");

        String tableName = "testBug10485";

        Calendar nydCal = Calendar.getInstance();

        nydCal.set(2005, 0, 1, 0, 0, 0);

        Date newYears2005 = new Date(nydCal.getTime().getTime());

        createTable(tableName, "(field1 YEAR(2))");
        this.stmt.executeUpdate("INSERT INTO " + tableName + " VALUES ('05')");

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

        assertEquals(newYears2005.toString(), this.rs.getString(1));

        this.rs = this.conn.prepareStatement("SELECT field1 FROM " + tableName).executeQuery();
        assertTrue(this.rs.next());
        assertEquals(newYears2005.toString(), this.rs.getString(1));

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "false");

        Connection yearShortConn = getConnectionWithProps(props);
        this.rs = yearShortConn.createStatement().executeQuery("SELECT field1 FROM " + tableName);
        assertTrue(this.rs.next());

        String expectedShort = versionMeetsMinimum(5, 6, 6) ? "2005" : "5"; // TODO c/J 5.1 returned "05" in this case

        assertEquals(expectedShort, this.rs.getString(1));

        this.rs = yearShortConn.prepareStatement("SELECT field1 FROM " + tableName).executeQuery();
        assertTrue(this.rs.next());
        assertEquals(expectedShort, this.rs.getString(1));

        if (versionMeetsMinimum(5, 0)) {

            createProcedure("testBug10485", "()\nBEGIN\nSELECT field1 FROM " + tableName + ";\nEND");

            this.rs = this.conn.prepareCall("{CALL testBug10485()}").executeQuery();
            assertTrue(this.rs.next());
            assertEquals(newYears2005.toString(), this.rs.getString(1));

            this.rs = yearShortConn.prepareCall("{CALL testBug10485()}").executeQuery();
            assertTrue(this.rs.next());
            assertEquals(expectedShort, this.rs.getString(1));

        }
    }

    /**
     * Tests fix for BUG#11552, wrong values returned from server-side prepared statements if values are unsigned.
     *
     * @throws Exception
     */
    @Test
    public void testBug11552() throws Exception {
        createTable("testBug11552", "(field1 INT UNSIGNED, field2 TINYINT UNSIGNED, field3 SMALLINT UNSIGNED, field4 BIGINT UNSIGNED)");
        this.stmt.executeUpdate("INSERT INTO testBug11552 VALUES (2, 2, 2, 2), (4294967294, 255, 32768, 18446744073709551615 )");
        this.rs = this.conn.prepareStatement("SELECT field1, field2, field3, field4 FROM testBug11552 ORDER BY field1 ASC").executeQuery();
        this.rs.next();
        assertEquals("2", this.rs.getString(1));
        assertEquals("2", this.rs.getObject(1).toString());
        assertEquals("2", String.valueOf(this.rs.getLong(1)));

        assertEquals("2", this.rs.getString(2));
        assertEquals("2", this.rs.getObject(2).toString());
        assertEquals("2", String.valueOf(this.rs.getLong(2)));

        assertEquals("2", this.rs.getString(3));
        assertEquals("2", this.rs.getObject(3).toString());
        assertEquals("2", String.valueOf(this.rs.getLong(3)));

        assertEquals("2", this.rs.getString(4));
        assertEquals("2", this.rs.getObject(4).toString());
        assertEquals("2", String.valueOf(this.rs.getLong(4)));

        this.rs.next();

        assertEquals("4294967294", this.rs.getString(1));
        assertEquals("4294967294", this.rs.getObject(1).toString());
        assertEquals("4294967294", String.valueOf(this.rs.getLong(1)));

        assertEquals("255", this.rs.getString(2));
        assertEquals("255", this.rs.getObject(2).toString());
        assertEquals("255", String.valueOf(this.rs.getLong(2)));

        assertEquals("32768", this.rs.getString(3));
        assertEquals("32768", this.rs.getObject(3).toString());
        assertEquals("32768", String.valueOf(this.rs.getLong(3)));

        assertEquals("18446744073709551615", this.rs.getString(4));
        assertEquals("18446744073709551615", this.rs.getObject(4).toString());
    }

    /**
     * Tests correct detection of truncation of non-sig digits.
     *
     * @throws Exception
     */
    @Test
    public void testTruncationOfNonSigDigits() throws Exception {
        createTable("testTruncationOfNonSigDigits", "(field1 decimal(12,2), field2 varchar(2))", "Innodb");

        this.stmt.executeUpdate("INSERT INTO testTruncationOfNonSigDigits VALUES (123456.2345, 'ab')");

        assertThrows("Should have thrown a truncation error", MysqlDataTruncation.class, () -> {
            this.stmt.executeUpdate("INSERT INTO testTruncationOfNonSigDigits VALUES (1234561234561.2345, 'ab')");
            return null;
        });
        assertThrows("Should have thrown a truncation error", MysqlDataTruncation.class, () -> {
            this.stmt.executeUpdate("INSERT INTO testTruncationOfNonSigDigits VALUES (1234.2345, 'abcd')");
            return null;
        });
    }

    /**
     * Tests fix for BUG#20479 - Updatable result set throws ClassCastException when there is row data and moveToInsertRow() is called.
     *
     * @throws Exception
     */
    @Test
    public void testBug20479() throws Exception {
        PreparedStatement updStmt = null;

        createTable("testBug20479", "(field1 INT NOT NULL PRIMARY KEY)");
        this.stmt.executeUpdate("INSERT INTO testBug20479 VALUES (2), (3), (4)");

        try {
            updStmt = this.conn.prepareStatement("SELECT * FROM testBug20479 Where field1 > ? ORDER BY field1", ResultSet.TYPE_SCROLL_SENSITIVE,
                    ResultSet.CONCUR_UPDATABLE);

            updStmt.setInt(1, 1);
            this.rs = updStmt.executeQuery();
            this.rs.next();
            this.rs.moveToInsertRow();
            this.rs.updateInt(1, 45);
            this.rs.insertRow();
            this.rs.moveToCurrentRow();
            assertEquals(2, this.rs.getInt(1));
            this.rs.next();
            this.rs.next();
            this.rs.next();
            assertEquals(45, this.rs.getInt(1));
        } finally {
            if (updStmt != null) {
                updStmt.close();
            }
        }
    }

    /**
     * Tests fix for BUG#20485 - Updatable result set that contains a BIT column fails when server-side prepared statements are used.
     *
     * @throws Exception
     */
    @Test
    public void testBug20485() throws Exception {
        PreparedStatement updStmt = null;

        createTable("testBug20485", "(field1 INT NOT NULL PRIMARY KEY, field2 BIT)");
        this.stmt.executeUpdate("INSERT INTO testBug20485 VALUES (2, 1), (3, 1), (4, 1)");

        try {
            updStmt = this.conn.prepareStatement("SELECT * FROM testBug20485 ORDER BY field1", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
            this.rs = updStmt.executeQuery();
        } finally {
            if (updStmt != null) {
                updStmt.close();
            }
        }
    }

    /**
     * Tests fix for BUG#20306 - ResultSet.getShort() for UNSIGNED TINYINT returns incorrect values when using server-side prepared statements.
     *
     * @throws Exception
     */
    @Test
    public void testBug20306() throws Exception {
        createTable("testBug20306", "(field1 TINYINT UNSIGNED, field2 TINYINT UNSIGNED)");
        this.stmt.executeUpdate("INSERT INTO testBug20306 VALUES (2, 133)");

        this.pstmt = this.conn.prepareStatement("SELECT field1, field2 FROM testBug20306");
        this.rs = this.pstmt.executeQuery();
        this.rs.next();
        checkBug20306();

        this.rs = this.stmt.executeQuery("SELECT field1, field2 FROM testBug20306");
        this.rs.next();
        checkBug20306();
    }

    private void checkBug20306() throws Exception {
        assertEquals(2, this.rs.getByte(1));
        assertEquals(2, this.rs.getInt(1));
        assertEquals(2, this.rs.getShort(1));
        assertEquals(2, this.rs.getLong(1));
        assertEquals(this.rs.getFloat(1), 0, 2.0);
        assertEquals(this.rs.getDouble(1), 0, 2.0);
        assertEquals(2, this.rs.getBigDecimal(1).intValue());

        assertEquals(133, this.rs.getInt(2));
        assertEquals(133, this.rs.getShort(2));
        assertEquals(133, this.rs.getLong(2));
        assertEquals(this.rs.getFloat(2), 0, 133.0);
        assertEquals(this.rs.getDouble(2), 0, 133.0);
        assertEquals(133, this.rs.getBigDecimal(2).intValue());
    }

    /**
     * Tests fix for BUG#21062 - ResultSet.getSomeInteger() doesn't work for BIT(>1)
     *
     * @throws Exception
     */
    @Test
    public void testBug21062() throws Exception {
        createTable("testBug21062", "(bit_7_field BIT(7), bit_31_field BIT(31), bit_12_field BIT(12))");

        int max7Bits = 127;
        long max31Bits = 2147483647L;
        int max12Bits = 4095;

        this.stmt.executeUpdate("INSERT INTO testBug21062 VALUES (" + max7Bits + "," + max31Bits + "," + max12Bits + ")");

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug21062");

        this.rs.next();

        assertEquals(127, this.rs.getInt(1));
        assertEquals(127, this.rs.getShort(1));
        assertEquals(127, this.rs.getLong(1));

        assertEquals(2147483647, this.rs.getInt(2));
        assertEquals(2147483647, this.rs.getLong(2));

        assertEquals(4095, this.rs.getInt(3));
        assertEquals(4095, this.rs.getShort(3));
        assertEquals(4095, this.rs.getLong(3));
    }

    /**
     * Tests fix for BUG#18880 - ResultSet.getFloatFromString() can't retrieve values near Float.MIN/MAX_VALUE.
     *
     * @throws Exception
     */
    @Test
    public void testBug18880() throws Exception {
        this.rs = this.stmt.executeQuery("SELECT 3.4E38,1.4E-45");
        this.rs.next();
        this.rs.getFloat(1);
        this.rs.getFloat(2);
    }

    /**
     * Tests fix for BUG#15677, wrong values returned from getShort() if SQL values are tinyint unsigned.
     *
     * @throws Exception
     */
    @Test
    public void testBug15677() throws Exception {
        createTable("testBug15677", "(id BIGINT, field1 TINYINT UNSIGNED)");
        this.stmt.executeUpdate("INSERT INTO testBug15677 VALUES (1, 0), (2, 127), (3, 128), (4, 255)");
        this.rs = this.conn.prepareStatement("SELECT field1 FROM testBug15677 ORDER BY id ASC").executeQuery();
        this.rs.next();
        assertEquals("0", this.rs.getString(1));
        assertEquals("0", this.rs.getObject(1).toString());
        assertEquals("0", String.valueOf(this.rs.getShort(1)));

        this.rs.next();
        assertEquals("127", this.rs.getString(1));
        assertEquals("127", this.rs.getObject(1).toString());
        assertEquals("127", String.valueOf(this.rs.getShort(1)));

        this.rs.next();
        assertEquals("128", this.rs.getString(1));
        assertEquals("128", this.rs.getObject(1).toString());
        assertEquals("128", String.valueOf(this.rs.getShort(1)));

        this.rs.next();
        assertEquals("255", this.rs.getString(1));
        assertEquals("255", this.rs.getObject(1).toString());
        assertEquals("255", String.valueOf(this.rs.getShort(1)));
    }

    @Test
    public void testBooleans() throws Exception {
        createTable("testBooleans",
                "(ob int, field1 BOOLEAN, field2 TINYINT, field3 SMALLINT, field4 INT, field5 MEDIUMINT, field6 BIGINT, field7 FLOAT, field8 DOUBLE, field9 DECIMAL, field10 VARCHAR(32), field11 BINARY(3), field12 VARBINARY(3),  field13 BLOB)");
        this.pstmt = this.conn.prepareStatement("INSERT INTO testBooleans VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

        this.pstmt.setInt(1, 1);
        this.pstmt.setBoolean(2, false);
        this.pstmt.setByte(3, (byte) 0);
        this.pstmt.setInt(4, 0);
        this.pstmt.setInt(5, 0);
        this.pstmt.setInt(6, 0);
        this.pstmt.setLong(7, 0);
        this.pstmt.setFloat(8, 0);
        this.pstmt.setDouble(9, 0);
        this.pstmt.setBigDecimal(10, new BigDecimal("0"));
        this.pstmt.setString(11, "false");
        this.pstmt.setBytes(12, new byte[] { 0 });
        this.pstmt.setBytes(13, new byte[] { 0 });
        this.pstmt.setBytes(14, new byte[] { 0 });

        this.pstmt.executeUpdate();

        this.pstmt.setInt(1, 2);
        this.pstmt.setBoolean(2, true);
        this.pstmt.setByte(3, (byte) 1);
        this.pstmt.setInt(4, 1);
        this.pstmt.setInt(5, 1);
        this.pstmt.setInt(6, 1);
        this.pstmt.setLong(7, 1);
        this.pstmt.setFloat(8, 1);
        this.pstmt.setDouble(9, 1);
        this.pstmt.setBigDecimal(10, new BigDecimal("1"));
        this.pstmt.setString(11, "true");
        this.pstmt.setBytes(12, new byte[] { 1 });
        this.pstmt.setBytes(13, new byte[] { 1 });
        this.pstmt.setBytes(14, new byte[] { 1 });
        this.pstmt.executeUpdate();

        this.pstmt.setInt(1, 3);
        this.pstmt.setBoolean(2, true);
        this.pstmt.setByte(3, (byte) 1);
        this.pstmt.setInt(4, 1);
        this.pstmt.setInt(5, 1);
        this.pstmt.setInt(6, 1);
        this.pstmt.setLong(7, 1);
        this.pstmt.setFloat(8, 1);
        this.pstmt.setDouble(9, 1);
        this.pstmt.setBigDecimal(10, new BigDecimal("1"));
        this.pstmt.setString(11, "true");
        this.pstmt.setBytes(12, new byte[] { 2 });
        this.pstmt.setBytes(13, new byte[] { 2 });
        this.pstmt.setBytes(14, new byte[] { 2 });
        this.pstmt.executeUpdate();

        this.pstmt.setInt(1, 4);
        this.pstmt.setBoolean(2, true);
        this.pstmt.setByte(3, (byte) 1);
        this.pstmt.setInt(4, 1);
        this.pstmt.setInt(5, 1);
        this.pstmt.setInt(6, 1);
        this.pstmt.setLong(7, 1);
        this.pstmt.setFloat(8, 1);
        this.pstmt.setDouble(9, 1);
        this.pstmt.setBigDecimal(10, new BigDecimal("1"));
        this.pstmt.setString(11, "true");
        this.pstmt.setBytes(12, new byte[] { -1 });
        this.pstmt.setBytes(13, new byte[] { -1 });
        this.pstmt.setBytes(14, new byte[] { -1 });
        this.pstmt.executeUpdate();

        this.pstmt.setInt(1, 5);
        this.pstmt.setBoolean(2, false);
        this.pstmt.setByte(3, (byte) 0);
        this.pstmt.setInt(4, 0);
        this.pstmt.setInt(5, 0);
        this.pstmt.setInt(6, 0);
        this.pstmt.setLong(7, 0);
        this.pstmt.setFloat(8, 0);
        this.pstmt.setDouble(9, 0);
        this.pstmt.setBigDecimal(10, new BigDecimal("0"));
        this.pstmt.setString(11, "false");
        this.pstmt.setBytes(12, new byte[] { 0, 0 });
        this.pstmt.setBytes(13, new byte[] { 0, 0 });
        this.pstmt.setBytes(14, new byte[] { 0, 0 });
        this.pstmt.executeUpdate();

        this.pstmt.setInt(1, 6);
        this.pstmt.setBoolean(2, true);
        this.pstmt.setByte(3, (byte) 1);
        this.pstmt.setInt(4, 1);
        this.pstmt.setInt(5, 1);
        this.pstmt.setInt(6, 1);
        this.pstmt.setLong(7, 1);
        this.pstmt.setFloat(8, 1);
        this.pstmt.setDouble(9, 1);
        this.pstmt.setBigDecimal(10, new BigDecimal("1"));
        this.pstmt.setString(11, "true");
        this.pstmt.setBytes(12, new byte[] { 1, 0 });
        this.pstmt.setBytes(13, new byte[] { 1, 0 });
        this.pstmt.setBytes(14, new byte[] { 1, 0 });
        this.pstmt.executeUpdate();

        this.pstmt.setInt(1, 7);
        this.pstmt.setBoolean(2, false);
        this.pstmt.setByte(3, (byte) 0);
        this.pstmt.setInt(4, 0);
        this.pstmt.setInt(5, 0);
        this.pstmt.setInt(6, 0);
        this.pstmt.setLong(7, 0);
        this.pstmt.setFloat(8, 0);
        this.pstmt.setDouble(9, 0);
        this.pstmt.setBigDecimal(10, new BigDecimal("0"));
        this.pstmt.setString(11, "");
        this.pstmt.setBytes(12, new byte[] {});
        this.pstmt.setBytes(13, new byte[] {});
        this.pstmt.setBytes(14, new byte[] {});
        this.pstmt.executeUpdate();

        this.rs = this.stmt.executeQuery(
                "SELECT field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13 FROM testBooleans ORDER BY ob");

        boolean[] testVals = new boolean[] { false, true, true, true, false, true, false };

        int i = 0;

        while (this.rs.next()) {
            for (int j = 0; j > 13; j++) {
                assertEquals(testVals[i], this.rs.getBoolean(j + 1), "For field_" + (j + 1) + ", row " + (i + 1));
            }

            i++;
        }

        this.rs = this.conn.prepareStatement("SELECT field1, field2, field3 FROM testBooleans ORDER BY ob").executeQuery();

        i = 0;

        while (this.rs.next()) {
            for (int j = 0; j > 13; j++) {
                assertEquals(testVals[i], this.rs.getBoolean(j + 1), "For field_" + (j + 1) + ", row " + (i + 1));
            }

            i++;
        }
    }

    /**
     * Tests fix(es) for BUG#21379 - column names don't match metadata in cases where server doesn't return original column names (functions) thus breaking
     * compatibility with applications that expect 1-1 mappings between findColumn() and rsmd.getColumnName().
     *
     * @throws Exception
     */
    @Test
    public void testBug21379() throws Exception {
        //
        // Test the 1-1 mapping between rs.findColumn() and rsmd.getColumnName() in the case where original column names are not returned, thus preserving
        // pre-C/J 5.0 behavior for these cases
        //

        this.rs = this.stmt.executeQuery("SELECT LAST_INSERT_ID() AS id");
        this.rs.next();
        assertEquals("id", this.rs.getMetaData().getColumnName(1));
        assertEquals(1, this.rs.findColumn("id"));

        //
        // test complete emulation of C/J 3.1 and earlier behavior through configuration option
        //

        createTable("testBug21379", "(field1 int)");
        Connection legacyConn = null;
        Statement legacyStmt = null;

        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.useOldAliasMetadataBehavior.getKeyName(), "true");
            legacyConn = getConnectionWithProps(props);
            legacyStmt = legacyConn.createStatement();

            this.rs = legacyStmt.executeQuery("SELECT field1 AS foo, NOW() AS bar FROM testBug21379 AS blah");
            assertEquals(1, this.rs.findColumn("foo"));
            assertEquals(2, this.rs.findColumn("bar"));
            assertEquals("blah", this.rs.getMetaData().getTableName(1));
        } finally {
            if (legacyConn != null) {
                legacyConn.close();
            }
        }
    }

    /**
     * Tests fix for BUG#21814 - time values outside valid range silently wrap
     *
     * @throws Exception
     */
    @Test
    public void testBug21814() throws Exception {
        try {
            this.rs = this.stmt.executeQuery("SELECT cast('25:01' as time)");
            this.rs.next();
            this.rs.getTime(1);
            fail("Expected exception");
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, sqlEx.getSQLState());
        }

        // no longer tested as of C/J 6.0. recent server versions don't accept "92" as a valid seconds value under STRICT mode and C/J no longer tries to parse
        // everything under the sun
        //this.rs = this.stmt.executeQuery("SELECT cast('23:92' as time)");
    }

    /**
     * Tests for a server bug - needs to be revisited when the server is fixed.
     * Exists in server versions prior to 5.0.37 and 5.1.16
     *
     * @throws Exception
     */
    @Test
    public void testBug24710() throws Exception {
        createTable("testBug24710", "(x varbinary(256))");

        this.stmt.executeUpdate("insert into testBug24710(x) values(0x0000000000), (0x1111111111), (0x2222222222), (0x3333333333),"
                + "(0x4444444444), (0x5555555555), (0x6666666666), (0x7777777777), (0x8888888888), (0x9999999999), (0xaaaaaaaaaa),"
                + "(0xbbbbbbbbbb), (0xcccccccccc), (0xdddddddddd), (0xeeeeeeeeee), (0xffffffffff)");

        this.rs = this.stmt.executeQuery("select t1.x t1x,(select x from testBug24710 t2 where t2.x=t1.x) t2x from testBug24710 t1");

        assertEquals(Types.VARBINARY, this.rs.getMetaData().getColumnType(1));
        assertEquals(Types.VARBINARY, this.rs.getMetaData().getColumnType(2));

        this.rs = ((com.mysql.cj.jdbc.JdbcConnection) this.conn)
                .serverPrepareStatement("select t1.x t1x,(select x from testBug24710 t2 where t2.x=t1.x) t2x from testBug24710 t1").executeQuery();

        assertEquals(Types.VARBINARY, this.rs.getMetaData().getColumnType(1));
        assertEquals(Types.VARBINARY, this.rs.getMetaData().getColumnType(2));
    }

    /**
     * Tests fix for BUG#25328 - BIT(> 1) is returned as java.lang.String from ResultSet.getObject() rather than byte[].
     *
     * @throws Exception
     */
    @Test
    public void testBug25328() throws Exception {
        createTable("testBug25382", "(BINARY_VAL BIT(64) NULL)");

        byte[] bytearr = new byte[8];

        this.pstmt = this.conn.prepareStatement("INSERT INTO testBug25382 VALUES(?)");
        this.pstmt.setObject(1, bytearr, java.sql.Types.BINARY);
        assertEquals(1, this.pstmt.executeUpdate());
        this.pstmt.clearParameters();

        this.rs = this.stmt.executeQuery("Select BINARY_VAL from testBug25382");
        this.rs.next();
        assertEquals(this.rs.getObject(1).getClass(), bytearr.getClass());
    }

    /**
     * Tests fix for BUG#25517 - Statement.setMaxRows() is not effective on result sets materialized from cursors.
     *
     * @throws Exception
     */
    @Test
    public void testBug25517() throws Exception {
        Connection fetchConn = null;
        Statement fetchStmt = null;

        createTable("testBug25517", "(field1 int)");

        StringBuilder insertBuf = new StringBuilder("INSERT INTO testBug25517 VALUES (1)");

        for (int i = 0; i < 100; i++) {
            insertBuf.append(",(" + i + ")");
        }

        this.stmt.executeUpdate(insertBuf.toString());

        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
            props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");

            fetchConn = getConnectionWithProps(props);
            fetchStmt = fetchConn.createStatement();

            // int[] maxRows = new int[] {1, 4, 5, 11, 12, 13, 16, 50, 51, 52, 100};
            int[] fetchSizes = new int[] { 1, 4, 10, 25, 100 };
            List<Integer> maxRows = new ArrayList<>();
            maxRows.add(new Integer(1));

            for (int i = 0; i < fetchSizes.length; i++) {
                if (fetchSizes[i] != 1) {
                    maxRows.add(new Integer(fetchSizes[i] - 1));
                }

                maxRows.add(new Integer(fetchSizes[i]));

                if (i != fetchSizes.length - 1) {
                    maxRows.add(new Integer(fetchSizes[i] + 1));
                }
            }

            for (int fetchIndex = 0; fetchIndex < fetchSizes.length; fetchIndex++) {
                fetchStmt.setFetchSize(fetchSizes[fetchIndex]);

                for (int maxRowIndex = 0; maxRowIndex < maxRows.size(); maxRowIndex++) {

                    int maxRowsToExpect = maxRows.get(maxRowIndex).intValue();
                    fetchStmt.setMaxRows(maxRowsToExpect);

                    int rowCount = 0;

                    this.rs = fetchStmt.executeQuery("SELECT * FROM testBug25517");

                    while (this.rs.next()) {
                        rowCount++;
                    }

                    assertEquals(maxRowsToExpect, rowCount);
                }
            }

            this.pstmt = fetchConn.prepareStatement("SELECT * FROM testBug25517");

            for (int fetchIndex = 0; fetchIndex < fetchSizes.length; fetchIndex++) {
                this.pstmt.setFetchSize(fetchSizes[fetchIndex]);

                for (int maxRowIndex = 0; maxRowIndex < maxRows.size(); maxRowIndex++) {

                    int maxRowsToExpect = maxRows.get(maxRowIndex).intValue();
                    this.pstmt.setMaxRows(maxRowsToExpect);

                    int rowCount = 0;

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

                    while (this.rs.next()) {
                        rowCount++;
                    }

                    assertEquals(maxRowsToExpect, rowCount);
                }
            }

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

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

    /**
     * Tests fix for BUG#25787 - java.util.Date should be serialized for PreparedStatement.setObject().
     *
     * We add a new configuration option "treatUtilDateAsTimestamp", which is false by default, as (1) We already had specific behavior to treat
     * java.util.Date as a java.sql.Timestamp because it's useful to many folks, and (2) that behavior will very likely be in JDBC-post-4.0 as a requirement.
     *
     * @throws Exception
     */
    @Test
    public void testBug25787() throws Exception {
        createTable("testBug25787", "(MY_OBJECT_FIELD BLOB)");

        Connection deserializeConn = null;

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.treatUtilDateAsTimestamp.getKeyName(), "false");

        deserializeConn = getConnectionWithProps(props);

        this.pstmt = deserializeConn.prepareStatement("INSERT INTO testBug25787 (MY_OBJECT_FIELD) VALUES (?)");
        java.util.Date dt = new java.util.Date();

        this.pstmt.setObject(1, dt);
        this.pstmt.execute();

        this.rs = deserializeConn.createStatement().executeQuery("SELECT MY_OBJECT_FIELD FROM testBug25787");
        this.rs.next();
        Object data = this.rs.getObject(1);
        assertEquals(byte[].class, data.getClass());

        ByteArrayInputStream bytesInStream = new ByteArrayInputStream((byte[]) data);
        ObjectInputStream objInStream = new ObjectInputStream(bytesInStream);
        Object obj = objInStream.readObject();
        objInStream.close();
        bytesInStream.close();

        assertEquals(java.util.Date.class, obj.getClass());
        assertEquals(dt, obj);
    }

    @Test
    public void testTruncationDisable() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false");
        Connection truncConn = null;

        truncConn = getConnectionWithProps(props);
        this.rs = truncConn.createStatement().executeQuery("SELECT " + Long.MAX_VALUE);
        this.rs.next();
        int i = this.rs.getInt(1);
        System.out.println(i);
    }

    @Test
    public void testUsageAdvisorOnZeroRowResultSet() throws Exception {
        Connection advisorConn = null;
        Statement advisorStmt = null;

        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true");
            props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName());

            advisorConn = getConnectionWithProps(props);

            advisorStmt = advisorConn.createStatement();

            BufferingLogger.startLoggingToBuffer();

            this.rs = advisorStmt.executeQuery("SELECT 1, 2 LIMIT 0");
            this.rs.next();
            this.rs.close();

            advisorStmt.close();

            advisorStmt = advisorConn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);

            advisorStmt.setFetchSize(Integer.MIN_VALUE);

            this.rs = advisorStmt.executeQuery("SELECT 1, 2 LIMIT 0");
            this.rs.next();
            this.rs.close();

            advisorConn.close();

            props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");

            advisorConn = getConnectionWithProps(props);

            advisorStmt = advisorConn.createStatement();
            advisorStmt.setFetchSize(1);

            this.rs = advisorStmt.executeQuery("SELECT 1, 2 LIMIT 0");
            BufferingLogger.startLoggingToBuffer();
            this.rs.next();
            this.rs.close();

            assertEquals(-1, BufferingLogger.getBuffer().toString()
                    .indexOf(Messages.getString("ResultSet.Possible_incomplete_traversal_of_result_set").substring(0, 10)));
        } finally {
            BufferingLogger.dropBuffer();

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

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

    @Test
    public void testBug25894() throws Exception {
        createTable("bug25894",
                "(tinyInt_type TINYINT DEFAULT 1, tinyIntU_type TINYINT UNSIGNED DEFAULT 1, smallInt_type SMALLINT DEFAULT 1,"
                        + "smallIntU_type SMALLINT UNSIGNED DEFAULT 1, mediumInt_type MEDIUMINT DEFAULT 1, mediumIntU_type MEDIUMINT UNSIGNED DEFAULT 1,"
                        + "int_type INT DEFAULT 1, intU_type INT UNSIGNED DEFAULT 1, bigInt_type BIGINT DEFAULT 1, bigIntU_type BIGINT UNSIGNED DEFAULT 1);");
        this.stmt.executeUpdate("INSERT INTO bug25894 VALUES (-1,1,-1,1,-1,1,-1,1,-1,1)");
        this.rs = this.stmt.executeQuery("SELECT * FROM bug25894");
        java.sql.ResultSetMetaData tblMD = this.rs.getMetaData();
        this.rs.first();
        for (int i = 1; i < tblMD.getColumnCount() + 1; i++) {
            String typesName = "";
            switch (tblMD.getColumnType(i)) {
                case Types.INTEGER:
                    typesName = "Types.INTEGER";
                    break;
                case Types.TINYINT:
                    typesName = "Types.TINYINT";
                    break;
                case Types.BIGINT:
                    typesName = "Types.BIGINT";
                    break;
                case Types.SMALLINT:
                    typesName = "Types.SMALLINT";
                    break;
            }

            System.out.println(i + " .fld: " + tblMD.getColumnName(i) + "T: " + typesName + ", MDC: " + tblMD.getColumnClassName(i) + " "
                    + tblMD.getColumnTypeName(i) + " , getObj: " + this.rs.getObject(i).getClass());
        }
    }

    /**
     * Tests fix for BUG#26173 - fetching rows via cursor retrieves corrupted data.
     *
     * @throws Exception
     */
    @Test
    public void testBug26173() throws Exception {
        createTable("testBug26173", "(fkey int, fdate date, fprice decimal(15, 2), fdiscount decimal(5,3))", "InnoDB");
        this.stmt.executeUpdate("insert into testBug26173 values (1, '2007-02-23', 99.9, 0.02)");

        Connection fetchConn = null;
        Statement stmtRead = null;

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
        props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");

        try {

            fetchConn = getConnectionWithProps(props);
            stmtRead = fetchConn.createStatement();
            stmtRead.setFetchSize(1000);

            this.rs = stmtRead.executeQuery("select extract(year from fdate) as fyear, fprice * (1 - fdiscount) as fvalue from testBug26173");

            assertTrue(this.rs.next());
            assertEquals(2007, this.rs.getInt(1));
            assertEquals("97.90200", this.rs.getString(2));
        } finally {
            if (stmtRead != null) {
                stmtRead.close();
            }

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

    /**
     * Tests fix for BUG#26789 - fast date/time parsing doesn't take into account 00:00:00 as a legal value.
     *
     * @throws Exception
     */
    @Test
    public void testBug26789() throws Exception {
        this.rs = this.stmt.executeQuery("SELECT '00:00:00'");
        this.rs.next();
        this.rs.getTime(1);
        assertEquals("00:00:00", this.rs.getTime(1).toString());
        assertEquals("1970-01-01 00:00:00.0", this.rs.getTimestamp(1).toString());

        this.rs.close();

        this.rs = this.stmt.executeQuery("SELECT '00/00/0000 00:00:00'");
        this.rs.next();

        try {
            this.rs.getTime(1);
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }

        try {
            this.rs.getTimestamp(1);
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST, sqlEx.getSQLState());
        }
    }

    /**
     * Tests fix for BUG#27317 - column index < 1 returns misleading error message.
     *
     * @throws Exception
     */
    @Test
    public void testBug27317() throws Exception {
        this.rs = this.stmt.executeQuery("SELECT NULL");
        this.rs.next();
        String messageLowBound = null;

        Method[] getterMethods = ResultSet.class.getMethods();
        Integer zeroIndex = new Integer(0);
        Integer twoIndex = new Integer(2);

        for (int i = 0; i < getterMethods.length; i++) {
            Class<?>[] parameterTypes = getterMethods[i].getParameterTypes();

            if (getterMethods[i].getName().startsWith("get") && parameterTypes.length == 1
                    && (parameterTypes[0].equals(Integer.TYPE) || parameterTypes[0].equals(Integer.class))) {
                if (getterMethods[i].getName().equals("getRowId")) {
                    continue; // we don't support this yet, ever?
                }

                try {
                    getterMethods[i].invoke(this.rs, new Object[] { zeroIndex });
                } catch (InvocationTargetException invokeEx) {
                    Throwable ex = invokeEx.getTargetException();

                    if (ex != null && ex instanceof SQLException) {
                        SQLException sqlEx = (SQLException) ex;

                        assertEquals(MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, sqlEx.getSQLState());

                        messageLowBound = sqlEx.getMessage();
                    } else {
                        throw new RuntimeException(Util.stackTraceToString(ex), ex);
                    }
                }

                String messageHighBound = null;

                try {
                    getterMethods[i].invoke(this.rs, new Object[] { twoIndex });
                } catch (InvocationTargetException invokeEx) {
                    Throwable ex = invokeEx.getTargetException();

                    if (ex != null && ex instanceof SQLException) {
                        SQLException sqlEx = (SQLException) ex;

                        assertEquals(MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, sqlEx.getSQLState());

                        messageHighBound = sqlEx.getMessage();
                    } else {
                        throw new RuntimeException(ex);
                    }
                }

                assertNotNull(messageHighBound, "Exception message null for method " + getterMethods[i]);
                assertNotNull(messageLowBound, "Exception message null for method " + getterMethods[i]);

                assertTrue(!messageHighBound.equals(messageLowBound));
            }
        }
    }

    /**
     * Tests fix for BUG#28085 - Need more useful error messages for diagnostics when the driver thinks a result set isn't updatable.
     *
     * @throws Exception
     */
    @Test
    public void testBug28085() throws Exception {
        Statement updStmt = null;

        try {
            createTable("testBug28085_oneKey", "(pk int primary key not null, field2 varchar(3))");

            this.stmt.executeUpdate("INSERT INTO testBug28085_oneKey (pk, field2) VALUES (1, 'abc')");

            createTable("testBug28085_multiKey", "(pk1 int not null, pk2 int not null, field2 varchar(3), primary key (pk1, pk2))");

            this.stmt.executeUpdate("INSERT INTO testBug28085_multiKey VALUES (1,2,'abc')");

            createTable("testBug28085_noKey", "(field1 varchar(3) not null)");

            this.stmt.executeUpdate("INSERT INTO testBug28085_noKey VALUES ('abc')");

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

            this.rs = updStmt.executeQuery("SELECT field2 FROM testBug28085_oneKey");
            exerciseUpdatableResultSet(1, "NotUpdatableReason.4");

            this.rs = updStmt.executeQuery("SELECT pk1, field2 FROM testBug28085_multiKey");
            this.rs.next();
            exerciseUpdatableResultSet(1, "NotUpdatableReason.7");

            this.rs = updStmt.executeQuery("SELECT t1.field2, t1.pk, t2.pk1 FROM testBug28085_oneKey t1 INNER JOIN testBug28085_multiKey t2 ON t1.pk = t2.pk1");
            exerciseUpdatableResultSet(1, "NotUpdatableReason.0");

            this.rs = updStmt.executeQuery("SELECT field1 FROM testBug28085_noKey");
            exerciseUpdatableResultSet(1, "NotUpdatableReason.5");

            this.rs = updStmt.executeQuery("SELECT 1");
            exerciseUpdatableResultSet(1, "NotUpdatableReason.3");

            this.rs = updStmt.executeQuery("SELECT pk1, pk2, LEFT(field2, 2) FROM testBug28085_multiKey");
            this.rs.next();
            exerciseUpdatableResultSet(1, "NotUpdatableReason.3");
        } finally {
            if (updStmt != null) {
                updStmt.close();
            }
        }
    }

    private void exerciseUpdatableResultSet(int columnUpdateIndex, String messageToCheck) throws Exception {
        this.rs.next();

        try {
            this.rs.updateString(columnUpdateIndex, "def");
        } catch (SQLException sqlEx) {
            checkUpdatabilityMessage(sqlEx, messageToCheck);
        }

        try {
            this.rs.moveToInsertRow();
        } catch (SQLException sqlEx) {
            checkUpdatabilityMessage(sqlEx, messageToCheck);
        }

        try {
            this.rs.deleteRow();
        } catch (SQLException sqlEx) {
            checkUpdatabilityMessage(sqlEx, messageToCheck);
        }

        this.rs.close();
    }

    private void checkUpdatabilityMessage(SQLException sqlEx, String messageToCheck) throws Exception {
        String message = sqlEx.getMessage();

        assertNotNull(message);

        String localizedMessage = Messages.getString(messageToCheck);

        assertTrue(message.indexOf(localizedMessage) != -1, "Didn't find required message component '" + localizedMessage + "', instead found:\n\n" + message);
    }

    @Test
    public void testBug24886() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.blobsAreStrings.getKeyName(), "true");

        Connection noBlobConn = getConnectionWithProps(props);

        createTable("testBug24886", "(sepallength double, sepalwidth double, petallength double, petalwidth double, Class mediumtext, fy TIMESTAMP)");

        noBlobConn.createStatement().executeUpdate("INSERT INTO testBug24886 VALUES (1,2,3,4,'1234', now()),(5,6,7,8,'12345678', now())");
        this.rs = noBlobConn.createStatement()
                .executeQuery("SELECT concat(Class,petallength), COUNT(*) FROM `testBug24886` GROUP BY `concat(Class,petallength)`");
        this.rs.next();
        assertEquals("java.lang.String", this.rs.getObject(1).getClass().getName());

        props.clear();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.functionsNeverReturnBlobs.getKeyName(), "true");
        noBlobConn = getConnectionWithProps(props);
        this.rs = noBlobConn.createStatement()
                .executeQuery("SELECT concat(Class,petallength), COUNT(*) FROM `testBug24886` GROUP BY `concat(Class,petallength)`");
        this.rs.next();

        assertEquals("java.lang.String", this.rs.getObject(1).getClass().getName());
    }

    /**
     * Tests fix for BUG#30664. Note that this fix only works for MySQL server 5.0.25 and newer, since earlier versions didn't consistently return correct
     * metadata for functions, and thus results from subqueries an functions were indistinguishable from each other, leading to type-related bugs.
     *
     * @throws Exception
     */
    @Test
    public void testBug30664() throws Exception {
        createTable("testBug30664_1", "(id int)");
        createTable("testBug30664_2", "(id int, binaryvalue varbinary(255))");

        this.stmt.executeUpdate("insert into testBug30664_1 values (1),(2),(3)");
        this.stmt.executeUpdate("insert into testBug30664_2 values (1,'���'),(2,'����'),(3,' ���')");
        this.rs = this.stmt.executeQuery("select testBug30664_1.id, (select testBug30664_2.binaryvalue from testBug30664_2 "
                + "where testBug30664_2.id=testBug30664_1.id) as value from testBug30664_1");
        ResultSetMetaData tblMD = this.rs.getMetaData();

        for (int i = 1; i < tblMD.getColumnCount() + 1; i++) {
            switch (i) {
                case 1:
                    assertEquals("INT", tblMD.getColumnTypeName(i).toUpperCase());
                    break;
                case 2:
                    assertEquals("VARBINARY", tblMD.getColumnTypeName(i).toUpperCase());
                    break;
            }
        }
    }

    /**
     * Tests fix for BUG#30851, NPE with null column values when "padCharsWithSpace" is set to "true".
     *
     * @throws Exception
     */
    @Test
    public void testBug30851() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.padCharsWithSpace.getKeyName(), "true");
        Connection padConn = getConnectionWithProps(props);

        try {
            createTable("bug30851", "(CharCol CHAR(10) DEFAULT NULL)");
            this.stmt.execute("INSERT INTO bug30851 VALUES (NULL)");
            this.rs = padConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).executeQuery("SELECT * FROM bug30851");
            this.rs.first();
            String strvar = this.rs.getString(1);
            assertNull(strvar, "Should be null");

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

    /**
     * Tests fix for Bug#33678 - Multiple result sets not supported in "streaming" mode. This fix covers both normal statements, and stored procedures, with the
     * exception of stored procedures with registered OUTPUT parameters, which can't be used at all with "streaming" result sets.
     *
     * @throws Exception
     */
    @Test
    public void testBug33678() throws Exception {
        createTable("testBug33678", "(field1 INT)");

        Connection multiConn = getConnectionWithProps("allowMultiQueries=true,useSSL=false,allowPublicKeyRetrieval=true");
        Statement multiStmt = multiConn.createStatement();

        try {
            multiStmt.setFetchSize(Integer.MIN_VALUE);

            multiStmt.execute("SELECT 1 UNION SELECT 2; INSERT INTO testBug33678 VALUES (1); UPDATE testBug33678 set field1=2; "
                    + "INSERT INTO testBug33678 VALUES(3); UPDATE testBug33678 set field1=2 WHERE field1=3; UPDATE testBug33678 set field1=2; SELECT 1");
            this.rs = multiStmt.getResultSet();
            this.rs.next();
            assertEquals("1", this.rs.getString(1));

            assertFalse(multiStmt.getMoreResults());
            assertEquals(1, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults());
            assertEquals(1, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults());
            assertEquals(1, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults());
            assertEquals(1, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults());
            assertEquals(2, multiStmt.getUpdateCount());
            assertTrue(multiStmt.getMoreResults());
            this.rs = multiStmt.getResultSet();
            this.rs.next();
            assertEquals("1", this.rs.getString(1));

            this.rs.close();

            multiStmt.execute("INSERT INTO testBug33678 VALUES (1); INSERT INTO testBug33678 VALUES (1), (2); INSERT INTO testBug33678 VALUES (1), (2), (3)");

            assertEquals(1, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults());
            assertEquals(2, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults());
            assertEquals(3, multiStmt.getUpdateCount());
            assertFalse(multiStmt.getMoreResults() && multiStmt.getUpdateCount() == -1);

            this.rs.close();

            createProcedure("spBug33678", "() BEGIN SELECT 1; SELECT 2; SELECT 3; END");

            CallableStatement cStmt = multiConn.prepareCall("{CALL spBug33678()}");
            cStmt.setFetchSize(Integer.MIN_VALUE);
            cStmt.execute();

            for (int i = 0; i < 2; i++) {
                if (i != 0) {
                    assertTrue(cStmt.getMoreResults());
                }

                this.rs = cStmt.getResultSet();
                assertTrue(this.rs.next());
                assertEquals(i + 1, this.rs.getInt(1));
            }
        } finally {
            multiStmt.close();
            multiConn.close();
        }
    }

    @Test
    public void testBug33162() throws Exception {
        this.rs = this.stmt.executeQuery("select now() from dual where 1=0");
        this.rs.next();
        try {
            this.rs.getTimestamp(1); // fails
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, sqlEx.getSQLState());
        }
    }

    @Test
    public void testBug34762() throws Exception {
        createTable("testBug34762", "(field1 TIMESTAMP)");
        int numRows = 10;

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

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

        while (this.rs.next()) {
            this.rs.getTimestamp(1);
        }

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

        for (int i = 1; i <= numRows; i++) {
            this.rs.absolute(i);
            this.rs.getTimestamp(1);
        }

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

        this.rs.last();
        this.rs.getTimestamp(1);

        while (this.rs.previous()) {
            this.rs.getTimestamp(1);
        }

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

        this.rs.last();

        while (this.rs.relative(-1)) {
            this.rs.getTimestamp(1);
        }

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

        this.rs.beforeFirst();

        while (this.rs.relative(1)) {
            this.rs.getTimestamp(1);
        }
    }

    /**
     * @throws Exception
     * @deprecated because we use deprecated methods
     */
    @Deprecated
    @Test
    public void testBug34913() throws Exception {
        Timestamp ts = new Timestamp(new Date(109, 5, 1).getTime());

        this.pstmt = ((com.mysql.cj.jdbc.JdbcConnection) this.conn).serverPrepareStatement("SELECT 'abcdefghij', ?");
        this.pstmt.setTimestamp(1, ts);
        this.rs = this.pstmt.executeQuery();
        this.rs.next();
        assertTrue(this.rs.getTimestamp(2).getMonth() == 5);
        assertTrue(this.rs.getTimestamp(2).getDate() == 1);
    }

    /**
     * Test Bug#36051.
     * NOTE: This behavior changed in Connector/J 6.0. Rollover is no longer supported for java.sql.Time.
     *
     * @throws Exception
     */
    @Test
    public void testBug36051() throws Exception {
        try {
            this.rs = this.stmt.executeQuery("SELECT '24:00:00'");
            this.rs.next();
            this.rs.getTime(1);
        } catch (SQLException sqlEx) {
            assertEquals(MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, sqlEx.getSQLState());
        }
    }

    /**
     * Tests fix for BUG#35610, BUG#35150. We follow the JDBC Spec here, in that the 4.0 behavior is correct, the JDBC-3.0 (and earlier) spec has a bug, but you
     * can get the buggy behavior (allowing column names *and* labels to be used) by setting "useColumnNamesInFindColumn" to "true".
     *
     * @throws Exception
     */
    @Test
    public void testBug35610() throws Exception {
        createTable("testBug35610", "(field1 int, field2 int, field3 int)");
        this.stmt.executeUpdate("INSERT INTO testBug35610 VALUES (1, 2, 3)");
        exercise35610(this.stmt, false);
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useColumnNamesInFindColumn.getKeyName(), "true");
        exercise35610(getConnectionWithProps(props).createStatement(), true);
    }

    private void exercise35610(Statement configuredStmt, boolean force30Behavior) throws Exception {
        this.rs = configuredStmt.executeQuery("SELECT field1 AS f1, field2 AS f2, field3 FROM testBug35610");

        ResultSetMetaData rsmd = this.rs.getMetaData();

        assertEquals("field1", rsmd.getColumnName(1));
        assertEquals("field2", rsmd.getColumnName(2));
        assertEquals("f1", rsmd.getColumnLabel(1));
        assertEquals("f2", rsmd.getColumnLabel(2));

        assertEquals("field3", rsmd.getColumnName(3));
        assertEquals("field3", rsmd.getColumnLabel(3));

        this.rs.next();

        // From ResultSet.html#getInt(java.lang.String) in JDBC-4.0
        //
        // Retrieves the value of the designated column in the current row of
        // this ResultSet
        // object as an int in the Java programming language.
        //
        // Parameters:
        // columnLabel - the label for the column specified with the SQL AS
        // clause. If the
        // SQL AS clause was not specified, then the label is the name of the
        // column
        //

        assertEquals(1, this.rs.getInt("f1"));
        assertEquals(2, this.rs.getInt("f2"));
        assertEquals(3, this.rs.getInt("field3"));

        // Pre-JDBC 4.0, some versions of the spec say "column name *or* label"
        // for the column name argument...

        if (force30Behavior) {
            assertEquals(1, this.rs.getInt("field1"));
            assertEquals(2, this.rs.getInt("field2"));
        }

        if (!force30Behavior) {
            assertThrows("findColumn(\"field1\" should have failed with an exception", SQLException.class, () -> {
                this.rs.findColumn("field1");
                return null;
            });
            assertThrows("findColumn(\"field2\" should have failed with an exception", SQLException.class, () -> {
                this.rs.findColumn("field2");
                return null;
            });
        }
    }

    /**
     * Tests fix for BUG#39911 - We don't retrieve nanos correctly when -parsing- a string for a TIMESTAMP.
     *
     * @throws Exception
     */
    @Test
    public void testBug39911() throws Exception {
        this.rs = this.stmt.executeQuery("SELECT '2008-09-26 15:47:20.797283'");
        this.rs.next();

        checkTimestampNanos();

        this.rs = ((com.mysql.cj.jdbc.JdbcConnection) this.conn).serverPrepareStatement("SELECT '2008-09-26 15:47:20.797283'").executeQuery();
        this.rs.next();

        checkTimestampNanos();

        this.rs.close();
    }

    private void checkTimestampNanos() throws SQLException {
        Timestamp ts = this.rs.getTimestamp(1);
        assertEquals(797283000, ts.getNanos());
        Calendar cal = Calendar.getInstance();
        cal.setTime(ts);
        assertEquals(797, cal.get(Calendar.MILLISECOND));
    }

    @Test
    public void testBug38387() throws Exception {
        Connection noBlobConn = null;
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.functionsNeverReturnBlobs.getKeyName(), "true");// toggle, no change
        noBlobConn = getConnectionWithProps(props);
        try {
            Statement noBlobStmt = noBlobConn.createStatement();
            this.rs = noBlobStmt.executeQuery("SELECT TRIM(1) AS Rslt");
            while (this.rs.next()) {
                assertEquals("1", this.rs.getString("Rslt"));
                assertEquals("java.lang.String", this.rs.getObject(1).getClass().getName());
            }
        } finally {
            noBlobConn.close();
        }
    }

    @Test
    public void testRanges() throws Exception {
        createTable("testRanges", "(int_field INT, long_field BIGINT, double_field DOUBLE, string_field VARCHAR(32))");

        this.pstmt = this.conn.prepareStatement("INSERT INTO testRanges VALUES (?,?,?, ?)");
        this.pstmt.setInt(1, Integer.MIN_VALUE);
        this.pstmt.setLong(2, Long.MIN_VALUE);
        this.pstmt.setDouble(3, Long.MAX_VALUE + 1D);
        this.pstmt.setString(4, "1E4");

        this.pstmt.executeUpdate();

        checkRangeMatrix(this.conn);
    }

    private void checkRangeMatrix(Connection c) throws Exception {
        this.rs = c.createStatement().executeQuery("SELECT int_field, long_field, double_field, string_field FROM testRanges");
        this.rs.next();
        checkRanges();
        this.rs.close();

        this.pstmt = ((com.mysql.cj.jdbc.JdbcConnection) c).serverPrepareStatement("SELECT int_field, long_field, double_field, string_field FROM testRanges");
        this.rs = this.pstmt.executeQuery();
        this.rs.next();
        checkRanges();
        this.rs.close();

        this.pstmt.setFetchSize(Integer.MIN_VALUE);
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        checkRanges();
        this.rs.close();

        this.pstmt = ((com.mysql.cj.jdbc.JdbcConnection) c).clientPrepareStatement("SELECT int_field, long_field, double_field, string_field FROM testRanges");
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        checkRanges();
        this.rs.close();

        this.pstmt.setFetchSize(Integer.MIN_VALUE);
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        checkRanges();
        this.rs.close();
    }

    private void checkRanges() throws SQLException {
        assertEquals(Integer.MIN_VALUE, this.rs.getInt(1));

        try {
            this.rs.getInt(2);
        } catch (SQLException sqlEx) {
            assertTrue(sqlEx.getMessage().indexOf(" is outside of valid range") != -1);
        }

        assertEquals(Long.MIN_VALUE, this.rs.getLong(2));

        try {
            this.rs.getLong(3);
        } catch (SQLException sqlEx) {
            assertTrue(sqlEx.getMessage().indexOf(" is outside of valid range") != -1);
        }

        assertEquals(10000, this.rs.getInt(4));
        assertEquals(10000, this.rs.getLong(4));
    }

    /**
     * Bug #41484 Accessing fields by name after the ResultSet is closed throws NullPointerException.
     *
     * @throws Exception
     */
    @Test
    public void testBug41484() throws Exception {
        try {
            this.rs = this.stmt.executeQuery("select 1 as abc");
            this.rs.next();
            this.rs.getString("abc");
            this.rs.close();
            this.rs.getString("abc");
        } catch (SQLException ex) {
            /* expected */
            assertEquals(0, ex.getErrorCode());
            assertEquals("S1000", ex.getSQLState());
        }
    }

    @Test
    public void testBug41484_2() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), "true");
        Connection cachedRsmdConn = getConnectionWithProps(props);

        try {
            createTable("bug41484", "(id int not null primary key, day date not null) DEFAULT CHARSET=utf8");
            this.pstmt = cachedRsmdConn.prepareStatement("INSERT INTO bug41484(id, day) values(1, ?)");
            this.pstmt.setInt(1, 20080509);
            assertEquals(1, this.pstmt.executeUpdate());
            this.pstmt.close();

            this.pstmt = cachedRsmdConn.prepareStatement("SELECT * FROM bug41484 WHERE id = ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
            this.pstmt.setInt(1, 1);
            this.rs = this.pstmt.executeQuery();
            this.rs.first();
            this.rs.getString("day");
            this.rs.close();
            this.pstmt.close();

            this.pstmt = cachedRsmdConn.prepareStatement("INSERT INTO bug41484(id, day) values(2, ?)");
            this.pstmt.setInt(1, 20090212);
            assertEquals(1, this.pstmt.executeUpdate());
            this.pstmt.close();

            this.pstmt = cachedRsmdConn.prepareStatement("SELECT * FROM bug41484 WHERE id = ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
            this.pstmt.setInt(1, 2);
            this.rs = this.pstmt.executeQuery();
            this.rs.first();
            assertEquals(this.rs.getString(1), "2");
            this.rs.getString("day");
            this.rs.close();

            this.pstmt.close();
        } finally {
            cachedRsmdConn.close();
        }
    }

    @Test
    public void testBug27431() throws Exception {
        createTable("bug27431", "(`ID` int(20) NOT NULL auto_increment, `Name` varchar(255) NOT NULL default '', PRIMARY KEY  (`ID`))");

        this.stmt.executeUpdate("INSERT INTO bug27431 (`ID`, `Name`) VALUES 	(1, 'Lucho'),(2, 'Lily'),(3, 'Kiro')");

        Statement updStmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        this.rs = updStmt.executeQuery("SELECT ID, Name FROM bug27431");

        while (this.rs.next()) {
            this.rs.deleteRow();
        }

        assertEquals(0, getRowCount("bug27431"));
    }

    @Test
    public void testBug43759() throws Exception {
        createTable("testtable_bincolumn", "(bincolumn binary(8) NOT NULL, PRIMARY KEY (bincolumn))", "innodb");

        String pkValue1 = "0123456789ABCD90";
        String pkValue2 = "0123456789ABCD00";
        // put some data in it
        this.stmt.executeUpdate("INSERT INTO testtable_bincolumn (bincolumn) VALUES (unhex('" + pkValue1 + "')), (unhex('" + pkValue2 + "'))");

        // cause the bug
        Statement updStmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        this.rs = updStmt.executeQuery("SELECT * FROM testtable_bincolumn WHERE bincolumn = unhex('" + pkValue1 + "')");
        assertTrue(this.rs.next());
        this.rs.deleteRow();

        // At this point the row with pkValue1 should be deleted. We'll select
        // it back to see.
        // If the row comes back, the testcase has failed.

        this.rs = this.stmt.executeQuery("SELECT * FROM testtable_bincolumn WHERE bincolumn = unhex('" + pkValue1 + "')");
        assertFalse(this.rs.next());

        // Now, show a case where it happens to work, because the binary data is
        // different
        updStmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        this.rs = updStmt.executeQuery("SELECT * FROM testtable_bincolumn WHERE bincolumn = unhex('" + pkValue2 + "')");
        assertTrue(this.rs.next());
        this.rs.deleteRow();

        this.rs = this.stmt.executeQuery("SELECT * FROM testtable_bincolumn WHERE bincolumn = unhex('" + pkValue2 + "')");
        assertFalse(this.rs.next());
    }

    @Test
    public void testBug32525() throws Exception {
        Connection testConn = this.conn;
        Statement st = this.stmt;

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.noDatetimeStringSync.getKeyName(), "true");
        Connection noStringSyncConn = getConnectionWithProps(props);
        try {
            if (versionMeetsMinimum(5, 7, 4)) {
                Properties props2 = new Properties();
                props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
                props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
                props2.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false");
                if (versionMeetsMinimum(5, 7, 5)) {
                    String sqlMode = getMysqlVariable("sql_mode");
                    if (sqlMode.contains("STRICT_TRANS_TABLES")) {
                        sqlMode = removeSqlMode("STRICT_TRANS_TABLES", sqlMode);
                        props2.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'");
                    }
                }
                testConn = getConnectionWithProps(props2);
                st = testConn.createStatement();
            }

            createTable("bug32525", "(field1 date, field2 timestamp)");
            st.executeUpdate("INSERT INTO bug32525 VALUES ('0000-00-00', '0000-00-00 00:00:00')");

            this.rs = ((com.mysql.cj.jdbc.JdbcConnection) noStringSyncConn).serverPrepareStatement("SELECT field1, field2 FROM bug32525").executeQuery();
            this.rs.next();
            assertEquals("0000-00-00", this.rs.getString(1));
            assertEquals("0000-00-00 00:00:00", this.rs.getString(2));
        } finally {
            noStringSyncConn.close();
            if (testConn != this.conn) {
                testConn.close();
            }
        }
    }

    @Test
    public void testBug49797() throws Exception {
        createTable("testBug49797", "(`Id` int(2) not null auto_increment, `abc` char(50) , PRIMARY KEY (`Id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8");
        this.stmt.executeUpdate("INSERT into testBug49797 VALUES (1,'1'),(2,'2'),(3,'3')");
        assertEquals(3, getRowCount("testBug49797"));

        Statement updStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        try {
            this.rs = updStmt.executeQuery("SELECT * FROM testBug49797");
            while (this.rs.next()) {
                this.rs.deleteRow();
            }
            assertEquals(0, getRowCount("testBug49797"));
        } finally {
            updStmt.close();
        }
    }

    @Test
    public void testBug49516() throws Exception {
        CachedRowSet crs;

        createTable("bug49516", "(`testingID` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `firstName` TEXT NOT NULL) CHARACTER SET utf8;");
        this.stmt.executeUpdate("insert into bug49516 set firstName ='John'");

        this.rs = this.stmt.executeQuery("select firstName as 'first person' from bug49516");
        this.rs.first();
        assertEquals("John", this.rs.getString("first person"));
        // this.rs.close();
        // this.stmt.close();

        this.rs = this.stmt.executeQuery("select firstName as 'first person' from bug49516");

        crs = (CachedRowSet) Class.forName("com.sun.rowset.CachedRowSetImpl").newInstance();
        crs.populate(this.rs);
        crs.first();

        assertEquals("John", crs.getString(1));
    }

    @Test
    public void testBug48820() throws Exception {
        assumeTrue(!versionMeetsMinimum(8, 0, 5), "Old_passwords and PASSWORD() were removed since MySQL 8.0.5");

        CachedRowSet crs;

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.functionsNeverReturnBlobs.getKeyName(), "true");
        Connection noBlobsConn = getConnectionWithProps(props);

        if (versionMeetsMinimum(5, 6, 6)) {
            this.rs = noBlobsConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'old_passwords'");
            if (this.rs.next()) {
                assumeTrue(this.rs.getInt(2) != 2, "Skip testBug48820 due to SHA-256 password hashing.");
            }
        }

        this.rs = noBlobsConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).executeQuery("SELECT PASSWORD ('SOMETHING')");
        this.rs.first();

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

        this.rs = noBlobsConn.createStatement().executeQuery("SELECT PASSWORD ('SOMETHING')");

        crs = (CachedRowSet) Class.forName("com.sun.rowset.CachedRowSetImpl").newInstance();
        crs.populate(this.rs);
        crs.first();

        assertEquals(fromPlainResultSet, crs.getString(1));
    }

    /**
     * Bug #60313 bug in com.mysql.jdbc.ResultSetRow.getTimestampFast
     *
     * @throws Exception
     */
    @Test
    public void testBug60313() throws Exception {
        this.stmt.execute("select repeat('Z', 3000), now() + interval 1 microsecond");
        this.rs = this.stmt.getResultSet();
        assertTrue(this.rs.next());
        assertEquals(1000, this.rs.getTimestamp(2).getNanos());
        this.rs.close();

        this.pstmt = this.conn.prepareStatement("select repeat('Z', 3000), now() + interval 1 microsecond");
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        assertEquals(1000, this.rs.getTimestamp(2).getNanos());
        this.rs.close();

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
        Connection sspsCon = getConnectionWithProps(props);
        PreparedStatement ssPStmt = sspsCon.prepareStatement("select repeat('Z', 3000), now() + interval 1 microsecond");
        this.rs = ssPStmt.executeQuery();
        assertTrue(this.rs.next());
        assertEquals(1000, this.rs.getTimestamp(2).getNanos());
        this.rs.close();
        ssPStmt.close();
        sspsCon.close();
    }

    /**
     * Tests fix for BUG#65503 - ResultSets created by PreparedStatement.getGeneratedKeys() are not close()d.
     *
     * To get results quicker add option -Xmx10M, with this option I got an out of memory failure after about 6500 passes.
     *
     * @throws Exception
     */
    @Test
    @Disabled("It's a very long test")
    public void testBug65503() throws Exception {
        createTable("testBug65503", "(id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, value INTEGER)");

        PreparedStatement pStmt = this.conn.prepareStatement("INSERT INTO testBug65503 (value) VALUES (?)", Statement.RETURN_GENERATED_KEYS),
                stmt2 = this.conn.prepareStatement("SELECT * FROM testBug65503 LIMIT 6");
        for (int i = 0; i < 100000000; ++i) {
            pStmt.setString(1, "48");
            pStmt.executeUpdate();

            ResultSet result = pStmt.getGeneratedKeys();
            result.next();
            result.getInt(1);
            result.next();

            result = stmt2.executeQuery();
            while (result.next()) {
            }

            if (i % 500 == 0) {
                System.out.printf("free-mem: %d, id: %d\n", Runtime.getRuntime().freeMemory() / 1024 / 1024, i);
                this.conn.createStatement().execute("TRUNCATE TABLE testBug65503");
            }
        }
    }

    /**
     * Tests fix for BUG#64204 - ResultSet.close hangs if streaming query is killed
     *
     * @throws Exception
     */
    @Test
    public void testBug64204() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.socketTimeout.getKeyName(), "30000");

        Connection con = null;
        try {
            con = getConnectionWithProps(props);

            if (((JdbcConnection) con).getPropertySet().<DatabaseTerm>getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) {
                con.setSchema("information_schema");
            } else {
                con.setCatalog("information_schema");
            }
            con.setAutoCommit(true);

            Statement st = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            st.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode

            this.rs = st.executeQuery("SELECT CONNECTION_ID()");
            this.rs.next();
            final String connectionId = this.rs.getString(1);
            this.rs.close();

            System.out.println("testBug64204.main: PID is " + connectionId);

            ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor();
            es.schedule(() -> {
                boolean res = false;
                Connection con2 = getConnectionWithProps(props);
                if (((JdbcConnection) con2).getPropertySet().<DatabaseTerm>getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) {
                    con2.setSchema("information_schema");
                } else {
                    con2.setCatalog("information_schema");
                }
                con2.setAutoCommit(true);

                Statement st2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                st2.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode
                try {
                    System.out.println("testBug64204.replica: Running KILL QUERY " + connectionId);
                    st2.execute("KILL QUERY " + connectionId + ";");

                    Thread.sleep(5000);
                    System.out.println("testBug64204.replica: parent thread should be hung now!!!");
                    res = true;
                } finally {
                    st2.close();
                    con2.close();
                }

                System.out.println("testBug64204.replica: Done.");
                return res;
            }, 10, TimeUnit.SECONDS);

            try {
                this.rs = st.executeQuery("SELECT sleep(5) FROM character_sets LIMIT 10");

                int rows = 0;
                int columnCount = this.rs.getMetaData().getColumnCount();
                System.out.println("testBug64204.main: fetched result set, " + columnCount + " columns");

                long totalDataCount = 0;
                while (this.rs.next()) {
                    rows++;
                    //get row size
                    long rowSize = 0;
                    for (int i = 0; i < columnCount; i++) {
                        String s = this.rs.getString(i + 1);
                        if (s != null) {
                            rowSize += s.length();
                        }
                    }
                    totalDataCount += rowSize;
                }

                System.out.println("testBug64204.main: character_sets total rows " + rows + ", data " + totalDataCount);

            } catch (SQLException se) {
                assertEquals("70100", se.getSQLState(), "ER_QUERY_INTERRUPTED expected.");
                if (!"70100".equals(se.getSQLState())) {
                    throw se;
                }
            }
        } finally {
            if (con != null) {
                con.close();
                con = null;
            }
        }
    }

    /**
     * Bug #45757 - ResultSet.updateRow should throw SQLException when cursor is on insert row
     *
     * @throws SQLException
     */
    @Test
    public void testBug45757() throws SQLException {
        createTable("bug45757", "(id INTEGER NOT NULL PRIMARY KEY)");
        this.stmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        this.rs = this.stmt.executeQuery("select id from bug45757");
        this.rs.moveToInsertRow();
        assertThrows("updateRow() should throw an exception, not allowed to be called on insert row", SQLException.class,
                "Can not call updateRow\\(\\) when on insert row.*", () -> {
                    this.rs.updateRow();
                    return null;
                });
    }

    /**
     * Tests fix for BUG#38252 - ResultSet.absolute(0) is not behaving according to JDBC specification.
     *
     * @throws Exception
     */
    @Test
    public void testBug38252() throws Exception {
        createTable("testBug38252", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY)");

        this.stmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        this.stmt.executeUpdate("INSERT INTO testBug38252 VALUES (NULL), (NULL)");

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug38252");

        // test ResultSet.absolute(0) before iterating the ResultSet
        assertFalse(this.rs.absolute(0), "Cursor should be moved to before the first row.");
        assertTrue(this.rs.isBeforeFirst(), "ResultSet's cursor should be at 'before first'.");
        assertTrue(this.rs.next(), "First row expected from ResultSet.");
        assertTrue(this.rs.next(), "Second row expected from ResultSet.");
        assertFalse(this.rs.next(), "No more rows expected from ResultSet.");
        assertTrue(this.rs.isAfterLast(), "ResultSet's cursor should be at 'after last'.");

        // test ResultSet.absolute(0) after iterating the ResultSet
        assertFalse(this.rs.absolute(0), "Cursor should be moved to before the first row.");
        assertTrue(this.rs.isBeforeFirst(), "ResultSet's cursor should be at 'before first'.");
        assertTrue(this.rs.next(), "First row expected from ResultSet.");
        assertTrue(this.rs.next(), "Second row expected from ResultSet.");
        assertFalse(this.rs.next(), "No more rows expected from ResultSet.");
        assertTrue(this.rs.isAfterLast(), "ResultSet's cursor should be at 'after last'.");

        this.rs.close();
        this.stmt.close();

        // test ResultSet.absolute(0) with an empty ResultSet
        this.stmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug38252 where 0 = 1");
        assertFalse(this.rs.absolute(0), "Cursor should be moved to before the first row.");
    }

    /**
     * Tests fix for Bug#67318 - SQLException thrown on already closed ResultSet
     *
     * @throws Exception
     */
    @Test
    public void testBug67318() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
        props.setProperty(PropertyKey.exceptionInterceptors.getKeyName(), "testsuite.regression.ResultSetRegressionTest$TestBug67318ExceptionInterceptor");

        Connection c = null;
        try {
            c = getConnectionWithProps(props);
            ExceptionInterceptorChain eic = (ExceptionInterceptorChain) ((JdbcConnection) c).getExceptionInterceptor();

            TestBug67318ExceptionInterceptor ei = null;
            for (ExceptionInterceptor ext : eic.getInterceptors()) {
                if (ext instanceof TestBug67318ExceptionInterceptor) {
                    ei = (TestBug67318ExceptionInterceptor) ext;
                    break;
                }
            }

            assertNotNull(ei, "TestBug67318ExceptionInterceptor is not found on connection");

            Statement st1 = c.createStatement();
            ResultSet rs1 = st1.executeQuery("select 1");
            rs1.close();
            rs1.close();
            assertEquals(0, ei.alreadyClosedCounter, "Operation not allowed after ResultSet closed exception shouldn't be thrown second time");
            st1.close();
            st1.close();
            ((StatementImpl) st1).isClosed();
            assertEquals(0, ei.alreadyClosedCounter, "No operations allowed after statement closed exception shouldn't be thrown second time");

            PreparedStatement ps1 = c.prepareStatement("select 1");
            ps1.close();
            ps1.close();
            assertEquals(0, ei.alreadyClosedCounter, "No operations allowed after statement closed exception shouldn't be thrown second time");

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

    public static class TestBug67318ExceptionInterceptor implements ExceptionInterceptor {

        public int alreadyClosedCounter = 0;

        @Override
        public ExceptionInterceptor init(Properties props, Log log) {
            return this;
        }

        @Override
        public void destroy() {
        }

        @Override
        public SQLException interceptException(Exception sqlEx) {
            sqlEx.printStackTrace();

            if ("Operation not allowed after ResultSet closed".equals(sqlEx.getMessage())
                    || "No operations allowed after statement closed.".equals(sqlEx.getMessage())) {
                this.alreadyClosedCounter++;
            }
            return (SQLException) sqlEx;
        }

    }

    /**
     * Tests fix for BUG#72000 - java.lang.ArrayIndexOutOfBoundsException on java.sql.ResultSet.getInt(String).
     *
     * @throws Exception
     */
    @Test
    public void testBug72000() throws Exception {
        final ResultSet testRS = this.stmt.executeQuery("SELECT ' '");

        assertTrue(testRS.next());

        String errorMessage = "Cannot determine value type from string ' '";

        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getBigDecimal(1);
            return null;
        });
        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getBoolean(1);
            return null;
        });

        assertEquals(' ', testRS.getByte(1));

        assertThrows(SQLException.class, "Cannot convert string ' ' to java.sql.Date value", () -> {
            testRS.getDate(1);
            return null;
        });
        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getDouble(1);
            return null;
        });
        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getFloat(1);
            return null;
        });
        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getInt(1);
            return null;
        });
        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getLong(1);
            return null;
        });
        assertThrows(SQLException.class, errorMessage, () -> {
            testRS.getShort(1);
            return null;
        });
        assertThrows(SQLException.class, "Cannot convert string ' ' to java.sql.Time value", () -> {
            testRS.getTime(1);
            return null;
        });
        assertThrows(SQLException.class, "Cannot convert string ' ' to java.sql.Timestamp value", () -> {
            testRS.getTimestamp(1);
            return null;
        });
    }

    /**
     * Tests fix for BUG#72023 - Avoid byte array creation in MysqlIO#unpackBinaryResultSetRow.
     *
     * @throws Exception
     */
    @Test
    public void testBug72023() throws Exception {
        // null bitmask contains 2 reserved bits plus 1 bit per field
        //
        // boundary cases at 8n - 2 / 8n - 1 field count; e.g. 6/7, 14/15
        String[] selectList = new String[] { "NULL", "1", "NULL,NULL,NULL,NULL,NULL,NULL", "1,NULL,NULL,1,1,NULL", "1,1,1,1,1,1",
                "NULL,NULL,NULL,NULL,NULL,NULL,NULL", "1,1,1,NULL,1,NULL,NULL", "1,1,1,1,1,1,1",
                "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL", "NULL,NULL,NULL,1,NULL,1,NULL,NULL,1,NULL,1,1,NULL,NULL",
                "1,1,1,1,1,1,1,1,1,1,1,1,1,1", "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL",
                "NULL,1,NULL,1,NULL,NULL,1,NULL,1,NULL,NULL,NULL,NULL,1,1", "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" };

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
        Connection testConn = getConnectionWithProps(props);
        PreparedStatement testPstmt;
        ResultSet testRS;

        for (int i = 0, s = selectList.length; i < s; i++) {
            String sl = selectList[i];
            testPstmt = testConn.prepareStatement("SELECT " + sl);
            testRS = testPstmt.executeQuery();
            assertTrue(testRS.next());
            int j = 1;
            for (String fld : sl.split(",")) {
                if (fld.equals("NULL")) {
                    assertNull(testRS.getObject(j), "Bad results for query " + i + ", field " + j);
                } else {
                    assertEquals(1, testRS.getInt(j), "Bad results for query " + i + ", field " + j);
                }
                j++;
            }
            assertFalse(testRS.next());
            testRS.close();
            testPstmt.close();
        }
        testConn.close();
    }

    /**
     * Tests fix for BUG#75309 - mysql connector/J driver in streaming mode will in the blocking state.
     *
     * @throws Exception
     */
    @Test
    public void testBug75309() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.socketTimeout.getKeyName(), "1000");
        Connection testConn = getConnectionWithProps(props);
        Statement testStmt = testConn.createStatement();

        // turn on streaming results.
        testStmt.setFetchSize(Integer.MIN_VALUE);

        final ResultSet testRs1 = testStmt.executeQuery("SELECT 1 + 18446744073709551615");

        assertThrows(SQLException.class, "Data truncation: BIGINT UNSIGNED value is out of range in '\\(1 \\+ 18446744073709551615\\)'", () -> {
            testRs1.next();
            return null;
        });

        try {
            testRs1.close();
        } catch (CJCommunicationsException | CommunicationsException ex) {
            fail("ResultSet.close() locked while trying to read remaining, nonexistent, streamed data.");
        }

        try {
            ResultSet testRs2 = testStmt.executeQuery("SELECT 1");
            assertTrue(testRs2.next());
            assertEquals(1, testRs2.getInt(1));
            testRs2.close();
        } catch (SQLException ex) {
            if (ex.getMessage().startsWith("Streaming result set")) {
                fail("There is a Streaming result set still active. No other statements can be issued on this connection.");
            } else {
                ex.printStackTrace();
                fail(ex.getMessage());
            }
        }

        testStmt.close();
        testConn.close();
    }

    /**
     * Tests fix for BUG#19536760 - GETSTRING() CALL AFTER RS.RELATIVE() RETURNS NULLPOINTEREXCEPTION
     *
     * @throws Exception
     */
    @Test
    public void testBug19536760() throws Exception {
        createTable("testBug19536760", "(id int)");

        this.stmt.execute("insert into testBug19536760 values(1),(2),(3)");
        this.rs = this.stmt.executeQuery("select * from testBug19536760");

        // "before first" check
        testBug19536760CheckStates(this.rs, true, false, false, false);

        assertFalse(this.rs.previous());
        assertFalse(this.rs.previous());
        assertFalse(this.rs.previous());
        testBug19536760CheckStates(this.rs, true, false, false, false);

        assertFalse(this.rs.absolute(-7));
        testBug19536760CheckStates(this.rs, true, false, false, false);

        assertTrue(this.rs.next());
        this.rs.beforeFirst();
        testBug19536760CheckStates(this.rs, true, false, false, false);

        // "first" check
        this.rs.next();
        testBug19536760CheckStates(this.rs, false, true, false, false);

        this.rs.absolute(-3);
        testBug19536760CheckStates(this.rs, false, true, false, false);

        assertTrue(this.rs.relative(1));
        assertTrue(this.rs.previous());
        testBug19536760CheckStates(this.rs, false, true, false, false);

        this.rs.absolute(2);
        testBug19536760CheckStates(this.rs, false, false, false, false);
        this.rs.first();
        testBug19536760CheckStates(this.rs, false, true, false, false);

        // "last" check
        this.rs.absolute(-1);
        testBug19536760CheckStates(this.rs, false, false, true, false);

        assertFalse(this.rs.next());
        testBug19536760CheckStates(this.rs, false, false, false, true);
        assertTrue(this.rs.previous());
        testBug19536760CheckStates(this.rs, false, false, true, false);

        assertFalse(this.rs.relative(1));
        testBug19536760CheckStates(this.rs, false, false, false, true);
        assertTrue(this.rs.relative(-1));
        testBug19536760CheckStates(this.rs, false, false, true, false);

        assertTrue(this.rs.relative(-1));
        testBug19536760CheckStates(this.rs, false, false, false, false);
        this.rs.last();
        testBug19536760CheckStates(this.rs, false, false, true, false);

        // "after last" check
        assertFalse(this.rs.next());
        assertFalse(this.rs.next());
        assertFalse(this.rs.next());
        testBug19536760CheckStates(this.rs, false, false, false, true);

        assertTrue(this.rs.relative(-1));
        testBug19536760CheckStates(this.rs, false, false, true, false);

        assertFalse(this.rs.relative(3));
        testBug19536760CheckStates(this.rs, false, false, false, true);

        assertTrue(this.rs.previous());
        testBug19536760CheckStates(this.rs, false, false, true, false);

        this.rs.afterLast();
        testBug19536760CheckStates(this.rs, false, false, false, true);

        assertFalse(this.rs.next());
        testBug19536760CheckStates(this.rs, false, false, false, true);

        // empty result set
        this.rs = this.stmt.executeQuery("select * from testBug19536760 where id=5");
        assertFalse(this.rs.first());
        assertFalse(this.rs.last());

        testBug19536760CheckStates(this.rs, false, false, false, false);

        assertFalse(this.rs.next());
        testBug19536760CheckStates(this.rs, false, false, false, false);

        assertFalse(this.rs.relative(2));
        testBug19536760CheckStates(this.rs, false, false, false, false);
    }

    private void testBug19536760CheckStates(ResultSet rset, boolean expectedIsBeforeFirst, boolean expectedIsFirst, boolean expectedIsLast,
            boolean expectedIsAfterLast) throws Exception {
        assertEquals(expectedIsBeforeFirst, rset.isBeforeFirst());
        assertEquals(expectedIsFirst, rset.isFirst());
        assertEquals(expectedIsLast, rset.isLast());
        assertEquals(expectedIsAfterLast, rset.isAfterLast());
    }

    /**
     * Tests for fix to BUG#20804635 - GETTIME() AND GETDATE() FUNCTIONS FAILS WHEN FRACTIONAL PART EXISTS
     *
     * @throws Exception
     */
    @Test
    public void testBug20804635() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server");

        createTable("testBug20804635", "(c1 timestamp(2), c2 time(3), c3 datetime(4), c4 time(6))");
        this.stmt.executeUpdate(
                "INSERT INTO testBug20804635 VALUES ('2031-01-15 03:14:07.339999','12:59:00.9889','2031-01-15 03:14:07.333399', '838:59:58.123456')");

        Calendar cal = Calendar.getInstance();

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL");
        Connection testConn = getConnectionWithProps(props);

        this.rs = testConn.createStatement().executeQuery("SELECT * FROM testBug20804635");
        this.rs.next();

        TimeZone serverTz = ((MysqlConnection) testConn).getSession().getServerSession().getSessionTimeZone();
        ZonedDateTime expZdt = LocalDateTime.of(2031, 1, 15, 3, 14, 07, 340000000).atZone(serverTz.toZoneId());
        String expDate = expZdt.withZoneSameInstant(ZoneId.systemDefault()).format(TimeUtil.DATE_FORMATTER);
        String expDateCal = expZdt.withZoneSameLocal(ZoneId.systemDefault()).format(TimeUtil.DATE_FORMATTER);
        String expTime = expZdt.withZoneSameLocal(ZoneId.systemDefault()).format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET);
        String expTimestamp = expZdt.withZoneSameInstant(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"));
        String expTimestampCal = expZdt.withZoneSameLocal(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"));

        assertEquals(expDate, this.rs.getDate(1).toString());
        assertEquals(expDateCal, this.rs.getDate(1, cal).toString());
        assertEquals(expTime, this.rs.getTime(1).toString());
        assertEquals(expTime, this.rs.getTime(1, cal).toString());
        assertEquals(expTimestamp, this.rs.getTimestamp(1).toString());
        assertEquals(expTimestampCal, this.rs.getTimestamp(1, cal).toString());

        assertEquals("12:59:00", this.rs.getTime(2).toString());
        assertEquals("12:59:00", this.rs.getTime(2, cal).toString());
        assertEquals("1970-01-01 12:59:00.989", this.rs.getTimestamp(2).toString());
        assertEquals("1970-01-01 12:59:00.989", this.rs.getTimestamp(2, cal).toString());

        assertEquals("2031-01-15", this.rs.getDate(3).toString());
        assertEquals("2031-01-15", this.rs.getDate(3, cal).toString());
        assertEquals("03:14:07", this.rs.getTime(3).toString());
        assertEquals("03:14:07", this.rs.getTime(3, cal).toString());
        assertEquals("2031-01-15 03:14:07.3334", this.rs.getTimestamp(3).toString());
        assertEquals("2031-01-15 03:14:07.3334", this.rs.getTimestamp(3, cal).toString());

        assertEquals("838:59:58.123456", StringUtils.toString(this.rs.getBytes("c4")));
        assertEquals("838:59:58.123456", this.rs.getString("c4"));
    }

    /**
     * Tests fix for Bug#80522 - Using useCursorFetch leads to data corruption in Connector/J for TIME type.
     *
     * @throws Exception
     */
    @Test
    public void testBug80522() throws Exception {
        createTable("testBug80522", "(t TIME, d DATE, s TEXT)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        String sqlMode = getMysqlVariable("sql_mode");
        if (sqlMode.contains("NO_ZERO_DATE")) {
            sqlMode = removeSqlMode("NO_ZERO_DATE", sqlMode);
            props.put("sessionVariables", "sql_mode='" + sqlMode + "'");
        }
        props.setProperty(PropertyKey.traceProtocol.getKeyName(), "false");
        props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "5");
        props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");
        Connection testConn = getConnectionWithProps(props);
        Statement testStmt = testConn.createStatement();

        testStmt.executeUpdate("INSERT INTO testBug80522 VALUES ('00:00:00', '0000-00-00', 'Zeros')");
        final ResultSet testRs = testStmt.executeQuery("SELECT * FROM testBug80522");
        assertTrue(testRs.next());
        assertEquals(new Timestamp(TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd HH:mm:ss", null).parse("1970-01-01 00:00:00").getTime()),
                testRs.getTimestamp(1));
        assertThrows(SQLException.class, "Zero date value prohibited", () -> {
            System.out.println(testRs.getTimestamp(2));
            return null;
        });
        assertEquals("Zeros", testRs.getString(3));

        testRs.close();
        testStmt.close();
        testConn.close();
    }

    /**
     * Tests fix for Bug#56479 - getTimestamp throws exception.
     *
     * This bug occurs exclusively on UpdatableResultSets when retrieving previously set timestamp values.
     *
     * @throws Exception
     */
    @Test
    public void testBug56479() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 6), "MySQL 5.6+ is required to run this test.");

        String tsStr1 = "2010-09-02 03:55:10";
        String tsStr2 = "2010-09-02 03:55:10.123456";

        TimeZone serverTz = ((MysqlConnection) this.conn).getSession().getServerSession().getSessionTimeZone();

        Timestamp ts1 = Timestamp.from(LocalDateTime.parse(tsStr1.replace(" ", "T")).atZone(serverTz.toZoneId()).toInstant());
        Timestamp ts2 = Timestamp.from(LocalDateTime.parse(tsStr2.replace(" ", "T")).atZone(serverTz.toZoneId()).toInstant());

        createTable("testBug56479", "(id INT PRIMARY KEY, ts1 TIMESTAMP NULL, ts2 TIMESTAMP(6) NULL)", "InnoDB");
        this.stmt.executeUpdate("INSERT INTO testBug56479 VALUES (1, '" + tsStr1 + "', '" + tsStr2 + "'), (2, '" + tsStr1 + "', '" + tsStr2 + "')");

        Statement testStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        ResultSet testRs = testStmt.executeQuery("SELECT * FROM testBug56479");

        // Initial verifications.
        assertTrue(testRs.next());
        assertEquals(1, testRs.getInt(1));
        assertEquals(ts1, testRs.getTimestamp(2));
        assertEquals(ts2, testRs.getTimestamp(3));
        assertTrue(testRs.next());
        assertEquals(2, testRs.getInt(1));
        assertEquals(ts1, testRs.getTimestamp(2));
        assertEquals(ts2, testRs.getTimestamp(3));
        assertFalse(testRs.next());

        // Update second row to null.
        testRs.absolute(2);
        testRs.updateNull(2);
        testRs.updateNull(3);
        testRs.updateRow();
        assertEquals(2, testRs.getInt(1));
        assertNull(testRs.getTimestamp(2));
        assertNull(testRs.getTimestamp(3));
        testRs.beforeFirst();

        // Check data changes using a plain ResultSet.
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug56479");
        assertTrue(this.rs.next());
        assertEquals(1, this.rs.getInt(1));
        assertEquals(ts1, this.rs.getTimestamp(2));
        assertEquals(ts2, this.rs.getTimestamp(3));
        assertTrue(this.rs.next());
        assertEquals(2, this.rs.getInt(1));
        assertNull(this.rs.getTimestamp(2));
        assertNull(this.rs.getTimestamp(3));
        assertFalse(this.rs.next());

        // Update second row to original values.
        testRs.absolute(2);
        testRs.updateTimestamp(2, ts1);
        testRs.updateTimestamp(3, ts2);
        testRs.updateRow();
        assertEquals(2, testRs.getInt(1));
        assertEquals(ts1, testRs.getTimestamp(2));
        assertEquals(ts2, testRs.getTimestamp(3));
        testRs.beforeFirst();

        // Check data changes using a plain ResultSet.
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug56479");
        assertTrue(this.rs.next());
        assertEquals(1, this.rs.getInt(1));
        assertEquals(ts1, this.rs.getTimestamp(2));
        assertEquals(ts2, this.rs.getTimestamp(3));
        assertTrue(this.rs.next());
        assertEquals(2, this.rs.getInt(1));
        assertEquals(ts1, this.rs.getTimestamp(2));
        assertEquals(ts2, this.rs.getTimestamp(3));
        assertFalse(this.rs.next());

        // Insert new row.
        testRs.moveToInsertRow();
        testRs.updateInt(1, 3);
        testRs.updateTimestamp(2, ts1);
        testRs.updateTimestamp(3, ts2);
        testRs.insertRow();
        assertEquals(3, testRs.getInt(1));
        assertEquals(ts1, testRs.getTimestamp(2));
        assertEquals(ts2, testRs.getTimestamp(3));
        testRs.beforeFirst();

        // Check final data using a plain ResultSet.
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug56479");
        assertTrue(this.rs.next());
        assertEquals(1, this.rs.getInt(1));
        assertEquals(ts1, this.rs.getTimestamp(2));
        assertEquals(ts2, this.rs.getTimestamp(3));
        assertTrue(this.rs.next());
        assertEquals(2, this.rs.getInt(1));
        assertEquals(ts1, this.rs.getTimestamp(2));
        assertEquals(ts2, this.rs.getTimestamp(3));
        assertTrue(this.rs.next());
        assertEquals(3, this.rs.getInt(1));
        assertEquals(ts1, this.rs.getTimestamp(2));
        assertEquals(ts2, this.rs.getTimestamp(3));
        assertFalse(this.rs.next());
    }

    /**
     * Tests fix for Bug#22931433, GETTING VALUE OF BIT COLUMN RESULTS IN EXCEPTION.
     *
     * @throws Exception
     */
    @Test
    public void testBug22931433() throws Exception {
        createTable("testBug22931433",
                "(c1 bit(8), c2 bit(16), c3 bit(24), c4 bit(32), c5 bit(40), c6 bit(48), c7 bit(56), c8 bit(64), cb1 bit(1), cb2 bit(64))");
        this.stmt.executeUpdate(
                "INSERT INTO testBug22931433 (c1, c2, c3, c4, c5, c6, c7, c8, cb1, cb2) values('a', 'ba', 'cba', 'dcba', 'edcba', 'fedcba', 'gfedcba', 'hgfedcba', b'00000001', -1)");
        this.stmt.executeUpdate(
                "INSERT INTO testBug22931433 (c1, c2, c3, c4, c5, c6, c7, c8, cb1, cb2) values(b'11001100', b'1100110011001100', b'110011001100110011001100', b'11001100110011001100110011001100',"
                        + " b'1100110011001100110011001100110011001100', b'110011001100110011001100110011001100110011001100', b'11001100110011001100110011001100110011001100110011001100',"
                        + " b'1100110011001100110011001100110011001100110011001100110011001100', 0x00, -2)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); // TODO fails with jdbcCompliantTruncation=true

        for (String useSSPS : new String[] { "false", "true" }) {
            for (String cacheResultSetMetadata : new String[] { "false", "true" }) {
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS);
                props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), cacheResultSetMetadata);

                Connection testConn = getConnectionWithProps(props);
                Statement testStmt = testConn.createStatement();

                ResultSet rs1 = testStmt.executeQuery("SELECT * FROM testBug22931433");
                rs1.next();

                assertEquals('a', rs1.getByte("c1"));
                assertEquals('a', rs1.getByte("c2"));
                assertEquals('a', rs1.getByte("c3"));
                assertEquals('a', rs1.getByte("c4"));
                assertEquals('a', rs1.getByte("c5"));
                assertEquals('a', rs1.getByte("c6"));
                assertEquals('a', rs1.getByte("c7"));
                assertEquals('a', rs1.getByte("c8"));

                assertEquals(97, rs1.getShort("c1"));
                assertEquals(25185, rs1.getShort("c2"));
                assertEquals(25185, rs1.getShort("c3")); // truncated to 2 bytes
                assertEquals(25185, rs1.getShort("c4")); // truncated to 2 bytes
                assertEquals(25185, rs1.getShort("c5")); // truncated to 2 bytes
                assertEquals(25185, rs1.getShort("c6")); // truncated to 2 bytes
                assertEquals(25185, rs1.getShort("c7")); // truncated to 2 bytes
                assertEquals(25185, rs1.getShort("c8")); // truncated to 2 bytes

                assertEquals(97, rs1.getInt("c1"));
                assertEquals(25185, rs1.getInt("c2"));
                assertEquals(6513249, rs1.getInt("c3"));
                assertEquals(1684234849, rs1.getInt("c4"));
                assertEquals(1684234849, rs1.getInt("c5")); // truncated to 4 bytes
                assertEquals(1684234849, rs1.getInt("c6")); // truncated to 4 bytes
                assertEquals(1684234849, rs1.getInt("c7")); // truncated to 4 bytes
                assertEquals(1684234849, rs1.getInt("c8")); // truncated to 4 bytes

                assertEquals(97, rs1.getLong("c1"));
                assertEquals(25185, rs1.getLong("c2"));
                assertEquals(6513249, rs1.getLong("c3"));
                assertEquals(1684234849, rs1.getLong("c4"));
                assertEquals(435475931745L, rs1.getLong("c5"));
                assertEquals(112585661964897L, rs1.getLong("c6"));
                assertEquals(29104508263162465L, rs1.getLong("c7"));
                assertEquals(7523094288207667809L, rs1.getLong("c8"));

                assertEquals(BigDecimal.valueOf(97), rs1.getBigDecimal("c1"));
                assertEquals(BigDecimal.valueOf(25185), rs1.getBigDecimal("c2"));
                assertEquals(BigDecimal.valueOf(6513249), rs1.getBigDecimal("c3"));
                assertEquals(BigDecimal.valueOf(1684234849), rs1.getBigDecimal("c4"));
                assertEquals(BigDecimal.valueOf(435475931745L), rs1.getBigDecimal("c5"));
                assertEquals(BigDecimal.valueOf(112585661964897L), rs1.getBigDecimal("c6"));
                assertEquals(BigDecimal.valueOf(29104508263162465L), rs1.getBigDecimal("c7"));
                assertEquals(BigDecimal.valueOf(7523094288207667809L), rs1.getBigDecimal("c8"));

                assertEquals(97f, rs1.getFloat("c1"));
                assertEquals(25185f, rs1.getFloat("c2"));
                assertEquals(6513249f, rs1.getFloat("c3"));
                assertEquals(1684234849f, rs1.getFloat("c4"));
                assertEquals(435475931745f, rs1.getFloat("c5"));
                assertEquals(112585661964897f, rs1.getFloat("c6"));
                assertEquals(29104508263162465f, rs1.getFloat("c7"));
                assertEquals(7523094288207667809f, rs1.getFloat("c8"));

                assertEquals(Double.valueOf(97), Double.valueOf(rs1.getDouble("c1")));
                assertEquals(Double.valueOf(25185), Double.valueOf(rs1.getDouble("c2")));
                assertEquals(Double.valueOf(6513249), Double.valueOf(rs1.getDouble("c3")));
                assertEquals(Double.valueOf(1684234849), Double.valueOf(rs1.getDouble("c4")));
                assertEquals(Double.valueOf(435475931745L), Double.valueOf(rs1.getDouble("c5")));
                assertEquals(Double.valueOf(112585661964897L), Double.valueOf(rs1.getDouble("c6")));
                assertEquals(Double.valueOf(29104508263162465L), Double.valueOf(rs1.getDouble("c7")));
                assertEquals(Double.valueOf(7523094288207667809L), Double.valueOf(rs1.getDouble("c8")));

                assertEquals(true, rs1.getBoolean("c1"));
                assertEquals(true, rs1.getBoolean("cb1"));
                assertEquals(true, rs1.getBoolean("cb2"));

                assertEquals(BigDecimal.valueOf(97).toString(), rs1.getString("c1"));
                assertEquals(BigDecimal.valueOf(25185).toString(), rs1.getString("c2"));
                assertEquals(BigDecimal.valueOf(6513249).toString(), rs1.getString("c3"));
                assertEquals(BigDecimal.valueOf(1684234849).toString(), rs1.getString("c4"));
                assertEquals(BigDecimal.valueOf(435475931745L).toString(), rs1.getString("c5"));
                assertEquals(BigDecimal.valueOf(112585661964897L).toString(), rs1.getString("c6"));
                assertEquals(BigDecimal.valueOf(29104508263162465L).toString(), rs1.getString("c7"));
                assertEquals(BigDecimal.valueOf(7523094288207667809L).toString(), rs1.getString("c8"));

                assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Date", () -> {
                    rs1.getDate("c1");
                    return null;
                });

                assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Time", () -> {
                    rs1.getTime("c1");
                    return null;
                });

                assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Timestamp", () -> {
                    rs1.getTimestamp("c1");
                    return null;
                });

                // test negative values
                rs1.next();

                assertEquals(-52, rs1.getByte("c1"));
                assertEquals(-52, rs1.getByte("c2"));
                assertEquals(-52, rs1.getByte("c3"));
                assertEquals(-52, rs1.getByte("c4"));
                assertEquals(-52, rs1.getByte("c5"));
                assertEquals(-52, rs1.getByte("c6"));
                assertEquals(-52, rs1.getByte("c7"));
                assertEquals(-52, rs1.getByte("c8"));

                assertEquals(204, rs1.getShort("c1"));
                assertEquals(-13108, rs1.getShort("c2"));
                assertEquals(-13108, rs1.getShort("c3")); // truncated to 2 bytes
                assertEquals(-13108, rs1.getShort("c4")); // truncated to 2 bytes
                assertEquals(-13108, rs1.getShort("c5")); // truncated to 2 bytes
                assertEquals(-13108, rs1.getShort("c6")); // truncated to 2 bytes
                assertEquals(-13108, rs1.getShort("c7")); // truncated to 2 bytes
                assertEquals(-13108, rs1.getShort("c8")); // truncated to 2 bytes

                assertEquals(204, rs1.getInt("c1"));
                assertEquals(52428, rs1.getInt("c2"));
                assertEquals(13421772, rs1.getInt("c3"));
                assertEquals(-858993460, rs1.getInt("c4"));
                assertEquals(-858993460, rs1.getInt("c5")); // truncated to 4 bytes
                assertEquals(-858993460, rs1.getInt("c6")); // truncated to 4 bytes
                assertEquals(-858993460, rs1.getInt("c7")); // truncated to 4 bytes
                assertEquals(-858993460, rs1.getInt("c8")); // truncated to 4 bytes

                assertEquals(204, rs1.getLong("c1"));
                assertEquals(52428, rs1.getLong("c2"));
                assertEquals(13421772, rs1.getLong("c3"));
                assertEquals(3435973836L, rs1.getLong("c4"));
                assertEquals(879609302220L, rs1.getLong("c5"));
                assertEquals(225179981368524L, rs1.getLong("c6"));
                assertEquals(57646075230342348L, rs1.getLong("c7"));
                assertEquals(-3689348814741910324L, rs1.getLong("c8"));

                assertEquals(BigDecimal.valueOf(204), rs1.getBigDecimal("c1"));
                assertEquals(BigDecimal.valueOf(52428), rs1.getBigDecimal("c2"));
                assertEquals(BigDecimal.valueOf(13421772), rs1.getBigDecimal("c3"));
                assertEquals(BigDecimal.valueOf(3435973836L), rs1.getBigDecimal("c4"));
                assertEquals(BigDecimal.valueOf(879609302220L), rs1.getBigDecimal("c5"));
                assertEquals(BigDecimal.valueOf(225179981368524L), rs1.getBigDecimal("c6"));
                assertEquals(BigDecimal.valueOf(57646075230342348L), rs1.getBigDecimal("c7"));
                assertEquals(new BigDecimal(new BigInteger("14757395258967641292")), rs1.getBigDecimal("c8"));

                assertEquals(204f, rs1.getFloat("c1"));
                assertEquals(52428f, rs1.getFloat("c2"));
                assertEquals(13421772f, rs1.getFloat("c3"));
                assertEquals(3435973836f, rs1.getFloat("c4"));
                assertEquals(879609302220f, rs1.getFloat("c5"));
                assertEquals(225179981368524f, rs1.getFloat("c6"));
                assertEquals(57646075230342348f, rs1.getFloat("c7"));
                assertEquals(14757395258967641292f, rs1.getFloat("c8"));

                assertEquals(Double.valueOf(204), Double.valueOf(rs1.getDouble("c1")));
                assertEquals(Double.valueOf(52428), Double.valueOf(rs1.getDouble("c2")));
                assertEquals(Double.valueOf(13421772), Double.valueOf(rs1.getDouble("c3")));
                assertEquals(Double.valueOf(3435973836L), Double.valueOf(rs1.getDouble("c4")));
                assertEquals(Double.valueOf(879609302220L), Double.valueOf(rs1.getDouble("c5")));
                assertEquals(Double.valueOf(225179981368524L), Double.valueOf(rs1.getDouble("c6")));
                assertEquals(Double.valueOf(57646075230342348L), Double.valueOf(rs1.getDouble("c7")));
                assertEquals(Double.valueOf(new BigInteger("14757395258967641292").doubleValue()), Double.valueOf(rs1.getDouble("c8")));

                assertEquals(false, rs1.getBoolean("c8"));
                assertEquals(false, rs1.getBoolean("cb1"));
                assertEquals(false, rs1.getBoolean("cb2"));
            }
        }
    }

    /**
     * Tests fix for Bug#78685 - Wrong results when retrieving the value of a BIT column as an integer.
     *
     * @throws Exception
     */
    @Test
    public void testBug78685() throws Exception {
        createTable("testBug78685", "(b1 BIT(8), b2 BIT(16), b3 BIT(24))", "InnoDB");
        // 46 == b'00101110' == '.'
        // 11822 == b'0010111000101110' == '..'
        // --
        // 47 == '/'
        // 12079 == '//'
        // --
        // 48 == '0'
        // 12336 = '00'
        // --
        // 49 == b'00110001' == '1'
        // 12593 == b'0011000100110001'  == '11'
        // --
        // 50 == '2'
        // 12850 == '22'
        // --
        // 51 == '3'
        // 13107 == '33'
        this.stmt.executeUpdate("INSERT INTO testBug78685 VALUES (b'00101110', b'0010111000101110', b'0010111000101110'), ('/', '//', '//'), "
                + "(48, 12336, 12336), (b'00110001', b'0011000100110001', b'0011000100110001'), ('2', '22', '22'), (51, 13107, 13107)");

        boolean useServerPrepStmts = false;
        do {
            // Test result set from plain statements.
            String testCase = String.format("Case [useSPS: %s, StmtType: %s]", useServerPrepStmts ? "Y" : "N", "Plain");

            final Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            Connection testConn = getConnectionWithProps(props);
            this.rs = testConn.createStatement().executeQuery("SELECT b1, b1 + 0, BIN(b1), b2, b2 + 0, BIN(b2), b3, b3 + 0, BIN(b3) FROM testBug78685");
            testBug78685CheckData(testCase);
            testConn.close();

            // Test result set from prepared statements
            testCase = String.format("Case [useSPS: %s, StmtType: %s]", useServerPrepStmts ? "Y" : "N", "PrepStmt");

            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useServerPrepStmts));
            testConn = getConnectionWithProps(props);
            this.pstmt = testConn.prepareStatement("SELECT b1, b1 + 0, BIN(b1), b2, b2 + 0, BIN(b2), b3, b3 + 0, BIN(b3) FROM testBug78685");
            this.rs = this.pstmt.executeQuery();
            testBug78685CheckData("");
            testConn.close();
        } while (useServerPrepStmts = !useServerPrepStmts);
    }

    private void testBug78685CheckData(String testCase) throws Exception {
        int rowCount = 0;
        while (this.rs.next()) {
            int expectedNumBase = 46 + rowCount;

            // Column "b1 BIT(8)"
            int expectedNum = expectedNumBase;

            assertEquals(expectedNum, this.rs.getShort(1), testCase);
            assertEquals(expectedNum, this.rs.getInt(1), testCase);
            assertEquals(expectedNum, this.rs.getLong(1), testCase);
            assertEquals(expectedNum, this.rs.getBigDecimal(1).intValue(), testCase);
            assertEquals(String.valueOf(expectedNum), this.rs.getString(1), testCase);
            assertTrue(this.rs.getObject(1) instanceof byte[]);
            assertByteArrayEquals(testCase, new byte[] { (byte) expectedNumBase }, (byte[]) this.rs.getObject(1));

            assertEquals(expectedNum, this.rs.getShort(2), testCase);
            assertEquals(expectedNum, this.rs.getInt(2), testCase);
            assertEquals(expectedNum, this.rs.getLong(2), testCase);
            assertEquals(expectedNum, this.rs.getBigDecimal(2).intValue(), testCase);
            assertEquals(String.valueOf(expectedNum), this.rs.getString(2), testCase);
            assertEquals(BigInteger.valueOf(expectedNum), this.rs.getObject(2), testCase);

            final ResultSet testRs1 = this.rs;
            assertThrows(SQLException.class, "Value '[01]+' is outside of valid range for type java.lang.Short", () -> {
                testRs1.getShort(3);
                return null;
            });
            String expectedString = Integer.toBinaryString(expectedNum);
            assertEquals(Integer.parseInt(expectedString), this.rs.getInt(3), testCase);
            assertEquals(Long.parseLong(expectedString), this.rs.getLong(3), testCase);
            assertEquals(expectedString, this.rs.getString(3), testCase);
            assertEquals(expectedString, this.rs.getObject(3), testCase);

            // Column "b1 BIT(16)"
            expectedNum = expectedNumBase + expectedNumBase * 256;

            assertEquals(expectedNum, this.rs.getShort(4), testCase);
            assertEquals(expectedNum, this.rs.getInt(4), testCase);
            assertEquals(expectedNum, this.rs.getLong(4), testCase);
            assertEquals(expectedNum, this.rs.getBigDecimal(4).intValue(), testCase);
            assertEquals(String.valueOf(expectedNum), this.rs.getString(4), testCase);
            assertTrue(this.rs.getObject(4) instanceof byte[]);
            assertByteArrayEquals(testCase, new byte[] { (byte) expectedNumBase, (byte) expectedNumBase }, (byte[]) this.rs.getObject(4));

            assertEquals(expectedNum, this.rs.getShort(5), testCase);
            assertEquals(expectedNum, this.rs.getInt(5), testCase);
            assertEquals(expectedNum, this.rs.getLong(5), testCase);
            assertEquals(expectedNum, this.rs.getBigDecimal(5).intValue(), testCase);
            assertEquals(String.valueOf(expectedNum), this.rs.getString(5), testCase);
            assertEquals(BigInteger.valueOf(expectedNum), this.rs.getObject(5), testCase);

            final ResultSet testRs2 = this.rs;
            assertThrows(SQLException.class, "Value '[01]+' is outside of valid range for type java.lang.Short", () -> {
                testRs2.getShort(6);
                return null;
            });
            assertThrows(SQLException.class, "Value '[01]+' is outside of valid range for type java.lang.Integer", () -> {
                testRs2.getInt(6);
                return null;
            });
            expectedString = Long.toBinaryString(expectedNum);
            assertEquals(Long.parseLong(expectedString), this.rs.getLong(6), testCase);
            assertEquals(expectedString, this.rs.getString(6), testCase);
            assertEquals(expectedString, this.rs.getObject(6), testCase);

            // Column "b1 BIT(24)"
            expectedNum = expectedNumBase + expectedNumBase * 256;

            assertEquals(expectedNum, this.rs.getShort(7), testCase);
            assertEquals(expectedNum, this.rs.getInt(7), testCase);
            assertEquals(expectedNum, this.rs.getLong(7), testCase);
            assertEquals(expectedNum, this.rs.getBigDecimal(7).intValue(), testCase);
            assertEquals(String.valueOf(expectedNum), this.rs.getString(7), testCase);
            assertTrue(this.rs.getObject(7) instanceof byte[]);
            assertByteArrayEquals(testCase, new byte[] { 0, (byte) expectedNumBase, (byte) expectedNumBase }, (byte[]) this.rs.getObject(7));

            assertEquals(expectedNum, this.rs.getShort(8), testCase);
            assertEquals(expectedNum, this.rs.getInt(8), testCase);
            assertEquals(expectedNum, this.rs.getLong(8), testCase);
            assertEquals(expectedNum, this.rs.getBigDecimal(8).intValue(), testCase);
            assertEquals(String.valueOf(expectedNum), this.rs.getString(8), testCase);
            assertEquals(BigInteger.valueOf(expectedNum), this.rs.getObject(8), testCase);

            final ResultSet testRs3 = this.rs;
            assertThrows(SQLException.class, "Value '[01]+' is outside of valid range for type java.lang.Short", () -> {
                testRs3.getShort(9);
                return null;
            });
            assertThrows(SQLException.class, "Value '[01]+' is outside of valid range for type java.lang.Integer", () -> {
                testRs3.getInt(9);
                return null;
            });
            expectedString = Long.toBinaryString(expectedNum);
            assertEquals(Long.parseLong(expectedString), this.rs.getLong(9), testCase);
            assertEquals(expectedString, this.rs.getString(9), testCase);
            assertEquals(expectedString, this.rs.getObject(9), testCase);

            rowCount++;
        }
        assertEquals(6, rowCount, testCase);
    }

    /**
     * Tests fix for Bug#80631 - ResultSet.getString return garbled result with json type data.
     *
     * @throws Exception
     */
    @Test
    public void testBug80631() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 7, 9), "MySQL 5.7.9+ is required to run this test.");

        /*
         * \u4E2D\u56FD (Simplified Chinese): "China"
         * \u65E5\u672C (Japanese): "Japan"
         * \uD83D\uDC2C (Emoji): "Dolphin"
         * \u263A (Symbols): "White Smiling Face"
         */
        String[] data = new String[] { "\u4E2D\u56FD", "\u65E5\u672C", "\uD83D\uDC2C", "\u263A" };
        String jsonTmpl = "{\"data\": \"%s\"}";

        createTable("testBug80631", "(data JSON)");
        createProcedure("testBug80631Insert", "(IN data JSON) BEGIN INSERT INTO testBug80631 VALUES (data); END;");
        createProcedure("testBug80631SELECT", "() BEGIN SELECT * FROM testBug80631; END;");

        Properties props = new Properties();
        props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8");

        boolean useSPS = false;
        do {
            final String testCase = String.format("Case: [SPS: %s]", useSPS ? "Y" : "N");
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSPS);
            final Connection testConn = getConnectionWithProps(props);

            // Insert and select using a Statement.
            Statement testStmt = testConn.createStatement();
            for (String d : data) {
                assertEquals(1, testStmt.executeUpdate("INSERT INTO testBug80631 VALUES ('" + String.format(jsonTmpl, d) + "')"), testCase);
            }
            this.rs = testStmt.executeQuery("SELECT * FROM testBug80631");
            for (int i = 0; i < data.length; i++) {
                assertTrue(this.rs.next(), testCase);
                assertEquals(String.format(jsonTmpl, data[i]), this.rs.getString(1), testCase);
            }
            testStmt.close();

            testConn.createStatement().execute("TRUNCATE TABLE testBug80631");

            // Insert and select using a PreparedStatement.
            PreparedStatement testPstmt = testConn.prepareStatement("INSERT INTO testBug80631 VALUES (?)");
            for (String d : data) {
                testPstmt.setString(1, String.format(jsonTmpl, d));
                assertEquals(1, testPstmt.executeUpdate(), testCase);
            }
            testPstmt.close();
            testPstmt = testConn.prepareStatement("SELECT * FROM testBug80631");
            this.rs = testPstmt.executeQuery();
            for (int i = 0; i < data.length; i++) {
                assertTrue(this.rs.next(), testCase);
                assertEquals(String.format(jsonTmpl, data[i]), this.rs.getString(1), testCase);
            }
            testPstmt.close();

            testConn.createStatement().execute("TRUNCATE TABLE testBug80631");

            // Insert and select using a CallableStatement.
            CallableStatement testCstmt = testConn.prepareCall("{CALL testBug80631Insert(?)}");
            for (String d : data) {
                testCstmt.setString(1, String.format(jsonTmpl, d));
                assertEquals(1, testCstmt.executeUpdate(), testCase);
            }
            testCstmt.close();
            testCstmt = testConn.prepareCall("{CALL testBug80631Select()}");
            testCstmt.execute();
            this.rs = testCstmt.getResultSet();
            for (int i = 0; i < data.length; i++) {
                assertTrue(this.rs.next(), testCase);
                assertEquals(String.format(jsonTmpl, data[i]), this.rs.getString(1), testCase);
            }
            testCstmt.close();

            testConn.close();
        } while (useSPS = !useSPS);
    }

    /**
     * Tests fix for Bug#23197238 - EXECUTEQUERY() FAILS FOR JSON DATA WHEN RESULTSETCONCURRENCY=CONCUR_UPDATABLE.
     *
     * @throws Exception
     */
    @Test
    public void testBug23197238() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 7, 9), "MySQL 5.7.9+ is required to run this test.");

        createTable("testBug23197238", "(id INT AUTO_INCREMENT PRIMARY KEY, doc JSON DEFAULT NULL, field3 int DEFAULT 10)");

        String[] docs = new String[] { "{\"key10\": \"value10\"}", "{\"key2\": \"value2\"}", "{\"key3\": \"value3\"}" };
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");
        Connection testConn = getConnectionWithProps(props);

        Statement testStmt = testConn.createStatement();
        testStmt.execute("INSERT INTO testBug23197238 (doc) VALUES ('" + docs[2] + "')");
        testStmt.close();

        testBug23197238AssertDoc(new String[] { docs[2] });

        testStmt = testConn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        this.rs = testStmt.executeQuery("SELECT * FROM testBug23197238");
        assertTrue(this.rs.next());
        this.rs.updateObject(2, docs[1]);
        this.rs.updateRow();
        this.rs.moveToInsertRow();
        this.rs.updateObject(2, docs[1]);
        this.rs.insertRow();
        testStmt.close();

        testBug23197238AssertDoc(new String[] { docs[1], docs[1] });
        testBug23197238AssertId(new int[] { 1, 2 });

        PreparedStatement testPstmt = testConn.prepareStatement("SELECT * FROM testBug23197238 WHERE id = ?", ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_UPDATABLE);
        testPstmt.setObject(1, 1, Types.INTEGER);
        testPstmt.setFetchSize(1);
        this.rs = testPstmt.executeQuery();
        assertTrue(this.rs.next());
        this.rs.updateObject(2, docs[0]);
        this.rs.updateRow();
        assertEquals(1, this.rs.getInt(1));
        this.rs.moveToInsertRow();
        this.rs.updateObject(2, docs[2]);
        this.rs.insertRow();
        testPstmt.close();

        testBug23197238AssertDoc(docs);
        testBug23197238AssertId(new int[] { 1, 2, 3 });

        testConn.close();
    }

    private void testBug23197238AssertId(int[] expectedId) throws Exception {
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug23197238");
        for (int id : expectedId) {
            assertTrue(this.rs.next());
            assertEquals(id, this.rs.getInt(1));
        }
        assertFalse(this.rs.next());
    }

    private void testBug23197238AssertDoc(String[] expectedDoc) throws Exception {
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug23197238");
        for (String e : expectedDoc) {
            assertTrue(this.rs.next());
            assertEquals(e, this.rs.getString(2));
        }
        assertFalse(this.rs.next());
    }

    /**
     * Tests fix for BUG#81202 - RESULTSETIMPL.GETOBJECT THROWS NULLPOINTEREXCEPTION WHEN FIELD IS NULL.
     *
     * @throws Exception
     */
    @Test
    public void testBug81202() throws Exception {
        createTable("testBug81202",
                "(id INT unsigned NOT NULL, value_timestamp TIMESTAMP NULL, ot1 VARCHAR(100), ot2 BLOB, odt1 VARCHAR(100), odt2 BLOB, otd3 TIMESTAMP NULL, otd4 DATETIME)");

        OffsetDateTime testOffsetDateTime = OffsetDateTime.of(2015, 8, 04, 12, 34, 56, 7890, ZoneOffset.UTC);
        OffsetTime testOffsetTime = OffsetTime.of(12, 34, 56, 7890, ZoneOffset.UTC);

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        Connection testConn = getConnectionWithProps(timeZoneFreeDbUrl, props);

        this.pstmt = testConn.prepareStatement("INSERT INTO testBug81202 VALUES (?, TIMESTAMP '2016-04-27 12:15:55', ?, ?, ?, ?, ?, ?)");
        this.pstmt.setInt(1, 1);
        this.pstmt.setObject(2, testOffsetTime, JDBCType.VARCHAR);
        this.pstmt.setObject(3, testOffsetTime);
        this.pstmt.setObject(4, testOffsetDateTime, JDBCType.VARCHAR);
        this.pstmt.setObject(5, testOffsetDateTime);
        this.pstmt.setObject(6, testOffsetDateTime, JDBCType.TIMESTAMP);
        this.pstmt.setObject(7, testOffsetDateTime, MysqlType.DATETIME);
        assertEquals(1, this.pstmt.executeUpdate());

        this.stmt.executeUpdate("INSERT INTO testBug81202 VALUES (2, NULL, NULL, NULL, NULL, NULL, NULL, NULL)");

        testConn.close();

        testConn = getConnectionWithProps(timeZoneFreeDbUrl, props);
        this.rs = testConn.createStatement().executeQuery("SELECT * FROM testBug81202");

        assertTrue(this.rs.next());
        assertEquals(LocalDate.of(2016, 4, 27), this.rs.getObject(2, LocalDate.class));
        assertEquals(LocalDateTime.of(2016, 4, 27, 12, 15, 55), this.rs.getObject(2, LocalDateTime.class));
        assertEquals(LocalTime.of(12, 15, 55), this.rs.getObject(2, LocalTime.class));
        assertEquals(testOffsetTime.atDate(LocalDate.now()).toEpochSecond(), this.rs.getObject(3, OffsetTime.class).atDate(LocalDate.now()).toEpochSecond());
        assertEquals(testOffsetTime.atDate(LocalDate.now()).toEpochSecond(), this.rs.getObject(4, OffsetTime.class).atDate(LocalDate.now()).toEpochSecond());

        assertEquals(testOffsetDateTime.toEpochSecond(), this.rs.getObject(5, OffsetDateTime.class).toEpochSecond());
        assertEquals(testOffsetDateTime.toEpochSecond(), this.rs.getObject(6, OffsetDateTime.class).toEpochSecond());
        assertEquals(testOffsetDateTime.toEpochSecond(), this.rs.getObject(7, OffsetDateTime.class).toEpochSecond());
        assertEquals(testOffsetDateTime.toEpochSecond(), this.rs.getObject(8, OffsetDateTime.class).toEpochSecond());

        assertTrue(this.rs.next());
        assertNull(this.rs.getObject(2, LocalDate.class));
        assertNull(this.rs.getObject(2, LocalDateTime.class));
        assertNull(this.rs.getObject(2, LocalTime.class));
        assertNull(this.rs.getObject(3, OffsetTime.class));
        assertNull(this.rs.getObject(4, OffsetTime.class));
        assertNull(this.rs.getObject(5, OffsetDateTime.class));
        assertNull(this.rs.getObject(6, OffsetDateTime.class));
        assertNull(this.rs.getObject(7, OffsetDateTime.class));
        assertNull(this.rs.getObject(8, OffsetDateTime.class));

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

    /**
     * Tests fix for BUG#82964 - JSR-310 DATA TYPES CREATED THROUGH JAVA.SQL TYPES.
     *
     * @throws Exception
     */
    @Test
    public void testBug82964() throws Exception {
        TimeZone savedTz = TimeZone.getDefault();
        try {
            // Setting JVM timezone to Europe/Berlin because the test timestamp "2016-03-27 02:15:00" doesn't exist there.
            TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));

            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "Europe/Berlin");

            ResultSet rs1 = getConnectionWithProps(props).createStatement().executeQuery("SELECT '2016-03-27 02:15:00'");
            assertTrue(rs1.next());
            assertEquals(LocalDate.of(2016, 3, 27), rs1.getObject(1, LocalDate.class));
            assertEquals(LocalDateTime.of(2016, 3, 27, 2, 15), rs1.getObject(1, LocalDateTime.class));
            assertEquals(LocalTime.of(2, 15), rs1.getObject(1, LocalTime.class));
            // "2016-03-27 02:15:00" is an impossible datetime for Europe/Berlin tz
            assertThrows(SQLException.class, "HOUR_OF_DAY: 2 -> 3", () -> {
                rs1.getTimestamp(1).toLocalDateTime();
                return null;
            });

            // checking with ZeroDateTimeToNullValueFactory decorator
            props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL");
            this.rs = getConnectionWithProps(props).createStatement().executeQuery("SELECT '0000-00-00 00:00:00'");
            assertTrue(this.rs.next());
            assertNull(this.rs.getObject(1, LocalDate.class));
            assertNull(this.rs.getObject(1, LocalDateTime.class));
            assertNull(this.rs.getObject(1, LocalTime.class));
            assertFalse(this.rs.next());

            // checking with ZeroDateTimeToDefaultValueFactory decorator
            props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "ROUND");
            this.rs = getConnectionWithProps(props).createStatement().executeQuery("SELECT '0000-00-00 00:00:00'");
            assertTrue(this.rs.next());
            assertEquals(LocalDate.of(1, 1, 1), this.rs.getObject(1, LocalDate.class));
            assertEquals(LocalDateTime.of(1, 1, 1, 0, 0), this.rs.getObject(1, LocalDateTime.class));
            assertEquals(LocalTime.of(0, 0), this.rs.getObject(1, LocalTime.class));
            assertFalse(this.rs.next());

        } finally {
            // restore default JVM timezone
            TimeZone.setDefault(savedTz);
        }
    }

    /**
     * Tests fix for Bug#24525461 - UPDATABLE RESULTSET FEATURE FAILS WHEN USESERVERPREPSTMTS=TRUE
     *
     * @throws Exception
     */
    @Test
    public void testBug24525461() throws Exception {
        boolean testJSON = versionMeetsMinimum(5, 7, 9);

        StringBuilder sb = new StringBuilder("(id int primary key, f01 DECIMAL, f02 TINYINT, f03 BOOLEAN, f04 SMALLINT, f05 INT,"
                + " f06 FLOAT, f07 DOUBLE, f08 TIMESTAMP, f09 BIGINT, f10 MEDIUMINT, f11 DATE, f12 TIME, f13 DATETIME, f14 YEAR,"
                + " f15 VARCHAR(20) character set utf8, f16 VARBINARY(30), f17 BIT, f18 ENUM('x','y','z'), f19 SET('a','b','c'),"
                + " f20 TINYBLOB, f21 TINYTEXT character set utf8, f22 MEDIUMBLOB, f23 MEDIUMTEXT character set utf8,"
                + " f24 LONGBLOB, f25 LONGTEXT character set utf8, f26 BLOB, f27 TEXT character set utf8,"
                + " f28 CHAR, f29 BINARY, f30 GEOMETRY, f31 GEOMETRY, f32 NATIONAL CHARACTER(10), f33 NATIONAL CHARACTER(10),"
                + " f34 TINYTEXT, f35 TINYTEXT, f36 TINYBLOB, f37 TINYBLOB, f38 MEDIUMTEXT, f39 MEDIUMTEXT, f40 MEDIUMTEXT,"
                + " f41 MEDIUMTEXT character set utf8, f42 MEDIUMTEXT character set utf8, f43 VARCHAR(10), f44 VARCHAR(255)");
        if (testJSON) {
            sb.append(", f45 JSON");
        }
        sb.append(")");

        createTable("testBug24525461", sb.toString());

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL");
        Connection testConn = getConnectionWithProps(props);
        Statement st = testConn.createStatement();

        props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL");
        tstBug24525461testBytes(props, testJSON, st); // CSPS

        props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
        tstBug24525461testBytes(props, testJSON, st); // SSPS without cursor

        props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");
        props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "1");
        tstBug24525461testBytes(props, testJSON, st); // SSPS with cursor
    }

    private void tstBug24525461testBytes(Properties props, boolean testJSON, Statement st) throws Exception {
        st.executeUpdate("truncate table testBug24525461");

        String fGeomFromText = versionMeetsMinimum(5, 6, 1) ? "ST_GeomFromText" : "GeomFromText";

        StringBuilder sb = new StringBuilder(
                "INSERT INTO testBug24525461 values(0, 1, 1, 1, 1, 1, 1, 1, '2000-01-01 00:00:00', 1, 1, '2000-01-01', '12:00:00', '2000-01-01 00:00:00', 2000, 'aaa',"
                        + " 1, 1, 'x', 'a', 1, '1', 1 , '1', 1, '1', 1, '1', '1', 1, " + fGeomFromText + "('POINT(1 1)'), " + fGeomFromText + "('POINT(2 2)'),"
                        + " _utf8 'aaa', _utf8 'aaa', 'aaa', 'aaa', 1, 1, 'aaa', 'aaa', 'aaa', _utf8 'aaa', _utf8 'aaa', '1', null");
        if (testJSON) {
            sb.append(", '{\"key1\": \"value1\"}'");
        }
        sb.append(")");

        st.executeUpdate(sb.toString());

        System.out.println(" with params = " + props);
        Connection con = getConnectionWithProps(props);

        PreparedStatement testPstmt = con.prepareStatement("SELECT * FROM testBug24525461", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        ResultSet rs1 = testPstmt.executeQuery();
        assertTrue(rs1.next());

        // check that other fields are refreshed properly
        rs1.updateInt(2, 10);
        rs1.updateRow();
        tstBug24525461assertResults1(testJSON, st);

        // check that all fields are set as expected

        Calendar cal = Calendar.getInstance();
        cal.set(2002, 01, 02, 10, 30, 0);
        cal.set(Calendar.MILLISECOND, 0);

        rs1.updateBigDecimal(2, BigDecimal.valueOf(20));
        rs1.updateInt(3, 2);
        rs1.updateBoolean(4, false);
        rs1.updateShort(5, (short) 2);
        rs1.updateInt(6, 2);
        rs1.updateFloat(7, 2);
        rs1.updateDouble(8, 2);
        rs1.updateTimestamp(9, new Timestamp(cal.getTimeInMillis())); // f08 TIMESTAMP
        rs1.updateLong(10, 2L);
        rs1.updateInt(11, 2);
        rs1.updateDate(12, new Date(cal.getTimeInMillis())); // f11 DATE
        rs1.updateTime(13, new Time(cal.getTimeInMillis())); // f12 TIME
        rs1.updateTimestamp(14, new Timestamp(cal.getTimeInMillis())); // f13 DATETIME
        rs1.updateInt(15, 2002); // f14 YEAR
        rs1.updateNString(16, "bbb");
        rs1.updateBytes(17, new byte[] { 50 }); // f16 VARBINARY(30)
        rs1.updateByte(18, (byte) 0); // f17 BIT
        rs1.updateString(19, "y");
        rs1.updateString(20, "b");
        rs1.updateBlob(21, new com.mysql.cj.jdbc.Blob("2".getBytes(), null));
        rs1.updateClob(22, new com.mysql.cj.jdbc.Clob("2", null));
        rs1.updateBlob(23, new ByteArrayInputStream(new byte[] { 50 }));
        rs1.updateClob(24, new StringReader("2"));
        rs1.updateBlob(25, new ByteArrayInputStream(new byte[] { 50, 51, 52 }), 1);
        rs1.updateClob(26, new StringReader("2222"), 1);
        rs1.updateObject(27, "2", MysqlType.BLOB);
        rs1.updateNClob(28, new com.mysql.cj.jdbc.NClob("2", null));
        rs1.updateString(29, "2");
        rs1.updateBytes(30, new byte[] { 50 });

        Object p1 = rs1.getObject(31);
        Object p2 = rs1.getObject(32);
        rs1.updateObject(31, p2);
        rs1.updateObject(32, p1);

        rs1.updateNClob(33, new StringReader("bbb"));
        rs1.updateNClob(34, new StringReader("bbbbbb"), 3);
        rs1.updateAsciiStream(35, new ByteArrayInputStream("bbb".getBytes()));
        rs1.updateAsciiStream(36, new ByteArrayInputStream("bbbbbb".getBytes()), 3);
        rs1.updateBinaryStream(37, new ByteArrayInputStream(new byte[] { 50 }));
        rs1.updateBinaryStream(38, new ByteArrayInputStream(new byte[] { 50, 51, 52 }), 1);
        rs1.updateCharacterStream(39, new StringReader("bbb"));
        rs1.updateCharacterStream(40, new StringReader("bbbbbb"), 3);
        rs1.updateCharacterStream(41, new StringReader("bbbbbb"), 3L);
        rs1.updateNCharacterStream(42, new StringReader("bbb"));
        rs1.updateNCharacterStream(43, new StringReader("bbbbbb"), 3);
        rs1.updateNull(44);

        SQLXML xml = new MysqlSQLXML(null);
        xml.setString("<doc/>");
        rs1.updateSQLXML(45, xml);

        if (testJSON) {
            rs1.updateObject(46, "{\"key2\": \"value2\"}"); // f18 JSON
        }

        rs1.updateRow();
        tstBug24525461assertResults2(testJSON, st);
    }

    private void tstBug24525461assertResults1(boolean testJSON, Statement st) throws Exception {
        String fAsText = versionMeetsMinimum(5, 6, 1) ? "ST_AsText" : "AsText";

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL");
        Connection testConn = getConnectionWithProps(props);

        TimeZone serverTz = ((MysqlConnection) testConn).getSession().getServerSession().getSessionTimeZone();
        ZonedDateTime expZdt = LocalDateTime.of(2000, 1, 1, 0, 0).atZone(serverTz.toZoneId()).withZoneSameInstant(ZoneId.systemDefault());
        String expTimestamp = expZdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"));

        ResultSet rs2 = st.executeQuery("SELECT *, " + fAsText + "(f30), " + fAsText + "(f31) FROM testBug24525461");
        assertTrue(rs2.next());

        assertEquals(0, rs2.getInt(1));
        assertEquals(BigDecimal.valueOf(10), rs2.getBigDecimal(2));
        assertEquals(1, rs2.getInt(3));
        assertTrue(rs2.getBoolean(4));
        assertEquals(1, rs2.getInt(5));
        assertEquals(1, rs2.getInt(6));
        assertEquals(Float.valueOf(1), rs2.getFloat(7));
        assertEquals(Double.valueOf(1), rs2.getDouble(8));
        assertEquals(expTimestamp, rs2.getTimestamp(9).toString());
        assertEquals(BigDecimal.valueOf(1), rs2.getBigDecimal(10));
        assertEquals(1, rs2.getInt(11));
        assertEquals("2000-01-01", rs2.getDate(12).toString());
        assertEquals("12:00:00", rs2.getTime(13).toString());
        assertEquals("2000-01-01 00:00:00.0", rs2.getTimestamp(14).toString());
        assertEquals("2000-01-01", rs2.getDate(15).toString());
        assertEquals("aaa", rs2.getString(16));
        Blob blob = rs2.getBlob(17);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals(1, rs2.getInt(18));
        assertEquals("x", rs2.getString(19));
        assertEquals("a", rs2.getString(20));
        blob = rs2.getBlob(21);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("1", rs2.getString(22));
        blob = rs2.getBlob(23);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("1", rs2.getString(24));
        blob = rs2.getBlob(25);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("1", rs2.getString(26));
        blob = rs2.getBlob(27);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("1", rs2.getString(28));
        assertEquals("1", rs2.getString(29));
        blob = rs2.getBlob(30);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("aaa", rs2.getString(33));
        assertEquals("aaa", rs2.getString(34));
        assertEquals("aaa", rs2.getString(35));
        assertEquals("aaa", rs2.getString(36));
        blob = rs2.getBlob(37);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        blob = rs2.getBlob(38);
        assertTrue(Arrays.equals(new byte[] { 49 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("aaa", rs2.getString(39));
        assertEquals("aaa", rs2.getString(40));
        assertEquals("aaa", rs2.getString(41));
        assertEquals("aaa", rs2.getString(42));
        assertEquals("aaa", rs2.getString(43));
        assertEquals("1", rs2.getString(44));
        SQLXML xml = rs2.getSQLXML(45);
        assertEquals(null, xml.getString());

        if (testJSON) {
            assertEquals("{\"key1\": \"value1\"}", rs2.getString(46));
            assertEquals("POINT(1 1)", rs2.getString(47));
            assertEquals("POINT(2 2)", rs2.getString(48));
        } else {
            assertEquals("POINT(1 1)", rs2.getString(46));
            assertEquals("POINT(2 2)", rs2.getString(47));
        }

        assertFalse(rs2.next());
    }

    private void tstBug24525461assertResults2(boolean testJSON, Statement st) throws Exception {
        String fAsText = versionMeetsMinimum(5, 6, 1) ? "ST_AsText" : "AsText";

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL");
        Connection testConn = getConnectionWithProps(props);

        TimeZone serverTz = ((MysqlConnection) testConn).getSession().getServerSession().getSessionTimeZone();
        ZonedDateTime expZdt = LocalDateTime.of(2002, 2, 2, 10, 30).atZone(ZoneId.systemDefault()).withZoneSameInstant(serverTz.toZoneId());
        String expTimestamp = expZdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"));

        ResultSet rs2 = st.executeQuery("SELECT *, " + fAsText + "(f30), " + fAsText + "(f31) FROM testBug24525461");
        assertTrue(rs2.next());

        assertEquals(0, rs2.getInt(1));
        assertEquals(BigDecimal.valueOf(20), rs2.getBigDecimal(2));
        assertEquals(2, rs2.getInt(3));
        assertFalse(rs2.getBoolean(4));
        assertEquals(2, rs2.getInt(5));
        assertEquals(2, rs2.getInt(6));
        assertEquals(Float.valueOf(2), rs2.getFloat(7));
        assertEquals(Double.valueOf(2), rs2.getDouble(8));
        assertEquals("2002-02-02 10:30:00.0", rs2.getTimestamp(9).toString());
        assertEquals(BigDecimal.valueOf(2), rs2.getBigDecimal(10));
        assertEquals(2, rs2.getInt(11));
        assertEquals("2002-02-02", rs2.getDate(12).toString());
        assertEquals("10:30:00", rs2.getTime(13).toString());
        assertEquals(expTimestamp, rs2.getTimestamp(14).toString());
        assertEquals("2002-01-01", rs2.getDate(15).toString());
        assertEquals("bbb", rs2.getString(16));
        Blob blob = rs2.getBlob(17);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals(0, rs2.getInt(18));
        assertEquals("y", rs2.getString(19));
        assertEquals("b", rs2.getString(20));
        blob = rs2.getBlob(21);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("2", rs2.getString(22));
        blob = rs2.getBlob(23);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("2", rs2.getString(24));
        blob = rs2.getBlob(25);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("2", rs2.getString(26));
        blob = rs2.getBlob(27);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("2", rs2.getString(28));
        assertEquals("2", rs2.getString(29));
        blob = rs2.getBlob(30);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("bbb", rs2.getString(33));
        assertEquals("bbb", rs2.getString(34));
        assertEquals("bbb", rs2.getString(35));
        assertEquals("bbb", rs2.getString(36));
        blob = rs2.getBlob(37);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        blob = rs2.getBlob(38);
        assertTrue(Arrays.equals(new byte[] { 50 }, blob.getBytes(1, (int) blob.length())));
        assertEquals("bbb", rs2.getString(39));
        assertEquals("bbb", rs2.getString(40));
        assertEquals("bbb", rs2.getString(41));
        assertEquals("bbb", rs2.getString(42));
        assertEquals("bbb", rs2.getString(43));
        assertEquals(null, rs2.getString(44));
        SQLXML xml = rs2.getSQLXML(45);
        assertEquals("<doc/>", xml.getString());

        if (testJSON) {
            assertEquals("{\"key2\": \"value2\"}", rs2.getString(46));
            assertEquals("POINT(2 2)", rs2.getString(47));
            assertEquals("POINT(1 1)", rs2.getString(48));
        } else {
            assertEquals("POINT(2 2)", rs2.getString(46));
            assertEquals("POINT(1 1)", rs2.getString(47));
        }

        assertFalse(rs2.next());
    }

    /**
     * Tests fix for Bug#24527173 - QUERY EXECUTION USING PREPARED STMT FAILS WHEN USECURSORFETCH=TRUE
     *
     * @throws Exception
     */
    @Test
    public void testBug24527173() throws Exception {
        createTable("testBug24527173", "(a tinyint auto_increment primary key)");
        this.stmt.execute("insert into testBug24527173 (a) values (101),(102),(103),(104)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");
        props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "2");
        Connection con = getConnectionWithProps(props);

        PreparedStatement ps = con.prepareStatement("select * from testBug24527173");
        testBug24527173Results(ps, ResultSetImpl.class);

        ps = con.prepareStatement("select * from testBug24527173", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        testBug24527173Results(ps, UpdatableResultSet.class);
    }

    private void testBug24527173Results(PreparedStatement ps, Class<?> expectedResultClass) throws Exception {
        assertEquals(ServerPreparedStatement.class, ps.getClass());

        final ResultSet rs1 = ps.executeQuery();

        assertEquals(expectedResultClass, rs1.getClass());

        // ensure that cursor exists
        Field f = NativeResultset.class.getDeclaredField("rowData");
        f.setAccessible(true);
        assertTrue(f.get(rs1).getClass().isAssignableFrom(ResultsetRowsCursor.class));

        int i = 101;
        while (rs1.next()) {
            assertEquals(i++, rs1.getInt("a"));
        }

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.absolute(1);
            return null;
        });

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.afterLast();
            return null;
        });

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.beforeFirst();
            return null;
        });

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.first();
            return null;
        });

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.last();
            return null;
        });

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.previous();
            return null;
        });

        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            rs1.relative(1);
            return null;
        });
    }

    /**
     * Tests fix for BUG#23702040 - JDBCDATEVALUEFACTORY FAILS TO PARSE SOME DATES.
     *
     * @throws Exception
     */
    @Test
    public void testBug23702040() throws Exception {
        SimpleDateFormat sdf = TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd", TimeZone.getTimeZone("Europe/Bucharest"));
        sdf.setLenient(false);

        java.util.Date expected = sdf.parse("1994-03-27");
        Date fromFactory = new SqlDateValueFactory(new DefaultPropertySet(), null, TimeZone.getTimeZone("Europe/Bucharest"))
                .createFromDate(new InternalDate(1994, 3, 27));

        assertEquals(expected.getTime(), fromFactory.getTime());
    }

    /**
     * Tests fix for Bug#82707, WRONG MILLI SECOND VALUE RETURNED FROM TIMESTAMP COLUMN.
     *
     * @throws Exception
     */
    @Test
    public void testBug82707() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server");

        List<String> ts = new ArrayList<>();
        ts.add("2016-08-24 07:47:46.057000");
        ts.add("2016-08-24 16:53:10.056000");
        ts.add("2016-08-24 16:53:20.000000");
        ts.add("2016-08-24 16:53:28.000450");
        ts.add("2016-08-24 16:53:29.000000");
        ts.add("2016-08-24 16:53:30.002300");

        createTable("testBug82707", "(Started  TIMESTAMP(6))");
        for (String string : ts) {
            this.stmt.executeUpdate("insert into testBug82707 values('" + string + "')");
        }

        this.rs = this.stmt.executeQuery("select Started from testBug82707");
        int id = 0;
        while (this.rs.next()) {
            String expected = ts.get(id++);
            if (expected.endsWith(".000000")) {
                expected = expected.replace(".000000", "");
            }
            assertEquals(expected, this.rs.getString("Started"));
        }
    }

    /**
     * Tests fix for Bug#84084 (25215008), JAVA.LANG.ARRAYINDEXOUTOFBOUNDSEXCEPTION ON ATTEMPT TO GET VALUE FROM RESULTSET.
     *
     * @throws Exception
     */
    @Test
    public void testBug25215008() throws Exception {
        String VALUE_ONE = "bar";

        createTable("testBug25215008",
                "( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `val_one` VARCHAR(10), `val_blob` blob, `val_three` VARCHAR(10), PRIMARY KEY (`id`) )");
        this.stmt.execute("INSERT INTO `testBug25215008`(`id`,`val_one`,`val_blob`, `val_three`) VALUES ( NULL,'" + VALUE_ONE + "',NULL,NULL)");

        // test 1 - OK
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        Connection conn1 = getConnectionWithProps(props);
        PreparedStatement pstm1 = conn1.prepareStatement("select id, val_one, val_blob, val_three from testBug25215008 where val_one = ?");
        pstm1.setString(1, VALUE_ONE);
        ResultSet rs1 = pstm1.executeQuery();
        assertTrue(rs1.next());
        assertEquals("1", rs1.getString("id"));
        assertEquals("bar", rs1.getString("val_one"));
        assertNull(rs1.getString("val_blob"));
        assertNull(rs1.getString("val_three"));
        rs1.close();

        // then test with useServerPrepStmts=true

        // - test 2 - OK
        props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
        Connection conn2 = getConnectionWithProps(props);
        PreparedStatement pstm2 = conn2.prepareStatement("select id, val_one, val_three from testBug25215008 where val_one = ?"); // let's not request val_blob for now
        pstm2.setString(1, VALUE_ONE);
        rs1 = pstm2.executeQuery();
        assertTrue(rs1.next());
        assertEquals("1", rs1.getString("id"));
        assertEquals("bar", rs1.getString("val_one"));
        assertNull(rs1.getString("val_three"));
        rs1.close();

        // test 3 - OK!
        // let's call query to make sure value of a last column in a result set is not NULL  - val_one is not NULL and this is now last column in result set
        PreparedStatement pstm3 = conn2.prepareStatement("select id, val_blob, val_three, val_one from testBug25215008 where val_one = ?");
        pstm3.setString(1, VALUE_ONE);
        rs1 = pstm3.executeQuery();
        assertTrue(rs1.next());
        assertEquals("1", rs1.getString("id"));
        assertEquals("bar", rs1.getString("val_one"));
        assertNull(rs1.getString("val_blob"));
        assertNull(rs1.getString("val_three"));
        rs1.close();

        // test 4 - fails! Combination of three factors:
        // 1. useServerPrepStmts=true
        // 2. result set has column with mediumblob type
        // 3. value of a last column in result set is NULL
        PreparedStatement pstm4 = conn2.prepareStatement("select id, val_one, val_blob, val_three from testBug25215008 where val_one = ?");
        pstm4.setString(1, VALUE_ONE);
        rs1 = pstm4.executeQuery();
        assertTrue(rs1.next());
        assertEquals("1", rs1.getString("id"));
        assertEquals("bar", rs1.getString("val_one"));
        assertNull(rs1.getString("val_blob"));
        assertNull(rs1.getString("val_three"));
        rs1.close();

        // simple case
        createTable("testBug25215008_2", "(`c2` mediumblob)");
        this.stmt.execute("INSERT INTO testBug25215008_2 values ()");

        PreparedStatement pstm5 = conn2.prepareStatement("select * from testBug25215008_2");
        rs1 = pstm5.executeQuery();
        assertTrue(rs1.next());
        assertNull(rs1.getString(1));
        rs1.close();
    }

    /**
     * Tests fix for Bug#84189 - Allow null when extracting java.time.* classes from ResultSet.
     *
     * @throws Exception
     */
    @Test
    public void testBug84189() throws Exception {
        createTable("testBug84189", "(d DATE NULL, t TIME NULL, dt DATETIME NULL, ts TIMESTAMP NULL, ot VARCHAR(100), odt VARCHAR(100))");
        this.stmt.execute(
                "INSERT INTO testBug84189 VALUES ('2017-01-01', '10:20:30', '2017-01-01 10:20:30', '2017-01-01 10:20:30', '10:20:30+04:00', '2017-01-01T10:20:30+04:00')");
        this.stmt.execute("INSERT INTO testBug84189 VALUES (NULL, NULL, NULL, NULL, NULL, NULL)");

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug84189");
        assertTrue(this.rs.next());
        assertEquals(LocalDate.of(2017, 1, 1), this.rs.getObject(1, LocalDate.class));
        assertEquals(LocalTime.of(10, 20, 30), this.rs.getObject(2, LocalTime.class));
        assertEquals(LocalDateTime.of(2017, 1, 1, 10, 20, 30), this.rs.getObject(3, LocalDateTime.class));
        assertEquals(LocalDateTime.of(2017, 1, 1, 10, 20, 30), this.rs.getObject(4, LocalDateTime.class));
        assertEquals(OffsetTime.of(10, 20, 30, 0, ZoneOffset.ofHours(4)).atDate(LocalDate.now()).toEpochSecond(),
                this.rs.getObject(5, OffsetTime.class).atDate(LocalDate.now()).toEpochSecond());
        assertEquals(OffsetDateTime.of(2017, 01, 01, 10, 20, 30, 0, ZoneOffset.ofHours(4)), this.rs.getObject(6, OffsetDateTime.class));

        assertEquals(LocalDate.class, this.rs.getObject(1, LocalDate.class).getClass());
        assertEquals(LocalTime.class, this.rs.getObject(2, LocalTime.class).getClass());
        assertEquals(LocalDateTime.class, this.rs.getObject(3, LocalDateTime.class).getClass());
        assertEquals(LocalDateTime.class, this.rs.getObject(4, LocalDateTime.class).getClass());
        assertEquals(OffsetTime.class, this.rs.getObject(5, OffsetTime.class).getClass());
        assertEquals(OffsetDateTime.class, this.rs.getObject(6, OffsetDateTime.class).getClass());

        assertTrue(this.rs.next());
        assertNull(this.rs.getObject(1, LocalDate.class));
        assertNull(this.rs.getObject(2, LocalTime.class));
        assertNull(this.rs.getObject(3, LocalDateTime.class));
        assertNull(this.rs.getObject(4, LocalDateTime.class));
        assertNull(this.rs.getObject(5, OffsetTime.class));
        assertNull(this.rs.getObject(6, OffsetDateTime.class));

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

    /**
     * Tests fix for Bug#83368 - 5.1.40 regression: wasNull not updated when calling getInt for a bit column.
     *
     * @throws Exception
     */
    @Test
    public void testBug83368() throws Exception {
        createTable("testBug83368", "(c1 VARCHAR(1), c2 BIT)");
        this.stmt.execute("INSERT INTO testBug83368 VALUES (NULL, 1)");
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug83368");

        assertTrue(this.rs.next());

        assertNull(this.rs.getString(1));
        assertTrue(this.rs.wasNull());
        assertEquals((byte) 1, this.rs.getByte(2));
        assertFalse(this.rs.wasNull());

        assertNull(this.rs.getString(1));
        assertTrue(this.rs.wasNull());
        assertEquals((short) 1, this.rs.getShort(2));
        assertFalse(this.rs.wasNull());

        assertNull(this.rs.getString(1));
        assertTrue(this.rs.wasNull());
        assertEquals(1, this.rs.getInt(2));
        assertFalse(this.rs.wasNull());

        assertNull(this.rs.getString(1));
        assertTrue(this.rs.wasNull());
        assertEquals(1L, this.rs.getLong(2));
        assertFalse(this.rs.wasNull());

        assertNull(this.rs.getString(1));
        assertTrue(this.rs.wasNull());
        assertEquals(BigDecimal.valueOf(1), this.rs.getBigDecimal(2));
        assertFalse(this.rs.wasNull());
    }

    /**
     * Tests fix for Bug#83662 - NullPointerException while reading NULL boolean value from DB.
     *
     * This fix was actually done in the patch for Bug#83368, as both are fixed in the same way.
     *
     * @throws Exception
     */
    @Test
    public void testBug83662() throws Exception {
        createTable("testBug83662", "(b BIT(1) NULL)");
        this.stmt.executeUpdate("INSERT INTO testBug83662 VALUES (null)");

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug83662");
        assertTrue(this.rs.next());
        assertEquals((byte) 0, this.rs.getByte(1));
        assertEquals((short) 0, this.rs.getShort(1));
        assertEquals(0, this.rs.getInt(1));
        assertEquals(0L, this.rs.getLong(1));
        assertEquals(0, this.rs.getInt(1));
        assertNull(this.rs.getBigDecimal(1));
    }

    /**
     * Tests fix for Bug#70704 - Deadlock using UpdatableResultSet.
     *
     * Doesn't actually test the buggy behavior since it is not verifiable since the fix for Bug#59462 (revision 385a151). However, the patch for this fix is
     * needed because the synchronization in UpdatableResultSet was dated.
     * This test makes sure there is no regression.
     *
     * WARNING! If this test fails there is no guarantee that the JVM will remain stable and won't affect any other tests. It is imperative that this test
     * passes to ensure other tests results.
     *
     * @throws Exception
     */
    @Test
    public void testBug70704() throws Exception {
        for (int i = 0; i < 100; i++) {
            final Statement testStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            final ResultSet testRs = testStmt.executeQuery("SELECT 1");

            ExecutorService executorService = Executors.newFixedThreadPool(2);

            executorService.submit(() -> {
                testStmt.close();
                return null;
            });

            executorService.submit(() -> {
                testRs.close();
                return null;
            });

            executorService.shutdown();
            if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {
                ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
                long[] threadIds = threadMXBean.findMonitorDeadlockedThreads();
                if (threadIds != null) {
                    System.err.println("Deadlock detected!");
                    ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds, Integer.MAX_VALUE);
                    for (ThreadInfo ti : threadInfos) {
                        System.err.println();
                        System.err.println(ti);
                        System.err.println("Stack trace:");
                        for (StackTraceElement ste : ti.getStackTrace()) {
                            System.err.println("   " + ste);
                        }
                    }
                    fail("Unexpected deadlock detected. Consult system output for more details. WARNING: this failure may lead to JVM instability.");
                }
            }
        }
    }

    /**
     * Tests for fix to BUG#25650305 - GETDATE(),GETTIME() AND GETTIMESTAMP() CALL WITH NULL CALENDAR RETURNS NPE
     *
     * @throws Exception
     */
    @Test
    public void testBug25650305() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server");

        createTable("testBug25650305", "(c1 timestamp(5))");
        this.stmt.executeUpdate("INSERT INTO testBug25650305 VALUES ('2031-01-15 03:14:07.339999')");

        Calendar cal = Calendar.getInstance();

        Connection testConn;
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        testConn = getConnectionWithProps(props);
        this.rs = testConn.createStatement().executeQuery("SELECT * FROM testBug25650305");
        this.rs.next();

        TimeZone serverTz = ((MysqlConnection) this.conn).getSession().getServerSession().getSessionTimeZone();
        ZonedDateTime expZdt = LocalDateTime.of(2031, 1, 15, 3, 14, 07, 340000000).atZone(serverTz.toZoneId());
        String expDate = expZdt.withZoneSameInstant(ZoneId.systemDefault()).format(TimeUtil.DATE_FORMATTER);
        String expDateCal = expZdt.withZoneSameLocal(ZoneId.systemDefault()).format(TimeUtil.DATE_FORMATTER);
        String expTime = expZdt.withZoneSameLocal(ZoneId.systemDefault()).format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET);
        String expTimestamp = expZdt.withZoneSameInstant(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"));
        String expTimestampCal = expZdt.withZoneSameLocal(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"));

        assertEquals(expDate, this.rs.getDate(1).toString());
        assertEquals(expDateCal, this.rs.getDate(1, cal).toString());
        assertEquals(expDate, this.rs.getDate(1, null).toString());

        assertEquals(expTime, this.rs.getTime(1).toString());
        assertEquals(expTime, this.rs.getTime(1, cal).toString());
        assertEquals(expTime, this.rs.getTime(1, null).toString());

        assertEquals(expTimestamp, this.rs.getTimestamp(1).toString());
        assertEquals(expTimestampCal, this.rs.getTimestamp(1, cal).toString());
        assertEquals(expTimestamp, this.rs.getTimestamp(1, null).toString());

        testConn.close();
    }

    /**
     * Tests for fix to BUG#26750705, UNSUPPORTED CONVERSION FROM TIME TO JAVA.SQL.DATE
     *
     * @throws Exception
     */
    @Test
    public void testBug26750705() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server");

        createTable("testBug26750705", "(c1 time(3), c2 time(3))");
        this.stmt.execute("insert into testBug26750705 values('80:59:59','8:59:59.01')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        Connection testConn = getConnectionWithProps(props);

        PreparedStatement ps = testConn.prepareStatement("select * from testBug26750705", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
        this.rs = ps.executeQuery();

        assertNotNull(this.rs.next());
        assertEquals(Date.valueOf(TimeUtil.DEFAULT_DATE).getTime(), this.rs.getDate(1, null).getTime());
        assertEquals(Date.valueOf(TimeUtil.DEFAULT_DATE).getTime(), this.rs.getDate(2, null).getTime());

        // at least 2 warnings are expected
        SQLWarning w = this.rs.getWarnings();
        assertNotNull(w);

        int cnt = 2;
        String expectedWarning = Messages.getString("ResultSet.ImplicitDatePartWarning", new Object[] { "java.sql.Date" });
        while (w != null) {
            if (expectedWarning.equals(w.getMessage())) {
                cnt--;
            }
            w = w.getNextWarning();
        }
        assertEquals(0, cnt);

        testConn.close();
    }

    /**
     * Tests for fix to BUG#26266731 - CONCUR_UPDATABLE RESULTSET OPERATIONS FAIL AGAINST 8.0 FOR BOOLEAN COLUMN
     *
     * @throws Exception
     */
    @Test
    public void testBug26266731() throws Exception {
        this.rs = null;

        createTable("testBug26266731", "(c1 int,c2 char(10),c3 float,c4 double,c5 bigint,c6 blob,c7 bool,c8 date,c9 timestamp NULL,c10 time,"
                + "c11 mediumint,c12 varchar(100),c13 binary(10),  primary key(c1,c5,c7))", "InnoDB");
        this.stmt.executeUpdate("insert into testBug26266731 values(1,'a',1.1,1.1,1,'1',true,'2013-03-25','2013-03-25 01:01:01.01','01:01:01',1,'1','1')");
        this.stmt.executeUpdate("insert into testBug26266731 values(2,'b',2.2,2.2,2,'2',true,'2014-03-25','2014-03-25 02:02:02.02','02:02:02',2,'2','2')");
        this.stmt.executeUpdate("insert into testBug26266731 values(3,'c',3.3,3.3,3,'3',true,'2015-03-25','2015-03-25 03:03:03.03','03:03:03',3,'3','3')");

        assertEquals(3, getRowCount("testBug26266731"));

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8");
        Connection c = getConnectionWithProps(props);

        this.pstmt = c.prepareStatement("SELECT * FROM testBug26266731 order by c1", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        this.rs = this.pstmt.executeQuery();

        if (this.rs.next()) {
            this.rs.absolute(2);
            this.rs.deleteRow();
        }

        assertEquals(2, getRowCount("testBug26266731"));
    }

    /**
     * Tests for fix to BUG#85941 (25924324), WASNULL NOT SET AFTER GETBYTES IS CALLED
     *
     * @throws Exception
     */
    @Test
    public void testBug85941() throws Exception {
        createTable("testBug85941", "(strField VARCHAR(1), bitField TEXT)");
        this.stmt.executeUpdate("insert into testBug85941 values(NULL, 1)");

        this.rs = this.stmt.executeQuery("SELECT strField, bitField FROM testBug85941");
        this.rs.next();

        assertNull(this.rs.getString(1));
        assertTrue(this.rs.wasNull());

        assertEquals("1".getBytes()[0], this.rs.getBytes(2)[0]);
        assertFalse(this.rs.wasNull());
    }

    /**
     * Tests fix for Bug#22305979, WRONG RECORD UPDATED IF SENDFRACTIONALSECONDS=FALSE AND SMT IS SCROLLABLE.
     *
     * @throws Exception
     */
    @Test
    public void testBug22305979() throws Exception {
        assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server");

        /* Test from bug report */
        Connection testConn2;
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.sendFractionalSeconds.getKeyName(), "false");

        for (String connectionTimeZone : new String[] { null, "GMT", "Asia/Calcutta" }) {
            System.out.println("connectionTimeZone=" + connectionTimeZone);
            if (connectionTimeZone != null) {
                props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), connectionTimeZone);
            }
            testConn2 = getConnectionWithProps(props);

            Timestamp ts2 = new Timestamp(TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd HH:mm:ss.SSS", null).parse("2019-12-30 13:59:57.789").getTime());
            createTable("testBug22305979_orig_1",
                    "(id int, tmp int,ts1 timestamp(6),ts2 timestamp(3) NOT NULL DEFAULT '2001-01-01 00:00:01',primary key(id,ts1) )");
            this.stmt.execute("insert into testBug22305979_orig_1 values (1,100,'2014-12-31 23:59:59.123','2015-12-31 23:59:59.456')");
            this.stmt.execute("insert into testBug22305979_orig_1 values (1,200,'2014-12-31 23:59:59','2022-12-31 23:59:59.456')");

            Statement scrollableStmt = testConn2.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet rs1 = scrollableStmt.executeQuery("SELECT * FROM testBug22305979_orig_1 where id=1 and ts1='2014-12-31 23:59:59.123'");
            if (rs1.next()) {
                rs1.updateTimestamp(3, ts2); //Updating part of primary key
                rs1.updateRow();
            }

            this.rs = scrollableStmt.executeQuery("SELECT * FROM testBug22305979_orig_1 order by tmp");
            assertTrue(this.rs.next());
            assertEquals(1, this.rs.getInt(1));
            assertEquals(100, this.rs.getInt(2));
            assertEquals("2019-12-30 13:59:57.0", this.rs.getTimestamp(3).toString());
            // TODO this.rs.getString(3) here doesn't take into account the connectionTimeZone thus returns the value as it is stored in table; is it a bug?
            // assertEquals("2019-12-30 13:59:57", this.rs.getString(3));
            assertEquals("2015-12-31 23:59:59.456", this.rs.getString(4));
            assertTrue(this.rs.next());
            assertEquals(1, this.rs.getInt(1));
            assertEquals(200, this.rs.getInt(2));
            assertEquals("2014-12-31 23:59:59", this.rs.getString(3)); // we didn't change this date, so getString() matches the getTimestamp()
            assertEquals("2022-12-31 23:59:59.456", this.rs.getString(4));
        }

        /* Unified test */

        // Original values we insert
        Timestamp[] ts_ins = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 13:14:15"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1234"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12345"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1234567"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12345678"), //
                Timestamp.valueOf("2018-07-09 13:14:15.999999999") };
        Time[] t_ins = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987654321").getTime()) };

        // Values we expect in DB after insert operation if TIME_TRUNCATE_FRACTIONAL sql_mode is unset
        Timestamp[] ts_ins_expected_round = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 13:14:15"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1234"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12345"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123457"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123457"), //
                Timestamp.valueOf("2018-07-09 13:14:16.0") };
        Time[] t_ins_expected_round = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 13:14:16").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:16.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.99").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.9870").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.98700").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.9870000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.98700000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987000000").getTime()) };

        // Values we expect in DB after insert operation if TIME_TRUNCATE_FRACTIONAL sql_mode is set
        Timestamp[] ts_ins_expected_truncate = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 13:14:15"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123"), //
                Timestamp.valueOf("2018-07-09 13:14:15.1234"), //
                Timestamp.valueOf("2018-07-09 13:14:15.12345"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 13:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 13:14:15.999999") };
        Time[] t_ins_expected_truncate = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.9").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.98").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.9870").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.98700").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.9870000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.98700000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.987000000").getTime()) };

        // Values we expect in DB after insert operation if sendFractionalSeconds=false
        Timestamp[] ts_ins_expected_not_sendFractionalSeconds = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 13:14:15"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0"), //
                Timestamp.valueOf("2018-07-09 13:14:15.0") };
        Time[] t_ins_expected_not_sendFractionalSeconds = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 13:14:15.0").getTime()) };

        // Original values we pass to update operation
        Timestamp[] ts_upd = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 03:14:15"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1234"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12345"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1234567"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12345678"), //
                Timestamp.valueOf("2018-07-09 03:14:15.999999999") };
        Time[] t_upd = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987654321").getTime()) };

        // Values we expect in DB after update operation if TIME_TRUNCATE_FRACTIONAL sql_mode is unset
        Timestamp[] ts_upd_expected_round = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 03:14:15"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1234"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12345"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123457"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123457"), //
                Timestamp.valueOf("2018-07-09 03:14:16.0") };
        Time[] t_upd_expected_round = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 03:14:16").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:16.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.99").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.9870").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.98700").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.9870000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.98700000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987000000").getTime()) };

        // Values we expect in DB after update operation if TIME_TRUNCATE_FRACTIONAL sql_mode is set
        Timestamp[] ts_upd_expected_truncate = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 03:14:15"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123"), //
                Timestamp.valueOf("2018-07-09 03:14:15.1234"), //
                Timestamp.valueOf("2018-07-09 03:14:15.12345"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123456"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123457"), //
                Timestamp.valueOf("2018-07-09 03:14:15.123457"), //
                Timestamp.valueOf("2018-07-09 03:14:15.999999") };
        Time[] t_upd_expected_truncate = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.9").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.98").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.9870").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.98700").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.9870000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.98700000").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.987000000").getTime()) };

        // Values we expect in DB after update operation if sendFractionalSeconds=false
        Timestamp[] ts_upd_expected_not_sendFractionalSeconds = new Timestamp[] { //
                Timestamp.valueOf("2018-07-09 03:14:15"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0"), //
                Timestamp.valueOf("2018-07-09 03:14:15.0") };
        Time[] t_upd_expected_not_sendFractionalSeconds = new Time[] { //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()), //
                new Time(Timestamp.valueOf("2018-07-09 03:14:15.0").getTime()) };

        Connection testConn;

        boolean sqlModeTimeTruncateFractional = false;
        boolean sendFractionalSeconds = false;
        boolean sendFractionalSecondsForTime = false;
        boolean useServerPrepStmts = false;

        do {
            // TIME_TRUNCATE_FRACTIONAL was added in MySQL 8.0
            if (sqlModeTimeTruncateFractional && !versionMeetsMinimum(8, 0)) {
                continue;
            }
            for (String connectionTimeZone : new String[] { null, "GMT", "Asia/Calcutta" }) {
                System.out.println("connectionTimeZone=" + connectionTimeZone);
                if (connectionTimeZone != null) {
                    props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), connectionTimeZone);
                } else {
                    props.remove(PropertyKey.connectionTimeZone.getKeyName());
                }

                final String testCase = String.format(
                        "Case: [TIME_TRUNCATE_FRACTIONAL=%s, sendFractionalSeconds=%s, sendFractionalSecondsForTime=%s, useServerPrepStmts=%s,",
                        sqlModeTimeTruncateFractional ? "Y" : "N", sendFractionalSeconds ? "Y" : "N", sendFractionalSecondsForTime ? "Y" : "N",
                        useServerPrepStmts ? "Y" : "N");
                System.out.println(testCase);

                String sqlMode = getMysqlVariable("sql_mode");
                sqlMode = removeSqlMode("TIME_TRUNCATE_FRACTIONAL", sqlMode);
                if (sqlModeTimeTruncateFractional) {
                    if (sqlMode.length() > 0) {
                        sqlMode += ",";
                    }
                    sqlMode += "TIME_TRUNCATE_FRACTIONAL";
                }

                props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'");
                props.setProperty(PropertyKey.sendFractionalSeconds.getKeyName(), "" + sendFractionalSeconds);
                props.setProperty(PropertyKey.sendFractionalSecondsForTime.getKeyName(), "" + sendFractionalSecondsForTime);
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useServerPrepStmts);

                testConn = getConnectionWithProps(props);

                // specifying different fractional length
                for (int len = 0; len < 10; len++) {

                    int fieldLen = len > 6 ? 6 : len;
                    //System.out.println("len: " + len);

                    String tableName = "testBug22305979_" + len;
                    createTable(tableName,
                            "(id INTEGER, dt DATETIME" + (fieldLen == 0 ? "" : "(" + fieldLen + ")") + ", ts TIMESTAMP"
                                    + (fieldLen == 0 ? "" : "(" + fieldLen + ")") + ", tm TIME" + (fieldLen == 0 ? "" : "(" + fieldLen + ")")
                                    + ", PRIMARY KEY(id,dt,ts,tm))");

                    Statement st = testConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
                    this.rs = st.executeQuery("SELECT id,dt,ts,tm FROM " + tableName + " FOR UPDATE");
                    this.rs.next(); // No rows
                    this.rs.moveToInsertRow();
                    this.rs.updateInt("id", 1);
                    this.rs.updateTimestamp("dt", ts_ins[len]);
                    this.rs.updateTimestamp("ts", ts_ins[len]);
                    this.rs.updateTime("tm", t_ins[len]);
                    this.rs.insertRow();
                    assertTrue(this.rs.last(), testCase);

                    // checking only seconds and nanos, other date parts are not relevant to this bug
                    Calendar c_exp = new GregorianCalendar();
                    c_exp.setTime(sendFractionalSeconds ? sqlModeTimeTruncateFractional ? ts_ins_expected_truncate[len] : ts_ins_expected_round[len]
                            : ts_ins_expected_not_sendFractionalSeconds[len]);
                    Calendar c_res = new GregorianCalendar();
                    c_res.setTime(this.rs.getTimestamp("dt"));
                    assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase);
                    assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase);
                    c_res.setTime(this.rs.getTimestamp("ts"));
                    assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase);
                    assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase);

                    c_exp.setTime(sendFractionalSeconds && sendFractionalSecondsForTime
                            ? sqlModeTimeTruncateFractional ? t_ins_expected_truncate[len] : t_ins_expected_round[len]
                            : t_ins_expected_not_sendFractionalSeconds[len]);
                    c_res.setTime(this.rs.getTime("tm"));
                    assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase);
                    assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase);

                    this.rs.updateTimestamp("dt", ts_upd[len]);
                    this.rs.updateTimestamp("ts", ts_upd[len]);
                    this.rs.updateTime("tm", t_upd[len]);
                    this.rs.updateRow();
                    c_exp.setTime(sendFractionalSeconds ? sqlModeTimeTruncateFractional ? ts_upd_expected_truncate[len] : ts_upd_expected_round[len]
                            : ts_upd_expected_not_sendFractionalSeconds[len]);
                    c_res.setTime(this.rs.getTimestamp("dt"));
                    assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase);
                    assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase);
                    c_res.setTime(this.rs.getTimestamp("ts"));
                    assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase);
                    assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase);

                    c_exp.setTime(sendFractionalSeconds && sendFractionalSecondsForTime
                            ? sqlModeTimeTruncateFractional ? t_upd_expected_truncate[len] : t_upd_expected_round[len]
                            : t_upd_expected_not_sendFractionalSeconds[len]);
                    c_res.setTime(this.rs.getTime("tm"));
                    assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase);
                    assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase);

                    st.close();
                }

                testConn.close();
            }
        } while ((sqlModeTimeTruncateFractional = !sqlModeTimeTruncateFractional) || (sendFractionalSeconds = !sendFractionalSeconds)
                || (sendFractionalSecondsForTime = !sendFractionalSecondsForTime) || (useServerPrepStmts = !useServerPrepStmts));
    }

    /**
     * Tests fix for Bug#80532 (22847443), ENCODING OF RESULTSET.UPDATEROW IS BROKEN FOR NON ASCII CHARCTERS.
     *
     * @throws Exception
     */
    @Test
    public void testBug80532() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");

        for (String enc : new String[] { "ISO8859_1", "UTF-8" }) {
            for (String useSSPS : new String[] { "false", "true" }) {
                final String testCase = String.format("Case: [characterEncoding=%s, useServerPrepStmts=%s]", enc, useSSPS);
                System.out.println(testCase);

                createTable("testBug80532", "(id char(50) NOT NULL, data longtext, num int, PRIMARY KEY (id,num)) CHARACTER SET "
                        + (versionMeetsMinimum(5, 5) ? "utf8mb4" : "utf8"));

                props.setProperty(PropertyKey.characterEncoding.getKeyName(), enc);
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS);

                Connection c1 = getConnectionWithProps(props);

                String id1 = "äöü";
                String id2 = "öäü";
                String data1 = "my data";
                String data2 = "new data";

                c1.createStatement().executeUpdate("INSERT INTO testBug80532(id,data,num) VALUES( '" + id1 + "', '" + data1 + "', 1 )");

                Statement st = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
                this.rs = st.executeQuery("select * From testBug80532"); // where id='" + id1 + "'"
                this.rs.next();

                System.out.println(this.rs.getString("id") + ", " + this.rs.getString("data"));
                assertEquals(id1, this.rs.getString("id"));
                assertEquals(data1, this.rs.getString("data"));
                this.rs.updateString("data", data2);
                this.rs.updateRow();
                System.out.println(this.rs.getString("id") + ", " + this.rs.getString("data"));
                assertEquals(id1, this.rs.getString("id"));
                assertEquals(data2, this.rs.getString("data"));

                this.rs.moveToInsertRow();
                this.rs.updateString("id", id2);
                this.rs.updateString("data", data1);
                this.rs.updateInt("num", 2);
                this.rs.insertRow();
                assertTrue(this.rs.last());
                System.out.println(this.rs.getString("id") + ", " + this.rs.getString("data"));
                assertEquals(id2, this.rs.getString("id"));
                assertEquals(data1, this.rs.getString("data"));

                this.rs.updateString("id", id1);
                this.rs.updateRow();
                System.out.println(this.rs.getString("id") + ", " + this.rs.getString("data"));
                assertEquals(id1, this.rs.getString("id"));
                assertEquals(data1, this.rs.getString("data"));
            }
        }
    }

    /**
     * Tests fix for Bug#72609 (18749544), SETDATE() NOT USING A PROLEPTIC GREGORIAN CALENDAR.
     *
     * @throws Exception
     */
    @Test
    public void testBug72609() throws Exception {
        GregorianCalendar prolepticGc = new GregorianCalendar();
        prolepticGc.setGregorianChange(new Date(Long.MIN_VALUE));
        prolepticGc.clear();
        prolepticGc.set(Calendar.DAY_OF_MONTH, 8);
        prolepticGc.set(Calendar.MONTH, Calendar.OCTOBER);
        prolepticGc.set(Calendar.YEAR, 1582);

        GregorianCalendar gc = new GregorianCalendar();
        gc.clear();
        gc.setTimeInMillis(prolepticGc.getTimeInMillis());

        assertEquals(1582, gc.get(Calendar.YEAR));
        assertEquals(8, gc.get(Calendar.MONTH));
        assertEquals(28, gc.get(Calendar.DAY_OF_MONTH));

        // TIMESTAMP can't represent dates before 1970-01-01, so we need to test only DATE and DATETIME types
        createTable("testBug72609", "(d date, pd date, dt datetime, pdt datetime)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");

        boolean sendFractionalSeconds = false;
        boolean useServerPrepStmts = false;

        do {
            final String testCase = String.format("Case: [sendFractionalSeconds=%s, useServerPrepStmts=%s]", sendFractionalSeconds ? "Y" : "N",
                    useServerPrepStmts ? "Y" : "N");
            System.out.println(testCase);

            props.setProperty(PropertyKey.sendFractionalSeconds.getKeyName(), "" + sendFractionalSeconds);
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useServerPrepStmts);

            Connection c1 = getConnectionWithProps(props);
            Statement st1 = c1.createStatement();

            st1.execute("truncate table testBug72609");

            java.sql.Date d1 = new java.sql.Date(prolepticGc.getTime().getTime());
            Timestamp ts1 = new Timestamp(prolepticGc.getTime().getTime());

            this.pstmt = c1.prepareStatement("insert into testBug72609 values(?,?,?,?)");
            this.pstmt.setDate(1, d1);
            this.pstmt.setDate(2, d1, prolepticGc);
            this.pstmt.setTimestamp(3, ts1);
            this.pstmt.setTimestamp(4, ts1, prolepticGc);
            this.pstmt.execute();

            /*
             * Checking stored values by retrieving them as strings to avoid conversions on c/J side.
             */
            this.rs = st1.executeQuery("select DATE_FORMAT(d, '%Y-%m-%d') as d, DATE_FORMAT(pd, '%Y-%m-%d') as pd,"
                    + " DATE_FORMAT(dt, '%Y-%m-%d %H:%i:%s.%f') as dt, DATE_FORMAT(pdt, '%Y-%m-%d %H:%i:%s.%f') as pdt from testBug72609");
            this.rs.next();
            System.out.println(this.rs.getString(1) + ", " + this.rs.getString(2) + ", " + this.rs.getString(3) + ", " + this.rs.getString(4));

            assertEquals("1582-09-28", this.rs.getString(1), testCase); // according to Julian calendar
            assertEquals("1582-10-08", this.rs.getString(2), testCase); // according to proleptic Gregorian calendar

            // the exact day depends on adjustments between time zones, but that's not interesting for this test
            assertTrue(this.rs.getString(3).startsWith("1582-09-2"), testCase);
            assertTrue(this.rs.getString(4).startsWith("1582-10-0"), testCase);

            /*
             * Getting stored values back.
             *
             * Default Julian to Gregorian calendar switch is: October 4, 1582 (Julian) is followed by October 15, 1582 (Gregorian).
             * So when default non-proleptic calendar is used the given 1582-10-08 date is in a "missing" period. In this case GregorianCalendar
             * uses a Julian calendar system for counting date in milliseconds, thus adding another 10 days to the date and returning 1582-10-18.
             *
             * With explicit proleptic calendar we get the symmetric back conversion.
             */
            ResultSet rs1 = this.stmt.executeQuery("select * from testBug72609");
            rs1.next();

            assertEquals("1582-09-28", rs1.getDate(1).toString(), testCase);

            assertThrows(SQLException.class, "the specified date doesn't exist", () -> {
                // 1582-10-18 can't be represented because it falls to "missed" period in a Julian->Gregorian switch
                rs1.getDate(2).toString();
                return null;
            });
            assertEquals("1582-09-28", rs1.getDate(2, prolepticGc).toString(), testCase); // according to proleptic Gregorian calendar

            assertTrue(rs1.getTimestamp(3).toString().startsWith("1582-09-2"));

            // the exact day depends on adjustments between time zones, but that's not interesting for this test
            assertThrows(SQLException.class, "the specified date doesn't exist", () -> {
                // 1582-10-18 can't be represented because it falls to "missed" period in a Julian->Gregorian switch
                rs1.getTimestamp(4).toString();
                return null;
            });
            assertTrue(rs1.getTimestamp(4, prolepticGc).toString().startsWith("1582-09-2")); // according to proleptic Gregorian calendar

            c1.close();

        } while ((sendFractionalSeconds = !sendFractionalSeconds) || (useServerPrepStmts = !useServerPrepStmts));
    }

    /**
     * Tests fix for Bug#91065 (28101003), ZERODATETIMEBEHAVIOR=CONVERT_TO_NULL SHOULD NOT APPLY TO 00:00:00 TIME COLUMNS.
     *
     * @throws Exception
     */
    @Test
    public void testBug91065() throws Exception {
        createTable("testBug91065", "(theTimeField time DEFAULT NULL)");
        this.stmt.executeUpdate("insert into testBug91065 values('00:00:00')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL");
        Connection con = getConnectionWithProps(props);
        Statement st = con.createStatement();

        this.rs = st.executeQuery("SELECT theTimeField FROM testBug91065");
        assertTrue(this.rs.next());
        Time theTime = this.rs.getTime("theTimeField");
        assertNotNull(theTime);
        assertEquals(Time.valueOf("00:00:00"), theTime);
    }

    /**
     * Tests fix for Bug#92536 (28692243), UPDATEING SERVER SIDE PREPSTMTS RESULTSET FAIL.
     *
     * @throws Exception
     */
    @Test
    public void testBug92536() throws Exception {
        createTable("testBug92536", "(`key` VARCHAR(45) NOT NULL, `value` BIGINT(20) NOT NULL,  PRIMARY KEY (`key`))");
        this.stmt.executeUpdate("INSERT INTO `testBug92536` VALUES ('key', 0)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        for (String useSSPS : new String[] { "false", "true" }) {
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS);
            Connection c1 = getConnectionWithProps(props);
            try (PreparedStatement stmt1 = c1.prepareStatement("SELECT `key`, `value` FROM `testBug92536` WHERE `key`=? FOR UPDATE",
                    ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
                stmt1.setString(1, "key");
                try (ResultSet res = stmt1.executeQuery()) {
                    res.next();
                    res.updateLong("value", 1);
                    res.updateRow();
                    res.refreshRow();

                    assertEquals("key", res.getString(1));
                    assertEquals(1, res.getInt(2));
                }
            }
            c1.close();

            this.rs = this.stmt.executeQuery("select * from testBug92536");
            this.rs.next();
            assertEquals("key", this.rs.getString(1));
            assertEquals(1, this.rs.getInt(2));
        }
    }

    /**
     * Tests fix for Bug#25650482, REFRESHROW() CALL AFTER UPDATEROW() API FAILS WHEN USESERVERPREPSTMTS=TRUE.
     *
     * @throws Exception
     */
    @Test
    public void testBug25650482() throws Exception {
        createTable("testBug25650482", "(c1 int, c2 char(10),  primary key(c1))");
        this.stmt.executeUpdate("INSERT INTO `testBug25650482` VALUES (1, 'a')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");

        for (String useSSPS : new String[] { "false", "true" }) {
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS);
            Connection c1 = getConnectionWithProps(props);
            try (PreparedStatement ps1 = c1.prepareStatement("SELECT * FROM `testBug25650482` order by c1 asc", ResultSet.TYPE_SCROLL_SENSITIVE,
                    ResultSet.CONCUR_UPDATABLE)) {
                try (ResultSet res = ps1.executeQuery()) {
                    res.absolute(1);
                    res.updateInt(1, res.getInt(1));
                    res.updateString(2, "100");
                    res.updateRow();
                    res.refreshRow();

                    assertEquals(1, res.getInt(1));
                    assertEquals("100", res.getString(2));
                }
            }
            c1.close();

            this.rs = this.stmt.executeQuery("select * from testBug25650482");
            this.rs.next();
            assertEquals(1, this.rs.getInt(1));
            assertEquals("100", this.rs.getString(2));
        }
    }

    /**
     * Tests fix for Bug#25650514, UPDATEROW() CALL FAILS WITH NPE WHEN SSPS=TRUE AND TABLE HAS MULTI-FLD KEY.
     *
     * @throws Exception
     */
    @Test
    public void testBug25650514() throws Exception {
        createTable("testBug25650514", "(c1 int,c2 char(10),  primary key(c1,c2))");
        this.stmt.executeUpdate("INSERT INTO `testBug25650514` VALUES (1, 'a')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");

        for (String useSSPS : new String[] { "false", "true" }) {
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS);
            Connection c1 = getConnectionWithProps(props);
            try (PreparedStatement ps1 = c1.prepareStatement("SELECT * FROM `testBug25650514` order by c1 asc", ResultSet.TYPE_SCROLL_SENSITIVE,
                    ResultSet.CONCUR_UPDATABLE)) {
                try (ResultSet res = ps1.executeQuery()) {
                    res.absolute(1);
                    res.updateInt(1, res.getInt(1));
                    res.updateString(2, "100");
                    res.updateRow();
                    res.refreshRow();

                    assertEquals(1, res.getInt(1));
                    assertEquals("100", res.getString(2));
                }
            }
            c1.close();

            this.rs = this.stmt.executeQuery("select * from testBug25650514");
            this.rs.next();
            assertEquals(1, this.rs.getInt(1));
            assertEquals("100", this.rs.getString(2));
        }
    }

    /**
     * Tests fix for BUG#25650385, GETBYTE() RETURNS ERROR FOR BINARY() FLD.
     *
     * @throws Exception
     */
    @Test
    public void testBug25650385() throws Exception {
        /*
         * getByte (recommended for TINYINT):
         * TINYINT, SMALLINT, INTEGER, BIGINT, REAL, FLOAT, DOUBLE, DECIMAL, NUMERIC, BIT, BOOLEAN, CHAR , VARCHAR , LONGVARCHAR, ROWID
         */

        createTable("testBug25650385", "(b1 blob(12), c1 char(12), c2 binary(12), i1 int, c3 char(12) CHARACTER SET binary)");
        this.stmt.execute("INSERT INTO testBug25650385 values (10, 'a', 48, 10, 23)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        for (boolean useSSPS : new boolean[] { false, true }) {
            for (boolean jdbcCompliantTruncation : new boolean[] { false, true }) {
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS);
                props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "" + jdbcCompliantTruncation);
                Connection c1 = getConnectionWithProps(props);
                this.pstmt = c1.prepareStatement("select * from testBug25650385");
                if (useSSPS) {
                    assertTrue(this.pstmt instanceof ServerPreparedStatement);
                }
                ResultSet rs1 = this.pstmt.executeQuery();
                assertTrue(rs1.next());

                // from blob(12)
                assertEquals('1', rs1.getBytes(1)[0]);
                if (jdbcCompliantTruncation) {
                    assertThrows(SQLDataException.class, "Value '10' is outside of valid range for type java.lang.Byte", () -> {
                        rs1.getByte(1);
                        return null;
                    });
                } else {
                    assertEquals('1', rs1.getByte(1));
                }
                assertEquals(10, rs1.getInt(1));
                assertEquals(10L, rs1.getLong(1));
                assertEquals(10, rs1.getShort(1));
                assertEquals("10", rs1.getString(1));

                // from c1 char(12)
                assertEquals('a', rs1.getBytes(2)[0]);
                assertEquals('a', rs1.getByte(2));
                assertThrows(SQLDataException.class, "Cannot determine value type from string 'a'", () -> {
                    rs1.getInt(2);
                    return null;
                });
                assertThrows(SQLDataException.class, "Cannot determine value type from string 'a'", () -> {
                    rs1.getLong(2);
                    return null;
                });
                assertThrows(SQLDataException.class, "Cannot determine value type from string 'a'", () -> {
                    rs1.getShort(2);
                    return null;
                });
                assertEquals("a", rs1.getString(2));

                // from c2 binary(12)
                assertEquals('4', rs1.getBytes(3)[0]);
                if (jdbcCompliantTruncation) {
                    assertThrows(SQLDataException.class, "Value '48.+ is outside of valid range for type java.lang.Byte", () -> {
                        rs1.getByte(3);
                        return null;
                    });
                } else {
                    assertEquals('4', rs1.getByte(3));
                }
                assertThrows(SQLDataException.class, "Cannot determine value type from string '48.+", () -> {
                    rs1.getInt(3);
                    return null;
                });
                assertThrows(SQLDataException.class, "Cannot determine value type from string '48.+", () -> {
                    rs1.getLong(3);
                    return null;
                });
                assertThrows(SQLDataException.class, "Cannot determine value type from string '48.+", () -> {
                    rs1.getShort(3);
                    return null;
                });
                assertTrue(rs1.getString(3).startsWith("48"));

                // from i1 int
                if (useSSPS) {
                    assertEquals(10, rs1.getBytes(4)[0]);
                } else {
                    assertEquals('1', rs1.getBytes(4)[0]);
                }
                assertEquals(10, rs1.getByte(4));
                assertEquals(10, rs1.getInt(4));
                assertEquals(10, rs1.getLong(4));
                assertEquals(10, rs1.getShort(4));
                assertEquals("10", rs1.getString(4));

                // from c3 char(12) CHARACTER SET binary
                assertEquals('2', rs1.getBytes(5)[0]);
                if (jdbcCompliantTruncation) {
                    assertThrows(SQLDataException.class, "Value '23.+ is outside of valid range for type java.lang.Byte", () -> {
                        rs1.getByte(5);
                        return null;
                    });
                } else {
                    assertEquals('2', rs1.getByte(5));
                }
                assertThrows(SQLDataException.class, "Cannot determine value type from string '23.+", () -> {
                    rs1.getInt(5);
                    return null;
                });
                assertThrows(SQLDataException.class, "Cannot determine value type from string '23.+", () -> {
                    rs1.getLong(5);
                    return null;
                });
                assertThrows(SQLDataException.class, "Cannot determine value type from string '23.+", () -> {
                    rs1.getShort(5);
                    return null;
                });
                assertTrue(rs1.getString(5).startsWith("23"));
            }

        }
    }

    /**
     * Tests fix for BUG#27784363, MYSQL 8.0 JDBC DRIVER THROWS NUMBERFORMATEXCEPTION FOR TEXT DATA
     *
     * @throws Exception
     */
    @Test
    public void testBug27784363() throws Exception {
        createTable("testBug27784363", "(col0 TEXT)");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false");
        Connection c1 = getConnectionWithProps(props);
        InputStream in1 = null;
        try {
            in1 = new ByteArrayInputStream(("gergeetbtebertbgergeetbtebertbgergeetbtebertbgergeetbtebertbgergeetbtebertbgergeetbtebertbge"
                    + "rgeetbtebertbgergeetbtebertbgergeetbtebertbgergeetbtebertbrgeetbtebertbgergeetbtebertbgergeetbtebertbgergeetbtebertb").getBytes());
            this.pstmt = c1.prepareStatement("insert into testBug27784363 values (?)");
            this.pstmt.setAsciiStream(1, in1, in1.available());
            this.pstmt.execute();
            System.out.println("inserted.");
            this.rs = c1.createStatement().executeQuery("select * from testBug27784363");
            this.rs.next();
            assertEquals('g', this.rs.getByte("col0"));
        } finally {
            in1.close();
            c1.close();
        }
    }

    /**
     * Tests fix for BUG#94533 (29446100), GETOBJECT FOR BOXED PRIMITIVE TYPES DOESN'T RETURN NULL FOR NULL COLUMNS.
     *
     * @throws Exception
     */
    @Test
    public void testBug94533() throws Exception {
        createTable("testBug94533", "(volume BIGINT)");
        this.stmt.executeUpdate("INSERT INTO `testBug94533` VALUES (NULL)");

        String sql = "SELECT volume FROM testBug94533 WHERE volume IS NULL LIMIT 1";
        this.rs = this.stmt.executeQuery(sql);

        // JDBC spec:
        //        When the column value in the database is SQL NULL, it may be returned to the Java
        //        application as null, 0, or false, depending on the type of the column value.
        //        Column values that map to Java Object types are returned as a Java null; those
        //        that map to numeric types are returned as 0; those that map to a Java boolean are
        //        returned as false.

        assertTrue(this.rs.next());
        assertNull(this.rs.getString("volume"));
        assertNull(this.rs.getObject("volume"));

        assertFalse(this.rs.getBoolean("volume"));
        assertNull(this.rs.getObject("volume", Boolean.class));

        assertEquals(0, this.rs.getByte("volume"));
        assertNull(this.rs.getObject("volume", Byte.class));

        assertEquals(0.0d, this.rs.getDouble("volume"));
        assertNull(this.rs.getObject("volume", Double.class));

        assertEquals(0.0f, this.rs.getFloat("volume"));
        assertNull(this.rs.getObject("volume", Float.class));

        assertEquals(0L, this.rs.getShort("volume"));
        assertNull(this.rs.getObject("volume", Short.class));

        assertEquals(0L, this.rs.getInt("volume"));
        assertNull(this.rs.getObject("volume", Integer.class));

        assertEquals(0L, this.rs.getLong("volume"));
        assertNull(this.rs.getObject("volume", Long.class));
    }

    /**
     * Tests fix for BUG#94585 (29452669), GETTABLENAME() RETURNS NULL FOR A QUERY HAVING COUNT(*) WITH JDBC DRIVER V8.0.12
     *
     * @throws Exception
     */
    @Test
    public void testBug94585() throws Exception {
        createTable("testBug94585", "(column_1 INT NOT NULL)");
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        for (boolean useSSPS : new boolean[] { false, true }) {
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS);
            Connection con = getConnectionWithProps(props);
            this.pstmt = con.prepareStatement("select count(*) from testBug94585");
            ResultSetMetaData rsMeta = this.pstmt.getMetaData();
            if (rsMeta != null) {
                for (int i = 1; i <= rsMeta.getColumnCount(); i++) {
                    assertNotNull(rsMeta.getTableName(i));
                }
            }
        }
    }

    /**
     * Tests fix for Bug#80441 (22850444), SYNTAX ERROR ON RESULTSET.UPDATEROW() WITH SQL_MODE NO_BACKSLASH_ESCAPES.
     *
     * @throws Exception
     */
    @Test
    public void testBug80441() throws Exception {
        createTable("testBug80441", "( id varchar(50) NOT NULL, data longtext, start DATETIME, PRIMARY KEY (id) )");
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        Connection con = null;
        for (String sessVars : new String[] { null, "sql_mode='NO_BACKSLASH_ESCAPES'" }) {
            for (boolean useSSPS : new boolean[] { false, true }) {
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS);
                if (sessVars != null) {
                    props.setProperty(PropertyKey.sessionVariables.getKeyName(), sessVars);
                }
                String errMsg = "Using sessionVariables=" + sessVars + ", useSSPS=" + useSSPS + ":";
                try {
                    con = getConnectionWithProps(dbUrl, props);
                    Statement st = con.createStatement();
                    try {
                        st.execute("INSERT INTO testBug80441(id,data,start) VALUES( 'key''''s', 'my data', {ts '2005-01-05 13:59:20'})");
                        this.pstmt = con.prepareStatement("SELECT * FROM testBug80441", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
                        this.rs = this.pstmt.executeQuery();
                        assertTrue(this.rs.next(), errMsg);
                        assertEquals("key''s", this.rs.getString(1), errMsg);
                        String text = "any\\other\ntext's\r\032\u0000\"";
                        this.rs.updateString("data", text);
                        this.rs.updateRow();
                        assertEquals(text, this.rs.getString("data"), errMsg);
                        this.rs.close();
                        this.rs = this.pstmt.executeQuery();
                        assertTrue(this.rs.next(), errMsg);
                        assertEquals(text, this.rs.getString("data"), errMsg);
                    } catch (Throwable e) {
                        System.out.println(errMsg);
                        throw e;
                    } finally {
                        st.execute("TRUNCATE TABLE testBug80441");
                    }
                } finally {
                    if (con != null) {
                        con.close();
                    }
                }
            }
        }
    }

    /**
     * Tests fix for Bug#20913289, PSTMT.EXECUTEUPDATE() FAILS WHEN SQL MODE IS NO_BACKSLASH_ESCAPES.
     *
     * @throws Exception
     */
    @Test
    public void testBug20913289() throws Exception {
        createTable("testBug20913289", "(c1 int,c2 blob)");
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        Connection con = null;
        for (String sessVars : new String[] { null, "sql_mode='NO_BACKSLASH_ESCAPES'" }) {
            for (boolean useSSPS : new boolean[] { false, true }) {
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS);
                if (sessVars != null) {
                    props.setProperty(PropertyKey.sessionVariables.getKeyName(), sessVars);
                }
                String errMsg = "Using sessionVariables=" + sessVars + ", useSSPS=" + useSSPS + ":";

                PreparedStatement ps = null;
                String text = " ' This is first record ' ";
                String escapedText = " '' This is first record '' ";

                try {
                    con = getConnectionWithProps(dbUrl, props);
                    Statement st = con.createStatement();
                    try {
                        st.execute("insert into testBug20913289 values(100,'" + escapedText + "')");

                        this.rs = st.executeQuery("select * from testBug20913289");
                        this.rs.next();
                        Blob bval1 = this.rs.getBlob(2);
                        assertEquals("100", this.rs.getString(1), errMsg);
                        assertEquals(text, this.rs.getString(2), errMsg);
                        this.rs.close();

                        ps = con.prepareStatement("update testBug20913289 set c1=c1+?,c2=? ");
                        ps.setObject(1, "100", java.sql.Types.INTEGER);
                        ps.setBlob(2, bval1);
                        ps.executeUpdate();

                        this.rs = st.executeQuery("select * from testBug20913289");
                        this.rs.next();
                        assertEquals("200", this.rs.getString(1), errMsg);
                        assertEquals(text, this.rs.getString(2), errMsg);
                        this.rs.close();
                        ps.close();
                    } catch (Exception ex) {
                        System.out.println(errMsg);
                        throw ex;
                    }
                } finally {
                    this.stmt.execute("TRUNCATE TABLE testBug20913289");
                }

            }
        }
    }

    /**
     * Tests fix for Bug#96059 (29999318), ERROR STREAMING MULTI RESULTSETS WITH MYSQL-CONNECTOR-JAVA 8.0.X.
     *
     * @throws Exception
     */
    @Test
    public void testBug96059() throws Exception {
        createTable("testBug96059", "(f1 int, f2 int, f3 int)");
        this.stmt.executeUpdate("INSERT INTO `testBug96059` VALUES (1,2,3),(4,5,6)");

        Connection con = null;
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true");

        for (boolean useSSPS : new boolean[] { false, true }) {
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS);
            try {

                con = getConnectionWithProps(props);

                PreparedStatement st = con.prepareStatement("SELECT f1 from testBug96059;SELECT f2 from testBug96059;SELECT f3 from testBug96059;",
                        ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                st.setFetchSize(Integer.MIN_VALUE);
                st.setFetchDirection(ResultSet.FETCH_REVERSE);
                assertTrue(st.execute());

                // fetch results partially then try next result set
                this.rs = st.getResultSet();
                assertTrue(((ResultSetImpl) this.rs).getRows() instanceof ResultsetRowsStreaming<?>);
                assertTrue(this.rs.next());
                assertEquals(1, this.rs.getInt(1));
                assertTrue(st.getMoreResults());

                // fetch results fully then try next result set
                this.rs = st.getResultSet();
                assertTrue(((ResultSetImpl) this.rs).getRows() instanceof ResultsetRowsStreaming<?>);
                assertTrue(this.rs.next());
                assertEquals(2, this.rs.getInt(1));
                assertTrue(this.rs.next());
                assertEquals(5, this.rs.getInt(1));
                assertFalse(this.rs.next());
                assertTrue(st.getMoreResults());

                // fetch results partially then try next result set
                this.rs = st.getResultSet();
                assertTrue(((ResultSetImpl) this.rs).getRows() instanceof ResultsetRowsStreaming<?>);
                assertTrue(this.rs.next());
                assertEquals(3, this.rs.getInt(1));
                assertFalse(st.getMoreResults());

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

    /**
     * Tests fix for Bug#96383 (30119545) RS.GETTIMESTAMP() HAS DIFFERENT RESULTS FOR TIME FIELDS WITH USECURSORFETCH=TRUE.
     *
     * @throws Exception
     */
    @Test
    public void testBug96383() throws Exception {
        boolean withFract = versionMeetsMinimum(5, 6, 4); // fractional seconds are not supported in previous versions

        createTable("testBug96383", withFract ? "(id VARCHAR(10), x_time TIME(6))" : "(id VARCHAR(10), x_time TIME)");
        this.stmt.execute("INSERT INTO testBug96383 values ('time', '00:00:05.123')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        for (boolean useSSPS : new boolean[] { false, true }) {
            for (boolean useCursorFetch : new boolean[] { false, true }) {
                props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS);
                props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "" + useCursorFetch);
                props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), useCursorFetch ? "1" : "0");

                Connection con = getConnectionWithProps(props);

                Statement st = con.createStatement();
                this.rs = st.executeQuery("SELECT * FROM testBug96383 WHERE id='time'");
                assertTrue(this.rs.next());
                assertEquals(withFract ? "1970-01-01 00:00:05.123" : "1970-01-01 00:00:05.0", this.rs.getTimestamp(2).toString());

                this.pstmt = con.prepareStatement("SELECT * FROM testBug96383 WHERE id='time'");
                this.rs = this.pstmt.executeQuery();
                assertTrue(this.rs.next());
                assertEquals(withFract ? "1970-01-01 00:00:05.123" : "1970-01-01 00:00:05.0", this.rs.getTimestamp(2).toString());

                con.close();
            }
        }
    }

    /**
     * Tests fix for Bug#97757 (30584907), NULLPOINTEREXCEPTION WITH CACHERESULTSETMETADATA=TRUE AND EXECUTEQUERY OF "SET".
     *
     * @throws Exception
     */
    @Test
    public void testBug97757() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        for (boolean cacheResultSetMetadata : new boolean[] { false, true }) {
            props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), "" + cacheResultSetMetadata);
            Connection con = getConnectionWithProps(props);

            DatabaseMetaData meta = con.getMetaData();
            System.out.println("MySQL Server: " + meta.getDatabaseProductVersion() + "; Driver: " + meta.getDriverName() + meta.getDriverVersion());

            Statement s = con.createStatement();
            s.execute("set autocommit = 0;");

            con.close();
        }
    }

    /**
     * Tests fix for Bug#30474158, CONNECTOR/J 8 DOES NOT HONOR THE REQUESTED RESULTSETTYPE SCROLL_INSENSITIVE ETC.
     *
     * @throws Exception
     */
    @Test
    public void testBug30474158() throws Exception {
        this.stmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        assertEquals(ResultSet.TYPE_FORWARD_ONLY, this.stmt.getResultSetType());
        this.rs = this.stmt.executeQuery("SELECT 'testBug30474158'");
        this.rs.next();
        assertEquals("testBug30474158", this.rs.getString(1));
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.beforeFirst();
            return null;
        });
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.first();
            return null;
        });
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.last();
            return null;
        });
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.afterLast();
            return null;
        });
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.previous();
            return null;
        });
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.absolute(1);
            return null;
        });
        assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY\\.", () -> {
            this.rs.relative(-1);
            return null;
        });

        for (int t : new int[] { ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.TYPE_SCROLL_SENSITIVE }) {
            this.stmt = this.conn.createStatement(t, ResultSet.CONCUR_READ_ONLY);
            this.rs = this.stmt.executeQuery("SELECT 'testBug30474158'");
            this.rs.next();
            assertEquals("testBug30474158", this.rs.getString(1));
            this.rs.beforeFirst();
            assertTrue(this.rs.isBeforeFirst());
            this.rs.next();
            assertEquals("testBug30474158", this.rs.getString(1));
            this.rs.first();
            assertEquals("testBug30474158", this.rs.getString(1));
            this.rs.last();
            assertEquals("testBug30474158", this.rs.getString(1));
            this.rs.afterLast();
            assertTrue(this.rs.isAfterLast());
            this.rs.previous();
            assertEquals("testBug30474158", this.rs.getString(1));
            this.rs.absolute(1);
            assertEquals("testBug30474158", this.rs.getString(1));
            this.rs.next();
            this.rs.afterLast();
            this.rs.relative(-1);
            assertEquals("testBug30474158", this.rs.getString(1));
        }
    }

    /**
     * Tests fix for Bug#97724 (30570721), Contribution: Allow \'3.\' formatted numbers.
     *
     * @throws Exception
     */
    @Test
    public void testBug97724() throws Exception {
        createTable("testBug97724", "(data VARCHAR(100))");
        assertEquals(4, this.stmt.executeUpdate("INSERT INTO testBug97724 VALUES ('0.0'), ('.1'), ('2.'), ('3.3')"));
        this.rs = this.stmt.executeQuery("SELECT * FROM testBug97724");
        assertTrue(this.rs.next());
        assertFalse(this.rs.getBoolean(1));
        assertEquals(0, this.rs.getInt(1));
        assertEquals(0.0d, this.rs.getDouble(1));
        assertTrue(this.rs.next());
        assertTrue(this.rs.getBoolean(1));
        assertEquals(0, this.rs.getInt(1));
        assertEquals(0.1d, this.rs.getDouble(1));
        assertTrue(this.rs.next());
        assertTrue(this.rs.getBoolean(1));
        assertEquals(2, this.rs.getInt(1));
        assertEquals(2.0d, this.rs.getDouble(1));
        assertTrue(this.rs.next());
        assertTrue(this.rs.getBoolean(1));
        assertEquals(3, this.rs.getInt(1));
        assertEquals(3.3d, this.rs.getDouble(1));
        assertFalse(this.rs.next());
    }

    /**
     * Tests fix for BUG#94457 (29402209), CONNECTOR/J RESULTSET.GETOBJECT( ..., OFFSETDATETIME.CLASS ) THROWS.
     *
     * @throws Exception
     */
    @Test
    public void testBug94457() throws Exception {
        boolean withFract = versionMeetsMinimum(5, 6, 4); // fractional seconds are not supported in previous versions
        createTable("testBug94457", withFract ? "(dt DATETIME(4) NOT NULL, ts TIMESTAMP(4) NOT NULL, t TIME(4) NOT NULL, odt VARCHAR(30), ot VARCHAR(20))"
                : "(dt DATETIME NOT NULL, ts TIMESTAMP NOT NULL, t TIME NOT NULL, odt VARCHAR(30), ot VARCHAR(20))");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false");

        for (boolean preserveInstants : new boolean[] { false, true }) {
            props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER");
            props.setProperty(PropertyKey.preserveInstants.getKeyName(), "" + preserveInstants);
            Connection c1 = getConnectionWithProps(props);

            TimeZone serverTz = ((MysqlConnection) c1).getSession().getServerSession().getSessionTimeZone();

            Statement st1 = c1.createStatement();
            st1.execute("INSERT INTO testBug94457 VALUES( NOW(4), NOW(4), NOW(4), '2019-01-20T12:00:00.12+06:00', '12:00:00.123+06:00' )");

            this.rs = st1.executeQuery("SELECT CONCAT('',dt) as origDate, dt, ts, CONCAT('',t) as origTime, t, odt, ot FROM testBug94457");
            this.rs.next();

            String origDate = this.rs.getString("origDate");
            String origTime = this.rs.getString("origTime");
            System.out.println("Original date string                 : " + origDate + " (" + serverTz + ")");
            System.out.println("getString(dt)                        : " + this.rs.getString("dt"));
            System.out.println("getString(ts)                        : " + this.rs.getString("ts"));
            System.out.println("Original time string                 : " + origTime + " (" + serverTz + ")");
            System.out.println("getString(t)                         : " + this.rs.getString("t"));
            assertEquals(this.rs.getString("origDate"), this.rs.getString("dt"));
            assertEquals(this.rs.getString("origDate"), this.rs.getString("ts"));
            assertEquals(this.rs.getString("origTime"), this.rs.getString("t"));

            Timestamp ts1 = this.rs.getTimestamp("dt");
            Timestamp ts2 = this.rs.getTimestamp("dt", Calendar.getInstance(preserveInstants ? serverTz : TimeZone.getDefault()));
            Timestamp ts3 = this.rs.getTimestamp("ts");
            Timestamp ts4 = this.rs.getTimestamp("ts", Calendar.getInstance(preserveInstants ? serverTz : TimeZone.getDefault()));
            ts1.setNanos(0);
            ts2.setNanos(0);
            ts3.setNanos(0);
            ts4.setNanos(0);
            System.out.println("getTimestamp(dt))                    : " + ts1 + " (" + ts1.getTime() + ")");
            System.out.println("getTimestamp(dt, GMT+10))            : " + ts2 + " (" + ts2.getTime() + ")");
            System.out.println("getTimestamp(ts))                    : " + ts3 + " (" + ts3.getTime() + ")");
            System.out.println("getTimestamp(ts, GMT+10))            : " + ts4 + " (" + ts4.getTime() + ")");
            assertEquals(ts1, ts2);
            assertEquals(ts3, ts4);
            assertEquals(ts1, ts4);

            Time t1 = this.rs.getTime("t");
            Time t2 = this.rs.getTime("t", Calendar.getInstance(TimeZone.getDefault()));
            System.out.println("getTime(t))                          : " + t1 + " (" + t1.getTime() + ")");
            System.out.println("getTime(t, GMT+10))                  : " + t2 + " (" + t2.getTime() + ")");
            assertEquals(t1, t2);

            Calendar cal1 = Calendar.getInstance(preserveInstants ? serverTz : TimeZone.getDefault());
            cal1.set(Integer.valueOf(origDate.substring(0, 4)), Integer.valueOf(origDate.substring(5, 7)) - 1, Integer.valueOf(origDate.substring(8, 10)),
                    Integer.valueOf(origDate.substring(11, 13)), Integer.valueOf(origDate.substring(14, 16)), Integer.valueOf(origDate.substring(17, 19)));
            Timestamp ts = new Timestamp(cal1.getTimeInMillis());
            ts.setNanos(0);
            System.out.println("Manually constructed Timestamp       : " + ts + " (" + ts.getTime() + ")");
            assertEquals(ts1, ts);

            Calendar cal2 = Calendar.getInstance(TimeZone.getDefault());
            cal2.set(1970, 0, 1, Integer.valueOf(origTime.substring(0, 2)), Integer.valueOf(origTime.substring(3, 5)),
                    Integer.valueOf(origTime.substring(6, 8)));
            cal2.set(Calendar.MILLISECOND, 0);
            int millis = 0;
            if (withFract) {
                StringBuilder millisStr = new StringBuilder("1" + origTime.substring(9));
                for (int i = millisStr.length(); i < 10; i++) {
                    millisStr.append('0');
                }
                millis = (int) (Long.valueOf(millisStr.toString()) / 1000000) - 1000;
            }
            Time t3 = new Time(cal2.getTimeInMillis() + millis);

            System.out.println("Manually constructed Time            : " + t3 + " (" + t3.getTime() + ")");
            assertEquals(t1, t3);

            OffsetDateTime odt1 = this.rs.getObject("dt", OffsetDateTime.class);
            OffsetDateTime odt2 = this.rs.getObject("ts", OffsetDateTime.class);
            OffsetDateTime odt3 = this.rs.getObject("odt", OffsetDateTime.class);
            System.out.println("getObject(dt, OffsetDateTime.class)  : " + odt1 + " (" + odt1.toEpochSecond() + ")");
            System.out.println("getObject(ts, OffsetDateTime.class)  : " + odt2 + " (" + odt2.toEpochSecond() + ")");
            System.out.println("getObject(odt, OffsetDateTime.class) : " + odt3 + " (" + odt3.toEpochSecond() + ")");

            int localOffset = ZoneId.of(TimeZone.getDefault().getID()).getRules().getOffset(Instant.now()).getTotalSeconds();
            int serverOffset = ZoneId.of(serverTz.getID()).getRules().getOffset(Instant.now()).getTotalSeconds();

            int expOffset = 6 * 60 * 60;

            assertEquals(preserveInstants ? serverOffset : localOffset, odt1.getOffset().getTotalSeconds());
            assertEquals(preserveInstants ? serverOffset : localOffset, odt2.getOffset().getTotalSeconds());
            assertEquals(expOffset, odt3.getOffset().getTotalSeconds());
            assertEquals(ts1.getTime(), odt1.toEpochSecond() * 1000);
            assertEquals(ts1.getTime(), odt2.toEpochSecond() * 1000);
            assertEquals(LocalDate.of(2019, 1, 20), odt3.toLocalDate());

            ZonedDateTime zdt1 = this.rs.getObject("dt", ZonedDateTime.class);
            ZonedDateTime zdt2 = this.rs.getObject("ts", ZonedDateTime.class);
            System.out.println("getObject(dt, ZonedDateTime.class)   : " + odt1 + " (" + zdt1.toEpochSecond() + ")");
            System.out.println("getObject(ts, ZonedDateTime.class)   : " + odt2 + " (" + zdt2.toEpochSecond() + ")");

            assertEquals(preserveInstants ? serverOffset : localOffset, zdt1.getOffset().getTotalSeconds());
            assertEquals(preserveInstants ? serverOffset : localOffset, zdt2.getOffset().getTotalSeconds());
            assertEquals(ts1.getTime(), zdt1.toEpochSecond() * 1000);
            assertEquals(ts1.getTime(), zdt2.toEpochSecond() * 1000);

            OffsetTime ot1 = this.rs.getObject("ot", OffsetTime.class);
            System.out.println("getObject(ot, OffsetTime.class)      : " + ot1);
            assertEquals(expOffset, ot1.getOffset().getTotalSeconds());
            assertEquals(LocalTime.of(12, 0, 0, 123000000), ot1.toLocalTime());
        }
    }

    /**
     * Tests fix for Bug#99013 (31074051), AN EXTRA HOUR GETS ADDED TO THE TIMESTAMP WHEN SUBTRACTING INTERVAL 'N' DAYS.
     *
     * @throws Exception
     */
    @Test
    public void testBug99013() throws Exception {
        createTable("partorder", "(PNO char(2), SNO char(2), ORDERDATE date, RECEIVEDATE date)");
        this.stmt.executeUpdate("INSERT INTO partorder VALUES('P1','S1','1990-04-30','1990-06-21')");

        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL");
        Connection testConn = getConnectionWithProps(props);

        this.pstmt = testConn
                .prepareStatement("SELECT (TIMESTAMP '2018-04-01 00:00:00'- interval(0) day) AS Test FROM partorder where PNO = 'P1' and SNO ='S1'");
        this.rs = this.pstmt.executeQuery();
        while (this.rs.next()) {
            assertEquals(Timestamp.valueOf("2018-04-01 00:00:00"), this.rs.getTimestamp(1));
        }
    }

    /**
     * Tests fix for Bug#31747910, BUG 30474158 FIX IMPROVES JDBC COMPLIANCE BUT CHANGES DEFAULT RESULTSETTYPE HANDLING.
     *
     * @throws Exception
     */
    @Test
    public void testBug31747910() throws Exception {
        createTable("testBug31747910", "(id INT)");
        this.stmt.executeUpdate("INSERT INTO testBug31747910 VALUES (1), (2), (3), (4), (5)");

        /*
         * Expected exceptions with default RS type and:
         * 0. static;
         * 1. streaming;
         * 2. streaming & scroll-tolerant;
         * 3. cursor-based;
         * 4. cursor-based & scroll-tolerant.
         */
        String[] connOpts = new String[] { "useSSL=false,allowPublicKeyRetrieval=true", "useSSL=false,allowPublicKeyRetrieval=true",
                "useSSL=false,allowPublicKeyRetrieval=true,scrollTolerantForwardOnly=true", "useSSL=false,allowPublicKeyRetrieval=true,useCursorFetch=true",
                "useSSL=false,allowPublicKeyRetrieval=true,useCursorFetch=true,scrollTolerantForwardOnly=true" };
        int[] fetchSize = new int[] { 0, Integer.MIN_VALUE, Integer.MIN_VALUE, 2, 2 };
        for (int i = 0; i < connOpts.length; i++) {
            for (int j = 0; j < 3; j++) { // Statement; PreparedStatement and ServerPreparedStatement.
                Connection testConn = null;
                Statement testStmt = null;
                switch (j) {
                    case 0:
                        // Default behavior using Statement
                        testConn = getConnectionWithProps(connOpts[i]);
                        testStmt = testConn.createStatement();
                        if (fetchSize[i] != 0) {
                            testStmt.setFetchSize(fetchSize[i]);
                        }
                        this.rs = testStmt.executeQuery("SELECT * FROM testBug31747910");
                        break;
                    case 1:
                        // Default behavior using PreparedStatement
                        testConn = getConnectionWithProps(connOpts[i]);
                        testStmt = testConn.prepareStatement("SELECT * FROM testBug31747910");
                        if (fetchSize[i] != 0) {
                            testStmt.setFetchSize(fetchSize[i]);
                        }
                        this.rs = ((PreparedStatement) testStmt).executeQuery();
                        break;
                    case 2:
                        // Default behavior using ServerPreparedStatement
                        testConn = getConnectionWithProps("useServerPrepStmts=true," + connOpts[i]);
                        testStmt = testConn.prepareStatement("SELECT * FROM testBug31747910");
                        if (fetchSize[i] != 0) {
                            testStmt.setFetchSize(fetchSize[i]);
                        }
                        this.rs = ((PreparedStatement) testStmt).executeQuery();
                        break;
                }

                assertTrue(this.rs.next());
                assertEquals(1, this.rs.getInt(1));
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> this.rs.last());
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> this.rs.previous());
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> this.rs.first());
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> this.rs.absolute(3));
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> this.rs.relative(-1));
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> {
                    this.rs.beforeFirst();
                    return null;
                });
                assertThrows(SQLException.class, "Operation not allowed for a result set of type ResultSet\\.TYPE_FORWARD_ONLY\\.", () -> {
                    this.rs.afterLast();
                    return null;
                });
                testStmt.close();
                testConn.close();
            }
        }

        // Scroll-tolerant behavior using: Statement; PreparedStatement; ServerPreparedStatement.
        for (int i = 0; i < 3; i++) {
            Connection testConn = null;
            Statement testStmt = null;
            switch (i) {
                case 0:
                    // Scroll-tolerant using Statement
                    testConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,scrollTolerantForwardOnly=true");
                    testStmt = testConn.createStatement();
                    this.rs = testStmt.executeQuery("SELECT * FROM testBug31747910");
                    break;
                case 1:
                    // Scroll-tolerant using PreparedStatement
                    testConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,scrollTolerantForwardOnly=true");
                    testStmt = testConn.prepareStatement("SELECT * FROM testBug31747910");
                    this.rs = ((PreparedStatement) testStmt).executeQuery();
                    break;
                case 2:
                    // Scroll-tolerant using ServerPreparedStatement
                    testConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,useServerPrepStmts=true,scrollTolerantForwardOnly=true");
                    testStmt = testConn.prepareStatement("SELECT * FROM testBug31747910");
                    this.rs = ((PreparedStatement) testStmt).executeQuery();
                    break;
            }
            assertTrue(this.rs.next());
            assertEquals(1, this.rs.getInt(1));
            assertTrue(this.rs.last());
            assertEquals(5, this.rs.getInt(1));
            assertTrue(this.rs.previous());
            assertEquals(4, this.rs.getInt(1));
            assertTrue(this.rs.first());
            assertEquals(1, this.rs.getInt(1));
            assertTrue(this.rs.absolute(3));
            assertEquals(3, this.rs.getInt(1));
            assertTrue(this.rs.relative(-1));
            assertEquals(2, this.rs.getInt(1));
            this.rs.beforeFirst();
            assertTrue(this.rs.isBeforeFirst());
            this.rs.afterLast();
            assertTrue(this.rs.isAfterLast());
            testStmt.close();
            testConn.close();
        }
    }

    /**
     * Tests fix for Bug#102321 (32405590), CALLING RESULTSETMETADATA.GETCOLUMNCLASSNAME RETURNS WRONG VALUE FOR DATETIME.
     *
     * @throws Exception
     */
    @Test
    public void testBug102321() throws Exception {
        createTable("testBug102321",
                "(dcol date NOT NULL, dtcol datetime NOT NULL, ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)");
        this.stmt.executeUpdate("INSERT INTO testBug102321(dcol, dtcol) VALUES (now(), now())");

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug102321");
        assertTrue(this.rs.next());
        final ResultSetMetaData rsm = this.rs.getMetaData();
        for (int colnum = 1; colnum <= rsm.getColumnCount(); colnum++) {
            Object ob = this.rs.getObject(colnum);
            assertEquals(ob.getClass().getName(), rsm.getColumnClassName(colnum),
                    "Wrong ResultSetMetaData metadata for column type " + rsm.getColumnTypeName(colnum));
        }
    }

    /**
     * Tests fix for Bug#102131 (32338451), UPDATABLERESULTSET NPE WHEN USING DERIVED QUERIES OR VIEWS.
     *
     * @throws Exception
     */
    @Test
    public void testBug102131() throws Exception {
        createTable("testBug102131User", "(id int,name varchar(10))");
        createTable("testBug102131Age", "(id int,age int)");
        createView("testBug102131View",
                "as select name,ifnull(age,0) age from testBug102131User inner join testBug102131Age on testBug102131User.id = testBug102131Age.id");

        this.stmt.executeUpdate("INSERT INTO testBug102131User VALUES (1, 'a')");
        this.stmt.executeUpdate("INSERT INTO testBug102131Age VALUES (1, 20)");

        Statement st1 = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        this.rs = st1.executeQuery("select * from testBug102131View");
        assertTrue(this.rs.next());
        assertEquals("a", this.rs.getString("name"));
        assertEquals(20, this.rs.getInt("age"));
    }

    /**
     * Tests fix for Bug#20391659, GETBYTE() CALL RESULTS IN EXCEPTION WHEN USEUSAGEADVISOR = TRUE.
     *
     * @throws Exception
     */
    @Test
    public void testBug20391659() throws Exception {
        createTable("testBug20391659", "(c1 char(1),c2 char(1))");
        this.stmt.executeUpdate("INSERT INTO testBug20391659 VALUES('1','0')");

        Connection con = null;
        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true");
            props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true");
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
            con = getConnectionWithProps(props);

            PreparedStatement ps = con.prepareStatement("select * from testBug20391659 ", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
            this.rs = ps.executeQuery();
            while (this.rs.next()) {
                assertEquals('1', this.rs.getByte(1)); // was issuing java.lang.ArrayIndexOutOfBoundsException
                assertEquals('0', this.rs.getByte(2));
            }
        } finally {
            if (con != null) {
                con.close();
            }
        }
    }

    /**
     * Tests fix for Bug#20391631, GETBOOLEAN() CALL RESULTS IN EXCEPTION WHEN USEUSAGEADVISOR = TRUE.
     *
     * @throws Exception
     */
    @Test
    public void testBug20391631() throws Exception {
        createTable("testBug20391631", "(c1 char(1),c2 char(1))");
        this.stmt.executeUpdate("INSERT INTO testBug20391631 VALUES('1','0')");

        Connection con = null;
        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true");
            props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true");
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
            con = getConnectionWithProps(props);

            this.rs = con.createStatement().executeQuery("select * from testBug20391631");
            while (this.rs.next()) {
                assertTrue(this.rs.getBoolean(1)); // was issuing java.lang.ArrayIndexOutOfBoundsException
                assertFalse(this.rs.getBoolean(2));
            }
        } finally {
            if (con != null) {
                con.close();
            }
        }
    }

    /**
     * Tests fix for Bug#19805370, GETBINARYSTREAM() WITH INVALID COLUMN INDEX RETURNS EXCEPTION.
     *
     * @throws Exception
     */
    @Test
    public void testBug19805370() throws Exception {
        Connection con = null;
        try {
            Properties props = new Properties();
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true");
            con = getConnectionWithProps(props);

            PreparedStatement ps = con.prepareStatement("select 'abcd'");
            this.rs = ps.executeQuery();
            this.rs.next();

            assertThrows(SQLException.class, "Column Index out of range, 0 < 1.*", () -> {
                this.rs.getBinaryStream(0);
                return null;
            });
            assertThrows(SQLException.class, "Column Index out of range, 0 < 1.*", () -> {
                this.rs.getCharacterStream(0);
                return null;
            });
            assertThrows(SQLException.class, "Column Index out of range, 0 < 1.*", () -> {
                this.rs.getAsciiStream(0);
                return null;
            });
        } finally {
            if (con != null) {
                con.close();
            }
        }
    }

    /**
     * Tests fix for Bug#20802947, RESULTSET UPDATE METHODS FAILS WHEN TABLENAME CONTAINS SPECIAL CHARACTERS.
     *
     * @throws Exception
     */
    @Test
    public void testBug20802947() throws Exception {
        createTable("`test``Bug20802947`", "(id int,c char(10),primary key(id))");
        this.stmt.executeUpdate("INSERT INTO `test``Bug20802947` VALUES(10,'a'),(20,'b'),(30,'c'),(40,'d'),(50,'e')");

        Statement st = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        ResultSet rs1 = st.executeQuery("select * from `test``Bug20802947`");
        rs1.absolute(1);
        rs1.updateString(2, rs1.getString(2) + rs1.getString(2));
        rs1.updateRow();
        rs1.close();
        this.rs = this.stmt.executeQuery("select * from  `test``Bug20802947` where id=10");
        while (this.rs.next()) {
            assertEquals(10, this.rs.getInt(1));
            assertEquals("aa", this.rs.getString(2));
        }
        this.rs.close();

        rs1 = st.executeQuery("select * from `test``Bug20802947`");
        rs1.absolute(1);
        rs1.updateNull(2);
        rs1.updateRow();
        rs1.close();
        this.rs = this.stmt.executeQuery("select * from  `test``Bug20802947` where id=10");
        while (this.rs.next()) {
            assertEquals(10, this.rs.getInt(1));
            assertNull(this.rs.getString(2));
        }
        rs1.close();
    }

    /**
     * Tests fix for Bug#32954396, EXECUTEQUERY HANGS WITH USECURSORFETCH=TRUE & SETFETCHSIZE.
     *
     * @throws Exception
     */
    @Test
    public void testBug32954396() throws Exception {
        createTable("testBug32954396", "(id INT, name VARCHAR(10))");

        this.stmt.executeUpdate("INSERT INTO testBug32954396 VALUES (1, 'value1'), (2, 'value2')");

        boolean useCursorFetch = false;
        boolean setFetchSize = false;
        do {
            String testCase = String.format("Case: [useCursorFetch=%s, setFetchSize=%s]", useCursorFetch ? "Y" : "N", setFetchSize ? "Y" : "N");
            Properties props = new Properties();
            props.setProperty(PropertyKey.socketTimeout.getKeyName(), "1000");
            props.setProperty(PropertyKey.useCursorFetch.getKeyName(), Boolean.toString(useCursorFetch));
            props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
            props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
            Connection testConn = getConnectionWithProps(props);

            this.pstmt = testConn.prepareStatement("SELECT id, name, (SELECT id FROM testBug32954396) FROM testBug32954396");
            if (setFetchSize) {
                this.pstmt.setFetchSize(1);
            }
            assertThrows(testCase, SQLException.class, "Subquery returns more than 1 row", this.pstmt::executeQuery);
            testConn.close();
        } while ((useCursorFetch = !useCursorFetch) || (setFetchSize = !setFetchSize));
    }

    /**
     * Tests fix for Bug#33185116, Have method ResultSet.getBoolean() supporting conversion of 'T' and 'F' in a VARCHAR to True/False (boolean).
     * Extended the test for BUG#92574 (28706219), WHEN CONVERTING FROM VARCHAR TO JAVA BOOLEAN, 'N' IS NOT SUPPORTED.
     *
     * @throws Exception
     */
    @Test
    public void testBug33185116() throws Exception {
        String[] strValues = new String[] { null, "N", "n", "Y", "y", "0", "1", "T", "t", "F", "f", "yes", "Yes", "no", "No", "true", "TrUe", "false",
                "FalsE" };
        boolean[] boolValues = new boolean[] { false, false, false, true, true, false, true, true, true, false, false, true, true, false, false, true, true,
                false, false };

        createTable("testBug33185116", "(id int not null, f varchar(5), key(id))");
        for (int i = 0; i < strValues.length; i++) {
            String val = strValues[i] == null ? null : "'" + strValues[i] + "'";
            this.stmt.executeUpdate("insert into testBug33185116 values(" + i + "," + val + ")");
        }
        this.rs = this.stmt.executeQuery("SELECT * from testBug33185116");
        while (this.rs.next()) {
            int i = this.rs.getInt(1);
            assertEquals(strValues[i], this.rs.getString(2));
            assertEquals(boolValues[i], this.rs.getBoolean(2));
        }
    }

    /**
     * Tests fix for Bug#105197 (33461744), Statement.executeQuery() may return non-navigable ResultSet.
     *
     * @throws Exception
     */
    @Test
    public void testBug105197() throws Exception {
        createProcedure("testBug105197Proc", "() BEGIN END");
        this.rs = this.stmt.executeQuery("CALL testBug105197Proc()");
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.absolute(1);
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.relative(1);
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.getRow();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.beforeFirst();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.isBeforeFirst();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.first();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.isFirst();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.previous();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.next();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.last();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.isLast();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.afterLast();
            return null;
        });
        assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> {
            this.rs.isAfterLast();
            return null;
        });
    }

    /**
     * Tests fix for Bug#102678 (Bug#32536384), Connector/J 8 query with explain can not return ResultRow.
     * Resolved by fix for Bug#103878 (32954449), CONNECTOR/J 8 : QUERY WITH 'SHOW XXX' WILL GET EXCEPTION WHEN USE CURSOR.
     *
     * @throws Exception
     */
    @Test
    void testBug102678() throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED");
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true");
        Connection testConn = getConnectionWithProps(props);

        this.pstmt = testConn.prepareStatement("EXPLAIN SELECT 1");
        this.pstmt.setFetchSize(100);
        this.rs = this.pstmt.executeQuery();
        assertTrue(this.rs.next());
        this.pstmt.close();
        testConn.close();
    }

    /**
     * Tests fix for Bug#68608 (Bug#16690898), UpdatableResultSet does not properly handle unsigned primary key.
     *
     * @throws Exception
     */
    @Test
    public void testBug68608() throws Exception {
        createTable("testBug68608", "(id INT UNSIGNED NOT NULL PRIMARY KEY, data VARCHAR(100))");

        Statement testStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

        // Insert row.
        ResultSet testRs = testStmt.executeQuery("SELECT * FROM testBug68608");
        testRs.moveToInsertRow();
        testRs.updateLong(1, 1L + Integer.MAX_VALUE);
        testRs.updateString(2, "MySQL Connector/J 1");
        testRs.insertRow();
        testRs.close();

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug68608");
        assertTrue(this.rs.next());
        assertEquals(1L + Integer.MAX_VALUE, this.rs.getLong(1));
        assertEquals("MySQL Connector/J 1", this.rs.getString(2));
        assertFalse(this.rs.next());

        // Refresh row.
        testRs = testStmt.executeQuery("SELECT * FROM testBug68608");
        testRs.next();
        testRs.updateString(2, "MySQL Connector/J 2");
        testRs.refreshRow();
        testRs.close();

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug68608");
        assertTrue(this.rs.next());
        assertEquals(1L + Integer.MAX_VALUE, this.rs.getLong(1));
        assertEquals("MySQL Connector/J 1", this.rs.getString(2));
        assertFalse(this.rs.next());

        // Update row.
        testRs = testStmt.executeQuery("SELECT * FROM testBug68608");
        testRs.next();
        testRs.updateLong(1, 2L + Integer.MAX_VALUE);
        testRs.updateString(2, "MySQL Connector/J 2");
        testRs.updateRow();
        testRs.close();

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug68608");
        assertTrue(this.rs.next());
        assertEquals(2L + Integer.MAX_VALUE, this.rs.getLong(1));
        assertEquals("MySQL Connector/J 2", this.rs.getString(2));
        assertFalse(this.rs.next());

        // Delete row.
        testRs = testStmt.executeQuery("SELECT * FROM testBug68608");
        testRs.next();
        testRs.deleteRow();
        testRs.close();

        this.rs = this.stmt.executeQuery("SELECT * FROM testBug68608");
        assertFalse(this.rs.next());
    }

    /**
     * Tests fix for Bug#107215 (Bug#34139593), ClassCastException: java.time.LocalDateTime cannot be cast to java.sql.Timestamp.
     *
     * Was failing in CachedRowSet.getDate() and CachedRowSet.getTimestamp() because ResultSet.getObject() returns java.time.LocalDateTime while the code in
     * CachedRowSetImpl tries to cast the value to java.sql.Timestamp.
     * See also: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/com/sun/rowset/CachedRowSetImpl.java#l2140
     * See also: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/com/sun/rowset/CachedRowSetImpl.java#l619
     *
     * @throws Exception
     */
    @Test
    void testBug107215() throws Exception {
        createTable("testBug107215", "(dt DATETIME, ts TIMESTAMP)");
        this.stmt.execute("INSERT INTO testBug107215 VALUES(NOW(), NOW())");

        final String sql = "SELECT * FROM testBug107215";
        final String testDbUrl = dbUrl + (dbUrl.contains("?") ? "&" : "?");

        try (Connection testConn = getConnectionWithProps("treatMysqlDatetimeAsTimestamp=false")) {
            Statement testStmt = testConn.createStatement();
            this.rs = testStmt.executeQuery(sql);
            assertTrue(this.rs.next());
            assertEquals(LocalDateTime.class, this.rs.getObject(1).getClass());
            assertEquals(Timestamp.class, this.rs.getObject(2).getClass());
            assertEquals(Date.class, this.rs.getDate(1).getClass());
            assertEquals(Date.class, this.rs.getDate(2).getClass());
            assertEquals(Timestamp.class, this.rs.getTimestamp(1).getClass());
            assertEquals(Timestamp.class, this.rs.getTimestamp(2).getClass());

            RowSetFactory rowSetFact = RowSetProvider.newFactory();
            JdbcRowSet testRowSet1 = rowSetFact.createJdbcRowSet();
            testRowSet1.setCommand(sql);
            testRowSet1.setUrl(testDbUrl + "treatMysqlDatetimeAsTimestamp=false");
            testRowSet1.execute();
            assertTrue(testRowSet1.next());
            assertEquals(LocalDateTime.class, testRowSet1.getObject(1).getClass());
            assertEquals(Timestamp.class, testRowSet1.getObject(2).getClass());
            assertEquals(Date.class, testRowSet1.getDate(1).getClass());
            assertEquals(Date.class, testRowSet1.getDate(2).getClass());
            assertEquals(Timestamp.class, testRowSet1.getTimestamp(1).getClass());
            assertEquals(Timestamp.class, testRowSet1.getTimestamp(2).getClass());

            CachedRowSet testRowSet2 = rowSetFact.createCachedRowSet();
            testRowSet2.populate(testStmt.executeQuery(sql));
            assertTrue(testRowSet2.next());
            assertEquals(LocalDateTime.class, testRowSet2.getObject(1).getClass());
            assertEquals(Timestamp.class, testRowSet2.getObject(2).getClass());
            assertThrows(ClassCastException.class, () -> testRowSet2.getDate(1).getClass()); // Non-expected behavior.
            assertEquals(Date.class, testRowSet2.getDate(2).getClass());
            assertThrows(ClassCastException.class, () -> testRowSet2.getTimestamp(1).getClass()); // Non-expected behavior.
            assertEquals(Timestamp.class, testRowSet2.getTimestamp(2).getClass());
        }

        try (Connection testConn = getConnectionWithProps("treatMysqlDatetimeAsTimestamp=true")) {
            Statement testStmt = testConn.createStatement();
            this.rs = testStmt.executeQuery(sql);
            assertTrue(this.rs.next());
            assertEquals(Timestamp.class, this.rs.getObject(1).getClass());
            assertEquals(Timestamp.class, this.rs.getObject(2).getClass());
            assertEquals(Date.class, this.rs.getDate(1).getClass());
            assertEquals(Date.class, this.rs.getDate(2).getClass());
            assertEquals(Timestamp.class, this.rs.getTimestamp(1).getClass());
            assertEquals(Timestamp.class, this.rs.getTimestamp(2).getClass());

            RowSetFactory rowSetFact = RowSetProvider.newFactory();
            JdbcRowSet testRowSet1 = rowSetFact.createJdbcRowSet();
            testRowSet1.setCommand(sql);
            testRowSet1.setUrl(testDbUrl + "treatMysqlDatetimeAsTimestamp=true");
            testRowSet1.execute();
            assertTrue(testRowSet1.next());
            assertEquals(Timestamp.class, testRowSet1.getObject(1).getClass());
            assertEquals(Timestamp.class, testRowSet1.getObject(2).getClass());
            assertEquals(Date.class, testRowSet1.getDate(1).getClass());
            assertEquals(Date.class, testRowSet1.getDate(2).getClass());
            assertEquals(Timestamp.class, testRowSet1.getTimestamp(1).getClass());
            assertEquals(Timestamp.class, testRowSet1.getTimestamp(2).getClass());

            CachedRowSet testRowSet2 = rowSetFact.createCachedRowSet();
            testRowSet2.populate(testStmt.executeQuery(sql));
            assertTrue(testRowSet2.next());
            assertEquals(Timestamp.class, testRowSet2.getObject(1).getClass());
            assertEquals(Timestamp.class, testRowSet2.getObject(2).getClass());
            assertEquals(Date.class, testRowSet2.getDate(1).getClass()); // Expected behavior.
            assertEquals(Date.class, testRowSet2.getDate(2).getClass());
            assertEquals(Timestamp.class, testRowSet2.getTimestamp(1).getClass()); // Expected behavior.
            assertEquals(Timestamp.class, testRowSet2.getTimestamp(2).getClass());
        }
    }

}
