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.IOException;
28 import java.io.UnsupportedEncodingException;
29 import java.lang.reflect.Array;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.Method;
32 import java.nio.ByteBuffer;
33 import java.nio.CharBuffer;
34 import java.nio.charset.Charset;
35 import java.nio.charset.CharsetEncoder;
36 import java.sql.Blob;
37 import java.sql.DatabaseMetaData;
38 import java.sql.ResultSet;
39 import java.sql.SQLException;
40 import java.sql.SQLWarning;
41 import java.sql.Savepoint;
42 import java.util.ArrayList;
43 import java.util.Calendar;
44 import java.util.GregorianCalendar;
45 import java.util.HashMap;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.Properties;
51 import java.util.Stack;
52 import java.util.StringTokenizer;
53 import java.util.TimeZone;
54 import java.util.Timer;
55 import java.util.TreeMap;
56
57 import com.mysql.jdbc.log.Log;
58 import com.mysql.jdbc.log.LogFactory;
59 import com.mysql.jdbc.log.NullLogger;
60 import com.mysql.jdbc.profiler.ProfilerEvent;
61 import com.mysql.jdbc.profiler.ProfilerEventHandler;
62 import com.mysql.jdbc.profiler.ProfilerEventHandlerFactory;
63 import com.mysql.jdbc.util.LRUCache;
64
65 /**
66 * A Connection represents a session with a specific database. Within the
67 * context of a Connection, SQL statements are executed and results are
68 * returned.
69 * <P>
70 * A Connection's database is able to provide information describing its tables,
71 * its supported SQL grammar, its stored procedures, the capabilities of this
72 * connection, etc. This information is obtained with the getMetaData method.
73 * </p>
74 *
75 * @author Mark Matthews
76 * @version $Id$
77 * @see java.sql.Connection
78 */
79 public class ConnectionImpl extends ConnectionPropertiesImpl implements
80 Connection {
81 private static final String JDBC_LOCAL_CHARACTER_SET_RESULTS = "jdbc.local.character_set_results";
82
83 class ExceptionInterceptorChain implements ExceptionInterceptor {
84 List interceptors;
85
86 ExceptionInterceptorChain(String interceptorClasses) throws SQLException {
87 interceptors = Util.loadExtensions(ConnectionImpl.this, props, interceptorClasses, "Connection.BadExceptionInterceptor", this);
88 }
89
90 public SQLException interceptException(SQLException sqlEx, Connection conn) {
91 if (interceptors != null) {
92 Iterator iter = interceptors.iterator();
93
94 while (iter.hasNext()) {
95 sqlEx = ((ExceptionInterceptor)iter.next()).interceptException(sqlEx, ConnectionImpl.this);
96 }
97 }
98
99 return sqlEx;
100 }
101
102 public void destroy() {
103 if (interceptors != null) {
104 Iterator iter = interceptors.iterator();
105
106 while (iter.hasNext()) {
107 ((ExceptionInterceptor)iter.next()).destroy();
108 }
109 }
110
111 }
112
113 public void init(Connection conn, Properties props) throws SQLException {
114 if (interceptors != null) {
115 Iterator iter = interceptors.iterator();
116
117 while (iter.hasNext()) {
118 ((ExceptionInterceptor)iter.next()).init(conn, props);
119 }
120 }
121 }
122 }
123
124 /**
125 * Used as a key for caching callable statements which (may) depend on
126 * current catalog...In 5.0.x, they don't (currently), but stored procedure
127 * names soon will, so current catalog is a (hidden) component of the name.
128 */
129 class CompoundCacheKey {
130 String componentOne;
131
132 String componentTwo;
133
134 int hashCode;
135
136 CompoundCacheKey(String partOne, String partTwo) {
137 this.componentOne = partOne;
138 this.componentTwo = partTwo;
139
140 // Handle first component (in most cases, currentCatalog)
141 // being NULL....
142 this.hashCode = (((this.componentOne != null) ? this.componentOne
143 : "") + this.componentTwo).hashCode();
144 }
145
146 /*
147 * (non-Javadoc)
148 *
149 * @see java.lang.Object#equals(java.lang.Object)
150 */
151 public boolean equals(Object obj) {
152 if (obj instanceof CompoundCacheKey) {
153 CompoundCacheKey another = (CompoundCacheKey) obj;
154
155 boolean firstPartEqual = false;
156
157 if (this.componentOne == null) {
158 firstPartEqual = (another.componentOne == null);
159 } else {
160 firstPartEqual = this.componentOne
161 .equals(another.componentOne);
162 }
163
164 return (firstPartEqual && this.componentTwo
165 .equals(another.componentTwo));
166 }
167
168 return false;
169 }
170
171 /*
172 * (non-Javadoc)
173 *
174 * @see java.lang.Object#hashCode()
175 */
176 public int hashCode() {
177 return this.hashCode;
178 }
179 }
180
181 /**
182 * Marker for character set converter not being available (not written,
183 * multibyte, etc) Used to prevent multiple instantiation requests.
184 */
185 private static final Object CHARSET_CONVERTER_NOT_AVAILABLE_MARKER = new Object();
186
187 /**
188 * The mapping between MySQL charset names and Java charset names.
189 * Initialized by loadCharacterSetMapping()
190 */
191 public static Map charsetMap;
192
193 /** Default logger class name */
194 protected static final String DEFAULT_LOGGER_CLASS = "com.mysql.jdbc.log.StandardLogger";
195
196 private final static int HISTOGRAM_BUCKETS = 20;
197
198 /** Logger instance name */
199 private static final String LOGGER_INSTANCE_NAME = "MySQL";
200
201 /**
202 * Map mysql transaction isolation level name to
203 * java.sql.Connection.TRANSACTION_XXX
204 */
205 private static Map mapTransIsolationNameToValue = null;
206
207 /** Null logger shared by all connections at startup */
208 private static final Log NULL_LOGGER = new NullLogger(LOGGER_INSTANCE_NAME);
209
210 private static Map roundRobinStatsMap;
211
212 private static final Map serverCollationByUrl = new HashMap();
213
214 private static final Map serverConfigByUrl = new HashMap();
215
216 private long queryTimeCount;
217 private double queryTimeSum;
218 private double queryTimeSumSquares;
219 private double queryTimeMean;
220
221 private Timer cancelTimer;
222
223 private List connectionLifecycleInterceptors;
224
225 private static final Constructor JDBC_4_CONNECTION_CTOR;
226
227 private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;
228
229 private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;
230
231 static {
232 mapTransIsolationNameToValue = new HashMap(8);
233 mapTransIsolationNameToValue.put("READ-UNCOMMITED", Constants.integerValueOf(
234 TRANSACTION_READ_UNCOMMITTED));
235 mapTransIsolationNameToValue.put("READ-UNCOMMITTED", Constants.integerValueOf(
236 TRANSACTION_READ_UNCOMMITTED));
237 mapTransIsolationNameToValue.put("READ-COMMITTED", Constants.integerValueOf(
238 TRANSACTION_READ_COMMITTED));
239 mapTransIsolationNameToValue.put("REPEATABLE-READ", Constants.integerValueOf(
240 TRANSACTION_REPEATABLE_READ));
241 mapTransIsolationNameToValue.put("SERIALIZABLE", Constants.integerValueOf(
242 TRANSACTION_SERIALIZABLE));
243
244 if (Util.isJdbc4()) {
245 try {
246 JDBC_4_CONNECTION_CTOR = Class.forName(
247 "com.mysql.jdbc.JDBC4Connection").getConstructor(
248 new Class[] { String.class, Integer.TYPE,
249 Properties.class, String.class, String.class });
250 } catch (SecurityException e) {
251 throw new RuntimeException(e);
252 } catch (NoSuchMethodException e) {
253 throw new RuntimeException(e);
254 } catch (ClassNotFoundException e) {
255 throw new RuntimeException(e);
256 }
257 } else {
258 JDBC_4_CONNECTION_CTOR = null;
259 }
260 }
261
262 protected static SQLException appendMessageToException(SQLException sqlEx,
263 String messageToAppend, ExceptionInterceptor interceptor) {
264 String origMessage = sqlEx.getMessage();
265 String sqlState = sqlEx.getSQLState();
266 int vendorErrorCode = sqlEx.getErrorCode();
267
268 StringBuffer messageBuf = new StringBuffer(origMessage.length()
269 + messageToAppend.length());
270 messageBuf.append(origMessage);
271 messageBuf.append(messageToAppend);
272
273 SQLException sqlExceptionWithNewMessage = SQLError.createSQLException(messageBuf
274 .toString(), sqlState, vendorErrorCode, interceptor);
275
276 //
277 // Try and maintain the original stack trace,
278 // only works on JDK-1.4 and newer
279 //
280
281 try {
282 // Have to do this with reflection, otherwise older JVMs croak
283 Method getStackTraceMethod = null;
284 Method setStackTraceMethod = null;
285 Object theStackTraceAsObject = null;
286
287 Class stackTraceElementClass = Class
288 .forName("java.lang.StackTraceElement");
289 Class stackTraceElementArrayClass = Array.newInstance(
290 stackTraceElementClass, new int[] { 0 }).getClass();
291
292 getStackTraceMethod = Throwable.class.getMethod("getStackTrace",
293 new Class[] {});
294
295 setStackTraceMethod = Throwable.class.getMethod("setStackTrace",
296 new Class[] { stackTraceElementArrayClass });
297
298 if (getStackTraceMethod != null && setStackTraceMethod != null) {
299 theStackTraceAsObject = getStackTraceMethod.invoke(sqlEx,
300 new Object[0]);
301 setStackTraceMethod.invoke(sqlExceptionWithNewMessage,
302 new Object[] { theStackTraceAsObject });
303 }
304 } catch (NoClassDefFoundError noClassDefFound) {
305
306 } catch (NoSuchMethodException noSuchMethodEx) {
307
308 } catch (Throwable catchAll) {
309
310 }
311
312 return sqlExceptionWithNewMessage;
313 }
314
315 protected synchronized Timer getCancelTimer() {
316 if (cancelTimer == null) {
317 boolean createdNamedTimer = false;
318
319 // Use reflection magic to try this on JDK's 1.5 and newer, fallback to non-named
320 // timer on older VMs.
321 try {
322 Constructor ctr = Timer.class.getConstructor(new Class[] {String.class, Boolean.TYPE});
323
324 cancelTimer = (Timer)ctr.newInstance(new Object[] { "MySQL Statement Cancellation Timer", Boolean.TRUE});
325 createdNamedTimer = true;
326 } catch (Throwable t) {
327 createdNamedTimer = false;
328 }
329
330 if (!createdNamedTimer) {
331 cancelTimer = new Timer(true);
332 }
333 }
334
335 return cancelTimer;
336 }
337
338
339 /**
340 * Creates a connection instance -- We need to provide factory-style methods
341 * so we can support both JDBC3 (and older) and JDBC4 runtimes, otherwise
342 * the class verifier complains when it tries to load JDBC4-only interface
343 * classes that are present in JDBC4 method signatures.
344 */
345
346 protected static Connection getInstance(String hostToConnectTo,
347 int portToConnectTo, Properties info, String databaseToConnectTo,
348 String url) throws SQLException {
349 if (!Util.isJdbc4()) {
350 return new ConnectionImpl(hostToConnectTo, portToConnectTo, info,
351 databaseToConnectTo, url);
352 }
353
354 return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR,
355 new Object[] {
356 hostToConnectTo, Constants.integerValueOf(portToConnectTo), info,
357 databaseToConnectTo, url }, null);
358 }
359
360 private static synchronized int getNextRoundRobinHostIndex(String url,
361 List hostList) {
362 // we really do "random" here, because you don't get even
363 // distribution when this is coupled with connection pools
364
365 int indexRange = hostList.size();
366
367 int index = (int)(Math.random() * indexRange);
368
369 return index;
370 }
371
372 private static boolean nullSafeCompare(String s1, String s2) {
373 if (s1 == null && s2 == null) {
374 return true;
375 }
376
377 if (s1 == null && s2 != null) {
378 return false;
379 }
380
381 return s1.equals(s2);
382 }
383
384 /** Are we in autoCommit mode? */
385 private boolean autoCommit = true;
386
387 /** A map of SQL to parsed prepared statement parameters. */
388 private Map cachedPreparedStatementParams;
389
390 /**
391 * For servers > 4.1.0, what character set is the metadata returned in?
392 */
393 private String characterSetMetadata = null;
394
395 /**
396 * The character set we want results and result metadata returned in (null ==
397 * results in any charset, metadata in UTF-8).
398 */
399 private String characterSetResultsOnServer = null;
400
401 /**
402 * Holds cached mappings to charset converters to avoid static
403 * synchronization and at the same time save memory (each charset converter
404 * takes approx 65K of static data).
405 */
406 private Map charsetConverterMap = new HashMap(CharsetMapping
407 .getNumberOfCharsetsConfigured());
408
409 /**
410 * The mapping between MySQL charset names and the max number of chars in
411 * them. Lazily instantiated via getMaxBytesPerChar().
412 */
413 private Map charsetToNumBytesMap;
414
415 /** The point in time when this connection was created */
416 private long connectionCreationTimeMillis = 0;
417
418 /** ID used when profiling */
419 private long connectionId;
420
421 /** The database we're currently using (called Catalog in JDBC terms). */
422 private String database = null;
423
424 /** Internal DBMD to use for various database-version specific features */
425 private DatabaseMetaData dbmd = null;
426
427 private TimeZone defaultTimeZone;
428
429 /** The event sink to use for profiling */
430 private ProfilerEventHandler eventSink;
431
432 private boolean executingFailoverReconnect = false;
433
434 /** Are we failed-over to a non-master host */
435 private boolean failedOver = false;
436
437 /** Why was this connection implicitly closed, if known? (for diagnostics) */
438 private Throwable forceClosedReason;
439
440 /** Where was this connection implicitly closed? (for diagnostics) */
441 private Throwable forcedClosedLocation;
442
443 /** Does the server suuport isolation levels? */
444 private boolean hasIsolationLevels = false;
445
446 /** Does this version of MySQL support quoted identifiers? */
447 private boolean hasQuotedIdentifiers = false;
448
449 /** The hostname we're connected to */
450 private String host = null;
451
452 /** The list of host(s) to try and connect to */
453 private List hostList = null;
454
455 /** How many hosts are in the host list? */
456 private int hostListSize = 0;
457
458 /**
459 * We need this 'bootstrapped', because 4.1 and newer will send fields back
460 * with this even before we fill this dynamically from the server.
461 */
462 private String[] indexToCharsetMapping = CharsetMapping.INDEX_TO_CHARSET;
463
464 /** The I/O abstraction interface (network conn to MySQL server */
465 private MysqlIO io = null;
466
467 private boolean isClientTzUTC = false;
468
469 /** Has this connection been closed? */
470 private boolean isClosed = true;
471
472 /** Is this connection associated with a global tx? */
473 private boolean isInGlobalTx = false;
474
475 /** Is this connection running inside a JDK-1.3 VM? */
476 private boolean isRunningOnJDK13 = false;
477
478 /** isolation level */
479 private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED;
480
481 private boolean isServerTzUTC = false;
482
483 /** When did the last query finish? */
484 private long lastQueryFinishedTime = 0;
485
486 /** The logger we're going to use */
487 private Log log = NULL_LOGGER;
488
489 /**
490 * If gathering metrics, what was the execution time of the longest query so
491 * far ?
492 */
493 private long longestQueryTimeMs = 0;
494
495 /** Is the server configured to use lower-case table names only? */
496 private boolean lowerCaseTableNames = false;
497
498 /** When did the master fail? */
499 private long masterFailTimeMillis = 0L;
500
501 private long maximumNumberTablesAccessed = 0;
502
503 /** Has the max-rows setting been changed from the default? */
504 private boolean maxRowsChanged = false;
505
506 /** When was the last time we reported metrics? */
507 private long metricsLastReportedMs;
508
509 private long minimumNumberTablesAccessed = Long.MAX_VALUE;
510
511 /** Mutex */
512 private final Object mutex = new Object();
513
514 /** The JDBC URL we're using */
515 private String myURL = null;
516
517 /** Does this connection need to be tested? */
518 private boolean needsPing = false;
519
520 private int netBufferLength = 16384;
521
522 private boolean noBackslashEscapes = false;
523
524 private long numberOfPreparedExecutes = 0;
525
526 private long numberOfPrepares = 0;
527
528 private long numberOfQueriesIssued = 0;
529
530 private long numberOfResultSetsCreated = 0;
531
532 private long[] numTablesMetricsHistBreakpoints;
533
534 private int[] numTablesMetricsHistCounts;
535
536 private long[] oldHistBreakpoints = null;
537
538 private int[] oldHistCounts = null;
539
540 /** A map of currently open statements */
541 private Map openStatements;
542
543 private LRUCache parsedCallableStatementCache;
544
545 private boolean parserKnowsUnicode = false;
546
547 /** The password we used */
548 private String password = null;
549
550 private long[] perfMetricsHistBreakpoints;
551
552 private int[] perfMetricsHistCounts;
553
554 /** Point of origin where this Connection was created */
555 private Throwable pointOfOrigin;
556
557 /** The port number we're connected to (defaults to 3306) */
558 private int port = 3306;
559
560 /**
561 * Used only when testing failover functionality for regressions, causes the
562 * failover code to not retry the master first
563 */
564 private boolean preferSlaveDuringFailover = false;
565
566 /** Properties for this connection specified by user */
567 protected Properties props = null;
568
569 /** Number of queries we've issued since the master failed */
570 private long queriesIssuedFailedOver = 0;
571
572 /** Should we retrieve 'info' messages from the server? */
573 private boolean readInfoMsg = false;
574
575 /** Are we in read-only mode? */
576 private boolean readOnly = false;
577
578 /** Cache of ResultSet metadata */
579 protected LRUCache resultSetMetadataCache;
580
581 /** The timezone of the server */
582 private TimeZone serverTimezoneTZ = null;
583
584 /** The map of server variables that we retrieve at connection init. */
585 private Map serverVariables = null;
586
587 private long shortestQueryTimeMs = Long.MAX_VALUE;
588
589 /** A map of statements that have had setMaxRows() called on them */
590 private Map statementsUsingMaxRows;
591
592 private double totalQueryTimeMs = 0;
593
594 /** Are transactions supported by the MySQL server we are connected to? */
595 private boolean transactionsSupported = false;
596
597 /**
598 * The type map for UDTs (not implemented, but used by some third-party
599 * vendors, most notably IBM WebSphere)
600 */
601 private Map typeMap;
602
603 /** Has ANSI_QUOTES been enabled on the server? */
604 private boolean useAnsiQuotes = false;
605
606 /** The user we're connected as */
607 private String user = null;
608
609 /**
610 * Should we use server-side prepared statements? (auto-detected, but can be
611 * disabled by user)
612 */
613 private boolean useServerPreparedStmts = false;
614
615 private LRUCache serverSideStatementCheckCache;
616 private LRUCache serverSideStatementCache;
617 private Calendar sessionCalendar;
618
619 private Calendar utcCalendar;
620
621 private String origHostToConnectTo;
622
623 // we don't want to be able to publicly clone this...
624
625 private int origPortToConnectTo;
626
627 private String origDatabaseToConnectTo;
628
629 private String errorMessageEncoding = "Cp1252"; // to begin with, changes after we talk to the server
630
631 private boolean usePlatformCharsetConverters;
632
633 /*
634 * For testing failover scenarios
635 */
636 private boolean hasTriedMasterFlag = false;
637
638 /**
639 * The comment (if any) that we'll prepend to all statements
640 * sent to the server (to show up in "SHOW PROCESSLIST")
641 */
642 private String statementComment = null;
643
644 private boolean storesLowerCaseTableName;
645
646 private List statementInterceptors;
647
648 /**
649 * If a CharsetEncoder is required for escaping. Needed for SJIS and related
650 * problems with \u00A5.
651 */
652 private boolean requiresEscapingEncoder;
653
654 /**'
655 * For the delegate only
656 */
657 protected ConnectionImpl() {
658 }
659
660 /**
661 * Creates a connection to a MySQL Server.
662 *
663 * @param hostToConnectTo
664 * the hostname of the database server
665 * @param portToConnectTo
666 * the port number the server is listening on
667 * @param info
668 * a Properties[] list holding the user and password
669 * @param databaseToConnectTo
670 * the database to connect to
671 * @param url
672 * the URL of the connection
673 * @param d
674 * the Driver instantation of the connection
675 * @exception SQLException
676 * if a database access error occurs
677 */
678 protected ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info,
679 String databaseToConnectTo, String url)
680 throws SQLException {
681 this.charsetToNumBytesMap = new HashMap();
682
683 this.connectionCreationTimeMillis = System.currentTimeMillis();
684 this.pointOfOrigin = new Throwable();
685
686 // Stash away for later, used to clone this connection for Statement.cancel
687 // and Statement.setQueryTimeout().
688 //
689
690 this.origHostToConnectTo = hostToConnectTo;
691 this.origPortToConnectTo = portToConnectTo;
692 this.origDatabaseToConnectTo = databaseToConnectTo;
693
694 try {
695 Blob.class.getMethod("truncate", new Class[] {Long.TYPE});
696
697 this.isRunningOnJDK13 = false;
698 } catch (NoSuchMethodException nsme) {
699 this.isRunningOnJDK13 = true;
700 }
701
702 this.sessionCalendar = new GregorianCalendar();
703 this.utcCalendar = new GregorianCalendar();
704 this.utcCalendar.setTimeZone(TimeZone.getTimeZone("GMT"));
705
706 //
707 // Normally, this code would be in initializeDriverProperties,
708 // but we need to do this as early as possible, so we can start
709 // logging to the 'correct' place as early as possible...this.log
710 // points to 'NullLogger' for every connection at startup to avoid
711 // NPEs and the overhead of checking for NULL at every logging call.
712 //
713 // We will reset this to the configured logger during properties
714 // initialization.
715 //
716 this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor());
717
718 // We store this per-connection, due to static synchronization
719 // issues in Java's built-in TimeZone class...
720 this.defaultTimeZone = Util.getDefaultTimeZone();
721
722 if ("GMT".equalsIgnoreCase(this.defaultTimeZone.getID())) {
723 this.isClientTzUTC = true;
724 } else {
725 this.isClientTzUTC = false;
726 }
727
728 this.openStatements = new HashMap();
729 this.serverVariables = new HashMap();
730 this.hostList = new ArrayList();
731
732 int numHosts = Integer.parseInt(info.getProperty(Driver.NUM_HOSTS_PROPERTY_KEY));
733
734 if (hostToConnectTo == null) {
735 this.host = "localhost";
736 this.hostList.add(this.host + ":" + portToConnectTo);
737 } else if (numHosts > 1) {
738 // multiple hosts separated by commas (failover)
739
740 for (int i = 0; i < numHosts; i++) {
741 int index = i + 1;
742
743 this.hostList.add(info.getProperty(Driver.HOST_PROPERTY_KEY + "." + index) +
744 ":" + info.getProperty(Driver.PORT_PROPERTY_KEY + "." + index));
745 }
746 } else {
747 this.host = hostToConnectTo;
748
749 if (hostToConnectTo.indexOf(":") == -1) {
750 this.hostList.add(this.host + ":" + portToConnectTo);
751 } else {
752 this.hostList.add(this.host);
753 }
754 }
755
756 this.hostListSize = this.hostList.size();
757 this.port = portToConnectTo;
758
759 if (databaseToConnectTo == null) {
760 databaseToConnectTo = "";
761 }
762
763 this.database = databaseToConnectTo;
764 this.myURL = url;
765 this.user = info.getProperty(NonRegisteringDriver.USER_PROPERTY_KEY);
766 this.password = info
767 .getProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY);
768
769 if ((this.user == null) || this.user.equals("")) {
770 this.user = "";
771 }
772
773 if (this.password == null) {
774 this.password = "";
775 }
776
777 this.props = info;
778 initializeDriverProperties(info);
779
780
781 try {
782 this.dbmd = getMetaData(false, false);
783 initializeSafeStatementInterceptors();
784 createNewIO(false);
785 unSafeStatementInterceptors();
786 } catch (SQLException ex) {
787 cleanup(ex);
788
789 // don't clobber SQL exceptions
790 throw ex;
791 } catch (Exception ex) {
792 cleanup(ex);
793
794 StringBuffer mesg = new StringBuffer(128);
795
796 if (!getParanoid()) {
797 mesg.append("Cannot connect to MySQL server on ");
798 mesg.append(this.host);
799 mesg.append(":");
800 mesg.append(this.port);
801 mesg.append(".\n\n");
802 mesg.append("Make sure that there is a MySQL server ");
803 mesg.append("running on the machine/port you are trying ");
804 mesg
805 .append("to connect to and that the machine this software is "
806 + "running on ");
807 mesg.append("is able to connect to this host/port "
808 + "(i.e. not firewalled). ");
809 mesg
810 .append("Also make sure that the server has not been started "
811 + "with the --skip-networking ");
812 mesg.append("flag.\n\n");
813 } else {
814 mesg.append("Unable to connect to database.");
815 }
816
817 SQLException sqlEx = SQLError.createSQLException(mesg.toString(),
818 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor());
819
820 sqlEx.initCause(ex);
821
822 throw sqlEx;
823 }
824 }
825
826 protected void unSafeStatementInterceptors() throws SQLException {
827
828 ArrayList unSafedStatementInterceptors = new ArrayList(this.statementInterceptors.size());
829
830 this.statementInterceptors = new ArrayList(this.statementInterceptors.size());
831
832 for (int i = 0; i < this.statementInterceptors.size(); i++) {
833 NoSubInterceptorWrapper wrappedInterceptor = (NoSubInterceptorWrapper) this.statementInterceptors.get(i);
834
835 unSafedStatementInterceptors.add(wrappedInterceptor.getUnderlyingInterceptor());
836 }
837
838 this.statementInterceptors = unSafedStatementInterceptors;
839 }
840
841 protected void initializeSafeStatementInterceptors() throws SQLException {
842 this.isClosed = false;
843
844 List unwrappedInterceptors = Util.loadExtensions(this, this.props,
845 getStatementInterceptors(),
846 "MysqlIo.BadStatementInterceptor", getExceptionInterceptor());
847
848 this.statementInterceptors = new ArrayList(unwrappedInterceptors.size());
849
850 for (int i = 0; i < unwrappedInterceptors.size(); i++) {
851 Object interceptor = unwrappedInterceptors.get(i);
852
853 // adapt older versions of statement interceptors, handle the case where something wants v2
854 // functionality but wants to run with an older driver
855 if (interceptor instanceof StatementInterceptor) {
856 if (ReflectiveStatementInterceptorAdapter.getV2PostProcessMethod(interceptor.getClass()) != null) {
857 this.statementInterceptors.add(new NoSubInterceptorWrapper(new ReflectiveStatementInterceptorAdapter((StatementInterceptor) interceptor)));
858 } else {
859 this.statementInterceptors.add(new NoSubInterceptorWrapper(new V1toV2StatementInterceptorAdapter((StatementInterceptor) interceptor)));
860 }
861 } else {
862 this.statementInterceptors.add(new NoSubInterceptorWrapper((StatementInterceptorV2)interceptor));
863 }
864 }
865
866
867 }
868
869 protected List getStatementInterceptorsInstances() {
870 return this.statementInterceptors;
871 }
872
873 private void addToHistogram(int[] histogramCounts,
874 long[] histogramBreakpoints, long value, int numberOfTimes,
875 long currentLowerBound, long currentUpperBound) {
876 if (histogramCounts == null) {
877 createInitialHistogram(histogramBreakpoints,
878 currentLowerBound, currentUpperBound);
879 } else {
880 for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
881 if (histogramBreakpoints[i] >= value) {
882 histogramCounts[i] += numberOfTimes;
883
884 break;
885 }
886 }
887 }
888 }
889
890 private void addToPerformanceHistogram(long value, int numberOfTimes) {
891 checkAndCreatePerformanceHistogram();
892
893 addToHistogram(this.perfMetricsHistCounts,
894 this.perfMetricsHistBreakpoints, value, numberOfTimes,
895 this.shortestQueryTimeMs == Long.MAX_VALUE ? 0
896 : this.shortestQueryTimeMs, this.longestQueryTimeMs);
897 }
898
899 private void addToTablesAccessedHistogram(long value, int numberOfTimes) {
900 checkAndCreateTablesAccessedHistogram();
901
902 addToHistogram(this.numTablesMetricsHistCounts,
903 this.numTablesMetricsHistBreakpoints, value, numberOfTimes,
904 this.minimumNumberTablesAccessed == Long.MAX_VALUE ? 0
905 : this.minimumNumberTablesAccessed,
906 this.maximumNumberTablesAccessed);
907 }
908
909 /**
910 * Builds the map needed for 4.1.0 and newer servers that maps field-level
911 * charset/collation info to a java character encoding name.
912 *
913 * @throws SQLException
914 * DOCUMENT ME!
915 */
916 private void buildCollationMapping() throws SQLException {
917 if (versionMeetsMinimum(4, 1, 0)) {
918
919 TreeMap sortedCollationMap = null;
920
921 if (getCacheServerConfiguration()) {
922 synchronized (serverConfigByUrl) {
923 sortedCollationMap = (TreeMap) serverCollationByUrl
924 .get(getURL());
925 }
926 }
927
928 java.sql.Statement stmt = null;
929 java.sql.ResultSet results = null;
930
931 try {
932 if (sortedCollationMap == null) {
933 sortedCollationMap = new TreeMap();
934
935 stmt = getMetadataSafeStatement();
936
937 results = stmt
938 .executeQuery("SHOW COLLATION");
939
940 while (results.next()) {
941 String charsetName = results.getString(2);
942 Integer charsetIndex = Constants.integerValueOf(results.getInt(3));
943
944 sortedCollationMap.put(charsetIndex, charsetName);
945 }
946
947 if (getCacheServerConfiguration()) {
948 synchronized (serverConfigByUrl) {
949 serverCollationByUrl.put(getURL(),
950 sortedCollationMap);
951 }
952 }
953
954 }
955
956 // Now, merge with what we already know
957 int highestIndex = ((Integer) sortedCollationMap.lastKey())
958 .intValue();
959
960 if (CharsetMapping.INDEX_TO_CHARSET.length > highestIndex) {
961 highestIndex = CharsetMapping.INDEX_TO_CHARSET.length;
962 }
963
964 this.indexToCharsetMapping = new String[highestIndex + 1];
965
966 for (int i = 0; i < CharsetMapping.INDEX_TO_CHARSET.length; i++) {
967 this.indexToCharsetMapping[i] = CharsetMapping.INDEX_TO_CHARSET[i];
968 }
969
970 for (Iterator indexIter = sortedCollationMap.entrySet()
971 .iterator(); indexIter.hasNext();) {
972 Map.Entry indexEntry = (Map.Entry) indexIter.next();
973
974 String mysqlCharsetName = (String) indexEntry.getValue();
975
976 this.indexToCharsetMapping[((Integer) indexEntry.getKey())
977 .intValue()] = CharsetMapping
978 .getJavaEncodingForMysqlEncoding(mysqlCharsetName,
979 this);
980 }
981 } catch (java.sql.SQLException e) {
982 throw e;
983 } finally {
984 if (results != null) {
985 try {
986 results.close();
987 } catch (java.sql.SQLException sqlE) {
988 // ignore
989 }
990 }
991
992 if (stmt != null) {
993 try {
994 stmt.close();
995 } catch (java.sql.SQLException sqlE) {
996 // ignore
997 }
998 }
999 }
1000 } else {
1001 // Safety, we already do this as an initializer, but this makes
1002 // the intent more clear
1003 this.indexToCharsetMapping = CharsetMapping.INDEX_TO_CHARSET;
1004 }
1005 }
1006
1007 private boolean canHandleAsServerPreparedStatement(String sql)
1008 throws SQLException {
1009 if (sql == null || sql.length() == 0) {
1010 return true;
1011 }
1012
1013 if (!this.useServerPreparedStmts) {
1014 return false;
1015 }
1016
1017 if (getCachePreparedStatements()) {
1018 synchronized (this.serverSideStatementCheckCache) {
1019 Boolean flag = (Boolean)this.serverSideStatementCheckCache.get(sql);
1020
1021 if (flag != null) {
1022 return flag.booleanValue();
1023 }
1024
1025 boolean canHandle = canHandleAsServerPreparedStatementNoCache(sql);
1026
1027 if (sql.length() < getPreparedStatementCacheSqlLimit()) {
1028 this.serverSideStatementCheckCache.put(sql,
1029 canHandle ? Boolean.TRUE : Boolean.FALSE);
1030 }
1031
1032 return canHandle;
1033 }
1034 }
1035
1036 return canHandleAsServerPreparedStatementNoCache(sql);
1037 }
1038
1039 private boolean canHandleAsServerPreparedStatementNoCache(String sql)
1040 throws SQLException {
1041
1042 // Can't use server-side prepare for CALL
1043 if (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "CALL")) {
1044 return false;
1045 }
1046
1047 boolean canHandleAsStatement = true;
1048
1049 if (!versionMeetsMinimum(5, 0, 7) &&
1050 (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "SELECT")
1051 || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
1052 "DELETE")
1053 || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
1054 "INSERT")
1055 || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
1056 "UPDATE")
1057 || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
1058 "REPLACE"))) {
1059
1060 // check for limit ?[,?]
1061
1062 /*
1063 * The grammar for this (from the server) is: ULONG_NUM | ULONG_NUM
1064 * ',' ULONG_NUM | ULONG_NUM OFFSET_SYM ULONG_NUM
1065 */
1066
1067 int currentPos = 0;
1068 int statementLength = sql.length();
1069 int lastPosToLook = statementLength - 7; // "LIMIT ".length()
1070 boolean allowBackslashEscapes = !this.noBackslashEscapes;
1071 char quoteChar = this.useAnsiQuotes ? '"' : '\'';
1072 boolean foundLimitWithPlaceholder = false;
1073
1074 while (currentPos < lastPosToLook) {
1075 int limitStart = StringUtils.indexOfIgnoreCaseRespectQuotes(
1076 currentPos, sql, "LIMIT ", quoteChar,
1077 allowBackslashEscapes);
1078
1079 if (limitStart == -1) {
1080 break;
1081 }
1082
1083 currentPos = limitStart + 7;
1084
1085 while (currentPos < statementLength) {
1086 char c = sql.charAt(currentPos);
1087
1088 //
1089 // Have we reached the end
1090 // of what can be in a LIMIT clause?
1091 //
1092
1093 if (!Character.isDigit(c) && !Character.isWhitespace(c)
1094 && c != ',' && c != '?') {
1095 break;
1096 }
1097
1098 if (c == '?') {
1099 foundLimitWithPlaceholder = true;
1100 break;
1101 }
1102
1103 currentPos++;
1104 }
1105 }
1106
1107 canHandleAsStatement = !foundLimitWithPlaceholder;
1108 } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "CREATE TABLE")) {
1109 canHandleAsStatement = false;
1110 } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "DO")) {
1111 canHandleAsStatement = false;
1112 } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "SET")) {
1113 canHandleAsStatement = false;
1114 }
1115
1116
1117
1118 return canHandleAsStatement;
1119 }
1120
1121 /**
1122 * Changes the user on this connection by performing a re-authentication. If
1123 * authentication fails, the connection will remain under the context of the
1124 * current user.
1125 *
1126 * @param userName
1127 * the username to authenticate with
1128 * @param newPassword
1129 * the password to authenticate with
1130 * @throws SQLException
1131 * if authentication fails, or some other error occurs while
1132 * performing the command.
1133 */
1134 public void changeUser(String userName, String newPassword)
1135 throws SQLException {
1136 if ((userName == null) || userName.equals("")) {
1137 userName = "";
1138 }
1139
1140 if (newPassword == null) {
1141 newPassword = "";
1142 }
1143
1144 this.io.changeUser(userName, newPassword, this.database);
1145 this.user = userName;
1146 this.password = newPassword;
1147
1148 if (versionMeetsMinimum(4, 1, 0)) {
1149 configureClientCharacterSet(true);
1150 }
1151
1152 setSessionVariables();
1153
1154 setupServerForTruncationChecks();
1155 }
1156
1157 private boolean characterSetNamesMatches(String mysqlEncodingName) {
1158 // set names is equivalent to character_set_client ..._results and ..._connection,
1159 // but we set _results later, so don't check it here.
1160
1161 return (mysqlEncodingName != null &&
1162 mysqlEncodingName.equalsIgnoreCase((String)this.serverVariables.get("character_set_client")) &&
1163 mysqlEncodingName.equalsIgnoreCase((String)this.serverVariables.get("character_set_connection")));
1164 }
1165
1166 private void checkAndCreatePerformanceHistogram() {
1167 if (this.perfMetricsHistCounts == null) {
1168 this.perfMetricsHistCounts = new int[HISTOGRAM_BUCKETS];
1169 }
1170
1171 if (this.perfMetricsHistBreakpoints == null) {
1172 this.perfMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS];
1173 }
1174 }
1175
1176 private void checkAndCreateTablesAccessedHistogram() {
1177 if (this.numTablesMetricsHistCounts == null) {
1178 this.numTablesMetricsHistCounts = new int[HISTOGRAM_BUCKETS];
1179 }
1180
1181 if (this.numTablesMetricsHistBreakpoints == null) {
1182 this.numTablesMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS];
1183 }
1184 }
1185
1186 protected void checkClosed() throws SQLException {
1187 if (this.isClosed) {
1188 throwConnectionClosedException();
1189 }
1190 }
1191
1192 void throwConnectionClosedException() throws SQLException {
1193 StringBuffer messageBuf = new StringBuffer(
1194 "No operations allowed after connection closed.");
1195
1196 if (this.forcedClosedLocation != null || this.forceClosedReason != null) {
1197 messageBuf
1198 .append("Connection was implicitly closed by the driver.");
1199 }
1200
1201 SQLException ex = SQLError.createSQLException(messageBuf.toString(),
1202 SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
1203
1204 if (this.forceClosedReason != null) {
1205 ex.initCause(this.forceClosedReason);
1206 }
1207
1208 throw ex;
1209 }
1210
1211 /**
1212 * If useUnicode flag is set and explicit client character encoding isn't
1213 * specified then assign encoding from server if any.
1214 *
1215 * @throws SQLException
1216 * DOCUMENT ME!
1217 */
1218 private void checkServerEncoding() throws SQLException {
1219 if (getUseUnicode() && (getEncoding() != null)) {
1220 // spec'd by client, don't map
1221 return;
1222 }
1223
1224 String serverEncoding = (String) this.serverVariables
1225 .get("character_set");
1226
1227 if (serverEncoding == null) {
1228 // must be 4.1.1 or newer?
1229 serverEncoding = (String) this.serverVariables
1230 .get("character_set_server");
1231 }
1232
1233 String mappedServerEncoding = null;
1234
1235 if (serverEncoding != null) {
1236 mappedServerEncoding = CharsetMapping
1237 .getJavaEncodingForMysqlEncoding(serverEncoding
1238 .toUpperCase(Locale.ENGLISH), this);
1239 }
1240
1241 //
1242 // First check if we can do the encoding ourselves
1243 //
1244 if (!getUseUnicode() && (mappedServerEncoding != null)) {
1245 SingleByteCharsetConverter converter = getCharsetConverter(mappedServerEncoding);
1246
1247 if (converter != null) { // we know how to convert this ourselves
1248 setUseUnicode(true); // force the issue
1249 setEncoding(mappedServerEncoding);
1250
1251 return;
1252 }
1253 }
1254
1255 //
1256 // Now, try and find a Java I/O converter that can do
1257 // the encoding for us
1258 //
1259 if (serverEncoding != null) {
1260 if (mappedServerEncoding == null) {
1261 // We don't have a mapping for it, so try
1262 // and canonicalize the name....
1263 if (Character.isLowerCase(serverEncoding.charAt(0))) {
1264 char[] ach = serverEncoding.toCharArray();
1265 ach[0] = Character.toUpperCase(serverEncoding.charAt(0));
1266 setEncoding(new String(ach));
1267 }
1268 }
1269
1270 if (mappedServerEncoding == null) {
1271 throw SQLError.createSQLException("Unknown character encoding on server '"
1272 + serverEncoding
1273 + "', use 'characterEncoding=' property "
1274 + " to provide correct mapping",
1275 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
1276 }
1277
1278 //
1279 // Attempt to use the encoding, and bail out if it
1280 // can't be used
1281 //
1282 try {
1283 "abc".getBytes(mappedServerEncoding);
1284 setEncoding(mappedServerEncoding);
1285 setUseUnicode(true);
1286 } catch (UnsupportedEncodingException UE) {
1287 throw SQLError.createSQLException(
1288 "The driver can not map the character encoding '"
1289 + getEncoding()
1290 + "' that your server is using "
1291 + "to a character encoding your JVM understands. You "
1292 + "can specify this mapping manually by adding \"useUnicode=true\" "
1293 + "as well as \"characterEncoding=[an_encoding_your_jvm_understands]\" "
1294 + "to your JDBC URL.", "0S100", getExceptionInterceptor());
1295 }
1296 }
1297 }
1298
1299 /**
1300 * Set transaction isolation level to the value received from server if any.
1301 * Is called by connectionInit(...)
1302 *
1303 * @throws SQLException
1304 * DOCUMENT ME!
1305 */
1306 private void checkTransactionIsolationLevel() throws SQLException {
1307 String txIsolationName = null;
1308
1309 if (versionMeetsMinimum(4, 0, 3)) {
1310 txIsolationName = "tx_isolation";
1311 } else {
1312 txIsolationName = "transaction_isolation";
1313 }
1314
1315 String s = (String) this.serverVariables.get(txIsolationName);
1316
1317 if (s != null) {
1318 Integer intTI = (Integer) mapTransIsolationNameToValue.get(s);
1319
1320 if (intTI != null) {
1321 this.isolationLevel = intTI.intValue();
1322 }
1323 }
1324 }
1325
1326 /**
1327 * Clobbers the physical network connection and marks
1328 * this connection as closed.
1329 *
1330 * @throws SQLException
1331 */
1332 protected void abortInternal() throws SQLException {
1333 if (this.io != null) {
1334 try {
1335 this.io.forceClose();
1336 } catch (Throwable t) {
1337 // can't do anything about it, and we're forcibly aborting
1338 }
1339 this.io = null;
1340 }
1341
1342 this.isClosed = true;
1343 }
1344
1345 /**
1346 * Destroys this connection and any underlying resources
1347 *
1348 * @param fromWhere
1349 * DOCUMENT ME!
1350 * @param whyCleanedUp
1351 * DOCUMENT ME!
1352 */
1353 private void cleanup(Throwable whyCleanedUp) {
1354 try {
1355 if ((this.io != null) && !isClosed()) {
1356 realClose(false, false, false, whyCleanedUp);
1357 } else if (this.io != null) {
1358 this.io.forceClose();
1359 }
1360 } catch (SQLException sqlEx) {
1361 // ignore, we're going away.
1362 ;
1363 }
1364
1365 this.isClosed = true;
1366 }
1367
1368 public void clearHasTriedMaster() {
1369 this.hasTriedMasterFlag = false;
1370 }
1371
1372 /**
1373 * After this call, getWarnings returns null until a new warning is reported
1374 * for this connection.
1375 *
1376 * @exception SQLException
1377 * if a database access error occurs
1378 */
1379 public void clearWarnings() throws SQLException {
1380 // firstWarning = null;
1381 }
1382
1383 /**
1384 * DOCUMENT ME!
1385 *
1386 * @param sql
1387 * DOCUMENT ME!
1388 * @return DOCUMENT ME!
1389 * @throws SQLException
1390 * DOCUMENT ME!
1391 */
1392 public java.sql.PreparedStatement clientPrepareStatement(String sql)
1393 throws SQLException {
1394 return clientPrepareStatement(sql,
1395 DEFAULT_RESULT_SET_TYPE,
1396 DEFAULT_RESULT_SET_CONCURRENCY);
1397 }
1398
1399 /**
1400 * @see Connection#prepareStatement(String, int)
1401 */
1402 public java.sql.PreparedStatement clientPrepareStatement(String sql,
1403 int autoGenKeyIndex) throws SQLException {
1404 java.sql.PreparedStatement pStmt = clientPrepareStatement(sql);
1405
1406 ((com.mysql.jdbc.PreparedStatement) pStmt)
1407 .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);
1408
1409 return pStmt;
1410 }
1411
1412 /**
1413 * DOCUMENT ME!
1414 *
1415 * @param sql
1416 * DOCUMENT ME!
1417 * @param resultSetType
1418 * DOCUMENT ME!
1419 * @param resultSetConcurrency
1420 * DOCUMENT ME!
1421 * @return DOCUMENT ME!
1422 * @throws SQLException
1423 * DOCUMENT ME!
1424 */
1425 public java.sql.PreparedStatement clientPrepareStatement(String sql,
1426 int resultSetType, int resultSetConcurrency) throws SQLException {
1427 return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true);
1428 }
1429
1430
1431
1432 protected java.sql.PreparedStatement clientPrepareStatement(String sql,
1433 int resultSetType, int resultSetConcurrency,
1434 boolean processEscapeCodesIfNeeded) throws SQLException {
1435 checkClosed();
1436
1437 String nativeSql = processEscapeCodesIfNeeded && getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
1438
1439 PreparedStatement pStmt = null;
1440
1441 if (getCachePreparedStatements()) {
1442 synchronized (this.cachedPreparedStatementParams) {
1443 PreparedStatement.ParseInfo pStmtInfo = (PreparedStatement.ParseInfo) this.cachedPreparedStatementParams
1444 .get(nativeSql);
1445
1446 if (pStmtInfo == null) {
1447 pStmt = com.mysql.jdbc.PreparedStatement.getInstance(this, nativeSql,
1448 this.database);
1449
1450 PreparedStatement.ParseInfo parseInfo = pStmt.getParseInfo();
1451
1452 if (parseInfo.statementLength < getPreparedStatementCacheSqlLimit()) {
1453 if (this.cachedPreparedStatementParams.size() >= getPreparedStatementCacheSize()) {
1454 Iterator oldestIter = this.cachedPreparedStatementParams
1455 .keySet().iterator();
1456 long lruTime = Long.MAX_VALUE;
1457 String oldestSql = null;
1458
1459 while (oldestIter.hasNext()) {
1460 String sqlKey = (String) oldestIter.next();
1461 PreparedStatement.ParseInfo lruInfo = (PreparedStatement.ParseInfo) this.cachedPreparedStatementParams
1462 .get(sqlKey);
1463
1464 if (lruInfo.lastUsed < lruTime) {
1465 lruTime = lruInfo.lastUsed;
1466 oldestSql = sqlKey;
1467 }
1468 }
1469
1470 if (oldestSql != null) {
1471 this.cachedPreparedStatementParams
1472 .remove(oldestSql);
1473 }
1474 }
1475
1476 this.cachedPreparedStatementParams.put(nativeSql, pStmt
1477 .getParseInfo());
1478 }
1479 } else {
1480 pStmtInfo.lastUsed = System.currentTimeMillis();
1481 pStmt = new com.mysql.jdbc.PreparedStatement(this, nativeSql,
1482 this.database, pStmtInfo);
1483 }
1484 }
1485 } else {
1486 pStmt = com.mysql.jdbc.PreparedStatement.getInstance(this, nativeSql,
1487 this.database);
1488 }
1489
1490 pStmt.setResultSetType(resultSetType);
1491 pStmt.setResultSetConcurrency(resultSetConcurrency);
1492
1493 return pStmt;
1494 }
1495
1496 /**
1497 * @see java.sql.Connection#prepareStatement(String, int[])
1498 */
1499 public java.sql.PreparedStatement clientPrepareStatement(String sql,
1500 int[] autoGenKeyIndexes) throws SQLException {
1501
1502 PreparedStatement pStmt = (PreparedStatement) clientPrepareStatement(sql);
1503
1504 pStmt
1505 .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
1506 && (autoGenKeyIndexes.length > 0));
1507
1508 return pStmt;
1509 }
1510
1511 /**
1512 * @see java.sql.Connection#prepareStatement(String, String[])
1513 */
1514 public java.sql.PreparedStatement clientPrepareStatement(String sql,
1515 String[] autoGenKeyColNames) throws SQLException {
1516 PreparedStatement pStmt = (PreparedStatement) clientPrepareStatement(sql);
1517
1518 pStmt
1519 .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
1520 && (autoGenKeyColNames.length > 0));
1521
1522 return pStmt;
1523 }
1524
1525 public java.sql.PreparedStatement clientPrepareStatement(String sql,
1526 int resultSetType, int resultSetConcurrency,
1527 int resultSetHoldability) throws SQLException {
1528 return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true);
1529 }
1530
1531 // --------------------------JDBC 2.0-----------------------------
1532
1533 /**
1534 * In some cases, it is desirable to immediately release a Connection's
1535 * database and JDBC resources instead of waiting for them to be
1536 * automatically released (cant think why off the top of my head) <B>Note:</B>
1537 * A Connection is automatically closed when it is garbage collected.
1538 * Certain fatal errors also result in a closed connection.
1539 *
1540 * @exception SQLException
1541 * if a database access error occurs
1542 */
1543 public synchronized void close() throws SQLException {
1544 if (this.connectionLifecycleInterceptors != null) {
1545 new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
1546 void forEach(Object each) throws SQLException {
1547 ((ConnectionLifecycleInterceptor)each).close();
1548 }
1549 }.doForAll();
1550 }
1551
1552 realClose(true, true, false, null);
1553 }
1554
1555 /**
1556 * Closes all currently open statements.
1557 *
1558 * @throws SQLException
1559 * DOCUMENT ME!
1560 */
1561 private void closeAllOpenStatements() throws SQLException {
1562 SQLException postponedException = null;
1563
1564 if (this.openStatements != null) {
1565 List currentlyOpenStatements = new ArrayList(); // we need this to
1566 // avoid
1567 // ConcurrentModificationEx
1568
1569 for (Iterator iter = this.openStatements.keySet().iterator(); iter
1570 .hasNext();) {
1571 currentlyOpenStatements.add(iter.next());
1572 }
1573
1574 int numStmts = currentlyOpenStatements.size();
1575
1576 for (int i = 0; i < numStmts; i++) {
1577 StatementImpl stmt = (StatementImpl) currentlyOpenStatements.get(i);
1578
1579 try {
1580 stmt.realClose(false, true);
1581 } catch (SQLException sqlEx) {
1582 postponedException = sqlEx; // throw it later, cleanup all
1583 // statements first
1584 }
1585 }
1586
1587 if (postponedException != null) {
1588 throw postponedException;
1589 }
1590 }
1591 }
1592
1593 private void closeStatement(java.sql.Statement stmt) {
1594 if (stmt != null) {
1595 try {
1596 stmt.close();
1597 } catch (SQLException sqlEx) {
1598 ; // ignore
1599 }
1600
1601 stmt = null;
1602 }
1603 }
1604
1605 /**
1606 * The method commit() makes all changes made since the previous
1607 * commit/rollback permanent and releases any database locks currently held
1608 * by the Connection. This method should only be used when auto-commit has
1609 * been disabled.
1610 * <p>
1611 * <b>Note:</b> MySQL does not support transactions, so this method is a
1612 * no-op.
1613 * </p>
1614 *
1615 * @exception SQLException
1616 * if a database access error occurs
1617 * @see setAutoCommit
1618 */
1619 public void commit() throws SQLException {
1620 synchronized (getMutex()) {
1621 checkClosed();
1622
1623 try {
1624 if (this.connectionLifecycleInterceptors != null) {
1625 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
1626
1627 void forEach(Object each) throws SQLException {
1628 if (!((ConnectionLifecycleInterceptor)each).commit()) {
1629 this.stopIterating = true;
1630 }
1631 }
1632 };
1633
1634 iter.doForAll();
1635
1636 if (!iter.fullIteration()) {
1637 return;
1638 }
1639 }
1640
1641 // no-op if _relaxAutoCommit == true
1642 if (this.autoCommit && !getRelaxAutoCommit()) {
1643 throw SQLError.createSQLException("Can't call commit when autocommit=true", getExceptionInterceptor());
1644 } else if (this.transactionsSupported) {
1645 if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) {
1646 if (!this.io.inTransactionOnServer()) {
1647 return; // effectively a no-op
1648 }
1649 }
1650
1651 execSQL(null, "commit", -1, null,
1652 DEFAULT_RESULT_SET_TYPE,
1653 DEFAULT_RESULT_SET_CONCURRENCY, false,
1654 this.database, null,
1655 false);
1656 }
1657 } catch (SQLException sqlException) {
1658 if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
1659 .equals(sqlException.getSQLState())) {
1660 throw SQLError.createSQLException(
1661 "Communications link failure during commit(). Transaction resolution unknown.",
1662 SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor());
1663 }
1664
1665 throw sqlException;
1666 } finally {
1667 this.needsPing = this.getReconnectAtTxEnd();
1668 }
1669
1670 return;
1671 }
1672 }
1673
1674 /**
1675 * Configures client-side properties for character set information.
1676 *
1677 * @throws SQLException
1678 * if unable to configure the specified character set.
1679 */
1680 private void configureCharsetProperties() throws SQLException {
1681 if (getEncoding() != null) {
1682 // Attempt to use the encoding, and bail out if it
1683 // can't be used
1684 try {
1685 String testString = "abc";
1686 testString.getBytes(getEncoding());
1687 } catch (UnsupportedEncodingException UE) {
1688 // Try the MySQL character encoding, then....
1689 String oldEncoding = getEncoding();
1690
1691 setEncoding(CharsetMapping.getJavaEncodingForMysqlEncoding(
1692 oldEncoding, this));
1693
1694 if (getEncoding() == null) {
1695 throw SQLError.createSQLException(
1696 "Java does not support the MySQL character encoding "
1697 + " " + "encoding '" + oldEncoding + "'.",
1698 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
1699 }
1700
1701 try {
1702 String testString = "abc";
1703 testString.getBytes(getEncoding());
1704 } catch (UnsupportedEncodingException encodingEx) {
1705 throw SQLError.createSQLException("Unsupported character "
1706 + "encoding '" + getEncoding() + "'.",
1707 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
1708 }
1709 }
1710 }
1711 }
1712
1713 /**
1714 * Sets up client character set for MySQL-4.1 and newer if the user This
1715 * must be done before any further communication with the server!
1716 *
1717 * @return true if this routine actually configured the client character
1718 * set, or false if the driver needs to use 'older' methods to
1719 * detect the character set, as it is connected to a MySQL server
1720 * older than 4.1.0
1721 * @throws SQLException
1722 * if an exception happens while sending 'SET NAMES' to the
1723 * server, or the server sends character set information that
1724 * the client doesn't know about.
1725 */
1726 private boolean configureClientCharacterSet(boolean dontCheckServerMatch) throws SQLException {
1727 String realJavaEncoding = getEncoding();
1728 boolean characterSetAlreadyConfigured = false;
1729
1730 try {
1731 if (versionMeetsMinimum(4, 1, 0)) {
1732 characterSetAlreadyConfigured = true;
1733
1734 setUseUnicode(true);
1735
1736 configureCharsetProperties();
1737 realJavaEncoding = getEncoding(); // we need to do this again
1738 // to grab this for
1739 // versions > 4.1.0
1740
1741 try {
1742
1743 // Fault injection for testing server character set indices
1744
1745 if (props != null && props.getProperty("com.mysql.jdbc.faultInjection.serverCharsetIndex") != null) {
1746 this.io.serverCharsetIndex = Integer.parseInt(
1747 props.getProperty(
1748 "com.mysql.jdbc.faultInjection.serverCharsetIndex"));
1749 }
1750
1751 String serverEncodingToSet =
1752 CharsetMapping.INDEX_TO_CHARSET[this.io.serverCharsetIndex];
1753
1754 if (serverEncodingToSet == null || serverEncodingToSet.length() == 0) {
1755 if (realJavaEncoding != null) {
1756 // user knows best, try it
1757 setEncoding(realJavaEncoding);
1758 } else {
1759 throw SQLError.createSQLException(
1760 "Unknown initial character set index '"
1761 + this.io.serverCharsetIndex
1762 + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.",
1763 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
1764 }
1765 }
1766
1767 // "latin1" on MySQL-4.1.0+ is actually CP1252, not ISO8859_1
1768 if (versionMeetsMinimum(4, 1, 0) &&
1769 "ISO8859_1".equalsIgnoreCase(serverEncodingToSet)) {
1770 serverEncodingToSet = "Cp1252";
1771 }
1772
1773 setEncoding(serverEncodingToSet);
1774
1775 } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) {
1776 if (realJavaEncoding != null) {
1777 // user knows best, try it
1778 setEncoding(realJavaEncoding);
1779 } else {
1780 throw SQLError.createSQLException(
1781 "Unknown initial character set index '"
1782 + this.io.serverCharsetIndex
1783 + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.",
1784 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
1785 }
1786 }
1787
1788 if (getEncoding() == null) {
1789 // punt?
1790 setEncoding("ISO8859_1");
1791 }
1792
1793 //
1794 // Has the user has 'forced' the character encoding via
1795 // driver properties?
1796 //
1797 if (getUseUnicode()) {
1798 if (realJavaEncoding != null) {
1799
1800 //
1801 // Now, inform the server what character set we
1802 // will be using from now-on...
1803 //
1804 if (realJavaEncoding.equalsIgnoreCase("UTF-8")
1805 || realJavaEncoding.equalsIgnoreCase("UTF8")) {
1806 // charset names are case-sensitive
1807
1808 if (!getUseOldUTF8Behavior()) {
1809 if (dontCheckServerMatch || !characterSetNamesMatches("utf8")) {
1810 execSQL(null, "SET NAMES utf8", -1, null,
1811 DEFAULT_RESULT_SET_TYPE,
1812 DEFAULT_RESULT_SET_CONCURRENCY,
1813 false, this.database, null, false);
1814 }
1815 }
1816
1817 setEncoding(realJavaEncoding);
1818 } /* not utf-8 */else {
1819 String mysqlEncodingName = CharsetMapping
1820 .getMysqlEncodingForJavaEncoding(
1821 realJavaEncoding
1822 .toUpperCase(Locale.ENGLISH),
1823 this);
1824
1825 /*
1826 * if ("koi8_ru".equals(mysqlEncodingName)) { //
1827 * This has a _different_ name in 4.1...
1828 * mysqlEncodingName = "ko18r"; } else if
1829 * ("euc_kr".equals(mysqlEncodingName)) { //
1830 * Different name in 4.1 mysqlEncodingName =
1831 * "euckr"; }
1832 */
1833
1834 if (mysqlEncodingName != null) {
1835
1836 if (dontCheckServerMatch || !characterSetNamesMatches(mysqlEncodingName)) {
1837 execSQL(null, "SET NAMES " + mysqlEncodingName,
1838 -1, null,
1839 DEFAULT_RESULT_SET_TYPE,
1840 DEFAULT_RESULT_SET_CONCURRENCY,
1841 false, this.database, null, false);
1842 }
1843 }
1844
1845 // Switch driver's encoding now, since the server
1846 // knows what we're sending...
1847 //
1848 setEncoding(realJavaEncoding);
1849 }
1850 } else if (getEncoding() != null) {
1851 // Tell the server we'll use the server default charset
1852 // to send our
1853 // queries from now on....
1854 String mysqlEncodingName = CharsetMapping
1855 .getMysqlEncodingForJavaEncoding(getEncoding()
1856 .toUpperCase(Locale.ENGLISH), this);
1857
1858 if (dontCheckServerMatch || !characterSetNamesMatches(mysqlEncodingName)) {
1859 execSQL(null, "SET NAMES " + mysqlEncodingName, -1,
1860 null, DEFAULT_RESULT_SET_TYPE,
1861 DEFAULT_RESULT_SET_CONCURRENCY, false,
1862 this.database, null, false);
1863 }
1864
1865 realJavaEncoding = getEncoding();
1866 }
1867
1868 }
1869
1870 //
1871 // We know how to deal with any charset coming back from
1872 // the database, so tell the server not to do conversion
1873 // if the user hasn't 'forced' a result-set character set
1874 //
1875
1876 String onServer = null;
1877 boolean isNullOnServer = false;
1878
1879 if (this.serverVariables != null) {
1880 onServer = (String)this.serverVariables.get("character_set_results");
1881
1882 isNullOnServer = onServer == null || "NULL".equalsIgnoreCase(onServer) || onServer.length() == 0;
1883 }
1884
1885 if (getCharacterSetResults() == null) {
1886
1887 //
1888 // Only send if needed, if we're caching server variables
1889 // we -have- to send, because we don't know what it was
1890 // before we cached them.
1891 //
1892 if (!isNullOnServer) {
1893 execSQL(null, "SET character_set_results = NULL", -1, null,
1894 DEFAULT_RESULT_SET_TYPE,
1895 DEFAULT_RESULT_SET_CONCURRENCY, false,
1896 this.database, null,
1897 false);
1898 if (!this.usingCachedConfig) {
1899 this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, null);
1900 }
1901 } else {
1902 if (!this.usingCachedConfig) {
1903 this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, onServer);
1904 }
1905 }
1906 } else {
1907 String charsetResults = getCharacterSetResults();
1908 String mysqlEncodingName = null;
1909
1910 if ("UTF-8".equalsIgnoreCase(charsetResults)
1911 || "UTF8".equalsIgnoreCase(charsetResults)) {
1912 mysqlEncodingName = "utf8";
1913 } else {
1914 mysqlEncodingName = CharsetMapping
1915 .getMysqlEncodingForJavaEncoding(charsetResults
1916 .toUpperCase(Locale.ENGLISH), this);
1917 }
1918
1919 //
1920 // Only change the value if needed
1921 //
1922
1923 if (!mysqlEncodingName.equalsIgnoreCase(
1924 (String)this.serverVariables.get("character_set_results"))) {
1925 StringBuffer setBuf = new StringBuffer(
1926 "SET character_set_results = ".length()
1927 + mysqlEncodingName.length());
1928 setBuf.append("SET character_set_results = ").append(
1929 mysqlEncodingName);
1930
1931 execSQL(null, setBuf.toString(), -1, null,
1932 DEFAULT_RESULT_SET_TYPE,
1933 DEFAULT_RESULT_SET_CONCURRENCY, false,
1934 this.database, null, false);
1935
1936 if (!this.usingCachedConfig) {
1937 this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS,
1938 mysqlEncodingName);
1939 }
1940 } else {
1941 if (!this.usingCachedConfig) {
1942 this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, onServer);
1943 }
1944 }
1945 }
1946
1947 if (getConnectionCollation() != null) {
1948 StringBuffer setBuf = new StringBuffer(
1949 "SET collation_connection = ".length()
1950 + getConnectionCollation().length());
1951 setBuf.append("SET collation_connection = ").append(
1952 getConnectionCollation());
1953
1954 execSQL(null, setBuf.toString(), -1, null,
1955 DEFAULT_RESULT_SET_TYPE,
1956 DEFAULT_RESULT_SET_CONCURRENCY, false,
1957 this.database, null, false);
1958 }
1959 } else {
1960 // Use what the server has specified
1961 realJavaEncoding = getEncoding(); // so we don't get
1962 // swapped out in the finally
1963 // block....
1964 }
1965 } finally {
1966 // Failsafe, make sure that the driver's notion of character
1967 // encoding matches what the user has specified.
1968 //
1969 setEncoding(realJavaEncoding);
1970 }
1971
1972 /**
1973 * Check if we need a CharsetEncoder for escaping codepoints that are
1974 * transformed to backslash (0x5c) in the connection encoding.
1975 */
1976 try {
1977 CharsetEncoder enc = Charset.forName(getEncoding()).newEncoder();
1978 CharBuffer cbuf = CharBuffer.allocate(1);
1979 ByteBuffer bbuf = ByteBuffer.allocate(1);
1980
1981 cbuf.put("\u00a5");
1982 cbuf.position(0);
1983 enc.encode(cbuf, bbuf, true);
1984 if(bbuf.get(0) == '\\') {
1985 requiresEscapingEncoder = true;
1986 } else {
1987 cbuf.clear();
1988 bbuf.clear();
1989
1990 cbuf.put("\u20a9");
1991 cbuf.position(0);
1992 enc.encode(cbuf, bbuf, true);
1993 if(bbuf.get(0) == '\\') {
1994 requiresEscapingEncoder = true;
1995 }
1996 }
1997 } catch(java.nio.charset.UnsupportedCharsetException ucex) {
1998 // fallback to String API - for Java 1.4
1999 try {
2000 byte bbuf[] = new String("\u00a5").getBytes(getEncoding());
2001 if (bbuf[0] == '\\') {
2002 requiresEscapingEncoder = true;
2003 } else {
2004 bbuf = new String("\u20a9").getBytes(getEncoding());
2005 if (bbuf[0] == '\\') {
2006 requiresEscapingEncoder = true;
2007 }
2008 }
2009 } catch(UnsupportedEncodingException ueex) {
2010 throw SQLError.createSQLException("Unable to use encoding: " + getEncoding(),
2011 SQLError.SQL_STATE_GENERAL_ERROR, ueex,
2012 getExceptionInterceptor());
2013 }
2014 }
2015
2016 return characterSetAlreadyConfigured;
2017 }
2018
2019 /**
2020 * Configures the client's timezone if required.
2021 *
2022 * @throws SQLException
2023 * if the timezone the server is configured to use can't be
2024 * mapped to a Java timezone.
2025 */
2026 private void configureTimezone() throws SQLException {
2027 String configuredTimeZoneOnServer = (String) this.serverVariables
2028 .get("timezone");
2029
2030 if (configuredTimeZoneOnServer == null) {
2031 configuredTimeZoneOnServer = (String) this.serverVariables
2032 .get("time_zone");
2033
2034 if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
2035 configuredTimeZoneOnServer = (String) this.serverVariables
2036 .get("system_time_zone");
2037 }
2038 }
2039
2040 String canoncicalTimezone = getServerTimezone();
2041
2042 if ((getUseTimezone() || !getUseLegacyDatetimeCode()) && configuredTimeZoneOnServer != null) {
2043 // user can override this with driver properties, so don't detect if that's the case
2044 if (canoncicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canoncicalTimezone)) {
2045 try {
2046 canoncicalTimezone = TimeUtil
2047 .getCanoncialTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());
2048
2049 if (canoncicalTimezone == null) {
2050 throw SQLError.createSQLException("Can't map timezone '"
2051 + configuredTimeZoneOnServer + "' to "
2052 + " canonical timezone.",
2053 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
2054 }
2055 } catch (IllegalArgumentException iae) {
2056 throw SQLError.createSQLException(iae.getMessage(),
2057 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
2058 }
2059 }
2060 } else {
2061 canoncicalTimezone = getServerTimezone();
2062 }
2063
2064 if (canoncicalTimezone != null && canoncicalTimezone.length() > 0) {
2065 this.serverTimezoneTZ = TimeZone.getTimeZone(canoncicalTimezone);
2066
2067 //
2068 // The Calendar class has the behavior of mapping
2069 // unknown timezones to 'GMT' instead of throwing an
2070 // exception, so we must check for this...
2071 //
2072 if (!canoncicalTimezone.equalsIgnoreCase("GMT")
2073 && this.serverTimezoneTZ.getID().equals("GMT")) {
2074 throw SQLError.createSQLException("No timezone mapping entry for '"
2075 + canoncicalTimezone + "'",
2076 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
2077 }
2078
2079 if ("GMT".equalsIgnoreCase(this.serverTimezoneTZ.getID())) {
2080 this.isServerTzUTC = true;
2081 } else {
2082 this.isServerTzUTC = false;
2083 }
2084 }
2085 }
2086
2087 private void createInitialHistogram(long[] breakpoints,
2088 long lowerBound, long upperBound) {
2089
2090 double bucketSize = (((double) upperBound - (double) lowerBound) / HISTOGRAM_BUCKETS) * 1.25;
2091
2092 if (bucketSize < 1) {
2093 bucketSize = 1;
2094 }
2095
2096 for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
2097 breakpoints[i] = lowerBound;
2098 lowerBound += bucketSize;
2099 }
2100 }
2101
2102 /**
2103 * Creates an IO channel to the server
2104 *
2105 * @param isForReconnect
2106 * is this request for a re-connect
2107 * @return a new MysqlIO instance connected to a server
2108 * @throws SQLException
2109 * if a database access error occurs
2110 * @throws CommunicationsException
2111 * DOCUMENT ME!
2112 */
2113 protected void createNewIO(boolean isForReconnect)
2114 throws SQLException {
2115 // Synchronization Not needed for *new* connections, but defintely for
2116 // connections going through fail-over, since we might get the
2117 // new connection up and running *enough* to start sending
2118 // cached or still-open server-side prepared statements over
2119 // to the backend before we get a chance to re-prepare them...
2120
2121 synchronized (this.mutex) {
2122 Properties mergedProps = exposeAsProperties(this.props);
2123
2124 long queriesIssuedFailedOverCopy = this.queriesIssuedFailedOver;
2125 this.queriesIssuedFailedOver = 0;
2126
2127 try {
2128 if (!getHighAvailability() && !this.failedOver) {
2129 boolean connectionGood = false;
2130 Exception connectionNotEstablishedBecause = null;
2131
2132 int hostIndex = 0;
2133
2134 //
2135 // TODO: Eventually, when there's enough metadata
2136 // on the server to support it, we should come up
2137 // with a smarter way to pick what server to connect
2138 // to...perhaps even making it 'pluggable'
2139 //
2140 if (getRoundRobinLoadBalance()) {
2141 hostIndex = getNextRoundRobinHostIndex(getURL(),
2142 this.hostList);
2143 }
2144
2145 for (; hostIndex < this.hostListSize; hostIndex++) {
2146
2147 if (hostIndex == 0) {
2148 this.hasTriedMasterFlag = true;
2149 }
2150
2151 try {
2152 String newHostPortPair = (String) this.hostList
2153 .get(hostIndex);
2154
2155 int newPort = 3306;
2156
2157 String[] hostPortPair = NonRegisteringDriver
2158 .parseHostPortPair(newHostPortPair);
2159 String newHost = hostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];
2160
2161 if (newHost == null || StringUtils.isEmptyOrWhitespaceOnly(newHost)) {
2162 newHost = "localhost";
2163 }
2164
2165 if (hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {
2166 try {
2167 newPort = Integer
2168 .parseInt(hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);
2169 } catch (NumberFormatException nfe) {
2170 throw SQLError.createSQLException(
2171 "Illegal connection port value '"
2172 + hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]
2173 + "'",
2174 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
2175 }
2176 }
2177
2178 this.io = new MysqlIO(newHost, newPort, mergedProps,
2179 getSocketFactoryClassName(), this,
2180 getSocketTimeout(),
2181 this.largeRowSizeThreshold.getValueAsInt());
2182
2183 this.io.doHandshake(this.user, this.password,
2184 this.database);
2185 this.connectionId = this.io.getThreadId();
2186 this.isClosed = false;
2187
2188 // save state from old connection
2189 boolean oldAutoCommit = getAutoCommit();
2190 int oldIsolationLevel = this.isolationLevel;
2191 boolean oldReadOnly = isReadOnly();
2192 String oldCatalog = getCatalog();
2193
2194 this.io.setStatementInterceptors(this.statementInterceptors);
2195
2196 // Server properties might be different
2197 // from previous connection, so initialize
2198 // again...
2199 initializePropsFromServer();
2200
2201 if (isForReconnect) {
2202 // Restore state from old connection
2203 setAutoCommit(oldAutoCommit);
2204
2205 if (this.hasIsolationLevels) {
2206 setTransactionIsolation(oldIsolationLevel);
2207 }
2208
2209 setCatalog(oldCatalog);
2210 }
2211
2212 if (hostIndex != 0) {
2213 setFailedOverState();
2214 queriesIssuedFailedOverCopy = 0;
2215 } else {
2216 this.failedOver = false;
2217 queriesIssuedFailedOverCopy = 0;
2218
2219 if (this.hostListSize > 1) {
2220 setReadOnlyInternal(false);
2221 } else {
2222 setReadOnlyInternal(oldReadOnly);
2223 }
2224 }
2225
2226 connectionGood = true;
2227
2228 break; // low-level connection succeeded
2229 } catch (Exception EEE) {
2230 if (this.io != null) {
2231 this.io.forceClose();
2232 }
2233
2234 connectionNotEstablishedBecause = EEE;
2235
2236 connectionGood = false;
2237
2238 if (EEE instanceof SQLException) {
2239 SQLException sqlEx = (SQLException)EEE;
2240
2241 String sqlState = sqlEx.getSQLState();
2242
2243 // If this isn't a communications failure, it will probably never succeed, so
2244 // give up right here and now ....
2245 if ((sqlState == null)
2246 || !sqlState
2247 .equals(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)) {
2248 throw sqlEx;
2249 }
2250 }
2251
2252 // Check next host, it might be up...
2253 if (getRoundRobinLoadBalance()) {
2254 hostIndex = getNextRoundRobinHostIndex(getURL(),
2255 this.hostList) - 1 /* incremented by for loop next time around */;
2256 } else if ((this.hostListSize - 1) == hostIndex) {
2257 throw SQLError.createCommunicationsException(this,
2258 (this.io != null) ? this.io
2259 .getLastPacketSentTimeMs() : 0,
2260 (this.io != null) ? this.io
2261 .getLastPacketReceivedTimeMs() : 0,
2262 EEE, getExceptionInterceptor());
2263 }
2264 }
2265 }
2266
2267 if (!connectionGood) {
2268 // We've really failed!
2269 SQLException chainedEx = SQLError.createSQLException(
2270 Messages.getString("Connection.UnableToConnect"),
2271 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
2272 chainedEx.initCause(connectionNotEstablishedBecause);
2273
2274 throw chainedEx;
2275 }
2276 } else {
2277 double timeout = getInitialTimeout();
2278 boolean connectionGood = false;
2279
2280 Exception connectionException = null;
2281
2282 int hostIndex = 0;
2283
2284 if (getRoundRobinLoadBalance()) {
2285 hostIndex = getNextRoundRobinHostIndex(getURL(),
2286 this.hostList);
2287 }
2288
2289 for (; (hostIndex < this.hostListSize) && !connectionGood; hostIndex++) {
2290 if (hostIndex == 0) {
2291 this.hasTriedMasterFlag = true;
2292 }
2293
2294 if (this.preferSlaveDuringFailover && hostIndex == 0) {
2295 hostIndex++;
2296 }
2297
2298 for (int attemptCount = 0; (attemptCount < getMaxReconnects())
2299 && !connectionGood; attemptCount++) {
2300 try {
2301 if (this.io != null) {
2302 this.io.forceClose();
2303 }
2304
2305 String newHostPortPair = (String) this.hostList
2306 .get(hostIndex);
2307
2308 int newPort = 3306;
2309
2310 String[] hostPortPair = NonRegisteringDriver
2311 .parseHostPortPair(newHostPortPair);
2312 String newHost = hostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];
2313
2314 if (newHost == null || StringUtils.isEmptyOrWhitespaceOnly(newHost)) {
2315 newHost = "localhost";
2316 }
2317
2318 if (hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {
2319 try {
2320 newPort = Integer
2321 .parseInt(hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);
2322 } catch (NumberFormatException nfe) {
2323 throw SQLError.createSQLException(
2324 "Illegal connection port value '"
2325 + hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]
2326 + "'",
2327 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
2328 }
2329 }
2330
2331 this.io = new MysqlIO(newHost, newPort,
2332 mergedProps, getSocketFactoryClassName(),
2333 this, getSocketTimeout(),
2334 this.largeRowSizeThreshold.getValueAsInt());
2335 this.io.doHandshake(this.user, this.password,
2336 this.database);
2337 pingInternal(false, 0);
2338 this.connectionId = this.io.getThreadId();
2339 this.isClosed = false;
2340
2341 // save state from old connection
2342 boolean oldAutoCommit = getAutoCommit();
2343 int oldIsolationLevel = this.isolationLevel;
2344 boolean oldReadOnly = isReadOnly();
2345 String oldCatalog = getCatalog();
2346
2347 this.io.setStatementInterceptors(this.statementInterceptors);
2348
2349 // Server properties might be different
2350 // from previous connection, so initialize
2351 // again...
2352 initializePropsFromServer();
2353
2354 if (isForReconnect) {
2355 // Restore state from old connection
2356 setAutoCommit(oldAutoCommit);
2357
2358 if (this.hasIsolationLevels) {
2359 setTransactionIsolation(oldIsolationLevel);
2360 }
2361
2362 setCatalog(oldCatalog);
2363 }
2364
2365 connectionGood = true;
2366
2367 if (hostIndex != 0) {
2368 setFailedOverState();
2369 queriesIssuedFailedOverCopy = 0;
2370 } else {
2371 this.failedOver = false;
2372 queriesIssuedFailedOverCopy = 0;
2373
2374 if (this.hostListSize > 1) {
2375 setReadOnlyInternal(false);
2376 } else {
2377 setReadOnlyInternal(oldReadOnly);
2378 }
2379 }
2380
2381 break;
2382 } catch (Exception EEE) {
2383 connectionException = EEE;
2384 connectionGood = false;
2385
2386 // Check next host, it might be up...
2387 if (getRoundRobinLoadBalance()) {
2388 hostIndex = getNextRoundRobinHostIndex(getURL(),
2389 this.hostList) - 1 /* incremented by for loop next time around */;
2390 }
2391 }
2392
2393 if (connectionGood) {
2394 break;
2395 }
2396
2397 if (attemptCount > 0) {
2398 try {
2399 Thread.sleep((long) timeout * 1000);
2400 } catch (InterruptedException IE) {
2401 // ignore
2402 }
2403 }
2404 } // end attempts for a single host
2405 } // end iterator for list of hosts
2406
2407 if (!connectionGood) {
2408 // We've really failed!
2409 SQLException chainedEx = SQLError.createSQLException(
2410 Messages.getString("Connection.UnableToConnectWithRetries",
2411 new Object[] {new Integer(getMaxReconnects())}),
2412 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());
2413 chainedEx.initCause(connectionException);
2414
2415 throw chainedEx;
2416 }
2417 }
2418
2419 if (getParanoid() && !getHighAvailability()
2420 && (this.hostListSize <= 1)) {
2421 this.password = null;
2422 this.user = null;
2423 }
2424
2425 if (isForReconnect) {
2426 //
2427 // Retrieve any 'lost' prepared statements if re-connecting
2428 //
2429 Iterator statementIter = this.openStatements.values()
2430 .iterator();
2431
2432 //
2433 // We build a list of these outside the map of open statements,
2434 // because
2435 // in the process of re-preparing, we might end up having to
2436 // close
2437 // a prepared statement, thus removing it from the map, and
2438 // generating
2439 // a ConcurrentModificationException
2440 //
2441 Stack serverPreparedStatements = null;
2442
2443 while (statementIter.hasNext()) {
2444 Object statementObj = statementIter.next();
2445
2446 if (statementObj instanceof ServerPreparedStatement) {
2447 if (serverPreparedStatements == null) {
2448 serverPreparedStatements = new Stack();
2449 }
2450
2451 serverPreparedStatements.add(statementObj);
2452 }
2453 }
2454
2455 if (serverPreparedStatements != null) {
2456 while (!serverPreparedStatements.isEmpty()) {
2457 ((ServerPreparedStatement) serverPreparedStatements
2458 .pop()).rePrepare();
2459 }
2460 }
2461 }
2462 } finally {
2463 this.queriesIssuedFailedOver = queriesIssuedFailedOverCopy;
2464 }
2465 }
2466 }
2467
2468 private void createPreparedStatementCaches() {
2469 int cacheSize = getPreparedStatementCacheSize();
2470
2471 this.cachedPreparedStatementParams = new HashMap(cacheSize);
2472
2473 if (getUseServerPreparedStmts()) {
2474 this.serverSideStatementCheckCache = new LRUCache(cacheSize);
2475
2476 this.serverSideStatementCache = new LRUCache(cacheSize) {
2477 protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
2478 if (this.maxElements <= 1) {
2479 return false;
2480 }
2481
2482 boolean removeIt = super.removeEldestEntry(eldest);
2483
2484 if (removeIt) {
2485 ServerPreparedStatement ps =
2486 (ServerPreparedStatement)eldest.getValue();
2487 ps.isCached = false;
2488 ps.setClosed(false);
2489
2490 try {
2491 ps.close();
2492 } catch (SQLException sqlEx) {
2493 // punt
2494 }
2495 }
2496
2497 return removeIt;
2498 }
2499 };
2500 }
2501 }
2502
2503 /**
2504 * SQL statements without parameters are normally executed using Statement
2505 * objects. If the same SQL statement is executed many times, it is more
2506 * efficient to use a PreparedStatement
2507 *
2508 * @return a new Statement object
2509 * @throws SQLException
2510 * passed through from the constructor
2511 */
2512 public java.sql.Statement createStatement() throws SQLException {
2513 return createStatement(DEFAULT_RESULT_SET_TYPE,
2514 DEFAULT_RESULT_SET_CONCURRENCY);
2515 }
2516
2517 /**
2518 * JDBC 2.0 Same as createStatement() above, but allows the default result
2519 * set type and result set concurrency type to be overridden.
2520 *
2521 * @param resultSetType
2522 * a result set type, see ResultSet.TYPE_XXX
2523 * @param resultSetConcurrency
2524 * a concurrency type, see ResultSet.CONCUR_XXX
2525 * @return a new Statement object
2526 * @exception SQLException
2527 * if a database-access error occurs.
2528 */
2529 public java.sql.Statement createStatement(int resultSetType,
2530 int resultSetConcurrency) throws SQLException {
2531 checkClosed();
2532
2533 StatementImpl stmt = new com.mysql.jdbc.StatementImpl(this, this.database);
2534 stmt.setResultSetType(resultSetType);
2535 stmt.setResultSetConcurrency(resultSetConcurrency);
2536
2537 return stmt;
2538 }
2539
2540 /**
2541 * @see Connection#createStatement(int, int, int)
2542 */
2543 public java.sql.Statement createStatement(int resultSetType,
2544 int resultSetConcurrency, int resultSetHoldability)
2545 throws SQLException {
2546 if (getPedantic()) {
2547 if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
2548 throw SQLError.createSQLException(
2549 "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
2550 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
2551 }
2552 }
2553
2554 return createStatement(resultSetType, resultSetConcurrency);
2555 }
2556
2557 protected void dumpTestcaseQuery(String query) {
2558 System.err.println(query);
2559 }
2560
2561 protected Connection duplicate() throws SQLException {
2562 return new ConnectionImpl( this.origHostToConnectTo,
2563 this.origPortToConnectTo,
2564 this.props,
2565 this.origDatabaseToConnectTo,
2566 this.myURL);
2567 }
2568
2569 /**
2570 * Send a query to the server. Returns one of the ResultSet objects. This is
2571 * synchronized, so Statement's queries will be serialized.
2572 *
2573 * @param callingStatement
2574 * DOCUMENT ME!
2575 * @param sql
2576 * the SQL statement to be executed
2577 * @param maxRows
2578 * DOCUMENT ME!
2579 * @param packet
2580 * DOCUMENT ME!
2581 * @param resultSetType
2582 * DOCUMENT ME!
2583 * @param resultSetConcurrency
2584 * DOCUMENT ME!
2585 * @param streamResults
2586 * DOCUMENT ME!
2587 * @param queryIsSelectOnly
2588 * DOCUMENT ME!
2589 * @param catalog
2590 * DOCUMENT ME!
2591 * @param unpackFields
2592 * DOCUMENT ME!
2593 * @return a ResultSet holding the results
2594 * @exception SQLException
2595 * if a database error occurs
2596 */
2597
2598 // ResultSet execSQL(Statement callingStatement, String sql,
2599 // int maxRowsToRetreive, String catalog) throws SQLException {
2600 // return execSQL(callingStatement, sql, maxRowsToRetreive, null,
2601 // java.sql.ResultSet.TYPE_FORWARD_ONLY,
2602 // DEFAULT_RESULT_SET_CONCURRENCY, catalog);
2603 // }
2604 // ResultSet execSQL(Statement callingStatement, String sql, int maxRows,
2605 // int resultSetType, int resultSetConcurrency, boolean streamResults,
2606 // boolean queryIsSelectOnly, String catalog, boolean unpackFields) throws
2607 // SQLException {
2608 // return execSQL(callingStatement, sql, maxRows, null, resultSetType,
2609 // resultSetConcurrency, streamResults, queryIsSelectOnly, catalog,
2610 // unpackFields);
2611 // }
2612 ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows,
2613 Buffer packet, int resultSetType, int resultSetConcurrency,
2614 boolean streamResults, String catalog,
2615 Field[] cachedMetadata) throws SQLException {
2616 return execSQL(callingStatement, sql, maxRows, packet, resultSetType,
2617 resultSetConcurrency, streamResults,
2618 catalog, cachedMetadata, false);
2619 }
2620
2621 ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows,
2622 Buffer packet, int resultSetType, int resultSetConcurrency,
2623 boolean streamResults, String catalog,
2624 Field[] cachedMetadata,
2625 boolean isBatch) throws SQLException {
2626 //
2627 // Fall-back if the master is back online if we've
2628 // issued queriesBeforeRetryMaster queries since
2629 // we failed over
2630 //
2631 synchronized (this.mutex) {
2632 long queryStartTime = 0;
2633
2634 int endOfQueryPacketPosition = 0;
2635
2636 if (packet != null) {
2637 endOfQueryPacketPosition = packet.getPosition();
2638 }
2639
2640 if (getGatherPerformanceMetrics()) {
2641 queryStartTime = System.currentTimeMillis();
2642 }
2643
2644 this.lastQueryFinishedTime = 0; // we're busy!
2645
2646 if (this.failedOver && this.autoCommit && !isBatch) {
2647 if (shouldFallBack() && !this.executingFailoverReconnect) {
2648 try {
2649 this.executingFailoverReconnect = true;
2650
2651 createNewIO(true);
2652
2653 String connectedHost = this.io.getHost();
2654
2655 if ((connectedHost != null)
2656 && this.hostList.get(0).equals(connectedHost)) {
2657 this.failedOver = false;
2658 this.queriesIssuedFailedOver = 0;
2659 setReadOnlyInternal(false);
2660 }
2661 } finally {
2662 this.executingFailoverReconnect = false;
2663 }
2664 }
2665 }
2666
2667 if ((getHighAvailability() || this.failedOver)
2668 && (this.autoCommit || getAutoReconnectForPools())
2669 && this.needsPing && !isBatch) {
2670 try {
2671 pingInternal(false, 0);
2672
2673 this.needsPing = false;
2674 } catch (Exception Ex) {
2675 createNewIO(true);
2676 }
2677 }
2678
2679 try {
2680 if (packet == null) {
2681 String encoding = null;
2682
2683 if (getUseUnicode()) {
2684 encoding = getEncoding();
2685 }
2686
2687 return this.io.sqlQueryDirect(callingStatement, sql,
2688 encoding, null, maxRows, resultSetType,
2689 resultSetConcurrency, streamResults, catalog,
2690 cachedMetadata);
2691 }
2692
2693 return this.io.sqlQueryDirect(callingStatement, null, null,
2694 packet, maxRows, resultSetType,
2695 resultSetConcurrency, streamResults, catalog,
2696 cachedMetadata);
2697 } catch (java.sql.SQLException sqlE) {
2698 // don't clobber SQL exceptions
2699
2700 if (getDumpQueriesOnException()) {
2701 String extractedSql = extractSqlFromPacket(sql, packet,
2702 endOfQueryPacketPosition);
2703 StringBuffer messageBuf = new StringBuffer(extractedSql
2704 .length() + 32);
2705 messageBuf
2706 .append("\n\nQuery being executed when exception was thrown:\n\n");
2707 messageBuf.append(extractedSql);
2708
2709 sqlE = appendMessageToException(sqlE, messageBuf.toString(), getExceptionInterceptor());
2710 }
2711
2712 if ((getHighAvailability() || this.failedOver)) {
2713 this.needsPing = true;
2714 } else {
2715 String sqlState = sqlE.getSQLState();
2716
2717 if ((sqlState != null)
2718 && sqlState
2719 .equals(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)) {
2720 cleanup(sqlE);
2721 }
2722 }
2723
2724 throw sqlE;
2725 } catch (Exception ex) {
2726 if ((getHighAvailability() || this.failedOver)) {
2727 this.needsPing = true;
2728 } else if (ex instanceof IOException) {
2729 cleanup(ex);
2730 }
2731
2732 SQLException sqlEx = SQLError.createSQLException(
2733 Messages.getString("Connection.UnexpectedException"),
2734 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
2735 sqlEx.initCause(ex);
2736
2737 throw sqlEx;
2738 } finally {
2739 if (getMaintainTimeStats()) {
2740 this.lastQueryFinishedTime = System.currentTimeMillis();
2741 }
2742
2743 if (this.failedOver) {
2744 this.queriesIssuedFailedOver++;
2745 }
2746
2747 if (getGatherPerformanceMetrics()) {
2748 long queryTime = System.currentTimeMillis()
2749 - queryStartTime;
2750
2751 registerQueryExecutionTime(queryTime);
2752 }
2753 }
2754 }
2755 }
2756
2757 protected String extractSqlFromPacket(String possibleSqlQuery,
2758 Buffer queryPacket, int endOfQueryPacketPosition)
2759 throws SQLException {
2760
2761 String extractedSql = null;
2762
2763 if (possibleSqlQuery != null) {
2764 if (possibleSqlQuery.length() > getMaxQuerySizeToLog()) {
2765 StringBuffer truncatedQueryBuf = new StringBuffer(
2766 possibleSqlQuery.substring(0, getMaxQuerySizeToLog()));
2767 truncatedQueryBuf.append(Messages.getString("MysqlIO.25"));
2768 extractedSql = truncatedQueryBuf.toString();
2769 } else {
2770 extractedSql = possibleSqlQuery;
2771 }
2772 }
2773
2774 if (extractedSql == null) {
2775 // This is probably from a client-side prepared
2776 // statement
2777
2778 int extractPosition = endOfQueryPacketPosition;
2779
2780 boolean truncated = false;
2781
2782 if (endOfQueryPacketPosition > getMaxQuerySizeToLog()) {
2783 extractPosition = getMaxQuerySizeToLog();
2784 truncated = true;
2785 }
2786
2787 extractedSql = new String(queryPacket.getByteBuffer(), 5,
2788 (extractPosition - 5));
2789
2790 if (truncated) {
2791 extractedSql += Messages.getString("MysqlIO.25"); //$NON-NLS-1$
2792 }
2793 }
2794
2795 return extractedSql;
2796
2797 }
2798
2799 /**
2800 * DOCUMENT ME!
2801 *
2802 * @throws Throwable
2803 * DOCUMENT ME!
2804 */
2805 protected void finalize() throws Throwable {
2806 cleanup(null);
2807
2808 super.finalize();
2809 }
2810
2811 protected StringBuffer generateConnectionCommentBlock(StringBuffer buf) {
2812 buf.append("/* conn id ");
2813 buf.append(getId());
2814 buf.append(" clock: ");
2815 buf.append(System.currentTimeMillis());
2816 buf.append(" */ ");
2817
2818 return buf;
2819 }
2820
2821 public int getActiveStatementCount() {
2822 // Might not have one of these if
2823 // not tracking open resources
2824 if (this.openStatements != null) {
2825 synchronized (this.openStatements) {
2826 return this.openStatements.size();
2827 }
2828 }
2829
2830 return 0;
2831 }
2832
2833 /**
2834 * Gets the current auto-commit state
2835 *
2836 * @return Current state of auto-commit
2837 * @exception SQLException
2838 * if an error occurs
2839 * @see setAutoCommit
2840 */
2841 public boolean getAutoCommit() throws SQLException {
2842 return this.autoCommit;
2843 }
2844
2845 /**
2846 * Optimization to only use one calendar per-session, or calculate it for
2847 * each call, depending on user configuration
2848 */
2849 protected Calendar getCalendarInstanceForSessionOrNew() {
2850 if (getDynamicCalendars()) {
2851 return Calendar.getInstance();
2852 }
2853
2854 return getSessionLockedCalendar();
2855 }
2856
2857 /**
2858 * Return the connections current catalog name, or null if no catalog name
2859 * is set, or we dont support catalogs.
2860 * <p>
2861 * <b>Note:</b> MySQL's notion of catalogs are individual databases.
2862 * </p>
2863 *
2864 * @return the current catalog name or null
2865 * @exception SQLException
2866 * if a database access error occurs
2867 */
2868 public String getCatalog() throws SQLException {
2869 return this.database;
2870 }
2871
2872 /**
2873 * @return Returns the characterSetMetadata.
2874 */
2875 protected String getCharacterSetMetadata() {
2876 return this.characterSetMetadata;
2877 }
2878
2879 /**
2880 * Returns the locally mapped instance of a charset converter (to avoid
2881 * overhead of static synchronization).
2882 *
2883 * @param javaEncodingName
2884 * the encoding name to retrieve
2885 * @return a character converter, or null if one couldn't be mapped.
2886 */
2887 SingleByteCharsetConverter getCharsetConverter(
2888 String javaEncodingName) throws SQLException {
2889 if (javaEncodingName == null) {
2890 return null;
2891 }
2892
2893 if (this.usePlatformCharsetConverters) {
2894 return null; // we'll use Java's built-in routines for this
2895 // they're finally fast enough
2896 }
2897
2898 SingleByteCharsetConverter converter = null;
2899
2900 synchronized (this.charsetConverterMap) {
2901 Object asObject = this.charsetConverterMap
2902 .get(javaEncodingName);
2903
2904 if (asObject == CHARSET_CONVERTER_NOT_AVAILABLE_MARKER) {
2905 return null;
2906 }
2907
2908 converter = (SingleByteCharsetConverter)asObject;
2909
2910 if (converter == null) {
2911 try {
2912 converter = SingleByteCharsetConverter.getInstance(
2913 javaEncodingName, this);
2914
2915 if (converter == null) {
2916 this.charsetConverterMap.put(javaEncodingName,
2917 CHARSET_CONVERTER_NOT_AVAILABLE_MARKER);
2918 } else {
2919 this.charsetConverterMap.put(javaEncodingName, converter);
2920 }
2921 } catch (UnsupportedEncodingException unsupEncEx) {
2922 this.charsetConverterMap.put(javaEncodingName,
2923 CHARSET_CONVERTER_NOT_AVAILABLE_MARKER);
2924
2925 converter = null;
2926 }
2927 }
2928 }
2929
2930 return converter;
2931 }
2932
2933 /**
2934 * Returns the Java character encoding name for the given MySQL server
2935 * charset index
2936 *
2937 * @param charsetIndex
2938 * @return the Java character encoding name for the given MySQL server
2939 * charset index
2940 * @throws SQLException
2941 * if the character set index isn't known by the driver
2942 */
2943 protected String getCharsetNameForIndex(int charsetIndex)
2944 throws SQLException {
2945 String charsetName = null;
2946
2947 if (getUseOldUTF8Behavior()) {
2948 return getEncoding();
2949 }
2950
2951 if (charsetIndex != MysqlDefs.NO_CHARSET_INFO) {
2952 try {
2953 charsetName = this.indexToCharsetMapping[charsetIndex];
2954
2955 if ("sjis".equalsIgnoreCase(charsetName) ||
2956 "MS932".equalsIgnoreCase(charsetName) /* for JDK6 */) {
2957 // Use our encoding so that code pages like Cp932 work
2958 if (CharsetMapping.isAliasForSjis(getEncoding())) {
2959 charsetName = getEncoding();
2960 }
2961 }
2962 } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) {
2963 throw SQLError.createSQLException(
2964 "Unknown character set index for field '"
2965 + charsetIndex + "' received from server.",
2966 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
2967 }
2968
2969 // Punt
2970 if (charsetName == null) {
2971 charsetName = getEncoding();
2972 }
2973 } else {
2974 charsetName = getEncoding();
2975 }
2976
2977 return charsetName;
2978 }
2979
2980 /**
2981 * DOCUMENT ME!
2982 *
2983 * @return Returns the defaultTimeZone.
2984 */
2985 protected TimeZone getDefaultTimeZone() {
2986 return this.defaultTimeZone;
2987 }
2988
2989 protected String getErrorMessageEncoding() {
2990 return errorMessageEncoding;
2991 }
2992
2993 /**
2994 * @see Connection#getHoldability()
2995 */
2996 public int getHoldability() throws SQLException {
2997 return java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT;
2998 }
2999
3000 long getId() {
3001 return this.connectionId;
3002 }
3003
3004 /**
3005 * NOT JDBC-Compliant, but clients can use this method to determine how long
3006 * this connection has been idle. This time (reported in milliseconds) is
3007 * updated once a query has completed.
3008 *
3009 * @return number of ms that this connection has been idle, 0 if the driver
3010 * is busy retrieving results.
3011 */
3012 public long getIdleFor() {
3013 if (this.lastQueryFinishedTime == 0) {
3014 return 0;
3015 }
3016
3017 long now = System.currentTimeMillis();
3018 long idleTime = now - this.lastQueryFinishedTime;
3019
3020 return idleTime;
3021 }
3022
3023 /**
3024 * Returns the IO channel to the server
3025 *
3026 * @return the IO channel to the server
3027 * @throws SQLException
3028 * if the connection is closed.
3029 */
3030 protected MysqlIO getIO() throws SQLException {
3031 if ((this.io == null) || this.isClosed) {
3032 throw SQLError.createSQLException(
3033 "Operation not allowed on closed connection",
3034 SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
3035 }
3036
3037 return this.io;
3038 }
3039
3040 /**
3041 * Returns the log mechanism that should be used to log information from/for
3042 * this Connection.
3043 *
3044 * @return the Log instance to use for logging messages.
3045 * @throws SQLException
3046 * if an error occurs
3047 */
3048 public Log getLog() throws SQLException {
3049 return this.log;
3050 }
3051
3052 protected int getMaxBytesPerChar(String javaCharsetName)
3053 throws SQLException {
3054 // TODO: Check if we can actually run this query at this point in time
3055 String charset = CharsetMapping.getMysqlEncodingForJavaEncoding(
3056 javaCharsetName, this);
3057
3058 if (versionMeetsMinimum(4, 1, 0)) {
3059 Map mapToCheck = null;
3060
3061 if (!getUseDynamicCharsetInfo()) {
3062 mapToCheck = CharsetMapping.STATIC_CHARSET_TO_NUM_BYTES_MAP;
3063 } else {
3064 mapToCheck = this.charsetToNumBytesMap;
3065
3066 synchronized (this.charsetToNumBytesMap) {
3067 if (this.charsetToNumBytesMap.isEmpty()) {
3068
3069 java.sql.Statement stmt = null;
3070 java.sql.ResultSet rs = null;
3071
3072 try {
3073 stmt = getMetadataSafeStatement();
3074
3075 rs = stmt.executeQuery("SHOW CHARACTER SET");
3076
3077 while (rs.next()) {
3078 this.charsetToNumBytesMap.put(rs.getString("Charset"),
3079 Constants.integerValueOf(rs.getInt("Maxlen")));
3080 }
3081
3082 rs.close();
3083 rs = null;
3084
3085 stmt.close();
3086
3087 stmt = null;
3088 } finally {
3089 if (rs != null) {
3090 rs.close();
3091 rs = null;
3092 }
3093
3094 if (stmt != null) {
3095 stmt.close();
3096 stmt = null;
3097 }
3098 }
3099 }
3100 }
3101 }
3102
3103 Integer mbPerChar = (Integer) mapToCheck.get(charset);
3104
3105 if (mbPerChar != null) {
3106 return mbPerChar.intValue();
3107 }
3108
3109 return 1; // we don't know
3110 }
3111
3112 return 1; // we don't know
3113 }
3114
3115 /**
3116 * A connection's database is able to provide information describing its
3117 * tables, its supported SQL grammar, its stored procedures, the
3118 * capabilities of this connection, etc. This information is made available
3119 * through a DatabaseMetaData object.
3120 *
3121 * @return a DatabaseMetaData object for this connection
3122 * @exception SQLException
3123 * if a database access error occurs
3124 */
3125 public java.sql.DatabaseMetaData getMetaData() throws SQLException {
3126 return getMetaData(true, true);
3127 }
3128
3129 private java.sql.DatabaseMetaData getMetaData(boolean checkClosed, boolean checkForInfoSchema) throws SQLException {
3130 if (checkClosed) {
3131 checkClosed();
3132 }
3133
3134 return com.mysql.jdbc.DatabaseMetaData.getInstance(this, this.database, checkForInfoSchema);
3135 }
3136
3137 protected java.sql.Statement getMetadataSafeStatement() throws SQLException {
3138 java.sql.Statement stmt = createStatement();
3139
3140 if (stmt.getMaxRows() != 0) {
3141 stmt.setMaxRows(0);
3142 }
3143
3144 stmt.setEscapeProcessing(false);
3145
3146 if (stmt.getFetchSize() != 0) {
3147 stmt.setFetchSize(0);
3148 }
3149
3150 return stmt;
3151 }
3152
3153 /**
3154 * Returns the Mutex all queries are locked against
3155 *
3156 * @return DOCUMENT ME!
3157 * @throws SQLException
3158 * DOCUMENT ME!
3159 */
3160 Object getMutex() throws SQLException {
3161 if (this.io == null) {
3162 throwConnectionClosedException();
3163 }
3164
3165 reportMetricsIfNeeded();
3166
3167 return this.mutex;
3168 }
3169
3170 /**
3171 * Returns the packet buffer size the MySQL server reported upon connection
3172 *
3173 * @return DOCUMENT ME!
3174 */
3175 int getNetBufferLength() {
3176 return this.netBufferLength;
3177 }
3178
3179 /**
3180 * Returns the server's character set
3181 *
3182 * @return the server's character set.
3183 */
3184 public String getServerCharacterEncoding() {
3185 if (this.io.versionMeetsMinimum(4, 1, 0)) {
3186 return (String) this.serverVariables.get("character_set_server");
3187 } else {
3188 return (String) this.serverVariables.get("character_set");
3189 }
3190 }
3191
3192 int getServerMajorVersion() {
3193 return this.io.getServerMajorVersion();
3194 }
3195
3196 int getServerMinorVersion() {
3197 return this.io.getServerMinorVersion();
3198 }
3199
3200 int getServerSubMinorVersion() {
3201 return this.io.getServerSubMinorVersion();
3202 }
3203
3204 /**
3205 * DOCUMENT ME!
3206 *
3207 * @return DOCUMENT ME!
3208 */
3209 public TimeZone getServerTimezoneTZ() {
3210 return this.serverTimezoneTZ;
3211 }
3212
3213
3214 String getServerVariable(String variableName) {
3215 if (this.serverVariables != null) {
3216 return (String) this.serverVariables.get(variableName);
3217 }
3218
3219 return null;
3220 }
3221
3222 String getServerVersion() {
3223 return this.io.getServerVersion();
3224 }
3225
3226 protected Calendar getSessionLockedCalendar() {
3227
3228 return this.sessionCalendar;
3229 }
3230
3231 /**
3232 * Get this Connection's current transaction isolation mode.
3233 *
3234 * @return the current TRANSACTION_ mode value
3235 * @exception SQLException
3236 * if a database access error occurs
3237 */
3238 public int getTransactionIsolation() throws SQLException {
3239
3240 if (this.hasIsolationLevels && !getUseLocalSessionState()) {
3241 java.sql.Statement stmt = null;
3242 java.sql.ResultSet rs = null;
3243
3244 try {
3245 stmt = getMetadataSafeStatement();
3246
3247 String query = null;
3248
3249 int offset = 0;
3250
3251 if (versionMeetsMinimum(4, 0, 3)) {
3252 query = "SELECT @@session.tx_isolation";
3253 offset = 1;
3254 } else {
3255 query = "SHOW VARIABLES LIKE 'transaction_isolation'";
3256 offset = 2;
3257 }
3258
3259 rs = stmt.executeQuery(query);
3260
3261 if (rs.next()) {
3262 String s = rs.getString(offset);
3263
3264 if (s != null) {
3265 Integer intTI = (Integer) mapTransIsolationNameToValue
3266 .get(s);
3267
3268 if (intTI != null) {
3269 return intTI.intValue();
3270 }
3271 }
3272
3273 throw SQLError.createSQLException(
3274 "Could not map transaction isolation '" + s
3275 + " to a valid JDBC level.",
3276 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3277 }
3278
3279 throw SQLError.createSQLException(
3280 "Could not retrieve transaction isolation level from server",
3281 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3282
3283 } finally {
3284 if (rs != null) {
3285 try {
3286 rs.close();
3287 } catch (Exception ex) {
3288 // ignore
3289 ;
3290 }
3291
3292 rs = null;
3293 }
3294
3295 if (stmt != null) {
3296 try {
3297 stmt.close();
3298 } catch (Exception ex) {
3299 // ignore
3300 ;
3301 }
3302
3303 stmt = null;
3304 }
3305 }
3306 }
3307
3308 return this.isolationLevel;
3309 }
3310
3311 /**
3312 * JDBC 2.0 Get the type-map object associated with this connection. By
3313 * default, the map returned is empty.
3314 *
3315 * @return the type map
3316 * @throws SQLException
3317 * if a database error occurs
3318 */
3319 public synchronized java.util.Map getTypeMap() throws SQLException {
3320 if (this.typeMap == null) {
3321 this.typeMap = new HashMap();
3322 }
3323
3324 return this.typeMap;
3325 }
3326
3327 String getURL() {
3328 return this.myURL;
3329 }
3330
3331 String getUser() {
3332 return this.user;
3333 }
3334
3335 protected Calendar getUtcCalendar() {
3336 return this.utcCalendar;
3337 }
3338
3339 /**
3340 * The first warning reported by calls on this Connection is returned.
3341 * <B>Note:</B> Sebsequent warnings will be changed to this
3342 * java.sql.SQLWarning
3343 *
3344 * @return the first java.sql.SQLWarning or null
3345 * @exception SQLException
3346 * if a database access error occurs
3347 */
3348 public SQLWarning getWarnings() throws SQLException {
3349 return null;
3350 }
3351
3352 public boolean hasSameProperties(Connection c) {
3353 return this.props.equals(c.getProperties());
3354 }
3355
3356 public Properties getProperties() {
3357 return this.props;
3358 }
3359
3360 public boolean hasTriedMaster() {
3361 return this.hasTriedMasterFlag;
3362 }
3363
3364 protected void incrementNumberOfPreparedExecutes() {
3365 if (getGatherPerformanceMetrics()) {
3366 this.numberOfPreparedExecutes++;
3367
3368 // We need to increment this, because
3369 // server-side prepared statements bypass
3370 // any execution by the connection itself...
3371 this.numberOfQueriesIssued++;
3372 }
3373 }
3374
3375 protected void incrementNumberOfPrepares() {
3376 if (getGatherPerformanceMetrics()) {
3377 this.numberOfPrepares++;
3378 }
3379 }
3380
3381 protected void incrementNumberOfResultSetsCreated() {
3382 if (getGatherPerformanceMetrics()) {
3383 this.numberOfResultSetsCreated++;
3384 }
3385 }
3386
3387 /**
3388 * Initializes driver properties that come from URL or properties passed to
3389 * the driver manager.
3390 *
3391 * @param info
3392 * DOCUMENT ME!
3393 * @throws SQLException
3394 * DOCUMENT ME!
3395 */
3396 private void initializeDriverProperties(Properties info)
3397 throws SQLException {
3398 initializeProperties(info);
3399
3400 String exceptionInterceptorClasses = getExceptionInterceptors();
3401
3402 if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) {
3403 this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses);
3404 this.exceptionInterceptor.init(this, info);
3405 }
3406
3407 this.usePlatformCharsetConverters = getUseJvmCharsetConverters();
3408
3409 this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor());
3410
3411 if (getProfileSql() || getUseUsageAdvisor()) {
3412 this.eventSink = ProfilerEventHandlerFactory.getInstance(this);
3413 }
3414
3415 if (getCachePreparedStatements()) {
3416 createPreparedStatementCaches();
3417 }
3418
3419 if (getNoDatetimeStringSync() && getUseTimezone()) {
3420 throw SQLError.createSQLException(
3421 "Can't enable noDatetimeSync and useTimezone configuration "
3422 + "properties at the same time",
3423 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor());
3424 }
3425
3426 if (getCacheCallableStatements()) {
3427 this.parsedCallableStatementCache = new LRUCache(
3428 getCallableStatementCacheSize());
3429 }
3430
3431 if (getAllowMultiQueries()) {
3432 setCacheResultSetMetadata(false); // we don't handle this yet
3433 }
3434
3435 if (getCacheResultSetMetadata()) {
3436 this.resultSetMetadataCache = new LRUCache(
3437 getMetadataCacheSize());
3438 }
3439 }
3440
3441 /**
3442 * Sets varying properties that depend on server information. Called once we
3443 * have connected to the server.
3444 *
3445 * @param info
3446 * DOCUMENT ME!
3447 * @throws SQLException
3448 * DOCUMENT ME!
3449 */
3450 private void initializePropsFromServer() throws SQLException {
3451 String connectionInterceptorClasses = getConnectionLifecycleInterceptors();
3452
3453 this.connectionLifecycleInterceptors = null;
3454
3455 if (connectionInterceptorClasses != null) {
3456 this.connectionLifecycleInterceptors = Util.loadExtensions(this, this.props,
3457 connectionInterceptorClasses,
3458 "Connection.badLifecycleInterceptor", getExceptionInterceptor());
3459
3460 Iterator iter = this.connectionLifecycleInterceptors.iterator();
3461
3462 new IterateBlock(iter) {
3463 void forEach(Object each) throws SQLException {
3464 // TODO: Fully initialize, or continue on error?
3465 ((ConnectionLifecycleInterceptor)each).init(ConnectionImpl.this, props);
3466 }
3467 }.doForAll();
3468 }
3469
3470 setSessionVariables();
3471
3472 //
3473 // the "boolean" type didn't come along until MySQL-4.1
3474 //
3475
3476 if (!versionMeetsMinimum(4, 1, 0)) {
3477 setTransformedBitIsBoolean(false);
3478 }
3479
3480 this.parserKnowsUnicode = versionMeetsMinimum(4, 1, 0);
3481
3482 //
3483 // Users can turn off detection of server-side prepared statements
3484 //
3485 if (getUseServerPreparedStmts() && versionMeetsMinimum(4, 1, 0)) {
3486 this.useServerPreparedStmts = true;
3487
3488 if (versionMeetsMinimum(5, 0, 0) && !versionMeetsMinimum(5, 0, 3)) {
3489 this.useServerPreparedStmts = false; // 4.1.2+ style prepared
3490 // statements
3491 // don't work on these versions
3492 }
3493 }
3494
3495 this.serverVariables.clear();
3496
3497 //
3498 // If version is greater than 3.21.22 get the server
3499 // variables.
3500 if (versionMeetsMinimum(3, 21, 22)) {
3501 loadServerVariables();
3502
3503 if (versionMeetsMinimum(5, 0, 2)) {
3504 this.autoIncrementIncrement = getServerVariableAsInt("auto_increment_increment", 1);
3505 } else {
3506 this.autoIncrementIncrement = 1;
3507 }
3508
3509 buildCollationMapping();
3510
3511 LicenseConfiguration.checkLicenseType(this.serverVariables);
3512
3513 String lowerCaseTables = (String) this.serverVariables
3514 .get("lower_case_table_names");
3515
3516 this.lowerCaseTableNames = "on".equalsIgnoreCase(lowerCaseTables)
3517 || "1".equalsIgnoreCase(lowerCaseTables)
3518 || "2".equalsIgnoreCase(lowerCaseTables);
3519
3520 this.storesLowerCaseTableName = "1".equalsIgnoreCase(lowerCaseTables) ||
3521 "on".equalsIgnoreCase(lowerCaseTables);
3522
3523 configureTimezone();
3524
3525 if (this.serverVariables.containsKey("max_allowed_packet")) {
3526 int serverMaxAllowedPacket = getServerVariableAsInt("max_allowed_packet", -1);
3527 // use server value if maxAllowedPacket hasn't been given, or max_allowed_packet is smaller
3528 if (serverMaxAllowedPacket != -1 && (serverMaxAllowedPacket < getMaxAllowedPacket() ||
3529 getMaxAllowedPacket() <= 0))
3530 setMaxAllowedPacket(serverMaxAllowedPacket);
3531 else if (serverMaxAllowedPacket == -1 && getMaxAllowedPacket() == -1)
3532 setMaxAllowedPacket(65535);
3533
3534 int preferredBlobSendChunkSize = getBlobSendChunkSize();
3535
3536 int allowedBlobSendChunkSize = Math.min(preferredBlobSendChunkSize,
3537 getMaxAllowedPacket()) -
3538 ServerPreparedStatement.BLOB_STREAM_READ_BUF_SIZE
3539 - 11 /* LONG_DATA and MySQLIO packet header size */;
3540
3541 setBlobSendChunkSize(String.valueOf(allowedBlobSendChunkSize));
3542 }
3543
3544 if (this.serverVariables.containsKey("net_buffer_length")) {
3545 this.netBufferLength = getServerVariableAsInt("net_buffer_length", 16 * 1024);
3546 }
3547
3548 checkTransactionIsolationLevel();
3549
3550 if (!versionMeetsMinimum(4, 1, 0)) {
3551 checkServerEncoding();
3552 }
3553
3554 this.io.checkForCharsetMismatch();
3555
3556 if (this.serverVariables.containsKey("sql_mode")) {
3557 int sqlMode = 0;
3558
3559 String sqlModeAsString = (String) this.serverVariables
3560 .get("sql_mode");
3561 try {
3562 sqlMode = Integer.parseInt(sqlModeAsString);
3563 } catch (NumberFormatException nfe) {
3564 // newer versions of the server has this as a string-y
3565 // list...
3566 sqlMode = 0;
3567
3568 if (sqlModeAsString != null) {
3569 if (sqlModeAsString.indexOf("ANSI_QUOTES") != -1) {
3570 sqlMode |= 4;
3571 }
3572
3573 if (sqlModeAsString.indexOf("NO_BACKSLASH_ESCAPES") != -1) {
3574 this.noBackslashEscapes = true;
3575 }
3576 }
3577 }
3578
3579 if ((sqlMode & 4) > 0) {
3580 this.useAnsiQuotes = true;
3581 } else {
3582 this.useAnsiQuotes = false;
3583 }
3584 }
3585 }
3586
3587 this.errorMessageEncoding =
3588 CharsetMapping.getCharacterEncodingForErrorMessages(this);
3589
3590
3591 boolean overrideDefaultAutocommit = isAutoCommitNonDefaultOnServer();
3592
3593 configureClientCharacterSet(false);
3594
3595 if (versionMeetsMinimum(3, 23, 15)) {
3596 this.transactionsSupported = true;
3597
3598 if (!overrideDefaultAutocommit) {
3599 setAutoCommit(true); // to override anything
3600 // the server is set to...reqd
3601 // by JDBC spec.
3602 }
3603 } else {
3604 this.transactionsSupported = false;
3605 }
3606
3607
3608 if (versionMeetsMinimum(3, 23, 36)) {
3609 this.hasIsolationLevels = true;
3610 } else {
3611 this.hasIsolationLevels = false;
3612 }
3613
3614 this.hasQuotedIdentifiers = versionMeetsMinimum(3, 23, 6);
3615
3616 this.io.resetMaxBuf();
3617
3618 //
3619 // If we're using MySQL 4.1.0 or newer, we need to figure
3620 // out what character set metadata will be returned in,
3621 // and then map that to a Java encoding name.
3622 //
3623 // We've already set it, and it might be different than what
3624 // was originally on the server, which is why we use the
3625 // "special" key to retrieve it
3626 if (this.io.versionMeetsMinimum(4, 1, 0)) {
3627 String characterSetResultsOnServerMysql = (String) this.serverVariables
3628 .get(JDBC_LOCAL_CHARACTER_SET_RESULTS);
3629
3630 if (characterSetResultsOnServerMysql == null
3631 || StringUtils.startsWithIgnoreCaseAndWs(
3632 characterSetResultsOnServerMysql, "NULL")
3633 || characterSetResultsOnServerMysql.length() == 0) {
3634 String defaultMetadataCharsetMysql = (String) this.serverVariables
3635 .get("character_set_system");
3636 String defaultMetadataCharset = null;
3637
3638 if (defaultMetadataCharsetMysql != null) {
3639 defaultMetadataCharset = CharsetMapping
3640 .getJavaEncodingForMysqlEncoding(
3641 defaultMetadataCharsetMysql, this);
3642 } else {
3643 defaultMetadataCharset = "UTF-8";
3644 }
3645
3646 this.characterSetMetadata = defaultMetadataCharset;
3647 } else {
3648 this.characterSetResultsOnServer = CharsetMapping
3649 .getJavaEncodingForMysqlEncoding(
3650 characterSetResultsOnServerMysql, this);
3651 this.characterSetMetadata = this.characterSetResultsOnServer;
3652 }
3653 } else {
3654 this.characterSetMetadata = getEncoding();
3655 }
3656
3657 //
3658 // Query cache is broken wrt. multi-statements before MySQL-4.1.10
3659 //
3660
3661 if (versionMeetsMinimum(4, 1, 0)
3662 && !this.versionMeetsMinimum(4, 1, 10)
3663 && getAllowMultiQueries()) {
3664 if (isQueryCacheEnabled()) {
3665 setAllowMultiQueries(false);
3666 }
3667 }
3668
3669 if (versionMeetsMinimum(5, 0, 0) &&
3670 (getUseLocalTransactionState() || getElideSetAutoCommits()) &&
3671 isQueryCacheEnabled() && !versionMeetsMinimum(6, 0, 10)) {
3672 // Can't trust the server status flag on the wire if query cache is enabled,
3673 // due to Bug#36326
3674 setUseLocalTransactionState(false);
3675 setElideSetAutoCommits(false);
3676 }
3677
3678 //
3679 // Server can do this more efficiently for us
3680 //
3681
3682 setupServerForTruncationChecks();
3683 }
3684
3685 private boolean isQueryCacheEnabled() {
3686 return "ON".equalsIgnoreCase((String) this.serverVariables
3687 .get("query_cache_type"))
3688 && !"0".equalsIgnoreCase((String) this.serverVariables
3689 .get("query_cache_size"));
3690 }
3691
3692 private int getServerVariableAsInt(String variableName, int fallbackValue)
3693 throws SQLException {
3694 try {
3695 return Integer.parseInt((String) this.serverVariables
3696 .get(variableName));
3697 } catch (NumberFormatException nfe) {
3698 getLog().logWarn(Messages.getString("Connection.BadValueInServerVariables", new Object[] {variableName,
3699 this.serverVariables.get(variableName), new Integer(fallbackValue)}));
3700
3701 return fallbackValue;
3702 }
3703 }
3704
3705 /**
3706 * Has the default autocommit value of 0 been changed on the server
3707 * via init_connect?
3708 *
3709 * @return true if autocommit is not the default of '0' on the server.
3710 *
3711 * @throws SQLException
3712 */
3713 private boolean isAutoCommitNonDefaultOnServer() throws SQLException {
3714 boolean overrideDefaultAutocommit = false;
3715
3716 String initConnectValue = (String) this.serverVariables
3717 .get("init_connect");
3718
3719 if (versionMeetsMinimum(4, 1, 2) && initConnectValue != null
3720 && initConnectValue.length() > 0) {
3721 if (!getElideSetAutoCommits()) {
3722 // auto-commit might have changed
3723 java.sql.ResultSet rs = null;
3724 java.sql.Statement stmt = null;
3725
3726 try {
3727 stmt = getMetadataSafeStatement();
3728
3729 rs = stmt.executeQuery("SELECT @@session.autocommit");
3730
3731 if (rs.next()) {
3732 this.autoCommit = rs.getBoolean(1);
3733 if (this.autoCommit != true) {
3734 overrideDefaultAutocommit = true;
3735 }
3736 }
3737
3738 } finally {
3739 if (rs != null) {
3740 try {
3741 rs.close();
3742 } catch (SQLException sqlEx) {
3743 // do nothing
3744 }
3745 }
3746
3747 if (stmt != null) {
3748 try {
3749 stmt.close();
3750 } catch (SQLException sqlEx) {
3751 // do nothing
3752 }
3753 }
3754 }
3755 } else {
3756 if (this.getIO().isSetNeededForAutoCommitMode(true)) {
3757 // we're not in standard autocommit=true mode
3758 this.autoCommit = false;
3759 overrideDefaultAutocommit = true;
3760 }
3761 }
3762 }
3763
3764 return overrideDefaultAutocommit;
3765 }
3766
3767 protected boolean isClientTzUTC() {
3768 return this.isClientTzUTC;
3769 }
3770
3771 /**
3772 * DOCUMENT ME!
3773 *
3774 * @return DOCUMENT ME!
3775 */
3776 public boolean isClosed() {
3777 return this.isClosed;
3778 }
3779
3780 protected boolean isCursorFetchEnabled() throws SQLException {
3781 return (versionMeetsMinimum(5, 0, 2) && getUseCursorFetch());
3782 }
3783
3784 public boolean isInGlobalTx() {
3785 return this.isInGlobalTx;
3786 }
3787
3788 /**
3789 * Is this connection connected to the first host in the list if
3790 * there is a list of servers in the URL?
3791 *
3792 * @return true if this connection is connected to the first in
3793 * the list.
3794 */
3795 public synchronized boolean isMasterConnection() {
3796 return !this.failedOver;
3797 }
3798
3799 /**
3800 * Is the server in a sql_mode that doesn't allow us to use \\ to escape
3801 * things?
3802 *
3803 * @return Returns the noBackslashEscapes.
3804 */
3805 public boolean isNoBackslashEscapesSet() {
3806 return this.noBackslashEscapes;
3807 }
3808
3809 boolean isReadInfoMsgEnabled() {
3810 return this.readInfoMsg;
3811 }
3812
3813 /**
3814 * Tests to see if the connection is in Read Only Mode. Note that we cannot
3815 * really put the database in read only mode, but we pretend we can by
3816 * returning the value of the readOnly flag
3817 *
3818 * @return true if the connection is read only
3819 * @exception SQLException
3820 * if a database access error occurs
3821 */
3822 public boolean isReadOnly() throws SQLException {
3823 return this.readOnly;
3824 }
3825
3826 protected boolean isRunningOnJDK13() {
3827 return this.isRunningOnJDK13;
3828 }
3829
3830 public synchronized boolean isSameResource(Connection otherConnection) {
3831 if (otherConnection == null) {
3832 return false;
3833 }
3834
3835 boolean directCompare = true;
3836
3837 String otherHost = ((ConnectionImpl)otherConnection).origHostToConnectTo;
3838 String otherOrigDatabase = ((ConnectionImpl)otherConnection).origDatabaseToConnectTo;
3839 String otherCurrentCatalog = ((ConnectionImpl)otherConnection).database;
3840
3841 if (!nullSafeCompare(otherHost, this.origHostToConnectTo)) {
3842 directCompare = false;
3843 } else if (otherHost != null && otherHost.indexOf(',') == -1 &&
3844 otherHost.indexOf(':') == -1) {
3845 // need to check port numbers
3846 directCompare = (((ConnectionImpl)otherConnection).origPortToConnectTo ==
3847 this.origPortToConnectTo);
3848 }
3849
3850 if (directCompare) {
3851 if (!nullSafeCompare(otherOrigDatabase, this.origDatabaseToConnectTo)) { directCompare = false;
3852 directCompare = false;
3853 } else if (!nullSafeCompare(otherCurrentCatalog, this.database)) {
3854 directCompare = false;
3855 }
3856 }
3857
3858 if (directCompare) {
3859 return true;
3860 }
3861
3862 // Has the user explicitly set a resourceId?
3863 String otherResourceId = ((ConnectionImpl)otherConnection).getResourceId();
3864 String myResourceId = getResourceId();
3865
3866 if (otherResourceId != null || myResourceId != null) {
3867 directCompare = nullSafeCompare(otherResourceId, myResourceId);
3868
3869 if (directCompare) {
3870 return true;
3871 }
3872 }
3873
3874 return false;
3875 }
3876
3877 protected boolean isServerTzUTC() {
3878 return this.isServerTzUTC;
3879 }
3880
3881 private boolean usingCachedConfig = false;
3882
3883 /**
3884 * Loads the result of 'SHOW VARIABLES' into the serverVariables field so
3885 * that the driver can configure itself.
3886 *
3887 * @throws SQLException
3888 * if the 'SHOW VARIABLES' query fails for any reason.
3889 */
3890 private void loadServerVariables() throws SQLException {
3891
3892 if (getCacheServerConfiguration()) {
3893 synchronized (serverConfigByUrl) {
3894 Map cachedVariableMap = (Map) serverConfigByUrl.get(getURL());
3895
3896 if (cachedVariableMap != null) {
3897 this.serverVariables = cachedVariableMap;
3898 this.usingCachedConfig = true;
3899
3900 return;
3901 }
3902 }
3903 }
3904
3905 java.sql.Statement stmt = null;
3906 java.sql.ResultSet results = null;
3907
3908 try {
3909 stmt = getMetadataSafeStatement();
3910
3911 String version = this.dbmd.getDriverVersion();
3912
3913 if (version != null && version.indexOf('*') != -1) {
3914 StringBuffer buf = new StringBuffer(version.length() + 10);
3915
3916 for (int i = 0; i < version.length(); i++) {
3917 char c = version.charAt(i);
3918
3919 if (c == '*') {
3920 buf.append("[star]");
3921 } else {
3922 buf.append(c);
3923 }
3924 }
3925
3926 version = buf.toString();
3927 }
3928
3929 String versionComment = (this.getParanoid() || version == null) ? ""
3930 : "/* " + version + " */";
3931
3932 String query = versionComment + "SHOW VARIABLES";
3933
3934 if (versionMeetsMinimum(5, 0, 3)) {
3935 query = versionComment + "SHOW VARIABLES WHERE Variable_name ='language'"
3936 + " OR Variable_name = 'net_write_timeout'"
3937 + " OR Variable_name = 'interactive_timeout'"
3938 + " OR Variable_name = 'wait_timeout'"
3939 + " OR Variable_name = 'character_set_client'"
3940 + " OR Variable_name = 'character_set_connection'"
3941 + " OR Variable_name = 'character_set'"
3942 + " OR Variable_name = 'character_set_server'"
3943 + " OR Variable_name = 'tx_isolation'"
3944 + " OR Variable_name = 'transaction_isolation'"
3945 + " OR Variable_name = 'character_set_results'"
3946 + " OR Variable_name = 'timezone'"
3947 + " OR Variable_name = 'time_zone'"
3948 + " OR Variable_name = 'system_time_zone'"
3949 + " OR Variable_name = 'lower_case_table_names'"
3950 + " OR Variable_name = 'max_allowed_packet'"
3951 + " OR Variable_name = 'net_buffer_length'"
3952 + " OR Variable_name = 'sql_mode'"
3953 + " OR Variable_name = 'query_cache_type'"
3954 + " OR Variable_name = 'query_cache_size'"
3955 + " OR Variable_name = 'init_connect'";
3956 }
3957
3958 results = stmt.executeQuery(query);
3959
3960 while (results.next()) {
3961 this.serverVariables.put(results.getString(1), results
3962 .getString(2));
3963 }
3964
3965 if (versionMeetsMinimum(5, 0, 2)) {
3966 results = stmt.executeQuery(versionComment + "SELECT @@session.auto_increment_increment");
3967
3968 if (results.next()) {
3969 this.serverVariables.put("auto_increment_increment", results.getString(1));
3970 }
3971 }
3972
3973 if (getCacheServerConfiguration()) {
3974 synchronized (serverConfigByUrl) {
3975 serverConfigByUrl.put(getURL(), this.serverVariables);
3976 }
3977 }
3978
3979
3980 } catch (SQLException e) {
3981 throw e;
3982 } finally {
3983 if (results != null) {
3984 try {
3985 results.close();
3986 } catch (SQLException sqlE) {
3987 ;
3988 }
3989 }
3990
3991 if (stmt != null) {
3992 try {
3993 stmt.close();
3994 } catch (SQLException sqlE) {
3995 ;
3996 }
3997 }
3998 }
3999 }
4000
4001 private int autoIncrementIncrement = 0;
4002
4003 public int getAutoIncrementIncrement() {
4004 return this.autoIncrementIncrement;
4005 }
4006
4007 /**
4008 * Is the server configured to use lower-case table names only?
4009 *
4010 * @return true if lower_case_table_names is 'on'
4011 */
4012 public boolean lowerCaseTableNames() {
4013 return this.lowerCaseTableNames;
4014 }
4015
4016 /**
4017 * Has the maxRows value changed?
4018 *
4019 * @param stmt
4020 * DOCUMENT ME!
4021 */
4022 void maxRowsChanged(Statement stmt) {
4023 synchronized (this.mutex) {
4024 if (this.statementsUsingMaxRows == null) {
4025 this.statementsUsingMaxRows = new HashMap();
4026 }
4027
4028 this.statementsUsingMaxRows.put(stmt, stmt);
4029
4030 this.maxRowsChanged = true;
4031 }
4032 }
4033
4034 /**
4035 * A driver may convert the JDBC sql grammar into its system's native SQL
4036 * grammar prior to sending it; nativeSQL returns the native form of the
4037 * statement that the driver would have sent.
4038 *
4039 * @param sql
4040 * a SQL statement that may contain one or more '?' parameter
4041 * placeholders
4042 * @return the native form of this statement
4043 * @exception SQLException
4044 * if a database access error occurs
4045 */
4046 public String nativeSQL(String sql) throws SQLException {
4047 if (sql == null) {
4048 return null;
4049 }
4050
4051 Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
4052 serverSupportsConvertFn(),
4053 this);
4054
4055 if (escapedSqlResult instanceof String) {
4056 return (String) escapedSqlResult;
4057 }
4058
4059 return ((EscapeProcessorResult) escapedSqlResult).escapedSql;
4060 }
4061
4062 private CallableStatement parseCallableStatement(String sql)
4063 throws SQLException {
4064 Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
4065 serverSupportsConvertFn(), this);
4066
4067 boolean isFunctionCall = false;
4068 String parsedSql = null;
4069
4070 if (escapedSqlResult instanceof EscapeProcessorResult) {
4071 parsedSql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
4072 isFunctionCall = ((EscapeProcessorResult) escapedSqlResult).callingStoredFunction;
4073 } else {
4074 parsedSql = (String) escapedSqlResult;
4075 isFunctionCall = false;
4076 }
4077
4078 return CallableStatement.getInstance(this, parsedSql, this.database,
4079 isFunctionCall);
4080 }
4081
4082 /**
4083 * DOCUMENT ME!
4084 *
4085 * @return DOCUMENT ME!
4086 */
4087 public boolean parserKnowsUnicode() {
4088 return this.parserKnowsUnicode;
4089 }
4090
4091 /**
4092 * Detect if the connection is still good
4093 *
4094 * @throws SQLException
4095 * if the ping fails
4096 */
4097 public void ping() throws SQLException {
4098 pingInternal(true, 0);
4099 }
4100
4101 protected void pingInternal(boolean checkForClosedConnection, int timeoutMillis)
4102 throws SQLException {
4103 if (checkForClosedConnection) {
4104 checkClosed();
4105 }
4106
4107 long pingMillisLifetime = getSelfDestructOnPingSecondsLifetime();
4108 int pingMaxOperations = getSelfDestructOnPingMaxOperations();
4109
4110 if ((pingMillisLifetime > 0 && (System.currentTimeMillis() - this.connectionCreationTimeMillis) > pingMillisLifetime)
4111 || (pingMaxOperations > 0 && pingMaxOperations <= this.io
4112 .getCommandCount())) {
4113
4114 close();
4115
4116 throw SQLError.createSQLException(Messages
4117 .getString("Connection.exceededConnectionLifetime"),
4118 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor());
4119 }
4120 // Need MySQL-3.22.1, but who uses anything older!?
4121 this.io.sendCommand(MysqlDefs.PING, null, null, false, null, timeoutMillis);
4122 }
4123
4124 /**
4125 * DOCUMENT ME!
4126 *
4127 * @param sql
4128 * DOCUMENT ME!
4129 * @return DOCUMENT ME!
4130 * @throws SQLException
4131 * DOCUMENT ME!
4132 */
4133 public java.sql.CallableStatement prepareCall(String sql)
4134 throws SQLException {
4135
4136 return prepareCall(sql, DEFAULT_RESULT_SET_TYPE,
4137 DEFAULT_RESULT_SET_CONCURRENCY);
4138 }
4139
4140 /**
4141 * JDBC 2.0 Same as prepareCall() above, but allows the default result set
4142 * type and result set concurrency type to be overridden.
4143 *
4144 * @param sql
4145 * the SQL representing the callable statement
4146 * @param resultSetType
4147 * a result set type, see ResultSet.TYPE_XXX
4148 * @param resultSetConcurrency
4149 * a concurrency type, see ResultSet.CONCUR_XXX
4150 * @return a new CallableStatement object containing the pre-compiled SQL
4151 * statement
4152 * @exception SQLException
4153 * if a database-access error occurs.
4154 */
4155 public java.sql.CallableStatement prepareCall(String sql,
4156 int resultSetType, int resultSetConcurrency) throws SQLException {
4157 if (versionMeetsMinimum(5, 0, 0)) {
4158 CallableStatement cStmt = null;
4159
4160 if (!getCacheCallableStatements()) {
4161
4162 cStmt = parseCallableStatement(sql);
4163 } else {
4164 synchronized (this.parsedCallableStatementCache) {
4165 CompoundCacheKey key = new CompoundCacheKey(getCatalog(), sql);
4166
4167 CallableStatement.CallableStatementParamInfo cachedParamInfo = (CallableStatement.CallableStatementParamInfo) this.parsedCallableStatementCache
4168 .get(key);
4169
4170 if (cachedParamInfo != null) {
4171 cStmt = CallableStatement.getInstance(this, cachedParamInfo);
4172 } else {
4173 cStmt = parseCallableStatement(sql);
4174
4175 cachedParamInfo = cStmt.paramInfo;
4176
4177 this.parsedCallableStatementCache.put(key, cachedParamInfo);
4178 }
4179 }
4180 }
4181
4182 cStmt.setResultSetType(resultSetType);
4183 cStmt.setResultSetConcurrency(resultSetConcurrency);
4184
4185 return cStmt;
4186 }
4187
4188 throw SQLError.createSQLException("Callable statements not " + "supported.",
4189 SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
4190 }
4191
4192 /**
4193 * @see Connection#prepareCall(String, int, int, int)
4194 */
4195 public java.sql.CallableStatement prepareCall(String sql,
4196 int resultSetType, int resultSetConcurrency,
4197 int resultSetHoldability) throws SQLException {
4198 if (getPedantic()) {
4199 if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
4200 throw SQLError.createSQLException(
4201 "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
4202 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4203 }
4204 }
4205
4206 CallableStatement cStmt = (com.mysql.jdbc.CallableStatement) prepareCall(
4207 sql, resultSetType, resultSetConcurrency);
4208
4209 return cStmt;
4210 }
4211
4212 /**
4213 * A SQL statement with or without IN parameters can be pre-compiled and
4214 * stored in a PreparedStatement object. This object can then be used to
4215 * efficiently execute this statement multiple times.
4216 * <p>
4217 * <B>Note:</B> This method is optimized for handling parametric SQL
4218 * statements that benefit from precompilation if the driver supports
4219 * precompilation. In this case, the statement is not sent to the database
4220 * until the PreparedStatement is executed. This has no direct effect on
4221 * users; however it does affect which method throws certain
4222 * java.sql.SQLExceptions
4223 * </p>
4224 * <p>
4225 * MySQL does not support precompilation of statements, so they are handled
4226 * by the driver.
4227 * </p>
4228 *
4229 * @param sql
4230 * a SQL statement that may contain one or more '?' IN parameter
4231 * placeholders
4232 * @return a new PreparedStatement object containing the pre-compiled
4233 * statement.
4234 * @exception SQLException
4235 * if a database access error occurs.
4236 */
4237 public java.sql.PreparedStatement prepareStatement(String sql)
4238 throws SQLException {
4239 return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE,
4240 DEFAULT_RESULT_SET_CONCURRENCY);
4241 }
4242
4243 /**
4244 * @see Connection#prepareStatement(String, int)
4245 */
4246 public java.sql.PreparedStatement prepareStatement(String sql,
4247 int autoGenKeyIndex) throws SQLException {
4248 java.sql.PreparedStatement pStmt = prepareStatement(sql);
4249
4250 ((com.mysql.jdbc.PreparedStatement) pStmt)
4251 .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);
4252
4253 return pStmt;
4254 }
4255
4256 /**
4257 * JDBC 2.0 Same as prepareStatement() above, but allows the default result
4258 * set type and result set concurrency type to be overridden.
4259 *
4260 * @param sql
4261 * the SQL query containing place holders
4262 * @param resultSetType
4263 * a result set type, see ResultSet.TYPE_XXX
4264 * @param resultSetConcurrency
4265 * a concurrency type, see ResultSet.CONCUR_XXX
4266 * @return a new PreparedStatement object containing the pre-compiled SQL
4267 * statement
4268 * @exception SQLException
4269 * if a database-access error occurs.
4270 */
4271 public java.sql.PreparedStatement prepareStatement(String sql,
4272 int resultSetType, int resultSetConcurrency) throws SQLException {
4273 checkClosed();
4274
4275 //
4276 // FIXME: Create warnings if can't create results of the given
4277 // type or concurrency
4278 //
4279 PreparedStatement pStmt = null;
4280
4281 boolean canServerPrepare = true;
4282
4283 String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
4284
4285 if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
4286 canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
4287 }
4288
4289 if (this.useServerPreparedStmts && canServerPrepare) {
4290 if (this.getCachePreparedStatements()) {
4291 synchronized (this.serverSideStatementCache) {
4292 pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);
4293
4294 if (pStmt != null) {
4295 ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);
4296 pStmt.clearParameters();
4297 }
4298
4299 if (pStmt == null) {
4300 try {
4301 pStmt = ServerPreparedStatement.getInstance(this, nativeSql,
4302 this.database, resultSetType, resultSetConcurrency);
4303 if (sql.length() < getPreparedStatementCacheSqlLimit()) {
4304 ((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true;
4305 }
4306
4307 pStmt.setResultSetType(resultSetType);
4308 pStmt.setResultSetConcurrency(resultSetConcurrency);
4309 } catch (SQLException sqlEx) {
4310 // Punt, if necessary
4311 if (getEmulateUnsupportedPstmts()) {
4312 pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
4313
4314 if (sql.length() < getPreparedStatementCacheSqlLimit()) {
4315 this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
4316 }
4317 } else {
4318 throw sqlEx;
4319 }
4320 }
4321 }
4322 }
4323 } else {
4324 try {
4325 pStmt = ServerPreparedStatement.getInstance(this, nativeSql,
4326 this.database, resultSetType, resultSetConcurrency);
4327
4328 pStmt.setResultSetType(resultSetType);
4329 pStmt.setResultSetConcurrency(resultSetConcurrency);
4330 } catch (SQLException sqlEx) {
4331 // Punt, if necessary
4332 if (getEmulateUnsupportedPstmts()) {
4333 pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
4334 } else {
4335 throw sqlEx;
4336 }
4337 }
4338 }
4339 } else {
4340 pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
4341 }
4342
4343 return pStmt;
4344 }
4345
4346 /**
4347 * @see Connection#prepareStatement(String, int, int, int)
4348 */
4349 public java.sql.PreparedStatement prepareStatement(String sql,
4350 int resultSetType, int resultSetConcurrency,
4351 int resultSetHoldability) throws SQLException {
4352 if (getPedantic()) {
4353 if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
4354 throw SQLError.createSQLException(
4355 "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
4356 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4357 }
4358 }
4359
4360 return prepareStatement(sql, resultSetType, resultSetConcurrency);
4361 }
4362
4363 /**
4364 * @see Connection#prepareStatement(String, int[])
4365 */
4366 public java.sql.PreparedStatement prepareStatement(String sql,
4367 int[] autoGenKeyIndexes) throws SQLException {
4368 java.sql.PreparedStatement pStmt = prepareStatement(sql);
4369
4370 ((com.mysql.jdbc.PreparedStatement) pStmt)
4371 .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
4372 && (autoGenKeyIndexes.length > 0));
4373
4374 return pStmt;
4375 }
4376
4377 /**
4378 * @see Connection#prepareStatement(String, String[])
4379 */
4380 public java.sql.PreparedStatement prepareStatement(String sql,
4381 String[] autoGenKeyColNames) throws SQLException {
4382 java.sql.PreparedStatement pStmt = prepareStatement(sql);
4383
4384 ((com.mysql.jdbc.PreparedStatement) pStmt)
4385 .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
4386 && (autoGenKeyColNames.length > 0));
4387
4388 return pStmt;
4389 }
4390
4391 /**
4392 * Closes connection and frees resources.
4393 *
4394 * @param calledExplicitly
4395 * is this being called from close()
4396 * @param issueRollback
4397 * should a rollback() be issued?
4398 * @throws SQLException
4399 * if an error occurs
4400 */
4401 protected void realClose(boolean calledExplicitly, boolean issueRollback,
4402 boolean skipLocalTeardown, Throwable reason) throws SQLException {
4403 SQLException sqlEx = null;
4404
4405 if (this.isClosed()) {
4406 return;
4407 }
4408
4409 this.forceClosedReason = reason;
4410
4411 try {
4412 if (!skipLocalTeardown) {
4413 if (!getAutoCommit() && issueRollback) {
4414 try {
4415 rollback();
4416 } catch (SQLException ex) {
4417 sqlEx = ex;
4418 }
4419 }
4420
4421 reportMetrics();
4422
4423 if (getUseUsageAdvisor()) {
4424 if (!calledExplicitly) {
4425 String message = "Connection implicitly closed by Driver. You should call Connection.close() from your code to free resources more efficiently and avoid resource leaks.";
4426
4427 this.eventSink.consumeEvent(new ProfilerEvent(
4428 ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$
4429 this.getCatalog(), this.getId(), -1, -1, System
4430 .currentTimeMillis(), 0, Constants.MILLIS_I18N,
4431 null,
4432 this.pointOfOrigin, message));
4433 }
4434
4435 long connectionLifeTime = System.currentTimeMillis()
4436 - this.connectionCreationTimeMillis;
4437
4438 if (connectionLifeTime < 500) {
4439 String message = "Connection lifetime of < .5 seconds. You might be un-necessarily creating short-lived connections and should investigate connection pooling to be more efficient.";
4440
4441 this.eventSink.consumeEvent(new ProfilerEvent(
4442 ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$
4443 this.getCatalog(), this.getId(), -1, -1, System
4444 .currentTimeMillis(), 0, Constants.MILLIS_I18N,
4445 null,
4446 this.pointOfOrigin, message));
4447 }
4448 }
4449
4450 try {
4451 closeAllOpenStatements();
4452 } catch (SQLException ex) {
4453 sqlEx = ex;
4454 }
4455
4456 if (this.io != null) {
4457 try {
4458 this.io.quit();
4459 } catch (Exception e) {
4460 ;
4461 }
4462
4463 }
4464 } else {
4465 this.io.forceClose();
4466 }
4467
4468 if (this.statementInterceptors != null) {
4469 for (int i = 0; i < this.statementInterceptors.size(); i++) {
4470 ((StatementInterceptorV2)this.statementInterceptors.get(i)).destroy();
4471 }
4472 }
4473
4474 if (this.exceptionInterceptor != null) {
4475 this.exceptionInterceptor.destroy();
4476 }
4477 } finally {
4478 this.openStatements = null;
4479 this.io = null;
4480 this.statementInterceptors = null;
4481 this.exceptionInterceptor = null;
4482 ProfilerEventHandlerFactory.removeInstance(this);
4483
4484 synchronized (this) {
4485 if (this.cancelTimer != null) {
4486 this.cancelTimer.cancel();
4487 }
4488 }
4489
4490 this.isClosed = true;
4491 }
4492
4493 if (sqlEx != null) {
4494 throw sqlEx;
4495 }
4496
4497 }
4498
4499 protected void recachePreparedStatement(ServerPreparedStatement pstmt) throws SQLException {
4500 if (pstmt.isPoolable()) {
4501 synchronized (this.serverSideStatementCache) {
4502 this.serverSideStatementCache.put(pstmt.originalSql, pstmt);
4503 }
4504 }
4505 }
4506
4507 /**
4508 * DOCUMENT ME!
4509 *
4510 * @param queryTimeMs
4511 */
4512 protected void registerQueryExecutionTime(long queryTimeMs) {
4513 if (queryTimeMs > this.longestQueryTimeMs) {
4514 this.longestQueryTimeMs = queryTimeMs;
4515
4516 repartitionPerformanceHistogram();
4517 }
4518
4519 addToPerformanceHistogram(queryTimeMs, 1);
4520
4521 if (queryTimeMs < this.shortestQueryTimeMs) {
4522 this.shortestQueryTimeMs = (queryTimeMs == 0) ? 1 : queryTimeMs;
4523 }
4524
4525 this.numberOfQueriesIssued++;
4526
4527 this.totalQueryTimeMs += queryTimeMs;
4528 }
4529
4530 /**
4531 * Register a Statement instance as open.
4532 *
4533 * @param stmt
4534 * the Statement instance to remove
4535 */
4536 void registerStatement(Statement stmt) {
4537 synchronized (this.openStatements) {
4538 this.openStatements.put(stmt, stmt);
4539 }
4540 }
4541
4542 /**
4543 * @see Connection#releaseSavepoint(Savepoint)
4544 */
4545 public void releaseSavepoint(Savepoint arg0) throws SQLException {
4546 // this is a no-op
4547 }
4548
4549 private void repartitionHistogram(int[] histCounts, long[] histBreakpoints,
4550 long currentLowerBound, long currentUpperBound) {
4551
4552 if (oldHistCounts == null) {
4553 oldHistCounts = new int[histCounts.length];
4554 oldHistBreakpoints = new long[histBreakpoints.length];
4555 }
4556
4557 System.arraycopy(histCounts, 0, oldHistCounts, 0, histCounts.length);
4558
4559 System.arraycopy(histBreakpoints, 0, oldHistBreakpoints, 0,
4560 histBreakpoints.length);
4561
4562 createInitialHistogram(histBreakpoints, currentLowerBound,
4563 currentUpperBound);
4564
4565 for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
4566 addToHistogram(histCounts, histBreakpoints, oldHistBreakpoints[i],
4567 oldHistCounts[i], currentLowerBound, currentUpperBound);
4568 }
4569 }
4570
4571 private void repartitionPerformanceHistogram() {
4572 checkAndCreatePerformanceHistogram();
4573
4574 repartitionHistogram(this.perfMetricsHistCounts,
4575 this.perfMetricsHistBreakpoints,
4576 this.shortestQueryTimeMs == Long.MAX_VALUE ? 0
4577 : this.shortestQueryTimeMs, this.longestQueryTimeMs);
4578 }
4579
4580 private void repartitionTablesAccessedHistogram() {
4581 checkAndCreateTablesAccessedHistogram();
4582
4583 repartitionHistogram(this.numTablesMetricsHistCounts,
4584 this.numTablesMetricsHistBreakpoints,
4585 this.minimumNumberTablesAccessed == Long.MAX_VALUE ? 0
4586 : this.minimumNumberTablesAccessed,
4587 this.maximumNumberTablesAccessed);
4588 }
4589
4590 private void reportMetrics() {
4591 if (getGatherPerformanceMetrics()) {
4592 StringBuffer logMessage = new StringBuffer(256);
4593
4594 logMessage.append("** Performance Metrics Report **\n");
4595 logMessage.append("\nLongest reported query: "
4596 + this.longestQueryTimeMs + " ms");
4597 logMessage.append("\nShortest reported query: "
4598 + this.shortestQueryTimeMs + " ms");
4599 logMessage
4600 .append("\nAverage query execution time: "
4601 + (this.totalQueryTimeMs / this.numberOfQueriesIssued)
4602 + " ms");
4603 logMessage.append("\nNumber of statements executed: "
4604 + this.numberOfQueriesIssued);
4605 logMessage.append("\nNumber of result sets created: "
4606 + this.numberOfResultSetsCreated);
4607 logMessage.append("\nNumber of statements prepared: "
4608 + this.numberOfPrepares);
4609 logMessage.append("\nNumber of prepared statement executions: "
4610 + this.numberOfPreparedExecutes);
4611
4612 if (this.perfMetricsHistBreakpoints != null) {
4613 logMessage.append("\n\n\tTiming Histogram:\n");
4614 int maxNumPoints = 20;
4615 int highestCount = Integer.MIN_VALUE;
4616
4617 for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) {
4618 if (this.perfMetricsHistCounts[i] > highestCount) {
4619 highestCount = this.perfMetricsHistCounts[i];
4620 }
4621 }
4622
4623 if (highestCount == 0) {
4624 highestCount = 1; // avoid DIV/0
4625 }
4626
4627 for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) {
4628
4629 if (i == 0) {
4630 logMessage.append("\n\tless than "
4631 + this.perfMetricsHistBreakpoints[i + 1]
4632 + " ms: \t" + this.perfMetricsHistCounts[i]);
4633 } else {
4634 logMessage.append("\n\tbetween "
4635 + this.perfMetricsHistBreakpoints[i] + " and "
4636 + this.perfMetricsHistBreakpoints[i + 1]
4637 + " ms: \t" + this.perfMetricsHistCounts[i]);
4638 }
4639
4640 logMessage.append("\t");
4641
4642 int numPointsToGraph = (int) (maxNumPoints * ((double) this.perfMetricsHistCounts[i] / (double) highestCount));
4643
4644 for (int j = 0; j < numPointsToGraph; j++) {
4645 logMessage.append("*");
4646 }
4647
4648 if (this.longestQueryTimeMs < this.perfMetricsHistCounts[i + 1]) {
4649 break;
4650 }
4651 }
4652
4653 if (this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this.longestQueryTimeMs) {
4654 logMessage.append("\n\tbetween ");
4655 logMessage
4656 .append(this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]);
4657 logMessage.append(" and ");
4658 logMessage
4659 .append(this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]);
4660 logMessage.append(" ms: \t");
4661 logMessage
4662 .append(this.perfMetricsHistCounts[HISTOGRAM_BUCKETS - 1]);
4663 }
4664 }
4665
4666 if (this.numTablesMetricsHistBreakpoints != null) {
4667 logMessage.append("\n\n\tTable Join Histogram:\n");
4668 int maxNumPoints = 20;
4669 int highestCount = Integer.MIN_VALUE;
4670
4671 for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) {
4672 if (this.numTablesMetricsHistCounts[i] > highestCount) {
4673 highestCount = this.numTablesMetricsHistCounts[i];
4674 }
4675 }
4676
4677 if (highestCount == 0) {
4678 highestCount = 1; // avoid DIV/0
4679 }
4680
4681 for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) {
4682
4683 if (i == 0) {
4684 logMessage.append("\n\t"
4685 + this.numTablesMetricsHistBreakpoints[i + 1]
4686 + " tables or less: \t\t"
4687 + this.numTablesMetricsHistCounts[i]);
4688 } else {
4689 logMessage.append("\n\tbetween "
4690 + this.numTablesMetricsHistBreakpoints[i]
4691 + " and "
4692 + this.numTablesMetricsHistBreakpoints[i + 1]
4693 + " tables: \t"
4694 + this.numTablesMetricsHistCounts[i]);
4695 }
4696
4697 logMessage.append("\t");
4698
4699 int numPointsToGraph = (int) (maxNumPoints * ((double) this.numTablesMetricsHistCounts[i] / (double) highestCount));
4700
4701 for (int j = 0; j < numPointsToGraph; j++) {
4702 logMessage.append("*");
4703 }
4704
4705 if (this.maximumNumberTablesAccessed < this.numTablesMetricsHistBreakpoints[i + 1]) {
4706 break;
4707 }
4708 }
4709
4710 if (this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this.maximumNumberTablesAccessed) {
4711 logMessage.append("\n\tbetween ");
4712 logMessage
4713 .append(this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]);
4714 logMessage.append(" and ");
4715 logMessage
4716 .append(this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]);
4717 logMessage.append(" tables: ");
4718 logMessage
4719 .append(this.numTablesMetricsHistCounts[HISTOGRAM_BUCKETS - 1]);
4720 }
4721 }
4722
4723 this.log.logInfo(logMessage);
4724
4725 this.metricsLastReportedMs = System.currentTimeMillis();
4726 }
4727 }
4728
4729 /**
4730 * Reports currently collected metrics if this feature is enabled and the
4731 * timeout has passed.
4732 */
4733 private void reportMetricsIfNeeded() {
4734 if (getGatherPerformanceMetrics()) {
4735 if ((System.currentTimeMillis() - this.metricsLastReportedMs) > getReportMetricsIntervalMillis()) {
4736 reportMetrics();
4737 }
4738 }
4739 }
4740
4741 protected void reportNumberOfTablesAccessed(int numTablesAccessed) {
4742 if (numTablesAccessed < this.minimumNumberTablesAccessed) {
4743 this.minimumNumberTablesAccessed = numTablesAccessed;
4744 }
4745
4746 if (numTablesAccessed > this.maximumNumberTablesAccessed) {
4747 this.maximumNumberTablesAccessed = numTablesAccessed;
4748
4749 repartitionTablesAccessedHistogram();
4750 }
4751
4752 addToTablesAccessedHistogram(numTablesAccessed, 1);
4753 }
4754
4755 /**
4756 * Resets the server-side state of this connection. Doesn't work for MySQL
4757 * versions older than 4.0.6 or if isParanoid() is set (it will become a
4758 * no-op in these cases). Usually only used from connection pooling code.
4759 *
4760 * @throws SQLException
4761 * if the operation fails while resetting server state.
4762 */
4763 public void resetServerState() throws SQLException {
4764 if (!getParanoid()
4765 && ((this.io != null) && versionMeetsMinimum(4, 0, 6))) {
4766 changeUser(this.user, this.password);
4767 }
4768 }
4769
4770 /**
4771 * The method rollback() drops all changes made since the previous
4772 * commit/rollback and releases any database locks currently held by the
4773 * Connection.
4774 *
4775 * @exception SQLException
4776 * if a database access error occurs
4777 * @see commit
4778 */
4779 public void rollback() throws SQLException {
4780 synchronized (getMutex()) {
4781 checkClosed();
4782
4783 try {
4784 if (this.connectionLifecycleInterceptors != null) {
4785 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
4786
4787 void forEach(Object each) throws SQLException {
4788 if (!((ConnectionLifecycleInterceptor)each).rollback()) {
4789 this.stopIterating = true;
4790 }
4791 }
4792 };
4793
4794 iter.doForAll();
4795
4796 if (!iter.fullIteration()) {
4797 return;
4798 }
4799 }
4800 // no-op if _relaxAutoCommit == true
4801 if (this.autoCommit && !getRelaxAutoCommit()) {
4802 throw SQLError.createSQLException(
4803 "Can't call rollback when autocommit=true",
4804 SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
4805 } else if (this.transactionsSupported) {
4806 try {
4807 rollbackNoChecks();
4808 } catch (SQLException sqlEx) {
4809 // We ignore non-transactional tables if told to do so
4810 if (getIgnoreNonTxTables()
4811 && (sqlEx.getErrorCode() != SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
4812 throw sqlEx;
4813 }
4814 }
4815 }
4816 } catch (SQLException sqlException) {
4817 if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
4818 .equals(sqlException.getSQLState())) {
4819 throw SQLError.createSQLException(
4820 "Communications link failure during rollback(). Transaction resolution unknown.",
4821 SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor());
4822 }
4823
4824 throw sqlException;
4825 } finally {
4826 this.needsPing = this.getReconnectAtTxEnd();
4827 }
4828 }
4829 }
4830
4831 /**
4832 * @see Connection#rollback(Savepoint)
4833 */
4834 public void rollback(final Savepoint savepoint) throws SQLException {
4835
4836 if (versionMeetsMinimum(4, 0, 14) || versionMeetsMinimum(4, 1, 1)) {
4837 synchronized (getMutex()) {
4838 checkClosed();
4839
4840 try {
4841 if (this.connectionLifecycleInterceptors != null) {
4842 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
4843
4844 void forEach(Object each) throws SQLException {
4845 if (!((ConnectionLifecycleInterceptor)each).rollback(savepoint)) {
4846 this.stopIterating = true;
4847 }
4848 }
4849 };
4850
4851 iter.doForAll();
4852
4853 if (!iter.fullIteration()) {
4854 return;
4855 }
4856 }
4857
4858 StringBuffer rollbackQuery = new StringBuffer(
4859 "ROLLBACK TO SAVEPOINT ");
4860 rollbackQuery.append('`');
4861 rollbackQuery.append(savepoint.getSavepointName());
4862 rollbackQuery.append('`');
4863
4864 java.sql.Statement stmt = null;
4865
4866 try {
4867 stmt = getMetadataSafeStatement();
4868
4869 stmt.executeUpdate(rollbackQuery.toString());
4870 } catch (SQLException sqlEx) {
4871 int errno = sqlEx.getErrorCode();
4872
4873 if (errno == 1181) {
4874 String msg = sqlEx.getMessage();
4875
4876 if (msg != null) {
4877 int indexOfError153 = msg.indexOf("153");
4878
4879 if (indexOfError153 != -1) {
4880 throw SQLError.createSQLException("Savepoint '"
4881 + savepoint.getSavepointName()
4882 + "' does not exist",
4883 SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
4884 errno, getExceptionInterceptor());
4885 }
4886 }
4887 }
4888
4889 // We ignore non-transactional tables if told to do so
4890 if (getIgnoreNonTxTables()
4891 && (sqlEx.getErrorCode() != SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
4892 throw sqlEx;
4893 }
4894
4895 if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
4896 .equals(sqlEx.getSQLState())) {
4897 throw SQLError.createSQLException(
4898 "Communications link failure during rollback(). Transaction resolution unknown.",
4899 SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor());
4900 }
4901
4902 throw sqlEx;
4903 } finally {
4904 closeStatement(stmt);
4905 }
4906 } finally {
4907 this.needsPing = this.getReconnectAtTxEnd();
4908 }
4909 }
4910 } else {
4911 throw SQLError.notImplemented();
4912 }
4913 }
4914
4915 private void rollbackNoChecks() throws SQLException {
4916 if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) {
4917 if (!this.io.inTransactionOnServer()) {
4918 return; // effectively a no-op
4919 }
4920 }
4921
4922 execSQL(null, "rollback", -1, null,
4923 DEFAULT_RESULT_SET_TYPE,
4924 DEFAULT_RESULT_SET_CONCURRENCY, false,
4925 this.database, null, false);
4926 }
4927
4928 /**
4929 * @see java.sql.Connection#prepareStatement(String)
4930 */
4931 public java.sql.PreparedStatement serverPrepareStatement(String sql)
4932 throws SQLException {
4933
4934 String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
4935
4936 return ServerPreparedStatement.getInstance(this, nativeSql, this.getCatalog(),
4937 DEFAULT_RESULT_SET_TYPE,
4938 DEFAULT_RESULT_SET_CONCURRENCY);
4939 }
4940
4941 /**
4942 * @see java.sql.Connection#prepareStatement(String, int)
4943 */
4944 public java.sql.PreparedStatement serverPrepareStatement(String sql,
4945 int autoGenKeyIndex) throws SQLException {
4946 String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
4947
4948 PreparedStatement pStmt = ServerPreparedStatement.getInstance(this, nativeSql, this.getCatalog(),
4949 DEFAULT_RESULT_SET_TYPE,
4950 DEFAULT_RESULT_SET_CONCURRENCY);
4951
4952 pStmt.setRetrieveGeneratedKeys(
4953 autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);
4954
4955 return pStmt;
4956 }
4957
4958 /**
4959 * @see java.sql.Connection#prepareStatement(String, int, int)
4960 */
4961 public java.sql.PreparedStatement serverPrepareStatement(String sql,
4962 int resultSetType, int resultSetConcurrency) throws SQLException {
4963 String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;
4964
4965 return ServerPreparedStatement.getInstance(this, nativeSql, this.getCatalog(),
4966 resultSetType,
4967 resultSetConcurrency);
4968 }
4969
4970 /**
4971 * @see java.sql.Connection#prepareStatement(String, int, int, int)
4972 */
4973 public java.sql.PreparedStatement serverPrepareStatement(String sql,
4974 int resultSetType, int resultSetConcurrency,
4975 int resultSetHoldability) throws SQLException {
4976 if (getPedantic()) {
4977 if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
4978 throw SQLError.createSQLException(
4979 "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
4980 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4981 }
4982 }
4983
4984 return serverPrepareStatement(sql, resultSetType, resultSetConcurrency);
4985 }
4986
4987 /**
4988 * @see java.sql.Connection#prepareStatement(String, int[])
4989 */
4990 public java.sql.PreparedStatement serverPrepareStatement(String sql,
4991 int[] autoGenKeyIndexes) throws SQLException {
4992
4993 PreparedStatement pStmt = (PreparedStatement) serverPrepareStatement(sql);
4994
4995 pStmt
4996 .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
4997 && (autoGenKeyIndexes.length > 0));
4998
4999 return pStmt;
5000 }
5001
5002 /**
5003 * @see java.sql.Connection#prepareStatement(String, String[])
5004 */
5005 public java.sql.PreparedStatement serverPrepareStatement(String sql,
5006 String[] autoGenKeyColNames) throws SQLException {
5007 PreparedStatement pStmt = (PreparedStatement) serverPrepareStatement(sql);
5008
5009 pStmt
5010 .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
5011 && (autoGenKeyColNames.length > 0));
5012
5013 return pStmt;
5014 }
5015
5016 protected boolean serverSupportsConvertFn() throws SQLException {
5017 return versionMeetsMinimum(4, 0, 2);
5018 }
5019
5020 /**
5021 * If a connection is in auto-commit mode, than all its SQL statements will
5022 * be executed and committed as individual transactions. Otherwise, its SQL
5023 * statements are grouped into transactions that are terminated by either
5024 * commit() or rollback(). By default, new connections are in auto- commit
5025 * mode. The commit occurs when the statement completes or the next execute
5026 * occurs, whichever comes first. In the case of statements returning a
5027 * ResultSet, the statement completes when the last row of the ResultSet has
5028 * been retrieved or the ResultSet has been closed. In advanced cases, a
5029 * single statement may return multiple results as well as output parameter
5030 * values. Here the commit occurs when all results and output param values
5031 * have been retrieved.
5032 * <p>
5033 * <b>Note:</b> MySQL does not support transactions, so this method is a
5034 * no-op.
5035 * </p>
5036 *
5037 * @param autoCommitFlag -
5038 * true enables auto-commit; false disables it
5039 * @exception SQLException
5040 * if a database access error occurs
5041 */
5042 public void setAutoCommit(final boolean autoCommitFlag) throws SQLException {
5043 synchronized (getMutex()) {
5044 checkClosed();
5045
5046 if (this.connectionLifecycleInterceptors != null) {
5047 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
5048
5049 void forEach(Object each) throws SQLException {
5050 if (!((ConnectionLifecycleInterceptor)each).setAutoCommit(autoCommitFlag)) {
5051 this.stopIterating = true;
5052 }
5053 }
5054 };
5055
5056 iter.doForAll();
5057
5058 if (!iter.fullIteration()) {
5059 return;
5060 }
5061 }
5062
5063 if (getAutoReconnectForPools()) {
5064 setHighAvailability(true);
5065 }
5066
5067 try {
5068 if (this.transactionsSupported) {
5069
5070 boolean needsSetOnServer = true;
5071
5072 if (this.getUseLocalSessionState()
5073 && this.autoCommit == autoCommitFlag) {
5074 needsSetOnServer = false;
5075 } else if (!this.getHighAvailability()) {
5076 needsSetOnServer = this.getIO()
5077 .isSetNeededForAutoCommitMode(autoCommitFlag);
5078 }
5079
5080 // this internal value must be set first as failover depends on
5081 // it
5082 // being set to true to fail over (which is done by most
5083 // app servers and connection pools at the end of
5084 // a transaction), and the driver issues an implicit set
5085 // based on this value when it (re)-connects to a server
5086 // so the value holds across connections
5087 this.autoCommit = autoCommitFlag;
5088
5089 if (needsSetOnServer) {
5090 execSQL(null, autoCommitFlag ? "SET autocommit=1"
5091 : "SET autocommit=0", -1, null,
5092 DEFAULT_RESULT_SET_TYPE,
5093 DEFAULT_RESULT_SET_CONCURRENCY, false,
5094 this.database, null, false);
5095 }
5096
5097 } else {
5098 if ((autoCommitFlag == false) && !getRelaxAutoCommit()) {
5099 throw SQLError.createSQLException("MySQL Versions Older than 3.23.15 "
5100 + "do not support transactions",
5101 SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
5102 }
5103
5104 this.autoCommit = autoCommitFlag;
5105 }
5106 } finally {
5107 if (this.getAutoReconnectForPools()) {
5108 setHighAvailability(false);
5109 }
5110 }
5111
5112 //if (autoCommitFlag) {
5113 // if (this.io.isSetNeededForAutoCommitMode(true)) {
5114 // throw new RuntimeException();
5115 // }
5116 //}
5117
5118 return;
5119 }
5120 }
5121
5122 /**
5123 * A sub-space of this Connection's database may be selected by setting a
5124 * catalog name. If the driver does not support catalogs, it will silently
5125 * ignore this request
5126 * <p>
5127 * <b>Note:</b> MySQL's notion of catalogs are individual databases.
5128 * </p>
5129 *
5130 * @param catalog
5131 * the database for this connection to use
5132 * @throws SQLException
5133 * if a database access error occurs
5134 */
5135 public void setCatalog(final String catalog) throws SQLException {
5136 synchronized (getMutex()) {
5137 checkClosed();
5138
5139 if (catalog == null) {
5140 throw SQLError.createSQLException("Catalog can not be null",
5141 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
5142 }
5143
5144 if (this.connectionLifecycleInterceptors != null) {
5145 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
5146
5147 void forEach(Object each) throws SQLException {
5148 if (!((ConnectionLifecycleInterceptor)each).setCatalog(catalog)) {
5149 this.stopIterating = true;
5150 }
5151 }
5152 };
5153
5154 iter.doForAll();
5155
5156 if (!iter.fullIteration()) {
5157 return;
5158 }
5159 }
5160
5161 if (getUseLocalSessionState()) {
5162 if (this.lowerCaseTableNames) {
5163 if (this.database.equalsIgnoreCase(catalog)) {
5164 return;
5165 }
5166 } else {
5167 if (this.database.equals(catalog)) {
5168 return;
5169 }
5170 }
5171 }
5172
5173 String quotedId = this.dbmd.getIdentifierQuoteString();
5174
5175 if ((quotedId == null) || quotedId.equals(" ")) {
5176 quotedId = "";
5177 }
5178
5179 StringBuffer query = new StringBuffer("USE ");
5180 query.append(quotedId);
5181 query.append(catalog);
5182 query.append(quotedId);
5183
5184 execSQL(null, query.toString(), -1, null,
5185 DEFAULT_RESULT_SET_TYPE,
5186 DEFAULT_RESULT_SET_CONCURRENCY, false,
5187 this.database, null, false);
5188
5189 this.database = catalog;
5190 }
5191 }
5192
5193 /**
5194 * @param failedOver
5195 * The failedOver to set.
5196 */
5197 public synchronized void setFailedOver(boolean flag) {
5198 if (flag && getRoundRobinLoadBalance()) {
5199 return; // we don't failover for round-robin load-balanced connections
5200 }
5201
5202 this.failedOver = flag;
5203 }
5204
5205 /**
5206 * Sets state for a failed-over connection
5207 *
5208 * @throws SQLException
5209 * DOCUMENT ME!
5210 */
5211 private void setFailedOverState() throws SQLException {
5212 if (getRoundRobinLoadBalance()) {
5213 return; // we don't failover for round-robin load-balanced connections
5214 }
5215
5216 if (getFailOverReadOnly()) {
5217 setReadOnlyInternal(true);
5218 }
5219
5220 this.queriesIssuedFailedOver = 0;
5221 this.failedOver = true;
5222 this.masterFailTimeMillis = System.currentTimeMillis();
5223 }
5224
5225 /**
5226 * @see Connection#setHoldability(int)
5227 */
5228 public void setHoldability(int arg0) throws SQLException {
5229 // do nothing
5230 }
5231
5232 public void setInGlobalTx(boolean flag) {
5233 this.isInGlobalTx = flag;
5234 }
5235
5236 // exposed for testing
5237 /**
5238 * @param preferSlaveDuringFailover
5239 * The preferSlaveDuringFailover to set.
5240 */
5241 public void setPreferSlaveDuringFailover(boolean flag) {
5242 this.preferSlaveDuringFailover = flag;
5243 }
5244
5245 void setReadInfoMsgEnabled(boolean flag) {
5246 this.readInfoMsg = flag;
5247 }
5248
5249 /**
5250 * You can put a connection in read-only mode as a hint to enable database
5251 * optimizations <B>Note:</B> setReadOnly cannot be called while in the
5252 * middle of a transaction
5253 *
5254 * @param readOnlyFlag -
5255 * true enables read-only mode; false disables it
5256 * @exception SQLException
5257 * if a database access error occurs
5258 */
5259 public void setReadOnly(boolean readOnlyFlag) throws SQLException {
5260 checkClosed();
5261
5262 // Ignore calls to this method if we're failed over and
5263 // we're configured to fail over read-only.
5264 if (this.failedOver && getFailOverReadOnly() && !readOnlyFlag) {
5265 return;
5266 }
5267
5268 setReadOnlyInternal(readOnlyFlag);
5269 }
5270
5271 protected void setReadOnlyInternal(boolean readOnlyFlag) throws SQLException {
5272 this.readOnly = readOnlyFlag;
5273 }
5274
5275 /**
5276 * @see Connection#setSavepoint()
5277 */
5278 public java.sql.Savepoint setSavepoint() throws SQLException {
5279 MysqlSavepoint savepoint = new MysqlSavepoint(getExceptionInterceptor());
5280
5281 setSavepoint(savepoint);
5282
5283 return savepoint;
5284 }
5285
5286 private void setSavepoint(MysqlSavepoint savepoint) throws SQLException {
5287
5288 if (versionMeetsMinimum(4, 0, 14) || versionMeetsMinimum(4, 1, 1)) {
5289 synchronized (getMutex()) {
5290 checkClosed();
5291
5292 StringBuffer savePointQuery = new StringBuffer("SAVEPOINT ");
5293 savePointQuery.append('`');
5294 savePointQuery.append(savepoint.getSavepointName());
5295 savePointQuery.append('`');
5296
5297 java.sql.Statement stmt = null;
5298
5299 try {
5300 stmt = getMetadataSafeStatement();
5301
5302 stmt.executeUpdate(savePointQuery.toString());
5303 } finally {
5304 closeStatement(stmt);
5305 }
5306 }
5307 } else {
5308 throw SQLError.notImplemented();
5309 }
5310 }
5311
5312 /**
5313 * @see Connection#setSavepoint(String)
5314 */
5315 public synchronized java.sql.Savepoint setSavepoint(String name) throws SQLException {
5316 MysqlSavepoint savepoint = new MysqlSavepoint(name, getExceptionInterceptor());
5317
5318 setSavepoint(savepoint);
5319
5320 return savepoint;
5321 }
5322
5323 /**
5324 *
5325 */
5326 private void setSessionVariables() throws SQLException {
5327 if (this.versionMeetsMinimum(4, 0, 0) && getSessionVariables() != null) {
5328 List variablesToSet = StringUtils.split(getSessionVariables(), ",", "\"'", "\"'",
5329 false);
5330
5331 int numVariablesToSet = variablesToSet.size();
5332
5333 java.sql.Statement stmt = null;
5334
5335 try {
5336 stmt = getMetadataSafeStatement();
5337
5338 for (int i = 0; i < numVariablesToSet; i++) {
5339 String variableValuePair = (String) variablesToSet.get(i);
5340
5341 if (variableValuePair.startsWith("@")) {
5342 stmt.executeUpdate("SET " + variableValuePair);
5343 } else {
5344 stmt.executeUpdate("SET SESSION " + variableValuePair);
5345 }
5346 }
5347 } finally {
5348 if (stmt != null) {
5349 stmt.close();
5350 }
5351 }
5352 }
5353
5354 }
5355
5356 /**
5357 * DOCUMENT ME!
5358 *
5359 * @param level
5360 * DOCUMENT ME!
5361 * @throws SQLException
5362 * DOCUMENT ME!
5363 */
5364 public synchronized void setTransactionIsolation(int level) throws SQLException {
5365 checkClosed();
5366
5367 if (this.hasIsolationLevels) {
5368 String sql = null;
5369
5370 boolean shouldSendSet = false;
5371
5372 if (getAlwaysSendSetIsolation()) {
5373 shouldSendSet = true;
5374 } else {
5375 if (level != this.isolationLevel) {
5376 shouldSendSet = true;
5377 }
5378 }
5379
5380 if (getUseLocalSessionState()) {
5381 shouldSendSet = this.isolationLevel != level;
5382 }
5383
5384 if (shouldSendSet) {
5385 switch (level) {
5386 case java.sql.Connection.TRANSACTION_NONE:
5387 throw SQLError.createSQLException("Transaction isolation level "
5388 + "NONE not supported by MySQL", getExceptionInterceptor());
5389
5390 case java.sql.Connection.TRANSACTION_READ_COMMITTED:
5391 sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED";
5392
5393 break;
5394
5395 case java.sql.Connection.TRANSACTION_READ_UNCOMMITTED:
5396 sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
5397
5398 break;
5399
5400 case java.sql.Connection.TRANSACTION_REPEATABLE_READ:
5401 sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
5402
5403 break;
5404
5405 case java.sql.Connection.TRANSACTION_SERIALIZABLE:
5406 sql = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE";
5407
5408 break;
5409
5410 default:
5411 throw SQLError.createSQLException("Unsupported transaction "
5412 + "isolation level '" + level + "'",
5413 SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
5414 }
5415
5416 execSQL(null, sql, -1, null,
5417 DEFAULT_RESULT_SET_TYPE,
5418 DEFAULT_RESULT_SET_CONCURRENCY,false,
5419 this.database, null, false);
5420
5421 this.isolationLevel = level;
5422 }
5423 } else {
5424 throw SQLError.createSQLException("Transaction Isolation Levels are "
5425 + "not supported on MySQL versions older than 3.23.36.",
5426 SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
5427 }
5428 }
5429
5430 /**
5431 * JDBC 2.0 Install a type-map object as the default type-map for this
5432 * connection
5433 *
5434 * @param map
5435 * the type mapping
5436 * @throws SQLException
5437 * if a database error occurs.
5438 */
5439 public synchronized void setTypeMap(java.util.Map map) throws SQLException {
5440 this.typeMap = map;
5441 }
5442
5443 private void setupServerForTruncationChecks() throws SQLException {
5444 if (getJdbcCompliantTruncation()) {
5445 if (versionMeetsMinimum(5, 0, 2)) {
5446 String currentSqlMode =
5447 (String)this.serverVariables.get("sql_mode");
5448
5449 boolean strictTransTablesIsSet = StringUtils.indexOfIgnoreCase(currentSqlMode, "STRICT_TRANS_TABLES") != -1;
5450
5451 if (currentSqlMode == null ||
5452 currentSqlMode.length() == 0 || !strictTransTablesIsSet) {
5453 StringBuffer commandBuf = new StringBuffer("SET sql_mode='");
5454
5455 if (currentSqlMode != null && currentSqlMode.length() > 0) {
5456 commandBuf.append(currentSqlMode);
5457 commandBuf.append(",");
5458 }
5459
5460 commandBuf.append("STRICT_TRANS_TABLES'");
5461
5462 execSQL(null, commandBuf.toString(), -1, null,
5463 DEFAULT_RESULT_SET_TYPE,
5464 DEFAULT_RESULT_SET_CONCURRENCY, false,
5465 this.database, null, false);
5466
5467 setJdbcCompliantTruncation(false); // server's handling this for us now
5468 } else if (strictTransTablesIsSet) {
5469 // We didn't set it, but someone did, so we piggy back on it
5470 setJdbcCompliantTruncation(false); // server's handling this for us now
5471 }
5472
5473 }
5474 }
5475 }
5476
5477 /**
5478 * Should we try to connect back to the master? We try when we've been
5479 * failed over >= this.secondsBeforeRetryMaster _or_ we've issued >
5480 * this.queriesIssuedFailedOver
5481 *
5482 * @return DOCUMENT ME!
5483 */
5484 private boolean shouldFallBack() {
5485 long secondsSinceFailedOver = (System.currentTimeMillis() - this.masterFailTimeMillis) / 1000;
5486
5487 // Done this way so we can set a condition in the debugger
5488 boolean tryFallback = ((secondsSinceFailedOver >= getSecondsBeforeRetryMaster()) || (this.queriesIssuedFailedOver >= getQueriesBeforeRetryMaster()));
5489
5490 return tryFallback;
5491 }
5492
5493 /**
5494 * Used by MiniAdmin to shutdown a MySQL server
5495 *
5496 * @throws SQLException
5497 * if the command can not be issued.
5498 */
5499 public void shutdownServer() throws SQLException {
5500 try {
5501 this.io.sendCommand(MysqlDefs.SHUTDOWN, null, null, false, null, 0);
5502 } catch (Exception ex) {
5503 SQLException sqlEx = SQLError.createSQLException(
5504 Messages.getString("Connection.UnhandledExceptionDuringShutdown"),
5505 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
5506
5507 sqlEx.initCause(ex);
5508
5509 throw sqlEx;
5510 }
5511 }
5512
5513 /**
5514 * DOCUMENT ME!
5515 *
5516 * @return DOCUMENT ME!
5517 */
5518 public boolean supportsIsolationLevel() {
5519 return this.hasIsolationLevels;
5520 }
5521
5522 /**
5523 * DOCUMENT ME!
5524 *
5525 * @return DOCUMENT ME!
5526 */
5527 public boolean supportsQuotedIdentifiers() {
5528 return this.hasQuotedIdentifiers;
5529 }
5530
5531 /**
5532 * DOCUMENT ME!
5533 *
5534 * @return DOCUMENT ME!
5535 */
5536 public boolean supportsTransactions() {
5537 return this.transactionsSupported;
5538 }
5539
5540 /**
5541 * Remove the given statement from the list of open statements
5542 *
5543 * @param stmt
5544 * the Statement instance to remove
5545 */
5546 void unregisterStatement(Statement stmt) {
5547 if (this.openStatements != null) {
5548 synchronized (this.openStatements) {
5549 this.openStatements.remove(stmt);
5550 }
5551 }
5552 }
5553
5554 /**
5555 * Called by statements on their .close() to let the connection know when it
5556 * is safe to set the connection back to 'default' row limits.
5557 *
5558 * @param stmt
5559 * the statement releasing it's max-rows requirement
5560 * @throws SQLException
5561 * if a database error occurs issuing the statement that sets
5562 * the limit default.
5563 */
5564 void unsetMaxRows(Statement stmt) throws SQLException {
5565 synchronized (this.mutex) {
5566 if (this.statementsUsingMaxRows != null) {
5567 Object found = this.statementsUsingMaxRows.remove(stmt);
5568
5569 if ((found != null)
5570 && (this.statementsUsingMaxRows.size() == 0)) {
5571 execSQL(null, "SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1,
5572 null, DEFAULT_RESULT_SET_TYPE,
5573 DEFAULT_RESULT_SET_CONCURRENCY, false,
5574 this.database, null, false);
5575
5576 this.maxRowsChanged = false;
5577 }
5578 }
5579 }
5580 }
5581
5582 boolean useAnsiQuotedIdentifiers() {
5583 return this.useAnsiQuotes;
5584 }
5585
5586 /**
5587 * Has maxRows() been set?
5588 *
5589 * @return DOCUMENT ME!
5590 */
5591 boolean useMaxRows() {
5592 synchronized (this.mutex) {
5593 return this.maxRowsChanged;
5594 }
5595 }
5596
5597 public boolean versionMeetsMinimum(int major, int minor, int subminor)
5598 throws SQLException {
5599 checkClosed();
5600
5601 return this.io.versionMeetsMinimum(major, minor, subminor);
5602 }
5603
5604 /**
5605 * Returns cached metadata (or null if not cached) for the given query,
5606 * which must match _exactly_.
5607 *
5608 * This method is synchronized by the caller on getMutex(), so if
5609 * calling this method from internal code in the driver, make sure it's
5610 * synchronized on the mutex that guards communication with the server.
5611 *
5612 * @param sql
5613 * the query that is the key to the cache
5614 *
5615 * @return metadata cached for the given SQL, or none if it doesn't
5616 * exist.
5617 */
5618 protected CachedResultSetMetaData getCachedMetaData(String sql) {
5619 if (this.resultSetMetadataCache != null) {
5620 synchronized (this.resultSetMetadataCache) {
5621 return (CachedResultSetMetaData) this.resultSetMetadataCache
5622 .get(sql);
5623 }
5624 }
5625
5626 return null; // no cache exists
5627 }
5628
5629 /**
5630 * Caches CachedResultSetMetaData that has been placed in the cache using
5631 * the given SQL as a key.
5632 *
5633 * This method is synchronized by the caller on getMutex(), so if
5634 * calling this method from internal code in the driver, make sure it's
5635 * synchronized on the mutex that guards communication with the server.
5636 *
5637 * @param sql the query that the metadata pertains too.
5638 * @param cachedMetaData metadata (if it exists) to populate the cache.
5639 * @param resultSet the result set to retreive metadata from, or apply to.
5640 *
5641 * @throws SQLException
5642 */
5643 protected void initializeResultsMetadataFromCache(String sql,
5644 CachedResultSetMetaData cachedMetaData, ResultSetInternalMethods resultSet)
5645 throws SQLException {
5646
5647 if (cachedMetaData == null) {
5648
5649 // read from results
5650 cachedMetaData = new CachedResultSetMetaData();
5651
5652 // assume that users will use named-based
5653 // lookups
5654 resultSet.buildIndexMapping();
5655 resultSet.initializeWithMetadata();
5656
5657 if (resultSet instanceof UpdatableResultSet) {
5658 ((UpdatableResultSet)resultSet).checkUpdatability();
5659 }
5660
5661 resultSet.populateCachedMetaData(cachedMetaData);
5662
5663 this.resultSetMetadataCache.put(sql, cachedMetaData);
5664 } else {
5665 resultSet.initializeFromCachedMetaData(cachedMetaData);
5666 resultSet.initializeWithMetadata();
5667
5668 if (resultSet instanceof UpdatableResultSet) {
5669 ((UpdatableResultSet)resultSet).checkUpdatability();
5670 }
5671 }
5672 }
5673
5674 /**
5675 * Returns the comment that will be prepended to all statements
5676 * sent to the server.
5677 *
5678 * @return the comment that will be prepended to all statements
5679 * sent to the server.
5680 */
5681 public String getStatementComment() {
5682 return this.statementComment;
5683 }
5684
5685 /**
5686 * Sets the comment that will be prepended to all statements
5687 * sent to the server. Do not use slash-star or star-slash tokens
5688 * in the comment as these will be added by the driver itself.
5689 *
5690 * @param comment the comment that will be prepended to all statements
5691 * sent to the server.
5692 */
5693 public void setStatementComment(String comment) {
5694 this.statementComment = comment;
5695 }
5696
5697 public synchronized void reportQueryTime(long millisOrNanos) {
5698 this.queryTimeCount++;
5699 this.queryTimeSum += millisOrNanos;
5700 this.queryTimeSumSquares += (millisOrNanos * millisOrNanos);
5701 this.queryTimeMean = ((this.queryTimeMean * (this.queryTimeCount - 1)) + millisOrNanos)
5702 / this.queryTimeCount;
5703 }
5704
5705 public synchronized boolean isAbonormallyLongQuery(long millisOrNanos) {
5706 if (this.queryTimeCount < 15) {
5707 return false; // need a minimum amount for this to make sense
5708 }
5709
5710 double stddev = Math.sqrt((this.queryTimeSumSquares - ((this.queryTimeSum*this.queryTimeSum) / this.queryTimeCount)) / (this.queryTimeCount - 1));
5711
5712 return millisOrNanos > (this.queryTimeMean + 5 * stddev);
5713 }
5714
5715 public void initializeExtension(Extension ex) throws SQLException {
5716 ex.init(this, this.props);
5717 }
5718
5719 protected void transactionBegun() throws SQLException {
5720 if (this.connectionLifecycleInterceptors != null) {
5721 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
5722
5723 void forEach(Object each) throws SQLException {
5724 ((ConnectionLifecycleInterceptor)each).transactionBegun();
5725 }
5726 };
5727
5728 iter.doForAll();
5729 }
5730 }
5731
5732 protected void transactionCompleted() throws SQLException {
5733 if (this.connectionLifecycleInterceptors != null) {
5734 IterateBlock iter = new IterateBlock(this.connectionLifecycleInterceptors.iterator()) {
5735
5736 void forEach(Object each) throws SQLException {
5737 ((ConnectionLifecycleInterceptor)each).transactionCompleted();
5738 }
5739 };
5740
5741 iter.doForAll();
5742 }
5743 }
5744
5745 public boolean storesLowerCaseTableName() {
5746 return storesLowerCaseTableName;
5747 }
5748
5749 private ExceptionInterceptor exceptionInterceptor;
5750
5751 public ExceptionInterceptor getExceptionInterceptor() {
5752 return this.exceptionInterceptor;
5753 }
5754
5755 public boolean getRequiresEscapingEncoder() {
5756 return requiresEscapingEncoder;
5757 }
5758 }