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

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

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

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

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

 */

package com.mysql.jdbc;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.ref.SoftReference;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.Deflater;

import com.mysql.jdbc.authentication.MysqlClearPasswordPlugin;
import com.mysql.jdbc.authentication.MysqlNativePasswordPlugin;
import com.mysql.jdbc.authentication.MysqlOldPasswordPlugin;
import com.mysql.jdbc.authentication.Sha256PasswordPlugin;
import com.mysql.jdbc.exceptions.MySQLStatementCancelledException;
import com.mysql.jdbc.exceptions.MySQLTimeoutException;
import com.mysql.jdbc.log.LogUtils;
import com.mysql.jdbc.profiler.ProfilerEvent;
import com.mysql.jdbc.profiler.ProfilerEventHandler;
import com.mysql.jdbc.util.ReadAheadInputStream;
import com.mysql.jdbc.util.ResultSetUtil;


/**
 * This class is used by Connection for communicating with the MySQL server.
 *
 * @author Mark Matthews
 * @version $Id$
 *
 * @see java.sql.Connection
 */
public class MysqlIO {
    private static final int UTF8_CHARSET_INDEX = 33;
    private static final String CODE_PAGE_1252 = "Cp1252";
	 protected static final int NULL_LENGTH = ~0;
    protected static final int COMP_HEADER_LENGTH = 3;
    protected static final int MIN_COMPRESS_LEN = 50;
    protected static final int HEADER_LENGTH = 4;
    protected static final int AUTH_411_OVERHEAD = 33;
    private static int maxBufferSize = 65535;

    private static final String NONE = "none";
    
    private static final int	CLIENT_LONG_PASSWORD		= 0x00000001; /* new more secure passwords */
    private static final int	CLIENT_FOUND_ROWS			= 0x00000002;
    private static final int	CLIENT_LONG_FLAG			= 0x00000004; /* Get all column flags */
    protected static final int	CLIENT_CONNECT_WITH_DB		= 0x00000008;
    private static final int	CLIENT_COMPRESS				= 0x00000020; /* Can use compression protcol */
    private static final int	CLIENT_LOCAL_FILES			= 0x00000080; /* Can use LOAD DATA LOCAL */
    private static final int	CLIENT_PROTOCOL_41			= 0x00000200; // for > 4.1.1
    private static final int	CLIENT_INTERACTIVE			= 0x00000400;
    protected static final int	CLIENT_SSL					= 0x00000800;
    private static final int	CLIENT_TRANSACTIONS			= 0x00002000; // Client knows about transactions
    protected static final int	CLIENT_RESERVED				= 0x00004000; // for 4.1.0 only
    protected static final int	CLIENT_SECURE_CONNECTION	= 0x00008000;
    private static final int	CLIENT_MULTI_STATEMENTS		= 0x00010000; // Enable/disable multiquery support
    private static final int	CLIENT_MULTI_RESULTS		= 0x00020000; // Enable/disable multi-results
    private static final int	CLIENT_PLUGIN_AUTH			= 0x00080000;
    private static final int	CLIENT_CONNECT_ATTRS		= 0x00100000;
    private static final int	CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA	= 0x00200000;
    private static final int	CLIENT_CAN_HANDLE_EXPIRED_PASSWORD		= 0x00400000;

	private static final int SERVER_STATUS_IN_TRANS = 1;
    private static final int SERVER_STATUS_AUTOCOMMIT = 2; // Server in auto_commit mode
    static final int SERVER_MORE_RESULTS_EXISTS = 8; // Multi query - next query exists
    private static final int SERVER_QUERY_NO_GOOD_INDEX_USED = 16;
    private static final int SERVER_QUERY_NO_INDEX_USED = 32;
    private static final int SERVER_QUERY_WAS_SLOW = 2048;
	private static final int  SERVER_STATUS_CURSOR_EXISTS = 64;
    private static final String FALSE_SCRAMBLE = "xxxxxxxx"; //$NON-NLS-1$
    protected static final int MAX_QUERY_SIZE_TO_LOG = 1024; // truncate logging of queries at 1K
    protected static final int MAX_QUERY_SIZE_TO_EXPLAIN = 1024 * 1024; // don't explain queries above 1MB
    protected static final int INITIAL_PACKET_SIZE = 1024;
    /**
     * We store the platform 'encoding' here, only used to avoid munging
     * filenames for LOAD DATA LOCAL INFILE...
     */
    private static String jvmPlatformCharset = null;

    /**
     * We need to have a 'marker' for all-zero datetimes so that ResultSet
     * can decide what to do based on connection setting
     */
    protected final static String ZERO_DATE_VALUE_MARKER = "0000-00-00";
    protected final static String ZERO_DATETIME_VALUE_MARKER = "0000-00-00 00:00:00";

    private static final String EXPLAINABLE_STATEMENT = "SELECT";
	private static final String[] EXPLAINABLE_STATEMENT_EXTENSION = new String[] { "INSERT", "UPDATE", "REPLACE",
			"DELETE" };

	static {
        OutputStreamWriter outWriter = null;

        //
        // Use the I/O system to get the encoding (if possible), to avoid
        // security restrictions on System.getProperty("file.encoding") in
        // applets (why is that restricted?)
        //
        try {
            outWriter = new OutputStreamWriter(new ByteArrayOutputStream());
            jvmPlatformCharset = outWriter.getEncoding();
        } finally {
            try {
                if (outWriter != null) {
                    outWriter.close();
                }
            } catch (IOException ioEx) {
                // ignore
            }
        }
    }

    /** Max number of bytes to dump when tracing the protocol */
    private final static int MAX_PACKET_DUMP_LENGTH = 1024;
    private boolean packetSequenceReset = false;
    protected int serverCharsetIndex;

    //
    // Use this when reading in rows to avoid thousands of new()
    // calls, because the byte arrays just get copied out of the
    // packet anyway
    //
    private Buffer reusablePacket = null;
    private Buffer sendPacket = null;
    private Buffer sharedSendPacket = null;

    /** Data to the server */
    protected BufferedOutputStream mysqlOutput = null;
    protected MySQLConnection connection;
    private Deflater deflater = null;
    protected InputStream mysqlInput = null;
    private LinkedList<StringBuffer> packetDebugRingBuffer = null;
    private RowData streamingData = null;

    /** The connection to the server */
    protected Socket mysqlConnection = null;
    protected SocketFactory socketFactory = null;

    //
    // Packet used for 'LOAD DATA LOCAL INFILE'
    //
    // We use a SoftReference, so that we don't penalize intermittent
    // use of this feature
    //
    private SoftReference<Buffer> loadFileBufRef;

    //
    // Used to send large packets to the server versions 4+
    // We use a SoftReference, so that we don't penalize intermittent
    // use of this feature
    //
    private SoftReference<Buffer> splitBufRef;
    private SoftReference<Buffer> compressBufRef;
    protected String host = null;
    protected String seed;
    private String serverVersion = null;
    private String socketFactoryClassName = null;
    private byte[] packetHeaderBuf = new byte[4];
    private boolean colDecimalNeedsBump = false; // do we need to increment the colDecimal flag?
    private boolean hadWarnings = false;
    private boolean has41NewNewProt = false;

    /** Does the server support long column info? */
    private boolean hasLongColumnInfo = false;
    private boolean isInteractiveClient = false;
    private boolean logSlowQueries = false;

    /**
     * Does the character set of this connection match the character set of the
     * platform
     */
    private boolean platformDbCharsetMatches = true; // changed once we've connected.
    private boolean profileSql = false;
    private boolean queryBadIndexUsed = false;
    private boolean queryNoIndexUsed = false;
    private boolean serverQueryWasSlow = false;

    /** Should we use 4.1 protocol extensions? */
    private boolean use41Extensions = false;
    private boolean useCompression = false;
    private boolean useNewLargePackets = false;
    private boolean useNewUpdateCounts = false; // should we use the new larger update counts?
    private byte packetSequence = 0;
    private byte compressedPacketSequence = 0;
    private byte readPacketSequence = -1;
    private boolean checkPacketSequence = false;
    private byte protocolVersion = 0;
    private int maxAllowedPacket = 1024 * 1024;
    protected int maxThreeBytes = 255 * 255 * 255;
    protected int port = 3306;
    protected int serverCapabilities;
    private int serverMajorVersion = 0;
    private int serverMinorVersion = 0;
    private int oldServerStatus = 0;
    private int serverStatus = 0;
    private int serverSubMinorVersion = 0;
    private int warningCount = 0;
    protected long clientParam = 0;
    protected long lastPacketSentTimeMs = 0;
    protected long lastPacketReceivedTimeMs = 0;
    private boolean traceProtocol = false;
    private boolean enablePacketDebug = false;
	private boolean useConnectWithDb;
	private boolean needToGrabQueryFromPacket;
	private boolean autoGenerateTestcaseScript;
	private long threadId;
	private boolean useNanosForElapsedTime;
	private long slowQueryThreshold;
	private String queryTimingUnits;
	private boolean useDirectRowUnpack = true;
	private int useBufferRowSizeThreshold;
	private int commandCount = 0;
	private List<StatementInterceptorV2> statementInterceptors;
	private ExceptionInterceptor exceptionInterceptor;
    private int authPluginDataLength = 0;
	
    /**
     * Constructor:  Connect to the MySQL server and setup a stream connection.
     *
     * @param host the hostname to connect to
     * @param port the port number that the server is listening on
     * @param props the Properties from DriverManager.getConnection()
     * @param socketFactoryClassName the socket factory to use
     * @param conn the Connection that is creating us
     * @param socketTimeout the timeout to set for the socket (0 means no
     *        timeout)
     *
     * @throws IOException if an IOException occurs during connect.
     * @throws SQLException if a database access error occurs.
     */
    public MysqlIO(String host, int port, Properties props,
        String socketFactoryClassName, MySQLConnection conn,
        int socketTimeout, int useBufferRowSizeThreshold) throws IOException, SQLException {
        this.connection = conn;
        
        if (this.connection.getEnablePacketDebug()) {
            this.packetDebugRingBuffer = new LinkedList<StringBuffer>();
        }
        this.traceProtocol = this.connection.getTraceProtocol();
        

        this.useAutoSlowLog = this.connection.getAutoSlowLog();
        
        this.useBufferRowSizeThreshold = useBufferRowSizeThreshold;
        this.useDirectRowUnpack = this.connection.getUseDirectRowUnpack();

        this.logSlowQueries = this.connection.getLogSlowQueries();

        this.reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
        this.sendPacket = new Buffer(INITIAL_PACKET_SIZE);

        this.port = port;
        this.host = host;

        this.socketFactoryClassName = socketFactoryClassName;
        this.socketFactory = createSocketFactory();
        this.exceptionInterceptor = this.connection.getExceptionInterceptor();
        
        try {
        	this.mysqlConnection = this.socketFactory.connect(this.host,
        		this.port, props);
	        
	
	        if (socketTimeout != 0) {
	        	try {
	        		this.mysqlConnection.setSoTimeout(socketTimeout);
	        	} catch (Exception ex) {
	        		/* Ignore if the platform does not support it */
	        	}
	        }
	
	        this.mysqlConnection = this.socketFactory.beforeHandshake();
	
	        if (this.connection.getUseReadAheadInput()) {
	        	this.mysqlInput = new ReadAheadInputStream(this.mysqlConnection.getInputStream(), 16384,
	        			this.connection.getTraceProtocol(),
	        			this.connection.getLog());
	        } else if (this.connection.useUnbufferedInput()) {
	        	this.mysqlInput = this.mysqlConnection.getInputStream();
	        } else {
	        	this.mysqlInput = new BufferedInputStream(this.mysqlConnection.getInputStream(),
	        			16384);
	        }
	
	        this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection.getOutputStream(),
	        		16384);
	
	
	        this.isInteractiveClient = this.connection.getInteractiveClient();
	        this.profileSql = this.connection.getProfileSql();
	        this.autoGenerateTestcaseScript = this.connection.getAutoGenerateTestcaseScript();
	
	        this.needToGrabQueryFromPacket = (this.profileSql ||
	        		this.logSlowQueries ||
	        		this.autoGenerateTestcaseScript);
	
	        if (this.connection.getUseNanosForElapsedTime()
					&& Util.nanoTimeAvailable()) {
				this.useNanosForElapsedTime = true;
	
				this.queryTimingUnits = Messages.getString("Nanoseconds");
			} else {
				this.queryTimingUnits = Messages.getString("Milliseconds");
			}
	
			if (this.connection.getLogSlowQueries()) {
				calculateSlowQueryThreshold();
			}
        } catch (IOException ioEx) {
        	throw SQLError.createCommunicationsException(this.connection, 0, 0, ioEx, getExceptionInterceptor());
        }
    }

    /**
	 * Does the server send back extra column info?
	 * 
	 * @return true if so
	 */
    public boolean hasLongColumnInfo() {
        return this.hasLongColumnInfo;
    }

    protected boolean isDataAvailable() throws SQLException {
        try {
            return this.mysqlInput.available() > 0;
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @return Returns the lastPacketSentTimeMs.
     */
    protected long getLastPacketSentTimeMs() {
        return this.lastPacketSentTimeMs;
    }
    
    protected long getLastPacketReceivedTimeMs() {
        return this.lastPacketReceivedTimeMs;
    }

    /**
     * Build a result set. Delegates to buildResultSetWithRows() to build a
     * JDBC-version-specific ResultSet, given rows as byte data, and field
     * information.
     *
     * @param callingStatement DOCUMENT ME!
     * @param columnCount the number of columns in the result set
     * @param maxRows the maximum number of rows to read (-1 means all rows)
     * @param resultSetType (TYPE_FORWARD_ONLY, TYPE_SCROLL_????)
     * @param resultSetConcurrency the type of result set (CONCUR_UPDATABLE or
     *        READ_ONLY)
     * @param streamResults should the result set be read all at once, or
     *        streamed?
     * @param catalog the database name in use when the result set was created
     * @param isBinaryEncoded is this result set in native encoding?
     * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
     *
     * @return a result set
     *
     * @throws SQLException if a database access error occurs
     */
    protected ResultSetImpl getResultSet(StatementImpl callingStatement,
        long columnCount, int maxRows, int resultSetType,
        int resultSetConcurrency, boolean streamResults, String catalog,
        boolean isBinaryEncoded, Field[] metadataFromCache)
        throws SQLException {
        Buffer packet; // The packet from the server
        Field[] fields = null;

        // Read in the column information

        if (metadataFromCache == null /* we want the metadata from the server */) {
            fields = new Field[(int) columnCount];

            for (int i = 0; i < columnCount; i++) {
            	Buffer fieldPacket = null;

                fieldPacket = readPacket();
                fields[i] = unpackField(fieldPacket, false);
            }
        } else {
        	for (int i = 0; i < columnCount; i++) {
        		skipPacket();
        	}
        }

        packet = reuseAndReadPacket(this.reusablePacket);
        
        readServerStatusForResultSets(packet);

		//
		// Handle cursor-based fetch first
		//

		if (this.connection.versionMeetsMinimum(5, 0, 2)
				&& this.connection.getUseCursorFetch()
				&& isBinaryEncoded
				&& callingStatement != null
				&& callingStatement.getFetchSize() != 0
				&& callingStatement.getResultSetType() == ResultSet.TYPE_FORWARD_ONLY) {
			ServerPreparedStatement prepStmt = (com.mysql.jdbc.ServerPreparedStatement) callingStatement;

			boolean usingCursor = true;

			//
			// Server versions 5.0.5 or newer will only open
			// a cursor and set this flag if they can, otherwise
			// they punt and go back to mysql_store_results() behavior
			//

			if (this.connection.versionMeetsMinimum(5, 0, 5)) {
				usingCursor = (this.serverStatus &
						SERVER_STATUS_CURSOR_EXISTS) != 0;
			}

			if (usingCursor) {
				RowData rows = new RowDataCursor(
					this,
					prepStmt,
					fields);

				ResultSetImpl rs = buildResultSetWithRows(
					callingStatement,
					catalog,
					fields,
					rows, resultSetType, resultSetConcurrency, isBinaryEncoded);

				if (usingCursor) {
		        	rs.setFetchSize(callingStatement.getFetchSize());
		        }

				return rs;
			}
		}

        RowData rowData = null;

        if (!streamResults) {
            rowData = readSingleRowSet(columnCount, maxRows,
                    resultSetConcurrency, isBinaryEncoded,
                    (metadataFromCache == null) ? fields : metadataFromCache);
        } else {
            rowData = new RowDataDynamic(this, (int) columnCount,
            		(metadataFromCache == null) ? fields : metadataFromCache,
                    isBinaryEncoded);
            this.streamingData = rowData;
        }

        ResultSetImpl rs = buildResultSetWithRows(callingStatement, catalog,
        		(metadataFromCache == null) ? fields : metadataFromCache,
            rowData, resultSetType, resultSetConcurrency, isBinaryEncoded);



        return rs;
    }

    // We do this to break the chain between MysqlIO and Connection, so that
    // we can have PhantomReferences on connections that let the driver
    // clean up the socket connection without having to use finalize() somewhere
    // (which although more straightforward, is horribly inefficent).
    protected NetworkResources getNetworkResources() {
    	return new NetworkResources(this.mysqlConnection, this.mysqlInput, this.mysqlOutput);
    }
    
    /**
     * Forcibly closes the underlying socket to MySQL.
     */
    protected final void forceClose() {	
        try {
        	getNetworkResources().forceClose();
        } finally {
        	this.mysqlConnection = null;
        	this.mysqlInput = null;
        	this.mysqlOutput = null;
        }
    }

    /**
     * Reads and discards a single MySQL packet from the input stream.
     *
     * @throws SQLException if the network fails while skipping the
     * packet.
     */
    protected final void skipPacket() throws SQLException {
		try {

			int lengthRead = readFully(this.mysqlInput, this.packetHeaderBuf,
					0, 4);

			if (lengthRead < 4) {
				forceClose();
				throw new IOException(Messages.getString("MysqlIO.1")); //$NON-NLS-1$
			}

			int packetLength = (this.packetHeaderBuf[0] & 0xff)
					+ ((this.packetHeaderBuf[1] & 0xff) << 8)
					+ ((this.packetHeaderBuf[2] & 0xff) << 16);

			if (this.traceProtocol) {
				StringBuffer traceMessageBuf = new StringBuffer();

				traceMessageBuf.append(Messages.getString("MysqlIO.2")); //$NON-NLS-1$
				traceMessageBuf.append(packetLength);
				traceMessageBuf.append(Messages.getString("MysqlIO.3")); //$NON-NLS-1$
				traceMessageBuf.append(StringUtils.dumpAsHex(
						this.packetHeaderBuf, 4));

				this.connection.getLog().logTrace(traceMessageBuf.toString());
			}

			byte multiPacketSeq = this.packetHeaderBuf[3];

			if (!this.packetSequenceReset) {
				if (this.enablePacketDebug && this.checkPacketSequence) {
					checkPacketSequencing(multiPacketSeq);
				}
			} else {
				this.packetSequenceReset = false;
			}

			this.readPacketSequence = multiPacketSeq;

			skipFully(this.mysqlInput, packetLength);
		} catch (IOException ioEx) {
			throw SQLError.createCommunicationsException(this.connection,
					this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
		} catch (OutOfMemoryError oom) {
			try {
				this.connection.realClose(false, false, true, oom);
        	} catch (Exception ex) {
    		}
			throw oom;
		}
	}

    /**
     * Read one packet from the MySQL server
     *
     * @return the packet from the server.
     *
     * @throws SQLException DOCUMENT ME!
     * @throws CommunicationsException DOCUMENT ME!
     */
    protected final Buffer readPacket() throws SQLException {
        try {

            int lengthRead = readFully(this.mysqlInput,
                    this.packetHeaderBuf, 0, 4);

            if (lengthRead < 4) {
                forceClose();
                throw new IOException(Messages.getString("MysqlIO.1")); //$NON-NLS-1$
            }

            int packetLength = (this.packetHeaderBuf[0] & 0xff) +
                ((this.packetHeaderBuf[1] & 0xff) << 8) +
                ((this.packetHeaderBuf[2] & 0xff) << 16);
            
            if (packetLength > this.maxAllowedPacket) {
                throw new PacketTooBigException(packetLength, this.maxAllowedPacket);
            }

            if (this.traceProtocol) {
                StringBuffer traceMessageBuf = new StringBuffer();

                traceMessageBuf.append(Messages.getString("MysqlIO.2")); //$NON-NLS-1$
                traceMessageBuf.append(packetLength);
                traceMessageBuf.append(Messages.getString("MysqlIO.3")); //$NON-NLS-1$
                traceMessageBuf.append(StringUtils.dumpAsHex(
                        this.packetHeaderBuf, 4));

                this.connection.getLog().logTrace(traceMessageBuf.toString());
            }

            byte multiPacketSeq = this.packetHeaderBuf[3];

            if (!this.packetSequenceReset) {
                if (this.enablePacketDebug && this.checkPacketSequence) {
                    checkPacketSequencing(multiPacketSeq);
                }
            } else {
                this.packetSequenceReset = false;
            }

            this.readPacketSequence = multiPacketSeq;

            // Read data
            byte[] buffer = new byte[packetLength + 1];
            int numBytesRead = readFully(this.mysqlInput, buffer, 0,
                    packetLength);

            if (numBytesRead != packetLength) {
                throw new IOException("Short read, expected " +
                    packetLength + " bytes, only read " + numBytesRead);
            }

            buffer[packetLength] = 0;

            Buffer packet = new Buffer(buffer);
            packet.setBufLength(packetLength + 1);

            if (this.traceProtocol) {
                StringBuffer traceMessageBuf = new StringBuffer();

                traceMessageBuf.append(Messages.getString("MysqlIO.4")); //$NON-NLS-1$
                traceMessageBuf.append(getPacketDumpToLog(packet,
                        packetLength));

                this.connection.getLog().logTrace(traceMessageBuf.toString());
            }

            if (this.enablePacketDebug) {
                enqueuePacketForDebugging(false, false, 0,
                    this.packetHeaderBuf, packet);
            }

            if (this.connection.getMaintainTimeStats()) {
				this.lastPacketReceivedTimeMs = System.currentTimeMillis();
			}
            
            return packet;
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        } catch (OutOfMemoryError oom) {
        	try {
    			this.connection.realClose(false, false, true, oom);
        	} catch (Exception ex) {
    		}
			throw oom;
        }
    }

    /**
     * Unpacks the Field information from the given packet. Understands pre 4.1
     * and post 4.1 server version field packet structures.
     *
     * @param packet the packet containing the field information
     * @param extractDefaultValues should default values be extracted?
     *
     * @return the unpacked field
     *
     * @throws SQLException DOCUMENT ME!
     */
    protected final Field unpackField(Buffer packet,
        boolean extractDefaultValues) throws SQLException {
        if (this.use41Extensions) {
            // we only store the position of the string and
            // materialize only if needed...
            if (this.has41NewNewProt) {
                // Not used yet, 5.0?
                int catalogNameStart = packet.getPosition() + 1;
                int catalogNameLength = packet.fastSkipLenString();
                catalogNameStart = adjustStartForFieldLength(catalogNameStart, catalogNameLength);
            }

            int databaseNameStart = packet.getPosition() + 1;
            int databaseNameLength = packet.fastSkipLenString();
            databaseNameStart = adjustStartForFieldLength(databaseNameStart, databaseNameLength);

            int tableNameStart = packet.getPosition() + 1;
            int tableNameLength = packet.fastSkipLenString();
            tableNameStart = adjustStartForFieldLength(tableNameStart, tableNameLength);

            // orgTableName is never used so skip
            int originalTableNameStart = packet.getPosition() + 1;
            int originalTableNameLength = packet.fastSkipLenString();
            originalTableNameStart = adjustStartForFieldLength(originalTableNameStart, originalTableNameLength);

            // we only store the position again...
            int nameStart = packet.getPosition() + 1;
            int nameLength = packet.fastSkipLenString();

            nameStart = adjustStartForFieldLength(nameStart, nameLength);

            // orgColName is not required so skip...
            int originalColumnNameStart = packet.getPosition() + 1;
            int originalColumnNameLength = packet.fastSkipLenString();
            originalColumnNameStart = adjustStartForFieldLength(originalColumnNameStart, originalColumnNameLength);

            packet.readByte();

            short charSetNumber = (short) packet.readInt();

            long colLength = 0;

            if (this.has41NewNewProt) {
                colLength = packet.readLong();
            } else {
                colLength = packet.readLongInt();
            }

            int colType = packet.readByte() & 0xff;

            short colFlag = 0;

            if (this.hasLongColumnInfo) {
                colFlag = (short) packet.readInt();
            } else {
                colFlag = (short) (packet.readByte() & 0xff);
            }

            int colDecimals = packet.readByte() & 0xff;

            int defaultValueStart = -1;
            int defaultValueLength = -1;

            if (extractDefaultValues) {
                defaultValueStart = packet.getPosition() + 1;
                defaultValueLength = packet.fastSkipLenString();
            }

            Field field = new Field(this.connection, packet.getByteBuffer(),
                    databaseNameStart, databaseNameLength, tableNameStart,
                    tableNameLength, originalTableNameStart,
                    originalTableNameLength, nameStart, nameLength,
                    originalColumnNameStart, originalColumnNameLength,
                    colLength, colType, colFlag, colDecimals,
                    defaultValueStart, defaultValueLength, charSetNumber);

            return field;
        }

        int tableNameStart = packet.getPosition() + 1;
        int tableNameLength = packet.fastSkipLenString();
        tableNameStart = adjustStartForFieldLength(tableNameStart, tableNameLength);

        int nameStart = packet.getPosition() + 1;
        int nameLength = packet.fastSkipLenString();
        nameStart = adjustStartForFieldLength(nameStart, nameLength);

        int colLength = packet.readnBytes();
        int colType = packet.readnBytes();
        packet.readByte(); // We know it's currently 2

        short colFlag = 0;

        if (this.hasLongColumnInfo) {
            colFlag = (short) (packet.readInt());
        } else {
            colFlag = (short) (packet.readByte() & 0xff);
        }

        int colDecimals = (packet.readByte() & 0xff);

        if (this.colDecimalNeedsBump) {
            colDecimals++;
        }

        Field field = new Field(this.connection, packet.getByteBuffer(),
                nameStart, nameLength, tableNameStart, tableNameLength,
                colLength, colType, colFlag, colDecimals);

        return field;
    }

    private int adjustStartForFieldLength(int nameStart, int nameLength) {
    	if (nameLength < 251) {
    		return nameStart;
    	}

		if (nameLength >= 251 && nameLength < 65536) {
			return nameStart + 2;
		}

		if (nameLength >= 65536 && nameLength < 16777216) {
			return nameStart + 3;
		}

		return nameStart + 8;
	}

    protected boolean isSetNeededForAutoCommitMode(boolean autoCommitFlag) {
        if (this.use41Extensions && this.connection.getElideSetAutoCommits()) {
            boolean autoCommitModeOnServer = ((this.serverStatus &
                SERVER_STATUS_AUTOCOMMIT) != 0);

            if (!autoCommitFlag && versionMeetsMinimum(5, 0, 0)) {
                // Just to be safe, check if a transaction is in progress on the server....
                // if so, then we must be in autoCommit == false
                // therefore return the opposite of transaction status
                boolean inTransactionOnServer = ((this.serverStatus &
                    SERVER_STATUS_IN_TRANS) != 0);

                return !inTransactionOnServer;
            }

            return autoCommitModeOnServer != autoCommitFlag;
        }

        return true;
    }

    protected boolean inTransactionOnServer() {
    	return (this.serverStatus & SERVER_STATUS_IN_TRANS) != 0;
    }

    /**
     * Re-authenticates as the given user and password
     *
     * @param userName DOCUMENT ME!
     * @param password DOCUMENT ME!
     * @param database DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     */
    protected void changeUser(String userName, String password, String database)
        throws SQLException {
        this.packetSequence = -1;
        this.compressedPacketSequence = -1;

        int passwordLength = 16;
        int userLength = (userName != null) ? userName.length() : 0;
        int databaseLength = (database != null) ? database.length() : 0;

        int packLength = ((userLength + passwordLength + databaseLength) * 3) + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;

    	if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {

            proceedHandshakeWithPluggableAuthentication(userName, password, database, null);
            
        } else if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
            Buffer changeUserPacket = new Buffer(packLength + 1);
            changeUserPacket.writeByte((byte) MysqlDefs.COM_CHANGE_USER);

            if (versionMeetsMinimum(4, 1, 1)) {
                secureAuth411(changeUserPacket, packLength, userName, password,
                    database, false);
            } else {
                secureAuth(changeUserPacket, packLength, userName, password,
                    database, false);
            }
        } else {
            // Passwords can be 16 chars long
            Buffer packet = new Buffer(packLength);
            packet.writeByte((byte) MysqlDefs.COM_CHANGE_USER);

            // User/Password data
            packet.writeString(userName);

            if (this.protocolVersion > 9) {
                packet.writeString(Util.newCrypt(password, this.seed));
            } else {
                packet.writeString(Util.oldCrypt(password, this.seed));
            }

			boolean localUseConnectWithDb = this.useConnectWithDb &&
				(database != null && database.length() > 0);

            if (localUseConnectWithDb) {
                packet.writeString(database);
            } else {
            	//Not needed, old server does not require \0 
            	//packet.writeString("");
            }

            send(packet, packet.getPosition());
            checkErrorPacket();

			if (!localUseConnectWithDb) {
				changeDatabaseTo(database);
			}
        }
    }

    /**
     * Checks for errors in the reply packet, and if none, returns the reply
     * packet, ready for reading
     *
     * @return a packet ready for reading.
     *
     * @throws SQLException is the packet is an error packet
     */
    protected Buffer checkErrorPacket() throws SQLException {
        return checkErrorPacket(-1);
    }

    /**
     * Determines if the database charset is the same as the platform charset
     */
    protected void checkForCharsetMismatch() {
        if (this.connection.getUseUnicode() &&
                (this.connection.getEncoding() != null)) {
            String encodingToCheck = jvmPlatformCharset;

            if (encodingToCheck == null) {
                encodingToCheck = System.getProperty("file.encoding"); //$NON-NLS-1$
            }

            if (encodingToCheck == null) {
                this.platformDbCharsetMatches = false;
            } else {
                this.platformDbCharsetMatches = encodingToCheck.equals(this.connection.getEncoding());
            }
        }
    }

    protected void clearInputStream() throws SQLException {

        try {
            int len = this.mysqlInput.available();

            while (len > 0) {
                this.mysqlInput.skip(len);
                len = this.mysqlInput.available();
            }
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        }
    }

    protected void resetReadPacketSequence() {
        this.readPacketSequence = 0;
    }

    protected void dumpPacketRingBuffer() throws SQLException {
        if ((this.packetDebugRingBuffer != null) &&
                this.connection.getEnablePacketDebug()) {
            StringBuffer dumpBuffer = new StringBuffer();

            dumpBuffer.append("Last " + this.packetDebugRingBuffer.size() +
                " packets received from server, from oldest->newest:\n");
            dumpBuffer.append("\n");

            for (Iterator<StringBuffer> ringBufIter = this.packetDebugRingBuffer.iterator();
                    ringBufIter.hasNext();) {
                dumpBuffer.append(ringBufIter.next());
                dumpBuffer.append("\n");
            }

            this.connection.getLog().logTrace(dumpBuffer.toString());
        }
    }

    /**
     * Runs an 'EXPLAIN' on the given query and dumps the results to  the log
     *
     * @param querySQL DOCUMENT ME!
     * @param truncatedQuery DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     */
    protected void explainSlowQuery(byte[] querySQL, String truncatedQuery)
        throws SQLException {
		if (StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery, EXPLAINABLE_STATEMENT)
				|| (versionMeetsMinimum(5, 6, 3) && StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery,
						EXPLAINABLE_STATEMENT_EXTENSION) != -1)) {

            PreparedStatement stmt = null;
            java.sql.ResultSet rs = null;

            try {
                stmt = (PreparedStatement) this.connection.clientPrepareStatement("EXPLAIN ?"); //$NON-NLS-1$
                stmt.setBytesNoEscapeNoQuotes(1, querySQL);
                rs = stmt.executeQuery();

                StringBuffer explainResults = new StringBuffer(Messages.getString(
                            "MysqlIO.8") + truncatedQuery //$NON-NLS-1$
                         +Messages.getString("MysqlIO.9")); //$NON-NLS-1$

                ResultSetUtil.appendResultSetSlashGStyle(explainResults, rs);

                this.connection.getLog().logWarn(explainResults.toString());
            } catch (SQLException sqlEx) {
            } finally {
                if (rs != null) {
                    rs.close();
                }

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

    static int getMaxBuf() {
        return maxBufferSize;
    }

    /**
     * Get the major version of the MySQL server we are talking to.
     *
     * @return DOCUMENT ME!
     */
    final int getServerMajorVersion() {
        return this.serverMajorVersion;
    }

    /**
     * Get the minor version of the MySQL server we are talking to.
     *
     * @return DOCUMENT ME!
     */
    final int getServerMinorVersion() {
        return this.serverMinorVersion;
    }

    /**
     * Get the sub-minor version of the MySQL server we are talking to.
     *
     * @return DOCUMENT ME!
     */
    final int getServerSubMinorVersion() {
        return this.serverSubMinorVersion;
    }

    /**
     * Get the version string of the server we are talking to
     *
     * @return DOCUMENT ME!
     */
    String getServerVersion() {
        return this.serverVersion;
    }

    /**
     * Initialize communications with the MySQL server. Handles logging on, and
     * handling initial connection errors.
     *
     * @param user DOCUMENT ME!
     * @param password DOCUMENT ME!
     * @param database DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     * @throws CommunicationsException DOCUMENT ME!
     */
    void doHandshake(String user, String password, String database)
        throws SQLException {
        // Read the first packet
        this.checkPacketSequence = false;
        this.readPacketSequence = 0;

        Buffer buf = readPacket();

        // Get the protocol version
        this.protocolVersion = buf.readByte();

        if (this.protocolVersion == -1) {
            try {
                this.mysqlConnection.close();
            } catch (Exception e) {
                // ignore
            }

            int errno = 2000;

            errno = buf.readInt();

            String serverErrorMessage = buf.readString("ASCII", getExceptionInterceptor());

            StringBuffer errorBuf = new StringBuffer(Messages.getString(
                        "MysqlIO.10")); //$NON-NLS-1$
            errorBuf.append(serverErrorMessage);
            errorBuf.append("\""); //$NON-NLS-1$

            String xOpen = SQLError.mysqlToSqlState(errno,
                    this.connection.getUseSqlStateCodes());

            throw SQLError.createSQLException(SQLError.get(xOpen) + ", " //$NON-NLS-1$
                 +errorBuf.toString(), xOpen, errno, getExceptionInterceptor());
        }

        this.serverVersion = buf.readString("ASCII", getExceptionInterceptor());

        // Parse the server version into major/minor/subminor
        int point = this.serverVersion.indexOf('.'); //$NON-NLS-1$

        if (point != -1) {
            try {
                int n = Integer.parseInt(this.serverVersion.substring(0, point));
                this.serverMajorVersion = n;
            } catch (NumberFormatException NFE1) {
                // ignore
            }

            String remaining = this.serverVersion.substring(point + 1,
                    this.serverVersion.length());
            point = remaining.indexOf('.'); //$NON-NLS-1$

            if (point != -1) {
                try {
                    int n = Integer.parseInt(remaining.substring(0, point));
                    this.serverMinorVersion = n;
                } catch (NumberFormatException nfe) {
                    // ignore
                }

                remaining = remaining.substring(point + 1, remaining.length());

                int pos = 0;

                while (pos < remaining.length()) {
                    if ((remaining.charAt(pos) < '0') ||
                            (remaining.charAt(pos) > '9')) {
                        break;
                    }

                    pos++;
                }

                try {
                    int n = Integer.parseInt(remaining.substring(0, pos));
                    this.serverSubMinorVersion = n;
                } catch (NumberFormatException nfe) {
                    // ignore
                }
            }
        }

        if (versionMeetsMinimum(4, 0, 8)) {
            this.maxThreeBytes = (256 * 256 * 256) - 1;
            this.useNewLargePackets = true;
        } else {
            this.maxThreeBytes = 255 * 255 * 255;
            this.useNewLargePackets = false;
        }

        this.colDecimalNeedsBump = versionMeetsMinimum(3, 23, 0);
        this.colDecimalNeedsBump = !versionMeetsMinimum(3, 23, 15); // guess? Not noted in changelog
        this.useNewUpdateCounts = versionMeetsMinimum(3, 22, 5);
        	  
        // read connection id
        threadId = buf.readLong();
        
        if (this.protocolVersion > 9) {
            // read auth-plugin-data-part-1 (string[8])
            this.seed = buf.readString("ASCII", getExceptionInterceptor(), 8);
            // read filler ([00])
            buf.readByte();
        } else {
        	// read scramble (string[NUL])
            this.seed = buf.readString("ASCII", getExceptionInterceptor());
        }

        this.serverCapabilities = 0;

        // read capability flags (lower 2 bytes)
        if (buf.getPosition() < buf.getBufLength()) {
            this.serverCapabilities = buf.readInt();
        }

        if ((versionMeetsMinimum(4, 1, 1) || ((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_PROTOCOL_41) != 0))) {

            /* New protocol with 16 bytes to describe server characteristics */
            // read character set (1 byte)
            this.serverCharsetIndex = buf.readByte() & 0xff;
            // read status flags (2 bytes)
            this.serverStatus = buf.readInt();
            checkTransactionState(0);
            
            // read capability flags (upper 2 bytes)
           	this.serverCapabilities |= buf.readInt() << 16;
           	
           	if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {
               	// read length of auth-plugin-data (1 byte)
               	this.authPluginDataLength = buf.readByte() & 0xff;
           	} else {
           		// read filler ([00])
           		buf.readByte();
           	}
            // next 10 bytes are reserved (all [00])
           	buf.setPosition(buf.getPosition() + 10);

           	if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
           		String seedPart2;
           		StringBuffer newSeed;
            	// read string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
           		if (this.authPluginDataLength > 0) {
// TODO: disabled the following check for further clarification
//         			if (this.authPluginDataLength < 21) {
//                      forceClose();
//                      throw SQLError.createSQLException(Messages.getString("MysqlIO.103"), //$NON-NLS-1$
//                          SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
//         			}
                    seedPart2 = buf.readString("ASCII", getExceptionInterceptor(), this.authPluginDataLength - 8);
                    newSeed = new StringBuffer(this.authPluginDataLength);
           		} else {
           			seedPart2 = buf.readString("ASCII", getExceptionInterceptor());
                    newSeed = new StringBuffer(20);
           		}
                newSeed.append(this.seed);
                newSeed.append(seedPart2);
                this.seed = newSeed.toString();
           	}
        }

        if (((this.serverCapabilities & CLIENT_COMPRESS) != 0) &&
                this.connection.getUseCompression()) {
            this.clientParam |= CLIENT_COMPRESS;
        }

        	this.useConnectWithDb = (database != null) &&
			(database.length() > 0) &&
			!this.connection.getCreateDatabaseIfNotExist();

        if (this.useConnectWithDb) {
            this.clientParam |= CLIENT_CONNECT_WITH_DB;
        }

        if (((this.serverCapabilities & CLIENT_SSL) == 0) &&
                this.connection.getUseSSL()) {
            if (this.connection.getRequireSSL()) {
                this.connection.close();
                forceClose();
                throw SQLError.createSQLException(Messages.getString("MysqlIO.15"), //$NON-NLS-1$
                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
            }

            this.connection.setUseSSL(false);
        }

        if ((this.serverCapabilities & CLIENT_LONG_FLAG) != 0) {
            // We understand other column flags, as well
            this.clientParam |= CLIENT_LONG_FLAG;
            this.hasLongColumnInfo = true;
        }

        // return FOUND rows
        if (!this.connection.getUseAffectedRows()) {
        	this.clientParam |= CLIENT_FOUND_ROWS;
        }

        if (this.connection.getAllowLoadLocalInfile()) {
            this.clientParam |= CLIENT_LOCAL_FILES;
        }

        if (this.isInteractiveClient) {
            this.clientParam |= CLIENT_INTERACTIVE;
        }

        //
        // switch to pluggable authentication if available
        //
    	if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {
            proceedHandshakeWithPluggableAuthentication(user, password, database, buf);
            return;
        }
        
        // Authenticate
        if (this.protocolVersion > 9) {
            this.clientParam |= CLIENT_LONG_PASSWORD; // for long passwords
        } else {
            this.clientParam &= ~CLIENT_LONG_PASSWORD;
        }

        //
        // 4.1 has some differences in the protocol
        //
        if ((versionMeetsMinimum(4, 1, 0) || ((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_RESERVED) != 0))) {
            if ((versionMeetsMinimum(4, 1, 1) || ((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_PROTOCOL_41) != 0))) {
                this.clientParam |= CLIENT_PROTOCOL_41;
                this.has41NewNewProt = true;

                // Need this to get server status values
                this.clientParam |= CLIENT_TRANSACTIONS;

                // We always allow multiple result sets
                this.clientParam |= CLIENT_MULTI_RESULTS;

                // We allow the user to configure whether
                // or not they want to support multiple queries
                // (by default, this is disabled).
                if (this.connection.getAllowMultiQueries()) {
                    this.clientParam |= CLIENT_MULTI_STATEMENTS;
                }
            } else {
                this.clientParam |= CLIENT_RESERVED;
                this.has41NewNewProt = false;
            }

            this.use41Extensions = true;
        }

        int passwordLength = 16;
        int userLength = (user != null) ? user.length() : 0;
        int databaseLength = (database != null) ? database.length() : 0;

        int packLength = ((userLength + passwordLength + databaseLength) * 3) + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;

        Buffer packet = null;

        if (!this.connection.getUseSSL()) {
            if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
                this.clientParam |= CLIENT_SECURE_CONNECTION;

                if ((versionMeetsMinimum(4, 1, 1) || ((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_PROTOCOL_41) != 0))) {
                    secureAuth411(null, packLength, user, password, database,
                        true);
                } else {
                    secureAuth(null, packLength, user, password, database, true);
                }
            } else {
                // Passwords can be 16 chars long
                packet = new Buffer(packLength);

                if ((this.clientParam & CLIENT_RESERVED) != 0) {
                    if ((versionMeetsMinimum(4, 1, 1) || ((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_PROTOCOL_41) != 0))) {
                        packet.writeLong(this.clientParam);
                        packet.writeLong(this.maxThreeBytes);

                        // charset, JDBC will connect as 'latin1',
                        // and use 'SET NAMES' to change to the desired
                        // charset after the connection is established.
                        packet.writeByte((byte) 8);

                        // Set of bytes reserved for future use.
                        packet.writeBytesNoNull(new byte[23]);
                    } else {
                        packet.writeLong(this.clientParam);
                        packet.writeLong(this.maxThreeBytes);
                    }
                } else {
                    packet.writeInt((int) this.clientParam);
                    packet.writeLongInt(this.maxThreeBytes);
                }

                // User/Password data
                packet.writeString(user, CODE_PAGE_1252, this.connection);

                if (this.protocolVersion > 9) {
                    packet.writeString(Util.newCrypt(password, this.seed), CODE_PAGE_1252, this.connection);
                } else {
                    packet.writeString(Util.oldCrypt(password, this.seed), CODE_PAGE_1252, this.connection);
                }

                if (this.useConnectWithDb) {
                    packet.writeString(database, CODE_PAGE_1252, this.connection);
                }

                send(packet, packet.getPosition());
            }
        } else {
            negotiateSSLConnection(user, password, database, packLength);

            if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
                if (versionMeetsMinimum(4, 1, 1)) {
                    secureAuth411(null, packLength, user, password, database, true);
                } else {
                    secureAuth411(null, packLength, user, password, database, true);
                }
            } else {
            	
            	packet = new Buffer(packLength);
            	
                if (this.use41Extensions) {
                    packet.writeLong(this.clientParam);
                    packet.writeLong(this.maxThreeBytes);
                } else {
                    packet.writeInt((int) this.clientParam);
                    packet.writeLongInt(this.maxThreeBytes);
                }

                // User/Password data
                packet.writeString(user);

                if (this.protocolVersion > 9) {
                    packet.writeString(Util.newCrypt(password, this.seed));
                } else {
                    packet.writeString(Util.oldCrypt(password, this.seed));
                }

                if (((this.serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0) &&
                        (database != null) && (database.length() > 0)) {
                    packet.writeString(database);
                }

                send(packet, packet.getPosition());
            }
        }

        // Check for errors, not for 4.1.1 or newer,
        // as the new auth protocol doesn't work that way
        // (see secureAuth411() for more details...)
        //if (!versionMeetsMinimum(4, 1, 1)) {
        if (!(versionMeetsMinimum(4, 1, 1) || !((this.protocolVersion > 9) && (this.serverCapabilities & CLIENT_PROTOCOL_41) != 0))) {
            checkErrorPacket();
        }

        //
        // Can't enable compression until after handshake
        //
        if (((this.serverCapabilities & CLIENT_COMPRESS) != 0) &&
                this.connection.getUseCompression()) {
            // The following matches with ZLIB's
            // compress()
            this.deflater = new Deflater();
            this.useCompression = true;
            this.mysqlInput = new CompressedInputStream(this.connection,
                    this.mysqlInput);
        }

        if (!this.useConnectWithDb) {
            changeDatabaseTo(database);
        }
        
        try {
        	this.mysqlConnection = this.socketFactory.afterHandshake();
        } catch (IOException ioEx) {
        	throw SQLError.createCommunicationsException(this.connection, this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        }
    }


	/**
	 * Contains instances of authentication plugins which implements
	 * {@link AuthenticationPlugin} interface. Key values are mysql
	 * protocol plugin names, for example "mysql_native_password" and
	 * "mysql_old_password" for built-in plugins.
	 */
    private Map<String, AuthenticationPlugin> authenticationPlugins = null;
	/**
	 * Contains names of classes or mechanisms ("mysql_native_password"
	 * for example) of authentication plugins which must be disabled.
	 */
    private List<String> disabledAuthenticationPlugins = null;
	/**
	 * Name of class for default authentication plugin
	 */
    private String defaultAuthenticationPlugin = null;
	/**
	 * Protocol name of default authentication plugin
	 */
    private String defaultAuthenticationPluginProtocolName = null;
	
	/**
	 * Fill the {@link MysqlIO#authenticationPlugins} map.
	 * First this method fill the map with instances of {@link MysqlOldPasswordPlugin}, {@link MysqlNativePasswordPlugin},
	 * {@link MysqlClearPasswordPlugin} and {@link Sha256PasswordPlugin}.
	 * Then it gets instances of plugins listed in "authenticationPlugins" connection property by
	 * {@link Util#loadExtensions(Connection, Properties, String, String, ExceptionInterceptor)} call and adds them to the map too.
	 * 
	 * The key for the map entry is getted by {@link AuthenticationPlugin#getProtocolPluginName()}.
	 * Thus it is possible to replace built-in plugin with custom one, to do it custom plugin should return value
	 * "mysql_native_password", "mysql_old_password", "mysql_clear_password" or "sha256_password" from it's own getProtocolPluginName() method.
	 * 
	 * All plugin instances in the map are initialized by {@link Extension#init(Connection, Properties)} call
	 * with this.connection and this.connection.getProperties() values.
	 *  
	 * @throws SQLException
	 */
	private void loadAuthenticationPlugins() throws SQLException {
		
		// default plugin
		this.defaultAuthenticationPlugin = this.connection.getDefaultAuthenticationPlugin();
		if (this.defaultAuthenticationPlugin == null || "".equals(defaultAuthenticationPlugin.trim())) {
			throw SQLError.createSQLException(
				Messages.getString("Connection.BadDefaultAuthenticationPlugin",
				new Object[] { this.defaultAuthenticationPlugin }),
				getExceptionInterceptor());
		}

		// disabled plugins
		String disabledPlugins = this.connection.getDisabledAuthenticationPlugins();
		if (disabledPlugins != null && !"".equals(disabledPlugins)) {
			this.disabledAuthenticationPlugins = new ArrayList<String>();
			List<String> pluginsToDisable = StringUtils.split(disabledPlugins, ",", true);
			Iterator<String> iter = pluginsToDisable.iterator();
			while (iter.hasNext()) {
				this.disabledAuthenticationPlugins.add(iter.next());
			}
		}

		this.authenticationPlugins = new HashMap<String, AuthenticationPlugin>();
		
		// embedded plugins
		AuthenticationPlugin plugin = new MysqlOldPasswordPlugin();
		plugin.init(this.connection, this.connection.getProperties());
		boolean defaultIsFound = addAuthenticationPlugin(plugin);

		plugin = new MysqlNativePasswordPlugin();
		plugin.init(this.connection, this.connection.getProperties());
		if (addAuthenticationPlugin(plugin)) defaultIsFound = true;

		plugin = new MysqlClearPasswordPlugin();
		plugin.init(this.connection, this.connection.getProperties());
		if (addAuthenticationPlugin(plugin)) defaultIsFound = true;

		plugin = new Sha256PasswordPlugin();
		plugin.init(this.connection, this.connection.getProperties());
		if (addAuthenticationPlugin(plugin)) defaultIsFound = true;

		// plugins from authenticationPluginClasses connection parameter
		String authenticationPluginClasses = this.connection.getAuthenticationPlugins();
		if (authenticationPluginClasses != null && !"".equals(authenticationPluginClasses)) {
			
			List<Extension> plugins = Util.loadExtensions(
				this.connection, this.connection.getProperties(), authenticationPluginClasses,
				"Connection.BadAuthenticationPlugin",  getExceptionInterceptor());

			for (Extension object : plugins) {
				plugin = (AuthenticationPlugin) object;
				if (addAuthenticationPlugin(plugin)) defaultIsFound = true;
			}
		}

		// check if default plugin is listed
		if (!defaultIsFound) {
			throw SQLError.createSQLException(
				Messages.getString("Connection.DefaultAuthenticationPluginIsNotListed",
				new Object[] { this.defaultAuthenticationPlugin }),
				getExceptionInterceptor());
		}

	}

	/**
	 * Add plugin to {@link MysqlIO#authenticationPlugins} if it is not disabled by
	 * "disabledAuthenticationPlugins" property, check is it a default plugin.
	 * @param plugin Instance of AuthenticationPlugin
	 * @return True if plugin is default, false if plugin is not default.
	 * @throws SQLException if plugin is default but disabled.
	 */
	private boolean addAuthenticationPlugin(AuthenticationPlugin plugin) throws SQLException {
		boolean isDefault = false;
		String pluginClassName = plugin.getClass().getName();
		String pluginProtocolName = plugin.getProtocolPluginName();
		boolean disabledByClassName =
			this.disabledAuthenticationPlugins != null &&
			this.disabledAuthenticationPlugins.contains(pluginClassName);
		boolean disabledByMechanism =
			this.disabledAuthenticationPlugins != null &&
			this.disabledAuthenticationPlugins.contains(pluginProtocolName);
		
		if (disabledByClassName || disabledByMechanism) {
			// if disabled then check is it default					
			if (this.defaultAuthenticationPlugin.equals(pluginClassName)) { 
				throw SQLError.createSQLException(
					Messages.getString("Connection.BadDisabledAuthenticationPlugin",
					new Object[] { disabledByClassName ? pluginClassName : pluginProtocolName}),
					getExceptionInterceptor());
			}
		} else {
			this.authenticationPlugins.put(pluginProtocolName , plugin);
			if (this.defaultAuthenticationPlugin.equals(pluginClassName)) {
				this.defaultAuthenticationPluginProtocolName = pluginProtocolName;
				isDefault = true;
			}
		}
		return isDefault;
	}
	
	/**
	 * Get authentication plugin instance from {@link MysqlIO#authenticationPlugins} map by
	 * pluginName key. If such plugin is found it's {@link AuthenticationPlugin#isReusable()} method
	 * is checked, when it's false this method returns a new instance of plugin
	 * and the same instance otherwise.
	 * 
	 * If plugin is not found method returns null, in such case the subsequent behavior
	 * of handshake process depends on type of last packet received from server:
	 * if it was Auth Challenge Packet then handshake will proceed with default plugin,
	 * if it was Auth Method Switch Request Packet then handshake will be interrupted with exception. 
	 * 
	 * @param pluginName mysql protocol plugin names, for example "mysql_native_password" and "mysql_old_password" for built-in plugins
	 * @return null if plugin is not found or authentication plugin instance initialized with current connection properties
	 * @throws SQLException 
	 */
	private AuthenticationPlugin getAuthenticationPlugin(String pluginName) throws SQLException {

		AuthenticationPlugin plugin = this.authenticationPlugins.get(pluginName);

		if (plugin != null && !plugin.isReusable()) {
			try {
				plugin = plugin.getClass().newInstance();
				plugin.init(this.connection, this.connection.getProperties());
			} catch (Throwable t) {
				SQLException sqlEx = SQLError.createSQLException(Messages
						.getString("Connection.BadAuthenticationPlugin", new Object[] { plugin.getClass().getName() }), getExceptionInterceptor());
				sqlEx.initCause(t);
				throw sqlEx;
			}
		}
		
		return plugin;
	}
	
	/**
	 * Check if given plugin requires confidentiality, but connection is without SSL
	 * @param plugin
	 * @throws SQLException
	 */
	private void checkConfidentiality(AuthenticationPlugin plugin) throws SQLException {
		if (plugin.requiresConfidentiality() && !(this.connection.getUseSSL() && this.connection.getRequireSSL())) {
			throw SQLError.createSQLException(
					Messages.getString("Connection.AuthenticationPluginRequiresSSL", new Object[] {plugin.getProtocolPluginName()}), getExceptionInterceptor());
		}
	}
	
	/**
	 * Performs an authentication handshake to authorize connection to a
	 * given database as a given MySQL user. This can happen upon initial
	 * connection to the server, after receiving Auth Challenge Packet, or
	 * at any moment during the connection life-time via a Change User
	 * request.
	 *
	 * This method is aware of pluggable authentication and will use
	 * registered authentication plugins as requested by the server.
	 *
	 * @param user       the MySQL user account to log into
	 * @param password   authentication data for the user account (depends
	 *                   on authentication method used - can be empty)
	 * @param database   database to connect to (can be empty)
	 * @param challenge  the Auth Challenge Packet received from server if
	 *                   this method is used during the initial connection.
	 *                   Otherwise null.
	 *
	 * @throws SQLException
	 */	
	private void proceedHandshakeWithPluggableAuthentication(String user, String password, String database, Buffer challenge) throws SQLException {
		if (this.authenticationPlugins == null) {
			loadAuthenticationPlugins();
		}

		int passwordLength = 16;
		int userLength = (user != null) ? user.length() : 0;
		int databaseLength = (database != null) ? database.length() : 0;

		int packLength = ((userLength + passwordLength + databaseLength) * 3) + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;

		AuthenticationPlugin plugin = null;
		Buffer fromServer = null;
		ArrayList<Buffer> toServer = new ArrayList<Buffer>();
		Boolean done = null;
		Buffer last_sent = null;
		
		boolean old_raw_challenge = false;
		
		int counter = 100;

		while (0 < counter--) {
			
			if (done == null) {

				if (challenge != null) {
					// read Auth Challenge Packet

					this.clientParam |=
							  CLIENT_PLUGIN_AUTH
							| CLIENT_LONG_PASSWORD
							| CLIENT_PROTOCOL_41
							| CLIENT_TRANSACTIONS		// Need this to get server status values
							| CLIENT_MULTI_RESULTS		// We always allow multiple result sets
							| CLIENT_SECURE_CONNECTION	// protocol with pluggable authentication always support this
							;

					// We allow the user to configure whether
					// or not they want to support multiple queries
					// (by default, this is disabled).
					if (this.connection.getAllowMultiQueries()) {
						this.clientParam |= CLIENT_MULTI_STATEMENTS;
					}

					if (((this.serverCapabilities & CLIENT_CAN_HANDLE_EXPIRED_PASSWORD) != 0) && !this.connection.getDisconnectOnExpiredPasswords()) {
						this.clientParam |= CLIENT_CAN_HANDLE_EXPIRED_PASSWORD;
					}
					if (((this.serverCapabilities & CLIENT_CONNECT_ATTRS) != 0 ) && 
							!NONE.equals(this.connection.getConnectionAttributes())) {
						this.clientParam |= CLIENT_CONNECT_ATTRS;
					}
					if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) != 0 ) {
						this.clientParam |= CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA;
					}

					this.has41NewNewProt = true;
					this.use41Extensions = true;

					if (this.connection.getUseSSL()) {
						negotiateSSLConnection(user, password, database, packLength);
					}

					String pluginName = null;
					// Due to Bug#59453 the auth-plugin-name is missing the terminating NUL-char in versions prior to 5.5.10 and 5.6.2.
		            if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {
		                if (!versionMeetsMinimum(5, 5, 10) || versionMeetsMinimum(5, 6, 0) && !versionMeetsMinimum(5, 6, 2)) {
		                	pluginName = challenge.readString("ASCII", getExceptionInterceptor(), this.authPluginDataLength);
		                } else {
		                	pluginName = challenge.readString("ASCII", getExceptionInterceptor());
		                }
		            }

					plugin = getAuthenticationPlugin(pluginName);
					// if plugin is not found for pluginName get default instead 
					if (plugin == null) plugin = getAuthenticationPlugin(this.defaultAuthenticationPluginProtocolName);

					checkConfidentiality(plugin);
					fromServer = new Buffer(StringUtils.getBytes(this.seed));
				} else {
					// no challenge so this is a changeUser call
					plugin = getAuthenticationPlugin(this.defaultAuthenticationPluginProtocolName);
					checkConfidentiality(plugin);
				}

			} else {
				
				// read packet from server and check if it's an ERROR packet
				challenge = checkErrorPacket();
				old_raw_challenge = false;

				if (challenge.isOKPacket()) {
					// if OK packet then finish handshake
					if (!done) {
						throw SQLError.createSQLException(Messages.getString("Connection.UnexpectedAuthenticationApproval", new Object[] {plugin.getProtocolPluginName()}), getExceptionInterceptor());
					}
					plugin.destroy();
					break;
					
				} else if (challenge.isAuthMethodSwitchRequestPacket()) {
					// read Auth Method Switch Request Packet
					String pluginName = challenge.readString("ASCII", getExceptionInterceptor());
					
					// get new plugin
					if (plugin != null && !plugin.getProtocolPluginName().equals(pluginName)) {
						plugin.destroy();
						plugin = getAuthenticationPlugin(pluginName);
						// if plugin is not found for pluginName throw exception
						if (plugin == null) {
							throw SQLError.createSQLException(Messages.getString("Connection.BadAuthenticationPlugin", new Object[] {pluginName}), getExceptionInterceptor());
						}
					}

					checkConfidentiality(plugin);
					fromServer = new Buffer(StringUtils.getBytes(challenge.readString("ASCII", getExceptionInterceptor())));
					
				} else {
					// read raw packet
					if (versionMeetsMinimum(5, 5, 16)) {
						fromServer = new Buffer(challenge.getBytes(challenge.getPosition(), challenge.getBufLength()-challenge.getPosition()));
					} else {
						old_raw_challenge = true;
						fromServer = new Buffer(challenge.getBytes(challenge.getPosition()-1, challenge.getBufLength()-challenge.getPosition()+1));
					}
				}
				
			}

			// call plugin
			try {
				plugin.setAuthenticationParameters(user, password);
				done = plugin.nextAuthenticationStep(fromServer, toServer);
			} catch (SQLException e) {
				throw SQLError.createSQLException(e.getMessage(), e.getSQLState(), e, getExceptionInterceptor());
			}
			
			// send response
			if (toServer.size() > 0) {
				if (challenge == null) {
					// write COM_CHANGE_USER Packet

					String enc = this.connection.getEncoding();
					int charsetIndex = 0;
					if (enc != null) {
						charsetIndex = CharsetMapping.getCharsetIndexForMysqlEncodingName(CharsetMapping.getMysqlEncodingForJavaEncoding(enc, this.connection));
					} else {
						enc = "utf-8";
					}
					if (charsetIndex == 0) {
						charsetIndex = UTF8_CHARSET_INDEX;
					}
					
					
					last_sent = new Buffer(packLength + 1);
					last_sent.writeByte((byte) MysqlDefs.COM_CHANGE_USER);
					
					// User/Password data
					last_sent.writeString(user, enc, this.connection);

					last_sent.writeByte((byte) toServer.get(0).getBufLength());
					last_sent.writeBytesNoNull(toServer.get(0).getByteBuffer(), 0, toServer.get(0).getBufLength());

					if (this.useConnectWithDb) {
						last_sent.writeString(database, enc, this.connection);
					} else {
						/* For empty database*/
						last_sent.writeByte((byte) 0);
					}

					// charset (2 bytes low-endian)
					last_sent.writeByte((byte) (charsetIndex % 256));
					if (charsetIndex > 255) {
						last_sent.writeByte((byte) (charsetIndex / 256));
					} else {
						last_sent.writeByte((byte) 0);
					}

					// plugin name
					if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {
						last_sent.writeString(plugin.getProtocolPluginName(), enc, this.connection);
					}
					
					// connection attributes
					if ((this.clientParam & CLIENT_CONNECT_ATTRS) != 0) {
						sendConnectionAttributes(last_sent, enc, this.connection);
						last_sent.writeByte((byte) 0);
					}

					send(last_sent, last_sent.getPosition());
					
				} else if (challenge.isAuthMethodSwitchRequestPacket()) {
					// write Auth Method Switch Response Packet
					
					byte savePacketSequence = this.packetSequence++;
					this.packetSequence = ++savePacketSequence;

					last_sent = new Buffer(toServer.get(0).getBufLength()+HEADER_LENGTH);
					last_sent.writeBytesNoNull(toServer.get(0).getByteBuffer(), 0, toServer.get(0).getBufLength());
					send(last_sent, last_sent.getPosition());

				} else if (challenge.isRawPacket() || old_raw_challenge) {
					// write raw packet(s)
					byte savePacketSequence = this.packetSequence++;
					
					for (Buffer buffer : toServer) {
						this.packetSequence = ++savePacketSequence;

						last_sent = new Buffer(buffer.getBufLength()+HEADER_LENGTH);
						last_sent.writeBytesNoNull(buffer.getByteBuffer(), 0, toServer.get(0).getBufLength());
						send(last_sent, last_sent.getPosition());
					}

				} else {
					// write Auth Response Packet

					last_sent = new Buffer(packLength);
					last_sent.writeLong(this.clientParam);
					last_sent.writeLong(this.maxThreeBytes);

					// charset, JDBC will connect as 'utf8',
					// and use 'SET NAMES' to change to the desired
					// charset after the connection is established.
					last_sent.writeByte((byte) UTF8_CHARSET_INDEX);

					last_sent.writeBytesNoNull(new byte[23]);	// Set of bytes reserved for future use.

					// User/Password data
					last_sent.writeString(user, "utf-8", this.connection);

					if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) != 0 ) {
						// send lenenc-int length of auth-response and string[n] auth-response
						last_sent.writeLenBytes(toServer.get(0).getBytes(toServer.get(0).getBufLength()));
					} else {
						// send 1 byte length of auth-response and string[n] auth-response
						last_sent.writeByte((byte) toServer.get(0).getBufLength());
						last_sent.writeBytesNoNull(toServer.get(0).getByteBuffer(), 0, toServer.get(0).getBufLength());
					}

					if (this.useConnectWithDb) {
						last_sent.writeString(database, "utf-8", this.connection);
					} else {
						/* For empty database*/
						last_sent.writeByte((byte) 0);
					}

					if ((this.serverCapabilities & CLIENT_PLUGIN_AUTH) != 0) {
						last_sent.writeString(plugin.getProtocolPluginName(), "utf-8", this.connection);
					}
					
					// connection attributes
					if (((this.clientParam & CLIENT_CONNECT_ATTRS) != 0) ) {
						sendConnectionAttributes(last_sent, "utf-8", this.connection);
					}
					
					send(last_sent, last_sent.getPosition());
				}
				
			}
			
		}
		
		if (counter == 0) {
			throw SQLError.createSQLException(Messages.getString("CommunicationsException.TooManyAuthenticationPluginNegotiations"), getExceptionInterceptor());
		}
		
		//
		// Can't enable compression until after handshake
		//
		if (((this.serverCapabilities & CLIENT_COMPRESS) != 0) &&
				this.connection.getUseCompression()) {
			// The following matches with ZLIB's
			// compress()
			this.deflater = new Deflater();
			this.useCompression = true;
			this.mysqlInput = new CompressedInputStream(this.connection, this.mysqlInput);
		}

		if (!this.useConnectWithDb) {
			changeDatabaseTo(database);
		}

		try {
			this.mysqlConnection = this.socketFactory.afterHandshake();
		} catch (IOException ioEx) {
			throw SQLError.createCommunicationsException(this.connection, this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
		}
	}

	private Properties getConnectionAttributesAsProperties(String atts) throws SQLException {

		Properties props = new Properties();
		
		
		if(atts != null) {
			String[] pairs = atts.split(",");
			for(String pair : pairs){
				int keyEnd = pair.indexOf(":");
				if (keyEnd > 0 && (keyEnd + 1) < pair.length()) {
					props.setProperty(pair.substring(0,keyEnd), pair.substring(keyEnd + 1));
				}
			}
		}
		
//		Leaving disabled until standard values are defined
//		props.setProperty("_os", NonRegisteringDriver.OS);
//		props.setProperty("_platform", NonRegisteringDriver.PLATFORM);
		props.setProperty("_client_name", NonRegisteringDriver.NAME);
		props.setProperty("_client_version", NonRegisteringDriver.VERSION);
		props.setProperty("_runtime_vendor", NonRegisteringDriver.RUNTIME_VENDOR);
		props.setProperty("_runtime_version", NonRegisteringDriver.RUNTIME_VERSION);
		props.setProperty("_client_license", NonRegisteringDriver.LICENSE);
		
		
		return props;
	}
	
	private void sendConnectionAttributes(Buffer buf, String enc, MySQLConnection conn) throws SQLException {
		String atts = conn.getConnectionAttributes();

		Buffer lb = new Buffer(100);
		try{
			
			Properties props = getConnectionAttributesAsProperties(atts);
			
			for(Object key : props.keySet()) {
				lb.writeLenString((String) key, enc, conn.getServerCharacterEncoding(), null, conn.parserKnowsUnicode(), conn);
				lb.writeLenString(props.getProperty((String) key), enc, conn.getServerCharacterEncoding(), null, conn.parserKnowsUnicode(), conn);				
			}

		} catch (UnsupportedEncodingException e){
			
		}

		buf.writeByte((byte) (lb.getPosition() - 4));
		buf.writeBytesNoNull(lb.getByteBuffer(), 4 , lb.getBufLength() - 4);
	
	}
	
	
	private void changeDatabaseTo(String database) throws SQLException {
		if (database == null || database.length() == 0) {
			return;
		}

		try {
		    sendCommand(MysqlDefs.INIT_DB, database, null, false, null, 0);
		} catch (Exception ex) {
			if (this.connection.getCreateDatabaseIfNotExist()) {
				sendCommand(MysqlDefs.QUERY, "CREATE DATABASE IF NOT EXISTS " +
					database,
					null, false, null, 0);
				sendCommand(MysqlDefs.INIT_DB, database, null, false, null, 0);
			} else {
				throw SQLError.createCommunicationsException(this.connection,
						this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ex, getExceptionInterceptor());
			}
		}
	}

    /**
    * Retrieve one row from the MySQL server. Note: this method is not
    * thread-safe, but it is only called from methods that are guarded by
    * synchronizing on this object.
    *
    * @param fields DOCUMENT ME!
    * @param columnCount DOCUMENT ME!
    * @param isBinaryEncoded DOCUMENT ME!
    * @param resultSetConcurrency DOCUMENT ME!
     * @param b
    *
    * @return DOCUMENT ME!
    *
    * @throws SQLException DOCUMENT ME!
    */
    final ResultSetRow nextRow(Field[] fields, int columnCount,
        boolean isBinaryEncoded, int resultSetConcurrency,
        boolean useBufferRowIfPossible,
        boolean useBufferRowExplicit,
        boolean canReuseRowPacketForBufferRow, Buffer existingRowPacket)
        throws SQLException {

    	if (this.useDirectRowUnpack && existingRowPacket == null
				&& !isBinaryEncoded && !useBufferRowIfPossible
				&& !useBufferRowExplicit) {
			return nextRowFast(fields, columnCount, isBinaryEncoded, resultSetConcurrency,
					useBufferRowIfPossible, useBufferRowExplicit, canReuseRowPacketForBufferRow);
		}

        Buffer rowPacket = null;

        if (existingRowPacket == null) {
        	rowPacket = checkErrorPacket();

        	if (!useBufferRowExplicit && useBufferRowIfPossible) {
        		if (rowPacket.getBufLength() > this.useBufferRowSizeThreshold) {
        			useBufferRowExplicit = true;
        		}
        	}
        } else {
        	// We attempted to do nextRowFast(), but the packet was a
        	// multipacket, so we couldn't unpack it directly
        	rowPacket = existingRowPacket;
        	checkErrorPacket(existingRowPacket);
        }


        if (!isBinaryEncoded) {
            //
            // Didn't read an error, so re-position to beginning
            // of packet in order to read result set data
            //
            rowPacket.setPosition(rowPacket.getPosition() - 1);

            if (!rowPacket.isLastDataPacket()) {
            	if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE
            			|| (!useBufferRowIfPossible && !useBufferRowExplicit)) {

            		byte[][] rowData = new byte[columnCount][];

            		for (int i = 0; i < columnCount; i++) {
            			rowData[i] = rowPacket.readLenByteArray(0);
            		}

            		return new ByteArrayRow(rowData, getExceptionInterceptor());
            	}

            	if (!canReuseRowPacketForBufferRow) {
            		this.reusablePacket = new Buffer(rowPacket.getBufLength());
            	}

            	return new BufferRow(rowPacket, fields, false, getExceptionInterceptor());

            }

            readServerStatusForResultSets(rowPacket);

            return null;
        }

        //
        // Handle binary-encoded data for server-side
        // PreparedStatements...
        //
        if (!rowPacket.isLastDataPacket()) {
        	if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE
        			|| (!useBufferRowIfPossible && !useBufferRowExplicit)) {
        		return unpackBinaryResultSetRow(fields, rowPacket,
        				resultSetConcurrency);
        	}

        	if (!canReuseRowPacketForBufferRow) {
        		this.reusablePacket = new Buffer(rowPacket.getBufLength());
        	}

            return new BufferRow(rowPacket, fields, true, getExceptionInterceptor());
        }

        rowPacket.setPosition(rowPacket.getPosition() - 1);
        readServerStatusForResultSets(rowPacket);

        return null;
    }

    final ResultSetRow nextRowFast(Field[] fields, int columnCount,
            boolean isBinaryEncoded, int resultSetConcurrency,
            boolean useBufferRowIfPossible,
            boolean useBufferRowExplicit, boolean canReuseRowPacket)
			throws SQLException {
		try {
			int lengthRead = readFully(this.mysqlInput, this.packetHeaderBuf,
					0, 4);

			if (lengthRead < 4) {
				forceClose();
				throw new RuntimeException(Messages.getString("MysqlIO.43")); //$NON-NLS-1$
			}

			int packetLength = (this.packetHeaderBuf[0] & 0xff)
					+ ((this.packetHeaderBuf[1] & 0xff) << 8)
					+ ((this.packetHeaderBuf[2] & 0xff) << 16);

			// Have we stumbled upon a multi-packet?
			if (packetLength == this.maxThreeBytes) {
				reuseAndReadPacket(this.reusablePacket, packetLength);

				// Go back to "old" way which uses packets
				return nextRow(fields, columnCount, isBinaryEncoded, resultSetConcurrency,
						useBufferRowIfPossible, useBufferRowExplicit,
						canReuseRowPacket, this.reusablePacket);
			}

			// Does this go over the threshold where we should use a BufferRow?

			if (packetLength > this.useBufferRowSizeThreshold) {
				reuseAndReadPacket(this.reusablePacket, packetLength);

				// Go back to "old" way which uses packets
				return nextRow(fields, columnCount, isBinaryEncoded, resultSetConcurrency,
						true, true,
						false, this.reusablePacket);
			}

			int remaining = packetLength;

			boolean firstTime = true;

			byte[][] rowData = null;

			for (int i = 0; i < columnCount; i++) {

				int sw = this.mysqlInput.read() & 0xff;
				remaining--;

				if (firstTime) {
					if (sw == 255) {
						// error packet - we assemble it whole for "fidelity"
						// in case we ever need an entire packet in checkErrorPacket()
						// but we could've gotten away with just writing the error code
						// and message in it (for now).
						Buffer errorPacket = new Buffer(packetLength + HEADER_LENGTH);
						errorPacket.setPosition(0);
						errorPacket.writeByte(this.packetHeaderBuf[0]);
						errorPacket.writeByte(this.packetHeaderBuf[1]);
						errorPacket.writeByte(this.packetHeaderBuf[2]);
						errorPacket.writeByte((byte) 1);
						errorPacket.writeByte((byte)sw);
						readFully(this.mysqlInput, errorPacket.getByteBuffer(), 5, packetLength - 1);
						errorPacket.setPosition(4);
						checkErrorPacket(errorPacket);
					}

					if (sw == 254 && packetLength < 9) {
						if (this.use41Extensions) {
							this.warningCount = (this.mysqlInput.read() & 0xff)
									| ((this.mysqlInput.read() & 0xff) << 8);
							remaining -= 2;

							if (this.warningCount > 0) {
								this.hadWarnings = true; // this is a
															// 'latch', it's
															// reset by
															// sendCommand()
							}

							this.oldServerStatus = this.serverStatus;
							
							this.serverStatus = (this.mysqlInput.read() & 0xff)
									| ((this.mysqlInput.read() & 0xff) << 8);
							checkTransactionState(oldServerStatus);
							
							remaining -= 2;

							if (remaining > 0) {
								skipFully(this.mysqlInput, remaining);
							}
						}

						return null; // last data packet
					}

					rowData = new byte[columnCount][];

					firstTime = false;
				}

				int len = 0;

				switch (sw) {
				case 251:
					len = NULL_LENGTH;
					break;

				case 252:
					len = (this.mysqlInput.read() & 0xff)
							| ((this.mysqlInput.read() & 0xff) << 8);
					remaining -= 2;
					break;

				case 253:
					len = (this.mysqlInput.read() & 0xff)
							| ((this.mysqlInput.read() & 0xff) << 8)
							| ((this.mysqlInput.read() & 0xff) << 16);

					remaining -= 3;
					break;

				case 254:
					len = (int) ((this.mysqlInput.read() & 0xff)
							| ((long) (this.mysqlInput.read() & 0xff) << 8)
							| ((long) (this.mysqlInput.read() & 0xff) << 16)
							| ((long) (this.mysqlInput.read() & 0xff) << 24)
							| ((long) (this.mysqlInput.read() & 0xff) << 32)
							| ((long) (this.mysqlInput.read() & 0xff) << 40)
							| ((long) (this.mysqlInput.read() & 0xff) << 48)
							| ((long) (this.mysqlInput.read() & 0xff) << 56));
					remaining -= 8;
					break;

				default:
					len = sw;
				}

				if (len == NULL_LENGTH) {
					rowData[i] = null;
				} else if (len == 0) {
					rowData[i] = Constants.EMPTY_BYTE_ARRAY;
				} else {
					rowData[i] = new byte[len];

					int bytesRead = readFully(this.mysqlInput, rowData[i], 0,
							len);

					if (bytesRead != len) {
						throw SQLError.createCommunicationsException(this.connection,
			    				this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
			    				new IOException(Messages.getString("MysqlIO.43")), getExceptionInterceptor());
					}

					remaining -= bytesRead;
				}
			}

			if (remaining > 0) {
				skipFully(this.mysqlInput, remaining);
			}

			return new ByteArrayRow(rowData, getExceptionInterceptor());
		} catch (IOException ioEx) {
			throw SQLError.createCommunicationsException(this.connection,
    				this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
		}
	}

    /**
     * Log-off of the MySQL server and close the socket.
     *
     * @throws SQLException DOCUMENT ME!
     */
    final void quit() throws SQLException {
    	try {
    		// we're not going to read the response, fixes BUG#56979 Improper connection closing logic 
    		// leads to TIME_WAIT sockets on server

    		try {
    			if (!this.mysqlConnection.isClosed()) {
    				try {
    					this.mysqlConnection.shutdownInput();
    				} catch (UnsupportedOperationException ex) {
	            		// ignore, some sockets do not support this method
	            	}
    			}
    		} catch (IOException ioEx) {
    			this.connection.getLog().logWarn("Caught while disconnecting...", ioEx);
    		}
    		
	        Buffer packet = new Buffer(6);
	        this.packetSequence = -1;
	        this.compressedPacketSequence = -1;
	        packet.writeByte((byte) MysqlDefs.QUIT);
	        send(packet, packet.getPosition());
    	} finally {
    		forceClose();
    	}
    }

    /**
     * Returns the packet used for sending data (used by PreparedStatement)
     * Guarded by external synchronization on a mutex.
     *
     * @return A packet to send data with
     */
    Buffer getSharedSendPacket() {
        if (this.sharedSendPacket == null) {
        	this.sharedSendPacket = new Buffer(INITIAL_PACKET_SIZE);
        }

        return this.sharedSendPacket;
    }

    void closeStreamer(RowData streamer) throws SQLException {
        if (this.streamingData == null) {
            throw SQLError.createSQLException(Messages.getString("MysqlIO.17") //$NON-NLS-1$
                 +streamer + Messages.getString("MysqlIO.18"), getExceptionInterceptor()); //$NON-NLS-1$
        }

        if (streamer != this.streamingData) {
            throw SQLError.createSQLException(Messages.getString("MysqlIO.19") //$NON-NLS-1$
                 +streamer + Messages.getString("MysqlIO.20") //$NON-NLS-1$
                 +Messages.getString("MysqlIO.21") //$NON-NLS-1$
                 +Messages.getString("MysqlIO.22"), getExceptionInterceptor()); //$NON-NLS-1$
        }

        this.streamingData = null;
    }

    boolean tackOnMoreStreamingResults(ResultSetImpl addingTo) throws SQLException {
    	if ((this.serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0) {
    		
			boolean moreRowSetsExist = true;
			ResultSetImpl currentResultSet = addingTo;
			boolean firstTime = true;
			
			while (moreRowSetsExist) {
				if (!firstTime && currentResultSet.reallyResult()) {
					break;
				}
				
				firstTime = false;
				
				Buffer fieldPacket = checkErrorPacket();
	            fieldPacket.setPosition(0);
	            
	            java.sql.Statement owningStatement = addingTo.getStatement();
	            
	            int maxRows = owningStatement.getMaxRows();
	            
	            // fixme for catalog, isBinary
	            
	            ResultSetImpl newResultSet = readResultsForQueryOrUpdate(
	            		(StatementImpl)owningStatement,
	                    maxRows, owningStatement.getResultSetType(), 
	                    owningStatement.getResultSetConcurrency(),
	                    true, owningStatement.getConnection().getCatalog(), fieldPacket, 
	                    addingTo.isBinaryEncoded,
	                    -1L, null);

	            currentResultSet.setNextResultSet(newResultSet);

	            currentResultSet = newResultSet;

	            moreRowSetsExist = (this.serverStatus & MysqlIO.SERVER_MORE_RESULTS_EXISTS) != 0;
	            
	            if (!currentResultSet.reallyResult() && !moreRowSetsExist) {
	            	// special case, we can stop "streaming"
	            	return false;
	            }
			}
			
			return true;
    	}

    	return false;
    }
    
    ResultSetImpl readAllResults(StatementImpl callingStatement, int maxRows,
        int resultSetType, int resultSetConcurrency, boolean streamResults,
        String catalog, Buffer resultPacket, boolean isBinaryEncoded,
        long preSentColumnCount, Field[] metadataFromCache)
        throws SQLException {
        resultPacket.setPosition(resultPacket.getPosition() - 1);

        ResultSetImpl topLevelResultSet = readResultsForQueryOrUpdate(callingStatement,
                maxRows, resultSetType, resultSetConcurrency, streamResults,
                catalog, resultPacket, isBinaryEncoded, preSentColumnCount,
                metadataFromCache);

        ResultSetImpl currentResultSet = topLevelResultSet;

        boolean checkForMoreResults = ((this.clientParam &
            CLIENT_MULTI_RESULTS) != 0);

        boolean serverHasMoreResults = (this.serverStatus &
            SERVER_MORE_RESULTS_EXISTS) != 0;

        //
        // TODO: We need to support streaming of multiple result sets
        //
        if (serverHasMoreResults && streamResults) {
            //clearInputStream();
//
            //throw SQLError.createSQLException(Messages.getString("MysqlIO.23"), //$NON-NLS-1$
                //SQLError.SQL_STATE_DRIVER_NOT_CAPABLE);
        	if (topLevelResultSet.getUpdateCount() != -1) {
        		tackOnMoreStreamingResults(topLevelResultSet);
        	}
        	
        	reclaimLargeReusablePacket();
        	
        	return topLevelResultSet;
        }

        boolean moreRowSetsExist = checkForMoreResults & serverHasMoreResults;

        while (moreRowSetsExist) {
        	Buffer fieldPacket = checkErrorPacket();
            fieldPacket.setPosition(0);

            ResultSetImpl newResultSet = readResultsForQueryOrUpdate(callingStatement,
                    maxRows, resultSetType, resultSetConcurrency,
                    streamResults, catalog, fieldPacket, isBinaryEncoded,
                    preSentColumnCount, metadataFromCache);

            currentResultSet.setNextResultSet(newResultSet);

            currentResultSet = newResultSet;

            moreRowSetsExist = (this.serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0;
        }

        if (!streamResults) {
            clearInputStream();
        }

        reclaimLargeReusablePacket();

        return topLevelResultSet;
    }

    /**
     * Sets the buffer size to max-buf
     */
    void resetMaxBuf() {
        this.maxAllowedPacket = this.connection.getMaxAllowedPacket();
    }

    /**
     * Send a command to the MySQL server If data is to be sent with command,
     * it should be put in extraData.
     *
     * Raw packets can be sent by setting queryPacket to something other
     * than null.
     *
     * @param command the MySQL protocol 'command' from MysqlDefs
     * @param extraData any 'string' data for the command
     * @param queryPacket a packet pre-loaded with data for the protocol (i.e.
     * from a client-side prepared statement).
     * @param skipCheck do not call checkErrorPacket() if true
     * @param extraDataCharEncoding the character encoding of the extraData
     * parameter.
     *
     * @return the response packet from the server
     *
     * @throws SQLException if an I/O error or SQL error occurs
     */

    final Buffer sendCommand(int command, String extraData, Buffer queryPacket,
        boolean skipCheck, String extraDataCharEncoding, int timeoutMillis)
        throws SQLException {
    	this.commandCount++;
    	
        //
        // We cache these locally, per-command, as the checks
        // for them are in very 'hot' sections of the I/O code
        // and we save 10-15% in overall performance by doing this...
        //
        this.enablePacketDebug = this.connection.getEnablePacketDebug();
        this.readPacketSequence = 0;

        int oldTimeout = 0;
        
        if (timeoutMillis != 0) {
        	try {
        		oldTimeout = this.mysqlConnection.getSoTimeout();
				this.mysqlConnection.setSoTimeout(timeoutMillis);
			} catch (SocketException e) {
				throw SQLError.createCommunicationsException(this.connection, lastPacketSentTimeMs, 
						lastPacketReceivedTimeMs, e, getExceptionInterceptor());
			}
        }
        
        try {

            checkForOutstandingStreamingData();

            // Clear serverStatus...this value is guarded by an
            // external mutex, as you can only ever be processing
            // one command at a time
            this.oldServerStatus = this.serverStatus;
            this.serverStatus = 0;
            this.hadWarnings = false;
            this.warningCount = 0;

            this.queryNoIndexUsed = false;
            this.queryBadIndexUsed = false;
            this.serverQueryWasSlow = false;
            
            //
            // Compressed input stream needs cleared at beginning
            // of each command execution...
            //
            if (this.useCompression) {
                int bytesLeft = this.mysqlInput.available();

                if (bytesLeft > 0) {
                    this.mysqlInput.skip(bytesLeft);
                }
            }

            try {
                clearInputStream();

                //
                // PreparedStatements construct their own packets,
                // for efficiency's sake.
                //
                // If this is a generic query, we need to re-use
                // the sending packet.
                //
                if (queryPacket == null) {
                    int packLength = HEADER_LENGTH + COMP_HEADER_LENGTH + 1 +
                        ((extraData != null) ? extraData.length() : 0) + 2;

                    if (this.sendPacket == null) {
                        this.sendPacket = new Buffer(packLength);
                    }

                    this.packetSequence = -1;
                    this.compressedPacketSequence = -1;
                    this.readPacketSequence = 0;
                    this.checkPacketSequence = true;
                    this.sendPacket.clear();

                    this.sendPacket.writeByte((byte) command);

                    if ((command == MysqlDefs.INIT_DB) ||
                            (command == MysqlDefs.CREATE_DB) ||
                            (command == MysqlDefs.DROP_DB) ||
                            (command == MysqlDefs.QUERY) ||
                            (command == MysqlDefs.COM_PREPARE)) {
                        if (extraDataCharEncoding == null) {
                            this.sendPacket.writeStringNoNull(extraData);
                        } else {
                            this.sendPacket.writeStringNoNull(extraData,
                                extraDataCharEncoding,
                                this.connection.getServerCharacterEncoding(),
                                this.connection.parserKnowsUnicode(), this.connection);
                        }
                    } else if (command == MysqlDefs.PROCESS_KILL) {
                        long id = Long.parseLong(extraData);
                        this.sendPacket.writeLong(id);
                    }

                    send(this.sendPacket, this.sendPacket.getPosition());
                } else {
                    this.packetSequence = -1;
                    this.compressedPacketSequence = -1;
                    send(queryPacket, queryPacket.getPosition()); // packet passed by PreparedStatement
                }
            } catch (SQLException sqlEx) {
                // don't wrap SQLExceptions
                throw sqlEx;
            } catch (Exception ex) {
                throw SQLError.createCommunicationsException(this.connection,
                    this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ex, getExceptionInterceptor());
            }

            Buffer returnPacket = null;

            if (!skipCheck) {
                if ((command == MysqlDefs.COM_EXECUTE) ||
                        (command == MysqlDefs.COM_RESET_STMT)) {
                    this.readPacketSequence = 0;
                    this.packetSequenceReset = true;
                }

                returnPacket = checkErrorPacket(command);
            }

            return returnPacket;
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        } finally {
        	if (timeoutMillis != 0) {
        		try {
        			this.mysqlConnection.setSoTimeout(oldTimeout);
        		} catch (SocketException e) {
    				throw SQLError.createCommunicationsException(this.connection, lastPacketSentTimeMs, 
    						lastPacketReceivedTimeMs, e, getExceptionInterceptor());
    			}
        	}
        }
    }

    private int statementExecutionDepth = 0;
	private boolean useAutoSlowLog;

	protected boolean shouldIntercept() {
		return this.statementInterceptors != null;
	}
	
    /**
     * Send a query stored in a packet directly to the server.
     *
     * @param callingStatement DOCUMENT ME!
     * @param resultSetConcurrency DOCUMENT ME!
     * @param characterEncoding DOCUMENT ME!
     * @param queryPacket DOCUMENT ME!
     * @param maxRows DOCUMENT ME!
     * @param conn DOCUMENT ME!
     * @param resultSetType DOCUMENT ME!
     * @param resultSetConcurrency DOCUMENT ME!
     * @param streamResults DOCUMENT ME!
     * @param catalog DOCUMENT ME!
     * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
     *
     * @return DOCUMENT ME!
     *
     * @throws Exception DOCUMENT ME!
     */
    final ResultSetInternalMethods sqlQueryDirect(StatementImpl callingStatement, String query,
    		String characterEncoding, Buffer queryPacket, int maxRows,
    		int resultSetType, int resultSetConcurrency,
    		boolean streamResults, String catalog, Field[] cachedMetadata)
    throws Exception {
    	this.statementExecutionDepth++;

    	try {
	    	if (this.statementInterceptors != null) {
	    		ResultSetInternalMethods interceptedResults =
	    			invokeStatementInterceptorsPre(query, callingStatement, false);

	    		if (interceptedResults != null) {
	    			return interceptedResults;
	    		}
	    	}

	    	long queryStartTime = 0;
	    	long queryEndTime = 0;

    		String statementComment = this.connection.getStatementComment();
    		
    		if (this.connection.getIncludeThreadNamesAsStatementComment()) {
    			statementComment = (statementComment != null ? statementComment + ", " : "") + "java thread: " + Thread.currentThread().getName();
    		}
    		
	    	if (query != null) {
	    		// We don't know exactly how many bytes we're going to get
	    		// from the query. Since we're dealing with Unicode, the
	    		// max is 2, so pad it (2 * query) + space for headers
	    		int packLength = HEADER_LENGTH + 1 + (query.length() * 3) + 2;

	    		byte[] commentAsBytes = null;

	    		if (statementComment != null) {
	    			commentAsBytes = StringUtils.getBytes(statementComment, null,
	    					characterEncoding, this.connection
	    					.getServerCharacterEncoding(),
	    					this.connection.parserKnowsUnicode(), getExceptionInterceptor());

	    			packLength += commentAsBytes.length;
	    			packLength += 6; // for /*[space] [space]*/
	    		}

	    		if (this.sendPacket == null) {
	    			this.sendPacket = new Buffer(packLength);
	    		} else {
	    			this.sendPacket.clear();
	    		}

	    		this.sendPacket.writeByte((byte) MysqlDefs.QUERY);

	    		if (commentAsBytes != null) {
	    			this.sendPacket.writeBytesNoNull(Constants.SLASH_STAR_SPACE_AS_BYTES);
	    			this.sendPacket.writeBytesNoNull(commentAsBytes);
	    			this.sendPacket.writeBytesNoNull(Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES);
	    		}

	    		if (characterEncoding != null) {
	    			if (this.platformDbCharsetMatches) {
	    				this.sendPacket.writeStringNoNull(query, characterEncoding,
	    						this.connection.getServerCharacterEncoding(),
	    						this.connection.parserKnowsUnicode(),
	    						this.connection);
	    			} else {
	    				if (StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) { //$NON-NLS-1$
	    					this.sendPacket.writeBytesNoNull(StringUtils.getBytes(query));
	    				} else {
	    					this.sendPacket.writeStringNoNull(query,
	    							characterEncoding,
	    							this.connection.getServerCharacterEncoding(),
	    							this.connection.parserKnowsUnicode(),
	    							this.connection);
	    				}
	    			}
	    		} else {
	    			this.sendPacket.writeStringNoNull(query);
	    		}

	    		queryPacket = this.sendPacket;
	    	}

	    	byte[] queryBuf = null;
	    	int oldPacketPosition = 0;

	    	if (needToGrabQueryFromPacket) {
	    		queryBuf = queryPacket.getByteBuffer();

	    		// save the packet position
	    		oldPacketPosition = queryPacket.getPosition();

	    		queryStartTime = getCurrentTimeNanosOrMillis();
	    	}
	    	
	    	if (this.autoGenerateTestcaseScript) {
	    		String testcaseQuery = null;

	    		if (query != null) {
	    			if (statementComment != null) {
	    				testcaseQuery = "/* " + statementComment + " */ " + query;
	    			} else {
	    				testcaseQuery = query;
	    			}
	    		} else {
	    			testcaseQuery = StringUtils.toString(queryBuf, 5,
	    					(oldPacketPosition - 5));
	    		}

	    		StringBuffer debugBuf = new StringBuffer(testcaseQuery.length() + 32);
	    		this.connection.generateConnectionCommentBlock(debugBuf);
	    		debugBuf.append(testcaseQuery);
	    		debugBuf.append(';');
	    		this.connection.dumpTestcaseQuery(debugBuf.toString());
	    	}

	    	// Send query command and sql query string
	    	Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket,
	    			false, null, 0);

	    	long fetchBeginTime = 0;
	    	long fetchEndTime = 0;

	    	String profileQueryToLog = null;

	    	boolean queryWasSlow = false;

	    	if (this.profileSql || this.logSlowQueries) {
	    		queryEndTime = getCurrentTimeNanosOrMillis();

	    		boolean shouldExtractQuery = false;

	    		if (this.profileSql) {
	    			shouldExtractQuery = true;
	    		} else if (this.logSlowQueries) {
	    			long queryTime = queryEndTime - queryStartTime;
	    			
	    			boolean logSlow = false;
	    			
	    			if (!this.useAutoSlowLog) {
	    				logSlow = queryTime > this.connection.getSlowQueryThresholdMillis();
	    			} else {
	    				logSlow = this.connection.isAbonormallyLongQuery(queryTime);
	    				
	    				this.connection.reportQueryTime(queryTime);
	    			}
	    			
	    			if (logSlow) {
	    				shouldExtractQuery = true;
	    				queryWasSlow = true;
	    			}
	    		}

	    		if (shouldExtractQuery) {
	    			// Extract the actual query from the network packet
	    			boolean truncated = false;

	    			int extractPosition = oldPacketPosition;

	    			if (oldPacketPosition > this.connection.getMaxQuerySizeToLog()) {
	    				extractPosition = this.connection.getMaxQuerySizeToLog() + 5;
	    				truncated = true;
	    			}

	    			profileQueryToLog = StringUtils.toString(queryBuf, 5,
	    					(extractPosition - 5));

	    			if (truncated) {
	    				profileQueryToLog += Messages.getString("MysqlIO.25"); //$NON-NLS-1$
	    			}
	    		}

	    		fetchBeginTime = queryEndTime;
	    	}

	    	ResultSetInternalMethods rs = readAllResults(callingStatement, maxRows, resultSetType,
	    			resultSetConcurrency, streamResults, catalog, resultPacket,
	    			false, -1L, cachedMetadata);

	    	if (queryWasSlow && !this.serverQueryWasSlow /* don't log slow queries twice */) {
	    		StringBuffer mesgBuf = new StringBuffer(48 +
	    				profileQueryToLog.length());

	    		mesgBuf.append(Messages.getString("MysqlIO.SlowQuery",
	    				new Object[] {String.valueOf(this.useAutoSlowLog ? 
	    						" 95% of all queries " : this.slowQueryThreshold),
	    				queryTimingUnits,
	    				Long.valueOf(queryEndTime - queryStartTime)}));
	    		mesgBuf.append(profileQueryToLog);

	    		ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);

	    		eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_SLOW_QUERY,
	    				"", catalog, this.connection.getId(), //$NON-NLS-1$
	    				(callingStatement != null) ? callingStatement.getId() : 999,
	    						((ResultSetImpl)rs).resultId, System.currentTimeMillis(),
	    						(int) (queryEndTime - queryStartTime), queryTimingUnits, null,
	    						LogUtils.findCallingClassAndMethod(new Throwable()), mesgBuf.toString()));

	    		if (this.connection.getExplainSlowQueries()) {
	    			if (oldPacketPosition < MAX_QUERY_SIZE_TO_EXPLAIN) {
	    				explainSlowQuery(queryPacket.getBytes(5,
	    						(oldPacketPosition - 5)), profileQueryToLog);
	    			} else {
	    				this.connection.getLog().logWarn(Messages.getString(
	    						"MysqlIO.28") //$NON-NLS-1$
	    						+MAX_QUERY_SIZE_TO_EXPLAIN +
	    						Messages.getString("MysqlIO.29")); //$NON-NLS-1$
	    			}
	    		}
	    	}

	    	if (this.logSlowQueries) {

	    		ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);

	    		if (this.queryBadIndexUsed && this.profileSql) {
	    			eventSink.consumeEvent(new ProfilerEvent(
	    					ProfilerEvent.TYPE_SLOW_QUERY, "", catalog, //$NON-NLS-1$
	    					this.connection.getId(),
	    					(callingStatement != null) ? callingStatement.getId()
	    							: 999, ((ResultSetImpl)rs).resultId,
	    							System.currentTimeMillis(),
	    							(queryEndTime - queryStartTime), this.queryTimingUnits,
	    							null,
	    							LogUtils.findCallingClassAndMethod(new Throwable()),
	    							Messages.getString("MysqlIO.33") //$NON-NLS-1$
	    							+profileQueryToLog));
	    		}

	    		if (this.queryNoIndexUsed && this.profileSql) {
	    			eventSink.consumeEvent(new ProfilerEvent(
	    					ProfilerEvent.TYPE_SLOW_QUERY, "", catalog, //$NON-NLS-1$
	    					this.connection.getId(),
	    					(callingStatement != null) ? callingStatement.getId()
	    							: 999, ((ResultSetImpl)rs).resultId,
	    							System.currentTimeMillis(),
	    							(queryEndTime - queryStartTime), this.queryTimingUnits,
	    							null,
	    							LogUtils.findCallingClassAndMethod(new Throwable()),
	    							Messages.getString("MysqlIO.35") //$NON-NLS-1$
	    							+profileQueryToLog));
	    		}
	    		
	    		if (this.serverQueryWasSlow && this.profileSql) {
	    			eventSink.consumeEvent(new ProfilerEvent(
	    					ProfilerEvent.TYPE_SLOW_QUERY, "", catalog, //$NON-NLS-1$
	    					this.connection.getId(),
	    					(callingStatement != null) ? callingStatement.getId()
	    							: 999, ((ResultSetImpl)rs).resultId,
	    							System.currentTimeMillis(),
	    							(queryEndTime - queryStartTime), this.queryTimingUnits,
	    							null,
	    							LogUtils.findCallingClassAndMethod(new Throwable()),
	    							Messages.getString("MysqlIO.ServerSlowQuery") //$NON-NLS-1$
	    							+profileQueryToLog));
	    		}
	    	}

	    	if (this.profileSql) {
	    		fetchEndTime = getCurrentTimeNanosOrMillis();

	    		ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);

	    		eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_QUERY,
	    				"", catalog, this.connection.getId(), //$NON-NLS-1$
	    				(callingStatement != null) ? callingStatement.getId() : 999,
	    						((ResultSetImpl)rs).resultId, System.currentTimeMillis(),
	    						(queryEndTime - queryStartTime), this.queryTimingUnits,
	    						null,
	    						LogUtils.findCallingClassAndMethod(new Throwable()), profileQueryToLog));

	    		eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_FETCH,
	    				"", catalog, this.connection.getId(), //$NON-NLS-1$
	    				(callingStatement != null) ? callingStatement.getId() : 999,
	    						((ResultSetImpl)rs).resultId, System.currentTimeMillis(),
	    						(fetchEndTime - fetchBeginTime), this.queryTimingUnits,
	    						null,
	    						LogUtils.findCallingClassAndMethod(new Throwable()), null));
	    	}

	    	if (this.hadWarnings) {
	    		scanForAndThrowDataTruncation();
	    	}

	    	if (this.statementInterceptors != null) {
	    		ResultSetInternalMethods interceptedResults = invokeStatementInterceptorsPost(
	    				query, callingStatement, rs, false, null);

	    		if (interceptedResults != null) {
	    			rs = interceptedResults;
	    		}
	    	}

	    	return rs;
    	} catch (SQLException sqlEx) {
    		if (this.statementInterceptors != null) {
	    		invokeStatementInterceptorsPost(
	    				query, callingStatement, null, false, sqlEx); // we don't do anything with the result set in this case
    		}
    		
    		if (callingStatement != null) {
    			synchronized (callingStatement.cancelTimeoutMutex) {
	    			if (callingStatement.wasCancelled) {
						SQLException cause = null;
						
						if (callingStatement.wasCancelledByTimeout) {
							cause = new MySQLTimeoutException();
						} else {
							cause = new MySQLStatementCancelledException();
						}
						
						callingStatement.resetCancelledState();
						
						throw cause;
					}
    			}
    		}
    		
    		throw sqlEx;
    	} finally {
    		this.statementExecutionDepth--;
    	}
    }

    ResultSetInternalMethods invokeStatementInterceptorsPre(String sql,
			Statement interceptedStatement, boolean forceExecute) throws SQLException {
		ResultSetInternalMethods previousResultSet = null;

		for (int i = 0, s = this.statementInterceptors.size(); i < s; i++) {
			StatementInterceptorV2 interceptor = this.statementInterceptors.get(i);

			boolean executeTopLevelOnly = interceptor.executeTopLevelOnly();
			boolean shouldExecute = (executeTopLevelOnly && (this.statementExecutionDepth == 1 || forceExecute))
					|| (!executeTopLevelOnly);

			if (shouldExecute) {
				String sqlToInterceptor = sql;

				//if (interceptedStatement instanceof PreparedStatement) {
				//	sqlToInterceptor = ((PreparedStatement) interceptedStatement)
				//			.asSql();
				//}

				ResultSetInternalMethods interceptedResultSet = interceptor
						.preProcess(sqlToInterceptor, interceptedStatement,
								this.connection);

				if (interceptedResultSet != null) {
					previousResultSet = interceptedResultSet;
				}
			}
		}

		return previousResultSet;
	}

	ResultSetInternalMethods invokeStatementInterceptorsPost(
			String sql, Statement interceptedStatement,
			ResultSetInternalMethods originalResultSet, boolean forceExecute, SQLException statementException) throws SQLException {

		for (int i = 0, s = this.statementInterceptors.size(); i < s; i++) {
			StatementInterceptorV2 interceptor = this.statementInterceptors.get(i);

			boolean executeTopLevelOnly = interceptor.executeTopLevelOnly();
			boolean shouldExecute = (executeTopLevelOnly && (this.statementExecutionDepth == 1 || forceExecute))
					|| (!executeTopLevelOnly);

			if (shouldExecute) {
				String sqlToInterceptor = sql;
				
				ResultSetInternalMethods interceptedResultSet = interceptor
						.postProcess(sqlToInterceptor, interceptedStatement,
								originalResultSet, this.connection, this.warningCount, 
								this.queryNoIndexUsed, this.queryBadIndexUsed, statementException);

				if (interceptedResultSet != null) {
					originalResultSet = interceptedResultSet;
				}
			}
		}

		return originalResultSet;
	}

	private void calculateSlowQueryThreshold() {
		this.slowQueryThreshold = this.connection.getSlowQueryThresholdMillis();

		if (this.connection.getUseNanosForElapsedTime()) {
			long nanosThreshold = this.connection.getSlowQueryThresholdNanos();

			if (nanosThreshold != 0) {
				this.slowQueryThreshold = nanosThreshold;
			} else {
				this.slowQueryThreshold *= 1000000; // 1 million millis in a nano
			}
		}
	}

	protected long getCurrentTimeNanosOrMillis() {
		if (this.useNanosForElapsedTime) {
			return Util.getCurrentTimeNanosOrMillis();
		}

		return System.currentTimeMillis();
	}

    /**
     * Returns the host this IO is connected to
     *
     * @return DOCUMENT ME!
     */
    String getHost() {
        return this.host;
    }

    /**
     * Is the version of the MySQL server we are connected to the given
     * version?
     *
     * @param major the major version
     * @param minor the minor version
     * @param subminor the subminor version
     *
     * @return true if the version of the MySQL server we are connected  is the
     *         given version
     */
    boolean isVersion(int major, int minor, int subminor) {
        return ((major == getServerMajorVersion()) &&
        (minor == getServerMinorVersion()) &&
        (subminor == getServerSubMinorVersion()));
    }

    /**
     * Does the version of the MySQL server we are connected to meet the given
     * minimums?
     *
     * @param major DOCUMENT ME!
     * @param minor DOCUMENT ME!
     * @param subminor DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    boolean versionMeetsMinimum(int major, int minor, int subminor) {
        if (getServerMajorVersion() >= major) {
            if (getServerMajorVersion() == major) {
                if (getServerMinorVersion() >= minor) {
                    if (getServerMinorVersion() == minor) {
                        return (getServerSubMinorVersion() >= subminor);
                    }

                    // newer than major.minor
                    return true;
                }

                // older than major.minor
                return false;
            }

            // newer than major
            return true;
        }

        return false;
    }

    /**
     * Returns the hex dump of the given packet, truncated to
     * MAX_PACKET_DUMP_LENGTH if packetLength exceeds that value.
     *
     * @param packetToDump the packet to dump in hex
     * @param packetLength the number of bytes to dump
     *
     * @return the hex dump of the given packet
     */
    private final static String getPacketDumpToLog(Buffer packetToDump,
        int packetLength) {
        if (packetLength < MAX_PACKET_DUMP_LENGTH) {
            return packetToDump.dump(packetLength);
        }

        StringBuffer packetDumpBuf = new StringBuffer(MAX_PACKET_DUMP_LENGTH * 4);
        packetDumpBuf.append(packetToDump.dump(MAX_PACKET_DUMP_LENGTH));
        packetDumpBuf.append(Messages.getString("MysqlIO.36")); //$NON-NLS-1$
        packetDumpBuf.append(MAX_PACKET_DUMP_LENGTH);
        packetDumpBuf.append(Messages.getString("MysqlIO.37")); //$NON-NLS-1$

        return packetDumpBuf.toString();
    }

    private final int readFully(InputStream in, byte[] b, int off, int len)
        throws IOException {
        if (len < 0) {
            throw new IndexOutOfBoundsException();
        }

        int n = 0;

        while (n < len) {
            int count = in.read(b, off + n, len - n);

            if (count < 0) {
                throw new EOFException(Messages.getString("MysqlIO.EOF",
                		new Object[] {Integer.valueOf(len), Integer.valueOf(n)}));
            }

            n += count;
        }

        return n;
    }

    private final long skipFully(InputStream in, long len) throws IOException {
    	if (len < 0) {
    		throw new IOException("Negative skip length not allowed");
    	}

    	long n = 0;

    	while (n < len) {
    		long count = in.skip(len - n);

    		if (count < 0) {
    			throw new EOFException(Messages.getString("MysqlIO.EOF",
                		new Object[] {Long.valueOf(len), Long.valueOf(n)}));
    		}

    		n += count;
    	}

    	return n;
    }

    /**
     * Reads one result set off of the wire, if the result is actually an
     * update count, creates an update-count only result set.
     *
     * @param callingStatement DOCUMENT ME!
     * @param maxRows the maximum rows to return in the result set.
     * @param resultSetType scrollability
     * @param resultSetConcurrency updatability
     * @param streamResults should the driver leave the results on the wire,
     *        and read them only when needed?
     * @param catalog the catalog in use
     * @param resultPacket the first packet of information in the result set
     * @param isBinaryEncoded is this result set from a prepared statement?
     * @param preSentColumnCount do we already know the number of columns?
     * @param unpackFieldInfo should we unpack the field information?
     *
     * @return a result set that either represents the rows, or an update count
     *
     * @throws SQLException if an error occurs while reading the rows
     */
    protected final ResultSetImpl readResultsForQueryOrUpdate(
        StatementImpl callingStatement, int maxRows, int resultSetType,
        int resultSetConcurrency, boolean streamResults, String catalog,
        Buffer resultPacket, boolean isBinaryEncoded, long preSentColumnCount,
        Field[] metadataFromCache) throws SQLException {
        long columnCount = resultPacket.readFieldLength();

        if (columnCount == 0) {
            return buildResultSetWithUpdates(callingStatement, resultPacket);
        } else if (columnCount == Buffer.NULL_LENGTH) {
            String charEncoding = null;

            if (this.connection.getUseUnicode()) {
                charEncoding = this.connection.getEncoding();
            }

            String fileName = null;

            if (this.platformDbCharsetMatches) {
                fileName = ((charEncoding != null)
                    ? resultPacket.readString(charEncoding, getExceptionInterceptor())
                    : resultPacket.readString());
            } else {
                fileName = resultPacket.readString();
            }

            return sendFileToServer(callingStatement, fileName);
        } else {
            com.mysql.jdbc.ResultSetImpl results = getResultSet(callingStatement,
                    columnCount, maxRows, resultSetType, resultSetConcurrency,
                    streamResults, catalog, isBinaryEncoded,
                    metadataFromCache);

            return results;
        }
    }

    private int alignPacketSize(int a, int l) {
        return ((((a) + (l)) - 1) & ~((l) - 1));
    }

    private com.mysql.jdbc.ResultSetImpl buildResultSetWithRows(
        StatementImpl callingStatement, String catalog,
        com.mysql.jdbc.Field[] fields, RowData rows, int resultSetType,
        int resultSetConcurrency, boolean isBinaryEncoded)
        throws SQLException {
        ResultSetImpl rs = null;

        switch (resultSetConcurrency) {
        case java.sql.ResultSet.CONCUR_READ_ONLY:
            rs = com.mysql.jdbc.ResultSetImpl.getInstance(catalog, fields, rows,
                    this.connection, callingStatement, false);

            if (isBinaryEncoded) {
                rs.setBinaryEncoded();
            }

            break;

        case java.sql.ResultSet.CONCUR_UPDATABLE:
            rs = com.mysql.jdbc.ResultSetImpl.getInstance(catalog, fields, rows,
                    this.connection, callingStatement, true);

            break;

        default:
            return com.mysql.jdbc.ResultSetImpl.getInstance(catalog, fields, rows,
                this.connection, callingStatement, false);
        }

        rs.setResultSetType(resultSetType);
        rs.setResultSetConcurrency(resultSetConcurrency);

        return rs;
    }

    private com.mysql.jdbc.ResultSetImpl buildResultSetWithUpdates(
        StatementImpl callingStatement, Buffer resultPacket)
        throws SQLException {
        long updateCount = -1;
        long updateID = -1;
        String info = null;

        try {
            if (this.useNewUpdateCounts) {
                updateCount = resultPacket.newReadLength();
                updateID = resultPacket.newReadLength();
            } else {
                updateCount = resultPacket.readLength();
                updateID = resultPacket.readLength();
            }

            if (this.use41Extensions) {
            	// oldStatus set in sendCommand()
                this.serverStatus = resultPacket.readInt();

                checkTransactionState(oldServerStatus);
                
                this.warningCount = resultPacket.readInt();

                if (this.warningCount > 0) {
                    this.hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
                }

                resultPacket.readByte(); // advance pointer

                setServerSlowQueryFlags();
            }

            if (this.connection.isReadInfoMsgEnabled()) {
                info = resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
            }
        } catch (Exception ex) {
            SQLException sqlEx = SQLError.createSQLException(SQLError.get(
                    SQLError.SQL_STATE_GENERAL_ERROR), SQLError.SQL_STATE_GENERAL_ERROR, -1, getExceptionInterceptor());
            sqlEx.initCause(ex);
            
            throw sqlEx;
        }

        ResultSetInternalMethods updateRs = com.mysql.jdbc.ResultSetImpl.getInstance(updateCount,
                updateID, this.connection, callingStatement);

        if (info != null) {
            ((com.mysql.jdbc.ResultSetImpl)updateRs).setServerInfo(info);
        }

        return (com.mysql.jdbc.ResultSetImpl)updateRs;
    }

	private void setServerSlowQueryFlags() {
	    this.queryBadIndexUsed = (this.serverStatus &
	        SERVER_QUERY_NO_GOOD_INDEX_USED) != 0;
	    this.queryNoIndexUsed = (this.serverStatus &
	        SERVER_QUERY_NO_INDEX_USED) != 0;
	    this.serverQueryWasSlow = (this.serverStatus & 
	    	SERVER_QUERY_WAS_SLOW) != 0;
	}

    private void checkForOutstandingStreamingData() throws SQLException {
        if (this.streamingData != null) {
        	boolean shouldClobber = this.connection.getClobberStreamingResults();

            if (!shouldClobber) {
                throw SQLError.createSQLException(Messages.getString("MysqlIO.39") //$NON-NLS-1$
                     +this.streamingData +
                    Messages.getString("MysqlIO.40") //$NON-NLS-1$
                     +Messages.getString("MysqlIO.41") //$NON-NLS-1$
                     +Messages.getString("MysqlIO.42"), getExceptionInterceptor()); //$NON-NLS-1$
            }

            // Close the result set
            this.streamingData.getOwner().realClose(false);

            // clear any pending data....
            clearInputStream();
        }
    }

    /**
     * 
     * @param packet original uncompressed MySQL packet
     * @param offset begin of MySQL packet header
     * @param packetLen real length of packet
     * @return compressed packet with header
     * @throws SQLException
     */
	private Buffer compressPacket(Buffer packet, int offset, int packetLen) throws SQLException {

		// uncompressed payload by default
		int compressedLength = packetLen;
		int uncompressedLength = 0;
		byte[] compressedBytes = null;
		int offsetWrite = offset;

		if (packetLen < MIN_COMPRESS_LEN) {
			compressedBytes = packet.getByteBuffer();

		} else {
			byte[] bytesToCompress = packet.getByteBuffer();
			compressedBytes = new byte[bytesToCompress.length * 2];

			if (this.deflater == null) {
				this.deflater = new Deflater();
			}
            this.deflater.reset();
            this.deflater.setInput(bytesToCompress, offset, packetLen);
            this.deflater.finish();

            compressedLength = this.deflater.deflate(compressedBytes);

            if (compressedLength > packetLen) {
            	// if compressed data is greater then uncompressed then send uncompressed
                compressedBytes = packet.getByteBuffer();
            	compressedLength = packetLen;
            } else {
        		uncompressedLength = packetLen;
        		offsetWrite = 0;
            }
        }

        Buffer compressedPacket = new Buffer(HEADER_LENGTH + COMP_HEADER_LENGTH + compressedLength);

        compressedPacket.setPosition(0);
        compressedPacket.writeLongInt(compressedLength);
        compressedPacket.writeByte(this.compressedPacketSequence);
        compressedPacket.writeLongInt(uncompressedLength);
        compressedPacket.writeBytesNoNull(compressedBytes, offsetWrite, compressedLength);

        return compressedPacket;
    }

    private final void readServerStatusForResultSets(Buffer rowPacket)
        throws SQLException {
        if (this.use41Extensions) {
            rowPacket.readByte(); // skips the 'last packet' flag

            this.warningCount = rowPacket.readInt();

            if (this.warningCount > 0) {
                this.hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
            }

            this.oldServerStatus = this.serverStatus;
            this.serverStatus = rowPacket.readInt();
            checkTransactionState(oldServerStatus);

            setServerSlowQueryFlags();
        }
    }

    private SocketFactory createSocketFactory() throws SQLException {
        try {
            if (this.socketFactoryClassName == null) {
                throw SQLError.createSQLException(Messages.getString("MysqlIO.75"), //$NON-NLS-1$
                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
            }

            return (SocketFactory) (Class.forName(this.socketFactoryClassName)
                                         .newInstance());
        } catch (Exception ex) {
            SQLException sqlEx = SQLError.createSQLException(Messages.getString("MysqlIO.76") //$NON-NLS-1$
                 +this.socketFactoryClassName +
                Messages.getString("MysqlIO.77"),
                SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
            
            sqlEx.initCause(ex);
            
            throw sqlEx;
        }
    }

    private void enqueuePacketForDebugging(boolean isPacketBeingSent,
        boolean isPacketReused, int sendLength, byte[] header, Buffer packet)
        throws SQLException {
        if ((this.packetDebugRingBuffer.size() + 1) > this.connection.getPacketDebugBufferSize()) {
            this.packetDebugRingBuffer.removeFirst();
        }

        StringBuffer packetDump = null;

        if (!isPacketBeingSent) {
            int bytesToDump = Math.min(MAX_PACKET_DUMP_LENGTH,
                    packet.getBufLength());

            Buffer packetToDump = new Buffer(4 + bytesToDump);

            packetToDump.setPosition(0);
            packetToDump.writeBytesNoNull(header);
            packetToDump.writeBytesNoNull(packet.getBytes(0, bytesToDump));

            String packetPayload = packetToDump.dump(bytesToDump);

            packetDump = new StringBuffer(96 + packetPayload.length());

            packetDump.append("Server ");

            if (isPacketReused) {
                packetDump.append("(re-used)");
            } else {
                packetDump.append("(new)");
            }

            packetDump.append(" ");
            packetDump.append(packet.toSuperString());
            packetDump.append(" --------------------> Client\n");
            packetDump.append("\nPacket payload:\n\n");
            packetDump.append(packetPayload);

            if (bytesToDump == MAX_PACKET_DUMP_LENGTH) {
                packetDump.append("\nNote: Packet of " + packet.getBufLength() +
                    " bytes truncated to " + MAX_PACKET_DUMP_LENGTH +
                    " bytes.\n");
            }
        } else {
            int bytesToDump = Math.min(MAX_PACKET_DUMP_LENGTH, sendLength);

            String packetPayload = packet.dump(bytesToDump);

            packetDump = new StringBuffer(64 + 4 + packetPayload.length());

            packetDump.append("Client ");
            packetDump.append(packet.toSuperString());
            packetDump.append("--------------------> Server\n");
            packetDump.append("\nPacket payload:\n\n");
            packetDump.append(packetPayload);

            if (bytesToDump == MAX_PACKET_DUMP_LENGTH) {
                packetDump.append("\nNote: Packet of " + sendLength +
                    " bytes truncated to " + MAX_PACKET_DUMP_LENGTH +
                    " bytes.\n");
            }
        }

        this.packetDebugRingBuffer.addLast(packetDump);
    }

    private RowData readSingleRowSet(long columnCount, int maxRows,
        int resultSetConcurrency, boolean isBinaryEncoded, Field[] fields)
        throws SQLException {
        RowData rowData;
        ArrayList<ResultSetRow> rows = new ArrayList<ResultSetRow>();

        boolean useBufferRowExplicit = useBufferRowExplicit(fields);

        // Now read the data
        ResultSetRow row = nextRow(fields, (int) columnCount, isBinaryEncoded,
                resultSetConcurrency, false, useBufferRowExplicit, false, null);

        int rowCount = 0;

        if (row != null) {
            rows.add(row);
            rowCount = 1;
        }

        while (row != null) {
        	row = nextRow(fields, (int) columnCount, isBinaryEncoded,
                    resultSetConcurrency, false, useBufferRowExplicit, false, null);

            if (row != null) {
            	if ((maxRows == -1) || (rowCount < maxRows)) {
            		rows.add(row);
            		rowCount++;
            	}
            }
        }

        rowData = new RowDataStatic(rows);

        return rowData;
    }

    public static boolean useBufferRowExplicit(Field[] fields) {
    	if (fields == null) {
    		return false;
    	}

		for (int i = 0; i < fields.length; i++) {
			switch (fields[i].getSQLType()) {
			case Types.BLOB:
			case Types.CLOB:
			case Types.LONGVARBINARY:
			case Types.LONGVARCHAR:
				return true;
			}
		}

		return false;
	}

	/**
     * Don't hold on to overly-large packets
     */
    private void reclaimLargeReusablePacket() {
        if ((this.reusablePacket != null) &&
                (this.reusablePacket.getCapacity() > 1048576)) {
            this.reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
        }
    }

    /**
     * Re-use a packet to read from the MySQL server
     *
     * @param reuse DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     * @throws SQLException DOCUMENT ME!
     */
    private final Buffer reuseAndReadPacket(Buffer reuse) throws SQLException {
    	return reuseAndReadPacket(reuse, -1);
    }

    private final Buffer reuseAndReadPacket(Buffer reuse, int existingPacketLength)
        throws SQLException {

    	try {
    		reuse.setWasMultiPacket(false);
    		int packetLength = 0;

    		if (existingPacketLength == -1) {
	    		int lengthRead = readFully(this.mysqlInput,
	    				this.packetHeaderBuf, 0, 4);

	    		if (lengthRead < 4) {
	    			forceClose();
	    			throw new IOException(Messages.getString("MysqlIO.43")); //$NON-NLS-1$
	    		}

	    		packetLength = (this.packetHeaderBuf[0] & 0xff) +
	    		((this.packetHeaderBuf[1] & 0xff) << 8) +
	    		((this.packetHeaderBuf[2] & 0xff) << 16);
    		} else {
    			packetLength = existingPacketLength;
    		}

    		if (this.traceProtocol) {
    			StringBuffer traceMessageBuf = new StringBuffer();

    			traceMessageBuf.append(Messages.getString("MysqlIO.44")); //$NON-NLS-1$
    			traceMessageBuf.append(packetLength);
    			traceMessageBuf.append(Messages.getString("MysqlIO.45")); //$NON-NLS-1$
    			traceMessageBuf.append(StringUtils.dumpAsHex(
    					this.packetHeaderBuf, 4));

    			this.connection.getLog().logTrace(traceMessageBuf.toString());
    		}

    		byte multiPacketSeq = this.packetHeaderBuf[3];

    		if (!this.packetSequenceReset) {
    			if (this.enablePacketDebug && this.checkPacketSequence) {
    				checkPacketSequencing(multiPacketSeq);
    			}
    		} else {
    			this.packetSequenceReset = false;
    		}

    		this.readPacketSequence = multiPacketSeq;

    		// Set the Buffer to it's original state
    		reuse.setPosition(0);

    		// Do we need to re-alloc the byte buffer?
    		//
    		// Note: We actually check the length of the buffer,
    		// rather than getBufLength(), because getBufLength() is not
    		// necesarily the actual length of the byte array
    		// used as the buffer
    		if (reuse.getByteBuffer().length <= packetLength) {
    			reuse.setByteBuffer(new byte[packetLength + 1]);
    		}

    		// Set the new length
    		reuse.setBufLength(packetLength);

    		// Read the data from the server
    		int numBytesRead = readFully(this.mysqlInput,
    				reuse.getByteBuffer(), 0, packetLength);

    		if (numBytesRead != packetLength) {
    			throw new IOException("Short read, expected " +
    					packetLength + " bytes, only read " + numBytesRead);
    		}

    		if (this.traceProtocol) {
    			StringBuffer traceMessageBuf = new StringBuffer();

    			traceMessageBuf.append(Messages.getString("MysqlIO.46")); //$NON-NLS-1$
    			traceMessageBuf.append(getPacketDumpToLog(reuse,
    					packetLength));

    			this.connection.getLog().logTrace(traceMessageBuf.toString());
    		}

    		if (this.enablePacketDebug) {
    			enqueuePacketForDebugging(false, true, 0,
    					this.packetHeaderBuf, reuse);
    		}

    		boolean isMultiPacket = false;

    		if (packetLength == this.maxThreeBytes) {
    			reuse.setPosition(this.maxThreeBytes);

    			// it's multi-packet
    			isMultiPacket = true;

    			packetLength = readRemainingMultiPackets(reuse, multiPacketSeq);
    		}

    		if (!isMultiPacket) {
    			reuse.getByteBuffer()[packetLength] = 0; // Null-termination
    		}

    		if (this.connection.getMaintainTimeStats()) {
 				this.lastPacketReceivedTimeMs = System.currentTimeMillis();
 			}
    		 
    		return reuse;
    	} catch (IOException ioEx) {
    		throw SQLError.createCommunicationsException(this.connection,
    				this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
    	} catch (OutOfMemoryError oom) {
    		try {
    			// _Try_ this
    			clearInputStream();
        	} catch (Exception ex) {
        	}
   			try {
   				this.connection.realClose(false, false, true, oom);
        	} catch (Exception ex) {
   			}
			throw oom;
    	}

    }

	private int readRemainingMultiPackets(Buffer reuse, byte multiPacketSeq) 
		throws IOException, SQLException {
		int lengthRead;
		int packetLength;
		lengthRead = readFully(this.mysqlInput,
				this.packetHeaderBuf, 0, 4);

		if (lengthRead < 4) {
			forceClose();
			throw new IOException(Messages.getString("MysqlIO.47")); //$NON-NLS-1$
		}

		packetLength = (this.packetHeaderBuf[0] & 0xff) +
			((this.packetHeaderBuf[1] & 0xff) << 8) +
			((this.packetHeaderBuf[2] & 0xff) << 16);

		Buffer multiPacket = new Buffer(packetLength);
		boolean firstMultiPkt = true;

		while (true) {
			if (!firstMultiPkt) {
				lengthRead = readFully(this.mysqlInput,
						this.packetHeaderBuf, 0, 4);

				if (lengthRead < 4) {
					forceClose();
					throw new IOException(Messages.getString(
					"MysqlIO.48")); //$NON-NLS-1$
				}

				packetLength = (this.packetHeaderBuf[0] & 0xff) +
				((this.packetHeaderBuf[1] & 0xff) << 8) +
				((this.packetHeaderBuf[2] & 0xff) << 16);
			} else {
				firstMultiPkt = false;
			}

			if (!this.useNewLargePackets && (packetLength == 1)) {
				clearInputStream();

				break;
			} else if (packetLength < this.maxThreeBytes) {
				byte newPacketSeq = this.packetHeaderBuf[3];

				if (newPacketSeq != (multiPacketSeq + 1)) {
					throw new IOException(Messages.getString(
					"MysqlIO.49")); //$NON-NLS-1$
				}

				multiPacketSeq = newPacketSeq;

				// Set the Buffer to it's original state
				multiPacket.setPosition(0);

				// Set the new length
				multiPacket.setBufLength(packetLength);

				// Read the data from the server
				byte[] byteBuf = multiPacket.getByteBuffer();
				int lengthToWrite = packetLength;

				int bytesRead = readFully(this.mysqlInput, byteBuf,
						0, packetLength);

				if (bytesRead != lengthToWrite) {
					throw SQLError.createCommunicationsException(this.connection,
							this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
							SQLError.createSQLException(Messages.getString(
							"MysqlIO.50") //$NON-NLS-1$
							+lengthToWrite +
							Messages.getString("MysqlIO.51") +
							bytesRead //$NON-NLS-1$
							+".", getExceptionInterceptor()), getExceptionInterceptor()); //$NON-NLS-1$
				}

				reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);

				break; // end of multipacket sequence
			}

			byte newPacketSeq = this.packetHeaderBuf[3];

			if (newPacketSeq != (multiPacketSeq + 1)) {
				throw new IOException(Messages.getString(
				"MysqlIO.53")); //$NON-NLS-1$
			}

			multiPacketSeq = newPacketSeq;

			// Set the Buffer to it's original state
			multiPacket.setPosition(0);

			// Set the new length
			multiPacket.setBufLength(packetLength);

			// Read the data from the server
			byte[] byteBuf = multiPacket.getByteBuffer();
			int lengthToWrite = packetLength;

			int bytesRead = readFully(this.mysqlInput, byteBuf, 0,
					packetLength);

			if (bytesRead != lengthToWrite) {
				throw SQLError.createCommunicationsException(this.connection,
						this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
						SQLError.createSQLException(Messages.getString(
						"MysqlIO.54") //$NON-NLS-1$
						+lengthToWrite +
						Messages.getString("MysqlIO.55") //$NON-NLS-1$
						+bytesRead + ".", getExceptionInterceptor()), getExceptionInterceptor()); //$NON-NLS-1$
			}

			reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
		}

		reuse.setPosition(0);
		reuse.setWasMultiPacket(true);
		return packetLength;
	}

    /**
         * @param multiPacketSeq
         * @throws CommunicationsException
         */
    private void checkPacketSequencing(byte multiPacketSeq)
        throws SQLException {
        if ((multiPacketSeq == -128) && (this.readPacketSequence != 127)) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
                new IOException("Packets out of order, expected packet # -128, but received packet # " +
                    multiPacketSeq), getExceptionInterceptor());
        }

        if ((this.readPacketSequence == -1) && (multiPacketSeq != 0)) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
                new IOException("Packets out of order, expected packet # -1, but received packet # " +
                    multiPacketSeq), getExceptionInterceptor());
        }

        if ((multiPacketSeq != -128) && (this.readPacketSequence != -1) &&
                (multiPacketSeq != (this.readPacketSequence + 1))) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
                new IOException("Packets out of order, expected packet # " +
                    (this.readPacketSequence + 1) + ", but received packet # " +
                    multiPacketSeq), getExceptionInterceptor());
        }
    }

    void enableMultiQueries() throws SQLException {
    	Buffer buf = getSharedSendPacket();

    	buf.clear();
    	buf.writeByte((byte)MysqlDefs.COM_SET_OPTION);
    	buf.writeInt(0);
    	sendCommand(MysqlDefs.COM_SET_OPTION, null, buf, false, null, 0);
    }

    void disableMultiQueries() throws SQLException {
    	Buffer buf = getSharedSendPacket();

    	buf.clear();
    	buf.writeByte((byte)MysqlDefs.COM_SET_OPTION);
    	buf.writeInt(1);
    	sendCommand(MysqlDefs.COM_SET_OPTION, null, buf, false, null, 0);
    }

    /**
     * 
     * @param packet
     * @param packetLen length of header + payload
     * @throws SQLException
     */
    private final void send(Buffer packet, int packetLen)
        throws SQLException {
        try {
            if (this.maxAllowedPacket > 0 && packetLen > this.maxAllowedPacket) {
                throw new PacketTooBigException(packetLen, this.maxAllowedPacket);
            }

            if ((this.serverMajorVersion >= 4) &&
            		(packetLen - HEADER_LENGTH >= this.maxThreeBytes ||
            		(this.useCompression && packetLen - HEADER_LENGTH >= this.maxThreeBytes - COMP_HEADER_LENGTH)
            		)) {
                sendSplitPackets(packet, packetLen);

            } else {
                this.packetSequence++;

                Buffer packetToSend = packet;
                packetToSend.setPosition(0);
                packetToSend.writeLongInt(packetLen - HEADER_LENGTH);
                packetToSend.writeByte(this.packetSequence);

                if (this.useCompression) {
                	this.compressedPacketSequence++;
                    int originalPacketLen = packetLen;

                    packetToSend = compressPacket(packetToSend, 0, packetLen);
                    packetLen = packetToSend.getPosition();

                    if (this.traceProtocol) {
                        StringBuffer traceMessageBuf = new StringBuffer();

                        traceMessageBuf.append(Messages.getString("MysqlIO.57")); //$NON-NLS-1$
                        traceMessageBuf.append(getPacketDumpToLog(
                                packetToSend, packetLen));
                        traceMessageBuf.append(Messages.getString("MysqlIO.58")); //$NON-NLS-1$
                        traceMessageBuf.append(getPacketDumpToLog(packet,
                                originalPacketLen));

                        this.connection.getLog().logTrace(traceMessageBuf.toString());
                    }
                } else {

                    if (this.traceProtocol) {
                        StringBuffer traceMessageBuf = new StringBuffer();

                        traceMessageBuf.append(Messages.getString("MysqlIO.59")); //$NON-NLS-1$
                        traceMessageBuf.append("host: '");
                        traceMessageBuf.append(host);
                        traceMessageBuf.append("' threadId: '");
                        traceMessageBuf.append(threadId);
                        traceMessageBuf.append("'\n");
                        traceMessageBuf.append(packetToSend.dump(packetLen));

                        this.connection.getLog().logTrace(traceMessageBuf.toString());
                    }
                }

                this.mysqlOutput.write(packetToSend.getByteBuffer(), 0, packetLen);
                this.mysqlOutput.flush();
            }

            if (this.enablePacketDebug) {
                enqueuePacketForDebugging(true, false, packetLen + 5,
                    this.packetHeaderBuf, packet);
            }

            //
            // Don't hold on to large packets
            //
            if (packet == this.sharedSendPacket) {
                reclaimLargeSharedSendPacket();
            }
            
            if (this.connection.getMaintainTimeStats()) {
				this.lastPacketSentTimeMs = System.currentTimeMillis();
			}
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        }
    }

    /**
     * Reads and sends a file to the server for LOAD DATA LOCAL INFILE
     *
     * @param callingStatement DOCUMENT ME!
     * @param fileName the file name to send.
     *
     * @return DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     */
    private final ResultSetImpl sendFileToServer(StatementImpl callingStatement,
        String fileName) throws SQLException {

        if (this.useCompression) {
        	this.compressedPacketSequence++;
        }

        Buffer filePacket = (this.loadFileBufRef == null) ? null
                                                          : this.loadFileBufRef.get();

        int bigPacketLength = Math.min(this.connection.getMaxAllowedPacket() -
                (HEADER_LENGTH * 3),
                alignPacketSize(this.connection.getMaxAllowedPacket() - 16, 4096) -
                (HEADER_LENGTH * 3));

        int oneMeg = 1024 * 1024;

        int smallerPacketSizeAligned = Math.min(oneMeg - (HEADER_LENGTH * 3),
        		alignPacketSize(oneMeg - 16, 4096) - (HEADER_LENGTH * 3));

        int packetLength = Math.min(smallerPacketSizeAligned, bigPacketLength);

        if (filePacket == null) {
        	try {
        		filePacket = new Buffer((packetLength + HEADER_LENGTH));
        		this.loadFileBufRef = new SoftReference<Buffer>(filePacket);
        	} catch (OutOfMemoryError oom) {
        		throw SQLError.createSQLException("Could not allocate packet of " + packetLength
        				+ " bytes required for LOAD DATA LOCAL INFILE operation."
						+ " Try increasing max heap allocation for JVM or decreasing server variable "
						+ "'max_allowed_packet'", SQLError.SQL_STATE_MEMORY_ALLOCATION_FAILURE, getExceptionInterceptor());

        	}
        }

        filePacket.clear();
        send(filePacket, 0);

        byte[] fileBuf = new byte[packetLength];

        BufferedInputStream fileIn = null;

        try {
        	if (!this.connection.getAllowLoadLocalInfile()) {
        		throw SQLError.createSQLException(
        				Messages.getString("MysqlIO.LoadDataLocalNotAllowed"),
        				SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
        	}

        	InputStream hookedStream = null;

        	if (callingStatement != null) {
        	    hookedStream = callingStatement.getLocalInfileInputStream();
        	}

        	if (hookedStream != null) {
        	    fileIn = new BufferedInputStream(hookedStream);
        	} else if (!this.connection.getAllowUrlInLocalInfile()) {
                fileIn = new BufferedInputStream(new FileInputStream(fileName));
            } else {
                // First look for ':'
                if (fileName.indexOf(':') != -1) {
                    try {
                        URL urlFromFileName = new URL(fileName);
                        fileIn = new BufferedInputStream(urlFromFileName.openStream());
                    } catch (MalformedURLException badUrlEx) {
                        // we fall back to trying this as a file input stream
                        fileIn = new BufferedInputStream(new FileInputStream(
                                    fileName));
                    }
                } else {
                    fileIn = new BufferedInputStream(new FileInputStream(
                                fileName));
                }
            }

            int bytesRead = 0;

            while ((bytesRead = fileIn.read(fileBuf)) != -1) {
                filePacket.clear();
                filePacket.writeBytesNoNull(fileBuf, 0, bytesRead);
                send(filePacket, filePacket.getPosition());
            }
        } catch (IOException ioEx) {
            StringBuffer messageBuf = new StringBuffer(Messages.getString(
                        "MysqlIO.60")); //$NON-NLS-1$

            if (!this.connection.getParanoid()) {
                messageBuf.append("'"); //$NON-NLS-1$

                if (fileName != null) {
                    messageBuf.append(fileName);
                }

                messageBuf.append("'"); //$NON-NLS-1$
            }

            messageBuf.append(Messages.getString("MysqlIO.63")); //$NON-NLS-1$

            if (!this.connection.getParanoid()) {
                messageBuf.append(Messages.getString("MysqlIO.64")); //$NON-NLS-1$
                messageBuf.append(Util.stackTraceToString(ioEx));
            }

            throw SQLError.createSQLException(messageBuf.toString(),
                SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        } finally {
            if (fileIn != null) {
                try {
                    fileIn.close();
                } catch (Exception ex) {
                    SQLException sqlEx = SQLError.createSQLException(Messages.getString("MysqlIO.65"), //$NON-NLS-1$
                        SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
                    sqlEx.initCause(ex);
                    
                    throw sqlEx;
                }

                fileIn = null;
            } else {
                // file open failed, but server needs one packet
                filePacket.clear();
                send(filePacket, filePacket.getPosition());
                checkErrorPacket(); // to clear response off of queue
            }
        }

        // send empty packet to mark EOF
        filePacket.clear();
        send(filePacket, filePacket.getPosition());

        Buffer resultPacket = checkErrorPacket();

        return buildResultSetWithUpdates(callingStatement, resultPacket);
    }

    /**
     * Checks for errors in the reply packet, and if none, returns the reply
     * packet, ready for reading
     *
     * @param command the command being issued (if used)
     *
     * @return DOCUMENT ME!
     *
     * @throws SQLException if an error packet was received
     * @throws CommunicationsException DOCUMENT ME!
     */
    private Buffer checkErrorPacket(int command) throws SQLException {
        //int statusCode = 0;
        Buffer resultPacket = null;
        this.serverStatus = 0;

        try {
            // Check return value, if we get a java.io.EOFException,
            // the server has gone away. We'll pass it on up the
            // exception chain and let someone higher up decide
            // what to do (barf, reconnect, etc).
            resultPacket = reuseAndReadPacket(this.reusablePacket);
        } catch (SQLException sqlEx) {
            // Don't wrap SQL Exceptions
            throw sqlEx;
        } catch (Exception fallThru) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, fallThru, getExceptionInterceptor());
        }

        checkErrorPacket(resultPacket);

        return resultPacket;
    }

    private void checkErrorPacket(Buffer resultPacket) throws SQLException {

        int statusCode = resultPacket.readByte();

        // Error handling
        if (statusCode == (byte) 0xff) {
            String serverErrorMessage;
            int errno = 2000;

            if (this.protocolVersion > 9) {
                errno = resultPacket.readInt();

                String xOpen = null;

                serverErrorMessage =
                	resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());

                if (serverErrorMessage.charAt(0) == '#') { //$NON-NLS-1$

                    // we have an SQLState
                    if (serverErrorMessage.length() > 6) {
                        xOpen = serverErrorMessage.substring(1, 6);
                        serverErrorMessage = serverErrorMessage.substring(6);

                        if (xOpen.equals("HY000")) { //$NON-NLS-1$
                            xOpen = SQLError.mysqlToSqlState(errno,
                                    this.connection.getUseSqlStateCodes());
                        }
                    } else {
                        xOpen = SQLError.mysqlToSqlState(errno,
                                this.connection.getUseSqlStateCodes());
                    }
                } else {
                    xOpen = SQLError.mysqlToSqlState(errno,
                            this.connection.getUseSqlStateCodes());
                }

                clearInputStream();

                StringBuffer errorBuf = new StringBuffer();

                String xOpenErrorMessage = SQLError.get(xOpen);

                if (!this.connection.getUseOnlyServerErrorMessages()) {
                    if (xOpenErrorMessage != null) {
                        errorBuf.append(xOpenErrorMessage);
                        errorBuf.append(Messages.getString("MysqlIO.68")); //$NON-NLS-1$
                    }
                }

                errorBuf.append(serverErrorMessage);

                if (!this.connection.getUseOnlyServerErrorMessages()) {
                    if (xOpenErrorMessage != null) {
                        errorBuf.append("\""); //$NON-NLS-1$
                    }
                }

                appendDeadlockStatusInformation(xOpen, errorBuf);

                if (xOpen != null && xOpen.startsWith("22")) {
                	throw new MysqlDataTruncation(errorBuf.toString(), 0, true, false, 0, 0, errno);
                }
                throw SQLError.createSQLException(errorBuf.toString(), xOpen, errno, false, getExceptionInterceptor(), this.connection);

            }

            serverErrorMessage = resultPacket.readString(
            		this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
            clearInputStream();

            if (serverErrorMessage.indexOf(Messages.getString("MysqlIO.70")) != -1) { //$NON-NLS-1$
                throw SQLError.createSQLException(SQLError.get(
                        SQLError.SQL_STATE_COLUMN_NOT_FOUND) +
                    ", " //$NON-NLS-1$
                     +serverErrorMessage, SQLError.SQL_STATE_COLUMN_NOT_FOUND,
                    -1, false, getExceptionInterceptor(), this.connection);
            }

            StringBuffer errorBuf = new StringBuffer(Messages.getString(
                        "MysqlIO.72")); //$NON-NLS-1$
            errorBuf.append(serverErrorMessage);
            errorBuf.append("\""); //$NON-NLS-1$

            throw SQLError.createSQLException(SQLError.get(
                    SQLError.SQL_STATE_GENERAL_ERROR) + ", " //$NON-NLS-1$
                 +errorBuf.toString(), SQLError.SQL_STATE_GENERAL_ERROR, -1, false, getExceptionInterceptor(), this.connection);
        }
    }

    private void appendDeadlockStatusInformation(String xOpen,
			StringBuffer errorBuf) throws SQLException {
		if (this.connection.getIncludeInnodbStatusInDeadlockExceptions()
				&& xOpen != null
				&& (xOpen.startsWith("40") || xOpen.startsWith("41"))
				&& this.streamingData == null) {
			ResultSet rs = null;

			try {
				rs = sqlQueryDirect(null, "SHOW ENGINE INNODB STATUS",
						this.connection.getEncoding(), null, -1,
						ResultSet.TYPE_FORWARD_ONLY,
						ResultSet.CONCUR_READ_ONLY, false, this.connection
								.getCatalog(), null);

				if (rs.next()) {
					errorBuf.append("\n\n");
					errorBuf.append(rs.getString("Status"));
				} else {
					errorBuf.append("\n\n");
					errorBuf.append(Messages
							.getString("MysqlIO.NoInnoDBStatusFound"));
				}
			} catch (Exception ex) {
				errorBuf.append("\n\n");
				errorBuf.append(Messages
						.getString("MysqlIO.InnoDBStatusFailed"));
				errorBuf.append("\n\n");
				errorBuf.append(Util.stackTraceToString(ex));
			} finally {
				if (rs != null) {
					rs.close();
				}
			}
		}
		
		if (this.connection.getIncludeThreadDumpInDeadlockExceptions()) {
			errorBuf.append("\n\n*** Java threads running at time of deadlock ***\n\n");
			
			ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
			long[] threadIds = threadMBean.getAllThreadIds();

			ThreadInfo[] threads = threadMBean.getThreadInfo(threadIds,
					Integer.MAX_VALUE);
			List<ThreadInfo> activeThreads = new ArrayList<ThreadInfo>();

			for (ThreadInfo info : threads) {
				if (info != null) {
					activeThreads.add(info);
				}
			}

			for (ThreadInfo threadInfo : activeThreads) {
				// "Thread-60" daemon prio=1 tid=0x093569c0 nid=0x1b99 in
				// Object.wait()

				errorBuf.append('"');
				errorBuf.append(threadInfo.getThreadName());
				errorBuf.append("\" tid=");
				errorBuf.append(threadInfo.getThreadId());
				errorBuf.append(" ");
				errorBuf.append(threadInfo.getThreadState());

				if (threadInfo.getLockName() != null) {
					errorBuf.append(" on lock=" + threadInfo.getLockName());
				}
				if (threadInfo.isSuspended()) {
					errorBuf.append(" (suspended)");
				}
				if (threadInfo.isInNative()) {
					errorBuf.append(" (running in native)");
				}

				StackTraceElement[] stackTrace = threadInfo.getStackTrace();

				if (stackTrace.length > 0) {
					errorBuf.append(" in ");
					errorBuf.append(stackTrace[0].getClassName());
					errorBuf.append(".");
					errorBuf.append(stackTrace[0].getMethodName());
					errorBuf.append("()");
				}

				errorBuf.append("\n");

				if (threadInfo.getLockOwnerName() != null) {
					errorBuf.append("\t owned by " + threadInfo.getLockOwnerName()
							+ " Id=" + threadInfo.getLockOwnerId());
					errorBuf.append("\n");
				}

				for (int j = 0; j < stackTrace.length; j++) {
					StackTraceElement ste = stackTrace[j];
					errorBuf.append("\tat " + ste.toString());
					errorBuf.append("\n");
				}
			}
		}
	}

    /**
     * Sends a large packet to the server as a series of smaller packets
     *
     * @param packet DOCUMENT ME!
     *
     * @throws SQLException DOCUMENT ME!
     * @throws CommunicationsException DOCUMENT ME!
     */
    private final void sendSplitPackets(Buffer packet, int packetLen)
        throws SQLException {
        try {
            Buffer packetToSend = (this.splitBufRef == null) ? null : this.splitBufRef.get();
            Buffer toCompress = (!this.useCompression || this.compressBufRef == null) ? null : this.compressBufRef.get();

            //
            // Store this packet in a soft reference...It can be re-used if not GC'd (so clients
            // that use it frequently won't have to re-alloc the 16M buffer), but we don't
            // penalize infrequent users of large packets by keeping 16M allocated all of the time
            //
            if (packetToSend == null) {
                packetToSend = new Buffer((this.maxThreeBytes + HEADER_LENGTH));
                this.splitBufRef = new SoftReference<Buffer>(packetToSend);
            }
            if (this.useCompression) {
            	int cbuflen = packetLen + ((packetLen / this.maxThreeBytes) + 1) * HEADER_LENGTH;
            	if (toCompress == null) {
                	toCompress = new Buffer(cbuflen);
            	} else if (toCompress.getBufLength() < cbuflen) {
            		toCompress.setPosition(toCompress.getBufLength());
            		toCompress.ensureCapacity(cbuflen - toCompress.getBufLength());
            	}
            }

            int len = packetLen - HEADER_LENGTH; // payload length left
            int splitSize = this.maxThreeBytes;
            int originalPacketPos = HEADER_LENGTH;
            byte[] origPacketBytes = packet.getByteBuffer();

            int toCompressPosition = 0;
            
            // split to MySQL packets
            while (len >= 0) {
                this.packetSequence++;

                if (len < splitSize) {
                	splitSize = len;
                }
                
                packetToSend.setPosition(0);
                packetToSend.writeLongInt(splitSize);
                packetToSend.writeByte(this.packetSequence);
                if (len > 0) {
                    System.arraycopy(origPacketBytes, originalPacketPos, packetToSend.getByteBuffer(), HEADER_LENGTH, splitSize);
                }
                
                if (this.useCompression) {
                	System.arraycopy(packetToSend.getByteBuffer(), 0, toCompress.getByteBuffer(), toCompressPosition, HEADER_LENGTH + splitSize);
                	toCompressPosition += HEADER_LENGTH + splitSize;
                } else {
                    this.mysqlOutput.write(packetToSend.getByteBuffer(), 0, HEADER_LENGTH + splitSize);
                    this.mysqlOutput.flush();
                }

                originalPacketPos += splitSize;
                len -= this.maxThreeBytes;
                
            }

            // split to compressed packets
			if (this.useCompression) {
				len = toCompressPosition;
				toCompressPosition = 0;
				splitSize = this.maxThreeBytes- COMP_HEADER_LENGTH;
                while (len >= 0) {
                	this.compressedPacketSequence++;

                	if (len < splitSize) {
                    	splitSize = len;
                    }

                    Buffer compressedPacketToSend = compressPacket(toCompress, toCompressPosition, splitSize);
                    packetLen = compressedPacketToSend.getPosition();
                    this.mysqlOutput.write(compressedPacketToSend.getByteBuffer(), 0, packetLen);
                    this.mysqlOutput.flush();


                    toCompressPosition += splitSize;
                    len -= (this.maxThreeBytes-COMP_HEADER_LENGTH);
                }
            }
        } catch (IOException ioEx) {
            throw SQLError.createCommunicationsException(this.connection,
                this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
        }
    }

    private void reclaimLargeSharedSendPacket() {
        if ((this.sharedSendPacket != null) &&
                (this.sharedSendPacket.getCapacity() > 1048576)) {
            this.sharedSendPacket = new Buffer(INITIAL_PACKET_SIZE);
        }
    }

    boolean hadWarnings() {
    	return this.hadWarnings;
    }

    void scanForAndThrowDataTruncation() throws SQLException {
        if ((this.streamingData == null) && versionMeetsMinimum(4, 1, 0) &&
                this.connection.getJdbcCompliantTruncation() && this.warningCount > 0) {
            SQLError.convertShowWarningsToSQLWarnings(this.connection,
                this.warningCount, true);
        }
    }

    /**
     * Secure authentication for 4.1 and newer servers.
     *
     * @param packet DOCUMENT ME!
     * @param packLength
     * @param user
     * @param password
     * @param database DOCUMENT ME!
     * @param writeClientParams
     *
     * @throws SQLException
     */
    private void secureAuth(Buffer packet, int packLength, String user,
        String password, String database, boolean writeClientParams)
        throws SQLException {
        // Passwords can be 16 chars long
        if (packet == null) {
            packet = new Buffer(packLength);
        }

        if (writeClientParams) {
            if (this.use41Extensions) {
                if (versionMeetsMinimum(4, 1, 1)) {
                    packet.writeLong(this.clientParam);
                    packet.writeLong(this.maxThreeBytes);

                    // charset, JDBC will connect as 'latin1',
                    // and use 'SET NAMES' to change to the desired
                    // charset after the connection is established.
                    packet.writeByte((byte) 8);

                    // Set of bytes reserved for future use.
                    packet.writeBytesNoNull(new byte[23]);
                } else {
                    packet.writeLong(this.clientParam);
                    packet.writeLong(this.maxThreeBytes);
                }
            } else {
                packet.writeInt((int) this.clientParam);
                packet.writeLongInt(this.maxThreeBytes);
            }
        }

        // User/Password data
        packet.writeString(user, CODE_PAGE_1252, this.connection);

        if (password.length() != 0) {
            /* Prepare false scramble  */
            packet.writeString(FALSE_SCRAMBLE, CODE_PAGE_1252, this.connection);
        } else {
            /* For empty password*/
            packet.writeString("", CODE_PAGE_1252, this.connection); //$NON-NLS-1$
        }

        if (this.useConnectWithDb) {
            packet.writeString(database, CODE_PAGE_1252, this.connection);
        }

        send(packet, packet.getPosition());

        //
        // Don't continue stages if password is empty
        //
        if (password.length() > 0) {
            Buffer b = readPacket();

            b.setPosition(0);

            byte[] replyAsBytes = b.getByteBuffer();

            if ((replyAsBytes.length == 25) && (replyAsBytes[0] != 0)) {
                // Old passwords will have '*' at the first byte of hash */
                if (replyAsBytes[0] != '*') {
                    try {
                        /* Build full password hash as it is required to decode scramble */
                        byte[] buff = Security.passwordHashStage1(password);

                        /* Store copy as we'll need it later */
                        byte[] passwordHash = new byte[buff.length];
                        System.arraycopy(buff, 0, passwordHash, 0, buff.length);

                        /* Finally hash complete password using hash we got from server */
                        passwordHash = Security.passwordHashStage2(passwordHash,
                                replyAsBytes);

                        byte[] packetDataAfterSalt = new byte[replyAsBytes.length -
                            5];

                        System.arraycopy(replyAsBytes, 4, packetDataAfterSalt,
                            0, replyAsBytes.length - 5);

                        byte[] mysqlScrambleBuff = new byte[20];

                        /* Decypt and store scramble 4 = hash for stage2 */
                        Security.passwordCrypt(packetDataAfterSalt,
                            mysqlScrambleBuff, passwordHash, 20);

                        /* Encode scramble with password. Recycle buffer */
                        Security.passwordCrypt(mysqlScrambleBuff, buff, buff, 20);

                        Buffer packet2 = new Buffer(25);
                        packet2.writeBytesNoNull(buff);

                        this.packetSequence++;

                        send(packet2, 24);
                    } catch (NoSuchAlgorithmException nse) {
                        throw SQLError.createSQLException(Messages.getString("MysqlIO.91") //$NON-NLS-1$
                             +Messages.getString("MysqlIO.92"), //$NON-NLS-1$
                            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
                    }
                } else {
                    try {
                        /* Create password to decode scramble */
                        byte[] passwordHash = Security.createKeyFromOldPassword(password);

                        /* Decypt and store scramble 4 = hash for stage2 */
                        byte[] netReadPos4 = new byte[replyAsBytes.length - 5];

                        System.arraycopy(replyAsBytes, 4, netReadPos4, 0,
                            replyAsBytes.length - 5);

                        byte[] mysqlScrambleBuff = new byte[20];

                        /* Decypt and store scramble 4 = hash for stage2 */
                        Security.passwordCrypt(netReadPos4, mysqlScrambleBuff,
                            passwordHash, 20);

                        /* Finally scramble decoded scramble with password */
                        String scrambledPassword = Util.scramble(StringUtils.toString(
                                    mysqlScrambleBuff), password);

                        Buffer packet2 = new Buffer(packLength);
                        packet2.writeString(scrambledPassword, CODE_PAGE_1252, this.connection);
                        this.packetSequence++;

                        send(packet2, 24);
                    } catch (NoSuchAlgorithmException nse) {
                        throw SQLError.createSQLException(Messages.getString("MysqlIO.93") //$NON-NLS-1$
                             +Messages.getString("MysqlIO.94"), //$NON-NLS-1$
                            SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
                    }
                }
            }
        }
    }

    /**
     * Secure authentication for 4.1.1 and newer servers.
     *
     * @param packet DOCUMENT ME!
     * @param packLength
     * @param user
     * @param password
     * @param database DOCUMENT ME!
     * @param writeClientParams
     *
     * @throws SQLException
     */
    void secureAuth411(Buffer packet, int packLength, String user,
        String password, String database, boolean writeClientParams)
        throws SQLException {
        //	SERVER:  public_seed=create_random_string()
        //			 send(public_seed)
        //
        //	CLIENT:  recv(public_seed)
        //			 hash_stage1=sha1("password")
        //			 hash_stage2=sha1(hash_stage1)
        //			 reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
        //
        //			 // this three steps are done in scramble()
        //
        //			 send(reply)
        //
        //
        //	SERVER:  recv(reply)
        //			 hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
        //			 candidate_hash2=sha1(hash_stage1)
        //			 check(candidate_hash2==hash_stage2)
        // Passwords can be 16 chars long
        if (packet == null) {
            packet = new Buffer(packLength);
        }

        if (writeClientParams) {
            if (this.use41Extensions) {
                if (versionMeetsMinimum(4, 1, 1)) {
                    packet.writeLong(this.clientParam);
                    packet.writeLong(this.maxThreeBytes);

                    // charset, JDBC will connect as 'utf8',
                    // and use 'SET NAMES' to change to the desired
                    // charset after the connection is established.
                    packet.writeByte((byte) UTF8_CHARSET_INDEX);

                    // Set of bytes reserved for future use.
                    packet.writeBytesNoNull(new byte[23]);
                } else {
                    packet.writeLong(this.clientParam);
                    packet.writeLong(this.maxThreeBytes);
                }
            } else {
                packet.writeInt((int) this.clientParam);
                packet.writeLongInt(this.maxThreeBytes);
            }
        }

        // User/Password data
        packet.writeString(user, "utf-8", this.connection);

        if (password.length() != 0) {
            packet.writeByte((byte) 0x14);

            try {
                packet.writeBytesNoNull(Security.scramble411(password, this.seed,
															 this.connection.getPasswordCharacterEncoding()));
            } catch (NoSuchAlgorithmException nse) {
                throw SQLError.createSQLException(Messages.getString("MysqlIO.95") //$NON-NLS-1$
                     +Messages.getString("MysqlIO.96"), //$NON-NLS-1$
                    SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
            } catch (UnsupportedEncodingException e) {
            	throw SQLError.createSQLException(Messages.getString("MysqlIO.95") //$NON-NLS-1$
                        +Messages.getString("MysqlIO.96"), //$NON-NLS-1$
                       SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
			}
        } else {
            /* For empty password*/
            packet.writeByte((byte) 0);
        }

        if (this.useConnectWithDb) {
            packet.writeString(database, "utf-8", this.connection);
        } else {
            /* For empty database*/
            packet.writeByte((byte) 0);
        }
                    
        if ((this.serverCapabilities & CLIENT_PROTOCOL_41) != 0) {
			// charset (2 bytes low-endian)
			String mysqlEncodingName = this.connection.getServerCharacterEncoding();
			if ("ucs2".equalsIgnoreCase(mysqlEncodingName) ||
					"utf16".equalsIgnoreCase(mysqlEncodingName) ||
					"utf16le".equalsIgnoreCase(mysqlEncodingName) ||
					"uft32".equalsIgnoreCase(mysqlEncodingName)) {
				packet.writeByte((byte) UTF8_CHARSET_INDEX);
				packet.writeByte((byte) 0);
			}
        }
        
		// connection attributes
		if ((this.serverCapabilities & CLIENT_CONNECT_ATTRS) != 0) {
			sendConnectionAttributes(packet, "utf-8", this.connection);
		}

        send(packet, packet.getPosition());

        byte savePacketSequence = this.packetSequence++;

        Buffer reply = checkErrorPacket();

        if (reply.isLastDataPacket()) {
            /*
                  By sending this very specific reply server asks us to send scrambled
                  password in old format. The reply contains scramble_323.
            */
            this.packetSequence = ++savePacketSequence;
            packet.clear();

            String seed323 = this.seed.substring(0, 8);
            packet.writeString(Util.newCrypt(password, seed323));
            send(packet, packet.getPosition());

            /* Read what server thinks about out new auth message report */
            checkErrorPacket();
        }
    }

    /**
     * Un-packs binary-encoded result set data for one row
     *
     * @param fields
     * @param binaryData
     * @param resultSetConcurrency DOCUMENT ME!
     *
     * @return byte[][]
     *
     * @throws SQLException DOCUMENT ME!
     */
    private final ResultSetRow unpackBinaryResultSetRow(Field[] fields,
        Buffer binaryData, int resultSetConcurrency) throws SQLException {
        int numFields = fields.length;

        byte[][] unpackedRowData = new byte[numFields][];

        //
        // Unpack the null bitmask, first
        //

        /* Reserve place for null-marker bytes */
        int nullCount = (numFields + 9) / 8;

        byte[] nullBitMask = new byte[nullCount];

        for (int i = 0; i < nullCount; i++) {
            nullBitMask[i] = binaryData.readByte();
        }

        int nullMaskPos = 0;
        int bit = 4; // first two bits are reserved for future use

        //
        // TODO: Benchmark if moving check for updatable result
        //       sets out of loop is worthwhile?
        //

        for (int i = 0; i < numFields; i++) {
            if ((nullBitMask[nullMaskPos] & bit) != 0) {
                unpackedRowData[i] = null;
            } else {
            	if (resultSetConcurrency != ResultSetInternalMethods.CONCUR_UPDATABLE) {
            		extractNativeEncodedColumn(binaryData, fields, i,
            				unpackedRowData);
            	} else {
            		unpackNativeEncodedColumn(binaryData, fields, i,
            				unpackedRowData);
            	}
            }

        	if (((bit <<= 1) & 255) == 0) {
        		bit = 1; /* To next byte */

        		nullMaskPos++;
        	}
        }

        return new ByteArrayRow(unpackedRowData, getExceptionInterceptor());
    }


    private final void extractNativeEncodedColumn(Buffer binaryData,
    		Field[] fields, int columnIndex, byte[][] unpackedRowData) throws SQLException {
    	Field curField = fields[columnIndex];

    	switch (curField.getMysqlType()) {
    	case MysqlDefs.FIELD_TYPE_NULL:
    		break; // for dummy binds

    	case MysqlDefs.FIELD_TYPE_TINY:

    		unpackedRowData[columnIndex] = new byte[] {binaryData.readByte()};
    		break;

    	case MysqlDefs.FIELD_TYPE_SHORT:
    	case MysqlDefs.FIELD_TYPE_YEAR:

    		unpackedRowData[columnIndex] = binaryData.getBytes(2);
    		break;
    	case MysqlDefs.FIELD_TYPE_LONG:
    	case MysqlDefs.FIELD_TYPE_INT24:

    		unpackedRowData[columnIndex] = binaryData.getBytes(4);
    		break;
    	case MysqlDefs.FIELD_TYPE_LONGLONG:

    		unpackedRowData[columnIndex] = binaryData.getBytes(8);
    		break;
    	case MysqlDefs.FIELD_TYPE_FLOAT:

    		unpackedRowData[columnIndex] = binaryData.getBytes(4);
    		break;
    	case MysqlDefs.FIELD_TYPE_DOUBLE:

    		unpackedRowData[columnIndex] = binaryData.getBytes(8);
    		break;
    	case MysqlDefs.FIELD_TYPE_TIME:

    		int length = (int) binaryData.readFieldLength();

    		unpackedRowData[columnIndex] = binaryData.getBytes(length);

    		break;
    	case MysqlDefs.FIELD_TYPE_DATE:

    		length = (int) binaryData.readFieldLength();

    		unpackedRowData[columnIndex] = binaryData.getBytes(length);

    		break;
    	case MysqlDefs.FIELD_TYPE_DATETIME:
    	case MysqlDefs.FIELD_TYPE_TIMESTAMP:
    		length = (int) binaryData.readFieldLength();

    		unpackedRowData[columnIndex] = binaryData.getBytes(length);
    		break;
    	case MysqlDefs.FIELD_TYPE_TINY_BLOB:
    	case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
    	case MysqlDefs.FIELD_TYPE_LONG_BLOB:
    	case MysqlDefs.FIELD_TYPE_BLOB:
    	case MysqlDefs.FIELD_TYPE_VAR_STRING:
    	case MysqlDefs.FIELD_TYPE_VARCHAR:
    	case MysqlDefs.FIELD_TYPE_STRING:
    	case MysqlDefs.FIELD_TYPE_DECIMAL:
    	case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
    	case MysqlDefs.FIELD_TYPE_GEOMETRY:
    		unpackedRowData[columnIndex] = binaryData.readLenByteArray(0);

    		break;
    	case MysqlDefs.FIELD_TYPE_BIT:
    		unpackedRowData[columnIndex] = binaryData.readLenByteArray(0);

    		break;
    	default:
    		throw SQLError.createSQLException(Messages.getString("MysqlIO.97") //$NON-NLS-1$
    				+curField.getMysqlType() +
					Messages.getString("MysqlIO.98") + columnIndex +
					Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
					+ fields.length + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
					SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
    	}
    }

    private final void unpackNativeEncodedColumn(Buffer binaryData,
    		Field[] fields, int columnIndex, byte[][] unpackedRowData)
    throws SQLException {
    	Field curField = fields[columnIndex];

    	switch (curField.getMysqlType()) {
    	case MysqlDefs.FIELD_TYPE_NULL:
    		break; // for dummy binds

    	case MysqlDefs.FIELD_TYPE_TINY:

    		byte tinyVal = binaryData.readByte();

    		if (!curField.isUnsigned()) {
    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(tinyVal));
    		} else {
    			short unsignedTinyVal = (short) (tinyVal & 0xff);

    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(unsignedTinyVal));
    		}

    		break;

    	case MysqlDefs.FIELD_TYPE_SHORT:
    	case MysqlDefs.FIELD_TYPE_YEAR:

    		short shortVal = (short) binaryData.readInt();

    		if (!curField.isUnsigned()) {
    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(shortVal));
    		} else {
    			int unsignedShortVal = shortVal & 0xffff;

    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(unsignedShortVal));
    		}

    		break;

    	case MysqlDefs.FIELD_TYPE_LONG:
    	case MysqlDefs.FIELD_TYPE_INT24:

    		int intVal = (int) binaryData.readLong();

    		if (!curField.isUnsigned()) {
    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(intVal));
    		} else {
    			long longVal = intVal & 0xffffffffL;

    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(longVal));
    		}

    		break;

    	case MysqlDefs.FIELD_TYPE_LONGLONG:

    		long longVal = binaryData.readLongLong();

    		if (!curField.isUnsigned()) {
    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					String.valueOf(longVal));
    		} else {
    			BigInteger asBigInteger = ResultSetImpl.convertLongToUlong(longVal);

    			unpackedRowData[columnIndex] = StringUtils.getBytes(
    					asBigInteger.toString());
    		}

    		break;

    	case MysqlDefs.FIELD_TYPE_FLOAT:

    		float floatVal = Float.intBitsToFloat(binaryData.readIntAsLong());

    		unpackedRowData[columnIndex] = StringUtils.getBytes(
    				String.valueOf(floatVal));

    		break;

    	case MysqlDefs.FIELD_TYPE_DOUBLE:

    		double doubleVal = Double.longBitsToDouble(binaryData.readLongLong());

    		unpackedRowData[columnIndex] = StringUtils.getBytes(
    				String.valueOf(doubleVal));

    		break;

    	case MysqlDefs.FIELD_TYPE_TIME:

    		int length = (int) binaryData.readFieldLength();

    		int hour = 0;
    		int minute = 0;
    		int seconds = 0;

    		if (length != 0) {
    			binaryData.readByte(); // skip tm->neg
    			binaryData.readLong(); // skip daysPart
    			hour = binaryData.readByte();
    			minute = binaryData.readByte();
    			seconds = binaryData.readByte();

    			if (length > 8) {
    				binaryData.readLong(); // ignore 'secondsPart'
    			}
    		}


    		byte[] timeAsBytes = new byte[8];

    		timeAsBytes[0] = (byte) Character.forDigit(hour / 10, 10);
    		timeAsBytes[1] = (byte) Character.forDigit(hour % 10, 10);

    		timeAsBytes[2] = (byte) ':';

    		timeAsBytes[3] = (byte) Character.forDigit(minute / 10,
    				10);
    		timeAsBytes[4] = (byte) Character.forDigit(minute % 10,
    				10);

    		timeAsBytes[5] = (byte) ':';

    		timeAsBytes[6] = (byte) Character.forDigit(seconds / 10,
    				10);
    		timeAsBytes[7] = (byte) Character.forDigit(seconds % 10,
    				10);

    		unpackedRowData[columnIndex] = timeAsBytes;


    		break;

    	case MysqlDefs.FIELD_TYPE_DATE:
    		length = (int) binaryData.readFieldLength();

    		int year = 0;
    		int month = 0;
    		int day = 0;

    		hour = 0;
    		minute = 0;
    		seconds = 0;

    		if (length != 0) {
    			year = binaryData.readInt();
    			month = binaryData.readByte();
    			day = binaryData.readByte();
    		}

    		if ((year == 0) && (month == 0) && (day == 0)) {
    			if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL.equals(
    					this.connection.getZeroDateTimeBehavior())) {
    				unpackedRowData[columnIndex] = null;

    				break;
    			} else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION.equals(
    					this.connection.getZeroDateTimeBehavior())) {
    				throw SQLError.createSQLException("Value '0000-00-00' can not be represented as java.sql.Date",
    						SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
    			}

    			year = 1;
    			month = 1;
    			day = 1;
    		}


    		byte[] dateAsBytes = new byte[10];

    		dateAsBytes[0] = (byte) Character.forDigit(year / 1000,
    				10);

    		int after1000 = year % 1000;

    		dateAsBytes[1] = (byte) Character.forDigit(after1000 / 100,
    				10);

    		int after100 = after1000 % 100;

    		dateAsBytes[2] = (byte) Character.forDigit(after100 / 10,
    				10);
    		dateAsBytes[3] = (byte) Character.forDigit(after100 % 10,
    				10);

    		dateAsBytes[4] = (byte) '-';

    		dateAsBytes[5] = (byte) Character.forDigit(month / 10,
    				10);
    		dateAsBytes[6] = (byte) Character.forDigit(month % 10,
    				10);

    		dateAsBytes[7] = (byte) '-';

    		dateAsBytes[8] = (byte) Character.forDigit(day / 10, 10);
    		dateAsBytes[9] = (byte) Character.forDigit(day % 10, 10);

    		unpackedRowData[columnIndex] = dateAsBytes;


    		break;

    	case MysqlDefs.FIELD_TYPE_DATETIME:
    	case MysqlDefs.FIELD_TYPE_TIMESTAMP:
    		length = (int) binaryData.readFieldLength();

    		year = 0;
    		month = 0;
    		day = 0;

    		hour = 0;
    		minute = 0;
    		seconds = 0;

    		int nanos = 0;

    		if (length != 0) {
    			year = binaryData.readInt();
    			month = binaryData.readByte();
    			day = binaryData.readByte();

    			if (length > 4) {
    				hour = binaryData.readByte();
    				minute = binaryData.readByte();
    				seconds = binaryData.readByte();
    			}

    			//if (length > 7) {
    			//    nanos = (int)binaryData.readLong();
    			//}
    		}

    		if ((year == 0) && (month == 0) && (day == 0)) {
    			if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL.equals(
    					this.connection.getZeroDateTimeBehavior())) {
    				unpackedRowData[columnIndex] = null;

    				break;
    			} else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION.equals(
    					this.connection.getZeroDateTimeBehavior())) {
    				throw SQLError.createSQLException("Value '0000-00-00' can not be represented as java.sql.Timestamp",
    						SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
    			}

    			year = 1;
    			month = 1;
    			day = 1;
    		}


    		int stringLength = 19;

    		byte[] nanosAsBytes = StringUtils.getBytes(Integer.toString(nanos));

    		stringLength += (1 + nanosAsBytes.length); // '.' + # of digits

    		byte[] datetimeAsBytes = new byte[stringLength];

    		datetimeAsBytes[0] = (byte) Character.forDigit(year / 1000,
    				10);

    		after1000 = year % 1000;

    		datetimeAsBytes[1] = (byte) Character.forDigit(after1000 / 100,
    				10);

    		after100 = after1000 % 100;

    		datetimeAsBytes[2] = (byte) Character.forDigit(after100 / 10,
    				10);
    		datetimeAsBytes[3] = (byte) Character.forDigit(after100 % 10,
    				10);

    		datetimeAsBytes[4] = (byte) '-';

    		datetimeAsBytes[5] = (byte) Character.forDigit(month / 10,
    				10);
    		datetimeAsBytes[6] = (byte) Character.forDigit(month % 10,
    				10);

    		datetimeAsBytes[7] = (byte) '-';

    		datetimeAsBytes[8] = (byte) Character.forDigit(day / 10,
    				10);
    		datetimeAsBytes[9] = (byte) Character.forDigit(day % 10,
    				10);

    		datetimeAsBytes[10] = (byte) ' ';

    		datetimeAsBytes[11] = (byte) Character.forDigit(hour / 10,
    				10);
    		datetimeAsBytes[12] = (byte) Character.forDigit(hour % 10,
    				10);

    		datetimeAsBytes[13] = (byte) ':';

    		datetimeAsBytes[14] = (byte) Character.forDigit(minute / 10,
    				10);
    		datetimeAsBytes[15] = (byte) Character.forDigit(minute % 10,
    				10);

    		datetimeAsBytes[16] = (byte) ':';

    		datetimeAsBytes[17] = (byte) Character.forDigit(seconds / 10,
    				10);
    		datetimeAsBytes[18] = (byte) Character.forDigit(seconds % 10,
    				10);

    		datetimeAsBytes[19] = (byte) '.';

    		final int nanosOffset = 20;

			System.arraycopy(nanosAsBytes, 0, datetimeAsBytes, nanosOffset, nanosAsBytes.length);
    		
    		unpackedRowData[columnIndex] = datetimeAsBytes;


    		break;

    	case MysqlDefs.FIELD_TYPE_TINY_BLOB:
    	case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
    	case MysqlDefs.FIELD_TYPE_LONG_BLOB:
    	case MysqlDefs.FIELD_TYPE_BLOB:
    	case MysqlDefs.FIELD_TYPE_VAR_STRING:
    	case MysqlDefs.FIELD_TYPE_STRING:
    	case MysqlDefs.FIELD_TYPE_VARCHAR:
    	case MysqlDefs.FIELD_TYPE_DECIMAL:
    	case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
    	case MysqlDefs.FIELD_TYPE_BIT:
    		unpackedRowData[columnIndex] = binaryData.readLenByteArray(0);

    		break;

    	default:
    		throw SQLError.createSQLException(Messages.getString("MysqlIO.97") //$NON-NLS-1$
    				+curField.getMysqlType() +
    				Messages.getString("MysqlIO.98") + columnIndex +
    				Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
    				+ fields.length + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
    				SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
    	}
    }

    /**
     * Negotiates the SSL communications channel used when connecting
     * to a MySQL server that understands SSL.
     *
     * @param user
     * @param password
     * @param database
     * @param packLength
     * @throws SQLException
     * @throws CommunicationsException
     */
    private void negotiateSSLConnection(String user, String password,
        String database, int packLength)
        throws SQLException {
        if (!ExportControlled.enabled()) {
            throw new ConnectionFeatureNotAvailableException(this.connection,
                this.lastPacketSentTimeMs, null);
        }

        if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
            this.clientParam |= CLIENT_SECURE_CONNECTION;
        }

        this.clientParam |= CLIENT_SSL;

        Buffer packet = new Buffer(packLength);

        if (this.use41Extensions) {
            packet.writeLong(this.clientParam);
            packet.writeLong(this.maxThreeBytes);
			int charsetIndex = 0;
			if (this.connection.getEncoding() != null) {
				charsetIndex = CharsetMapping.getCharsetIndexForMysqlEncodingName(CharsetMapping.getMysqlEncodingForJavaEncoding(this.connection.getEncoding(), this.connection));
			}
            packet.writeByte(charsetIndex == 0 ? (byte) UTF8_CHARSET_INDEX : (byte) charsetIndex);
            packet.writeBytesNoNull(new byte[23]);	// Set of bytes reserved for future use.
        } else {
            packet.writeInt((int) this.clientParam);
        }

        send(packet, packet.getPosition());

        ExportControlled.transformSocketToSSLSocket(this);
    }

	protected int getServerStatus() {
		return this.serverStatus;
	}

	protected List<ResultSetRow> fetchRowsViaCursor(List<ResultSetRow> fetchedRows, long statementId,
			Field[] columnTypes, int fetchSize, boolean useBufferRowExplicit) throws SQLException {

		if (fetchedRows == null) {
			fetchedRows = new ArrayList<ResultSetRow>(fetchSize);
		} else {
			fetchedRows.clear();
		}

		this.sharedSendPacket.clear();

		this.sharedSendPacket.writeByte((byte) MysqlDefs.COM_FETCH);
		this.sharedSendPacket.writeLong(statementId);
		this.sharedSendPacket.writeLong(fetchSize);

		sendCommand(MysqlDefs.COM_FETCH, null, this.sharedSendPacket, true,
				null, 0);

		ResultSetRow row = null;

		while ((row = nextRow(columnTypes, columnTypes.length, true,
				ResultSet.CONCUR_READ_ONLY, false, useBufferRowExplicit, false, null)) != null) {
			fetchedRows.add(row);
		}

		return fetchedRows;
	}

	protected long getThreadId() {
		return this.threadId;
	}

	protected boolean useNanosForElapsedTime() {
		return this.useNanosForElapsedTime;
	}

	protected long getSlowQueryThreshold() {
		return this.slowQueryThreshold;
	}

	protected String getQueryTimingUnits() {
		return this.queryTimingUnits;
	}
	
	protected int getCommandCount() {
		return this.commandCount;
	}
	
	private void checkTransactionState(int oldStatus) throws SQLException {
		boolean previouslyInTrans = ((oldStatus & SERVER_STATUS_IN_TRANS) != 0);
		boolean currentlyInTrans = ((this.serverStatus & SERVER_STATUS_IN_TRANS) != 0);

		if (previouslyInTrans && !currentlyInTrans) {
			this.connection.transactionCompleted();
		} else if (!previouslyInTrans && currentlyInTrans) {
			this.connection.transactionBegun();
		}
	}

	protected void setStatementInterceptors(List<StatementInterceptorV2> statementInterceptors) {
		this.statementInterceptors = statementInterceptors.isEmpty() ? null : statementInterceptors;
	}
	
	protected ExceptionInterceptor getExceptionInterceptor() {
		return this.exceptionInterceptor;
	}
	
	protected void setSocketTimeout(int milliseconds) throws SQLException {
		try {
			mysqlConnection.setSoTimeout(milliseconds);
		} catch (SocketException e) {
			SQLException sqlEx = SQLError.createSQLException("Invalid socket timeout value or state", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			sqlEx.initCause(e);
			
			throw sqlEx;
		}
	}

	protected void releaseResources() {
		if (this.deflater != null) {
			this.deflater.end();
			this.deflater = null;
		}
	}
}
