1 /*
2 Copyright 2002-2007 MySQL AB, 2008 Sun Microsystems
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as
6 published by the Free Software Foundation.
7
8 There are special exceptions to the terms and conditions of the GPL
9 as it is applied to this software. View the full text of the
10 exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
11 software distribution.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22
23
24 */
25 package com.mysql.jdbc;
26
27 import java.io.BufferedInputStream;
28 import java.io.BufferedOutputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.EOFException;
31 import java.io.FileInputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStreamWriter;
35 import java.io.UnsupportedEncodingException;
36 import java.lang.ref.SoftReference;
37 import java.math.BigInteger;
38 import java.net.ConnectException;
39 import java.net.MalformedURLException;
40 import java.net.Socket;
41 import java.net.SocketException;
42 import java.net.URL;
43 import java.security.NoSuchAlgorithmException;
44 import java.sql.ResultSet;
45 import java.sql.SQLException;
46 import java.sql.Types;
47 import java.util.ArrayList;
48 import java.util.Calendar;
49 import java.util.Iterator;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Properties;
53 import java.util.zip.Deflater;
54
55 import com.mysql.jdbc.exceptions.MySQLStatementCancelledException;
56 import com.mysql.jdbc.exceptions.MySQLTimeoutException;
57 import com.mysql.jdbc.profiler.ProfilerEvent;
58 import com.mysql.jdbc.profiler.ProfilerEventHandler;
59 import com.mysql.jdbc.profiler.ProfilerEventHandlerFactory;
60 import com.mysql.jdbc.util.ReadAheadInputStream;
61 import com.mysql.jdbc.util.ResultSetUtil;
62
63
64 /**
65 * This class is used by Connection for communicating with the MySQL server.
66 *
67 * @author Mark Matthews
68 * @version $Id$
69 *
70 * @see java.sql.Connection
71 */
72 class MysqlIO {
73 private static final int UTF8_CHARSET_INDEX = 33;
74 private static final String CODE_PAGE_1252 = "Cp1252";
75 protected static final int NULL_LENGTH = ~0;
76 protected static final int COMP_HEADER_LENGTH = 3;
77 protected static final int MIN_COMPRESS_LEN = 50;
78 protected static final int HEADER_LENGTH = 4;
79 protected static final int AUTH_411_OVERHEAD = 33;
80 private static int maxBufferSize = 65535;
81 private static final int CLIENT_COMPRESS = 32; /* Can use compression
82 protcol */
83 protected static final int CLIENT_CONNECT_WITH_DB = 8;
84 private static final int CLIENT_FOUND_ROWS = 2;
85 private static final int CLIENT_LOCAL_FILES = 128; /* Can use LOAD DATA
86 LOCAL */
87
88 /* Found instead of
89 affected rows */
90 private static final int CLIENT_LONG_FLAG = 4; /* Get all column flags */
91 private static final int CLIENT_LONG_PASSWORD = 1; /* new more secure
92 passwords */
93 private static final int CLIENT_PROTOCOL_41 = 512; // for > 4.1.1
94 private static final int CLIENT_INTERACTIVE = 1024;
95 protected static final int CLIENT_SSL = 2048;
96 private static final int CLIENT_TRANSACTIONS = 8192; // Client knows about transactions
97 protected static final int CLIENT_RESERVED = 16384; // for 4.1.0 only
98 protected static final int CLIENT_SECURE_CONNECTION = 32768;
99 private static final int CLIENT_MULTI_QUERIES = 65536; // Enable/disable multiquery support
100 private static final int CLIENT_MULTI_RESULTS = 131072; // Enable/disable multi-results
101 private static final int SERVER_STATUS_IN_TRANS = 1;
102 private static final int SERVER_STATUS_AUTOCOMMIT = 2; // Server in auto_commit mode
103 static final int SERVER_MORE_RESULTS_EXISTS = 8; // Multi query - next query exists
104 private static final int SERVER_QUERY_NO_GOOD_INDEX_USED = 16;
105 private static final int SERVER_QUERY_NO_INDEX_USED = 32;
106 private static final int SERVER_QUERY_WAS_SLOW = 2048;
107 private static final int SERVER_STATUS_CURSOR_EXISTS = 64;
108 private static final String FALSE_SCRAMBLE = "xxxxxxxx"; //$NON-NLS-1$
109 protected static final int MAX_QUERY_SIZE_TO_LOG = 1024; // truncate logging of queries at 1K
110 protected static final int MAX_QUERY_SIZE_TO_EXPLAIN = 1024 * 1024; // don't explain queries above 1MB
111 protected static final int INITIAL_PACKET_SIZE = 1024;
112 /**
113 * We store the platform 'encoding' here, only used to avoid munging
114 * filenames for LOAD DATA LOCAL INFILE...
115 */
116 private static String jvmPlatformCharset = null;
117
118 /**
119 * We need to have a 'marker' for all-zero datetimes so that ResultSet
120 * can decide what to do based on connection setting
121 */
122 protected final static String ZERO_DATE_VALUE_MARKER = "0000-00-00";
123 protected final static String ZERO_DATETIME_VALUE_MARKER = "0000-00-00 00:00:00";
124
125 static {
126 OutputStreamWriter outWriter = null;
127
128 //
129 // Use the I/O system to get the encoding (if possible), to avoid
130 // security restrictions on System.getProperty("file.encoding") in
131 // applets (why is that restricted?)
132 //
133 try {
134 outWriter = new OutputStreamWriter(new ByteArrayOutputStream());
135 jvmPlatformCharset = outWriter.getEncoding();
136 } finally {
137 try {
138 if (outWriter != null) {
139 outWriter.close();
140 }
141 } catch (IOException ioEx) {
142 // ignore
143 }
144 }
145 }
146
147 /** Max number of bytes to dump when tracing the protocol */
148 private final static int MAX_PACKET_DUMP_LENGTH = 1024;
149 private boolean packetSequenceReset = false;
150 protected int serverCharsetIndex;
151
152 //
153 // Use this when reading in rows to avoid thousands of new()
154 // calls, because the byte arrays just get copied out of the
155 // packet anyway
156 //
157 private Buffer reusablePacket = null;
158 private Buffer sendPacket = null;
159 private Buffer sharedSendPacket = null;
160
161 /** Data to the server */
162 protected BufferedOutputStream mysqlOutput = null;
163 protected ConnectionImpl connection;
164 private Deflater deflater = null;
165 protected InputStream mysqlInput = null;
166 private LinkedList packetDebugRingBuffer = null;
167 private RowData streamingData = null;
168
169 /** The connection to the server */
170 protected Socket mysqlConnection = null;
171 private SocketFactory socketFactory = null;
172
173 //
174 // Packet used for 'LOAD DATA LOCAL INFILE'
175 //
176 // We use a SoftReference, so that we don't penalize intermittent
177 // use of this feature
178 //
179 private SoftReference loadFileBufRef;
180
181 //
182 // Used to send large packets to the server versions 4+
183 // We use a SoftReference, so that we don't penalize intermittent
184 // use of this feature
185 //
186 private SoftReference splitBufRef;
187 protected String host = null;
188 protected String seed;
189 private String serverVersion = null;
190 private String socketFactoryClassName = null;
191 private byte[] packetHeaderBuf = new byte[4];
192 private boolean colDecimalNeedsBump = false; // do we need to increment the colDecimal flag?
193 private boolean hadWarnings = false;
194 private boolean has41NewNewProt = false;
195
196 /** Does the server support long column info? */
197 private boolean hasLongColumnInfo = false;
198 private boolean isInteractiveClient = false;
199 private boolean logSlowQueries = false;
200
201 /**
202 * Does the character set of this connection match the character set of the
203 * platform
204 */
205 private boolean platformDbCharsetMatches = true; // changed once we've connected.
206 private boolean profileSql = false;
207 private boolean queryBadIndexUsed = false;
208 private boolean queryNoIndexUsed = false;
209 private boolean serverQueryWasSlow = false;
210
211 /** Should we use 4.1 protocol extensions? */
212 private boolean use41Extensions = false;
213 private boolean useCompression = false;
214 private boolean useNewLargePackets = false;
215 private boolean useNewUpdateCounts = false; // should we use the new larger update counts?
216 private byte packetSequence = 0;
217 private byte readPacketSequence = -1;
218 private boolean checkPacketSequence = false;
219 private byte protocolVersion = 0;
220 private int maxAllowedPacket = 1024 * 1024;
221 protected int maxThreeBytes = 255 * 255 * 255;
222 protected int port = 3306;
223 protected int serverCapabilities;
224 private int serverMajorVersion = 0;
225 private int serverMinorVersion = 0;
226 private int oldServerStatus = 0;
227 private int serverStatus = 0;
228 private int serverSubMinorVersion = 0;
229 private int warningCount = 0;
230 protected long clientParam = 0;
231 protected long lastPacketSentTimeMs = 0;
232 protected long lastPacketReceivedTimeMs = 0;
233 private boolean traceProtocol = false;
234 private boolean enablePacketDebug = false;
235 private Calendar sessionCalendar;
236 private boolean useConnectWithDb;
237 private boolean needToGrabQueryFromPacket;
238 private boolean autoGenerateTestcaseScript;
239 private long threadId;
240 private boolean useNanosForElapsedTime;
241 private long slowQueryThreshold;
242 private String queryTimingUnits;
243 private boolean useDirectRowUnpack = true;
244 private int useBufferRowSizeThreshold;
245 private int commandCount = 0;
246 private List statementInterceptors;
247 private ExceptionInterceptor exceptionInterceptor;
248
249 /**
250 * Constructor: Connect to the MySQL server and setup a stream connection.
251 *
252 * @param host the hostname to connect to
253 * @param port the port number that the server is listening on
254 * @param props the Properties from DriverManager.getConnection()
255 * @param socketFactoryClassName the socket factory to use
256 * @param conn the Connection that is creating us
257 * @param socketTimeout the timeout to set for the socket (0 means no
258 * timeout)
259 *
260 * @throws IOException if an IOException occurs during connect.
261 * @throws SQLException if a database access error occurs.
262 */
263 public MysqlIO(String host, int port, Properties props,
264 String socketFactoryClassName, ConnectionImpl conn,
265 int socketTimeout, int useBufferRowSizeThreshold) throws IOException, SQLException {
266 this.connection = conn;
267
268 if (this.connection.getEnablePacketDebug()) {
269 this.packetDebugRingBuffer = new LinkedList();
270 }
271 this.traceProtocol = this.connection.getTraceProtocol();
272
273
274 this.useAutoSlowLog = this.connection.getAutoSlowLog();
275
276 this.useBufferRowSizeThreshold = useBufferRowSizeThreshold;
277 this.useDirectRowUnpack = this.connection.getUseDirectRowUnpack();
278
279 this.logSlowQueries = this.connection.getLogSlowQueries();
280
281 this.reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
282 this.sendPacket = new Buffer(INITIAL_PACKET_SIZE);
283
284 this.port = port;
285 this.host = host;
286
287 this.socketFactoryClassName = socketFactoryClassName;
288 this.socketFactory = createSocketFactory();
289 this.exceptionInterceptor = this.connection.getExceptionInterceptor();
290
291 try {
292 this.mysqlConnection = this.socketFactory.connect(this.host,
293 this.port, props);
294
295
296 if (socketTimeout != 0) {
297 try {
298 this.mysqlConnection.setSoTimeout(socketTimeout);
299 } catch (Exception ex) {
300 /* Ignore if the platform does not support it */
301 }
302 }
303
304 this.mysqlConnection = this.socketFactory.beforeHandshake();
305
306 if (this.connection.getUseReadAheadInput()) {
307 this.mysqlInput = new ReadAheadInputStream(this.mysqlConnection.getInputStream(), 16384,
308 this.connection.getTraceProtocol(),
309 this.connection.getLog());
310 } else if (this.connection.useUnbufferedInput()) {
311 this.mysqlInput = this.mysqlConnection.getInputStream();
312 } else {
313 this.mysqlInput = new BufferedInputStream(this.mysqlConnection.getInputStream(),
314 16384);
315 }
316
317 this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection.getOutputStream(),
318 16384);
319
320
321 this.isInteractiveClient = this.connection.getInteractiveClient();
322 this.profileSql = this.connection.getProfileSql();
323 this.sessionCalendar = Calendar.getInstance();
324 this.autoGenerateTestcaseScript = this.connection.getAutoGenerateTestcaseScript();
325
326 this.needToGrabQueryFromPacket = (this.profileSql ||
327 this.logSlowQueries ||
328 this.autoGenerateTestcaseScript);
329
330 if (this.connection.getUseNanosForElapsedTime()
331 && Util.nanoTimeAvailable()) {
332 this.useNanosForElapsedTime = true;
333
334 this.queryTimingUnits = Messages.getString("Nanoseconds");
335 } else {
336 this.queryTimingUnits = Messages.getString("Milliseconds");
337 }
338
339 if (this.connection.getLogSlowQueries()) {
340 calculateSlowQueryThreshold();
341 }
342 } catch (IOException ioEx) {
343 throw SQLError.createCommunicationsException(this.connection, 0, 0, ioEx, getExceptionInterceptor());
344 }
345 }
346
347 /**
348 * Does the server send back extra column info?
349 *
350 * @return true if so
351 */
352 public boolean hasLongColumnInfo() {
353 return this.hasLongColumnInfo;
354 }
355
356 protected boolean isDataAvailable() throws SQLException {
357 try {
358 return this.mysqlInput.available() > 0;
359 } catch (IOException ioEx) {
360 throw SQLError.createCommunicationsException(this.connection,
361 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
362 }
363 }
364
365 /**
366 * DOCUMENT ME!
367 *
368 * @return Returns the lastPacketSentTimeMs.
369 */
370 protected long getLastPacketSentTimeMs() {
371 return this.lastPacketSentTimeMs;
372 }
373
374 protected long getLastPacketReceivedTimeMs() {
375 return this.lastPacketReceivedTimeMs;
376 }
377
378 /**
379 * Build a result set. Delegates to buildResultSetWithRows() to build a
380 * JDBC-version-specific ResultSet, given rows as byte data, and field
381 * information.
382 *
383 * @param callingStatement DOCUMENT ME!
384 * @param columnCount the number of columns in the result set
385 * @param maxRows the maximum number of rows to read (-1 means all rows)
386 * @param resultSetType (TYPE_FORWARD_ONLY, TYPE_SCROLL_????)
387 * @param resultSetConcurrency the type of result set (CONCUR_UPDATABLE or
388 * READ_ONLY)
389 * @param streamResults should the result set be read all at once, or
390 * streamed?
391 * @param catalog the database name in use when the result set was created
392 * @param isBinaryEncoded is this result set in native encoding?
393 * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
394 *
395 * @return a result set
396 *
397 * @throws SQLException if a database access error occurs
398 */
399 protected ResultSetImpl getResultSet(StatementImpl callingStatement,
400 long columnCount, int maxRows, int resultSetType,
401 int resultSetConcurrency, boolean streamResults, String catalog,
402 boolean isBinaryEncoded, Field[] metadataFromCache)
403 throws SQLException {
404 Buffer packet; // The packet from the server
405 Field[] fields = null;
406
407 // Read in the column information
408
409 if (metadataFromCache == null /* we want the metadata from the server */) {
410 fields = new Field[(int) columnCount];
411
412 for (int i = 0; i < columnCount; i++) {
413 Buffer fieldPacket = null;
414
415 fieldPacket = readPacket();
416 fields[i] = unpackField(fieldPacket, false);
417 }
418 } else {
419 for (int i = 0; i < columnCount; i++) {
420 skipPacket();
421 }
422 }
423
424 packet = reuseAndReadPacket(this.reusablePacket);
425
426 readServerStatusForResultSets(packet);
427
428 //
429 // Handle cursor-based fetch first
430 //
431
432 if (this.connection.versionMeetsMinimum(5, 0, 2)
433 && this.connection.getUseCursorFetch()
434 && isBinaryEncoded
435 && callingStatement != null
436 && callingStatement.getFetchSize() != 0
437 && callingStatement.getResultSetType() == ResultSet.TYPE_FORWARD_ONLY) {
438 ServerPreparedStatement prepStmt = (com.mysql.jdbc.ServerPreparedStatement) callingStatement;
439
440 boolean usingCursor = true;
441
442 //
443 // Server versions 5.0.5 or newer will only open
444 // a cursor and set this flag if they can, otherwise
445 // they punt and go back to mysql_store_results() behavior
446 //
447
448 if (this.connection.versionMeetsMinimum(5, 0, 5)) {
449 usingCursor = (this.serverStatus &
450 SERVER_STATUS_CURSOR_EXISTS) != 0;
451 }
452
453 if (usingCursor) {
454 RowData rows = new RowDataCursor(
455 this,
456 prepStmt,
457 fields);
458
459 ResultSetImpl rs = buildResultSetWithRows(
460 callingStatement,
461 catalog,
462 fields,
463 rows, resultSetType, resultSetConcurrency, isBinaryEncoded);
464
465 if (usingCursor) {
466 rs.setFetchSize(callingStatement.getFetchSize());
467 }
468
469 return rs;
470 }
471 }
472
473 RowData rowData = null;
474
475 if (!streamResults) {
476 rowData = readSingleRowSet(columnCount, maxRows,
477 resultSetConcurrency, isBinaryEncoded,
478 (metadataFromCache == null) ? fields : metadataFromCache);
479 } else {
480 rowData = new RowDataDynamic(this, (int) columnCount,
481 (metadataFromCache == null) ? fields : metadataFromCache,
482 isBinaryEncoded);
483 this.streamingData = rowData;
484 }
485
486 ResultSetImpl rs = buildResultSetWithRows(callingStatement, catalog,
487 (metadataFromCache == null) ? fields : metadataFromCache,
488 rowData, resultSetType, resultSetConcurrency, isBinaryEncoded);
489
490
491
492 return rs;
493 }
494
495 /**
496 * Forcibly closes the underlying socket to MySQL.
497 */
498 protected final void forceClose() {
499 try {
500 if (this.mysqlInput != null) {
501 this.mysqlInput.close();
502 }
503 } catch (IOException ioEx) {
504 // we can't do anything constructive about this
505 // Let the JVM clean it up later
506 this.mysqlInput = null;
507 }
508
509 try {
510 if (this.mysqlOutput != null) {
511 this.mysqlOutput.close();
512 }
513 } catch (IOException ioEx) {
514 // we can't do anything constructive about this
515 // Let the JVM clean it up later
516 this.mysqlOutput = null;
517 }
518
519 try {
520 if (this.mysqlConnection != null) {
521 this.mysqlConnection.close();
522 }
523 } catch (IOException ioEx) {
524 // we can't do anything constructive about this
525 // Let the JVM clean it up later
526 this.mysqlConnection = null;
527 }
528 }
529
530 /**
531 * Reads and discards a single MySQL packet from the input stream.
532 *
533 * @throws SQLException if the network fails while skipping the
534 * packet.
535 */
536 protected final void skipPacket() throws SQLException {
537 try {
538
539 int lengthRead = readFully(this.mysqlInput, this.packetHeaderBuf,
540 0, 4);
541
542 if (lengthRead < 4) {
543 forceClose();
544 throw new IOException(Messages.getString("MysqlIO.1")); //$NON-NLS-1$
545 }
546
547 int packetLength = (this.packetHeaderBuf[0] & 0xff)
548 + ((this.packetHeaderBuf[1] & 0xff) << 8)
549 + ((this.packetHeaderBuf[2] & 0xff) << 16);
550
551 if (this.traceProtocol) {
552 StringBuffer traceMessageBuf = new StringBuffer();
553
554 traceMessageBuf.append(Messages.getString("MysqlIO.2")); //$NON-NLS-1$
555 traceMessageBuf.append(packetLength);
556 traceMessageBuf.append(Messages.getString("MysqlIO.3")); //$NON-NLS-1$
557 traceMessageBuf.append(StringUtils.dumpAsHex(
558 this.packetHeaderBuf, 4));
559
560 this.connection.getLog().logTrace(traceMessageBuf.toString());
561 }
562
563 byte multiPacketSeq = this.packetHeaderBuf[3];
564
565 if (!this.packetSequenceReset) {
566 if (this.enablePacketDebug && this.checkPacketSequence) {
567 checkPacketSequencing(multiPacketSeq);
568 }
569 } else {
570 this.packetSequenceReset = false;
571 }
572
573 this.readPacketSequence = multiPacketSeq;
574
575 skipFully(this.mysqlInput, packetLength);
576 } catch (IOException ioEx) {
577 throw SQLError.createCommunicationsException(this.connection,
578 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
579 } catch (OutOfMemoryError oom) {
580 try {
581 this.connection.realClose(false, false, true, oom);
582 } finally {
583 throw oom;
584 }
585 }
586 }
587
588 /**
589 * Read one packet from the MySQL server
590 *
591 * @return the packet from the server.
592 *
593 * @throws SQLException DOCUMENT ME!
594 * @throws CommunicationsException DOCUMENT ME!
595 */
596 protected final Buffer readPacket() throws SQLException {
597 try {
598
599 int lengthRead = readFully(this.mysqlInput,
600 this.packetHeaderBuf, 0, 4);
601
602 if (lengthRead < 4) {
603 forceClose();
604 throw new IOException(Messages.getString("MysqlIO.1")); //$NON-NLS-1$
605 }
606
607 int packetLength = (this.packetHeaderBuf[0] & 0xff) +
608 ((this.packetHeaderBuf[1] & 0xff) << 8) +
609 ((this.packetHeaderBuf[2] & 0xff) << 16);
610
611 if (packetLength > this.maxAllowedPacket) {
612 throw new PacketTooBigException(packetLength, this.maxAllowedPacket);
613 }
614
615 if (this.traceProtocol) {
616 StringBuffer traceMessageBuf = new StringBuffer();
617
618 traceMessageBuf.append(Messages.getString("MysqlIO.2")); //$NON-NLS-1$
619 traceMessageBuf.append(packetLength);
620 traceMessageBuf.append(Messages.getString("MysqlIO.3")); //$NON-NLS-1$
621 traceMessageBuf.append(StringUtils.dumpAsHex(
622 this.packetHeaderBuf, 4));
623
624 this.connection.getLog().logTrace(traceMessageBuf.toString());
625 }
626
627 byte multiPacketSeq = this.packetHeaderBuf[3];
628
629 if (!this.packetSequenceReset) {
630 if (this.enablePacketDebug && this.checkPacketSequence) {
631 checkPacketSequencing(multiPacketSeq);
632 }
633 } else {
634 this.packetSequenceReset = false;
635 }
636
637 this.readPacketSequence = multiPacketSeq;
638
639 // Read data
640 byte[] buffer = new byte[packetLength + 1];
641 int numBytesRead = readFully(this.mysqlInput, buffer, 0,
642 packetLength);
643
644 if (numBytesRead != packetLength) {
645 throw new IOException("Short read, expected " +
646 packetLength + " bytes, only read " + numBytesRead);
647 }
648
649 buffer[packetLength] = 0;
650
651 Buffer packet = new Buffer(buffer);
652 packet.setBufLength(packetLength + 1);
653
654 if (this.traceProtocol) {
655 StringBuffer traceMessageBuf = new StringBuffer();
656
657 traceMessageBuf.append(Messages.getString("MysqlIO.4")); //$NON-NLS-1$
658 traceMessageBuf.append(getPacketDumpToLog(packet,
659 packetLength));
660
661 this.connection.getLog().logTrace(traceMessageBuf.toString());
662 }
663
664 if (this.enablePacketDebug) {
665 enqueuePacketForDebugging(false, false, 0,
666 this.packetHeaderBuf, packet);
667 }
668
669 if (this.connection.getMaintainTimeStats()) {
670 this.lastPacketReceivedTimeMs = System.currentTimeMillis();
671 }
672
673 return packet;
674 } catch (IOException ioEx) {
675 throw SQLError.createCommunicationsException(this.connection,
676 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
677 } catch (OutOfMemoryError oom) {
678 try {
679 this.connection.realClose(false, false, true, oom);
680 } finally {
681 throw oom;
682 }
683 }
684 }
685
686 /**
687 * Unpacks the Field information from the given packet. Understands pre 4.1
688 * and post 4.1 server version field packet structures.
689 *
690 * @param packet the packet containing the field information
691 * @param extractDefaultValues should default values be extracted?
692 *
693 * @return the unpacked field
694 *
695 * @throws SQLException DOCUMENT ME!
696 */
697 protected final Field unpackField(Buffer packet,
698 boolean extractDefaultValues) throws SQLException {
699 if (this.use41Extensions) {
700 // we only store the position of the string and
701 // materialize only if needed...
702 if (this.has41NewNewProt) {
703 // Not used yet, 5.0?
704 int catalogNameStart = packet.getPosition() + 1;
705 int catalogNameLength = packet.fastSkipLenString();
706 catalogNameStart = adjustStartForFieldLength(catalogNameStart, catalogNameLength);
707 }
708
709 int databaseNameStart = packet.getPosition() + 1;
710 int databaseNameLength = packet.fastSkipLenString();
711 databaseNameStart = adjustStartForFieldLength(databaseNameStart, databaseNameLength);
712
713 int tableNameStart = packet.getPosition() + 1;
714 int tableNameLength = packet.fastSkipLenString();
715 tableNameStart = adjustStartForFieldLength(tableNameStart, tableNameLength);
716
717 // orgTableName is never used so skip
718 int originalTableNameStart = packet.getPosition() + 1;
719 int originalTableNameLength = packet.fastSkipLenString();
720 originalTableNameStart = adjustStartForFieldLength(originalTableNameStart, originalTableNameLength);
721
722 // we only store the position again...
723 int nameStart = packet.getPosition() + 1;
724 int nameLength = packet.fastSkipLenString();
725
726 nameStart = adjustStartForFieldLength(nameStart, nameLength);
727
728 // orgColName is not required so skip...
729 int originalColumnNameStart = packet.getPosition() + 1;
730 int originalColumnNameLength = packet.fastSkipLenString();
731 originalColumnNameStart = adjustStartForFieldLength(originalColumnNameStart, originalColumnNameLength);
732
733 packet.readByte();
734
735 short charSetNumber = (short) packet.readInt();
736
737 long colLength = 0;
738
739 if (this.has41NewNewProt) {
740 colLength = packet.readLong();
741 } else {
742 colLength = packet.readLongInt();
743 }
744
745 int colType = packet.readByte() & 0xff;
746
747 short colFlag = 0;
748
749 if (this.hasLongColumnInfo) {
750 colFlag = (short) packet.readInt();
751 } else {
752 colFlag = (short) (packet.readByte() & 0xff);
753 }
754
755 int colDecimals = packet.readByte() & 0xff;
756
757 int defaultValueStart = -1;
758 int defaultValueLength = -1;
759
760 if (extractDefaultValues) {
761 defaultValueStart = packet.getPosition() + 1;
762 defaultValueLength = packet.fastSkipLenString();
763 }
764
765 Field field = new Field(this.connection, packet.getByteBuffer(),
766 databaseNameStart, databaseNameLength, tableNameStart,
767 tableNameLength, originalTableNameStart,
768 originalTableNameLength, nameStart, nameLength,
769 originalColumnNameStart, originalColumnNameLength,
770 colLength, colType, colFlag, colDecimals,
771 defaultValueStart, defaultValueLength, charSetNumber);
772
773 return field;
774 }
775
776 int tableNameStart = packet.getPosition() + 1;
777 int tableNameLength = packet.fastSkipLenString();
778 tableNameStart = adjustStartForFieldLength(tableNameStart, tableNameLength);
779
780 int nameStart = packet.getPosition() + 1;
781 int nameLength = packet.fastSkipLenString();
782 nameStart = adjustStartForFieldLength(nameStart, nameLength);
783
784 int colLength = packet.readnBytes();
785 int colType = packet.readnBytes();
786 packet.readByte(); // We know it's currently 2
787
788 short colFlag = 0;
789
790 if (this.hasLongColumnInfo) {
791 colFlag = (short) (packet.readInt());
792 } else {
793 colFlag = (short) (packet.readByte() & 0xff);
794 }
795
796 int colDecimals = (packet.readByte() & 0xff);
797
798 if (this.colDecimalNeedsBump) {
799 colDecimals++;
800 }
801
802 Field field = new Field(this.connection, packet.getByteBuffer(),
803 nameStart, nameLength, tableNameStart, tableNameLength,
804 colLength, colType, colFlag, colDecimals);
805
806 return field;
807 }
808
809 private int adjustStartForFieldLength(int nameStart, int nameLength) {
810 if (nameLength < 251) {
811 return nameStart;
812 }
813
814 if (nameLength >= 251 && nameLength < 65536) {
815 return nameStart + 2;
816 }
817
818 if (nameLength >= 65536 && nameLength < 16777216) {
819 return nameStart + 3;
820 }
821
822 return nameStart + 8;
823 }
824
825 protected boolean isSetNeededForAutoCommitMode(boolean autoCommitFlag) {
826 if (this.use41Extensions && this.connection.getElideSetAutoCommits()) {
827 boolean autoCommitModeOnServer = ((this.serverStatus &
828 SERVER_STATUS_AUTOCOMMIT) != 0);
829
830 if (!autoCommitFlag && versionMeetsMinimum(5, 0, 0)) {
831 // Just to be safe, check if a transaction is in progress on the server....
832 // if so, then we must be in autoCommit == false
833 // therefore return the opposite of transaction status
834 boolean inTransactionOnServer = ((this.serverStatus &
835 SERVER_STATUS_IN_TRANS) != 0);
836
837 return !inTransactionOnServer;
838 }
839
840 return autoCommitModeOnServer != autoCommitFlag;
841 }
842
843 return true;
844 }
845
846 protected boolean inTransactionOnServer() {
847 return (this.serverStatus & SERVER_STATUS_IN_TRANS) != 0;
848 }
849
850 /**
851 * Re-authenticates as the given user and password
852 *
853 * @param userName DOCUMENT ME!
854 * @param password DOCUMENT ME!
855 * @param database DOCUMENT ME!
856 *
857 * @throws SQLException DOCUMENT ME!
858 */
859 protected void changeUser(String userName, String password, String database)
860 throws SQLException {
861 this.packetSequence = -1;
862
863 int passwordLength = 16;
864 int userLength = (userName != null) ? userName.length() : 0;
865 int databaseLength = (database != null) ? database.length() : 0;
866
867 int packLength = ((userLength + passwordLength + databaseLength) * 2) + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;
868
869 if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
870 Buffer changeUserPacket = new Buffer(packLength + 1);
871 changeUserPacket.writeByte((byte) MysqlDefs.COM_CHANGE_USER);
872
873 if (versionMeetsMinimum(4, 1, 1)) {
874 secureAuth411(changeUserPacket, packLength, userName, password,
875 database, false);
876 } else {
877 secureAuth(changeUserPacket, packLength, userName, password,
878 database, false);
879 }
880 } else {
881 // Passwords can be 16 chars long
882 Buffer packet = new Buffer(packLength);
883 packet.writeByte((byte) MysqlDefs.COM_CHANGE_USER);
884
885 // User/Password data
886 packet.writeString(userName);
887
888 if (this.protocolVersion > 9) {
889 packet.writeString(Util.newCrypt(password, this.seed));
890 } else {
891 packet.writeString(Util.oldCrypt(password, this.seed));
892 }
893
894 boolean localUseConnectWithDb = this.useConnectWithDb &&
895 (database != null && database.length() > 0);
896
897 if (localUseConnectWithDb) {
898 packet.writeString(database);
899 }
900
901 send(packet, packet.getPosition());
902 checkErrorPacket();
903
904 if (!localUseConnectWithDb) {
905 changeDatabaseTo(database);
906 }
907 }
908 }
909
910 /**
911 * Checks for errors in the reply packet, and if none, returns the reply
912 * packet, ready for reading
913 *
914 * @return a packet ready for reading.
915 *
916 * @throws SQLException is the packet is an error packet
917 */
918 protected Buffer checkErrorPacket() throws SQLException {
919 return checkErrorPacket(-1);
920 }
921
922 /**
923 * Determines if the database charset is the same as the platform charset
924 */
925 protected void checkForCharsetMismatch() {
926 if (this.connection.getUseUnicode() &&
927 (this.connection.getEncoding() != null)) {
928 String encodingToCheck = jvmPlatformCharset;
929
930 if (encodingToCheck == null) {
931 encodingToCheck = System.getProperty("file.encoding"); //$NON-NLS-1$
932 }
933
934 if (encodingToCheck == null) {
935 this.platformDbCharsetMatches = false;
936 } else {
937 this.platformDbCharsetMatches = encodingToCheck.equals(this.connection.getEncoding());
938 }
939 }
940 }
941
942 protected void clearInputStream() throws SQLException {
943
944 try {
945 int len = this.mysqlInput.available();
946
947 while (len > 0) {
948 this.mysqlInput.skip(len);
949 len = this.mysqlInput.available();
950 }
951 } catch (IOException ioEx) {
952 throw SQLError.createCommunicationsException(this.connection,
953 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
954 }
955 }
956
957 protected void resetReadPacketSequence() {
958 this.readPacketSequence = 0;
959 }
960
961 protected void dumpPacketRingBuffer() throws SQLException {
962 if ((this.packetDebugRingBuffer != null) &&
963 this.connection.getEnablePacketDebug()) {
964 StringBuffer dumpBuffer = new StringBuffer();
965
966 dumpBuffer.append("Last " + this.packetDebugRingBuffer.size() +
967 " packets received from server, from oldest->newest:\n");
968 dumpBuffer.append("\n");
969
970 for (Iterator ringBufIter = this.packetDebugRingBuffer.iterator();
971 ringBufIter.hasNext();) {
972 dumpBuffer.append((StringBuffer) ringBufIter.next());
973 dumpBuffer.append("\n");
974 }
975
976 this.connection.getLog().logTrace(dumpBuffer.toString());
977 }
978 }
979
980 /**
981 * Runs an 'EXPLAIN' on the given query and dumps the results to the log
982 *
983 * @param querySQL DOCUMENT ME!
984 * @param truncatedQuery DOCUMENT ME!
985 *
986 * @throws SQLException DOCUMENT ME!
987 */
988 protected void explainSlowQuery(byte[] querySQL, String truncatedQuery)
989 throws SQLException {
990 if (StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery, "SELECT")) { //$NON-NLS-1$
991
992 PreparedStatement stmt = null;
993 java.sql.ResultSet rs = null;
994
995 try {
996 stmt = (PreparedStatement) this.connection.clientPrepareStatement("EXPLAIN ?"); //$NON-NLS-1$
997 stmt.setBytesNoEscapeNoQuotes(1, querySQL);
998 rs = stmt.executeQuery();
999
1000 StringBuffer explainResults = new StringBuffer(Messages.getString(
1001 "MysqlIO.8") + truncatedQuery //$NON-NLS-1$
1002 +Messages.getString("MysqlIO.9")); //$NON-NLS-1$
1003
1004 ResultSetUtil.appendResultSetSlashGStyle(explainResults, rs);
1005
1006 this.connection.getLog().logWarn(explainResults.toString());
1007 } catch (SQLException sqlEx) {
1008 } finally {
1009 if (rs != null) {
1010 rs.close();
1011 }
1012
1013 if (stmt != null) {
1014 stmt.close();
1015 }
1016 }
1017 } else {
1018 }
1019 }
1020
1021 static int getMaxBuf() {
1022 return maxBufferSize;
1023 }
1024
1025 /**
1026 * Get the major version of the MySQL server we are talking to.
1027 *
1028 * @return DOCUMENT ME!
1029 */
1030 final int getServerMajorVersion() {
1031 return this.serverMajorVersion;
1032 }
1033
1034 /**
1035 * Get the minor version of the MySQL server we are talking to.
1036 *
1037 * @return DOCUMENT ME!
1038 */
1039 final int getServerMinorVersion() {
1040 return this.serverMinorVersion;
1041 }
1042
1043 /**
1044 * Get the sub-minor version of the MySQL server we are talking to.
1045 *
1046 * @return DOCUMENT ME!
1047 */
1048 final int getServerSubMinorVersion() {
1049 return this.serverSubMinorVersion;
1050 }
1051
1052 /**
1053 * Get the version string of the server we are talking to
1054 *
1055 * @return DOCUMENT ME!
1056 */
1057 String getServerVersion() {
1058 return this.serverVersion;
1059 }
1060
1061 /**
1062 * Initialize communications with the MySQL server. Handles logging on, and
1063 * handling initial connection errors.
1064 *
1065 * @param user DOCUMENT ME!
1066 * @param password DOCUMENT ME!
1067 * @param database DOCUMENT ME!
1068 *
1069 * @throws SQLException DOCUMENT ME!
1070 * @throws CommunicationsException DOCUMENT ME!
1071 */
1072 void doHandshake(String user, String password, String database)
1073 throws SQLException {
1074 // Read the first packet
1075 this.checkPacketSequence = false;
1076 this.readPacketSequence = 0;
1077
1078 Buffer buf = readPacket();
1079
1080 // Get the protocol version
1081 this.protocolVersion = buf.readByte();
1082
1083 if (this.protocolVersion == -1) {
1084 try {
1085 this.mysqlConnection.close();
1086 } catch (Exception e) {
1087 // ignore
1088 }
1089
1090 int errno = 2000;
1091
1092 errno = buf.readInt();
1093
1094 String serverErrorMessage = buf.readString("ASCII", getExceptionInterceptor());
1095
1096 StringBuffer errorBuf = new StringBuffer(Messages.getString(
1097 "MysqlIO.10")); //$NON-NLS-1$
1098 errorBuf.append(serverErrorMessage);
1099 errorBuf.append("\""); //$NON-NLS-1$
1100
1101 String xOpen = SQLError.mysqlToSqlState(errno,
1102 this.connection.getUseSqlStateCodes());
1103
1104 throw SQLError.createSQLException(SQLError.get(xOpen) + ", " //$NON-NLS-1$
1105 +errorBuf.toString(), xOpen, errno, getExceptionInterceptor());
1106 }
1107
1108 this.serverVersion = buf.readString("ASCII", getExceptionInterceptor());
1109
1110 // Parse the server version into major/minor/subminor
1111 int point = this.serverVersion.indexOf('.'); //$NON-NLS-1$
1112
1113 if (point != -1) {
1114 try {
1115 int n = Integer.parseInt(this.serverVersion.substring(0, point));
1116 this.serverMajorVersion = n;
1117 } catch (NumberFormatException NFE1) {
1118 // ignore
1119 }
1120
1121 String remaining = this.serverVersion.substring(point + 1,
1122 this.serverVersion.length());
1123 point = remaining.indexOf('.'); //$NON-NLS-1$
1124
1125 if (point != -1) {
1126 try {
1127 int n = Integer.parseInt(remaining.substring(0, point));
1128 this.serverMinorVersion = n;
1129 } catch (NumberFormatException nfe) {
1130 // ignore
1131 }
1132
1133 remaining = remaining.substring(point + 1, remaining.length());
1134
1135 int pos = 0;
1136
1137 while (pos < remaining.length()) {
1138 if ((remaining.charAt(pos) < '0') ||
1139 (remaining.charAt(pos) > '9')) {
1140 break;
1141 }
1142
1143 pos++;
1144 }
1145
1146 try {
1147 int n = Integer.parseInt(remaining.substring(0, pos));
1148 this.serverSubMinorVersion = n;
1149 } catch (NumberFormatException nfe) {
1150 // ignore
1151 }
1152 }
1153 }
1154
1155 if (versionMeetsMinimum(4, 0, 8)) {
1156 this.maxThreeBytes = (256 * 256 * 256) - 1;
1157 this.useNewLargePackets = true;
1158 } else {
1159 this.maxThreeBytes = 255 * 255 * 255;
1160 this.useNewLargePackets = false;
1161 }
1162
1163 this.colDecimalNeedsBump = versionMeetsMinimum(3, 23, 0);
1164 this.colDecimalNeedsBump = !versionMeetsMinimum(3, 23, 15); // guess? Not noted in changelog
1165 this.useNewUpdateCounts = versionMeetsMinimum(3, 22, 5);
1166
1167 threadId = buf.readLong();
1168 this.seed = buf.readString("ASCII", getExceptionInterceptor());
1169
1170 this.serverCapabilities = 0;
1171
1172 if (buf.getPosition() < buf.getBufLength()) {
1173 this.serverCapabilities = buf.readInt();
1174 }
1175
1176 if (versionMeetsMinimum(4, 1, 1)) {
1177 int position = buf.getPosition();
1178
1179 /* New protocol with 16 bytes to describe server characteristics */
1180 this.serverCharsetIndex = buf.readByte() & 0xff;
1181 this.serverStatus = buf.readInt();
1182 checkTransactionState(0);
1183 buf.setPosition(position + 16);
1184
1185 String seedPart2 = buf.readString("ASCII", getExceptionInterceptor());
1186 StringBuffer newSeed = new StringBuffer(20);
1187 newSeed.append(this.seed);
1188 newSeed.append(seedPart2);
1189 this.seed = newSeed.toString();
1190 }
1191
1192 if (((this.serverCapabilities & CLIENT_COMPRESS) != 0) &&
1193 this.connection.getUseCompression()) {
1194 this.clientParam |= CLIENT_COMPRESS;
1195 }
1196
1197 this.useConnectWithDb = (database != null) &&
1198 (database.length() > 0) &&
1199 !this.connection.getCreateDatabaseIfNotExist();
1200
1201 if (this.useConnectWithDb) {
1202 this.clientParam |= CLIENT_CONNECT_WITH_DB;
1203 }
1204
1205 if (((this.serverCapabilities & CLIENT_SSL) == 0) &&
1206 this.connection.getUseSSL()) {
1207 if (this.connection.getRequireSSL()) {
1208 this.connection.close();
1209 forceClose();
1210 throw SQLError.createSQLException(Messages.getString("MysqlIO.15"), //$NON-NLS-1$
1211 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
1212 }
1213
1214 this.connection.setUseSSL(false);
1215 }
1216
1217 if ((this.serverCapabilities & CLIENT_LONG_FLAG) != 0) {
1218 // We understand other column flags, as well
1219 this.clientParam |= CLIENT_LONG_FLAG;
1220 this.hasLongColumnInfo = true;
1221 }
1222
1223 // return FOUND rows
1224 if (!this.connection.getUseAffectedRows()) {
1225 this.clientParam |= CLIENT_FOUND_ROWS;
1226 }
1227
1228 if (this.connection.getAllowLoadLocalInfile()) {
1229 this.clientParam |= CLIENT_LOCAL_FILES;
1230 }
1231
1232 if (this.isInteractiveClient) {
1233 this.clientParam |= CLIENT_INTERACTIVE;
1234 }
1235
1236 // Authenticate
1237 if (this.protocolVersion > 9) {
1238 this.clientParam |= CLIENT_LONG_PASSWORD; // for long passwords
1239 } else {
1240 this.clientParam &= ~CLIENT_LONG_PASSWORD;
1241 }
1242
1243 //
1244 // 4.1 has some differences in the protocol
1245 //
1246 if (versionMeetsMinimum(4, 1, 0)) {
1247 if (versionMeetsMinimum(4, 1, 1)) {
1248 this.clientParam |= CLIENT_PROTOCOL_41;
1249 this.has41NewNewProt = true;
1250
1251 // Need this to get server status values
1252 this.clientParam |= CLIENT_TRANSACTIONS;
1253
1254 // We always allow multiple result sets
1255 this.clientParam |= CLIENT_MULTI_RESULTS;
1256
1257 // We allow the user to configure whether
1258 // or not they want to support multiple queries
1259 // (by default, this is disabled).
1260 if (this.connection.getAllowMultiQueries()) {
1261 this.clientParam |= CLIENT_MULTI_QUERIES;
1262 }
1263 } else {
1264 this.clientParam |= CLIENT_RESERVED;
1265 this.has41NewNewProt = false;
1266 }
1267
1268 this.use41Extensions = true;
1269 }
1270
1271 int passwordLength = 16;
1272 int userLength = (user != null) ? user.length() : 0;
1273 int databaseLength = (database != null) ? database.length() : 0;
1274
1275 int packLength = ((userLength + passwordLength + databaseLength) * 2) + 7 + HEADER_LENGTH + AUTH_411_OVERHEAD;
1276
1277 Buffer packet = null;
1278
1279 if (!this.connection.getUseSSL()) {
1280 if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
1281 this.clientParam |= CLIENT_SECURE_CONNECTION;
1282
1283 if (versionMeetsMinimum(4, 1, 1)) {
1284 secureAuth411(null, packLength, user, password, database,
1285 true);
1286 } else {
1287 secureAuth(null, packLength, user, password, database, true);
1288 }
1289 } else {
1290 // Passwords can be 16 chars long
1291 packet = new Buffer(packLength);
1292
1293 if ((this.clientParam & CLIENT_RESERVED) != 0) {
1294 if (versionMeetsMinimum(4, 1, 1)) {
1295 packet.writeLong(this.clientParam);
1296 packet.writeLong(this.maxThreeBytes);
1297
1298 // charset, JDBC will connect as 'latin1',
1299 // and use 'SET NAMES' to change to the desired
1300 // charset after the connection is established.
1301 packet.writeByte((byte) 8);
1302
1303 // Set of bytes reserved for future use.
1304 packet.writeBytesNoNull(new byte[23]);
1305 } else {
1306 packet.writeLong(this.clientParam);
1307 packet.writeLong(this.maxThreeBytes);
1308 }
1309 } else {
1310 packet.writeInt((int) this.clientParam);
1311 packet.writeLongInt(this.maxThreeBytes);
1312 }
1313
1314 // User/Password data
1315 packet.writeString(user, CODE_PAGE_1252, this.connection);
1316
1317 if (this.protocolVersion > 9) {
1318 packet.writeString(Util.newCrypt(password, this.seed), CODE_PAGE_1252, this.connection);
1319 } else {
1320 packet.writeString(Util.oldCrypt(password, this.seed), CODE_PAGE_1252, this.connection);
1321 }
1322
1323 if (this.useConnectWithDb) {
1324 packet.writeString(database, CODE_PAGE_1252, this.connection);
1325 }
1326
1327 send(packet, packet.getPosition());
1328 }
1329 } else {
1330 negotiateSSLConnection(user, password, database, packLength);
1331 }
1332
1333 // Check for errors, not for 4.1.1 or newer,
1334 // as the new auth protocol doesn't work that way
1335 // (see secureAuth411() for more details...)
1336 if (!versionMeetsMinimum(4, 1, 1)) {
1337 checkErrorPacket();
1338 }
1339
1340 //
1341 // Can't enable compression until after handshake
1342 //
1343 if (((this.serverCapabilities & CLIENT_COMPRESS) != 0) &&
1344 this.connection.getUseCompression()) {
1345 // The following matches with ZLIB's
1346 // compress()
1347 this.deflater = new Deflater();
1348 this.useCompression = true;
1349 this.mysqlInput = new CompressedInputStream(this.connection,
1350 this.mysqlInput);
1351 }
1352
1353 if (!this.useConnectWithDb) {
1354 changeDatabaseTo(database);
1355 }
1356
1357 try {
1358 this.mysqlConnection = this.socketFactory.afterHandshake();
1359 } catch (IOException ioEx) {
1360 throw SQLError.createCommunicationsException(this.connection, this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
1361 }
1362 }
1363
1364 private void changeDatabaseTo(String database) throws SQLException {
1365 if (database == null || database.length() == 0) {
1366 return;
1367 }
1368
1369 try {
1370 sendCommand(MysqlDefs.INIT_DB, database, null, false, null, 0);
1371 } catch (Exception ex) {
1372 if (this.connection.getCreateDatabaseIfNotExist()) {
1373 sendCommand(MysqlDefs.QUERY, "CREATE DATABASE IF NOT EXISTS " +
1374 database,
1375 null, false, null, 0);
1376 sendCommand(MysqlDefs.INIT_DB, database, null, false, null, 0);
1377 } else {
1378 throw SQLError.createCommunicationsException(this.connection,
1379 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ex, getExceptionInterceptor());
1380 }
1381 }
1382 }
1383
1384 /**
1385 * Retrieve one row from the MySQL server. Note: this method is not
1386 * thread-safe, but it is only called from methods that are guarded by
1387 * synchronizing on this object.
1388 *
1389 * @param fields DOCUMENT ME!
1390 * @param columnCount DOCUMENT ME!
1391 * @param isBinaryEncoded DOCUMENT ME!
1392 * @param resultSetConcurrency DOCUMENT ME!
1393 * @param b
1394 *
1395 * @return DOCUMENT ME!
1396 *
1397 * @throws SQLException DOCUMENT ME!
1398 */
1399 final ResultSetRow nextRow(Field[] fields, int columnCount,
1400 boolean isBinaryEncoded, int resultSetConcurrency,
1401 boolean useBufferRowIfPossible,
1402 boolean useBufferRowExplicit,
1403 boolean canReuseRowPacketForBufferRow, Buffer existingRowPacket)
1404 throws SQLException {
1405
1406 if (this.useDirectRowUnpack && existingRowPacket == null
1407 && !isBinaryEncoded && !useBufferRowIfPossible
1408 && !useBufferRowExplicit) {
1409 return nextRowFast(fields, columnCount, isBinaryEncoded, resultSetConcurrency,
1410 useBufferRowIfPossible, useBufferRowExplicit, canReuseRowPacketForBufferRow);
1411 }
1412
1413 Buffer rowPacket = null;
1414
1415 if (existingRowPacket == null) {
1416 rowPacket = checkErrorPacket();
1417
1418 if (!useBufferRowExplicit && useBufferRowIfPossible) {
1419 if (rowPacket.getBufLength() > this.useBufferRowSizeThreshold) {
1420 useBufferRowExplicit = true;
1421 }
1422 }
1423 } else {
1424 // We attempted to do nextRowFast(), but the packet was a
1425 // multipacket, so we couldn't unpack it directly
1426 rowPacket = existingRowPacket;
1427 checkErrorPacket(existingRowPacket);
1428 }
1429
1430
1431 if (!isBinaryEncoded) {
1432 //
1433 // Didn't read an error, so re-position to beginning
1434 // of packet in order to read result set data
1435 //
1436 rowPacket.setPosition(rowPacket.getPosition() - 1);
1437
1438 if (!rowPacket.isLastDataPacket()) {
1439 if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE
1440 || (!useBufferRowIfPossible && !useBufferRowExplicit)) {
1441
1442 byte[][] rowData = new byte[columnCount][];
1443
1444 for (int i = 0; i < columnCount; i++) {
1445 rowData[i] = rowPacket.readLenByteArray(0);
1446 }
1447
1448 return new ByteArrayRow(rowData, getExceptionInterceptor());
1449 }
1450
1451 if (!canReuseRowPacketForBufferRow) {
1452 this.reusablePacket = new Buffer(rowPacket.getBufLength());
1453 }
1454
1455 return new BufferRow(rowPacket, fields, false, getExceptionInterceptor());
1456
1457 }
1458
1459 readServerStatusForResultSets(rowPacket);
1460
1461 return null;
1462 }
1463
1464 //
1465 // Handle binary-encoded data for server-side
1466 // PreparedStatements...
1467 //
1468 if (!rowPacket.isLastDataPacket()) {
1469 if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE
1470 || (!useBufferRowIfPossible && !useBufferRowExplicit)) {
1471 return unpackBinaryResultSetRow(fields, rowPacket,
1472 resultSetConcurrency);
1473 }
1474
1475 if (!canReuseRowPacketForBufferRow) {
1476 this.reusablePacket = new Buffer(rowPacket.getBufLength());
1477 }
1478
1479 return new BufferRow(rowPacket, fields, true, getExceptionInterceptor());
1480 }
1481
1482 rowPacket.setPosition(rowPacket.getPosition() - 1);
1483 readServerStatusForResultSets(rowPacket);
1484
1485 return null;
1486 }
1487
1488 final ResultSetRow nextRowFast(Field[] fields, int columnCount,
1489 boolean isBinaryEncoded, int resultSetConcurrency,
1490 boolean useBufferRowIfPossible,
1491 boolean useBufferRowExplicit, boolean canReuseRowPacket)
1492 throws SQLException {
1493 try {
1494 int lengthRead = readFully(this.mysqlInput, this.packetHeaderBuf,
1495 0, 4);
1496
1497 if (lengthRead < 4) {
1498 forceClose();
1499 throw new RuntimeException(Messages.getString("MysqlIO.43")); //$NON-NLS-1$
1500 }
1501
1502 int packetLength = (this.packetHeaderBuf[0] & 0xff)
1503 + ((this.packetHeaderBuf[1] & 0xff) << 8)
1504 + ((this.packetHeaderBuf[2] & 0xff) << 16);
1505
1506 // Have we stumbled upon a multi-packet?
1507 if (packetLength == this.maxThreeBytes) {
1508 reuseAndReadPacket(this.reusablePacket, packetLength);
1509
1510 // Go back to "old" way which uses packets
1511 return nextRow(fields, columnCount, isBinaryEncoded, resultSetConcurrency,
1512 useBufferRowIfPossible, useBufferRowExplicit,
1513 canReuseRowPacket, this.reusablePacket);
1514 }
1515
1516 // Does this go over the threshold where we should use a BufferRow?
1517
1518 if (packetLength > this.useBufferRowSizeThreshold) {
1519 reuseAndReadPacket(this.reusablePacket, packetLength);
1520
1521 // Go back to "old" way which uses packets
1522 return nextRow(fields, columnCount, isBinaryEncoded, resultSetConcurrency,
1523 true, true,
1524 false, this.reusablePacket);
1525 }
1526
1527 int remaining = packetLength;
1528
1529 boolean firstTime = true;
1530
1531 byte[][] rowData = null;
1532
1533 for (int i = 0; i < columnCount; i++) {
1534
1535 int sw = this.mysqlInput.read() & 0xff;
1536 remaining--;
1537
1538 if (firstTime) {
1539 if (sw == 255) {
1540 // error packet - we assemble it whole for "fidelity"
1541 // in case we ever need an entire packet in checkErrorPacket()
1542 // but we could've gotten away with just writing the error code
1543 // and message in it (for now).
1544 Buffer errorPacket = new Buffer(packetLength + HEADER_LENGTH);
1545 errorPacket.setPosition(0);
1546 errorPacket.writeByte(this.packetHeaderBuf[0]);
1547 errorPacket.writeByte(this.packetHeaderBuf[1]);
1548 errorPacket.writeByte(this.packetHeaderBuf[2]);
1549 errorPacket.writeByte((byte) 1);
1550 errorPacket.writeByte((byte)sw);
1551 readFully(this.mysqlInput, errorPacket.getByteBuffer(), 5, packetLength - 1);
1552 errorPacket.setPosition(4);
1553 checkErrorPacket(errorPacket);
1554 }
1555
1556 if (sw == 254 && packetLength < 9) {
1557 if (this.use41Extensions) {
1558 this.warningCount = (this.mysqlInput.read() & 0xff)
1559 | ((this.mysqlInput.read() & 0xff) << 8);
1560 remaining -= 2;
1561
1562 if (this.warningCount > 0) {
1563 this.hadWarnings = true; // this is a
1564 // 'latch', it's
1565 // reset by
1566 // sendCommand()
1567 }
1568
1569 this.oldServerStatus = this.serverStatus;
1570
1571 this.serverStatus = (this.mysqlInput.read() & 0xff)
1572 | ((this.mysqlInput.read() & 0xff) << 8);
1573 checkTransactionState(oldServerStatus);
1574
1575 remaining -= 2;
1576
1577 if (remaining > 0) {
1578 skipFully(this.mysqlInput, remaining);
1579 }
1580 }
1581
1582 return null; // last data packet
1583 }
1584
1585 rowData = new byte[columnCount][];
1586
1587 firstTime = false;
1588 }
1589
1590 int len = 0;
1591
1592 switch (sw) {
1593 case 251:
1594 len = NULL_LENGTH;
1595 break;
1596
1597 case 252:
1598 len = (this.mysqlInput.read() & 0xff)
1599 | ((this.mysqlInput.read() & 0xff) << 8);
1600 remaining -= 2;
1601 break;
1602
1603 case 253:
1604 len = (this.mysqlInput.read() & 0xff)
1605 | ((this.mysqlInput.read() & 0xff) << 8)
1606 | ((this.mysqlInput.read() & 0xff) << 16);
1607
1608 remaining -= 3;
1609 break;
1610
1611 case 254:
1612 len = (int) ((this.mysqlInput.read() & 0xff)
1613 | ((long) (this.mysqlInput.read() & 0xff) << 8)
1614 | ((long) (this.mysqlInput.read() & 0xff) << 16)
1615 | ((long) (this.mysqlInput.read() & 0xff) << 24)
1616 | ((long) (this.mysqlInput.read() & 0xff) << 32)
1617 | ((long) (this.mysqlInput.read() & 0xff) << 40)
1618 | ((long) (this.mysqlInput.read() & 0xff) << 48)
1619 | ((long) (this.mysqlInput.read() & 0xff) << 56));
1620 remaining -= 8;
1621 break;
1622
1623 default:
1624 len = sw;
1625 }
1626
1627 if (len == NULL_LENGTH) {
1628 rowData[i] = null;
1629 } else if (len == 0) {
1630 rowData[i] = Constants.EMPTY_BYTE_ARRAY;
1631 } else {
1632 rowData[i] = new byte[len];
1633
1634 int bytesRead = readFully(this.mysqlInput, rowData[i], 0,
1635 len);
1636
1637 if (bytesRead != len) {
1638 throw SQLError.createCommunicationsException(this.connection,
1639 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
1640 new IOException(Messages.getString("MysqlIO.43")), getExceptionInterceptor());
1641 }
1642
1643 remaining -= bytesRead;
1644 }
1645 }
1646
1647 if (remaining > 0) {
1648 skipFully(this.mysqlInput, remaining);
1649 }
1650
1651 return new ByteArrayRow(rowData, getExceptionInterceptor());
1652 } catch (IOException ioEx) {
1653 throw SQLError.createCommunicationsException(this.connection,
1654 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
1655 }
1656 }
1657
1658 /**
1659 * Log-off of the MySQL server and close the socket.
1660 *
1661 * @throws SQLException DOCUMENT ME!
1662 */
1663 final void quit() throws SQLException {
1664 Buffer packet = new Buffer(6);
1665 this.packetSequence = -1;
1666 packet.writeByte((byte) MysqlDefs.QUIT);
1667 send(packet, packet.getPosition());
1668 forceClose();
1669 }
1670
1671 /**
1672 * Returns the packet used for sending data (used by PreparedStatement)
1673 * Guarded by external synchronization on a mutex.
1674 *
1675 * @return A packet to send data with
1676 */
1677 Buffer getSharedSendPacket() {
1678 if (this.sharedSendPacket == null) {
1679 this.sharedSendPacket = new Buffer(INITIAL_PACKET_SIZE);
1680 }
1681
1682 return this.sharedSendPacket;
1683 }
1684
1685 void closeStreamer(RowData streamer) throws SQLException {
1686 if (this.streamingData == null) {
1687 throw SQLError.createSQLException(Messages.getString("MysqlIO.17") //$NON-NLS-1$
1688 +streamer + Messages.getString("MysqlIO.18"), getExceptionInterceptor()); //$NON-NLS-1$
1689 }
1690
1691 if (streamer != this.streamingData) {
1692 throw SQLError.createSQLException(Messages.getString("MysqlIO.19") //$NON-NLS-1$
1693 +streamer + Messages.getString("MysqlIO.20") //$NON-NLS-1$
1694 +Messages.getString("MysqlIO.21") //$NON-NLS-1$
1695 +Messages.getString("MysqlIO.22"), getExceptionInterceptor()); //$NON-NLS-1$
1696 }
1697
1698 this.streamingData = null;
1699 }
1700
1701 boolean tackOnMoreStreamingResults(ResultSetImpl addingTo) throws SQLException {
1702 if ((this.serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0) {
1703
1704 boolean moreRowSetsExist = true;
1705 ResultSetImpl currentResultSet = addingTo;
1706 boolean firstTime = true;
1707
1708 while (moreRowSetsExist) {
1709 if (!firstTime && currentResultSet.reallyResult()) {
1710 break;
1711 }
1712
1713 firstTime = false;
1714
1715 Buffer fieldPacket = checkErrorPacket();
1716 fieldPacket.setPosition(0);
1717
1718 java.sql.Statement owningStatement = addingTo.getStatement();
1719
1720 int maxRows = owningStatement.getMaxRows();
1721
1722 // fixme for catalog, isBinary
1723
1724 ResultSetImpl newResultSet = readResultsForQueryOrUpdate(
1725 (StatementImpl)owningStatement,
1726 maxRows, owningStatement.getResultSetType(),
1727 owningStatement.getResultSetConcurrency(),
1728 true, owningStatement.getConnection().getCatalog(), fieldPacket,
1729 addingTo.isBinaryEncoded,
1730 -1L, null);
1731
1732 currentResultSet.setNextResultSet(newResultSet);
1733
1734 currentResultSet = newResultSet;
1735
1736 moreRowSetsExist = (this.serverStatus & MysqlIO.SERVER_MORE_RESULTS_EXISTS) != 0;
1737
1738 if (!currentResultSet.reallyResult() && !moreRowSetsExist) {
1739 // special case, we can stop "streaming"
1740 return false;
1741 }
1742 }
1743
1744 return true;
1745 }
1746
1747 return false;
1748 }
1749
1750 ResultSetImpl readAllResults(StatementImpl callingStatement, int maxRows,
1751 int resultSetType, int resultSetConcurrency, boolean streamResults,
1752 String catalog, Buffer resultPacket, boolean isBinaryEncoded,
1753 long preSentColumnCount, Field[] metadataFromCache)
1754 throws SQLException {
1755 resultPacket.setPosition(resultPacket.getPosition() - 1);
1756
1757 ResultSetImpl topLevelResultSet = readResultsForQueryOrUpdate(callingStatement,
1758 maxRows, resultSetType, resultSetConcurrency, streamResults,
1759 catalog, resultPacket, isBinaryEncoded, preSentColumnCount,
1760 metadataFromCache);
1761
1762 ResultSetImpl currentResultSet = topLevelResultSet;
1763
1764 boolean checkForMoreResults = ((this.clientParam &
1765 CLIENT_MULTI_RESULTS) != 0);
1766
1767 boolean serverHasMoreResults = (this.serverStatus &
1768 SERVER_MORE_RESULTS_EXISTS) != 0;
1769
1770 //
1771 // TODO: We need to support streaming of multiple result sets
1772 //
1773 if (serverHasMoreResults && streamResults) {
1774 //clearInputStream();
1775 //
1776 //throw SQLError.createSQLException(Messages.getString("MysqlIO.23"), //$NON-NLS-1$
1777 //SQLError.SQL_STATE_DRIVER_NOT_CAPABLE);
1778 if (topLevelResultSet.getUpdateCount() != -1) {
1779 tackOnMoreStreamingResults(topLevelResultSet);
1780 }
1781
1782 reclaimLargeReusablePacket();
1783
1784 return topLevelResultSet;
1785 }
1786
1787 boolean moreRowSetsExist = checkForMoreResults & serverHasMoreResults;
1788
1789 while (moreRowSetsExist) {
1790 Buffer fieldPacket = checkErrorPacket();
1791 fieldPacket.setPosition(0);
1792
1793 ResultSetImpl newResultSet = readResultsForQueryOrUpdate(callingStatement,
1794 maxRows, resultSetType, resultSetConcurrency,
1795 streamResults, catalog, fieldPacket, isBinaryEncoded,
1796 preSentColumnCount, metadataFromCache);
1797
1798 currentResultSet.setNextResultSet(newResultSet);
1799
1800 currentResultSet = newResultSet;
1801
1802 moreRowSetsExist = (this.serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0;
1803 }
1804
1805 if (!streamResults) {
1806 clearInputStream();
1807 }
1808
1809 reclaimLargeReusablePacket();
1810
1811 return topLevelResultSet;
1812 }
1813
1814 /**
1815 * Sets the buffer size to max-buf
1816 */
1817 void resetMaxBuf() {
1818 this.maxAllowedPacket = this.connection.getMaxAllowedPacket();
1819 }
1820
1821 /**
1822 * Send a command to the MySQL server If data is to be sent with command,
1823 * it should be put in extraData.
1824 *
1825 * Raw packets can be sent by setting queryPacket to something other
1826 * than null.
1827 *
1828 * @param command the MySQL protocol 'command' from MysqlDefs
1829 * @param extraData any 'string' data for the command
1830 * @param queryPacket a packet pre-loaded with data for the protocol (i.e.
1831 * from a client-side prepared statement).
1832 * @param skipCheck do not call checkErrorPacket() if true
1833 * @param extraDataCharEncoding the character encoding of the extraData
1834 * parameter.
1835 *
1836 * @return the response packet from the server
1837 *
1838 * @throws SQLException if an I/O error or SQL error occurs
1839 */
1840
1841 final Buffer sendCommand(int command, String extraData, Buffer queryPacket,
1842 boolean skipCheck, String extraDataCharEncoding, int timeoutMillis)
1843 throws SQLException {
1844 this.commandCount++;
1845
1846 //
1847 // We cache these locally, per-command, as the checks
1848 // for them are in very 'hot' sections of the I/O code
1849 // and we save 10-15% in overall performance by doing this...
1850 //
1851 this.enablePacketDebug = this.connection.getEnablePacketDebug();
1852 this.readPacketSequence = 0;
1853
1854 int oldTimeout = 0;
1855
1856 if (timeoutMillis != 0) {
1857 try {
1858 oldTimeout = this.mysqlConnection.getSoTimeout();
1859 this.mysqlConnection.setSoTimeout(timeoutMillis);
1860 } catch (SocketException e) {
1861 throw SQLError.createCommunicationsException(this.connection, lastPacketSentTimeMs,
1862 lastPacketReceivedTimeMs, e, getExceptionInterceptor());
1863 }
1864 }
1865
1866 try {
1867
1868 checkForOutstandingStreamingData();
1869
1870 // Clear serverStatus...this value is guarded by an
1871 // external mutex, as you can only ever be processing
1872 // one command at a time
1873 this.oldServerStatus = this.serverStatus;
1874 this.serverStatus = 0;
1875 this.hadWarnings = false;
1876 this.warningCount = 0;
1877
1878 this.queryNoIndexUsed = false;
1879 this.queryBadIndexUsed = false;
1880 this.serverQueryWasSlow = false;
1881
1882 //
1883 // Compressed input stream needs cleared at beginning
1884 // of each command execution...
1885 //
1886 if (this.useCompression) {
1887 int bytesLeft = this.mysqlInput.available();
1888
1889 if (bytesLeft > 0) {
1890 this.mysqlInput.skip(bytesLeft);
1891 }
1892 }
1893
1894 try {
1895 clearInputStream();
1896
1897 //
1898 // PreparedStatements construct their own packets,
1899 // for efficiency's sake.
1900 //
1901 // If this is a generic query, we need to re-use
1902 // the sending packet.
1903 //
1904 if (queryPacket == null) {
1905 int packLength = HEADER_LENGTH + COMP_HEADER_LENGTH + 1 +
1906 ((extraData != null) ? extraData.length() : 0) + 2;
1907
1908 if (this.sendPacket == null) {
1909 this.sendPacket = new Buffer(packLength);
1910 }
1911
1912 this.packetSequence = -1;
1913 this.readPacketSequence = 0;
1914 this.checkPacketSequence = true;
1915 this.sendPacket.clear();
1916
1917 this.sendPacket.writeByte((byte) command);
1918
1919 if ((command == MysqlDefs.INIT_DB) ||
1920 (command == MysqlDefs.CREATE_DB) ||
1921 (command == MysqlDefs.DROP_DB) ||
1922 (command == MysqlDefs.QUERY) ||
1923 (command == MysqlDefs.COM_PREPARE)) {
1924 if (extraDataCharEncoding == null) {
1925 this.sendPacket.writeStringNoNull(extraData);
1926 } else {
1927 this.sendPacket.writeStringNoNull(extraData,
1928 extraDataCharEncoding,
1929 this.connection.getServerCharacterEncoding(),
1930 this.connection.parserKnowsUnicode(), this.connection);
1931 }
1932 } else if (command == MysqlDefs.PROCESS_KILL) {
1933 long id = Long.parseLong(extraData);
1934 this.sendPacket.writeLong(id);
1935 }
1936
1937 send(this.sendPacket, this.sendPacket.getPosition());
1938 } else {
1939 this.packetSequence = -1;
1940 send(queryPacket, queryPacket.getPosition()); // packet passed by PreparedStatement
1941 }
1942 } catch (SQLException sqlEx) {
1943 // don't wrap SQLExceptions
1944 throw sqlEx;
1945 } catch (Exception ex) {
1946 throw SQLError.createCommunicationsException(this.connection,
1947 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ex, getExceptionInterceptor());
1948 }
1949
1950 Buffer returnPacket = null;
1951
1952 if (!skipCheck) {
1953 if ((command == MysqlDefs.COM_EXECUTE) ||
1954 (command == MysqlDefs.COM_RESET_STMT)) {
1955 this.readPacketSequence = 0;
1956 this.packetSequenceReset = true;
1957 }
1958
1959 returnPacket = checkErrorPacket(command);
1960 }
1961
1962 return returnPacket;
1963 } catch (IOException ioEx) {
1964 throw SQLError.createCommunicationsException(this.connection,
1965 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
1966 } finally {
1967 if (timeoutMillis != 0) {
1968 try {
1969 this.mysqlConnection.setSoTimeout(oldTimeout);
1970 } catch (SocketException e) {
1971 throw SQLError.createCommunicationsException(this.connection, lastPacketSentTimeMs,
1972 lastPacketReceivedTimeMs, e, getExceptionInterceptor());
1973 }
1974 }
1975 }
1976 }
1977
1978 private int statementExecutionDepth = 0;
1979 private boolean useAutoSlowLog;
1980
1981 protected boolean shouldIntercept() {
1982 return this.statementInterceptors != null;
1983 }
1984
1985 /**
1986 * Send a query stored in a packet directly to the server.
1987 *
1988 * @param callingStatement DOCUMENT ME!
1989 * @param resultSetConcurrency DOCUMENT ME!
1990 * @param characterEncoding DOCUMENT ME!
1991 * @param queryPacket DOCUMENT ME!
1992 * @param maxRows DOCUMENT ME!
1993 * @param conn DOCUMENT ME!
1994 * @param resultSetType DOCUMENT ME!
1995 * @param resultSetConcurrency DOCUMENT ME!
1996 * @param streamResults DOCUMENT ME!
1997 * @param catalog DOCUMENT ME!
1998 * @param unpackFieldInfo should we read MYSQL_FIELD info (if available)?
1999 *
2000 * @return DOCUMENT ME!
2001 *
2002 * @throws Exception DOCUMENT ME!
2003 */
2004 final ResultSetInternalMethods sqlQueryDirect(StatementImpl callingStatement, String query,
2005 String characterEncoding, Buffer queryPacket, int maxRows,
2006 int resultSetType, int resultSetConcurrency,
2007 boolean streamResults, String catalog, Field[] cachedMetadata)
2008 throws Exception {
2009 this.statementExecutionDepth++;
2010
2011 try {
2012 if (this.statementInterceptors != null) {
2013 ResultSetInternalMethods interceptedResults =
2014 invokeStatementInterceptorsPre(query, callingStatement, false);
2015
2016 if (interceptedResults != null) {
2017 return interceptedResults;
2018 }
2019 }
2020
2021 long queryStartTime = 0;
2022 long queryEndTime = 0;
2023
2024 if (query != null) {
2025
2026 // We don't know exactly how many bytes we're going to get
2027 // from the query. Since we're dealing with Unicode, the
2028 // max is 2, so pad it (2 * query) + space for headers
2029 int packLength = HEADER_LENGTH + 1 + (query.length() * 2) + 2;
2030
2031 String statementComment = this.connection.getStatementComment();
2032
2033 byte[] commentAsBytes = null;
2034
2035 if (statementComment != null) {
2036 commentAsBytes = StringUtils.getBytes(statementComment, null,
2037 characterEncoding, this.connection
2038 .getServerCharacterEncoding(),
2039 this.connection.parserKnowsUnicode(), getExceptionInterceptor());
2040
2041 packLength += commentAsBytes.length;
2042 packLength += 6; // for /*[space] [space]*/
2043 }
2044
2045 if (this.sendPacket == null) {
2046 this.sendPacket = new Buffer(packLength);
2047 } else {
2048 this.sendPacket.clear();
2049 }
2050
2051 this.sendPacket.writeByte((byte) MysqlDefs.QUERY);
2052
2053 if (commentAsBytes != null) {
2054 this.sendPacket.writeBytesNoNull(Constants.SLASH_STAR_SPACE_AS_BYTES);
2055 this.sendPacket.writeBytesNoNull(commentAsBytes);
2056 this.sendPacket.writeBytesNoNull(Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES);
2057 }
2058
2059 if (characterEncoding != null) {
2060 if (this.platformDbCharsetMatches) {
2061 this.sendPacket.writeStringNoNull(query, characterEncoding,
2062 this.connection.getServerCharacterEncoding(),
2063 this.connection.parserKnowsUnicode(),
2064 this.connection);
2065 } else {
2066 if (StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) { //$NON-NLS-1$
2067 this.sendPacket.writeBytesNoNull(query.getBytes());
2068 } else {
2069 this.sendPacket.writeStringNoNull(query,
2070 characterEncoding,
2071 this.connection.getServerCharacterEncoding(),
2072 this.connection.parserKnowsUnicode(),
2073 this.connection);
2074 }
2075 }
2076 } else {
2077 this.sendPacket.writeStringNoNull(query);
2078 }
2079
2080 queryPacket = this.sendPacket;
2081 }
2082
2083 byte[] queryBuf = null;
2084 int oldPacketPosition = 0;
2085
2086 if (needToGrabQueryFromPacket) {
2087 queryBuf = queryPacket.getByteBuffer();
2088
2089 // save the packet position
2090 oldPacketPosition = queryPacket.getPosition();
2091
2092 queryStartTime = getCurrentTimeNanosOrMillis();
2093 }
2094
2095 if (this.autoGenerateTestcaseScript) {
2096 String testcaseQuery = null;
2097
2098 if (query != null) {
2099 testcaseQuery = query;
2100 } else {
2101 testcaseQuery = new String(queryBuf, 5,
2102 (oldPacketPosition - 5));
2103 }
2104
2105 StringBuffer debugBuf = new StringBuffer(testcaseQuery.length() + 32);
2106 this.connection.generateConnectionCommentBlock(debugBuf);
2107 debugBuf.append(testcaseQuery);
2108 debugBuf.append(';');
2109 this.connection.dumpTestcaseQuery(debugBuf.toString());
2110 }
2111
2112 // Send query command and sql query string
2113 Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket,
2114 false, null, 0);
2115
2116 long fetchBeginTime = 0;
2117 long fetchEndTime = 0;
2118
2119 String profileQueryToLog = null;
2120
2121 boolean queryWasSlow = false;
2122
2123 if (this.profileSql || this.logSlowQueries) {
2124 queryEndTime = System.currentTimeMillis();
2125
2126 boolean shouldExtractQuery = false;
2127
2128 if (this.profileSql) {
2129 shouldExtractQuery = true;
2130 } else if (this.logSlowQueries) {
2131 long queryTime = queryEndTime - queryStartTime;
2132
2133 boolean logSlow = false;
2134
2135 if (this.useAutoSlowLog) {
2136 logSlow = queryTime > this.connection.getSlowQueryThresholdMillis();
2137 } else {
2138 logSlow = this.connection.isAbonormallyLongQuery(queryTime);
2139
2140 this.connection.reportQueryTime(queryTime);
2141 }
2142
2143 if (logSlow) {
2144 shouldExtractQuery = true;
2145 queryWasSlow = true;
2146 }
2147 }
2148
2149 if (shouldExtractQuery) {
2150 // Extract the actual query from the network packet
2151 boolean truncated = false;
2152
2153 int extractPosition = oldPacketPosition;
2154
2155 if (oldPacketPosition > this.connection.getMaxQuerySizeToLog()) {
2156 extractPosition = this.connection.getMaxQuerySizeToLog() + 5;
2157 truncated = true;
2158 }
2159
2160 profileQueryToLog = new String(queryBuf, 5,
2161 (extractPosition - 5));
2162
2163 if (truncated) {
2164 profileQueryToLog += Messages.getString("MysqlIO.25"); //$NON-NLS-1$
2165 }
2166 }
2167
2168 fetchBeginTime = queryEndTime;
2169 }
2170
2171 ResultSetInternalMethods rs = readAllResults(callingStatement, maxRows, resultSetType,
2172 resultSetConcurrency, streamResults, catalog, resultPacket,
2173 false, -1L, cachedMetadata);
2174
2175 if (queryWasSlow && !this.serverQueryWasSlow /* don't log slow queries twice */) {
2176 StringBuffer mesgBuf = new StringBuffer(48 +
2177 profileQueryToLog.length());
2178
2179 mesgBuf.append(Messages.getString("MysqlIO.SlowQuery",
2180 new Object[] {new Long(this.slowQueryThreshold),
2181 queryTimingUnits,
2182 new Long(queryEndTime - queryStartTime)}));
2183 mesgBuf.append(profileQueryToLog);
2184
2185 ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);
2186
2187 eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_SLOW_QUERY,
2188 "", catalog, this.connection.getId(), //$NON-NLS-1$
2189 (callingStatement != null) ? callingStatement.getId() : 999,
2190 ((ResultSetImpl)rs).resultId, System.currentTimeMillis(),
2191 (int) (queryEndTime - queryStartTime), queryTimingUnits, null,
2192 new Throwable(), mesgBuf.toString()));
2193
2194 if (this.connection.getExplainSlowQueries()) {
2195 if (oldPacketPosition < MAX_QUERY_SIZE_TO_EXPLAIN) {
2196 explainSlowQuery(queryPacket.getBytes(5,
2197 (oldPacketPosition - 5)), profileQueryToLog);
2198 } else {
2199 this.connection.getLog().logWarn(Messages.getString(
2200 "MysqlIO.28") //$NON-NLS-1$
2201 +MAX_QUERY_SIZE_TO_EXPLAIN +
2202 Messages.getString("MysqlIO.29")); //$NON-NLS-1$
2203 }
2204 }
2205 }
2206
2207 if (this.logSlowQueries) {
2208
2209 ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);
2210
2211 if (this.queryBadIndexUsed) {
2212 eventSink.consumeEvent(new ProfilerEvent(
2213 ProfilerEvent.TYPE_SLOW_QUERY, "", catalog, //$NON-NLS-1$
2214 this.connection.getId(),
2215 (callingStatement != null) ? callingStatement.getId()
2216 : 999, ((ResultSetImpl)rs).resultId,
2217 System.currentTimeMillis(),
2218 (queryEndTime - queryStartTime), this.queryTimingUnits,
2219 null,
2220 new Throwable(),
2221 Messages.getString("MysqlIO.33") //$NON-NLS-1$
2222 +profileQueryToLog));
2223 }
2224
2225 if (this.queryNoIndexUsed) {
2226 eventSink.consumeEvent(new ProfilerEvent(
2227 ProfilerEvent.TYPE_SLOW_QUERY, "", catalog, //$NON-NLS-1$
2228 this.connection.getId(),
2229 (callingStatement != null) ? callingStatement.getId()
2230 : 999, ((ResultSetImpl)rs).resultId,
2231 System.currentTimeMillis(),
2232 (queryEndTime - queryStartTime), this.queryTimingUnits,
2233 null,
2234 new Throwable(),
2235 Messages.getString("MysqlIO.35") //$NON-NLS-1$
2236 +profileQueryToLog));
2237 }
2238
2239 if (this.serverQueryWasSlow) {
2240 eventSink.consumeEvent(new ProfilerEvent(
2241 ProfilerEvent.TYPE_SLOW_QUERY, "", catalog, //$NON-NLS-1$
2242 this.connection.getId(),
2243 (callingStatement != null) ? callingStatement.getId()
2244 : 999, ((ResultSetImpl)rs).resultId,
2245 System.currentTimeMillis(),
2246 (queryEndTime - queryStartTime), this.queryTimingUnits,
2247 null,
2248 new Throwable(),
2249 Messages.getString("MysqlIO.ServerSlowQuery") //$NON-NLS-1$
2250 +profileQueryToLog));
2251 }
2252 }
2253
2254 if (this.profileSql) {
2255 fetchEndTime = getCurrentTimeNanosOrMillis();
2256
2257 ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(this.connection);
2258
2259 eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_QUERY,
2260 "", catalog, this.connection.getId(), //$NON-NLS-1$
2261 (callingStatement != null) ? callingStatement.getId() : 999,
2262 ((ResultSetImpl)rs).resultId, System.currentTimeMillis(),
2263 (queryEndTime - queryStartTime), this.queryTimingUnits,
2264 null,
2265 new Throwable(), profileQueryToLog));
2266
2267 eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_FETCH,
2268 "", catalog, this.connection.getId(), //$NON-NLS-1$
2269 (callingStatement != null) ? callingStatement.getId() : 999,
2270 ((ResultSetImpl)rs).resultId, System.currentTimeMillis(),
2271 (fetchEndTime - fetchBeginTime), this.queryTimingUnits,
2272 null,
2273 new Throwable(), null));
2274 }
2275
2276 if (this.hadWarnings) {
2277 scanForAndThrowDataTruncation();
2278 }
2279
2280 if (this.statementInterceptors != null) {
2281 ResultSetInternalMethods interceptedResults = invokeStatementInterceptorsPost(
2282 query, callingStatement, rs, false, null);
2283
2284 if (interceptedResults != null) {
2285 rs = interceptedResults;
2286 }
2287 }
2288
2289 return rs;
2290 } catch (SQLException sqlEx) {
2291 if (this.statementInterceptors != null) {
2292 invokeStatementInterceptorsPost(
2293 query, callingStatement, null, false, sqlEx); // we don't do anything with the result set in this case
2294 }
2295
2296 if (callingStatement != null) {
2297 synchronized (callingStatement.cancelTimeoutMutex) {
2298 if (callingStatement.wasCancelled) {
2299 SQLException cause = null;
2300
2301 if (callingStatement.wasCancelledByTimeout) {
2302 cause = new MySQLTimeoutException();
2303 } else {
2304 cause = new MySQLStatementCancelledException();
2305 }
2306
2307 callingStatement.resetCancelledState();
2308
2309 throw cause;
2310 }
2311 }
2312 }
2313
2314 throw sqlEx;
2315 } finally {
2316 this.statementExecutionDepth--;
2317 }
2318 }
2319
2320 ResultSetInternalMethods invokeStatementInterceptorsPre(String sql,
2321 Statement interceptedStatement, boolean forceExecute) throws SQLException {
2322 ResultSetInternalMethods previousResultSet = null;
2323
2324 Iterator interceptors = this.statementInterceptors.iterator();
2325
2326 while (interceptors.hasNext()) {
2327 StatementInterceptorV2 interceptor = ((StatementInterceptorV2) interceptors
2328 .next());
2329
2330 boolean executeTopLevelOnly = interceptor.executeTopLevelOnly();
2331 boolean shouldExecute = (executeTopLevelOnly && (this.statementExecutionDepth == 1 || forceExecute))
2332 || (!executeTopLevelOnly);
2333
2334 if (shouldExecute) {
2335 String sqlToInterceptor = sql;
2336
2337 //if (interceptedStatement instanceof PreparedStatement) {
2338 // sqlToInterceptor = ((PreparedStatement) interceptedStatement)
2339 // .asSql();
2340 //}
2341
2342 ResultSetInternalMethods interceptedResultSet = interceptor
2343 .preProcess(sqlToInterceptor, interceptedStatement,
2344 this.connection);
2345
2346 if (interceptedResultSet != null) {
2347 previousResultSet = interceptedResultSet;
2348 }
2349 }
2350 }
2351
2352 return previousResultSet;
2353 }
2354
2355 ResultSetInternalMethods invokeStatementInterceptorsPost(
2356 String sql, Statement interceptedStatement,
2357 ResultSetInternalMethods originalResultSet, boolean forceExecute, SQLException statementException) throws SQLException {
2358 Iterator interceptors = this.statementInterceptors.iterator();
2359
2360 while (interceptors.hasNext()) {
2361 StatementInterceptorV2 interceptor = ((StatementInterceptorV2) interceptors
2362 .next());
2363
2364 boolean executeTopLevelOnly = interceptor.executeTopLevelOnly();
2365 boolean shouldExecute = (executeTopLevelOnly && (this.statementExecutionDepth == 1 || forceExecute))
2366 || (!executeTopLevelOnly);
2367
2368 if (shouldExecute) {
2369 String sqlToInterceptor = sql;
2370
2371 ResultSetInternalMethods interceptedResultSet = interceptor
2372 .postProcess(sqlToInterceptor, interceptedStatement,
2373 originalResultSet, this.connection, this.warningCount,
2374 this.queryNoIndexUsed, this.queryBadIndexUsed, statementException);
2375
2376 if (interceptedResultSet != null) {
2377 originalResultSet = interceptedResultSet;
2378 }
2379 }
2380 }
2381
2382 return originalResultSet;
2383 }
2384
2385 private void calculateSlowQueryThreshold() {
2386 this.slowQueryThreshold = this.connection.getSlowQueryThresholdMillis();
2387
2388 if (this.connection.getUseNanosForElapsedTime()) {
2389 long nanosThreshold = this.connection.getSlowQueryThresholdNanos();
2390
2391 if (nanosThreshold != 0) {
2392 this.slowQueryThreshold = nanosThreshold;
2393 } else {
2394 this.slowQueryThreshold *= 1000000; // 1 million millis in a nano
2395 }
2396 }
2397 }
2398
2399 protected long getCurrentTimeNanosOrMillis() {
2400 if (this.useNanosForElapsedTime) {
2401 return Util.getCurrentTimeNanosOrMillis();
2402 }
2403
2404 return System.currentTimeMillis();
2405 }
2406
2407 /**
2408 * Returns the host this IO is connected to
2409 *
2410 * @return DOCUMENT ME!
2411 */
2412 String getHost() {
2413 return this.host;
2414 }
2415
2416 /**
2417 * Is the version of the MySQL server we are connected to the given
2418 * version?
2419 *
2420 * @param major the major version
2421 * @param minor the minor version
2422 * @param subminor the subminor version
2423 *
2424 * @return true if the version of the MySQL server we are connected is the
2425 * given version
2426 */
2427 boolean isVersion(int major, int minor, int subminor) {
2428 return ((major == getServerMajorVersion()) &&
2429 (minor == getServerMinorVersion()) &&
2430 (subminor == getServerSubMinorVersion()));
2431 }
2432
2433 /**
2434 * Does the version of the MySQL server we are connected to meet the given
2435 * minimums?
2436 *
2437 * @param major DOCUMENT ME!
2438 * @param minor DOCUMENT ME!
2439 * @param subminor DOCUMENT ME!
2440 *
2441 * @return DOCUMENT ME!
2442 */
2443 boolean versionMeetsMinimum(int major, int minor, int subminor) {
2444 if (getServerMajorVersion() >= major) {
2445 if (getServerMajorVersion() == major) {
2446 if (getServerMinorVersion() >= minor) {
2447 if (getServerMinorVersion() == minor) {
2448 return (getServerSubMinorVersion() >= subminor);
2449 }
2450
2451 // newer than major.minor
2452 return true;
2453 }
2454
2455 // older than major.minor
2456 return false;
2457 }
2458
2459 // newer than major
2460 return true;
2461 }
2462
2463 return false;
2464 }
2465
2466 /**
2467 * Returns the hex dump of the given packet, truncated to
2468 * MAX_PACKET_DUMP_LENGTH if packetLength exceeds that value.
2469 *
2470 * @param packetToDump the packet to dump in hex
2471 * @param packetLength the number of bytes to dump
2472 *
2473 * @return the hex dump of the given packet
2474 */
2475 private final static String getPacketDumpToLog(Buffer packetToDump,
2476 int packetLength) {
2477 if (packetLength < MAX_PACKET_DUMP_LENGTH) {
2478 return packetToDump.dump(packetLength);
2479 }
2480
2481 StringBuffer packetDumpBuf = new StringBuffer(MAX_PACKET_DUMP_LENGTH * 4);
2482 packetDumpBuf.append(packetToDump.dump(MAX_PACKET_DUMP_LENGTH));
2483 packetDumpBuf.append(Messages.getString("MysqlIO.36")); //$NON-NLS-1$
2484 packetDumpBuf.append(MAX_PACKET_DUMP_LENGTH);
2485 packetDumpBuf.append(Messages.getString("MysqlIO.37")); //$NON-NLS-1$
2486
2487 return packetDumpBuf.toString();
2488 }
2489
2490 private final int readFully(InputStream in, byte[] b, int off, int len)
2491 throws IOException {
2492 if (len < 0) {
2493 throw new IndexOutOfBoundsException();
2494 }
2495
2496 int n = 0;
2497
2498 while (n < len) {
2499 int count = in.read(b, off + n, len - n);
2500
2501 if (count < 0) {
2502 throw new EOFException(Messages.getString("MysqlIO.EOF",
2503 new Object[] {new Integer(len), new Integer(n)}));
2504 }
2505
2506 n += count;
2507 }
2508
2509 return n;
2510 }
2511
2512 private final long skipFully(InputStream in, long len) throws IOException {
2513 if (len < 0) {
2514 throw new IOException("Negative skip length not allowed");
2515 }
2516
2517 long n = 0;
2518
2519 while (n < len) {
2520 long count = in.skip(len - n);
2521
2522 if (count < 0) {
2523 throw new EOFException(Messages.getString("MysqlIO.EOF",
2524 new Object[] {new Long(len), new Long(n)}));
2525 }
2526
2527 n += count;
2528 }
2529
2530 return n;
2531 }
2532
2533 /**
2534 * Reads one result set off of the wire, if the result is actually an
2535 * update count, creates an update-count only result set.
2536 *
2537 * @param callingStatement DOCUMENT ME!
2538 * @param maxRows the maximum rows to return in the result set.
2539 * @param resultSetType scrollability
2540 * @param resultSetConcurrency updatability
2541 * @param streamResults should the driver leave the results on the wire,
2542 * and read them only when needed?
2543 * @param catalog the catalog in use
2544 * @param resultPacket the first packet of information in the result set
2545 * @param isBinaryEncoded is this result set from a prepared statement?
2546 * @param preSentColumnCount do we already know the number of columns?
2547 * @param unpackFieldInfo should we unpack the field information?
2548 *
2549 * @return a result set that either represents the rows, or an update count
2550 *
2551 * @throws SQLException if an error occurs while reading the rows
2552 */
2553 protected final ResultSetImpl readResultsForQueryOrUpdate(
2554 StatementImpl callingStatement, int maxRows, int resultSetType,
2555 int resultSetConcurrency, boolean streamResults, String catalog,
2556 Buffer resultPacket, boolean isBinaryEncoded, long preSentColumnCount,
2557 Field[] metadataFromCache) throws SQLException {
2558 long columnCount = resultPacket.readFieldLength();
2559
2560 if (columnCount == 0) {
2561 return buildResultSetWithUpdates(callingStatement, resultPacket);
2562 } else if (columnCount == Buffer.NULL_LENGTH) {
2563 String charEncoding = null;
2564
2565 if (this.connection.getUseUnicode()) {
2566 charEncoding = this.connection.getEncoding();
2567 }
2568
2569 String fileName = null;
2570
2571 if (this.platformDbCharsetMatches) {
2572 fileName = ((charEncoding != null)
2573 ? resultPacket.readString(charEncoding, getExceptionInterceptor())
2574 : resultPacket.readString());
2575 } else {
2576 fileName = resultPacket.readString();
2577 }
2578
2579 return sendFileToServer(callingStatement, fileName);
2580 } else {
2581 com.mysql.jdbc.ResultSetImpl results = getResultSet(callingStatement,
2582 columnCount, maxRows, resultSetType, resultSetConcurrency,
2583 streamResults, catalog, isBinaryEncoded,
2584 metadataFromCache);
2585
2586 return results;
2587 }
2588 }
2589
2590 private int alignPacketSize(int a, int l) {
2591 return ((((a) + (l)) - 1) & ~((l) - 1));
2592 }
2593
2594 private com.mysql.jdbc.ResultSetImpl buildResultSetWithRows(
2595 StatementImpl callingStatement, String catalog,
2596 com.mysql.jdbc.Field[] fields, RowData rows, int resultSetType,
2597 int resultSetConcurrency, boolean isBinaryEncoded)
2598 throws SQLException {
2599 ResultSetImpl rs = null;
2600
2601 switch (resultSetConcurrency) {
2602 case java.sql.ResultSet.CONCUR_READ_ONLY:
2603 rs = com.mysql.jdbc.ResultSetImpl.getInstance(catalog, fields, rows,
2604 this.connection, callingStatement, false);
2605
2606 if (isBinaryEncoded) {
2607 rs.setBinaryEncoded();
2608 }
2609
2610 break;
2611
2612 case java.sql.ResultSet.CONCUR_UPDATABLE:
2613 rs = com.mysql.jdbc.ResultSetImpl.getInstance(catalog, fields, rows,
2614 this.connection, callingStatement, true);
2615
2616 break;
2617
2618 default:
2619 return com.mysql.jdbc.ResultSetImpl.getInstance(catalog, fields, rows,
2620 this.connection, callingStatement, false);
2621 }
2622
2623 rs.setResultSetType(resultSetType);
2624 rs.setResultSetConcurrency(resultSetConcurrency);
2625
2626 return rs;
2627 }
2628
2629 private com.mysql.jdbc.ResultSetImpl buildResultSetWithUpdates(
2630 StatementImpl callingStatement, Buffer resultPacket)
2631 throws SQLException {
2632 long updateCount = -1;
2633 long updateID = -1;
2634 String info = null;
2635
2636 try {
2637 if (this.useNewUpdateCounts) {
2638 updateCount = resultPacket.newReadLength();
2639 updateID = resultPacket.newReadLength();
2640 } else {
2641 updateCount = resultPacket.readLength();
2642 updateID = resultPacket.readLength();
2643 }
2644
2645 if (this.use41Extensions) {
2646 // oldStatus set in sendCommand()
2647 this.serverStatus = resultPacket.readInt();
2648
2649 checkTransactionState(oldServerStatus);
2650
2651 this.warningCount = resultPacket.readInt();
2652
2653 if (this.warningCount > 0) {
2654 this.hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
2655 }
2656
2657 resultPacket.readByte(); // advance pointer
2658
2659 setServerSlowQueryFlags();
2660 }
2661
2662 if (this.connection.isReadInfoMsgEnabled()) {
2663 info = resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
2664 }
2665 } catch (Exception ex) {
2666 SQLException sqlEx = SQLError.createSQLException(SQLError.get(
2667 SQLError.SQL_STATE_GENERAL_ERROR), SQLError.SQL_STATE_GENERAL_ERROR, -1, getExceptionInterceptor());
2668 sqlEx.initCause(ex);
2669
2670 throw sqlEx;
2671 }
2672
2673 ResultSetInternalMethods updateRs = com.mysql.jdbc.ResultSetImpl.getInstance(updateCount,
2674 updateID, this.connection, callingStatement);
2675
2676 if (info != null) {
2677 ((com.mysql.jdbc.ResultSetImpl)updateRs).setServerInfo(info);
2678 }
2679
2680 return (com.mysql.jdbc.ResultSetImpl)updateRs;
2681 }
2682
2683 private void setServerSlowQueryFlags() {
2684 if (this.profileSql) {
2685 this.queryNoIndexUsed = (this.serverStatus &
2686 SERVER_QUERY_NO_GOOD_INDEX_USED) != 0;
2687 this.queryBadIndexUsed = (this.serverStatus &
2688 SERVER_QUERY_NO_INDEX_USED) != 0;
2689 this.serverQueryWasSlow = (this.serverStatus &
2690 SERVER_QUERY_WAS_SLOW) != 0;
2691 }
2692 }
2693
2694 private void checkForOutstandingStreamingData() throws SQLException {
2695 if (this.streamingData != null) {
2696 boolean shouldClobber = this.connection.getClobberStreamingResults();
2697
2698 if (!shouldClobber) {
2699 throw SQLError.createSQLException(Messages.getString("MysqlIO.39") //$NON-NLS-1$
2700 +this.streamingData +
2701 Messages.getString("MysqlIO.40") //$NON-NLS-1$
2702 +Messages.getString("MysqlIO.41") //$NON-NLS-1$
2703 +Messages.getString("MysqlIO.42"), getExceptionInterceptor()); //$NON-NLS-1$
2704 }
2705
2706 // Close the result set
2707 this.streamingData.getOwner().realClose(false);
2708
2709 // clear any pending data....
2710 clearInputStream();
2711 }
2712 }
2713
2714 private Buffer compressPacket(Buffer packet, int offset, int packetLen,
2715 int headerLength) throws SQLException {
2716 packet.writeLongInt(packetLen - headerLength);
2717 packet.writeByte((byte) 0); // wrapped packet has 0 packet seq.
2718
2719 int lengthToWrite = 0;
2720 int compressedLength = 0;
2721 byte[] bytesToCompress = packet.getByteBuffer();
2722 byte[] compressedBytes = null;
2723 int offsetWrite = 0;
2724
2725 if (packetLen < MIN_COMPRESS_LEN) {
2726 lengthToWrite = packetLen;
2727 compressedBytes = packet.getByteBuffer();
2728 compressedLength = 0;
2729 offsetWrite = offset;
2730 } else {
2731 compressedBytes = new byte[bytesToCompress.length * 2];
2732
2733 this.deflater.reset();
2734 this.deflater.setInput(bytesToCompress, offset, packetLen);
2735 this.deflater.finish();
2736
2737 int compLen = this.deflater.deflate(compressedBytes);
2738
2739 if (compLen > packetLen) {
2740 lengthToWrite = packetLen;
2741 compressedBytes = packet.getByteBuffer();
2742 compressedLength = 0;
2743 offsetWrite = offset;
2744 } else {
2745 lengthToWrite = compLen;
2746 headerLength += COMP_HEADER_LENGTH;
2747 compressedLength = packetLen;
2748 }
2749 }
2750
2751 Buffer compressedPacket = new Buffer(packetLen + headerLength);
2752
2753 compressedPacket.setPosition(0);
2754 compressedPacket.writeLongInt(lengthToWrite);
2755 compressedPacket.writeByte(this.packetSequence);
2756 compressedPacket.writeLongInt(compressedLength);
2757 compressedPacket.writeBytesNoNull(compressedBytes, offsetWrite,
2758 lengthToWrite);
2759
2760 return compressedPacket;
2761 }
2762
2763 private final void readServerStatusForResultSets(Buffer rowPacket)
2764 throws SQLException {
2765 if (this.use41Extensions) {
2766 rowPacket.readByte(); // skips the 'last packet' flag
2767
2768 this.warningCount = rowPacket.readInt();
2769
2770 if (this.warningCount > 0) {
2771 this.hadWarnings = true; // this is a 'latch', it's reset by sendCommand()
2772 }
2773
2774 this.oldServerStatus = this.serverStatus;
2775 this.serverStatus = rowPacket.readInt();
2776 checkTransactionState(oldServerStatus);
2777
2778 setServerSlowQueryFlags();
2779 }
2780 }
2781
2782 private SocketFactory createSocketFactory() throws SQLException {
2783 try {
2784 if (this.socketFactoryClassName == null) {
2785 throw SQLError.createSQLException(Messages.getString("MysqlIO.75"), //$NON-NLS-1$
2786 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
2787 }
2788
2789 return (SocketFactory) (Class.forName(this.socketFactoryClassName)
2790 .newInstance());
2791 } catch (Exception ex) {
2792 SQLException sqlEx = SQLError.createSQLException(Messages.getString("MysqlIO.76") //$NON-NLS-1$
2793 +this.socketFactoryClassName +
2794 Messages.getString("MysqlIO.77"),
2795 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
2796
2797 sqlEx.initCause(ex);
2798
2799 throw sqlEx;
2800 }
2801 }
2802
2803 private void enqueuePacketForDebugging(boolean isPacketBeingSent,
2804 boolean isPacketReused, int sendLength, byte[] header, Buffer packet)
2805 throws SQLException {
2806 if ((this.packetDebugRingBuffer.size() + 1) > this.connection.getPacketDebugBufferSize()) {
2807 this.packetDebugRingBuffer.removeFirst();
2808 }
2809
2810 StringBuffer packetDump = null;
2811
2812 if (!isPacketBeingSent) {
2813 int bytesToDump = Math.min(MAX_PACKET_DUMP_LENGTH,
2814 packet.getBufLength());
2815
2816 Buffer packetToDump = new Buffer(4 + bytesToDump);
2817
2818 packetToDump.setPosition(0);
2819 packetToDump.writeBytesNoNull(header);
2820 packetToDump.writeBytesNoNull(packet.getBytes(0, bytesToDump));
2821
2822 String packetPayload = packetToDump.dump(bytesToDump);
2823
2824 packetDump = new StringBuffer(96 + packetPayload.length());
2825
2826 packetDump.append("Server ");
2827
2828 if (isPacketReused) {
2829 packetDump.append("(re-used)");
2830 } else {
2831 packetDump.append("(new)");
2832 }
2833
2834 packetDump.append(" ");
2835 packetDump.append(packet.toSuperString());
2836 packetDump.append(" --------------------> Client\n");
2837 packetDump.append("\nPacket payload:\n\n");
2838 packetDump.append(packetPayload);
2839
2840 if (bytesToDump == MAX_PACKET_DUMP_LENGTH) {
2841 packetDump.append("\nNote: Packet of " + packet.getBufLength() +
2842 " bytes truncated to " + MAX_PACKET_DUMP_LENGTH +
2843 " bytes.\n");
2844 }
2845 } else {
2846 int bytesToDump = Math.min(MAX_PACKET_DUMP_LENGTH, sendLength);
2847
2848 String packetPayload = packet.dump(bytesToDump);
2849
2850 packetDump = new StringBuffer(64 + 4 + packetPayload.length());
2851
2852 packetDump.append("Client ");
2853 packetDump.append(packet.toSuperString());
2854 packetDump.append("--------------------> Server\n");
2855 packetDump.append("\nPacket payload:\n\n");
2856 packetDump.append(packetPayload);
2857
2858 if (bytesToDump == MAX_PACKET_DUMP_LENGTH) {
2859 packetDump.append("\nNote: Packet of " + sendLength +
2860 " bytes truncated to " + MAX_PACKET_DUMP_LENGTH +
2861 " bytes.\n");
2862 }
2863 }
2864
2865 this.packetDebugRingBuffer.addLast(packetDump);
2866 }
2867
2868 private RowData readSingleRowSet(long columnCount, int maxRows,
2869 int resultSetConcurrency, boolean isBinaryEncoded, Field[] fields)
2870 throws SQLException {
2871 RowData rowData;
2872 ArrayList rows = new ArrayList();
2873
2874 boolean useBufferRowExplicit = useBufferRowExplicit(fields);
2875
2876 // Now read the data
2877 ResultSetRow row = nextRow(fields, (int) columnCount, isBinaryEncoded,
2878 resultSetConcurrency, false, useBufferRowExplicit, false, null);
2879
2880 int rowCount = 0;
2881
2882 if (row != null) {
2883 rows.add(row);
2884 rowCount = 1;
2885 }
2886
2887 while (row != null) {
2888 row = nextRow(fields, (int) columnCount, isBinaryEncoded,
2889 resultSetConcurrency, false, useBufferRowExplicit, false, null);
2890
2891 if (row != null) {
2892 if ((maxRows == -1) || (rowCount < maxRows)) {
2893 rows.add(row);
2894 rowCount++;
2895 }
2896 }
2897 }
2898
2899 rowData = new RowDataStatic(rows);
2900
2901 return rowData;
2902 }
2903
2904 public static boolean useBufferRowExplicit(Field[] fields) {
2905 if (fields == null) {
2906 return false;
2907 }
2908
2909 for (int i = 0; i < fields.length; i++) {
2910 switch (fields[i].getSQLType()) {
2911 case Types.BLOB:
2912 case Types.CLOB:
2913 case Types.LONGVARBINARY:
2914 case Types.LONGVARCHAR:
2915 return true;
2916 }
2917 }
2918
2919 return false;
2920 }
2921
2922 /**
2923 * Don't hold on to overly-large packets
2924 */
2925 private void reclaimLargeReusablePacket() {
2926 if ((this.reusablePacket != null) &&
2927 (this.reusablePacket.getCapacity() > 1048576)) {
2928 this.reusablePacket = new Buffer(INITIAL_PACKET_SIZE);
2929 }
2930 }
2931
2932 /**
2933 * Re-use a packet to read from the MySQL server
2934 *
2935 * @param reuse DOCUMENT ME!
2936 *
2937 * @return DOCUMENT ME!
2938 *
2939 * @throws SQLException DOCUMENT ME!
2940 * @throws SQLException DOCUMENT ME!
2941 */
2942 private final Buffer reuseAndReadPacket(Buffer reuse) throws SQLException {
2943 return reuseAndReadPacket(reuse, -1);
2944 }
2945
2946 private final Buffer reuseAndReadPacket(Buffer reuse, int existingPacketLength)
2947 throws SQLException {
2948
2949 try {
2950 reuse.setWasMultiPacket(false);
2951 int packetLength = 0;
2952
2953 if (existingPacketLength == -1) {
2954 int lengthRead = readFully(this.mysqlInput,
2955 this.packetHeaderBuf, 0, 4);
2956
2957 if (lengthRead < 4) {
2958 forceClose();
2959 throw new IOException(Messages.getString("MysqlIO.43")); //$NON-NLS-1$
2960 }
2961
2962 packetLength = (this.packetHeaderBuf[0] & 0xff) +
2963 ((this.packetHeaderBuf[1] & 0xff) << 8) +
2964 ((this.packetHeaderBuf[2] & 0xff) << 16);
2965 } else {
2966 packetLength = existingPacketLength;
2967 }
2968
2969 if (this.traceProtocol) {
2970 StringBuffer traceMessageBuf = new StringBuffer();
2971
2972 traceMessageBuf.append(Messages.getString("MysqlIO.44")); //$NON-NLS-1$
2973 traceMessageBuf.append(packetLength);
2974 traceMessageBuf.append(Messages.getString("MysqlIO.45")); //$NON-NLS-1$
2975 traceMessageBuf.append(StringUtils.dumpAsHex(
2976 this.packetHeaderBuf, 4));
2977
2978 this.connection.getLog().logTrace(traceMessageBuf.toString());
2979 }
2980
2981 byte multiPacketSeq = this.packetHeaderBuf[3];
2982
2983 if (!this.packetSequenceReset) {
2984 if (this.enablePacketDebug && this.checkPacketSequence) {
2985 checkPacketSequencing(multiPacketSeq);
2986 }
2987 } else {
2988 this.packetSequenceReset = false;
2989 }
2990
2991 this.readPacketSequence = multiPacketSeq;
2992
2993 // Set the Buffer to it's original state
2994 reuse.setPosition(0);
2995
2996 // Do we need to re-alloc the byte buffer?
2997 //
2998 // Note: We actually check the length of the buffer,
2999 // rather than getBufLength(), because getBufLength() is not
3000 // necesarily the actual length of the byte array
3001 // used as the buffer
3002 if (reuse.getByteBuffer().length <= packetLength) {
3003 reuse.setByteBuffer(new byte[packetLength + 1]);
3004 }
3005
3006 // Set the new length
3007 reuse.setBufLength(packetLength);
3008
3009 // Read the data from the server
3010 int numBytesRead = readFully(this.mysqlInput,
3011 reuse.getByteBuffer(), 0, packetLength);
3012
3013 if (numBytesRead != packetLength) {
3014 throw new IOException("Short read, expected " +
3015 packetLength + " bytes, only read " + numBytesRead);
3016 }
3017
3018 if (this.traceProtocol) {
3019 StringBuffer traceMessageBuf = new StringBuffer();
3020
3021 traceMessageBuf.append(Messages.getString("MysqlIO.46")); //$NON-NLS-1$
3022 traceMessageBuf.append(getPacketDumpToLog(reuse,
3023 packetLength));
3024
3025 this.connection.getLog().logTrace(traceMessageBuf.toString());
3026 }
3027
3028 if (this.enablePacketDebug) {
3029 enqueuePacketForDebugging(false, true, 0,
3030 this.packetHeaderBuf, reuse);
3031 }
3032
3033 boolean isMultiPacket = false;
3034
3035 if (packetLength == this.maxThreeBytes) {
3036 reuse.setPosition(this.maxThreeBytes);
3037
3038 int packetEndPoint = packetLength;
3039
3040 // it's multi-packet
3041 isMultiPacket = true;
3042
3043 packetLength = readRemainingMultiPackets(reuse, multiPacketSeq,
3044 packetEndPoint);
3045 }
3046
3047 if (!isMultiPacket) {
3048 reuse.getByteBuffer()[packetLength] = 0; // Null-termination
3049 }
3050
3051 if (this.connection.getMaintainTimeStats()) {
3052 this.lastPacketReceivedTimeMs = System.currentTimeMillis();
3053 }
3054
3055 return reuse;
3056 } catch (IOException ioEx) {
3057 throw SQLError.createCommunicationsException(this.connection,
3058 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
3059 } catch (OutOfMemoryError oom) {
3060 try {
3061 // _Try_ this
3062 clearInputStream();
3063 } finally {
3064 try {
3065 this.connection.realClose(false, false, true, oom);
3066 } finally {
3067 throw oom;
3068 }
3069 }
3070 }
3071
3072 }
3073
3074 private int readRemainingMultiPackets(Buffer reuse, byte multiPacketSeq,
3075 int packetEndPoint) throws IOException, SQLException {
3076 int lengthRead;
3077 int packetLength;
3078 lengthRead = readFully(this.mysqlInput,
3079 this.packetHeaderBuf, 0, 4);
3080
3081 if (lengthRead < 4) {
3082 forceClose();
3083 throw new IOException(Messages.getString("MysqlIO.47")); //$NON-NLS-1$
3084 }
3085
3086 packetLength = (this.packetHeaderBuf[0] & 0xff) +
3087 ((this.packetHeaderBuf[1] & 0xff) << 8) +
3088 ((this.packetHeaderBuf[2] & 0xff) << 16);
3089
3090 Buffer multiPacket = new Buffer(packetLength);
3091 boolean firstMultiPkt = true;
3092
3093 while (true) {
3094 if (!firstMultiPkt) {
3095 lengthRead = readFully(this.mysqlInput,
3096 this.packetHeaderBuf, 0, 4);
3097
3098 if (lengthRead < 4) {
3099 forceClose();
3100 throw new IOException(Messages.getString(
3101 "MysqlIO.48")); //$NON-NLS-1$
3102 }
3103
3104 packetLength = (this.packetHeaderBuf[0] & 0xff) +
3105 ((this.packetHeaderBuf[1] & 0xff) << 8) +
3106 ((this.packetHeaderBuf[2] & 0xff) << 16);
3107 } else {
3108 firstMultiPkt = false;
3109 }
3110
3111 if (!this.useNewLargePackets && (packetLength == 1)) {
3112 clearInputStream();
3113
3114 break;
3115 } else if (packetLength < this.maxThreeBytes) {
3116 byte newPacketSeq = this.packetHeaderBuf[3];
3117
3118 if (newPacketSeq != (multiPacketSeq + 1)) {
3119 throw new IOException(Messages.getString(
3120 "MysqlIO.49")); //$NON-NLS-1$
3121 }
3122
3123 multiPacketSeq = newPacketSeq;
3124
3125 // Set the Buffer to it's original state
3126 multiPacket.setPosition(0);
3127
3128 // Set the new length
3129 multiPacket.setBufLength(packetLength);
3130
3131 // Read the data from the server
3132 byte[] byteBuf = multiPacket.getByteBuffer();
3133 int lengthToWrite = packetLength;
3134
3135 int bytesRead = readFully(this.mysqlInput, byteBuf,
3136 0, packetLength);
3137
3138 if (bytesRead != lengthToWrite) {
3139 throw SQLError.createCommunicationsException(this.connection,
3140 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
3141 SQLError.createSQLException(Messages.getString(
3142 "MysqlIO.50") //$NON-NLS-1$
3143 +lengthToWrite +
3144 Messages.getString("MysqlIO.51") +
3145 bytesRead //$NON-NLS-1$
3146 +".", getExceptionInterceptor()), getExceptionInterceptor()); //$NON-NLS-1$
3147 }
3148
3149 reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
3150
3151 packetEndPoint += lengthToWrite;
3152
3153 break; // end of multipacket sequence
3154 }
3155
3156 byte newPacketSeq = this.packetHeaderBuf[3];
3157
3158 if (newPacketSeq != (multiPacketSeq + 1)) {
3159 throw new IOException(Messages.getString(
3160 "MysqlIO.53")); //$NON-NLS-1$
3161 }
3162
3163 multiPacketSeq = newPacketSeq;
3164
3165 // Set the Buffer to it's original state
3166 multiPacket.setPosition(0);
3167
3168 // Set the new length
3169 multiPacket.setBufLength(packetLength);
3170
3171 // Read the data from the server
3172 byte[] byteBuf = multiPacket.getByteBuffer();
3173 int lengthToWrite = packetLength;
3174
3175 int bytesRead = readFully(this.mysqlInput, byteBuf, 0,
3176 packetLength);
3177
3178 if (bytesRead != lengthToWrite) {
3179 throw SQLError.createCommunicationsException(this.connection,
3180 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
3181 SQLError.createSQLException(Messages.getString(
3182 "MysqlIO.54") //$NON-NLS-1$
3183 +lengthToWrite +
3184 Messages.getString("MysqlIO.55") //$NON-NLS-1$
3185 +bytesRead + ".", getExceptionInterceptor()), getExceptionInterceptor()); //$NON-NLS-1$
3186 }
3187
3188 reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
3189
3190 packetEndPoint += lengthToWrite;
3191 }
3192
3193 reuse.setPosition(0);
3194 reuse.setWasMultiPacket(true);
3195 return packetLength;
3196 }
3197
3198 /**
3199 * @param multiPacketSeq
3200 * @throws CommunicationsException
3201 */
3202 private void checkPacketSequencing(byte multiPacketSeq)
3203 throws SQLException {
3204 if ((multiPacketSeq == -128) && (this.readPacketSequence != 127)) {
3205 throw SQLError.createCommunicationsException(this.connection,
3206 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
3207 new IOException("Packets out of order, expected packet # -128, but received packet # " +
3208 multiPacketSeq), getExceptionInterceptor());
3209 }
3210
3211 if ((this.readPacketSequence == -1) && (multiPacketSeq != 0)) {
3212 throw SQLError.createCommunicationsException(this.connection,
3213 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
3214 new IOException("Packets out of order, expected packet # -1, but received packet # " +
3215 multiPacketSeq), getExceptionInterceptor());
3216 }
3217
3218 if ((multiPacketSeq != -128) && (this.readPacketSequence != -1) &&
3219 (multiPacketSeq != (this.readPacketSequence + 1))) {
3220 throw SQLError.createCommunicationsException(this.connection,
3221 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs,
3222 new IOException("Packets out of order, expected packet # " +
3223 (this.readPacketSequence + 1) + ", but received packet # " +
3224 multiPacketSeq), getExceptionInterceptor());
3225 }
3226 }
3227
3228 void enableMultiQueries() throws SQLException {
3229 Buffer buf = getSharedSendPacket();
3230
3231 buf.clear();
3232 buf.writeByte((byte)MysqlDefs.COM_SET_OPTION);
3233 buf.writeInt(0);
3234 sendCommand(MysqlDefs.COM_SET_OPTION, null, buf, false, null, 0);
3235 }
3236
3237 void disableMultiQueries() throws SQLException {
3238 Buffer buf = getSharedSendPacket();
3239
3240 buf.clear();
3241 buf.writeByte((byte)MysqlDefs.COM_SET_OPTION);
3242 buf.writeInt(1);
3243 sendCommand(MysqlDefs.COM_SET_OPTION, null, buf, false, null, 0);
3244 }
3245
3246 private final void send(Buffer packet, int packetLen)
3247 throws SQLException {
3248 try {
3249 if (this.maxAllowedPacket > 0 && packetLen > this.maxAllowedPacket) {
3250 throw new PacketTooBigException(packetLen, this.maxAllowedPacket);
3251 }
3252
3253 if ((this.serverMajorVersion >= 4) &&
3254 (packetLen >= this.maxThreeBytes)) {
3255 sendSplitPackets(packet);
3256 } else {
3257 this.packetSequence++;
3258
3259 Buffer packetToSend = packet;
3260
3261 packetToSend.setPosition(0);
3262
3263 if (this.useCompression) {
3264 int originalPacketLen = packetLen;
3265
3266 packetToSend = compressPacket(packet, 0, packetLen,
3267 HEADER_LENGTH);
3268 packetLen = packetToSend.getPosition();
3269
3270 if (this.traceProtocol) {
3271 StringBuffer traceMessageBuf = new StringBuffer();
3272
3273 traceMessageBuf.append(Messages.getString("MysqlIO.57")); //$NON-NLS-1$
3274 traceMessageBuf.append(getPacketDumpToLog(
3275 packetToSend, packetLen));
3276 traceMessageBuf.append(Messages.getString("MysqlIO.58")); //$NON-NLS-1$
3277 traceMessageBuf.append(getPacketDumpToLog(packet,
3278 originalPacketLen));
3279
3280 this.connection.getLog().logTrace(traceMessageBuf.toString());
3281 }
3282 } else {
3283 packetToSend.writeLongInt(packetLen - HEADER_LENGTH);
3284 packetToSend.writeByte(this.packetSequence);
3285
3286 if (this.traceProtocol) {
3287 StringBuffer traceMessageBuf = new StringBuffer();
3288
3289 traceMessageBuf.append(Messages.getString("MysqlIO.59")); //$NON-NLS-1$
3290 traceMessageBuf.append(packetToSend.dump(packetLen));
3291
3292 this.connection.getLog().logTrace(traceMessageBuf.toString());
3293 }
3294 }
3295
3296
3297 this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
3298 packetLen);
3299 this.mysqlOutput.flush();
3300 }
3301
3302 if (this.enablePacketDebug) {
3303 enqueuePacketForDebugging(true, false, packetLen + 5,
3304 this.packetHeaderBuf, packet);
3305 }
3306
3307 //
3308 // Don't hold on to large packets
3309 //
3310 if (packet == this.sharedSendPacket) {
3311 reclaimLargeSharedSendPacket();
3312 }
3313
3314 if (this.connection.getMaintainTimeStats()) {
3315 this.lastPacketSentTimeMs = System.currentTimeMillis();
3316 }
3317 } catch (IOException ioEx) {
3318 throw SQLError.createCommunicationsException(this.connection,
3319 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
3320 }
3321 }
3322
3323 /**
3324 * Reads and sends a file to the server for LOAD DATA LOCAL INFILE
3325 *
3326 * @param callingStatement DOCUMENT ME!
3327 * @param fileName the file name to send.
3328 *
3329 * @return DOCUMENT ME!
3330 *
3331 * @throws SQLException DOCUMENT ME!
3332 */
3333 private final ResultSetImpl sendFileToServer(StatementImpl callingStatement,
3334 String fileName) throws SQLException {
3335
3336 Buffer filePacket = (this.loadFileBufRef == null) ? null
3337 : (Buffer) (this.loadFileBufRef.get());
3338
3339 int bigPacketLength = Math.min(this.connection.getMaxAllowedPacket() -
3340 (HEADER_LENGTH * 3),
3341 alignPacketSize(this.connection.getMaxAllowedPacket() - 16, 4096) -
3342 (HEADER_LENGTH * 3));
3343
3344 int oneMeg = 1024 * 1024;
3345
3346 int smallerPacketSizeAligned = Math.min(oneMeg - (HEADER_LENGTH * 3),
3347 alignPacketSize(oneMeg - 16, 4096) - (HEADER_LENGTH * 3));
3348
3349 int packetLength = Math.min(smallerPacketSizeAligned, bigPacketLength);
3350
3351 if (filePacket == null) {
3352 try {
3353 filePacket = new Buffer((packetLength + HEADER_LENGTH));
3354 this.loadFileBufRef = new SoftReference(filePacket);
3355 } catch (OutOfMemoryError oom) {
3356 throw SQLError.createSQLException("Could not allocate packet of " + packetLength
3357 + " bytes required for LOAD DATA LOCAL INFILE operation."
3358 + " Try increasing max heap allocation for JVM or decreasing server variable "
3359 + "'max_allowed_packet'", SQLError.SQL_STATE_MEMORY_ALLOCATION_FAILURE, getExceptionInterceptor());
3360
3361 }
3362 }
3363
3364 filePacket.clear();
3365 send(filePacket, 0);
3366
3367 byte[] fileBuf = new byte[packetLength];
3368
3369 BufferedInputStream fileIn = null;
3370
3371 try {
3372 if (!this.connection.getAllowLoadLocalInfile()) {
3373 throw SQLError.createSQLException(
3374 Messages.getString("MysqlIO.LoadDataLocalNotAllowed"),
3375 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3376 }
3377
3378 InputStream hookedStream = null;
3379
3380 if (callingStatement != null) {
3381 hookedStream = callingStatement.getLocalInfileInputStream();
3382 }
3383
3384 if (hookedStream != null) {
3385 fileIn = new BufferedInputStream(hookedStream);
3386 } else if (!this.connection.getAllowUrlInLocalInfile()) {
3387 fileIn = new BufferedInputStream(new FileInputStream(fileName));
3388 } else {
3389 // First look for ':'
3390 if (fileName.indexOf(':') != -1) {
3391 try {
3392 URL urlFromFileName = new URL(fileName);
3393 fileIn = new BufferedInputStream(urlFromFileName.openStream());
3394 } catch (MalformedURLException badUrlEx) {
3395 // we fall back to trying this as a file input stream
3396 fileIn = new BufferedInputStream(new FileInputStream(
3397 fileName));
3398 }
3399 } else {
3400 fileIn = new BufferedInputStream(new FileInputStream(
3401 fileName));
3402 }
3403 }
3404
3405 int bytesRead = 0;
3406
3407 while ((bytesRead = fileIn.read(fileBuf)) != -1) {
3408 filePacket.clear();
3409 filePacket.writeBytesNoNull(fileBuf, 0, bytesRead);
3410 send(filePacket, filePacket.getPosition());
3411 }
3412 } catch (IOException ioEx) {
3413 StringBuffer messageBuf = new StringBuffer(Messages.getString(
3414 "MysqlIO.60")); //$NON-NLS-1$
3415
3416 if (!this.connection.getParanoid()) {
3417 messageBuf.append("'"); //$NON-NLS-1$
3418
3419 if (fileName != null) {
3420 messageBuf.append(fileName);
3421 }
3422
3423 messageBuf.append("'"); //$NON-NLS-1$
3424 }
3425
3426 messageBuf.append(Messages.getString("MysqlIO.63")); //$NON-NLS-1$
3427
3428 if (!this.connection.getParanoid()) {
3429 messageBuf.append(Messages.getString("MysqlIO.64")); //$NON-NLS-1$
3430 messageBuf.append(Util.stackTraceToString(ioEx));
3431 }
3432
3433 throw SQLError.createSQLException(messageBuf.toString(),
3434 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3435 } finally {
3436 if (fileIn != null) {
3437 try {
3438 fileIn.close();
3439 } catch (Exception ex) {
3440 SQLException sqlEx = SQLError.createSQLException(Messages.getString("MysqlIO.65"), //$NON-NLS-1$
3441 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3442 sqlEx.initCause(ex);
3443
3444 throw sqlEx;
3445 }
3446
3447 fileIn = null;
3448 } else {
3449 // file open failed, but server needs one packet
3450 filePacket.clear();
3451 send(filePacket, filePacket.getPosition());
3452 checkErrorPacket(); // to clear response off of queue
3453 }
3454 }
3455
3456 // send empty packet to mark EOF
3457 filePacket.clear();
3458 send(filePacket, filePacket.getPosition());
3459
3460 Buffer resultPacket = checkErrorPacket();
3461
3462 return buildResultSetWithUpdates(callingStatement, resultPacket);
3463 }
3464
3465 /**
3466 * Checks for errors in the reply packet, and if none, returns the reply
3467 * packet, ready for reading
3468 *
3469 * @param command the command being issued (if used)
3470 *
3471 * @return DOCUMENT ME!
3472 *
3473 * @throws SQLException if an error packet was received
3474 * @throws CommunicationsException DOCUMENT ME!
3475 */
3476 private Buffer checkErrorPacket(int command) throws SQLException {
3477 int statusCode = 0;
3478 Buffer resultPacket = null;
3479 this.serverStatus = 0;
3480
3481 try {
3482 // Check return value, if we get a java.io.EOFException,
3483 // the server has gone away. We'll pass it on up the
3484 // exception chain and let someone higher up decide
3485 // what to do (barf, reconnect, etc).
3486 resultPacket = reuseAndReadPacket(this.reusablePacket);
3487 } catch (SQLException sqlEx) {
3488 // Don't wrap SQL Exceptions
3489 throw sqlEx;
3490 } catch (Exception fallThru) {
3491 throw SQLError.createCommunicationsException(this.connection,
3492 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, fallThru, getExceptionInterceptor());
3493 }
3494
3495 checkErrorPacket(resultPacket);
3496
3497 return resultPacket;
3498 }
3499
3500 private void checkErrorPacket(Buffer resultPacket) throws SQLException {
3501
3502 int statusCode = resultPacket.readByte();
3503
3504 // Error handling
3505 if (statusCode == (byte) 0xff) {
3506 String serverErrorMessage;
3507 int errno = 2000;
3508
3509 if (this.protocolVersion > 9) {
3510 errno = resultPacket.readInt();
3511
3512 String xOpen = null;
3513
3514 serverErrorMessage =
3515 resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
3516
3517 if (serverErrorMessage.charAt(0) == '#') { //$NON-NLS-1$
3518
3519 // we have an SQLState
3520 if (serverErrorMessage.length() > 6) {
3521 xOpen = serverErrorMessage.substring(1, 6);
3522 serverErrorMessage = serverErrorMessage.substring(6);
3523
3524 if (xOpen.equals("HY000")) { //$NON-NLS-1$
3525 xOpen = SQLError.mysqlToSqlState(errno,
3526 this.connection.getUseSqlStateCodes());
3527 }
3528 } else {
3529 xOpen = SQLError.mysqlToSqlState(errno,
3530 this.connection.getUseSqlStateCodes());
3531 }
3532 } else {
3533 xOpen = SQLError.mysqlToSqlState(errno,
3534 this.connection.getUseSqlStateCodes());
3535 }
3536
3537 clearInputStream();
3538
3539 StringBuffer errorBuf = new StringBuffer();
3540
3541 String xOpenErrorMessage = SQLError.get(xOpen);
3542
3543 if (!this.connection.getUseOnlyServerErrorMessages()) {
3544 if (xOpenErrorMessage != null) {
3545 errorBuf.append(xOpenErrorMessage);
3546 errorBuf.append(Messages.getString("MysqlIO.68")); //$NON-NLS-1$
3547 }
3548 }
3549
3550 errorBuf.append(serverErrorMessage);
3551
3552 if (!this.connection.getUseOnlyServerErrorMessages()) {
3553 if (xOpenErrorMessage != null) {
3554 errorBuf.append("\""); //$NON-NLS-1$
3555 }
3556 }
3557
3558 appendInnodbStatusInformation(xOpen, errorBuf);
3559
3560 if (xOpen != null && xOpen.startsWith("22")) {
3561 throw new MysqlDataTruncation(errorBuf.toString(), 0, true, false, 0, 0, errno);
3562 } else {
3563 throw SQLError.createSQLException(errorBuf.toString(), xOpen, errno, false, getExceptionInterceptor(), this.connection);
3564 }
3565 }
3566
3567 serverErrorMessage = resultPacket.readString(
3568 this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
3569 clearInputStream();
3570
3571 if (serverErrorMessage.indexOf(Messages.getString("MysqlIO.70")) != -1) { //$NON-NLS-1$
3572 throw SQLError.createSQLException(SQLError.get(
3573 SQLError.SQL_STATE_COLUMN_NOT_FOUND) +
3574 ", " //$NON-NLS-1$
3575 +serverErrorMessage, SQLError.SQL_STATE_COLUMN_NOT_FOUND,
3576 -1, false, getExceptionInterceptor(), this.connection);
3577 }
3578
3579 StringBuffer errorBuf = new StringBuffer(Messages.getString(
3580 "MysqlIO.72")); //$NON-NLS-1$
3581 errorBuf.append(serverErrorMessage);
3582 errorBuf.append("\""); //$NON-NLS-1$
3583
3584 throw SQLError.createSQLException(SQLError.get(
3585 SQLError.SQL_STATE_GENERAL_ERROR) + ", " //$NON-NLS-1$
3586 +errorBuf.toString(), SQLError.SQL_STATE_GENERAL_ERROR, -1, false, getExceptionInterceptor(), this.connection);
3587 }
3588 }
3589
3590 private void appendInnodbStatusInformation(String xOpen,
3591 StringBuffer errorBuf) throws SQLException {
3592 if (this.connection.getIncludeInnodbStatusInDeadlockExceptions()
3593 && xOpen != null
3594 && (xOpen.startsWith("40") || xOpen.startsWith("41"))
3595 && this.streamingData == null) {
3596 ResultSet rs = null;
3597
3598 try {
3599 rs = sqlQueryDirect(null, "SHOW ENGINE INNODB STATUS",
3600 this.connection.getEncoding(), null, -1,
3601 ResultSet.TYPE_FORWARD_ONLY,
3602 ResultSet.CONCUR_READ_ONLY, false, this.connection
3603 .getCatalog(), null);
3604
3605 if (rs.next()) {
3606 errorBuf.append("\n\n");
3607 errorBuf.append(rs.getString("Status"));
3608 } else {
3609 errorBuf.append("\n\n");
3610 errorBuf.append(Messages
3611 .getString("MysqlIO.NoInnoDBStatusFound"));
3612 }
3613 } catch (Exception ex) {
3614 errorBuf.append("\n\n");
3615 errorBuf.append(Messages
3616 .getString("MysqlIO.InnoDBStatusFailed"));
3617 errorBuf.append("\n\n");
3618 errorBuf.append(Util.stackTraceToString(ex));
3619 } finally {
3620 if (rs != null) {
3621 rs.close();
3622 }
3623 }
3624 }
3625 }
3626
3627 /**
3628 * Sends a large packet to the server as a series of smaller packets
3629 *
3630 * @param packet DOCUMENT ME!
3631 *
3632 * @throws SQLException DOCUMENT ME!
3633 * @throws CommunicationsException DOCUMENT ME!
3634 */
3635 private final void sendSplitPackets(Buffer packet)
3636 throws SQLException {
3637 try {
3638 //
3639 // Big packets are handled by splitting them in packets of MAX_THREE_BYTES
3640 // length. The last packet is always a packet that is < MAX_THREE_BYTES.
3641 // (The last packet may even have a length of 0)
3642 //
3643 //
3644 // NB: Guarded by execSQL. If the driver changes architecture, this
3645 // will need to be synchronized in some other way
3646 //
3647 Buffer headerPacket = (this.splitBufRef == null) ? null
3648 : (Buffer) (this.splitBufRef.get());
3649
3650 //
3651 // Store this packet in a soft reference...It can be re-used if not GC'd (so clients
3652 // that use it frequently won't have to re-alloc the 16M buffer), but we don't
3653 // penalize infrequent users of large packets by keeping 16M allocated all of the time
3654 //
3655 if (headerPacket == null) {
3656 headerPacket = new Buffer((this.maxThreeBytes +
3657 HEADER_LENGTH));
3658 this.splitBufRef = new SoftReference(headerPacket);
3659 }
3660
3661 int len = packet.getPosition();
3662 int splitSize = this.maxThreeBytes;
3663 int originalPacketPos = HEADER_LENGTH;
3664 byte[] origPacketBytes = packet.getByteBuffer();
3665 byte[] headerPacketBytes = headerPacket.getByteBuffer();
3666
3667 while (len >= this.maxThreeBytes) {
3668 this.packetSequence++;
3669
3670 headerPacket.setPosition(0);
3671 headerPacket.writeLongInt(splitSize);
3672
3673 headerPacket.writeByte(this.packetSequence);
3674 System.arraycopy(origPacketBytes, originalPacketPos,
3675 headerPacketBytes, 4, splitSize);
3676
3677 int packetLen = splitSize + HEADER_LENGTH;
3678
3679 //
3680 // Swap a compressed packet in, if we're using
3681 // compression...
3682 //
3683 if (!this.useCompression) {
3684 this.mysqlOutput.write(headerPacketBytes, 0,
3685 splitSize + HEADER_LENGTH);
3686 this.mysqlOutput.flush();
3687 } else {
3688 Buffer packetToSend;
3689
3690 headerPacket.setPosition(0);
3691 packetToSend = compressPacket(headerPacket, HEADER_LENGTH,
3692 splitSize, HEADER_LENGTH);
3693 packetLen = packetToSend.getPosition();
3694
3695 this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
3696 packetLen);
3697 this.mysqlOutput.flush();
3698 }
3699
3700 originalPacketPos += splitSize;
3701 len -= splitSize;
3702 }
3703
3704 //
3705 // Write last packet
3706 //
3707 headerPacket.clear();
3708 headerPacket.setPosition(0);
3709 headerPacket.writeLongInt(len - HEADER_LENGTH);
3710 this.packetSequence++;
3711 headerPacket.writeByte(this.packetSequence);
3712
3713 if (len != 0) {
3714 System.arraycopy(origPacketBytes, originalPacketPos,
3715 headerPacketBytes, 4, len - HEADER_LENGTH);
3716 }
3717
3718 int packetLen = len - HEADER_LENGTH;
3719
3720 //
3721 // Swap a compressed packet in, if we're using
3722 // compression...
3723 //
3724 if (!this.useCompression) {
3725 this.mysqlOutput.write(headerPacket.getByteBuffer(), 0, len);
3726 this.mysqlOutput.flush();
3727 } else {
3728 Buffer packetToSend;
3729
3730 headerPacket.setPosition(0);
3731 packetToSend = compressPacket(headerPacket, HEADER_LENGTH,
3732 packetLen, HEADER_LENGTH);
3733 packetLen = packetToSend.getPosition();
3734
3735 this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
3736 packetLen);
3737 this.mysqlOutput.flush();
3738 }
3739 } catch (IOException ioEx) {
3740 throw SQLError.createCommunicationsException(this.connection,
3741 this.lastPacketSentTimeMs, this.lastPacketReceivedTimeMs, ioEx, getExceptionInterceptor());
3742 }
3743 }
3744
3745 private void reclaimLargeSharedSendPacket() {
3746 if ((this.sharedSendPacket != null) &&
3747 (this.sharedSendPacket.getCapacity() > 1048576)) {
3748 this.sharedSendPacket = new Buffer(INITIAL_PACKET_SIZE);
3749 }
3750 }
3751
3752 boolean hadWarnings() {
3753 return this.hadWarnings;
3754 }
3755
3756 void scanForAndThrowDataTruncation() throws SQLException {
3757 if ((this.streamingData == null) && versionMeetsMinimum(4, 1, 0) &&
3758 this.connection.getJdbcCompliantTruncation() && this.warningCount > 0) {
3759 SQLError.convertShowWarningsToSQLWarnings(this.connection,
3760 this.warningCount, true);
3761 }
3762 }
3763
3764 /**
3765 * Secure authentication for 4.1 and newer servers.
3766 *
3767 * @param packet DOCUMENT ME!
3768 * @param packLength
3769 * @param user
3770 * @param password
3771 * @param database DOCUMENT ME!
3772 * @param writeClientParams
3773 *
3774 * @throws SQLException
3775 */
3776 private void secureAuth(Buffer packet, int packLength, String user,
3777 String password, String database, boolean writeClientParams)
3778 throws SQLException {
3779 // Passwords can be 16 chars long
3780 if (packet == null) {
3781 packet = new Buffer(packLength);
3782 }
3783
3784 if (writeClientParams) {
3785 if (this.use41Extensions) {
3786 if (versionMeetsMinimum(4, 1, 1)) {
3787 packet.writeLong(this.clientParam);
3788 packet.writeLong(this.maxThreeBytes);
3789
3790 // charset, JDBC will connect as 'latin1',
3791 // and use 'SET NAMES' to change to the desired
3792 // charset after the connection is established.
3793 packet.writeByte((byte) 8);
3794
3795 // Set of bytes reserved for future use.
3796 packet.writeBytesNoNull(new byte[23]);
3797 } else {
3798 packet.writeLong(this.clientParam);
3799 packet.writeLong(this.maxThreeBytes);
3800 }
3801 } else {
3802 packet.writeInt((int) this.clientParam);
3803 packet.writeLongInt(this.maxThreeBytes);
3804 }
3805 }
3806
3807 // User/Password data
3808 packet.writeString(user, CODE_PAGE_1252, this.connection);
3809
3810 if (password.length() != 0) {
3811 /* Prepare false scramble */
3812 packet.writeString(FALSE_SCRAMBLE, CODE_PAGE_1252, this.connection);
3813 } else {
3814 /* For empty password*/
3815 packet.writeString("", CODE_PAGE_1252, this.connection); //$NON-NLS-1$
3816 }
3817
3818 if (this.useConnectWithDb) {
3819 packet.writeString(database, CODE_PAGE_1252, this.connection);
3820 }
3821
3822 send(packet, packet.getPosition());
3823
3824 //
3825 // Don't continue stages if password is empty
3826 //
3827 if (password.length() > 0) {
3828 Buffer b = readPacket();
3829
3830 b.setPosition(0);
3831
3832 byte[] replyAsBytes = b.getByteBuffer();
3833
3834 if ((replyAsBytes.length == 25) && (replyAsBytes[0] != 0)) {
3835 // Old passwords will have '*' at the first byte of hash */
3836 if (replyAsBytes[0] != '*') {
3837 try {
3838 /* Build full password hash as it is required to decode scramble */
3839 byte[] buff = Security.passwordHashStage1(password);
3840
3841 /* Store copy as we'll need it later */
3842 byte[] passwordHash = new byte[buff.length];
3843 System.arraycopy(buff, 0, passwordHash, 0, buff.length);
3844
3845 /* Finally hash complete password using hash we got from server */
3846 passwordHash = Security.passwordHashStage2(passwordHash,
3847 replyAsBytes);
3848
3849 byte[] packetDataAfterSalt = new byte[replyAsBytes.length -
3850 5];
3851
3852 System.arraycopy(replyAsBytes, 4, packetDataAfterSalt,
3853 0, replyAsBytes.length - 5);
3854
3855 byte[] mysqlScrambleBuff = new byte[20];
3856
3857 /* Decypt and store scramble 4 = hash for stage2 */
3858 Security.passwordCrypt(packetDataAfterSalt,
3859 mysqlScrambleBuff, passwordHash, 20);
3860
3861 /* Encode scramble with password. Recycle buffer */
3862 Security.passwordCrypt(mysqlScrambleBuff, buff, buff, 20);
3863
3864 Buffer packet2 = new Buffer(25);
3865 packet2.writeBytesNoNull(buff);
3866
3867 this.packetSequence++;
3868
3869 send(packet2, 24);
3870 } catch (NoSuchAlgorithmException nse) {
3871 throw SQLError.createSQLException(Messages.getString("MysqlIO.91") //$NON-NLS-1$
3872 +Messages.getString("MysqlIO.92"), //$NON-NLS-1$
3873 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3874 }
3875 } else {
3876 try {
3877 /* Create password to decode scramble */
3878 byte[] passwordHash = Security.createKeyFromOldPassword(password);
3879
3880 /* Decypt and store scramble 4 = hash for stage2 */
3881 byte[] netReadPos4 = new byte[replyAsBytes.length - 5];
3882
3883 System.arraycopy(replyAsBytes, 4, netReadPos4, 0,
3884 replyAsBytes.length - 5);
3885
3886 byte[] mysqlScrambleBuff = new byte[20];
3887
3888 /* Decypt and store scramble 4 = hash for stage2 */
3889 Security.passwordCrypt(netReadPos4, mysqlScrambleBuff,
3890 passwordHash, 20);
3891
3892 /* Finally scramble decoded scramble with password */
3893 String scrambledPassword = Util.scramble(new String(
3894 mysqlScrambleBuff), password);
3895
3896 Buffer packet2 = new Buffer(packLength);
3897 packet2.writeString(scrambledPassword, CODE_PAGE_1252, this.connection);
3898 this.packetSequence++;
3899
3900 send(packet2, 24);
3901 } catch (NoSuchAlgorithmException nse) {
3902 throw SQLError.createSQLException(Messages.getString("MysqlIO.93") //$NON-NLS-1$
3903 +Messages.getString("MysqlIO.94"), //$NON-NLS-1$
3904 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3905 }
3906 }
3907 }
3908 }
3909 }
3910
3911 /**
3912 * Secure authentication for 4.1.1 and newer servers.
3913 *
3914 * @param packet DOCUMENT ME!
3915 * @param packLength
3916 * @param user
3917 * @param password
3918 * @param database DOCUMENT ME!
3919 * @param writeClientParams
3920 *
3921 * @throws SQLException
3922 */
3923 void secureAuth411(Buffer packet, int packLength, String user,
3924 String password, String database, boolean writeClientParams)
3925 throws SQLException {
3926 // SERVER: public_seed=create_random_string()
3927 // send(public_seed)
3928 //
3929 // CLIENT: recv(public_seed)
3930 // hash_stage1=sha1("password")
3931 // hash_stage2=sha1(hash_stage1)
3932 // reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
3933 //
3934 // // this three steps are done in scramble()
3935 //
3936 // send(reply)
3937 //
3938 //
3939 // SERVER: recv(reply)
3940 // hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
3941 // candidate_hash2=sha1(hash_stage1)
3942 // check(candidate_hash2==hash_stage2)
3943 // Passwords can be 16 chars long
3944 if (packet == null) {
3945 packet = new Buffer(packLength);
3946 }
3947
3948 if (writeClientParams) {
3949 if (this.use41Extensions) {
3950 if (versionMeetsMinimum(4, 1, 1)) {
3951 packet.writeLong(this.clientParam);
3952 packet.writeLong(this.maxThreeBytes);
3953
3954 // charset, JDBC will connect as 'utf8',
3955 // and use 'SET NAMES' to change to the desired
3956 // charset after the connection is established.
3957 packet.writeByte((byte) UTF8_CHARSET_INDEX);
3958
3959 // Set of bytes reserved for future use.
3960 packet.writeBytesNoNull(new byte[23]);
3961 } else {
3962 packet.writeLong(this.clientParam);
3963 packet.writeLong(this.maxThreeBytes);
3964 }
3965 } else {
3966 packet.writeInt((int) this.clientParam);
3967 packet.writeLongInt(this.maxThreeBytes);
3968 }
3969 }
3970
3971 // User/Password data
3972 packet.writeString(user, "utf-8", this.connection);
3973
3974 if (password.length() != 0) {
3975 packet.writeByte((byte) 0x14);
3976
3977 try {
3978 packet.writeBytesNoNull(Security.scramble411(password, this.seed, this.connection));
3979 } catch (NoSuchAlgorithmException nse) {
3980 throw SQLError.createSQLException(Messages.getString("MysqlIO.95") //$NON-NLS-1$
3981 +Messages.getString("MysqlIO.96"), //$NON-NLS-1$
3982 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3983 } catch (UnsupportedEncodingException e) {
3984 throw SQLError.createSQLException(Messages.getString("MysqlIO.95") //$NON-NLS-1$
3985 +Messages.getString("MysqlIO.96"), //$NON-NLS-1$
3986 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3987 }
3988 } else {
3989 /* For empty password*/
3990 packet.writeByte((byte) 0);
3991 }
3992
3993 if (this.useConnectWithDb) {
3994 packet.writeString(database, "utf-8", this.connection);
3995 }
3996
3997 send(packet, packet.getPosition());
3998
3999 byte savePacketSequence = this.packetSequence++;
4000
4001 Buffer reply = checkErrorPacket();
4002
4003 if (reply.isLastDataPacket()) {
4004 /*
4005 By sending this very specific reply server asks us to send scrambled
4006 password in old format. The reply contains scramble_323.
4007 */
4008 this.packetSequence = ++savePacketSequence;
4009 packet.clear();
4010
4011 String seed323 = this.seed.substring(0, 8);
4012 packet.writeString(Util.newCrypt(password, seed323));
4013 send(packet, packet.getPosition());
4014
4015 /* Read what server thinks about out new auth message report */
4016 checkErrorPacket();
4017 }
4018 }
4019
4020 /**
4021 * Un-packs binary-encoded result set data for one row
4022 *
4023 * @param fields
4024 * @param binaryData
4025 * @param resultSetConcurrency DOCUMENT ME!
4026 *
4027 * @return byte[][]
4028 *
4029 * @throws SQLException DOCUMENT ME!
4030 */
4031 private final ResultSetRow unpackBinaryResultSetRow(Field[] fields,
4032 Buffer binaryData, int resultSetConcurrency) throws SQLException {
4033 int numFields = fields.length;
4034
4035 byte[][] unpackedRowData = new byte[numFields][];
4036
4037 //
4038 // Unpack the null bitmask, first
4039 //
4040
4041 /* Reserve place for null-marker bytes */
4042 int nullCount = (numFields + 9) / 8;
4043
4044 byte[] nullBitMask = new byte[nullCount];
4045
4046 for (int i = 0; i < nullCount; i++) {
4047 nullBitMask[i] = binaryData.readByte();
4048 }
4049
4050 int nullMaskPos = 0;
4051 int bit = 4; // first two bits are reserved for future use
4052
4053 //
4054 // TODO: Benchmark if moving check for updatable result
4055 // sets out of loop is worthwhile?
4056 //
4057
4058 for (int i = 0; i < numFields; i++) {
4059 if ((nullBitMask[nullMaskPos] & bit) != 0) {
4060 unpackedRowData[i] = null;
4061 } else {
4062 if (resultSetConcurrency != ResultSetInternalMethods.CONCUR_UPDATABLE) {
4063 extractNativeEncodedColumn(binaryData, fields, i,
4064 unpackedRowData);
4065 } else {
4066 unpackNativeEncodedColumn(binaryData, fields, i,
4067 unpackedRowData);
4068 }
4069 }
4070
4071 if (((bit <<= 1) & 255) == 0) {
4072 bit = 1; /* To next byte */
4073
4074 nullMaskPos++;
4075 }
4076 }
4077
4078 return new ByteArrayRow(unpackedRowData, getExceptionInterceptor());
4079 }
4080
4081
4082 private final void extractNativeEncodedColumn(Buffer binaryData,
4083 Field[] fields, int columnIndex, byte[][] unpackedRowData) throws SQLException {
4084 Field curField = fields[columnIndex];
4085
4086 switch (curField.getMysqlType()) {
4087 case MysqlDefs.FIELD_TYPE_NULL:
4088 break; // for dummy binds
4089
4090 case MysqlDefs.FIELD_TYPE_TINY:
4091
4092 unpackedRowData[columnIndex] = new byte[] {binaryData.readByte()};
4093 break;
4094
4095 case MysqlDefs.FIELD_TYPE_SHORT:
4096 case MysqlDefs.FIELD_TYPE_YEAR:
4097
4098 unpackedRowData[columnIndex] = binaryData.getBytes(2);
4099 break;
4100 case MysqlDefs.FIELD_TYPE_LONG:
4101 case MysqlDefs.FIELD_TYPE_INT24:
4102
4103 unpackedRowData[columnIndex] = binaryData.getBytes(4);
4104 break;
4105 case MysqlDefs.FIELD_TYPE_LONGLONG:
4106
4107 unpackedRowData[columnIndex] = binaryData.getBytes(8);
4108 break;
4109 case MysqlDefs.FIELD_TYPE_FLOAT:
4110
4111 unpackedRowData[columnIndex] = binaryData.getBytes(4);
4112 break;
4113 case MysqlDefs.FIELD_TYPE_DOUBLE:
4114
4115 unpackedRowData[columnIndex] = binaryData.getBytes(8);
4116 break;
4117 case MysqlDefs.FIELD_TYPE_TIME:
4118
4119 int length = (int) binaryData.readFieldLength();
4120
4121 unpackedRowData[columnIndex] = binaryData.getBytes(length);
4122
4123 break;
4124 case MysqlDefs.FIELD_TYPE_DATE:
4125
4126 length = (int) binaryData.readFieldLength();
4127
4128 unpackedRowData[columnIndex] = binaryData.getBytes(length);
4129
4130 break;
4131 case MysqlDefs.FIELD_TYPE_DATETIME:
4132 case MysqlDefs.FIELD_TYPE_TIMESTAMP:
4133 length = (int) binaryData.readFieldLength();
4134
4135 unpackedRowData[columnIndex] = binaryData.getBytes(length);
4136 break;
4137 case MysqlDefs.FIELD_TYPE_TINY_BLOB:
4138 case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
4139 case MysqlDefs.FIELD_TYPE_LONG_BLOB:
4140 case MysqlDefs.FIELD_TYPE_BLOB:
4141 case MysqlDefs.FIELD_TYPE_VAR_STRING:
4142 case MysqlDefs.FIELD_TYPE_VARCHAR:
4143 case MysqlDefs.FIELD_TYPE_STRING:
4144 case MysqlDefs.FIELD_TYPE_DECIMAL:
4145 case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
4146 case MysqlDefs.FIELD_TYPE_GEOMETRY:
4147 unpackedRowData[columnIndex] = binaryData.readLenByteArray(0);
4148
4149 break;
4150 case MysqlDefs.FIELD_TYPE_BIT:
4151 unpackedRowData[columnIndex] = binaryData.readLenByteArray(0);
4152
4153 break;
4154 default:
4155 throw SQLError.createSQLException(Messages.getString("MysqlIO.97") //$NON-NLS-1$
4156 +curField.getMysqlType() +
4157 Messages.getString("MysqlIO.98") + columnIndex +
4158 Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
4159 + fields.length + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
4160 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
4161 }
4162 }
4163
4164 private final void unpackNativeEncodedColumn(Buffer binaryData,
4165 Field[] fields, int columnIndex, byte[][] unpackedRowData)
4166 throws SQLException {
4167 Field curField = fields[columnIndex];
4168
4169 switch (curField.getMysqlType()) {
4170 case MysqlDefs.FIELD_TYPE_NULL:
4171 break; // for dummy binds
4172
4173 case MysqlDefs.FIELD_TYPE_TINY:
4174
4175 byte tinyVal = binaryData.readByte();
4176
4177 if (!curField.isUnsigned()) {
4178 unpackedRowData[columnIndex] = String.valueOf(tinyVal)
4179 .getBytes();
4180 } else {
4181 short unsignedTinyVal = (short) (tinyVal & 0xff);
4182
4183 unpackedRowData[columnIndex] = String.valueOf(unsignedTinyVal)
4184 .getBytes();
4185 }
4186
4187 break;
4188
4189 case MysqlDefs.FIELD_TYPE_SHORT:
4190 case MysqlDefs.FIELD_TYPE_YEAR:
4191
4192 short shortVal = (short) binaryData.readInt();
4193
4194 if (!curField.isUnsigned()) {
4195 unpackedRowData[columnIndex] = String.valueOf(shortVal)
4196 .getBytes();
4197 } else {
4198 int unsignedShortVal = shortVal & 0xffff;
4199
4200 unpackedRowData[columnIndex] = String.valueOf(unsignedShortVal)
4201 .getBytes();
4202 }
4203
4204 break;
4205
4206 case MysqlDefs.FIELD_TYPE_LONG:
4207 case MysqlDefs.FIELD_TYPE_INT24:
4208
4209 int intVal = (int) binaryData.readLong();
4210
4211 if (!curField.isUnsigned()) {
4212 unpackedRowData[columnIndex] = String.valueOf(intVal)
4213 .getBytes();
4214 } else {
4215 long longVal = intVal & 0xffffffffL;
4216
4217 unpackedRowData[columnIndex] = String.valueOf(longVal)
4218 .getBytes();
4219 }
4220
4221 break;
4222
4223 case MysqlDefs.FIELD_TYPE_LONGLONG:
4224
4225 long longVal = binaryData.readLongLong();
4226
4227 if (!curField.isUnsigned()) {
4228 unpackedRowData[columnIndex] = String.valueOf(longVal)
4229 .getBytes();
4230 } else {
4231 BigInteger asBigInteger = ResultSetImpl.convertLongToUlong(longVal);
4232
4233 unpackedRowData[columnIndex] = asBigInteger.toString()
4234 .getBytes();
4235 }
4236
4237 break;
4238
4239 case MysqlDefs.FIELD_TYPE_FLOAT:
4240
4241 float floatVal = Float.intBitsToFloat(binaryData.readIntAsLong());
4242
4243 unpackedRowData[columnIndex] = String.valueOf(floatVal).getBytes();
4244
4245 break;
4246
4247 case MysqlDefs.FIELD_TYPE_DOUBLE:
4248
4249 double doubleVal = Double.longBitsToDouble(binaryData.readLongLong());
4250
4251 unpackedRowData[columnIndex] = String.valueOf(doubleVal).getBytes();
4252
4253 break;
4254
4255 case MysqlDefs.FIELD_TYPE_TIME:
4256
4257 int length = (int) binaryData.readFieldLength();
4258
4259 int hour = 0;
4260 int minute = 0;
4261 int seconds = 0;
4262
4263 if (length != 0) {
4264 binaryData.readByte(); // skip tm->neg
4265 binaryData.readLong(); // skip daysPart
4266 hour = binaryData.readByte();
4267 minute = binaryData.readByte();
4268 seconds = binaryData.readByte();
4269
4270 if (length > 8) {
4271 binaryData.readLong(); // ignore 'secondsPart'
4272 }
4273 }
4274
4275
4276 byte[] timeAsBytes = new byte[8];
4277
4278 timeAsBytes[0] = (byte) Character.forDigit(hour / 10, 10);
4279 timeAsBytes[1] = (byte) Character.forDigit(hour % 10, 10);
4280
4281 timeAsBytes[2] = (byte) ':';
4282
4283 timeAsBytes[3] = (byte) Character.forDigit(minute / 10,
4284 10);
4285 timeAsBytes[4] = (byte) Character.forDigit(minute % 10,
4286 10);
4287
4288 timeAsBytes[5] = (byte) ':';
4289
4290 timeAsBytes[6] = (byte) Character.forDigit(seconds / 10,
4291 10);
4292 timeAsBytes[7] = (byte) Character.forDigit(seconds % 10,
4293 10);
4294
4295 unpackedRowData[columnIndex] = timeAsBytes;
4296
4297
4298 break;
4299
4300 case MysqlDefs.FIELD_TYPE_DATE:
4301 length = (int) binaryData.readFieldLength();
4302
4303 int year = 0;
4304 int month = 0;
4305 int day = 0;
4306
4307 hour = 0;
4308 minute = 0;
4309 seconds = 0;
4310
4311 if (length != 0) {
4312 year = binaryData.readInt();
4313 month = binaryData.readByte();
4314 day = binaryData.readByte();
4315 }
4316
4317 if ((year == 0) && (month == 0) && (day == 0)) {
4318 if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL.equals(
4319 this.connection.getZeroDateTimeBehavior())) {
4320 unpackedRowData[columnIndex] = null;
4321
4322 break;
4323 } else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION.equals(
4324 this.connection.getZeroDateTimeBehavior())) {
4325 throw SQLError.createSQLException("Value '0000-00-00' can not be represented as java.sql.Date",
4326 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4327 }
4328
4329 year = 1;
4330 month = 1;
4331 day = 1;
4332 }
4333
4334
4335 byte[] dateAsBytes = new byte[10];
4336
4337 dateAsBytes[0] = (byte) Character.forDigit(year / 1000,
4338 10);
4339
4340 int after1000 = year % 1000;
4341
4342 dateAsBytes[1] = (byte) Character.forDigit(after1000 / 100,
4343 10);
4344
4345 int after100 = after1000 % 100;
4346
4347 dateAsBytes[2] = (byte) Character.forDigit(after100 / 10,
4348 10);
4349 dateAsBytes[3] = (byte) Character.forDigit(after100 % 10,
4350 10);
4351
4352 dateAsBytes[4] = (byte) '-';
4353
4354 dateAsBytes[5] = (byte) Character.forDigit(month / 10,
4355 10);
4356 dateAsBytes[6] = (byte) Character.forDigit(month % 10,
4357 10);
4358
4359 dateAsBytes[7] = (byte) '-';
4360
4361 dateAsBytes[8] = (byte) Character.forDigit(day / 10, 10);
4362 dateAsBytes[9] = (byte) Character.forDigit(day % 10, 10);
4363
4364 unpackedRowData[columnIndex] = dateAsBytes;
4365
4366
4367 break;
4368
4369 case MysqlDefs.FIELD_TYPE_DATETIME:
4370 case MysqlDefs.FIELD_TYPE_TIMESTAMP:
4371 length = (int) binaryData.readFieldLength();
4372
4373 year = 0;
4374 month = 0;
4375 day = 0;
4376
4377 hour = 0;
4378 minute = 0;
4379 seconds = 0;
4380
4381 int nanos = 0;
4382
4383 if (length != 0) {
4384 year = binaryData.readInt();
4385 month = binaryData.readByte();
4386 day = binaryData.readByte();
4387
4388 if (length > 4) {
4389 hour = binaryData.readByte();
4390 minute = binaryData.readByte();
4391 seconds = binaryData.readByte();
4392 }
4393
4394 //if (length > 7) {
4395 // nanos = (int)binaryData.readLong();
4396 //}
4397 }
4398
4399 if ((year == 0) && (month == 0) && (day == 0)) {
4400 if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL.equals(
4401 this.connection.getZeroDateTimeBehavior())) {
4402 unpackedRowData[columnIndex] = null;
4403
4404 break;
4405 } else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION.equals(
4406 this.connection.getZeroDateTimeBehavior())) {
4407 throw SQLError.createSQLException("Value '0000-00-00' can not be represented as java.sql.Timestamp",
4408 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4409 }
4410
4411 year = 1;
4412 month = 1;
4413 day = 1;
4414 }
4415
4416
4417 int stringLength = 19;
4418
4419 byte[] nanosAsBytes = Integer.toString(nanos).getBytes();
4420
4421 stringLength += (1 + nanosAsBytes.length); // '.' + # of digits
4422
4423 byte[] datetimeAsBytes = new byte[stringLength];
4424
4425 datetimeAsBytes[0] = (byte) Character.forDigit(year / 1000,
4426 10);
4427
4428 after1000 = year % 1000;
4429
4430 datetimeAsBytes[1] = (byte) Character.forDigit(after1000 / 100,
4431 10);
4432
4433 after100 = after1000 % 100;
4434
4435 datetimeAsBytes[2] = (byte) Character.forDigit(after100 / 10,
4436 10);
4437 datetimeAsBytes[3] = (byte) Character.forDigit(after100 % 10,
4438 10);
4439
4440 datetimeAsBytes[4] = (byte) '-';
4441
4442 datetimeAsBytes[5] = (byte) Character.forDigit(month / 10,
4443 10);
4444 datetimeAsBytes[6] = (byte) Character.forDigit(month % 10,
4445 10);
4446
4447 datetimeAsBytes[7] = (byte) '-';
4448
4449 datetimeAsBytes[8] = (byte) Character.forDigit(day / 10,
4450 10);
4451 datetimeAsBytes[9] = (byte) Character.forDigit(day % 10,
4452 10);
4453
4454 datetimeAsBytes[10] = (byte) ' ';
4455
4456 datetimeAsBytes[11] = (byte) Character.forDigit(hour / 10,
4457 10);
4458 datetimeAsBytes[12] = (byte) Character.forDigit(hour % 10,
4459 10);
4460
4461 datetimeAsBytes[13] = (byte) ':';
4462
4463 datetimeAsBytes[14] = (byte) Character.forDigit(minute / 10,
4464 10);
4465 datetimeAsBytes[15] = (byte) Character.forDigit(minute % 10,
4466 10);
4467
4468 datetimeAsBytes[16] = (byte) ':';
4469
4470 datetimeAsBytes[17] = (byte) Character.forDigit(seconds / 10,
4471 10);
4472 datetimeAsBytes[18] = (byte) Character.forDigit(seconds % 10,
4473 10);
4474
4475 datetimeAsBytes[19] = (byte) '.';
4476
4477 int nanosOffset = 20;
4478
4479 for (int j = 0; j < nanosAsBytes.length; j++) {
4480 datetimeAsBytes[nanosOffset + j] = nanosAsBytes[j];
4481 }
4482
4483 unpackedRowData[columnIndex] = datetimeAsBytes;
4484
4485
4486 break;
4487
4488 case MysqlDefs.FIELD_TYPE_TINY_BLOB:
4489 case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
4490 case MysqlDefs.FIELD_TYPE_LONG_BLOB:
4491 case MysqlDefs.FIELD_TYPE_BLOB:
4492 case MysqlDefs.FIELD_TYPE_VAR_STRING:
4493 case MysqlDefs.FIELD_TYPE_STRING:
4494 case MysqlDefs.FIELD_TYPE_VARCHAR:
4495 case MysqlDefs.FIELD_TYPE_DECIMAL:
4496 case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
4497 case MysqlDefs.FIELD_TYPE_BIT:
4498 unpackedRowData[columnIndex] = binaryData.readLenByteArray(0);
4499
4500 break;
4501
4502 default:
4503 throw SQLError.createSQLException(Messages.getString("MysqlIO.97") //$NON-NLS-1$
4504 +curField.getMysqlType() +
4505 Messages.getString("MysqlIO.98") + columnIndex +
4506 Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
4507 + fields.length + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
4508 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
4509 }
4510 }
4511
4512 /**
4513 * Negotiates the SSL communications channel used when connecting
4514 * to a MySQL server that understands SSL.
4515 *
4516 * @param user
4517 * @param password
4518 * @param database
4519 * @param packLength
4520 * @throws SQLException
4521 * @throws CommunicationsException
4522 */
4523 private void negotiateSSLConnection(String user, String password,
4524 String database, int packLength)
4525 throws SQLException {
4526 if (!ExportControlled.enabled()) {
4527 throw new ConnectionFeatureNotAvailableException(this.connection,
4528 this.lastPacketSentTimeMs, null);
4529 }
4530
4531 boolean doSecureAuth = false;
4532
4533 if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
4534 this.clientParam |= CLIENT_SECURE_CONNECTION;
4535 doSecureAuth = true;
4536 }
4537
4538 this.clientParam |= CLIENT_SSL;
4539
4540 Buffer packet = new Buffer(packLength);
4541
4542 if (this.use41Extensions) {
4543 packet.writeLong(this.clientParam);
4544 } else {
4545 packet.writeInt((int) this.clientParam);
4546 }
4547
4548 send(packet, packet.getPosition());
4549
4550 ExportControlled.transformSocketToSSLSocket(this);
4551
4552 packet.clear();
4553
4554 if (doSecureAuth) {
4555 if (versionMeetsMinimum(4, 1, 1)) {
4556 secureAuth411(null, packLength, user, password, database, true);
4557 } else {
4558 secureAuth411(null, packLength, user, password, database, true);
4559 }
4560 } else {
4561 if (this.use41Extensions) {
4562 packet.writeLong(this.clientParam);
4563 packet.writeLong(this.maxThreeBytes);
4564 } else {
4565 packet.writeInt((int) this.clientParam);
4566 packet.writeLongInt(this.maxThreeBytes);
4567 }
4568
4569 // User/Password data
4570 packet.writeString(user);
4571
4572 if (this.protocolVersion > 9) {
4573 packet.writeString(Util.newCrypt(password, this.seed));
4574 } else {
4575 packet.writeString(Util.oldCrypt(password, this.seed));
4576 }
4577
4578 if (((this.serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0) &&
4579 (database != null) && (database.length() > 0)) {
4580 packet.writeString(database);
4581 }
4582
4583 send(packet, packet.getPosition());
4584 }
4585 }
4586
4587 protected int getServerStatus() {
4588 return this.serverStatus;
4589 }
4590
4591 protected List fetchRowsViaCursor(List fetchedRows, long statementId,
4592 Field[] columnTypes, int fetchSize, boolean useBufferRowExplicit) throws SQLException {
4593
4594 if (fetchedRows == null) {
4595 fetchedRows = new ArrayList(fetchSize);
4596 } else {
4597 fetchedRows.clear();
4598 }
4599
4600 this.sharedSendPacket.clear();
4601
4602 this.sharedSendPacket.writeByte((byte) MysqlDefs.COM_FETCH);
4603 this.sharedSendPacket.writeLong(statementId);
4604 this.sharedSendPacket.writeLong(fetchSize);
4605
4606 sendCommand(MysqlDefs.COM_FETCH, null, this.sharedSendPacket, true,
4607 null, 0);
4608
4609 ResultSetRow row = null;
4610
4611 while ((row = nextRow(columnTypes, columnTypes.length, true,
4612 ResultSet.CONCUR_READ_ONLY, false, useBufferRowExplicit, false, null)) != null) {
4613 fetchedRows.add(row);
4614 }
4615
4616 return fetchedRows;
4617 }
4618
4619 protected long getThreadId() {
4620 return this.threadId;
4621 }
4622
4623 protected boolean useNanosForElapsedTime() {
4624 return this.useNanosForElapsedTime;
4625 }
4626
4627 protected long getSlowQueryThreshold() {
4628 return this.slowQueryThreshold;
4629 }
4630
4631 protected String getQueryTimingUnits() {
4632 return this.queryTimingUnits;
4633 }
4634
4635 protected int getCommandCount() {
4636 return this.commandCount;
4637 }
4638
4639 private void checkTransactionState(int oldStatus) throws SQLException {
4640 boolean previouslyInTrans = ((oldStatus & SERVER_STATUS_IN_TRANS) != 0);
4641 boolean currentlyInTrans = ((this.serverStatus & SERVER_STATUS_IN_TRANS) != 0);
4642
4643 if (previouslyInTrans && !currentlyInTrans) {
4644 this.connection.transactionCompleted();
4645 } else if (!previouslyInTrans && currentlyInTrans) {
4646 this.connection.transactionBegun();
4647 }
4648 }
4649
4650 protected void setStatementInterceptors(List statementInterceptors) {
4651 this.statementInterceptors = statementInterceptors;
4652 }
4653
4654 protected ExceptionInterceptor getExceptionInterceptor() {
4655 return this.exceptionInterceptor;
4656 }
4657 }