0001: /*
0002: Copyright (C) 2002-2007 MySQL AB
0003:
0004: This program is free software; you can redistribute it and/or modify
0005: it under the terms of version 2 of the GNU General Public License as
0006: published by the Free Software Foundation.
0007:
0008: There are special exceptions to the terms and conditions of the GPL
0009: as it is applied to this software. View the full text of the
0010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
0011: software distribution.
0012:
0013: This program is distributed in the hope that it will be useful,
0014: but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: GNU General Public License for more details.
0017:
0018: You should have received a copy of the GNU General Public License
0019: along with this program; if not, write to the Free Software
0020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0021:
0022:
0023:
0024: */
0025: package com.mysql.jdbc;
0026:
0027: import java.io.BufferedInputStream;
0028: import java.io.BufferedOutputStream;
0029: import java.io.ByteArrayOutputStream;
0030: import java.io.EOFException;
0031: import java.io.FileInputStream;
0032: import java.io.IOException;
0033: import java.io.InputStream;
0034: import java.io.OutputStreamWriter;
0035: import java.lang.ref.SoftReference;
0036: import java.math.BigInteger;
0037: import java.net.MalformedURLException;
0038: import java.net.Socket;
0039: import java.net.URL;
0040: import java.security.NoSuchAlgorithmException;
0041: import java.sql.ResultSet;
0042: import java.sql.SQLException;
0043: import java.sql.Types;
0044: import java.util.ArrayList;
0045: import java.util.Calendar;
0046: import java.util.Iterator;
0047: import java.util.LinkedList;
0048: import java.util.List;
0049: import java.util.Properties;
0050: import java.util.zip.Deflater;
0051:
0052: import com.mysql.jdbc.profiler.ProfileEventSink;
0053: import com.mysql.jdbc.profiler.ProfilerEvent;
0054: import com.mysql.jdbc.util.ReadAheadInputStream;
0055: import com.mysql.jdbc.util.ResultSetUtil;
0056:
0057: /**
0058: * This class is used by Connection for communicating with the MySQL server.
0059: *
0060: * @author Mark Matthews
0061: * @version $Id: MysqlIO.java 6496 2007-08-13 20:22:33Z mmatthews $
0062: *
0063: * @see java.sql.Connection
0064: */
0065: class MysqlIO {
0066: private static final int UTF8_CHARSET_INDEX = 33;
0067: private static final String CODE_PAGE_1252 = "Cp1252";
0068: protected static final int NULL_LENGTH = ~0;
0069: protected static final int COMP_HEADER_LENGTH = 3;
0070: protected static final int MIN_COMPRESS_LEN = 50;
0071: protected static final int HEADER_LENGTH = 4;
0072: protected static final int AUTH_411_OVERHEAD = 33;
0073: private static int maxBufferSize = 65535;
0074: private static final int CLIENT_COMPRESS = 32; /* Can use compression
0075: protcol */
0076: protected static final int CLIENT_CONNECT_WITH_DB = 8;
0077: private static final int CLIENT_FOUND_ROWS = 2;
0078: private static final int CLIENT_LOCAL_FILES = 128; /* Can use LOAD DATA
0079: LOCAL */
0080:
0081: /* Found instead of
0082: affected rows */
0083: private static final int CLIENT_LONG_FLAG = 4; /* Get all column flags */
0084: private static final int CLIENT_LONG_PASSWORD = 1; /* new more secure
0085: passwords */
0086: private static final int CLIENT_PROTOCOL_41 = 512; // for > 4.1.1
0087: private static final int CLIENT_INTERACTIVE = 1024;
0088: protected static final int CLIENT_SSL = 2048;
0089: private static final int CLIENT_TRANSACTIONS = 8192; // Client knows about transactions
0090: protected static final int CLIENT_RESERVED = 16384; // for 4.1.0 only
0091: protected static final int CLIENT_SECURE_CONNECTION = 32768;
0092: private static final int CLIENT_MULTI_QUERIES = 65536; // Enable/disable multiquery support
0093: private static final int CLIENT_MULTI_RESULTS = 131072; // Enable/disable multi-results
0094: private static final int SERVER_STATUS_IN_TRANS = 1;
0095: private static final int SERVER_STATUS_AUTOCOMMIT = 2; // Server in auto_commit mode
0096: private static final int SERVER_MORE_RESULTS_EXISTS = 8; // Multi query - next query exists
0097: private static final int SERVER_QUERY_NO_GOOD_INDEX_USED = 16;
0098: private static final int SERVER_QUERY_NO_INDEX_USED = 32;
0099: private static final int SERVER_STATUS_CURSOR_EXISTS = 64;
0100: private static final String FALSE_SCRAMBLE = "xxxxxxxx"; //$NON-NLS-1$
0101: protected static final int MAX_QUERY_SIZE_TO_LOG = 1024; // truncate logging of queries at 1K
0102: protected static final int MAX_QUERY_SIZE_TO_EXPLAIN = 1024 * 1024; // don't explain queries above 1MB
0103: protected static final int INITIAL_PACKET_SIZE = 1024;
0104: /**
0105: * We store the platform 'encoding' here, only used to avoid munging
0106: * filenames for LOAD DATA LOCAL INFILE...
0107: */
0108: private static String jvmPlatformCharset = null;
0109:
0110: /**
0111: * We need to have a 'marker' for all-zero datetimes so that ResultSet
0112: * can decide what to do based on connection setting
0113: */
0114: protected final static String ZERO_DATE_VALUE_MARKER = "0000-00-00";
0115: protected final static String ZERO_DATETIME_VALUE_MARKER = "0000-00-00 00:00:00";
0116:
0117: static {
0118: OutputStreamWriter outWriter = null;
0119:
0120: //
0121: // Use the I/O system to get the encoding (if possible), to avoid
0122: // security restrictions on System.getProperty("file.encoding") in
0123: // applets (why is that restricted?)
0124: //
0125: try {
0126: outWriter = new OutputStreamWriter(
0127: new ByteArrayOutputStream());
0128: jvmPlatformCharset = outWriter.getEncoding();
0129: } finally {
0130: try {
0131: if (outWriter != null) {
0132: outWriter.close();
0133: }
0134: } catch (IOException ioEx) {
0135: // ignore
0136: }
0137: }
0138: }
0139:
0140: /** Max number of bytes to dump when tracing the protocol */
0141: private final static int MAX_PACKET_DUMP_LENGTH = 1024;
0142: private boolean packetSequenceReset = false;
0143: protected int serverCharsetIndex;
0144:
0145: //
0146: // Use this when reading in rows to avoid thousands of new()
0147: // calls, because the byte arrays just get copied out of the
0148: // packet anyway
0149: //
0150: private Buffer reusablePacket = null;
0151: private Buffer sendPacket = null;
0152: private Buffer sharedSendPacket = null;
0153:
0154: /** Data to the server */
0155: protected BufferedOutputStream mysqlOutput = null;
0156: protected ConnectionImpl connection;
0157: private Deflater deflater = null;
0158: protected InputStream mysqlInput = null;
0159: private LinkedList packetDebugRingBuffer = null;
0160: private RowData streamingData = null;
0161:
0162: /** The connection to the server */
0163: protected Socket mysqlConnection = null;
0164: private SocketFactory socketFactory = null;
0165:
0166: //
0167: // Packet used for 'LOAD DATA LOCAL INFILE'
0168: //
0169: // We use a SoftReference, so that we don't penalize intermittent
0170: // use of this feature
0171: //
0172: private SoftReference loadFileBufRef;
0173:
0174: //
0175: // Used to send large packets to the server versions 4+
0176: // We use a SoftReference, so that we don't penalize intermittent
0177: // use of this feature
0178: //
0179: private SoftReference splitBufRef;
0180: protected String host = null;
0181: protected String seed;
0182: private String serverVersion = null;
0183: private String socketFactoryClassName = null;
0184: private byte[] packetHeaderBuf = new byte[4];
0185: private boolean colDecimalNeedsBump = false; // do we need to increment the colDecimal flag?
0186: private boolean hadWarnings = false;
0187: private boolean has41NewNewProt = false;
0188:
0189: /** Does the server support long column info? */
0190: private boolean hasLongColumnInfo = false;
0191: private boolean isInteractiveClient = false;
0192: private boolean logSlowQueries = false;
0193:
0194: /**
0195: * Does the character set of this connection match the character set of the
0196: * platform
0197: */
0198: private boolean platformDbCharsetMatches = true; // changed once we've connected.
0199: private boolean profileSql = false;
0200: private boolean queryBadIndexUsed = false;
0201: private boolean queryNoIndexUsed = false;
0202:
0203: /** Should we use 4.1 protocol extensions? */
0204: private boolean use41Extensions = false;
0205: private boolean useCompression = false;
0206: private boolean useNewLargePackets = false;
0207: private boolean useNewUpdateCounts = false; // should we use the new larger update counts?
0208: private byte packetSequence = 0;
0209: private byte readPacketSequence = -1;
0210: private boolean checkPacketSequence = false;
0211: private byte protocolVersion = 0;
0212: private int maxAllowedPacket = 1024 * 1024;
0213: protected int maxThreeBytes = 255 * 255 * 255;
0214: protected int port = 3306;
0215: protected int serverCapabilities;
0216: private int serverMajorVersion = 0;
0217: private int serverMinorVersion = 0;
0218: private int serverStatus = 0;
0219: private int serverSubMinorVersion = 0;
0220: private int warningCount = 0;
0221: protected long clientParam = 0;
0222: protected long lastPacketSentTimeMs = 0;
0223: private boolean traceProtocol = false;
0224: private boolean enablePacketDebug = false;
0225: private Calendar sessionCalendar;
0226: private boolean useConnectWithDb;
0227: private boolean needToGrabQueryFromPacket;
0228: private boolean autoGenerateTestcaseScript;
0229: private long threadId;
0230: private boolean useNanosForElapsedTime;
0231: private long slowQueryThreshold;
0232: private String queryTimingUnits;
0233: private List statementInterceptors;
0234: private boolean useDirectRowUnpack = true;
0235: private int useBufferRowSizeThreshold;
0236:
0237: /**
0238: * Constructor: Connect to the MySQL server and setup a stream connection.
0239: *
0240: * @param host the hostname to connect to
0241: * @param port the port number that the server is listening on
0242: * @param props the Properties from DriverManager.getConnection()
0243: * @param socketFactoryClassName the socket factory to use
0244: * @param conn the Connection that is creating us
0245: * @param socketTimeout the timeout to set for the socket (0 means no
0246: * timeout)
0247: *
0248: * @throws IOException if an IOException occurs during connect.
0249: * @throws SQLException if a database access error occurs.
0250: */
0251: public MysqlIO(String host, int port, Properties props,
0252: String socketFactoryClassName, ConnectionImpl conn,
0253: int socketTimeout, int useBufferRowSizeThreshold)
0254: throws IOException, SQLException {
0255: this .connection = conn;
0256:
0257: if (this .connection.getEnablePacketDebug()) {
0258: this .packetDebugRingBuffer = new LinkedList();
0259: }
0260:
0261: this .useBufferRowSizeThreshold = useBufferRowSizeThreshold;
0262: this .useDirectRowUnpack = this .connection
0263: .getUseDirectRowUnpack();
0264:
0265: this .logSlowQueries = this .connection.getLogSlowQueries();
0266:
0267: this .reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
0268: this .sendPacket = new Buffer(INITIAL_PACKET_SIZE);
0269:
0270: this .port = port;
0271: this .host = host;
0272:
0273: this .socketFactoryClassName = socketFactoryClassName;
0274: this .socketFactory = createSocketFactory();
0275:
0276: this .mysqlConnection = this .socketFactory.connect(this .host,
0277: this .port, props);
0278:
0279: if (socketTimeout != 0) {
0280: try {
0281: this .mysqlConnection.setSoTimeout(socketTimeout);
0282: } catch (Exception ex) {
0283: /* Ignore if the platform does not support it */
0284: }
0285: }
0286:
0287: this .mysqlConnection = this .socketFactory.beforeHandshake();
0288:
0289: if (this .connection.getUseReadAheadInput()) {
0290: this .mysqlInput = new ReadAheadInputStream(
0291: this .mysqlConnection.getInputStream(), 16384,
0292: this .connection.getTraceProtocol(), this .connection
0293: .getLog());
0294: } else if (this .connection.useUnbufferedInput()) {
0295: this .mysqlInput = this .mysqlConnection.getInputStream();
0296: } else {
0297: this .mysqlInput = new BufferedInputStream(
0298: this .mysqlConnection.getInputStream(), 16384);
0299: }
0300:
0301: this .mysqlOutput = new BufferedOutputStream(
0302: this .mysqlConnection.getOutputStream(), 16384);
0303:
0304: this .isInteractiveClient = this .connection
0305: .getInteractiveClient();
0306: this .profileSql = this .connection.getProfileSql();
0307: this .sessionCalendar = Calendar.getInstance();
0308: this .autoGenerateTestcaseScript = this .connection
0309: .getAutoGenerateTestcaseScript();
0310:
0311: this .needToGrabQueryFromPacket = (this .profileSql
0312: || this .logSlowQueries || this .autoGenerateTestcaseScript);
0313:
0314: if (this .connection.getUseNanosForElapsedTime()
0315: && Util.nanoTimeAvailable()) {
0316: this .useNanosForElapsedTime = true;
0317:
0318: this .queryTimingUnits = Messages.getString("Nanoseconds");
0319: } else {
0320: this .queryTimingUnits = Messages.getString("Milliseconds");
0321: }
0322:
0323: if (this .connection.getLogSlowQueries()) {
0324: calculateSlowQueryThreshold();
0325: }
0326: }
0327:
0328: protected void initializeStatementInterceptors(
0329: String interceptorClasses, Properties props)
0330: throws SQLException {
0331: List interceptorsToCreate = StringUtils.split(
0332: interceptorClasses, ",", true);
0333:
0334: this .statementInterceptors = new LinkedList();
0335:
0336: Iterator iter = interceptorsToCreate.iterator();
0337:
0338: String classname = null;
0339:
0340: try {
0341: while (iter.hasNext()) {
0342: classname = iter.next().toString();
0343: StatementInterceptor interceptorInstance = (StatementInterceptor) Class
0344: .forName(classname).newInstance();
0345: interceptorInstance.init(this .connection, props);
0346:
0347: this .statementInterceptors.add(interceptorInstance);
0348: }
0349: } catch (Throwable t) {
0350: SQLException sqlEx = SQLError.createSQLException(Messages
0351: .getString("MysqlIo.BadStatementInterceptor",
0352: new Object[] { classname }));
0353: sqlEx.initCause(t);
0354:
0355: throw sqlEx;
0356: }
0357: }
0358:
0359: /**
0360: * Does the server send back extra column info?
0361: *
0362: * @return true if so
0363: */
0364: public boolean hasLongColumnInfo() {
0365: return this .hasLongColumnInfo;
0366: }
0367:
0368: protected boolean isDataAvailable() throws SQLException {
0369: try {
0370: return this .mysqlInput.available() > 0;
0371: } catch (IOException ioEx) {
0372: throw SQLError.createCommunicationsException(
0373: this .connection, this .lastPacketSentTimeMs, ioEx);
0374: }
0375: }
0376:
0377: /**
0378: * DOCUMENT ME!
0379: *
0380: * @return Returns the lastPacketSentTimeMs.
0381: */
0382: protected long getLastPacketSentTimeMs() {
0383: return this .lastPacketSentTimeMs;
0384: }
0385:
0386: /**
0387: * Build a result set. Delegates to buildResultSetWithRows() to build a
0388: * JDBC-version-specific ResultSet, given rows as byte data, and field
0389: * information.
0390: *
0391: * @param callingStatement DOCUMENT ME!
0392: * @param columnCount the number of columns in the result set
0393: * @param maxRows the maximum number of rows to read (-1 means all rows)
0394: * @param resultSetType (TYPE_FORWARD_ONLY, TYPE_SCROLL_????)
0395: * @param resultSetConcurrency the type of result set (CONCUR_UPDATABLE or
0396: * READ_ONLY)
0397: * @param streamResults should the result set be read all at once, or
0398: * streamed?
0399: * @param catalog the database name in use when the result set was created
0400: * @param isBinaryEncoded is this result set in native encoding?
0401: * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
0402: *
0403: * @return a result set
0404: *
0405: * @throws SQLException if a database access error occurs
0406: */
0407: protected ResultSetImpl getResultSet(
0408: StatementImpl callingStatement, long columnCount,
0409: int maxRows, int resultSetType, int resultSetConcurrency,
0410: boolean streamResults, String catalog,
0411: boolean isBinaryEncoded, Field[] metadataFromCache)
0412: throws SQLException {
0413: Buffer packet; // The packet from the server
0414: Field[] fields = null;
0415:
0416: // Read in the column information
0417:
0418: if (metadataFromCache == null /* we want the metadata from the server */) {
0419: fields = new Field[(int) columnCount];
0420:
0421: for (int i = 0; i < columnCount; i++) {
0422: Buffer fieldPacket = null;
0423:
0424: fieldPacket = readPacket();
0425: fields[i] = unpackField(fieldPacket, false);
0426: }
0427: } else {
0428: for (int i = 0; i < columnCount; i++) {
0429: skipPacket();
0430: }
0431: }
0432:
0433: packet = reuseAndReadPacket(this .reusablePacket);
0434:
0435: readServerStatusForResultSets(packet);
0436:
0437: //
0438: // Handle cursor-based fetch first
0439: //
0440:
0441: if (this .connection.versionMeetsMinimum(5, 0, 2)
0442: && this .connection.getUseCursorFetch()
0443: && isBinaryEncoded
0444: && callingStatement != null
0445: && callingStatement.getFetchSize() != 0
0446: && callingStatement.getResultSetType() == ResultSet.TYPE_FORWARD_ONLY) {
0447: ServerPreparedStatement prepStmt = (com.mysql.jdbc.ServerPreparedStatement) callingStatement;
0448:
0449: boolean usingCursor = true;
0450:
0451: //
0452: // Server versions 5.0.5 or newer will only open
0453: // a cursor and set this flag if they can, otherwise
0454: // they punt and go back to mysql_store_results() behavior
0455: //
0456:
0457: if (this .connection.versionMeetsMinimum(5, 0, 5)) {
0458: usingCursor = (this .serverStatus & SERVER_STATUS_CURSOR_EXISTS) != 0;
0459: }
0460:
0461: if (usingCursor) {
0462: RowData rows = new RowDataCursor(this , prepStmt, fields);
0463:
0464: ResultSetImpl rs = buildResultSetWithRows(
0465: callingStatement, catalog, fields, rows,
0466: resultSetType, resultSetConcurrency,
0467: isBinaryEncoded);
0468:
0469: if (usingCursor) {
0470: rs.setFetchSize(callingStatement.getFetchSize());
0471: }
0472:
0473: return rs;
0474: }
0475: }
0476:
0477: RowData rowData = null;
0478:
0479: if (!streamResults) {
0480: rowData = readSingleRowSet(columnCount, maxRows,
0481: resultSetConcurrency, isBinaryEncoded,
0482: (metadataFromCache == null) ? fields
0483: : metadataFromCache);
0484: } else {
0485: rowData = new RowDataDynamic(this , (int) columnCount,
0486: (metadataFromCache == null) ? fields
0487: : metadataFromCache, isBinaryEncoded);
0488: this .streamingData = rowData;
0489: }
0490:
0491: ResultSetImpl rs = buildResultSetWithRows(callingStatement,
0492: catalog, (metadataFromCache == null) ? fields
0493: : metadataFromCache, rowData, resultSetType,
0494: resultSetConcurrency, isBinaryEncoded);
0495:
0496: return rs;
0497: }
0498:
0499: /**
0500: * Forcibly closes the underlying socket to MySQL.
0501: */
0502: protected final void forceClose() {
0503: try {
0504: if (this .mysqlInput != null) {
0505: this .mysqlInput.close();
0506: }
0507: } catch (IOException ioEx) {
0508: // we can't do anything constructive about this
0509: // Let the JVM clean it up later
0510: this .mysqlInput = null;
0511: }
0512:
0513: try {
0514: if (this .mysqlOutput != null) {
0515: this .mysqlOutput.close();
0516: }
0517: } catch (IOException ioEx) {
0518: // we can't do anything constructive about this
0519: // Let the JVM clean it up later
0520: this .mysqlOutput = null;
0521: }
0522:
0523: try {
0524: if (this .mysqlConnection != null) {
0525: this .mysqlConnection.close();
0526: }
0527: } catch (IOException ioEx) {
0528: // we can't do anything constructive about this
0529: // Let the JVM clean it up later
0530: this .mysqlConnection = null;
0531: }
0532: }
0533:
0534: /**
0535: * Reads and discards a single MySQL packet from the input stream.
0536: *
0537: * @throws SQLException if the network fails while skipping the
0538: * packet.
0539: */
0540: protected final void skipPacket() throws SQLException {
0541: try {
0542:
0543: int lengthRead = readFully(this .mysqlInput,
0544: this .packetHeaderBuf, 0, 4);
0545:
0546: if (lengthRead < 4) {
0547: forceClose();
0548: throw new IOException(Messages.getString("MysqlIO.1")); //$NON-NLS-1$
0549: }
0550:
0551: int packetLength = (this .packetHeaderBuf[0] & 0xff)
0552: + ((this .packetHeaderBuf[1] & 0xff) << 8)
0553: + ((this .packetHeaderBuf[2] & 0xff) << 16);
0554:
0555: if (this .traceProtocol) {
0556: StringBuffer traceMessageBuf = new StringBuffer();
0557:
0558: traceMessageBuf.append(Messages.getString("MysqlIO.2")); //$NON-NLS-1$
0559: traceMessageBuf.append(packetLength);
0560: traceMessageBuf.append(Messages.getString("MysqlIO.3")); //$NON-NLS-1$
0561: traceMessageBuf.append(StringUtils.dumpAsHex(
0562: this .packetHeaderBuf, 4));
0563:
0564: this .connection.getLog().logTrace(
0565: traceMessageBuf.toString());
0566: }
0567:
0568: byte multiPacketSeq = this .packetHeaderBuf[3];
0569:
0570: if (!this .packetSequenceReset) {
0571: if (this .enablePacketDebug && this .checkPacketSequence) {
0572: checkPacketSequencing(multiPacketSeq);
0573: }
0574: } else {
0575: this .packetSequenceReset = false;
0576: }
0577:
0578: this .readPacketSequence = multiPacketSeq;
0579:
0580: skipFully(this .mysqlInput, packetLength);
0581: } catch (IOException ioEx) {
0582: throw SQLError.createCommunicationsException(
0583: this .connection, this .lastPacketSentTimeMs, ioEx);
0584: } catch (OutOfMemoryError oom) {
0585: try {
0586: this .connection.realClose(false, false, true, oom);
0587: } finally {
0588: throw oom;
0589: }
0590: }
0591: }
0592:
0593: /**
0594: * Read one packet from the MySQL server
0595: *
0596: * @return the packet from the server.
0597: *
0598: * @throws SQLException DOCUMENT ME!
0599: * @throws CommunicationsException DOCUMENT ME!
0600: */
0601: protected final Buffer readPacket() throws SQLException {
0602: try {
0603:
0604: int lengthRead = readFully(this .mysqlInput,
0605: this .packetHeaderBuf, 0, 4);
0606:
0607: if (lengthRead < 4) {
0608: forceClose();
0609: throw new IOException(Messages.getString("MysqlIO.1")); //$NON-NLS-1$
0610: }
0611:
0612: int packetLength = (this .packetHeaderBuf[0] & 0xff)
0613: + ((this .packetHeaderBuf[1] & 0xff) << 8)
0614: + ((this .packetHeaderBuf[2] & 0xff) << 16);
0615:
0616: if (this .traceProtocol) {
0617: StringBuffer traceMessageBuf = new StringBuffer();
0618:
0619: traceMessageBuf.append(Messages.getString("MysqlIO.2")); //$NON-NLS-1$
0620: traceMessageBuf.append(packetLength);
0621: traceMessageBuf.append(Messages.getString("MysqlIO.3")); //$NON-NLS-1$
0622: traceMessageBuf.append(StringUtils.dumpAsHex(
0623: this .packetHeaderBuf, 4));
0624:
0625: this .connection.getLog().logTrace(
0626: traceMessageBuf.toString());
0627: }
0628:
0629: byte multiPacketSeq = this .packetHeaderBuf[3];
0630:
0631: if (!this .packetSequenceReset) {
0632: if (this .enablePacketDebug && this .checkPacketSequence) {
0633: checkPacketSequencing(multiPacketSeq);
0634: }
0635: } else {
0636: this .packetSequenceReset = false;
0637: }
0638:
0639: this .readPacketSequence = multiPacketSeq;
0640:
0641: // Read data
0642: byte[] buffer = new byte[packetLength + 1];
0643: int numBytesRead = readFully(this .mysqlInput, buffer, 0,
0644: packetLength);
0645:
0646: if (numBytesRead != packetLength) {
0647: throw new IOException("Short read, expected "
0648: + packetLength + " bytes, only read "
0649: + numBytesRead);
0650: }
0651:
0652: buffer[packetLength] = 0;
0653:
0654: Buffer packet = new Buffer(buffer);
0655: packet.setBufLength(packetLength + 1);
0656:
0657: if (this .traceProtocol) {
0658: StringBuffer traceMessageBuf = new StringBuffer();
0659:
0660: traceMessageBuf.append(Messages.getString("MysqlIO.4")); //$NON-NLS-1$
0661: traceMessageBuf.append(getPacketDumpToLog(packet,
0662: packetLength));
0663:
0664: this .connection.getLog().logTrace(
0665: traceMessageBuf.toString());
0666: }
0667:
0668: if (this .enablePacketDebug) {
0669: enqueuePacketForDebugging(false, false, 0,
0670: this .packetHeaderBuf, packet);
0671: }
0672:
0673: return packet;
0674: } catch (IOException ioEx) {
0675: throw SQLError.createCommunicationsException(
0676: this .connection, this .lastPacketSentTimeMs, ioEx);
0677: } catch (OutOfMemoryError oom) {
0678: try {
0679: this .connection.realClose(false, false, true, oom);
0680: } finally {
0681: throw oom;
0682: }
0683: }
0684: }
0685:
0686: /**
0687: * Unpacks the Field information from the given packet. Understands pre 4.1
0688: * and post 4.1 server version field packet structures.
0689: *
0690: * @param packet the packet containing the field information
0691: * @param extractDefaultValues should default values be extracted?
0692: *
0693: * @return the unpacked field
0694: *
0695: * @throws SQLException DOCUMENT ME!
0696: */
0697: protected final Field unpackField(Buffer packet,
0698: boolean extractDefaultValues) throws SQLException {
0699: if (this .use41Extensions) {
0700: // we only store the position of the string and
0701: // materialize only if needed...
0702: if (this .has41NewNewProt) {
0703: // Not used yet, 5.0?
0704: int catalogNameStart = packet.getPosition() + 1;
0705: int catalogNameLength = packet.fastSkipLenString();
0706: catalogNameStart = adjustStartForFieldLength(
0707: catalogNameStart, catalogNameLength);
0708: }
0709:
0710: int databaseNameStart = packet.getPosition() + 1;
0711: int databaseNameLength = packet.fastSkipLenString();
0712: databaseNameStart = adjustStartForFieldLength(
0713: databaseNameStart, databaseNameLength);
0714:
0715: int tableNameStart = packet.getPosition() + 1;
0716: int tableNameLength = packet.fastSkipLenString();
0717: tableNameStart = adjustStartForFieldLength(tableNameStart,
0718: tableNameLength);
0719:
0720: // orgTableName is never used so skip
0721: int originalTableNameStart = packet.getPosition() + 1;
0722: int originalTableNameLength = packet.fastSkipLenString();
0723: originalTableNameStart = adjustStartForFieldLength(
0724: originalTableNameStart, originalTableNameLength);
0725:
0726: // we only store the position again...
0727: int nameStart = packet.getPosition() + 1;
0728: int nameLength = packet.fastSkipLenString();
0729:
0730: nameStart = adjustStartForFieldLength(nameStart, nameLength);
0731:
0732: // orgColName is not required so skip...
0733: int originalColumnNameStart = packet.getPosition() + 1;
0734: int originalColumnNameLength = packet.fastSkipLenString();
0735: originalColumnNameStart = adjustStartForFieldLength(
0736: originalColumnNameStart, originalColumnNameLength);
0737:
0738: packet.readByte();
0739:
0740: short charSetNumber = (short) packet.readInt();
0741:
0742: long colLength = 0;
0743:
0744: if (this .has41NewNewProt) {
0745: colLength = packet.readLong();
0746: } else {
0747: colLength = packet.readLongInt();
0748: }
0749:
0750: int colType = packet.readByte() & 0xff;
0751:
0752: short colFlag = 0;
0753:
0754: if (this .hasLongColumnInfo) {
0755: colFlag = (short) packet.readInt();
0756: } else {
0757: colFlag = (short) (packet.readByte() & 0xff);
0758: }
0759:
0760: int colDecimals = packet.readByte() & 0xff;
0761:
0762: int defaultValueStart = -1;
0763: int defaultValueLength = -1;
0764:
0765: if (extractDefaultValues) {
0766: defaultValueStart = packet.getPosition() + 1;
0767: defaultValueLength = packet.fastSkipLenString();
0768: }
0769:
0770: Field field = new Field(this .connection, packet
0771: .getByteBuffer(), databaseNameStart,
0772: databaseNameLength, tableNameStart,
0773: tableNameLength, originalTableNameStart,
0774: originalTableNameLength, nameStart, nameLength,
0775: originalColumnNameStart, originalColumnNameLength,
0776: colLength, colType, colFlag, colDecimals,
0777: defaultValueStart, defaultValueLength,
0778: charSetNumber);
0779:
0780: return field;
0781: }
0782:
0783: int tableNameStart = packet.getPosition() + 1;
0784: int tableNameLength = packet.fastSkipLenString();
0785: tableNameStart = adjustStartForFieldLength(tableNameStart,
0786: tableNameLength);
0787:
0788: int nameStart = packet.getPosition() + 1;
0789: int nameLength = packet.fastSkipLenString();
0790: nameStart = adjustStartForFieldLength(nameStart, nameLength);
0791:
0792: int colLength = packet.readnBytes();
0793: int colType = packet.readnBytes();
0794: packet.readByte(); // We know it's currently 2
0795:
0796: short colFlag = 0;
0797:
0798: if (this .hasLongColumnInfo) {
0799: colFlag = (short) (packet.readInt());
0800: } else {
0801: colFlag = (short) (packet.readByte() & 0xff);
0802: }
0803:
0804: int colDecimals = (packet.readByte() & 0xff);
0805:
0806: if (this .colDecimalNeedsBump) {
0807: colDecimals++;
0808: }
0809:
0810: Field field = new Field(this .connection,
0811: packet.getByteBuffer(), nameStart, nameLength,
0812: tableNameStart, tableNameLength, colLength, colType,
0813: colFlag, colDecimals);
0814:
0815: return field;
0816: }
0817:
0818: private int adjustStartForFieldLength(int nameStart, int nameLength) {
0819: if (nameLength < 251) {
0820: return nameStart;
0821: }
0822:
0823: if (nameLength >= 251 && nameLength < 65536) {
0824: return nameStart + 2;
0825: }
0826:
0827: if (nameLength >= 65536 && nameLength < 16777216) {
0828: return nameStart + 3;
0829: }
0830:
0831: return nameStart + 8;
0832: }
0833:
0834: protected boolean isSetNeededForAutoCommitMode(
0835: boolean autoCommitFlag) {
0836: if (this .use41Extensions
0837: && this .connection.getElideSetAutoCommits()) {
0838: boolean autoCommitModeOnServer = ((this .serverStatus & SERVER_STATUS_AUTOCOMMIT) != 0);
0839:
0840: if (!autoCommitFlag && versionMeetsMinimum(5, 0, 0)) {
0841: // Just to be safe, check if a transaction is in progress on the server....
0842: // if so, then we must be in autoCommit == false
0843: // therefore return the opposite of transaction status
0844: boolean inTransactionOnServer = ((this .serverStatus & SERVER_STATUS_IN_TRANS) != 0);
0845:
0846: return !inTransactionOnServer;
0847: }
0848:
0849: return autoCommitModeOnServer != autoCommitFlag;
0850: }
0851:
0852: return true;
0853: }
0854:
0855: protected boolean inTransactionOnServer() {
0856: return (this .serverStatus & SERVER_STATUS_IN_TRANS) != 0;
0857: }
0858:
0859: /**
0860: * Re-authenticates as the given user and password
0861: *
0862: * @param userName DOCUMENT ME!
0863: * @param password DOCUMENT ME!
0864: * @param database DOCUMENT ME!
0865: *
0866: * @throws SQLException DOCUMENT ME!
0867: */
0868: protected void changeUser(String userName, String password,
0869: String database) throws SQLException {
0870: this .packetSequence = -1;
0871:
0872: int passwordLength = 16;
0873: int userLength = (userName != null) ? userName.length() : 0;
0874: int databaseLength = (database != null) ? database.length() : 0;
0875:
0876: int packLength = ((userLength + passwordLength + databaseLength) * 2)
0877: + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;
0878:
0879: if ((this .serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
0880: Buffer changeUserPacket = new Buffer(packLength + 1);
0881: changeUserPacket
0882: .writeByte((byte) MysqlDefs.COM_CHANGE_USER);
0883:
0884: if (versionMeetsMinimum(4, 1, 1)) {
0885: secureAuth411(changeUserPacket, packLength, userName,
0886: password, database, false);
0887: } else {
0888: secureAuth(changeUserPacket, packLength, userName,
0889: password, database, false);
0890: }
0891: } else {
0892: // Passwords can be 16 chars long
0893: Buffer packet = new Buffer(packLength);
0894: packet.writeByte((byte) MysqlDefs.COM_CHANGE_USER);
0895:
0896: // User/Password data
0897: packet.writeString(userName);
0898:
0899: if (this .protocolVersion > 9) {
0900: packet.writeString(Util.newCrypt(password, this .seed));
0901: } else {
0902: packet.writeString(Util.oldCrypt(password, this .seed));
0903: }
0904:
0905: boolean localUseConnectWithDb = this .useConnectWithDb
0906: && (database != null && database.length() > 0);
0907:
0908: if (localUseConnectWithDb) {
0909: packet.writeString(database);
0910: }
0911:
0912: send(packet, packet.getPosition());
0913: checkErrorPacket();
0914:
0915: if (!localUseConnectWithDb) {
0916: changeDatabaseTo(database);
0917: }
0918: }
0919: }
0920:
0921: /**
0922: * Checks for errors in the reply packet, and if none, returns the reply
0923: * packet, ready for reading
0924: *
0925: * @return a packet ready for reading.
0926: *
0927: * @throws SQLException is the packet is an error packet
0928: */
0929: protected Buffer checkErrorPacket() throws SQLException {
0930: return checkErrorPacket(-1);
0931: }
0932:
0933: /**
0934: * Determines if the database charset is the same as the platform charset
0935: */
0936: protected void checkForCharsetMismatch() {
0937: if (this .connection.getUseUnicode()
0938: && (this .connection.getEncoding() != null)) {
0939: String encodingToCheck = jvmPlatformCharset;
0940:
0941: if (encodingToCheck == null) {
0942: encodingToCheck = System.getProperty("file.encoding"); //$NON-NLS-1$
0943: }
0944:
0945: if (encodingToCheck == null) {
0946: this .platformDbCharsetMatches = false;
0947: } else {
0948: this .platformDbCharsetMatches = encodingToCheck
0949: .equals(this .connection.getEncoding());
0950: }
0951: }
0952: }
0953:
0954: protected void clearInputStream() throws SQLException {
0955:
0956: try {
0957: int len = this .mysqlInput.available();
0958:
0959: while (len > 0) {
0960: this .mysqlInput.skip(len);
0961: len = this .mysqlInput.available();
0962: }
0963: } catch (IOException ioEx) {
0964: throw SQLError.createCommunicationsException(
0965: this .connection, this .lastPacketSentTimeMs, ioEx);
0966: }
0967: }
0968:
0969: protected void resetReadPacketSequence() {
0970: this .readPacketSequence = 0;
0971: }
0972:
0973: protected void dumpPacketRingBuffer() throws SQLException {
0974: if ((this .packetDebugRingBuffer != null)
0975: && this .connection.getEnablePacketDebug()) {
0976: StringBuffer dumpBuffer = new StringBuffer();
0977:
0978: dumpBuffer
0979: .append("Last "
0980: + this .packetDebugRingBuffer.size()
0981: + " packets received from server, from oldest->newest:\n");
0982: dumpBuffer.append("\n");
0983:
0984: for (Iterator ringBufIter = this .packetDebugRingBuffer
0985: .iterator(); ringBufIter.hasNext();) {
0986: dumpBuffer.append((StringBuffer) ringBufIter.next());
0987: dumpBuffer.append("\n");
0988: }
0989:
0990: this .connection.getLog().logTrace(dumpBuffer.toString());
0991: }
0992: }
0993:
0994: /**
0995: * Runs an 'EXPLAIN' on the given query and dumps the results to the log
0996: *
0997: * @param querySQL DOCUMENT ME!
0998: * @param truncatedQuery DOCUMENT ME!
0999: *
1000: * @throws SQLException DOCUMENT ME!
1001: */
1002: protected void explainSlowQuery(byte[] querySQL,
1003: String truncatedQuery) throws SQLException {
1004: if (StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery,
1005: "SELECT")) { //$NON-NLS-1$
1006:
1007: PreparedStatement stmt = null;
1008: java.sql.ResultSet rs = null;
1009:
1010: try {
1011: stmt = this .connection
1012: .clientPrepareStatement("EXPLAIN ?"); //$NON-NLS-1$
1013: stmt.setBytesNoEscapeNoQuotes(1, querySQL);
1014: rs = stmt.executeQuery();
1015:
1016: StringBuffer explainResults = new StringBuffer(Messages
1017: .getString("MysqlIO.8") + truncatedQuery //$NON-NLS-1$
1018: + Messages.getString("MysqlIO.9")); //$NON-NLS-1$
1019:
1020: ResultSetUtil.appendResultSetSlashGStyle(
1021: explainResults, rs);
1022:
1023: this .connection.getLog().logWarn(
1024: explainResults.toString());
1025: } catch (SQLException sqlEx) {
1026: } finally {
1027: if (rs != null) {
1028: rs.close();
1029: }
1030:
1031: if (stmt != null) {
1032: stmt.close();
1033: }
1034: }
1035: } else {
1036: }
1037: }
1038:
1039: static int getMaxBuf() {
1040: return maxBufferSize;
1041: }
1042:
1043: /**
1044: * Get the major version of the MySQL server we are talking to.
1045: *
1046: * @return DOCUMENT ME!
1047: */
1048: final int getServerMajorVersion() {
1049: return this .serverMajorVersion;
1050: }
1051:
1052: /**
1053: * Get the minor version of the MySQL server we are talking to.
1054: *
1055: * @return DOCUMENT ME!
1056: */
1057: final int getServerMinorVersion() {
1058: return this .serverMinorVersion;
1059: }
1060:
1061: /**
1062: * Get the sub-minor version of the MySQL server we are talking to.
1063: *
1064: * @return DOCUMENT ME!
1065: */
1066: final int getServerSubMinorVersion() {
1067: return this .serverSubMinorVersion;
1068: }
1069:
1070: /**
1071: * Get the version string of the server we are talking to
1072: *
1073: * @return DOCUMENT ME!
1074: */
1075: String getServerVersion() {
1076: return this .serverVersion;
1077: }
1078:
1079: /**
1080: * Initialize communications with the MySQL server. Handles logging on, and
1081: * handling initial connection errors.
1082: *
1083: * @param user DOCUMENT ME!
1084: * @param password DOCUMENT ME!
1085: * @param database DOCUMENT ME!
1086: *
1087: * @throws SQLException DOCUMENT ME!
1088: * @throws CommunicationsException DOCUMENT ME!
1089: */
1090: void doHandshake(String user, String password, String database)
1091: throws SQLException {
1092: // Read the first packet
1093: this .checkPacketSequence = false;
1094: this .readPacketSequence = 0;
1095:
1096: Buffer buf = readPacket();
1097:
1098: // Get the protocol version
1099: this .protocolVersion = buf.readByte();
1100:
1101: if (this .protocolVersion == -1) {
1102: try {
1103: this .mysqlConnection.close();
1104: } catch (Exception e) {
1105: // ignore
1106: }
1107:
1108: int errno = 2000;
1109:
1110: errno = buf.readInt();
1111:
1112: String serverErrorMessage = buf.readString();
1113:
1114: StringBuffer errorBuf = new StringBuffer(Messages
1115: .getString("MysqlIO.10")); //$NON-NLS-1$
1116: errorBuf.append(serverErrorMessage);
1117: errorBuf.append("\""); //$NON-NLS-1$
1118:
1119: String xOpen = SQLError.mysqlToSqlState(errno,
1120: this .connection.getUseSqlStateCodes());
1121:
1122: throw SQLError.createSQLException(SQLError.get(xOpen)
1123: + ", " //$NON-NLS-1$
1124: + errorBuf.toString(), xOpen, errno);
1125: }
1126:
1127: this .serverVersion = buf.readString();
1128:
1129: // Parse the server version into major/minor/subminor
1130: int point = this .serverVersion.indexOf('.'); //$NON-NLS-1$
1131:
1132: if (point != -1) {
1133: try {
1134: int n = Integer.parseInt(this .serverVersion.substring(
1135: 0, point));
1136: this .serverMajorVersion = n;
1137: } catch (NumberFormatException NFE1) {
1138: // ignore
1139: }
1140:
1141: String remaining = this .serverVersion.substring(point + 1,
1142: this .serverVersion.length());
1143: point = remaining.indexOf('.'); //$NON-NLS-1$
1144:
1145: if (point != -1) {
1146: try {
1147: int n = Integer.parseInt(remaining.substring(0,
1148: point));
1149: this .serverMinorVersion = n;
1150: } catch (NumberFormatException nfe) {
1151: // ignore
1152: }
1153:
1154: remaining = remaining.substring(point + 1, remaining
1155: .length());
1156:
1157: int pos = 0;
1158:
1159: while (pos < remaining.length()) {
1160: if ((remaining.charAt(pos) < '0')
1161: || (remaining.charAt(pos) > '9')) {
1162: break;
1163: }
1164:
1165: pos++;
1166: }
1167:
1168: try {
1169: int n = Integer.parseInt(remaining
1170: .substring(0, pos));
1171: this .serverSubMinorVersion = n;
1172: } catch (NumberFormatException nfe) {
1173: // ignore
1174: }
1175: }
1176: }
1177:
1178: if (versionMeetsMinimum(4, 0, 8)) {
1179: this .maxThreeBytes = (256 * 256 * 256) - 1;
1180: this .useNewLargePackets = true;
1181: } else {
1182: this .maxThreeBytes = 255 * 255 * 255;
1183: this .useNewLargePackets = false;
1184: }
1185:
1186: this .colDecimalNeedsBump = versionMeetsMinimum(3, 23, 0);
1187: this .colDecimalNeedsBump = !versionMeetsMinimum(3, 23, 15); // guess? Not noted in changelog
1188: this .useNewUpdateCounts = versionMeetsMinimum(3, 22, 5);
1189:
1190: threadId = buf.readLong();
1191: this .seed = buf.readString();
1192:
1193: this .serverCapabilities = 0;
1194:
1195: if (buf.getPosition() < buf.getBufLength()) {
1196: this .serverCapabilities = buf.readInt();
1197: }
1198:
1199: if (versionMeetsMinimum(4, 1, 1)) {
1200: int position = buf.getPosition();
1201:
1202: /* New protocol with 16 bytes to describe server characteristics */
1203: this .serverCharsetIndex = buf.readByte() & 0xff;
1204: this .serverStatus = buf.readInt();
1205: buf.setPosition(position + 16);
1206:
1207: String seedPart2 = buf.readString();
1208: StringBuffer newSeed = new StringBuffer(20);
1209: newSeed.append(this .seed);
1210: newSeed.append(seedPart2);
1211: this .seed = newSeed.toString();
1212: }
1213:
1214: if (((this .serverCapabilities & CLIENT_COMPRESS) != 0)
1215: && this .connection.getUseCompression()) {
1216: this .clientParam |= CLIENT_COMPRESS;
1217: }
1218:
1219: this .useConnectWithDb = (database != null)
1220: && (database.length() > 0)
1221: && !this .connection.getCreateDatabaseIfNotExist();
1222:
1223: if (this .useConnectWithDb) {
1224: this .clientParam |= CLIENT_CONNECT_WITH_DB;
1225: }
1226:
1227: if (((this .serverCapabilities & CLIENT_SSL) == 0)
1228: && this .connection.getUseSSL()) {
1229: if (this .connection.getRequireSSL()) {
1230: this .connection.close();
1231: forceClose();
1232: throw SQLError
1233: .createSQLException(
1234: Messages.getString("MysqlIO.15"), //$NON-NLS-1$
1235: SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
1236: }
1237:
1238: this .connection.setUseSSL(false);
1239: }
1240:
1241: if ((this .serverCapabilities & CLIENT_LONG_FLAG) != 0) {
1242: // We understand other column flags, as well
1243: this .clientParam |= CLIENT_LONG_FLAG;
1244: this .hasLongColumnInfo = true;
1245: }
1246:
1247: // return FOUND rows
1248: this .clientParam |= CLIENT_FOUND_ROWS;
1249:
1250: if (this .connection.getAllowLoadLocalInfile()) {
1251: this .clientParam |= CLIENT_LOCAL_FILES;
1252: }
1253:
1254: if (this .isInteractiveClient) {
1255: this .clientParam |= CLIENT_INTERACTIVE;
1256: }
1257:
1258: // Authenticate
1259: if (this .protocolVersion > 9) {
1260: this .clientParam |= CLIENT_LONG_PASSWORD; // for long passwords
1261: } else {
1262: this .clientParam &= ~CLIENT_LONG_PASSWORD;
1263: }
1264:
1265: //
1266: // 4.1 has some differences in the protocol
1267: //
1268: if (versionMeetsMinimum(4, 1, 0)) {
1269: if (versionMeetsMinimum(4, 1, 1)) {
1270: this .clientParam |= CLIENT_PROTOCOL_41;
1271: this .has41NewNewProt = true;
1272:
1273: // Need this to get server status values
1274: this .clientParam |= CLIENT_TRANSACTIONS;
1275:
1276: // We always allow multiple result sets
1277: this .clientParam |= CLIENT_MULTI_RESULTS;
1278:
1279: // We allow the user to configure whether
1280: // or not they want to support multiple queries
1281: // (by default, this is disabled).
1282: if (this .connection.getAllowMultiQueries()) {
1283: this .clientParam |= CLIENT_MULTI_QUERIES;
1284: }
1285: } else {
1286: this .clientParam |= CLIENT_RESERVED;
1287: this .has41NewNewProt = false;
1288: }
1289:
1290: this .use41Extensions = true;
1291: }
1292:
1293: int passwordLength = 16;
1294: int userLength = (user != null) ? user.length() : 0;
1295: int databaseLength = (database != null) ? database.length() : 0;
1296:
1297: int packLength = ((userLength + passwordLength + databaseLength) * 2)
1298: + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;
1299:
1300: Buffer packet = null;
1301:
1302: if (!this .connection.getUseSSL()) {
1303: if ((this .serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
1304: this .clientParam |= CLIENT_SECURE_CONNECTION;
1305:
1306: if (versionMeetsMinimum(4, 1, 1)) {
1307: secureAuth411(null, packLength, user, password,
1308: database, true);
1309: } else {
1310: secureAuth(null, packLength, user, password,
1311: database, true);
1312: }
1313: } else {
1314: // Passwords can be 16 chars long
1315: packet = new Buffer(packLength);
1316:
1317: if ((this .clientParam & CLIENT_RESERVED) != 0) {
1318: if (versionMeetsMinimum(4, 1, 1)) {
1319: packet.writeLong(this .clientParam);
1320: packet.writeLong(this .maxThreeBytes);
1321:
1322: // charset, JDBC will connect as 'latin1',
1323: // and use 'SET NAMES' to change to the desired
1324: // charset after the connection is established.
1325: packet.writeByte((byte) 8);
1326:
1327: // Set of bytes reserved for future use.
1328: packet.writeBytesNoNull(new byte[23]);
1329: } else {
1330: packet.writeLong(this .clientParam);
1331: packet.writeLong(this .maxThreeBytes);
1332: }
1333: } else {
1334: packet.writeInt((int) this .clientParam);
1335: packet.writeLongInt(this .maxThreeBytes);
1336: }
1337:
1338: // User/Password data
1339: packet.writeString(user, CODE_PAGE_1252,
1340: this .connection);
1341:
1342: if (this .protocolVersion > 9) {
1343: packet
1344: .writeString(Util.newCrypt(password,
1345: this .seed), CODE_PAGE_1252,
1346: this .connection);
1347: } else {
1348: packet
1349: .writeString(Util.oldCrypt(password,
1350: this .seed), CODE_PAGE_1252,
1351: this .connection);
1352: }
1353:
1354: if (this .useConnectWithDb) {
1355: packet.writeString(database, CODE_PAGE_1252,
1356: this .connection);
1357: }
1358:
1359: send(packet, packet.getPosition());
1360: }
1361: } else {
1362: negotiateSSLConnection(user, password, database, packLength);
1363: }
1364:
1365: // Check for errors, not for 4.1.1 or newer,
1366: // as the new auth protocol doesn't work that way
1367: // (see secureAuth411() for more details...)
1368: if (!versionMeetsMinimum(4, 1, 1)) {
1369: checkErrorPacket();
1370: }
1371:
1372: //
1373: // Can't enable compression until after handshake
1374: //
1375: if (((this .serverCapabilities & CLIENT_COMPRESS) != 0)
1376: && this .connection.getUseCompression()) {
1377: // The following matches with ZLIB's
1378: // compress()
1379: this .deflater = new Deflater();
1380: this .useCompression = true;
1381: this .mysqlInput = new CompressedInputStream(
1382: this .connection, this .mysqlInput);
1383: }
1384:
1385: if (!this .useConnectWithDb) {
1386: changeDatabaseTo(database);
1387: }
1388: }
1389:
1390: private void changeDatabaseTo(String database) throws SQLException {
1391: if (database == null || database.length() == 0) {
1392: return;
1393: }
1394:
1395: try {
1396: sendCommand(MysqlDefs.INIT_DB, database, null, false, null);
1397: } catch (Exception ex) {
1398: if (this .connection.getCreateDatabaseIfNotExist()) {
1399: sendCommand(MysqlDefs.QUERY,
1400: "CREATE DATABASE IF NOT EXISTS " + database,
1401: null, false, null);
1402: sendCommand(MysqlDefs.INIT_DB, database, null, false,
1403: null);
1404: } else {
1405: throw SQLError.createCommunicationsException(
1406: this .connection, this .lastPacketSentTimeMs, ex);
1407: }
1408: }
1409: }
1410:
1411: /**
1412: * Retrieve one row from the MySQL server. Note: this method is not
1413: * thread-safe, but it is only called from methods that are guarded by
1414: * synchronizing on this object.
1415: *
1416: * @param fields DOCUMENT ME!
1417: * @param columnCount DOCUMENT ME!
1418: * @param isBinaryEncoded DOCUMENT ME!
1419: * @param resultSetConcurrency DOCUMENT ME!
1420: * @param b
1421: *
1422: * @return DOCUMENT ME!
1423: *
1424: * @throws SQLException DOCUMENT ME!
1425: */
1426: final ResultSetRow nextRow(Field[] fields, int columnCount,
1427: boolean isBinaryEncoded, int resultSetConcurrency,
1428: boolean useBufferRowIfPossible,
1429: boolean useBufferRowExplicit,
1430: boolean canReuseRowPacketForBufferRow,
1431: Buffer existingRowPacket) throws SQLException {
1432:
1433: if (this .useDirectRowUnpack && existingRowPacket == null
1434: && !isBinaryEncoded && !useBufferRowIfPossible
1435: && !useBufferRowExplicit) {
1436: return nextRowFast(fields, columnCount, isBinaryEncoded,
1437: resultSetConcurrency, useBufferRowIfPossible,
1438: useBufferRowExplicit, canReuseRowPacketForBufferRow);
1439: }
1440:
1441: Buffer rowPacket = null;
1442:
1443: if (existingRowPacket == null) {
1444: rowPacket = checkErrorPacket();
1445:
1446: if (!useBufferRowExplicit && useBufferRowIfPossible) {
1447: if (rowPacket.getBufLength() > this .useBufferRowSizeThreshold) {
1448: useBufferRowExplicit = true;
1449: }
1450: }
1451: } else {
1452: // We attempted to do nextRowFast(), but the packet was a
1453: // multipacket, so we couldn't unpack it directly
1454: rowPacket = existingRowPacket;
1455: checkErrorPacket(existingRowPacket);
1456: }
1457:
1458: if (!isBinaryEncoded) {
1459: //
1460: // Didn't read an error, so re-position to beginning
1461: // of packet in order to read result set data
1462: //
1463: rowPacket.setPosition(rowPacket.getPosition() - 1);
1464:
1465: if (!rowPacket.isLastDataPacket()) {
1466: if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE
1467: || (!useBufferRowIfPossible && !useBufferRowExplicit)) {
1468:
1469: byte[][] rowData = new byte[columnCount][];
1470:
1471: for (int i = 0; i < columnCount; i++) {
1472: rowData[i] = rowPacket.readLenByteArray(0);
1473: }
1474:
1475: return new ByteArrayRow(rowData);
1476: }
1477:
1478: if (!canReuseRowPacketForBufferRow) {
1479: this .reusablePacket = new Buffer(rowPacket
1480: .getBufLength());
1481: }
1482:
1483: return new BufferRow(rowPacket, fields, false);
1484:
1485: }
1486:
1487: readServerStatusForResultSets(rowPacket);
1488:
1489: return null;
1490: }
1491:
1492: //
1493: // Handle binary-encoded data for server-side
1494: // PreparedStatements...
1495: //
1496: if (!rowPacket.isLastDataPacket()) {
1497: if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE
1498: || (!useBufferRowIfPossible && !useBufferRowExplicit)) {
1499: return unpackBinaryResultSetRow(fields, rowPacket,
1500: resultSetConcurrency);
1501: }
1502:
1503: if (!canReuseRowPacketForBufferRow) {
1504: this .reusablePacket = new Buffer(rowPacket
1505: .getBufLength());
1506: }
1507:
1508: return new BufferRow(rowPacket, fields, true);
1509: }
1510:
1511: rowPacket.setPosition(rowPacket.getPosition() - 1);
1512: readServerStatusForResultSets(rowPacket);
1513:
1514: return null;
1515: }
1516:
1517: final ResultSetRow nextRowFast(Field[] fields, int columnCount,
1518: boolean isBinaryEncoded, int resultSetConcurrency,
1519: boolean useBufferRowIfPossible,
1520: boolean useBufferRowExplicit, boolean canReuseRowPacket)
1521: throws SQLException {
1522: try {
1523: int lengthRead = readFully(this .mysqlInput,
1524: this .packetHeaderBuf, 0, 4);
1525:
1526: if (lengthRead < 4) {
1527: forceClose();
1528: throw new RuntimeException(Messages
1529: .getString("MysqlIO.43")); //$NON-NLS-1$
1530: }
1531:
1532: int packetLength = (this .packetHeaderBuf[0] & 0xff)
1533: + ((this .packetHeaderBuf[1] & 0xff) << 8)
1534: + ((this .packetHeaderBuf[2] & 0xff) << 16);
1535:
1536: // Have we stumbled upon a multi-packet?
1537: if (packetLength == this .maxThreeBytes) {
1538: reuseAndReadPacket(this .reusablePacket, packetLength);
1539:
1540: // Go back to "old" way which uses packets
1541: return nextRow(fields, columnCount, isBinaryEncoded,
1542: resultSetConcurrency, useBufferRowIfPossible,
1543: useBufferRowExplicit, canReuseRowPacket,
1544: this .reusablePacket);
1545: }
1546:
1547: // Does this go over the threshold where we should use a BufferRow?
1548:
1549: if (packetLength > this .useBufferRowSizeThreshold) {
1550: reuseAndReadPacket(this .reusablePacket, packetLength);
1551:
1552: // Go back to "old" way which uses packets
1553: return nextRow(fields, columnCount, isBinaryEncoded,
1554: resultSetConcurrency, true, true, false,
1555: this .reusablePacket);
1556: }
1557:
1558: int remaining = packetLength;
1559:
1560: boolean firstTime = true;
1561:
1562: byte[][] rowData = null;
1563:
1564: for (int i = 0; i < columnCount; i++) {
1565:
1566: int sw = this .mysqlInput.read() & 0xff;
1567: remaining--;
1568:
1569: if (firstTime) {
1570: if (sw == 255) {
1571: // error packet
1572: Buffer errorPacket = new Buffer(packetLength);
1573: errorPacket.writeByte((byte) sw);
1574: readFully(this .mysqlInput, errorPacket
1575: .getByteBuffer(), 1, packetLength - 1);
1576:
1577: checkErrorPacket(errorPacket);
1578: }
1579:
1580: if (sw == 254 && packetLength < 9) {
1581: this .warningCount = (this .mysqlInput.read() & 0xff)
1582: | ((this .mysqlInput.read() & 0xff) << 8);
1583: remaining -= 2;
1584:
1585: if (this .warningCount > 0) {
1586: this .hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
1587: }
1588:
1589: this .serverStatus = (this .mysqlInput.read() & 0xff)
1590: | ((this .mysqlInput.read() & 0xff) << 8);
1591: remaining -= 2;
1592:
1593: if (remaining > 0) {
1594: skipFully(this .mysqlInput, remaining);
1595: }
1596:
1597: return null; // last data packet
1598: }
1599:
1600: rowData = new byte[columnCount][];
1601:
1602: firstTime = false;
1603: }
1604:
1605: int len = 0;
1606:
1607: switch (sw) {
1608: case 251:
1609: len = NULL_LENGTH;
1610: break;
1611:
1612: case 252:
1613: len = (this .mysqlInput.read() & 0xff)
1614: | ((this .mysqlInput.read() & 0xff) << 8);
1615: remaining -= 2;
1616: break;
1617:
1618: case 253:
1619: len = (this .mysqlInput.read() & 0xff)
1620: | ((this .mysqlInput.read() & 0xff) << 8)
1621: | ((this .mysqlInput.read() & 0xff) << 16);
1622:
1623: remaining -= 3;
1624: break;
1625:
1626: case 254:
1627: len = (int) ((this .mysqlInput.read() & 0xff)
1628: | ((long) (this .mysqlInput.read() & 0xff) << 8)
1629: | ((long) (this .mysqlInput.read() & 0xff) << 16)
1630: | ((long) (this .mysqlInput.read() & 0xff) << 24)
1631: | ((long) (this .mysqlInput.read() & 0xff) << 32)
1632: | ((long) (this .mysqlInput.read() & 0xff) << 40)
1633: | ((long) (this .mysqlInput.read() & 0xff) << 48) | ((long) (this .mysqlInput
1634: .read() & 0xff) << 56));
1635: remaining -= 8;
1636: break;
1637:
1638: default:
1639: len = sw;
1640: }
1641:
1642: if (len == NULL_LENGTH) {
1643: rowData[i] = null;
1644: } else if (len == 0) {
1645: rowData[i] = Constants.EMPTY_BYTE_ARRAY;
1646: } else {
1647: rowData[i] = new byte[len];
1648:
1649: int bytesRead = readFully(this .mysqlInput,
1650: rowData[i], 0, len);
1651:
1652: if (bytesRead != len) {
1653: throw SQLError.createCommunicationsException(
1654: this .connection,
1655: this .lastPacketSentTimeMs,
1656: new IOException(Messages
1657: .getString("MysqlIO.43")));
1658: }
1659:
1660: remaining -= bytesRead;
1661: }
1662: }
1663:
1664: if (remaining > 0) {
1665: skipFully(this .mysqlInput, remaining);
1666: }
1667:
1668: return new ByteArrayRow(rowData);
1669: } catch (IOException ioEx) {
1670: throw SQLError.createCommunicationsException(
1671: this .connection, this .lastPacketSentTimeMs, ioEx);
1672: }
1673: }
1674:
1675: /**
1676: * Log-off of the MySQL server and close the socket.
1677: *
1678: * @throws SQLException DOCUMENT ME!
1679: */
1680: final void quit() throws SQLException {
1681: Buffer packet = new Buffer(6);
1682: this .packetSequence = -1;
1683: packet.writeByte((byte) MysqlDefs.QUIT);
1684: send(packet, packet.getPosition());
1685: forceClose();
1686: }
1687:
1688: /**
1689: * Returns the packet used for sending data (used by PreparedStatement)
1690: * Guarded by external synchronization on a mutex.
1691: *
1692: * @return A packet to send data with
1693: */
1694: Buffer getSharedSendPacket() {
1695: if (this .sharedSendPacket == null) {
1696: this .sharedSendPacket = new Buffer(INITIAL_PACKET_SIZE);
1697: }
1698:
1699: return this .sharedSendPacket;
1700: }
1701:
1702: void closeStreamer(RowData streamer) throws SQLException {
1703: if (this .streamingData == null) {
1704: throw SQLError.createSQLException(Messages
1705: .getString("MysqlIO.17") //$NON-NLS-1$
1706: + streamer + Messages.getString("MysqlIO.18")); //$NON-NLS-1$
1707: }
1708:
1709: if (streamer != this .streamingData) {
1710: throw SQLError.createSQLException(Messages
1711: .getString("MysqlIO.19") //$NON-NLS-1$
1712: + streamer + Messages.getString("MysqlIO.20") //$NON-NLS-1$
1713: + Messages.getString("MysqlIO.21") //$NON-NLS-1$
1714: + Messages.getString("MysqlIO.22")); //$NON-NLS-1$
1715: }
1716:
1717: this .streamingData = null;
1718: }
1719:
1720: ResultSetImpl readAllResults(StatementImpl callingStatement,
1721: int maxRows, int resultSetType, int resultSetConcurrency,
1722: boolean streamResults, String catalog, Buffer resultPacket,
1723: boolean isBinaryEncoded, long preSentColumnCount,
1724: Field[] metadataFromCache) throws SQLException {
1725: resultPacket.setPosition(resultPacket.getPosition() - 1);
1726:
1727: ResultSetImpl topLevelResultSet = readResultsForQueryOrUpdate(
1728: callingStatement, maxRows, resultSetType,
1729: resultSetConcurrency, streamResults, catalog,
1730: resultPacket, isBinaryEncoded, preSentColumnCount,
1731: metadataFromCache);
1732:
1733: ResultSetImpl currentResultSet = topLevelResultSet;
1734:
1735: boolean checkForMoreResults = ((this .clientParam & CLIENT_MULTI_RESULTS) != 0);
1736:
1737: boolean serverHasMoreResults = (this .serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0;
1738:
1739: //
1740: // TODO: We need to support streaming of multiple result sets
1741: //
1742: if (serverHasMoreResults && streamResults) {
1743: clearInputStream();
1744:
1745: throw SQLError.createSQLException(Messages
1746: .getString("MysqlIO.23"), //$NON-NLS-1$
1747: SQLError.SQL_STATE_DRIVER_NOT_CAPABLE);
1748: }
1749:
1750: boolean moreRowSetsExist = checkForMoreResults
1751: & serverHasMoreResults;
1752:
1753: while (moreRowSetsExist) {
1754: Buffer fieldPacket = checkErrorPacket();
1755: fieldPacket.setPosition(0);
1756:
1757: ResultSetImpl newResultSet = readResultsForQueryOrUpdate(
1758: callingStatement, maxRows, resultSetType,
1759: resultSetConcurrency, streamResults, catalog,
1760: fieldPacket, isBinaryEncoded, preSentColumnCount,
1761: metadataFromCache);
1762:
1763: currentResultSet.setNextResultSet(newResultSet);
1764:
1765: currentResultSet = newResultSet;
1766:
1767: moreRowSetsExist = (this .serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0;
1768: }
1769:
1770: if (!streamResults) {
1771: clearInputStream();
1772: }
1773:
1774: reclaimLargeReusablePacket();
1775:
1776: return topLevelResultSet;
1777: }
1778:
1779: /**
1780: * Sets the buffer size to max-buf
1781: */
1782: void resetMaxBuf() {
1783: this .maxAllowedPacket = this .connection.getMaxAllowedPacket();
1784: }
1785:
1786: /**
1787: * Send a command to the MySQL server If data is to be sent with command,
1788: * it should be put in extraData.
1789: *
1790: * Raw packets can be sent by setting queryPacket to something other
1791: * than null.
1792: *
1793: * @param command the MySQL protocol 'command' from MysqlDefs
1794: * @param extraData any 'string' data for the command
1795: * @param queryPacket a packet pre-loaded with data for the protocol (i.e.
1796: * from a client-side prepared statement).
1797: * @param skipCheck do not call checkErrorPacket() if true
1798: * @param extraDataCharEncoding the character encoding of the extraData
1799: * parameter.
1800: *
1801: * @return the response packet from the server
1802: *
1803: * @throws SQLException if an I/O error or SQL error occurs
1804: */
1805:
1806: final Buffer sendCommand(int command, String extraData,
1807: Buffer queryPacket, boolean skipCheck,
1808: String extraDataCharEncoding) throws SQLException {
1809: //
1810: // We cache these locally, per-command, as the checks
1811: // for them are in very 'hot' sections of the I/O code
1812: // and we save 10-15% in overall performance by doing this...
1813: //
1814: this .enablePacketDebug = this .connection.getEnablePacketDebug();
1815: this .traceProtocol = this .connection.getTraceProtocol();
1816: this .readPacketSequence = 0;
1817:
1818: try {
1819:
1820: checkForOutstandingStreamingData();
1821:
1822: // Clear serverStatus...this value is guarded by an
1823: // external mutex, as you can only ever be processing
1824: // one command at a time
1825: this .serverStatus = 0;
1826: this .hadWarnings = false;
1827: this .warningCount = 0;
1828:
1829: this .queryNoIndexUsed = false;
1830: this .queryBadIndexUsed = false;
1831:
1832: //
1833: // Compressed input stream needs cleared at beginning
1834: // of each command execution...
1835: //
1836: if (this .useCompression) {
1837: int bytesLeft = this .mysqlInput.available();
1838:
1839: if (bytesLeft > 0) {
1840: this .mysqlInput.skip(bytesLeft);
1841: }
1842: }
1843:
1844: try {
1845: clearInputStream();
1846:
1847: //
1848: // PreparedStatements construct their own packets,
1849: // for efficiency's sake.
1850: //
1851: // If this is a generic query, we need to re-use
1852: // the sending packet.
1853: //
1854: if (queryPacket == null) {
1855: int packLength = HEADER_LENGTH
1856: + COMP_HEADER_LENGTH
1857: + 1
1858: + ((extraData != null) ? extraData.length()
1859: : 0) + 2;
1860:
1861: if (this .sendPacket == null) {
1862: this .sendPacket = new Buffer(packLength);
1863: }
1864:
1865: this .packetSequence = -1;
1866: this .readPacketSequence = 0;
1867: this .checkPacketSequence = true;
1868: this .sendPacket.clear();
1869:
1870: this .sendPacket.writeByte((byte) command);
1871:
1872: if ((command == MysqlDefs.INIT_DB)
1873: || (command == MysqlDefs.CREATE_DB)
1874: || (command == MysqlDefs.DROP_DB)
1875: || (command == MysqlDefs.QUERY)
1876: || (command == MysqlDefs.COM_PREPARE)) {
1877: if (extraDataCharEncoding == null) {
1878: this .sendPacket
1879: .writeStringNoNull(extraData);
1880: } else {
1881: this .sendPacket
1882: .writeStringNoNull(
1883: extraData,
1884: extraDataCharEncoding,
1885: this .connection
1886: .getServerCharacterEncoding(),
1887: this .connection
1888: .parserKnowsUnicode(),
1889: this .connection);
1890: }
1891: } else if (command == MysqlDefs.PROCESS_KILL) {
1892: long id = Long.parseLong(extraData);
1893: this .sendPacket.writeLong(id);
1894: }
1895:
1896: send(this .sendPacket, this .sendPacket.getPosition());
1897: } else {
1898: this .packetSequence = -1;
1899: send(queryPacket, queryPacket.getPosition()); // packet passed by PreparedStatement
1900: }
1901: } catch (SQLException sqlEx) {
1902: // don't wrap SQLExceptions
1903: throw sqlEx;
1904: } catch (Exception ex) {
1905: throw SQLError.createCommunicationsException(
1906: this .connection, this .lastPacketSentTimeMs, ex);
1907: }
1908:
1909: Buffer returnPacket = null;
1910:
1911: if (!skipCheck) {
1912: if ((command == MysqlDefs.COM_EXECUTE)
1913: || (command == MysqlDefs.COM_RESET_STMT)) {
1914: this .readPacketSequence = 0;
1915: this .packetSequenceReset = true;
1916: }
1917:
1918: returnPacket = checkErrorPacket(command);
1919: }
1920:
1921: return returnPacket;
1922: } catch (IOException ioEx) {
1923: throw SQLError.createCommunicationsException(
1924: this .connection, this .lastPacketSentTimeMs, ioEx);
1925: }
1926: }
1927:
1928: private int statementExecutionDepth = 0;
1929:
1930: /**
1931: * Send a query stored in a packet directly to the server.
1932: *
1933: * @param callingStatement DOCUMENT ME!
1934: * @param resultSetConcurrency DOCUMENT ME!
1935: * @param characterEncoding DOCUMENT ME!
1936: * @param queryPacket DOCUMENT ME!
1937: * @param maxRows DOCUMENT ME!
1938: * @param conn DOCUMENT ME!
1939: * @param resultSetType DOCUMENT ME!
1940: * @param resultSetConcurrency DOCUMENT ME!
1941: * @param streamResults DOCUMENT ME!
1942: * @param catalog DOCUMENT ME!
1943: * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
1944: *
1945: * @return DOCUMENT ME!
1946: *
1947: * @throws Exception DOCUMENT ME!
1948: */
1949: final ResultSetInternalMethods sqlQueryDirect(
1950: StatementImpl callingStatement, String query,
1951: String characterEncoding, Buffer queryPacket, int maxRows,
1952: int resultSetType, int resultSetConcurrency,
1953: boolean streamResults, String catalog,
1954: Field[] cachedMetadata) throws Exception {
1955: this .statementExecutionDepth++;
1956:
1957: try {
1958: if (this .statementInterceptors != null) {
1959: ResultSetInternalMethods interceptedResults = invokeStatementInterceptorsPre(
1960: query, callingStatement);
1961:
1962: if (interceptedResults != null) {
1963: return interceptedResults;
1964: }
1965: }
1966:
1967: long queryStartTime = 0;
1968: long queryEndTime = 0;
1969:
1970: if (query != null) {
1971:
1972: // We don't know exactly how many bytes we're going to get
1973: // from the query. Since we're dealing with Unicode, the
1974: // max is 2, so pad it (2 * query) + space for headers
1975: int packLength = HEADER_LENGTH + 1
1976: + (query.length() * 2) + 2;
1977:
1978: String statementComment = this .connection
1979: .getStatementComment();
1980:
1981: byte[] commentAsBytes = null;
1982:
1983: if (statementComment != null) {
1984: commentAsBytes = StringUtils.getBytes(
1985: statementComment, null, characterEncoding,
1986: this .connection
1987: .getServerCharacterEncoding(),
1988: this .connection.parserKnowsUnicode());
1989:
1990: packLength += commentAsBytes.length;
1991: packLength += 6; // for /*[space] [space]*/
1992: }
1993:
1994: if (this .sendPacket == null) {
1995: this .sendPacket = new Buffer(packLength);
1996: } else {
1997: this .sendPacket.clear();
1998: }
1999:
2000: this .sendPacket.writeByte((byte) MysqlDefs.QUERY);
2001:
2002: if (commentAsBytes != null) {
2003: this .sendPacket
2004: .writeBytesNoNull(Constants.SLASH_STAR_SPACE_AS_BYTES);
2005: this .sendPacket.writeBytesNoNull(commentAsBytes);
2006: this .sendPacket
2007: .writeBytesNoNull(Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES);
2008: }
2009:
2010: if (characterEncoding != null) {
2011: if (this .platformDbCharsetMatches) {
2012: this .sendPacket.writeStringNoNull(query,
2013: characterEncoding, this .connection
2014: .getServerCharacterEncoding(),
2015: this .connection.parserKnowsUnicode(),
2016: this .connection);
2017: } else {
2018: if (StringUtils.startsWithIgnoreCaseAndWs(
2019: query, "LOAD DATA")) { //$NON-NLS-1$
2020: this .sendPacket.writeBytesNoNull(query
2021: .getBytes());
2022: } else {
2023: this .sendPacket
2024: .writeStringNoNull(
2025: query,
2026: characterEncoding,
2027: this .connection
2028: .getServerCharacterEncoding(),
2029: this .connection
2030: .parserKnowsUnicode(),
2031: this .connection);
2032: }
2033: }
2034: } else {
2035: this .sendPacket.writeStringNoNull(query);
2036: }
2037:
2038: queryPacket = this .sendPacket;
2039: }
2040:
2041: byte[] queryBuf = null;
2042: int oldPacketPosition = 0;
2043:
2044: if (needToGrabQueryFromPacket) {
2045: queryBuf = queryPacket.getByteBuffer();
2046:
2047: // save the packet position
2048: oldPacketPosition = queryPacket.getPosition();
2049:
2050: queryStartTime = getCurrentTimeNanosOrMillis();
2051: }
2052:
2053: // Send query command and sql query string
2054: Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null,
2055: queryPacket, false, null);
2056:
2057: long fetchBeginTime = 0;
2058: long fetchEndTime = 0;
2059:
2060: String profileQueryToLog = null;
2061:
2062: boolean queryWasSlow = false;
2063:
2064: if (this .profileSql || this .logSlowQueries) {
2065: queryEndTime = System.currentTimeMillis();
2066:
2067: boolean shouldExtractQuery = false;
2068:
2069: if (this .profileSql) {
2070: shouldExtractQuery = true;
2071: } else if (this .logSlowQueries
2072: && ((queryEndTime - queryStartTime) > this .connection
2073: .getSlowQueryThresholdMillis())) {
2074: shouldExtractQuery = true;
2075: queryWasSlow = true;
2076: }
2077:
2078: if (shouldExtractQuery) {
2079: // Extract the actual query from the network packet
2080: boolean truncated = false;
2081:
2082: int extractPosition = oldPacketPosition;
2083:
2084: if (oldPacketPosition > this .connection
2085: .getMaxQuerySizeToLog()) {
2086: extractPosition = this .connection
2087: .getMaxQuerySizeToLog() + 5;
2088: truncated = true;
2089: }
2090:
2091: profileQueryToLog = new String(queryBuf, 5,
2092: (extractPosition - 5));
2093:
2094: if (truncated) {
2095: profileQueryToLog += Messages
2096: .getString("MysqlIO.25"); //$NON-NLS-1$
2097: }
2098: }
2099:
2100: fetchBeginTime = queryEndTime;
2101: }
2102:
2103: if (this .autoGenerateTestcaseScript) {
2104: String testcaseQuery = null;
2105:
2106: if (query != null) {
2107: testcaseQuery = query;
2108: } else {
2109: testcaseQuery = new String(queryBuf, 5,
2110: (oldPacketPosition - 5));
2111: }
2112:
2113: StringBuffer debugBuf = new StringBuffer(testcaseQuery
2114: .length() + 32);
2115: this .connection
2116: .generateConnectionCommentBlock(debugBuf);
2117: debugBuf.append(testcaseQuery);
2118: debugBuf.append(';');
2119: this .connection.dumpTestcaseQuery(debugBuf.toString());
2120: }
2121:
2122: ResultSetInternalMethods rs = readAllResults(
2123: callingStatement, maxRows, resultSetType,
2124: resultSetConcurrency, streamResults, catalog,
2125: resultPacket, false, -1L, cachedMetadata);
2126:
2127: if (queryWasSlow) {
2128: StringBuffer mesgBuf = new StringBuffer(
2129: 48 + profileQueryToLog.length());
2130:
2131: mesgBuf
2132: .append(Messages
2133: .getString(
2134: "MysqlIO.SlowQuery",
2135: new Object[] {
2136: new Long(
2137: this .slowQueryThreshold),
2138: queryTimingUnits,
2139: new Long(
2140: queryEndTime
2141: - queryStartTime) }));
2142: mesgBuf.append(profileQueryToLog);
2143:
2144: ProfileEventSink eventSink = ProfileEventSink
2145: .getInstance(this .connection);
2146:
2147: eventSink.consumeEvent(new ProfilerEvent(
2148: ProfilerEvent.TYPE_SLOW_QUERY,
2149: "", catalog, this .connection.getId(), //$NON-NLS-1$
2150: (callingStatement != null) ? callingStatement
2151: .getId() : 999,
2152: ((ResultSetImpl) rs).resultId, System
2153: .currentTimeMillis(),
2154: (int) (queryEndTime - queryStartTime),
2155: queryTimingUnits, null, new Throwable(),
2156: mesgBuf.toString()));
2157:
2158: if (this .connection.getExplainSlowQueries()) {
2159: if (oldPacketPosition < MAX_QUERY_SIZE_TO_EXPLAIN) {
2160: explainSlowQuery(queryPacket.getBytes(5,
2161: (oldPacketPosition - 5)),
2162: profileQueryToLog);
2163: } else {
2164: this .connection
2165: .getLog()
2166: .logWarn(
2167: Messages
2168: .getString("MysqlIO.28") //$NON-NLS-1$
2169: + MAX_QUERY_SIZE_TO_EXPLAIN
2170: + Messages
2171: .getString("MysqlIO.29")); //$NON-NLS-1$
2172: }
2173: }
2174: }
2175:
2176: if (this .logSlowQueries) {
2177:
2178: ProfileEventSink eventSink = ProfileEventSink
2179: .getInstance(this .connection);
2180:
2181: if (this .queryBadIndexUsed) {
2182: eventSink
2183: .consumeEvent(new ProfilerEvent(
2184: ProfilerEvent.TYPE_SLOW_QUERY,
2185: "", catalog, //$NON-NLS-1$
2186: this .connection.getId(),
2187: (callingStatement != null) ? callingStatement
2188: .getId()
2189: : 999,
2190: ((ResultSetImpl) rs).resultId,
2191: System.currentTimeMillis(),
2192: (queryEndTime - queryStartTime),
2193: this .queryTimingUnits, null,
2194: new Throwable(), Messages
2195: .getString("MysqlIO.33") //$NON-NLS-1$
2196: + profileQueryToLog));
2197: }
2198:
2199: if (this .queryNoIndexUsed) {
2200: eventSink
2201: .consumeEvent(new ProfilerEvent(
2202: ProfilerEvent.TYPE_SLOW_QUERY,
2203: "", catalog, //$NON-NLS-1$
2204: this .connection.getId(),
2205: (callingStatement != null) ? callingStatement
2206: .getId()
2207: : 999,
2208: ((ResultSetImpl) rs).resultId,
2209: System.currentTimeMillis(),
2210: (queryEndTime - queryStartTime),
2211: this .queryTimingUnits, null,
2212: new Throwable(), Messages
2213: .getString("MysqlIO.35") //$NON-NLS-1$
2214: + profileQueryToLog));
2215: }
2216: }
2217:
2218: if (this .profileSql) {
2219: fetchEndTime = getCurrentTimeNanosOrMillis();
2220:
2221: ProfileEventSink eventSink = ProfileEventSink
2222: .getInstance(this .connection);
2223:
2224: eventSink.consumeEvent(new ProfilerEvent(
2225: ProfilerEvent.TYPE_QUERY,
2226: "", catalog, this .connection.getId(), //$NON-NLS-1$
2227: (callingStatement != null) ? callingStatement
2228: .getId() : 999,
2229: ((ResultSetImpl) rs).resultId, System
2230: .currentTimeMillis(),
2231: (queryEndTime - queryStartTime),
2232: this .queryTimingUnits, null, new Throwable(),
2233: profileQueryToLog));
2234:
2235: eventSink.consumeEvent(new ProfilerEvent(
2236: ProfilerEvent.TYPE_FETCH,
2237: "", catalog, this .connection.getId(), //$NON-NLS-1$
2238: (callingStatement != null) ? callingStatement
2239: .getId() : 999,
2240: ((ResultSetImpl) rs).resultId, System
2241: .currentTimeMillis(),
2242: (fetchEndTime - fetchBeginTime),
2243: this .queryTimingUnits, null, new Throwable(),
2244: null));
2245: }
2246:
2247: if (this .hadWarnings) {
2248: scanForAndThrowDataTruncation();
2249: }
2250:
2251: if (this .statementInterceptors != null) {
2252: ResultSetInternalMethods interceptedResults = invokeStatementInterceptorsPost(
2253: query, callingStatement, rs);
2254:
2255: if (interceptedResults != null) {
2256: rs = interceptedResults;
2257: }
2258: }
2259:
2260: return rs;
2261: } finally {
2262: this .statementExecutionDepth--;
2263: }
2264: }
2265:
2266: private ResultSetInternalMethods invokeStatementInterceptorsPre(
2267: String sql, Statement interceptedStatement)
2268: throws SQLException {
2269: ResultSetInternalMethods previousResultSet = null;
2270:
2271: Iterator interceptors = this .statementInterceptors.iterator();
2272:
2273: while (interceptors.hasNext()) {
2274: StatementInterceptor interceptor = ((StatementInterceptor) interceptors
2275: .next());
2276:
2277: boolean executeTopLevelOnly = interceptor
2278: .executeTopLevelOnly();
2279: boolean shouldExecute = (executeTopLevelOnly && this .statementExecutionDepth == 1)
2280: || (!executeTopLevelOnly);
2281:
2282: if (shouldExecute) {
2283: String sqlToInterceptor = sql;
2284:
2285: if (interceptedStatement instanceof PreparedStatement) {
2286: sqlToInterceptor = ((PreparedStatement) interceptedStatement)
2287: .asSql();
2288: }
2289:
2290: ResultSetInternalMethods interceptedResultSet = interceptor
2291: .preProcess(sqlToInterceptor,
2292: interceptedStatement, this .connection);
2293:
2294: if (interceptedResultSet != null) {
2295: previousResultSet = interceptedResultSet;
2296: }
2297: }
2298: }
2299:
2300: return previousResultSet;
2301: }
2302:
2303: private ResultSetInternalMethods invokeStatementInterceptorsPost(
2304: String sql, Statement interceptedStatement,
2305: ResultSetInternalMethods originalResultSet)
2306: throws SQLException {
2307: Iterator interceptors = this .statementInterceptors.iterator();
2308:
2309: while (interceptors.hasNext()) {
2310: StatementInterceptor interceptor = ((StatementInterceptor) interceptors
2311: .next());
2312:
2313: boolean executeTopLevelOnly = interceptor
2314: .executeTopLevelOnly();
2315: boolean shouldExecute = (executeTopLevelOnly && this .statementExecutionDepth == 1)
2316: || (!executeTopLevelOnly);
2317:
2318: if (shouldExecute) {
2319: String sqlToInterceptor = sql;
2320:
2321: if (interceptedStatement instanceof PreparedStatement) {
2322: sqlToInterceptor = ((PreparedStatement) interceptedStatement)
2323: .asSql();
2324: }
2325:
2326: ResultSetInternalMethods interceptedResultSet = interceptor
2327: .postProcess(sqlToInterceptor,
2328: interceptedStatement,
2329: originalResultSet, this .connection);
2330:
2331: if (interceptedResultSet != null) {
2332: originalResultSet = interceptedResultSet;
2333: }
2334: }
2335: }
2336:
2337: return originalResultSet;
2338: }
2339:
2340: private void calculateSlowQueryThreshold() {
2341: this .slowQueryThreshold = this .connection
2342: .getSlowQueryThresholdMillis();
2343:
2344: if (this .connection.getUseNanosForElapsedTime()) {
2345: long nanosThreshold = this .connection
2346: .getSlowQueryThresholdNanos();
2347:
2348: if (nanosThreshold != 0) {
2349: this .slowQueryThreshold = nanosThreshold;
2350: } else {
2351: this .slowQueryThreshold *= 1000000; // 1 million millis in a nano
2352: }
2353: }
2354: }
2355:
2356: protected long getCurrentTimeNanosOrMillis() {
2357: if (this .useNanosForElapsedTime) {
2358: return Util.getCurrentTimeNanosOrMillis();
2359: }
2360:
2361: return System.currentTimeMillis();
2362: }
2363:
2364: /**
2365: * Returns the host this IO is connected to
2366: *
2367: * @return DOCUMENT ME!
2368: */
2369: String getHost() {
2370: return this .host;
2371: }
2372:
2373: /**
2374: * Is the version of the MySQL server we are connected to the given
2375: * version?
2376: *
2377: * @param major the major version
2378: * @param minor the minor version
2379: * @param subminor the subminor version
2380: *
2381: * @return true if the version of the MySQL server we are connected is the
2382: * given version
2383: */
2384: boolean isVersion(int major, int minor, int subminor) {
2385: return ((major == getServerMajorVersion())
2386: && (minor == getServerMinorVersion()) && (subminor == getServerSubMinorVersion()));
2387: }
2388:
2389: /**
2390: * Does the version of the MySQL server we are connected to meet the given
2391: * minimums?
2392: *
2393: * @param major DOCUMENT ME!
2394: * @param minor DOCUMENT ME!
2395: * @param subminor DOCUMENT ME!
2396: *
2397: * @return DOCUMENT ME!
2398: */
2399: boolean versionMeetsMinimum(int major, int minor, int subminor) {
2400: if (getServerMajorVersion() >= major) {
2401: if (getServerMajorVersion() == major) {
2402: if (getServerMinorVersion() >= minor) {
2403: if (getServerMinorVersion() == minor) {
2404: return (getServerSubMinorVersion() >= subminor);
2405: }
2406:
2407: // newer than major.minor
2408: return true;
2409: }
2410:
2411: // older than major.minor
2412: return false;
2413: }
2414:
2415: // newer than major
2416: return true;
2417: }
2418:
2419: return false;
2420: }
2421:
2422: /**
2423: * Returns the hex dump of the given packet, truncated to
2424: * MAX_PACKET_DUMP_LENGTH if packetLength exceeds that value.
2425: *
2426: * @param packetToDump the packet to dump in hex
2427: * @param packetLength the number of bytes to dump
2428: *
2429: * @return the hex dump of the given packet
2430: */
2431: private final static String getPacketDumpToLog(Buffer packetToDump,
2432: int packetLength) {
2433: if (packetLength < MAX_PACKET_DUMP_LENGTH) {
2434: return packetToDump.dump(packetLength);
2435: }
2436:
2437: StringBuffer packetDumpBuf = new StringBuffer(
2438: MAX_PACKET_DUMP_LENGTH * 4);
2439: packetDumpBuf.append(packetToDump.dump(MAX_PACKET_DUMP_LENGTH));
2440: packetDumpBuf.append(Messages.getString("MysqlIO.36")); //$NON-NLS-1$
2441: packetDumpBuf.append(MAX_PACKET_DUMP_LENGTH);
2442: packetDumpBuf.append(Messages.getString("MysqlIO.37")); //$NON-NLS-1$
2443:
2444: return packetDumpBuf.toString();
2445: }
2446:
2447: private final int readFully(InputStream in, byte[] b, int off,
2448: int len) throws IOException {
2449: if (len < 0) {
2450: throw new IndexOutOfBoundsException();
2451: }
2452:
2453: int n = 0;
2454:
2455: while (n < len) {
2456: int count = in.read(b, off + n, len - n);
2457:
2458: if (count < 0) {
2459: throw new EOFException(Messages.getString(
2460: "MysqlIO.EOF", new Object[] { new Integer(len),
2461: new Integer(n) }));
2462: }
2463:
2464: n += count;
2465: }
2466:
2467: return n;
2468: }
2469:
2470: private final long skipFully(InputStream in, long len)
2471: throws IOException {
2472: if (len < 0) {
2473: throw new IOException("Negative skip length not allowed");
2474: }
2475:
2476: long n = 0;
2477:
2478: while (n < len) {
2479: long count = in.skip(len - n);
2480:
2481: if (count < 0) {
2482: throw new EOFException(Messages.getString(
2483: "MysqlIO.EOF", new Object[] { new Long(len),
2484: new Long(n) }));
2485: }
2486:
2487: n += count;
2488: }
2489:
2490: return n;
2491: }
2492:
2493: /**
2494: * Reads one result set off of the wire, if the result is actually an
2495: * update count, creates an update-count only result set.
2496: *
2497: * @param callingStatement DOCUMENT ME!
2498: * @param maxRows the maximum rows to return in the result set.
2499: * @param resultSetType scrollability
2500: * @param resultSetConcurrency updatability
2501: * @param streamResults should the driver leave the results on the wire,
2502: * and read them only when needed?
2503: * @param catalog the catalog in use
2504: * @param resultPacket the first packet of information in the result set
2505: * @param isBinaryEncoded is this result set from a prepared statement?
2506: * @param preSentColumnCount do we already know the number of columns?
2507: * @param unpackFieldInfo should we unpack the field information?
2508: *
2509: * @return a result set that either represents the rows, or an update count
2510: *
2511: * @throws SQLException if an error occurs while reading the rows
2512: */
2513: private final ResultSetImpl readResultsForQueryOrUpdate(
2514: StatementImpl callingStatement, int maxRows,
2515: int resultSetType, int resultSetConcurrency,
2516: boolean streamResults, String catalog, Buffer resultPacket,
2517: boolean isBinaryEncoded, long preSentColumnCount,
2518: Field[] metadataFromCache) throws SQLException {
2519: long columnCount = resultPacket.readFieldLength();
2520:
2521: if (columnCount == 0) {
2522: return buildResultSetWithUpdates(callingStatement,
2523: resultPacket);
2524: } else if (columnCount == Buffer.NULL_LENGTH) {
2525: String charEncoding = null;
2526:
2527: if (this .connection.getUseUnicode()) {
2528: charEncoding = this .connection.getEncoding();
2529: }
2530:
2531: String fileName = null;
2532:
2533: if (this .platformDbCharsetMatches) {
2534: fileName = ((charEncoding != null) ? resultPacket
2535: .readString(charEncoding) : resultPacket
2536: .readString());
2537: } else {
2538: fileName = resultPacket.readString();
2539: }
2540:
2541: return sendFileToServer(callingStatement, fileName);
2542: } else {
2543: com.mysql.jdbc.ResultSetImpl results = getResultSet(
2544: callingStatement, columnCount, maxRows,
2545: resultSetType, resultSetConcurrency, streamResults,
2546: catalog, isBinaryEncoded, metadataFromCache);
2547:
2548: return results;
2549: }
2550: }
2551:
2552: private int alignPacketSize(int a, int l) {
2553: return ((((a) + (l)) - 1) & ~((l) - 1));
2554: }
2555:
2556: private com.mysql.jdbc.ResultSetImpl buildResultSetWithRows(
2557: StatementImpl callingStatement, String catalog,
2558: com.mysql.jdbc.Field[] fields, RowData rows,
2559: int resultSetType, int resultSetConcurrency,
2560: boolean isBinaryEncoded) throws SQLException {
2561: ResultSetImpl rs = null;
2562:
2563: switch (resultSetConcurrency) {
2564: case java.sql.ResultSet.CONCUR_READ_ONLY:
2565: rs = com.mysql.jdbc.ResultSetImpl.getInstance(catalog,
2566: fields, rows, this .connection, callingStatement,
2567: false);
2568:
2569: if (isBinaryEncoded) {
2570: rs.setBinaryEncoded();
2571: }
2572:
2573: break;
2574:
2575: case java.sql.ResultSet.CONCUR_UPDATABLE:
2576: rs = com.mysql.jdbc.ResultSetImpl.getInstance(catalog,
2577: fields, rows, this .connection, callingStatement,
2578: true);
2579:
2580: break;
2581:
2582: default:
2583: return com.mysql.jdbc.ResultSetImpl.getInstance(catalog,
2584: fields, rows, this .connection, callingStatement,
2585: false);
2586: }
2587:
2588: rs.setResultSetType(resultSetType);
2589: rs.setResultSetConcurrency(resultSetConcurrency);
2590:
2591: return rs;
2592: }
2593:
2594: private com.mysql.jdbc.ResultSetImpl buildResultSetWithUpdates(
2595: StatementImpl callingStatement, Buffer resultPacket)
2596: throws SQLException {
2597: long updateCount = -1;
2598: long updateID = -1;
2599: String info = null;
2600:
2601: try {
2602: if (this .useNewUpdateCounts) {
2603: updateCount = resultPacket.newReadLength();
2604: updateID = resultPacket.newReadLength();
2605: } else {
2606: updateCount = resultPacket.readLength();
2607: updateID = resultPacket.readLength();
2608: }
2609:
2610: if (this .use41Extensions) {
2611: this .serverStatus = resultPacket.readInt();
2612:
2613: this .warningCount = resultPacket.readInt();
2614:
2615: if (this .warningCount > 0) {
2616: this .hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
2617: }
2618:
2619: resultPacket.readByte(); // advance pointer
2620:
2621: if (this .profileSql) {
2622: this .queryNoIndexUsed = (this .serverStatus & SERVER_QUERY_NO_GOOD_INDEX_USED) != 0;
2623: this .queryBadIndexUsed = (this .serverStatus & SERVER_QUERY_NO_INDEX_USED) != 0;
2624: }
2625: }
2626:
2627: if (this .connection.isReadInfoMsgEnabled()) {
2628: info = resultPacket.readString();
2629: }
2630: } catch (Exception ex) {
2631: throw SQLError.createSQLException(SQLError
2632: .get(SQLError.SQL_STATE_GENERAL_ERROR)
2633: + ": " //$NON-NLS-1$
2634: + ex.getClass().getName(),
2635: SQLError.SQL_STATE_GENERAL_ERROR, -1);
2636: }
2637:
2638: ResultSetInternalMethods updateRs = com.mysql.jdbc.ResultSetImpl
2639: .getInstance(updateCount, updateID, this .connection,
2640: callingStatement);
2641:
2642: if (info != null) {
2643: ((com.mysql.jdbc.ResultSetImpl) updateRs)
2644: .setServerInfo(info);
2645: }
2646:
2647: return (com.mysql.jdbc.ResultSetImpl) updateRs;
2648: }
2649:
2650: private void checkForOutstandingStreamingData() throws SQLException {
2651: if (this .streamingData != null) {
2652: boolean shouldClobber = this .connection
2653: .getClobberStreamingResults();
2654:
2655: if (!shouldClobber) {
2656: throw SQLError.createSQLException(Messages
2657: .getString("MysqlIO.39") //$NON-NLS-1$
2658: + this .streamingData
2659: + Messages.getString("MysqlIO.40") //$NON-NLS-1$
2660: + Messages.getString("MysqlIO.41") //$NON-NLS-1$
2661: + Messages.getString("MysqlIO.42")); //$NON-NLS-1$
2662: }
2663:
2664: // Close the result set
2665: this .streamingData.getOwner().realClose(false);
2666:
2667: // clear any pending data....
2668: clearInputStream();
2669: }
2670: }
2671:
2672: private Buffer compressPacket(Buffer packet, int offset,
2673: int packetLen, int headerLength) throws SQLException {
2674: packet.writeLongInt(packetLen - headerLength);
2675: packet.writeByte((byte) 0); // wrapped packet has 0 packet seq.
2676:
2677: int lengthToWrite = 0;
2678: int compressedLength = 0;
2679: byte[] bytesToCompress = packet.getByteBuffer();
2680: byte[] compressedBytes = null;
2681: int offsetWrite = 0;
2682:
2683: if (packetLen < MIN_COMPRESS_LEN) {
2684: lengthToWrite = packetLen;
2685: compressedBytes = packet.getByteBuffer();
2686: compressedLength = 0;
2687: offsetWrite = offset;
2688: } else {
2689: compressedBytes = new byte[bytesToCompress.length * 2];
2690:
2691: this .deflater.reset();
2692: this .deflater.setInput(bytesToCompress, offset, packetLen);
2693: this .deflater.finish();
2694:
2695: int compLen = this .deflater.deflate(compressedBytes);
2696:
2697: if (compLen > packetLen) {
2698: lengthToWrite = packetLen;
2699: compressedBytes = packet.getByteBuffer();
2700: compressedLength = 0;
2701: offsetWrite = offset;
2702: } else {
2703: lengthToWrite = compLen;
2704: headerLength += COMP_HEADER_LENGTH;
2705: compressedLength = packetLen;
2706: }
2707: }
2708:
2709: Buffer compressedPacket = new Buffer(packetLen + headerLength);
2710:
2711: compressedPacket.setPosition(0);
2712: compressedPacket.writeLongInt(lengthToWrite);
2713: compressedPacket.writeByte(this .packetSequence);
2714: compressedPacket.writeLongInt(compressedLength);
2715: compressedPacket.writeBytesNoNull(compressedBytes, offsetWrite,
2716: lengthToWrite);
2717:
2718: return compressedPacket;
2719: }
2720:
2721: private final void readServerStatusForResultSets(Buffer rowPacket)
2722: throws SQLException {
2723: if (this .use41Extensions) {
2724: rowPacket.readByte(); // skips the 'last packet' flag
2725:
2726: this .warningCount = rowPacket.readInt();
2727:
2728: if (this .warningCount > 0) {
2729: this .hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
2730: }
2731:
2732: this .serverStatus = rowPacket.readInt();
2733:
2734: if (this .profileSql) {
2735: this .queryNoIndexUsed = (this .serverStatus & SERVER_QUERY_NO_GOOD_INDEX_USED) != 0;
2736: this .queryBadIndexUsed = (this .serverStatus & SERVER_QUERY_NO_INDEX_USED) != 0;
2737: }
2738: }
2739: }
2740:
2741: private SocketFactory createSocketFactory() throws SQLException {
2742: try {
2743: if (this .socketFactoryClassName == null) {
2744: throw SQLError
2745: .createSQLException(
2746: Messages.getString("MysqlIO.75"), //$NON-NLS-1$
2747: SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
2748: }
2749:
2750: return (SocketFactory) (Class
2751: .forName(this .socketFactoryClassName).newInstance());
2752: } catch (Exception ex) {
2753: throw SQLError.createSQLException(Messages
2754: .getString("MysqlIO.76") //$NON-NLS-1$
2755: + this .socketFactoryClassName
2756: + Messages.getString("MysqlIO.77") + ex.toString() //$NON-NLS-1$
2757: + (this .connection.getParanoid() ? "" //$NON-NLS-1$
2758: : Util.stackTraceToString(ex)),
2759: SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
2760: }
2761: }
2762:
2763: private void enqueuePacketForDebugging(boolean isPacketBeingSent,
2764: boolean isPacketReused, int sendLength, byte[] header,
2765: Buffer packet) throws SQLException {
2766: if ((this .packetDebugRingBuffer.size() + 1) > this .connection
2767: .getPacketDebugBufferSize()) {
2768: this .packetDebugRingBuffer.removeFirst();
2769: }
2770:
2771: StringBuffer packetDump = null;
2772:
2773: if (!isPacketBeingSent) {
2774: int bytesToDump = Math.min(MAX_PACKET_DUMP_LENGTH, packet
2775: .getBufLength());
2776:
2777: Buffer packetToDump = new Buffer(4 + bytesToDump);
2778:
2779: packetToDump.setPosition(0);
2780: packetToDump.writeBytesNoNull(header);
2781: packetToDump.writeBytesNoNull(packet.getBytes(0,
2782: bytesToDump));
2783:
2784: String packetPayload = packetToDump.dump(bytesToDump);
2785:
2786: packetDump = new StringBuffer(96 + packetPayload.length());
2787:
2788: packetDump.append("Server ");
2789:
2790: if (isPacketReused) {
2791: packetDump.append("(re-used)");
2792: } else {
2793: packetDump.append("(new)");
2794: }
2795:
2796: packetDump.append(" ");
2797: packetDump.append(packet.toSuperString());
2798: packetDump.append(" --------------------> Client\n");
2799: packetDump.append("\nPacket payload:\n\n");
2800: packetDump.append(packetPayload);
2801:
2802: if (bytesToDump == MAX_PACKET_DUMP_LENGTH) {
2803: packetDump.append("\nNote: Packet of "
2804: + packet.getBufLength()
2805: + " bytes truncated to "
2806: + MAX_PACKET_DUMP_LENGTH + " bytes.\n");
2807: }
2808: } else {
2809: int bytesToDump = Math.min(MAX_PACKET_DUMP_LENGTH,
2810: sendLength);
2811:
2812: String packetPayload = packet.dump(bytesToDump);
2813:
2814: packetDump = new StringBuffer(64 + 4 + packetPayload
2815: .length());
2816:
2817: packetDump.append("Client ");
2818: packetDump.append(packet.toSuperString());
2819: packetDump.append("--------------------> Server\n");
2820: packetDump.append("\nPacket payload:\n\n");
2821: packetDump.append(packetPayload);
2822:
2823: if (bytesToDump == MAX_PACKET_DUMP_LENGTH) {
2824: packetDump.append("\nNote: Packet of " + sendLength
2825: + " bytes truncated to "
2826: + MAX_PACKET_DUMP_LENGTH + " bytes.\n");
2827: }
2828: }
2829:
2830: this .packetDebugRingBuffer.addLast(packetDump);
2831: }
2832:
2833: private RowData readSingleRowSet(long columnCount, int maxRows,
2834: int resultSetConcurrency, boolean isBinaryEncoded,
2835: Field[] fields) throws SQLException {
2836: RowData rowData;
2837: ArrayList rows = new ArrayList();
2838:
2839: boolean useBufferRowExplicit = useBufferRowExplicit(fields);
2840:
2841: // Now read the data
2842: ResultSetRow row = nextRow(fields, (int) columnCount,
2843: isBinaryEncoded, resultSetConcurrency, false,
2844: useBufferRowExplicit, false, null);
2845:
2846: int rowCount = 0;
2847:
2848: if (row != null) {
2849: rows.add(row);
2850: rowCount = 1;
2851: }
2852:
2853: while (row != null) {
2854: row = nextRow(fields, (int) columnCount, isBinaryEncoded,
2855: resultSetConcurrency, false, useBufferRowExplicit,
2856: false, null);
2857:
2858: if (row != null) {
2859: if ((maxRows == -1) || (rowCount < maxRows)) {
2860: rows.add(row);
2861: rowCount++;
2862: }
2863: }
2864: }
2865:
2866: rowData = new RowDataStatic(rows);
2867:
2868: return rowData;
2869: }
2870:
2871: public static boolean useBufferRowExplicit(Field[] fields) {
2872: if (fields == null) {
2873: return false;
2874: }
2875:
2876: for (int i = 0; i < fields.length; i++) {
2877: switch (fields[i].getSQLType()) {
2878: case Types.BLOB:
2879: case Types.CLOB:
2880: case Types.LONGVARBINARY:
2881: case Types.LONGVARCHAR:
2882: return true;
2883: }
2884: }
2885:
2886: return false;
2887: }
2888:
2889: /**
2890: * Don't hold on to overly-large packets
2891: */
2892: private void reclaimLargeReusablePacket() {
2893: if ((this .reusablePacket != null)
2894: && (this .reusablePacket.getCapacity() > 1048576)) {
2895: this .reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
2896: }
2897: }
2898:
2899: /**
2900: * Re-use a packet to read from the MySQL server
2901: *
2902: * @param reuse DOCUMENT ME!
2903: *
2904: * @return DOCUMENT ME!
2905: *
2906: * @throws SQLException DOCUMENT ME!
2907: * @throws SQLException DOCUMENT ME!
2908: */
2909: private final Buffer reuseAndReadPacket(Buffer reuse)
2910: throws SQLException {
2911: return reuseAndReadPacket(reuse, -1);
2912: }
2913:
2914: private final Buffer reuseAndReadPacket(Buffer reuse,
2915: int existingPacketLength) throws SQLException {
2916:
2917: try {
2918: reuse.setWasMultiPacket(false);
2919: int packetLength = 0;
2920:
2921: if (existingPacketLength == -1) {
2922: int lengthRead = readFully(this .mysqlInput,
2923: this .packetHeaderBuf, 0, 4);
2924:
2925: if (lengthRead < 4) {
2926: forceClose();
2927: throw new IOException(Messages
2928: .getString("MysqlIO.43")); //$NON-NLS-1$
2929: }
2930:
2931: packetLength = (this .packetHeaderBuf[0] & 0xff)
2932: + ((this .packetHeaderBuf[1] & 0xff) << 8)
2933: + ((this .packetHeaderBuf[2] & 0xff) << 16);
2934: } else {
2935: packetLength = existingPacketLength;
2936: }
2937:
2938: if (this .traceProtocol) {
2939: StringBuffer traceMessageBuf = new StringBuffer();
2940:
2941: traceMessageBuf
2942: .append(Messages.getString("MysqlIO.44")); //$NON-NLS-1$
2943: traceMessageBuf.append(packetLength);
2944: traceMessageBuf
2945: .append(Messages.getString("MysqlIO.45")); //$NON-NLS-1$
2946: traceMessageBuf.append(StringUtils.dumpAsHex(
2947: this .packetHeaderBuf, 4));
2948:
2949: this .connection.getLog().logTrace(
2950: traceMessageBuf.toString());
2951: }
2952:
2953: byte multiPacketSeq = this .packetHeaderBuf[3];
2954:
2955: if (!this .packetSequenceReset) {
2956: if (this .enablePacketDebug && this .checkPacketSequence) {
2957: checkPacketSequencing(multiPacketSeq);
2958: }
2959: } else {
2960: this .packetSequenceReset = false;
2961: }
2962:
2963: this .readPacketSequence = multiPacketSeq;
2964:
2965: // Set the Buffer to it's original state
2966: reuse.setPosition(0);
2967:
2968: // Do we need to re-alloc the byte buffer?
2969: //
2970: // Note: We actually check the length of the buffer,
2971: // rather than getBufLength(), because getBufLength() is not
2972: // necesarily the actual length of the byte array
2973: // used as the buffer
2974: if (reuse.getByteBuffer().length <= packetLength) {
2975: reuse.setByteBuffer(new byte[packetLength + 1]);
2976: }
2977:
2978: // Set the new length
2979: reuse.setBufLength(packetLength);
2980:
2981: // Read the data from the server
2982: int numBytesRead = readFully(this .mysqlInput, reuse
2983: .getByteBuffer(), 0, packetLength);
2984:
2985: if (numBytesRead != packetLength) {
2986: throw new IOException("Short read, expected "
2987: + packetLength + " bytes, only read "
2988: + numBytesRead);
2989: }
2990:
2991: if (this .traceProtocol) {
2992: StringBuffer traceMessageBuf = new StringBuffer();
2993:
2994: traceMessageBuf
2995: .append(Messages.getString("MysqlIO.46")); //$NON-NLS-1$
2996: traceMessageBuf.append(getPacketDumpToLog(reuse,
2997: packetLength));
2998:
2999: this .connection.getLog().logTrace(
3000: traceMessageBuf.toString());
3001: }
3002:
3003: if (this .enablePacketDebug) {
3004: enqueuePacketForDebugging(false, true, 0,
3005: this .packetHeaderBuf, reuse);
3006: }
3007:
3008: boolean isMultiPacket = false;
3009:
3010: if (packetLength == this .maxThreeBytes) {
3011: reuse.setPosition(this .maxThreeBytes);
3012:
3013: int packetEndPoint = packetLength;
3014:
3015: // it's multi-packet
3016: isMultiPacket = true;
3017:
3018: packetLength = readRemainingMultiPackets(reuse,
3019: multiPacketSeq, packetEndPoint);
3020: }
3021:
3022: if (!isMultiPacket) {
3023: reuse.getByteBuffer()[packetLength] = 0; // Null-termination
3024: }
3025:
3026: return reuse;
3027: } catch (IOException ioEx) {
3028: throw SQLError.createCommunicationsException(
3029: this .connection, this .lastPacketSentTimeMs, ioEx);
3030: } catch (OutOfMemoryError oom) {
3031: try {
3032: // _Try_ this
3033: clearInputStream();
3034: } finally {
3035: try {
3036: this .connection.realClose(false, false, true, oom);
3037: } finally {
3038: throw oom;
3039: }
3040: }
3041: }
3042:
3043: }
3044:
3045: private int readRemainingMultiPackets(Buffer reuse,
3046: byte multiPacketSeq, int packetEndPoint)
3047: throws IOException, SQLException {
3048: int lengthRead;
3049: int packetLength;
3050: lengthRead = readFully(this .mysqlInput, this .packetHeaderBuf,
3051: 0, 4);
3052:
3053: if (lengthRead < 4) {
3054: forceClose();
3055: throw new IOException(Messages.getString("MysqlIO.47")); //$NON-NLS-1$
3056: }
3057:
3058: packetLength = (this .packetHeaderBuf[0] & 0xff)
3059: + ((this .packetHeaderBuf[1] & 0xff) << 8)
3060: + ((this .packetHeaderBuf[2] & 0xff) << 16);
3061:
3062: Buffer multiPacket = new Buffer(packetLength);
3063: boolean firstMultiPkt = true;
3064:
3065: while (true) {
3066: if (!firstMultiPkt) {
3067: lengthRead = readFully(this .mysqlInput,
3068: this .packetHeaderBuf, 0, 4);
3069:
3070: if (lengthRead < 4) {
3071: forceClose();
3072: throw new IOException(Messages
3073: .getString("MysqlIO.48")); //$NON-NLS-1$
3074: }
3075:
3076: packetLength = (this .packetHeaderBuf[0] & 0xff)
3077: + ((this .packetHeaderBuf[1] & 0xff) << 8)
3078: + ((this .packetHeaderBuf[2] & 0xff) << 16);
3079: } else {
3080: firstMultiPkt = false;
3081: }
3082:
3083: if (!this .useNewLargePackets && (packetLength == 1)) {
3084: clearInputStream();
3085:
3086: break;
3087: } else if (packetLength < this .maxThreeBytes) {
3088: byte newPacketSeq = this .packetHeaderBuf[3];
3089:
3090: if (newPacketSeq != (multiPacketSeq + 1)) {
3091: throw new IOException(Messages
3092: .getString("MysqlIO.49")); //$NON-NLS-1$
3093: }
3094:
3095: multiPacketSeq = newPacketSeq;
3096:
3097: // Set the Buffer to it's original state
3098: multiPacket.setPosition(0);
3099:
3100: // Set the new length
3101: multiPacket.setBufLength(packetLength);
3102:
3103: // Read the data from the server
3104: byte[] byteBuf = multiPacket.getByteBuffer();
3105: int lengthToWrite = packetLength;
3106:
3107: int bytesRead = readFully(this .mysqlInput, byteBuf, 0,
3108: packetLength);
3109:
3110: if (bytesRead != lengthToWrite) {
3111: throw SQLError.createCommunicationsException(
3112: this .connection, this .lastPacketSentTimeMs,
3113: SQLError.createSQLException(Messages
3114: .getString("MysqlIO.50") //$NON-NLS-1$
3115: + lengthToWrite
3116: + Messages.getString("MysqlIO.51")
3117: + bytesRead //$NON-NLS-1$
3118: + ".")); //$NON-NLS-1$
3119: }
3120:
3121: reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
3122:
3123: packetEndPoint += lengthToWrite;
3124:
3125: break; // end of multipacket sequence
3126: }
3127:
3128: byte newPacketSeq = this .packetHeaderBuf[3];
3129:
3130: if (newPacketSeq != (multiPacketSeq + 1)) {
3131: throw new IOException(Messages.getString("MysqlIO.53")); //$NON-NLS-1$
3132: }
3133:
3134: multiPacketSeq = newPacketSeq;
3135:
3136: // Set the Buffer to it's original state
3137: multiPacket.setPosition(0);
3138:
3139: // Set the new length
3140: multiPacket.setBufLength(packetLength);
3141:
3142: // Read the data from the server
3143: byte[] byteBuf = multiPacket.getByteBuffer();
3144: int lengthToWrite = packetLength;
3145:
3146: int bytesRead = readFully(this .mysqlInput, byteBuf, 0,
3147: packetLength);
3148:
3149: if (bytesRead != lengthToWrite) {
3150: throw SQLError.createCommunicationsException(
3151: this .connection, this .lastPacketSentTimeMs,
3152: SQLError.createSQLException(Messages
3153: .getString("MysqlIO.54") //$NON-NLS-1$
3154: + lengthToWrite
3155: + Messages.getString("MysqlIO.55") //$NON-NLS-1$
3156: + bytesRead + ".")); //$NON-NLS-1$
3157: }
3158:
3159: reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
3160:
3161: packetEndPoint += lengthToWrite;
3162: }
3163:
3164: reuse.setPosition(0);
3165: reuse.setWasMultiPacket(true);
3166: return packetLength;
3167: }
3168:
3169: /**
3170: * @param multiPacketSeq
3171: * @throws CommunicationsException
3172: */
3173: private void checkPacketSequencing(byte multiPacketSeq)
3174: throws SQLException {
3175: if ((multiPacketSeq == -128)
3176: && (this .readPacketSequence != 127)) {
3177: throw SQLError.createCommunicationsException(
3178: this .connection, this .lastPacketSentTimeMs,
3179: new IOException(
3180: "Packets out of order, expected packet # -128, but received packet # "
3181: + multiPacketSeq));
3182: }
3183:
3184: if ((this .readPacketSequence == -1) && (multiPacketSeq != 0)) {
3185: throw SQLError.createCommunicationsException(
3186: this .connection, this .lastPacketSentTimeMs,
3187: new IOException(
3188: "Packets out of order, expected packet # -1, but received packet # "
3189: + multiPacketSeq));
3190: }
3191:
3192: if ((multiPacketSeq != -128) && (this .readPacketSequence != -1)
3193: && (multiPacketSeq != (this .readPacketSequence + 1))) {
3194: throw SQLError.createCommunicationsException(
3195: this .connection, this .lastPacketSentTimeMs,
3196: new IOException(
3197: "Packets out of order, expected packet # "
3198: + (this .readPacketSequence + 1)
3199: + ", but received packet # "
3200: + multiPacketSeq));
3201: }
3202: }
3203:
3204: void enableMultiQueries() throws SQLException {
3205: Buffer buf = getSharedSendPacket();
3206:
3207: buf.clear();
3208: buf.writeByte((byte) MysqlDefs.COM_SET_OPTION);
3209: buf.writeInt(0);
3210: sendCommand(MysqlDefs.COM_SET_OPTION, null, buf, false, null);
3211: }
3212:
3213: void disableMultiQueries() throws SQLException {
3214: Buffer buf = getSharedSendPacket();
3215:
3216: buf.clear();
3217: buf.writeByte((byte) MysqlDefs.COM_SET_OPTION);
3218: buf.writeInt(1);
3219: sendCommand(MysqlDefs.COM_SET_OPTION, null, buf, false, null);
3220: }
3221:
3222: private final void send(Buffer packet, int packetLen)
3223: throws SQLException {
3224: try {
3225: if (packetLen > this .maxAllowedPacket) {
3226: throw new PacketTooBigException(packetLen,
3227: this .maxAllowedPacket);
3228: }
3229:
3230: if (this .connection.getMaintainTimeStats()) {
3231: this .lastPacketSentTimeMs = System.currentTimeMillis();
3232: }
3233:
3234: if ((this .serverMajorVersion >= 4)
3235: && (packetLen >= this .maxThreeBytes)) {
3236: sendSplitPackets(packet);
3237: } else {
3238: this .packetSequence++;
3239:
3240: Buffer packetToSend = packet;
3241:
3242: packetToSend.setPosition(0);
3243:
3244: if (this .useCompression) {
3245: int originalPacketLen = packetLen;
3246:
3247: packetToSend = compressPacket(packet, 0, packetLen,
3248: HEADER_LENGTH);
3249: packetLen = packetToSend.getPosition();
3250:
3251: if (this .traceProtocol) {
3252: StringBuffer traceMessageBuf = new StringBuffer();
3253:
3254: traceMessageBuf.append(Messages
3255: .getString("MysqlIO.57")); //$NON-NLS-1$
3256: traceMessageBuf.append(getPacketDumpToLog(
3257: packetToSend, packetLen));
3258: traceMessageBuf.append(Messages
3259: .getString("MysqlIO.58")); //$NON-NLS-1$
3260: traceMessageBuf.append(getPacketDumpToLog(
3261: packet, originalPacketLen));
3262:
3263: this .connection.getLog().logTrace(
3264: traceMessageBuf.toString());
3265: }
3266: } else {
3267: packetToSend
3268: .writeLongInt(packetLen - HEADER_LENGTH);
3269: packetToSend.writeByte(this .packetSequence);
3270:
3271: if (this .traceProtocol) {
3272: StringBuffer traceMessageBuf = new StringBuffer();
3273:
3274: traceMessageBuf.append(Messages
3275: .getString("MysqlIO.59")); //$NON-NLS-1$
3276: traceMessageBuf.append(packetToSend
3277: .dump(packetLen));
3278:
3279: this .connection.getLog().logTrace(
3280: traceMessageBuf.toString());
3281: }
3282: }
3283:
3284: this .mysqlOutput.write(packetToSend.getByteBuffer(), 0,
3285: packetLen);
3286: this .mysqlOutput.flush();
3287: }
3288:
3289: if (this .enablePacketDebug) {
3290: enqueuePacketForDebugging(true, false, packetLen + 5,
3291: this .packetHeaderBuf, packet);
3292: }
3293:
3294: //
3295: // Don't hold on to large packets
3296: //
3297: if (packet == this .sharedSendPacket) {
3298: reclaimLargeSharedSendPacket();
3299: }
3300: } catch (IOException ioEx) {
3301: throw SQLError.createCommunicationsException(
3302: this .connection, this .lastPacketSentTimeMs, ioEx);
3303: }
3304: }
3305:
3306: /**
3307: * Reads and sends a file to the server for LOAD DATA LOCAL INFILE
3308: *
3309: * @param callingStatement DOCUMENT ME!
3310: * @param fileName the file name to send.
3311: *
3312: * @return DOCUMENT ME!
3313: *
3314: * @throws SQLException DOCUMENT ME!
3315: */
3316: private final ResultSetImpl sendFileToServer(
3317: StatementImpl callingStatement, String fileName)
3318: throws SQLException {
3319:
3320: Buffer filePacket = (this .loadFileBufRef == null) ? null
3321: : (Buffer) (this .loadFileBufRef.get());
3322:
3323: int bigPacketLength = Math.min(this .connection
3324: .getMaxAllowedPacket()
3325: - (HEADER_LENGTH * 3), alignPacketSize(this .connection
3326: .getMaxAllowedPacket() - 16, 4096)
3327: - (HEADER_LENGTH * 3));
3328:
3329: int oneMeg = 1024 * 1024;
3330:
3331: int smallerPacketSizeAligned = Math.min(oneMeg
3332: - (HEADER_LENGTH * 3), alignPacketSize(oneMeg - 16,
3333: 4096)
3334: - (HEADER_LENGTH * 3));
3335:
3336: int packetLength = Math.min(smallerPacketSizeAligned,
3337: bigPacketLength);
3338:
3339: if (filePacket == null) {
3340: try {
3341: filePacket = new Buffer((packetLength + HEADER_LENGTH));
3342: this .loadFileBufRef = new SoftReference(filePacket);
3343: } catch (OutOfMemoryError oom) {
3344: throw SQLError
3345: .createSQLException(
3346: "Could not allocate packet of "
3347: + packetLength
3348: + " bytes required for LOAD DATA LOCAL INFILE operation."
3349: + " Try increasing max heap allocation for JVM or decreasing server variable "
3350: + "'max_allowed_packet'",
3351: SQLError.SQL_STATE_MEMORY_ALLOCATION_FAILURE);
3352:
3353: }
3354: }
3355:
3356: filePacket.clear();
3357: send(filePacket, 0);
3358:
3359: byte[] fileBuf = new byte[packetLength];
3360:
3361: BufferedInputStream fileIn = null;
3362:
3363: try {
3364: if (!this .connection.getAllowLoadLocalInfile()) {
3365: throw SQLError.createSQLException(Messages
3366: .getString("MysqlIO.LoadDataLocalNotAllowed"),
3367: SQLError.SQL_STATE_GENERAL_ERROR);
3368: }
3369:
3370: InputStream hookedStream = null;
3371:
3372: if (callingStatement != null) {
3373: hookedStream = callingStatement
3374: .getLocalInfileInputStream();
3375: }
3376:
3377: if (hookedStream != null) {
3378: fileIn = new BufferedInputStream(hookedStream);
3379: } else if (!this .connection.getAllowUrlInLocalInfile()) {
3380: fileIn = new BufferedInputStream(new FileInputStream(
3381: fileName));
3382: } else {
3383: // First look for ':'
3384: if (fileName.indexOf(':') != -1) {
3385: try {
3386: URL urlFromFileName = new URL(fileName);
3387: fileIn = new BufferedInputStream(
3388: urlFromFileName.openStream());
3389: } catch (MalformedURLException badUrlEx) {
3390: // we fall back to trying this as a file input stream
3391: fileIn = new BufferedInputStream(
3392: new FileInputStream(fileName));
3393: }
3394: } else {
3395: fileIn = new BufferedInputStream(
3396: new FileInputStream(fileName));
3397: }
3398: }
3399:
3400: int bytesRead = 0;
3401:
3402: while ((bytesRead = fileIn.read(fileBuf)) != -1) {
3403: filePacket.clear();
3404: filePacket.writeBytesNoNull(fileBuf, 0, bytesRead);
3405: send(filePacket, filePacket.getPosition());
3406: }
3407: } catch (IOException ioEx) {
3408: StringBuffer messageBuf = new StringBuffer(Messages
3409: .getString("MysqlIO.60")); //$NON-NLS-1$
3410:
3411: if (!this .connection.getParanoid()) {
3412: messageBuf.append("'"); //$NON-NLS-1$
3413:
3414: if (fileName != null) {
3415: messageBuf.append(fileName);
3416: }
3417:
3418: messageBuf.append("'"); //$NON-NLS-1$
3419: }
3420:
3421: messageBuf.append(Messages.getString("MysqlIO.63")); //$NON-NLS-1$
3422:
3423: if (!this .connection.getParanoid()) {
3424: messageBuf.append(Messages.getString("MysqlIO.64")); //$NON-NLS-1$
3425: messageBuf.append(Util.stackTraceToString(ioEx));
3426: }
3427:
3428: throw SQLError.createSQLException(messageBuf.toString(),
3429: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
3430: } finally {
3431: if (fileIn != null) {
3432: try {
3433: fileIn.close();
3434: } catch (Exception ex) {
3435: throw SQLError.createSQLException(Messages
3436: .getString("MysqlIO.65"), //$NON-NLS-1$
3437: SQLError.SQL_STATE_GENERAL_ERROR);
3438: }
3439:
3440: fileIn = null;
3441: } else {
3442: // file open failed, but server needs one packet
3443: filePacket.clear();
3444: send(filePacket, filePacket.getPosition());
3445: checkErrorPacket(); // to clear response off of queue
3446: }
3447: }
3448:
3449: // send empty packet to mark EOF
3450: filePacket.clear();
3451: send(filePacket, filePacket.getPosition());
3452:
3453: Buffer resultPacket = checkErrorPacket();
3454:
3455: return buildResultSetWithUpdates(callingStatement, resultPacket);
3456: }
3457:
3458: /**
3459: * Checks for errors in the reply packet, and if none, returns the reply
3460: * packet, ready for reading
3461: *
3462: * @param command the command being issued (if used)
3463: *
3464: * @return DOCUMENT ME!
3465: *
3466: * @throws SQLException if an error packet was received
3467: * @throws CommunicationsException DOCUMENT ME!
3468: */
3469: private Buffer checkErrorPacket(int command) throws SQLException {
3470: int statusCode = 0;
3471: Buffer resultPacket = null;
3472: this .serverStatus = 0;
3473:
3474: try {
3475: // Check return value, if we get a java.io.EOFException,
3476: // the server has gone away. We'll pass it on up the
3477: // exception chain and let someone higher up decide
3478: // what to do (barf, reconnect, etc).
3479: resultPacket = reuseAndReadPacket(this .reusablePacket);
3480: } catch (SQLException sqlEx) {
3481: // Don't wrap SQL Exceptions
3482: throw sqlEx;
3483: } catch (Exception fallThru) {
3484: throw SQLError.createCommunicationsException(
3485: this .connection, this .lastPacketSentTimeMs,
3486: fallThru);
3487: }
3488:
3489: checkErrorPacket(resultPacket);
3490:
3491: return resultPacket;
3492: }
3493:
3494: private void checkErrorPacket(Buffer resultPacket)
3495: throws SQLException {
3496:
3497: int statusCode = resultPacket.readByte();
3498:
3499: // Error handling
3500: if (statusCode == (byte) 0xff) {
3501: String serverErrorMessage;
3502: int errno = 2000;
3503:
3504: if (this .protocolVersion > 9) {
3505: errno = resultPacket.readInt();
3506:
3507: String xOpen = null;
3508:
3509: serverErrorMessage = resultPacket
3510: .readString(this .connection
3511: .getErrorMessageEncoding());
3512:
3513: if (serverErrorMessage.charAt(0) == '#') { //$NON-NLS-1$
3514:
3515: // we have an SQLState
3516: if (serverErrorMessage.length() > 6) {
3517: xOpen = serverErrorMessage.substring(1, 6);
3518: serverErrorMessage = serverErrorMessage
3519: .substring(6);
3520:
3521: if (xOpen.equals("HY000")) { //$NON-NLS-1$
3522: xOpen = SQLError.mysqlToSqlState(errno,
3523: this .connection
3524: .getUseSqlStateCodes());
3525: }
3526: } else {
3527: xOpen = SQLError.mysqlToSqlState(errno,
3528: this .connection.getUseSqlStateCodes());
3529: }
3530: } else {
3531: xOpen = SQLError.mysqlToSqlState(errno,
3532: this .connection.getUseSqlStateCodes());
3533: }
3534:
3535: clearInputStream();
3536:
3537: StringBuffer errorBuf = new StringBuffer();
3538:
3539: String xOpenErrorMessage = SQLError.get(xOpen);
3540:
3541: if (!this .connection.getUseOnlyServerErrorMessages()) {
3542: if (xOpenErrorMessage != null) {
3543: errorBuf.append(xOpenErrorMessage);
3544: errorBuf.append(Messages
3545: .getString("MysqlIO.68")); //$NON-NLS-1$
3546: }
3547: }
3548:
3549: errorBuf.append(serverErrorMessage);
3550:
3551: if (!this .connection.getUseOnlyServerErrorMessages()) {
3552: if (xOpenErrorMessage != null) {
3553: errorBuf.append("\""); //$NON-NLS-1$
3554: }
3555: }
3556:
3557: appendInnodbStatusInformation(xOpen, errorBuf);
3558:
3559: if (xOpen != null && xOpen.startsWith("22")) {
3560: throw new MysqlDataTruncation(errorBuf.toString(),
3561: 0, true, false, 0, 0);
3562: } else {
3563: throw SQLError.createSQLException(errorBuf
3564: .toString(), xOpen, errno);
3565: }
3566: }
3567:
3568: serverErrorMessage = resultPacket
3569: .readString(this .connection
3570: .getErrorMessageEncoding());
3571: clearInputStream();
3572:
3573: if (serverErrorMessage.indexOf(Messages
3574: .getString("MysqlIO.70")) != -1) { //$NON-NLS-1$
3575: throw SQLError.createSQLException(SQLError
3576: .get(SQLError.SQL_STATE_COLUMN_NOT_FOUND)
3577: + ", " //$NON-NLS-1$
3578: + serverErrorMessage,
3579: SQLError.SQL_STATE_COLUMN_NOT_FOUND, -1);
3580: }
3581:
3582: StringBuffer errorBuf = new StringBuffer(Messages
3583: .getString("MysqlIO.72")); //$NON-NLS-1$
3584: errorBuf.append(serverErrorMessage);
3585: errorBuf.append("\""); //$NON-NLS-1$
3586:
3587: throw SQLError.createSQLException(SQLError
3588: .get(SQLError.SQL_STATE_GENERAL_ERROR)
3589: + ", " //$NON-NLS-1$
3590: + errorBuf.toString(),
3591: SQLError.SQL_STATE_GENERAL_ERROR, -1);
3592: }
3593: }
3594:
3595: private void appendInnodbStatusInformation(String xOpen,
3596: StringBuffer errorBuf) throws SQLException {
3597: if (this .connection
3598: .getIncludeInnodbStatusInDeadlockExceptions()
3599: && xOpen != null
3600: && (xOpen.startsWith("40") || xOpen.startsWith("41"))
3601: && this .streamingData == null) {
3602: ResultSet rs = null;
3603:
3604: try {
3605: rs = sqlQueryDirect(null, "SHOW ENGINE INNODB STATUS",
3606: this .connection.getEncoding(), null, -1,
3607: ResultSet.TYPE_FORWARD_ONLY,
3608: ResultSet.CONCUR_READ_ONLY, false,
3609: this .connection.getCatalog(), null);
3610:
3611: if (rs.next()) {
3612: errorBuf.append("\n\n");
3613: errorBuf.append(rs.getString(1));
3614: } else {
3615: errorBuf.append(Messages
3616: .getString("MysqlIO.NoInnoDBStatusFound"));
3617: }
3618: } catch (Exception ex) {
3619: errorBuf.append(Messages
3620: .getString("MysqlIO.InnoDBStatusFailed"));
3621: errorBuf.append("\n\n");
3622: errorBuf.append(Util.stackTraceToString(ex));
3623: } finally {
3624: if (rs != null) {
3625: rs.close();
3626: }
3627: }
3628: }
3629: }
3630:
3631: /**
3632: * Sends a large packet to the server as a series of smaller packets
3633: *
3634: * @param packet DOCUMENT ME!
3635: *
3636: * @throws SQLException DOCUMENT ME!
3637: * @throws CommunicationsException DOCUMENT ME!
3638: */
3639: private final void sendSplitPackets(Buffer packet)
3640: throws SQLException {
3641: try {
3642: //
3643: // Big packets are handled by splitting them in packets of MAX_THREE_BYTES
3644: // length. The last packet is always a packet that is < MAX_THREE_BYTES.
3645: // (The last packet may even have a length of 0)
3646: //
3647: //
3648: // NB: Guarded by execSQL. If the driver changes architecture, this
3649: // will need to be synchronized in some other way
3650: //
3651: Buffer headerPacket = (this .splitBufRef == null) ? null
3652: : (Buffer) (this .splitBufRef.get());
3653:
3654: //
3655: // Store this packet in a soft reference...It can be re-used if not GC'd (so clients
3656: // that use it frequently won't have to re-alloc the 16M buffer), but we don't
3657: // penalize infrequent users of large packets by keeping 16M allocated all of the time
3658: //
3659: if (headerPacket == null) {
3660: headerPacket = new Buffer(
3661: (this .maxThreeBytes + HEADER_LENGTH));
3662: this .splitBufRef = new SoftReference(headerPacket);
3663: }
3664:
3665: int len = packet.getPosition();
3666: int splitSize = this .maxThreeBytes;
3667: int originalPacketPos = HEADER_LENGTH;
3668: byte[] origPacketBytes = packet.getByteBuffer();
3669: byte[] headerPacketBytes = headerPacket.getByteBuffer();
3670:
3671: while (len >= this .maxThreeBytes) {
3672: this .packetSequence++;
3673:
3674: headerPacket.setPosition(0);
3675: headerPacket.writeLongInt(splitSize);
3676:
3677: headerPacket.writeByte(this .packetSequence);
3678: System.arraycopy(origPacketBytes, originalPacketPos,
3679: headerPacketBytes, 4, splitSize);
3680:
3681: int packetLen = splitSize + HEADER_LENGTH;
3682:
3683: //
3684: // Swap a compressed packet in, if we're using
3685: // compression...
3686: //
3687: if (!this .useCompression) {
3688: this .mysqlOutput.write(headerPacketBytes, 0,
3689: splitSize + HEADER_LENGTH);
3690: this .mysqlOutput.flush();
3691: } else {
3692: Buffer packetToSend;
3693:
3694: headerPacket.setPosition(0);
3695: packetToSend = compressPacket(headerPacket,
3696: HEADER_LENGTH, splitSize, HEADER_LENGTH);
3697: packetLen = packetToSend.getPosition();
3698:
3699: this .mysqlOutput.write(
3700: packetToSend.getByteBuffer(), 0, packetLen);
3701: this .mysqlOutput.flush();
3702: }
3703:
3704: originalPacketPos += splitSize;
3705: len -= splitSize;
3706: }
3707:
3708: //
3709: // Write last packet
3710: //
3711: headerPacket.clear();
3712: headerPacket.setPosition(0);
3713: headerPacket.writeLongInt(len - HEADER_LENGTH);
3714: this .packetSequence++;
3715: headerPacket.writeByte(this .packetSequence);
3716:
3717: if (len != 0) {
3718: System.arraycopy(origPacketBytes, originalPacketPos,
3719: headerPacketBytes, 4, len - HEADER_LENGTH);
3720: }
3721:
3722: int packetLen = len - HEADER_LENGTH;
3723:
3724: //
3725: // Swap a compressed packet in, if we're using
3726: // compression...
3727: //
3728: if (!this .useCompression) {
3729: this .mysqlOutput.write(headerPacket.getByteBuffer(), 0,
3730: len);
3731: this .mysqlOutput.flush();
3732: } else {
3733: Buffer packetToSend;
3734:
3735: headerPacket.setPosition(0);
3736: packetToSend = compressPacket(headerPacket,
3737: HEADER_LENGTH, packetLen, HEADER_LENGTH);
3738: packetLen = packetToSend.getPosition();
3739:
3740: this .mysqlOutput.write(packetToSend.getByteBuffer(), 0,
3741: packetLen);
3742: this .mysqlOutput.flush();
3743: }
3744: } catch (IOException ioEx) {
3745: throw SQLError.createCommunicationsException(
3746: this .connection, this .lastPacketSentTimeMs, ioEx);
3747: }
3748: }
3749:
3750: private void reclaimLargeSharedSendPacket() {
3751: if ((this .sharedSendPacket != null)
3752: && (this .sharedSendPacket.getCapacity() > 1048576)) {
3753: this .sharedSendPacket = new Buffer(INITIAL_PACKET_SIZE);
3754: }
3755: }
3756:
3757: boolean hadWarnings() {
3758: return this .hadWarnings;
3759: }
3760:
3761: void scanForAndThrowDataTruncation() throws SQLException {
3762: if ((this .streamingData == null)
3763: && versionMeetsMinimum(4, 1, 0)
3764: && this .connection.getJdbcCompliantTruncation()
3765: && this .warningCount > 0) {
3766: SQLError.convertShowWarningsToSQLWarnings(this .connection,
3767: this .warningCount, true);
3768: }
3769: }
3770:
3771: /**
3772: * Secure authentication for 4.1 and newer servers.
3773: *
3774: * @param packet DOCUMENT ME!
3775: * @param packLength
3776: * @param user
3777: * @param password
3778: * @param database DOCUMENT ME!
3779: * @param writeClientParams
3780: *
3781: * @throws SQLException
3782: */
3783: private void secureAuth(Buffer packet, int packLength, String user,
3784: String password, String database, boolean writeClientParams)
3785: throws SQLException {
3786: // Passwords can be 16 chars long
3787: if (packet == null) {
3788: packet = new Buffer(packLength);
3789: }
3790:
3791: if (writeClientParams) {
3792: if (this .use41Extensions) {
3793: if (versionMeetsMinimum(4, 1, 1)) {
3794: packet.writeLong(this .clientParam);
3795: packet.writeLong(this .maxThreeBytes);
3796:
3797: // charset, JDBC will connect as 'latin1',
3798: // and use 'SET NAMES' to change to the desired
3799: // charset after the connection is established.
3800: packet.writeByte((byte) 8);
3801:
3802: // Set of bytes reserved for future use.
3803: packet.writeBytesNoNull(new byte[23]);
3804: } else {
3805: packet.writeLong(this .clientParam);
3806: packet.writeLong(this .maxThreeBytes);
3807: }
3808: } else {
3809: packet.writeInt((int) this .clientParam);
3810: packet.writeLongInt(this .maxThreeBytes);
3811: }
3812: }
3813:
3814: // User/Password data
3815: packet.writeString(user, CODE_PAGE_1252, this .connection);
3816:
3817: if (password.length() != 0) {
3818: /* Prepare false scramble */
3819: packet.writeString(FALSE_SCRAMBLE, CODE_PAGE_1252,
3820: this .connection);
3821: } else {
3822: /* For empty password*/
3823: packet.writeString("", CODE_PAGE_1252, this .connection); //$NON-NLS-1$
3824: }
3825:
3826: if (this .useConnectWithDb) {
3827: packet.writeString(database, CODE_PAGE_1252,
3828: this .connection);
3829: }
3830:
3831: send(packet, packet.getPosition());
3832:
3833: //
3834: // Don't continue stages if password is empty
3835: //
3836: if (password.length() > 0) {
3837: Buffer b = readPacket();
3838:
3839: b.setPosition(0);
3840:
3841: byte[] replyAsBytes = b.getByteBuffer();
3842:
3843: if ((replyAsBytes.length == 25) && (replyAsBytes[0] != 0)) {
3844: // Old passwords will have '*' at the first byte of hash */
3845: if (replyAsBytes[0] != '*') {
3846: try {
3847: /* Build full password hash as it is required to decode scramble */
3848: byte[] buff = Security
3849: .passwordHashStage1(password);
3850:
3851: /* Store copy as we'll need it later */
3852: byte[] passwordHash = new byte[buff.length];
3853: System.arraycopy(buff, 0, passwordHash, 0,
3854: buff.length);
3855:
3856: /* Finally hash complete password using hash we got from server */
3857: passwordHash = Security.passwordHashStage2(
3858: passwordHash, replyAsBytes);
3859:
3860: byte[] packetDataAfterSalt = new byte[replyAsBytes.length - 5];
3861:
3862: System.arraycopy(replyAsBytes, 4,
3863: packetDataAfterSalt, 0,
3864: replyAsBytes.length - 5);
3865:
3866: byte[] mysqlScrambleBuff = new byte[20];
3867:
3868: /* Decypt and store scramble 4 = hash for stage2 */
3869: Security.passwordCrypt(packetDataAfterSalt,
3870: mysqlScrambleBuff, passwordHash, 20);
3871:
3872: /* Encode scramble with password. Recycle buffer */
3873: Security.passwordCrypt(mysqlScrambleBuff, buff,
3874: buff, 20);
3875:
3876: Buffer packet2 = new Buffer(25);
3877: packet2.writeBytesNoNull(buff);
3878:
3879: this .packetSequence++;
3880:
3881: send(packet2, 24);
3882: } catch (NoSuchAlgorithmException nse) {
3883: throw SQLError.createSQLException(Messages
3884: .getString("MysqlIO.91") //$NON-NLS-1$
3885: + Messages.getString("MysqlIO.92"), //$NON-NLS-1$
3886: SQLError.SQL_STATE_GENERAL_ERROR);
3887: }
3888: } else {
3889: try {
3890: /* Create password to decode scramble */
3891: byte[] passwordHash = Security
3892: .createKeyFromOldPassword(password);
3893:
3894: /* Decypt and store scramble 4 = hash for stage2 */
3895: byte[] netReadPos4 = new byte[replyAsBytes.length - 5];
3896:
3897: System.arraycopy(replyAsBytes, 4, netReadPos4,
3898: 0, replyAsBytes.length - 5);
3899:
3900: byte[] mysqlScrambleBuff = new byte[20];
3901:
3902: /* Decypt and store scramble 4 = hash for stage2 */
3903: Security.passwordCrypt(netReadPos4,
3904: mysqlScrambleBuff, passwordHash, 20);
3905:
3906: /* Finally scramble decoded scramble with password */
3907: String scrambledPassword = Util
3908: .scramble(
3909: new String(mysqlScrambleBuff),
3910: password);
3911:
3912: Buffer packet2 = new Buffer(packLength);
3913: packet2.writeString(scrambledPassword,
3914: CODE_PAGE_1252, this .connection);
3915: this .packetSequence++;
3916:
3917: send(packet2, 24);
3918: } catch (NoSuchAlgorithmException nse) {
3919: throw SQLError.createSQLException(Messages
3920: .getString("MysqlIO.93") //$NON-NLS-1$
3921: + Messages.getString("MysqlIO.94"), //$NON-NLS-1$
3922: SQLError.SQL_STATE_GENERAL_ERROR);
3923: }
3924: }
3925: }
3926: }
3927: }
3928:
3929: /**
3930: * Secure authentication for 4.1.1 and newer servers.
3931: *
3932: * @param packet DOCUMENT ME!
3933: * @param packLength
3934: * @param user
3935: * @param password
3936: * @param database DOCUMENT ME!
3937: * @param writeClientParams
3938: *
3939: * @throws SQLException
3940: */
3941: void secureAuth411(Buffer packet, int packLength, String user,
3942: String password, String database, boolean writeClientParams)
3943: throws SQLException {
3944: // SERVER: public_seed=create_random_string()
3945: // send(public_seed)
3946: //
3947: // CLIENT: recv(public_seed)
3948: // hash_stage1=sha1("password")
3949: // hash_stage2=sha1(hash_stage1)
3950: // reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
3951: //
3952: // // this three steps are done in scramble()
3953: //
3954: // send(reply)
3955: //
3956: //
3957: // SERVER: recv(reply)
3958: // hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
3959: // candidate_hash2=sha1(hash_stage1)
3960: // check(candidate_hash2==hash_stage2)
3961: // Passwords can be 16 chars long
3962: if (packet == null) {
3963: packet = new Buffer(packLength);
3964: }
3965:
3966: if (writeClientParams) {
3967: if (this .use41Extensions) {
3968: if (versionMeetsMinimum(4, 1, 1)) {
3969: packet.writeLong(this .clientParam);
3970: packet.writeLong(this .maxThreeBytes);
3971:
3972: // charset, JDBC will connect as 'utf8',
3973: // and use 'SET NAMES' to change to the desired
3974: // charset after the connection is established.
3975: packet.writeByte((byte) UTF8_CHARSET_INDEX);
3976:
3977: // Set of bytes reserved for future use.
3978: packet.writeBytesNoNull(new byte[23]);
3979: } else {
3980: packet.writeLong(this .clientParam);
3981: packet.writeLong(this .maxThreeBytes);
3982: }
3983: } else {
3984: packet.writeInt((int) this .clientParam);
3985: packet.writeLongInt(this .maxThreeBytes);
3986: }
3987: }
3988:
3989: // User/Password data
3990: packet.writeString(user, "utf-8", this .connection);
3991:
3992: if (password.length() != 0) {
3993: packet.writeByte((byte) 0x14);
3994:
3995: try {
3996: packet.writeBytesNoNull(Security.scramble411(password,
3997: this .seed));
3998: } catch (NoSuchAlgorithmException nse) {
3999: throw SQLError.createSQLException(Messages
4000: .getString("MysqlIO.95") //$NON-NLS-1$
4001: + Messages.getString("MysqlIO.96"), //$NON-NLS-1$
4002: SQLError.SQL_STATE_GENERAL_ERROR);
4003: }
4004: } else {
4005: /* For empty password*/
4006: packet.writeByte((byte) 0);
4007: }
4008:
4009: if (this .useConnectWithDb) {
4010: packet.writeString(database, "utf-8", this .connection);
4011: }
4012:
4013: send(packet, packet.getPosition());
4014:
4015: byte savePacketSequence = this .packetSequence++;
4016:
4017: Buffer reply = checkErrorPacket();
4018:
4019: if (reply.isLastDataPacket()) {
4020: /*
4021: By sending this very specific reply server asks us to send scrambled
4022: password in old format. The reply contains scramble_323.
4023: */
4024: this .packetSequence = ++savePacketSequence;
4025: packet.clear();
4026:
4027: String seed323 = this .seed.substring(0, 8);
4028: packet.writeString(Util.newCrypt(password, seed323));
4029: send(packet, packet.getPosition());
4030:
4031: /* Read what server thinks about out new auth message report */
4032: checkErrorPacket();
4033: }
4034: }
4035:
4036: /**
4037: * Un-packs binary-encoded result set data for one row
4038: *
4039: * @param fields
4040: * @param binaryData
4041: * @param resultSetConcurrency DOCUMENT ME!
4042: *
4043: * @return byte[][]
4044: *
4045: * @throws SQLException DOCUMENT ME!
4046: */
4047: private final ResultSetRow unpackBinaryResultSetRow(Field[] fields,
4048: Buffer binaryData, int resultSetConcurrency)
4049: throws SQLException {
4050: int numFields = fields.length;
4051:
4052: byte[][] unpackedRowData = new byte[numFields][];
4053:
4054: //
4055: // Unpack the null bitmask, first
4056: //
4057:
4058: /* Reserve place for null-marker bytes */
4059: int nullCount = (numFields + 9) / 8;
4060:
4061: byte[] nullBitMask = new byte[nullCount];
4062:
4063: for (int i = 0; i < nullCount; i++) {
4064: nullBitMask[i] = binaryData.readByte();
4065: }
4066:
4067: int nullMaskPos = 0;
4068: int bit = 4; // first two bits are reserved for future use
4069:
4070: //
4071: // TODO: Benchmark if moving check for updatable result
4072: // sets out of loop is worthwhile?
4073: //
4074:
4075: for (int i = 0; i < numFields; i++) {
4076: if ((nullBitMask[nullMaskPos] & bit) != 0) {
4077: unpackedRowData[i] = null;
4078: } else {
4079: if (resultSetConcurrency != ResultSetInternalMethods.CONCUR_UPDATABLE) {
4080: extractNativeEncodedColumn(binaryData, fields, i,
4081: unpackedRowData);
4082: } else {
4083: unpackNativeEncodedColumn(binaryData, fields, i,
4084: unpackedRowData);
4085: }
4086: }
4087:
4088: if (((bit <<= 1) & 255) == 0) {
4089: bit = 1; /* To next byte */
4090:
4091: nullMaskPos++;
4092: }
4093: }
4094:
4095: return new ByteArrayRow(unpackedRowData);
4096: }
4097:
4098: private final void extractNativeEncodedColumn(Buffer binaryData,
4099: Field[] fields, int columnIndex, byte[][] unpackedRowData)
4100: throws SQLException {
4101: Field curField = fields[columnIndex];
4102:
4103: switch (curField.getMysqlType()) {
4104: case MysqlDefs.FIELD_TYPE_NULL:
4105: break; // for dummy binds
4106:
4107: case MysqlDefs.FIELD_TYPE_TINY:
4108:
4109: unpackedRowData[columnIndex] = new byte[] { binaryData
4110: .readByte() };
4111: break;
4112:
4113: case MysqlDefs.FIELD_TYPE_SHORT:
4114: case MysqlDefs.FIELD_TYPE_YEAR:
4115:
4116: unpackedRowData[columnIndex] = binaryData.getBytes(2);
4117: break;
4118: case MysqlDefs.FIELD_TYPE_LONG:
4119: case MysqlDefs.FIELD_TYPE_INT24:
4120:
4121: unpackedRowData[columnIndex] = binaryData.getBytes(4);
4122: break;
4123: case MysqlDefs.FIELD_TYPE_LONGLONG:
4124:
4125: unpackedRowData[columnIndex] = binaryData.getBytes(8);
4126: break;
4127: case MysqlDefs.FIELD_TYPE_FLOAT:
4128:
4129: unpackedRowData[columnIndex] = binaryData.getBytes(4);
4130: break;
4131: case MysqlDefs.FIELD_TYPE_DOUBLE:
4132:
4133: unpackedRowData[columnIndex] = binaryData.getBytes(8);
4134: break;
4135: case MysqlDefs.FIELD_TYPE_TIME:
4136:
4137: int length = (int) binaryData.readFieldLength();
4138:
4139: unpackedRowData[columnIndex] = binaryData.getBytes(length);
4140:
4141: break;
4142: case MysqlDefs.FIELD_TYPE_DATE:
4143:
4144: length = (int) binaryData.readFieldLength();
4145:
4146: unpackedRowData[columnIndex] = binaryData.getBytes(length);
4147:
4148: break;
4149: case MysqlDefs.FIELD_TYPE_DATETIME:
4150: case MysqlDefs.FIELD_TYPE_TIMESTAMP:
4151: length = (int) binaryData.readFieldLength();
4152:
4153: unpackedRowData[columnIndex] = binaryData.getBytes(length);
4154: break;
4155: case MysqlDefs.FIELD_TYPE_TINY_BLOB:
4156: case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
4157: case MysqlDefs.FIELD_TYPE_LONG_BLOB:
4158: case MysqlDefs.FIELD_TYPE_BLOB:
4159: case MysqlDefs.FIELD_TYPE_VAR_STRING:
4160: case MysqlDefs.FIELD_TYPE_VARCHAR:
4161: case MysqlDefs.FIELD_TYPE_STRING:
4162: case MysqlDefs.FIELD_TYPE_DECIMAL:
4163: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
4164: case MysqlDefs.FIELD_TYPE_GEOMETRY:
4165: unpackedRowData[columnIndex] = binaryData
4166: .readLenByteArray(0);
4167:
4168: break;
4169: case MysqlDefs.FIELD_TYPE_BIT:
4170: unpackedRowData[columnIndex] = binaryData
4171: .readLenByteArray(0);
4172:
4173: break;
4174: default:
4175: throw SQLError.createSQLException(
4176: Messages.getString("MysqlIO.97") //$NON-NLS-1$
4177: + curField.getMysqlType()
4178: + Messages.getString("MysqlIO.98")
4179: + columnIndex
4180: + Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
4181: + fields.length
4182: + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
4183: SQLError.SQL_STATE_GENERAL_ERROR);
4184: }
4185: }
4186:
4187: private final void unpackNativeEncodedColumn(Buffer binaryData,
4188: Field[] fields, int columnIndex, byte[][] unpackedRowData)
4189: throws SQLException {
4190: Field curField = fields[columnIndex];
4191:
4192: switch (curField.getMysqlType()) {
4193: case MysqlDefs.FIELD_TYPE_NULL:
4194: break; // for dummy binds
4195:
4196: case MysqlDefs.FIELD_TYPE_TINY:
4197:
4198: byte tinyVal = binaryData.readByte();
4199:
4200: if (!curField.isUnsigned()) {
4201: unpackedRowData[columnIndex] = String.valueOf(tinyVal)
4202: .getBytes();
4203: } else {
4204: short unsignedTinyVal = (short) (tinyVal & 0xff);
4205:
4206: unpackedRowData[columnIndex] = String.valueOf(
4207: unsignedTinyVal).getBytes();
4208: }
4209:
4210: break;
4211:
4212: case MysqlDefs.FIELD_TYPE_SHORT:
4213: case MysqlDefs.FIELD_TYPE_YEAR:
4214:
4215: short shortVal = (short) binaryData.readInt();
4216:
4217: if (!curField.isUnsigned()) {
4218: unpackedRowData[columnIndex] = String.valueOf(shortVal)
4219: .getBytes();
4220: } else {
4221: int unsignedShortVal = shortVal & 0xffff;
4222:
4223: unpackedRowData[columnIndex] = String.valueOf(
4224: unsignedShortVal).getBytes();
4225: }
4226:
4227: break;
4228:
4229: case MysqlDefs.FIELD_TYPE_LONG:
4230: case MysqlDefs.FIELD_TYPE_INT24:
4231:
4232: int intVal = (int) binaryData.readLong();
4233:
4234: if (!curField.isUnsigned()) {
4235: unpackedRowData[columnIndex] = String.valueOf(intVal)
4236: .getBytes();
4237: } else {
4238: long longVal = intVal & 0xffffffffL;
4239:
4240: unpackedRowData[columnIndex] = String.valueOf(longVal)
4241: .getBytes();
4242: }
4243:
4244: break;
4245:
4246: case MysqlDefs.FIELD_TYPE_LONGLONG:
4247:
4248: long longVal = binaryData.readLongLong();
4249:
4250: if (!curField.isUnsigned()) {
4251: unpackedRowData[columnIndex] = String.valueOf(longVal)
4252: .getBytes();
4253: } else {
4254: BigInteger asBigInteger = ResultSetImpl
4255: .convertLongToUlong(longVal);
4256:
4257: unpackedRowData[columnIndex] = asBigInteger.toString()
4258: .getBytes();
4259: }
4260:
4261: break;
4262:
4263: case MysqlDefs.FIELD_TYPE_FLOAT:
4264:
4265: float floatVal = Float.intBitsToFloat(binaryData
4266: .readIntAsLong());
4267:
4268: unpackedRowData[columnIndex] = String.valueOf(floatVal)
4269: .getBytes();
4270:
4271: break;
4272:
4273: case MysqlDefs.FIELD_TYPE_DOUBLE:
4274:
4275: double doubleVal = Double.longBitsToDouble(binaryData
4276: .readLongLong());
4277:
4278: unpackedRowData[columnIndex] = String.valueOf(doubleVal)
4279: .getBytes();
4280:
4281: break;
4282:
4283: case MysqlDefs.FIELD_TYPE_TIME:
4284:
4285: int length = (int) binaryData.readFieldLength();
4286:
4287: int hour = 0;
4288: int minute = 0;
4289: int seconds = 0;
4290:
4291: if (length != 0) {
4292: binaryData.readByte(); // skip tm->neg
4293: binaryData.readLong(); // skip daysPart
4294: hour = binaryData.readByte();
4295: minute = binaryData.readByte();
4296: seconds = binaryData.readByte();
4297:
4298: if (length > 8) {
4299: binaryData.readLong(); // ignore 'secondsPart'
4300: }
4301: }
4302:
4303: byte[] timeAsBytes = new byte[8];
4304:
4305: timeAsBytes[0] = (byte) Character.forDigit(hour / 10, 10);
4306: timeAsBytes[1] = (byte) Character.forDigit(hour % 10, 10);
4307:
4308: timeAsBytes[2] = (byte) ':';
4309:
4310: timeAsBytes[3] = (byte) Character.forDigit(minute / 10, 10);
4311: timeAsBytes[4] = (byte) Character.forDigit(minute % 10, 10);
4312:
4313: timeAsBytes[5] = (byte) ':';
4314:
4315: timeAsBytes[6] = (byte) Character
4316: .forDigit(seconds / 10, 10);
4317: timeAsBytes[7] = (byte) Character
4318: .forDigit(seconds % 10, 10);
4319:
4320: unpackedRowData[columnIndex] = timeAsBytes;
4321:
4322: break;
4323:
4324: case MysqlDefs.FIELD_TYPE_DATE:
4325: length = (int) binaryData.readFieldLength();
4326:
4327: int year = 0;
4328: int month = 0;
4329: int day = 0;
4330:
4331: hour = 0;
4332: minute = 0;
4333: seconds = 0;
4334:
4335: if (length != 0) {
4336: year = binaryData.readInt();
4337: month = binaryData.readByte();
4338: day = binaryData.readByte();
4339: }
4340:
4341: if ((year == 0) && (month == 0) && (day == 0)) {
4342: if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL
4343: .equals(this .connection
4344: .getZeroDateTimeBehavior())) {
4345: unpackedRowData[columnIndex] = null;
4346:
4347: break;
4348: } else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION
4349: .equals(this .connection
4350: .getZeroDateTimeBehavior())) {
4351: throw SQLError
4352: .createSQLException(
4353: "Value '0000-00-00' can not be represented as java.sql.Date",
4354: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
4355: }
4356:
4357: year = 1;
4358: month = 1;
4359: day = 1;
4360: }
4361:
4362: byte[] dateAsBytes = new byte[10];
4363:
4364: dateAsBytes[0] = (byte) Character.forDigit(year / 1000, 10);
4365:
4366: int after1000 = year % 1000;
4367:
4368: dateAsBytes[1] = (byte) Character.forDigit(after1000 / 100,
4369: 10);
4370:
4371: int after100 = after1000 % 100;
4372:
4373: dateAsBytes[2] = (byte) Character.forDigit(after100 / 10,
4374: 10);
4375: dateAsBytes[3] = (byte) Character.forDigit(after100 % 10,
4376: 10);
4377:
4378: dateAsBytes[4] = (byte) '-';
4379:
4380: dateAsBytes[5] = (byte) Character.forDigit(month / 10, 10);
4381: dateAsBytes[6] = (byte) Character.forDigit(month % 10, 10);
4382:
4383: dateAsBytes[7] = (byte) '-';
4384:
4385: dateAsBytes[8] = (byte) Character.forDigit(day / 10, 10);
4386: dateAsBytes[9] = (byte) Character.forDigit(day % 10, 10);
4387:
4388: unpackedRowData[columnIndex] = dateAsBytes;
4389:
4390: break;
4391:
4392: case MysqlDefs.FIELD_TYPE_DATETIME:
4393: case MysqlDefs.FIELD_TYPE_TIMESTAMP:
4394: length = (int) binaryData.readFieldLength();
4395:
4396: year = 0;
4397: month = 0;
4398: day = 0;
4399:
4400: hour = 0;
4401: minute = 0;
4402: seconds = 0;
4403:
4404: int nanos = 0;
4405:
4406: if (length != 0) {
4407: year = binaryData.readInt();
4408: month = binaryData.readByte();
4409: day = binaryData.readByte();
4410:
4411: if (length > 4) {
4412: hour = binaryData.readByte();
4413: minute = binaryData.readByte();
4414: seconds = binaryData.readByte();
4415: }
4416:
4417: //if (length > 7) {
4418: // nanos = (int)binaryData.readLong();
4419: //}
4420: }
4421:
4422: if ((year == 0) && (month == 0) && (day == 0)) {
4423: if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL
4424: .equals(this .connection
4425: .getZeroDateTimeBehavior())) {
4426: unpackedRowData[columnIndex] = null;
4427:
4428: break;
4429: } else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION
4430: .equals(this .connection
4431: .getZeroDateTimeBehavior())) {
4432: throw SQLError
4433: .createSQLException(
4434: "Value '0000-00-00' can not be represented as java.sql.Timestamp",
4435: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
4436: }
4437:
4438: year = 1;
4439: month = 1;
4440: day = 1;
4441: }
4442:
4443: int stringLength = 19;
4444:
4445: byte[] nanosAsBytes = Integer.toString(nanos).getBytes();
4446:
4447: stringLength += (1 + nanosAsBytes.length); // '.' + # of digits
4448:
4449: byte[] datetimeAsBytes = new byte[stringLength];
4450:
4451: datetimeAsBytes[0] = (byte) Character.forDigit(year / 1000,
4452: 10);
4453:
4454: after1000 = year % 1000;
4455:
4456: datetimeAsBytes[1] = (byte) Character.forDigit(
4457: after1000 / 100, 10);
4458:
4459: after100 = after1000 % 100;
4460:
4461: datetimeAsBytes[2] = (byte) Character.forDigit(
4462: after100 / 10, 10);
4463: datetimeAsBytes[3] = (byte) Character.forDigit(
4464: after100 % 10, 10);
4465:
4466: datetimeAsBytes[4] = (byte) '-';
4467:
4468: datetimeAsBytes[5] = (byte) Character.forDigit(month / 10,
4469: 10);
4470: datetimeAsBytes[6] = (byte) Character.forDigit(month % 10,
4471: 10);
4472:
4473: datetimeAsBytes[7] = (byte) '-';
4474:
4475: datetimeAsBytes[8] = (byte) Character
4476: .forDigit(day / 10, 10);
4477: datetimeAsBytes[9] = (byte) Character
4478: .forDigit(day % 10, 10);
4479:
4480: datetimeAsBytes[10] = (byte) ' ';
4481:
4482: datetimeAsBytes[11] = (byte) Character.forDigit(hour / 10,
4483: 10);
4484: datetimeAsBytes[12] = (byte) Character.forDigit(hour % 10,
4485: 10);
4486:
4487: datetimeAsBytes[13] = (byte) ':';
4488:
4489: datetimeAsBytes[14] = (byte) Character.forDigit(
4490: minute / 10, 10);
4491: datetimeAsBytes[15] = (byte) Character.forDigit(
4492: minute % 10, 10);
4493:
4494: datetimeAsBytes[16] = (byte) ':';
4495:
4496: datetimeAsBytes[17] = (byte) Character.forDigit(
4497: seconds / 10, 10);
4498: datetimeAsBytes[18] = (byte) Character.forDigit(
4499: seconds % 10, 10);
4500:
4501: datetimeAsBytes[19] = (byte) '.';
4502:
4503: int nanosOffset = 20;
4504:
4505: for (int j = 0; j < nanosAsBytes.length; j++) {
4506: datetimeAsBytes[nanosOffset + j] = nanosAsBytes[j];
4507: }
4508:
4509: unpackedRowData[columnIndex] = datetimeAsBytes;
4510:
4511: break;
4512:
4513: case MysqlDefs.FIELD_TYPE_TINY_BLOB:
4514: case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
4515: case MysqlDefs.FIELD_TYPE_LONG_BLOB:
4516: case MysqlDefs.FIELD_TYPE_BLOB:
4517: case MysqlDefs.FIELD_TYPE_VAR_STRING:
4518: case MysqlDefs.FIELD_TYPE_STRING:
4519: case MysqlDefs.FIELD_TYPE_VARCHAR:
4520: case MysqlDefs.FIELD_TYPE_DECIMAL:
4521: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
4522: case MysqlDefs.FIELD_TYPE_BIT:
4523: unpackedRowData[columnIndex] = binaryData
4524: .readLenByteArray(0);
4525:
4526: break;
4527:
4528: default:
4529: throw SQLError.createSQLException(
4530: Messages.getString("MysqlIO.97") //$NON-NLS-1$
4531: + curField.getMysqlType()
4532: + Messages.getString("MysqlIO.98")
4533: + columnIndex
4534: + Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
4535: + fields.length
4536: + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
4537: SQLError.SQL_STATE_GENERAL_ERROR);
4538: }
4539: }
4540:
4541: /**
4542: * Negotiates the SSL communications channel used when connecting
4543: * to a MySQL server that understands SSL.
4544: *
4545: * @param user
4546: * @param password
4547: * @param database
4548: * @param packLength
4549: * @throws SQLException
4550: * @throws CommunicationsException
4551: */
4552: private void negotiateSSLConnection(String user, String password,
4553: String database, int packLength) throws SQLException {
4554: if (!ExportControlled.enabled()) {
4555: throw new ConnectionFeatureNotAvailableException(
4556: this .connection, this .lastPacketSentTimeMs, null);
4557: }
4558:
4559: boolean doSecureAuth = false;
4560:
4561: if ((this .serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
4562: this .clientParam |= CLIENT_SECURE_CONNECTION;
4563: doSecureAuth = true;
4564: }
4565:
4566: this .clientParam |= CLIENT_SSL;
4567:
4568: Buffer packet = new Buffer(packLength);
4569:
4570: if (this .use41Extensions) {
4571: packet.writeLong(this .clientParam);
4572: } else {
4573: packet.writeInt((int) this .clientParam);
4574: }
4575:
4576: send(packet, packet.getPosition());
4577:
4578: ExportControlled.transformSocketToSSLSocket(this );
4579:
4580: packet.clear();
4581:
4582: if (doSecureAuth) {
4583: if (versionMeetsMinimum(4, 1, 1)) {
4584: secureAuth411(null, packLength, user, password,
4585: database, true);
4586: } else {
4587: secureAuth411(null, packLength, user, password,
4588: database, true);
4589: }
4590: } else {
4591: if (this .use41Extensions) {
4592: packet.writeLong(this .clientParam);
4593: packet.writeLong(this .maxThreeBytes);
4594: } else {
4595: packet.writeInt((int) this .clientParam);
4596: packet.writeLongInt(this .maxThreeBytes);
4597: }
4598:
4599: // User/Password data
4600: packet.writeString(user);
4601:
4602: if (this .protocolVersion > 9) {
4603: packet.writeString(Util.newCrypt(password, this .seed));
4604: } else {
4605: packet.writeString(Util.oldCrypt(password, this .seed));
4606: }
4607:
4608: if (((this .serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
4609: && (database != null) && (database.length() > 0)) {
4610: packet.writeString(database);
4611: }
4612:
4613: send(packet, packet.getPosition());
4614: }
4615: }
4616:
4617: protected int getServerStatus() {
4618: return this .serverStatus;
4619: }
4620:
4621: protected List fetchRowsViaCursor(List fetchedRows,
4622: long statementId, Field[] columnTypes, int fetchSize,
4623: boolean useBufferRowExplicit) throws SQLException {
4624:
4625: if (fetchedRows == null) {
4626: fetchedRows = new ArrayList(fetchSize);
4627: } else {
4628: fetchedRows.clear();
4629: }
4630:
4631: this .sharedSendPacket.clear();
4632:
4633: this .sharedSendPacket.writeByte((byte) MysqlDefs.COM_FETCH);
4634: this .sharedSendPacket.writeLong(statementId);
4635: this .sharedSendPacket.writeLong(fetchSize);
4636:
4637: sendCommand(MysqlDefs.COM_FETCH, null, this .sharedSendPacket,
4638: true, null);
4639:
4640: ResultSetRow row = null;
4641:
4642: while ((row = nextRow(columnTypes, columnTypes.length, true,
4643: ResultSet.CONCUR_READ_ONLY, false,
4644: useBufferRowExplicit, false, null)) != null) {
4645: fetchedRows.add(row);
4646: }
4647:
4648: return fetchedRows;
4649: }
4650:
4651: protected long getThreadId() {
4652: return this .threadId;
4653: }
4654:
4655: protected boolean useNanosForElapsedTime() {
4656: return this .useNanosForElapsedTime;
4657: }
4658:
4659: protected long getSlowQueryThreshold() {
4660: return this .slowQueryThreshold;
4661: }
4662:
4663: protected String getQueryTimingUnits() {
4664: return this.queryTimingUnits;
4665: }
4666: }
|