0001: /*
0002: Copyright (C) 2002-2007 MySQL AB
0003:
0004: This program is free software; you can redistribute it and/or modify
0005: it under the terms of version 2 of the GNU General Public License as
0006: published by the Free Software Foundation.
0007:
0008: There are special exceptions to the terms and conditions of the GPL
0009: as it is applied to this software. View the full text of the
0010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
0011: software distribution.
0012:
0013: This program is distributed in the hope that it will be useful,
0014: but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: GNU General Public License for more details.
0017:
0018: You should have received a copy of the GNU General Public License
0019: along with this program; if not, write to the Free Software
0020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0021:
0022:
0023:
0024: */
0025: package com.mysql.jdbc;
0026:
0027: import java.io.IOException;
0028: import java.io.UnsupportedEncodingException;
0029: import java.lang.reflect.Array;
0030: import java.lang.reflect.Constructor;
0031: import java.lang.reflect.Method;
0032: import java.sql.Blob;
0033: import java.sql.DatabaseMetaData;
0034: import java.sql.SQLException;
0035: import java.sql.SQLWarning;
0036: import java.sql.Savepoint;
0037: import java.util.ArrayList;
0038: import java.util.Calendar;
0039: import java.util.GregorianCalendar;
0040: import java.util.HashMap;
0041: import java.util.Iterator;
0042: import java.util.List;
0043: import java.util.Locale;
0044: import java.util.Map;
0045: import java.util.Properties;
0046: import java.util.Stack;
0047: import java.util.StringTokenizer;
0048: import java.util.TimeZone;
0049: import java.util.Timer;
0050: import java.util.TreeMap;
0051:
0052: import com.mysql.jdbc.log.Log;
0053: import com.mysql.jdbc.log.LogFactory;
0054: import com.mysql.jdbc.log.NullLogger;
0055: import com.mysql.jdbc.profiler.ProfileEventSink;
0056: import com.mysql.jdbc.profiler.ProfilerEvent;
0057: import com.mysql.jdbc.util.LRUCache;
0058:
0059: /**
0060: * A Connection represents a session with a specific database. Within the
0061: * context of a Connection, SQL statements are executed and results are
0062: * returned.
0063: * <P>
0064: * A Connection's database is able to provide information describing its tables,
0065: * its supported SQL grammar, its stored procedures, the capabilities of this
0066: * connection, etc. This information is obtained with the getMetaData method.
0067: * </p>
0068: *
0069: * @author Mark Matthews
0070: * @version $Id: ConnectionImpl.java 6562 2007-09-05 16:02:17Z mmatthews $
0071: * @see java.sql.Connection
0072: */
0073: public class ConnectionImpl extends ConnectionPropertiesImpl implements
0074: Connection {
0075: private static final String JDBC_LOCAL_CHARACTER_SET_RESULTS = "jdbc.local.character_set_results";
0076:
0077: /**
0078: * Used as a key for caching callable statements which (may) depend on
0079: * current catalog...In 5.0.x, they don't (currently), but stored procedure
0080: * names soon will, so current catalog is a (hidden) component of the name.
0081: */
0082: class CompoundCacheKey {
0083: String componentOne;
0084:
0085: String componentTwo;
0086:
0087: int hashCode;
0088:
0089: CompoundCacheKey(String partOne, String partTwo) {
0090: this .componentOne = partOne;
0091: this .componentTwo = partTwo;
0092:
0093: // Handle first component (in most cases, currentCatalog)
0094: // being NULL....
0095: this .hashCode = (((this .componentOne != null) ? this .componentOne
0096: : "") + this .componentTwo).hashCode();
0097: }
0098:
0099: /*
0100: * (non-Javadoc)
0101: *
0102: * @see java.lang.Object#equals(java.lang.Object)
0103: */
0104: public boolean equals(Object obj) {
0105: if (obj instanceof CompoundCacheKey) {
0106: CompoundCacheKey another = (CompoundCacheKey) obj;
0107:
0108: boolean firstPartEqual = false;
0109:
0110: if (this .componentOne == null) {
0111: firstPartEqual = (another.componentOne == null);
0112: } else {
0113: firstPartEqual = this .componentOne
0114: .equals(another.componentOne);
0115: }
0116:
0117: return (firstPartEqual && this .componentTwo
0118: .equals(another.componentTwo));
0119: }
0120:
0121: return false;
0122: }
0123:
0124: /*
0125: * (non-Javadoc)
0126: *
0127: * @see java.lang.Object#hashCode()
0128: */
0129: public int hashCode() {
0130: return this .hashCode;
0131: }
0132: }
0133:
0134: /**
0135: * Marker for character set converter not being available (not written,
0136: * multibyte, etc) Used to prevent multiple instantiation requests.
0137: */
0138: private static final Object CHARSET_CONVERTER_NOT_AVAILABLE_MARKER = new Object();
0139:
0140: /**
0141: * The mapping between MySQL charset names and Java charset names.
0142: * Initialized by loadCharacterSetMapping()
0143: */
0144: public static Map charsetMap;
0145:
0146: /** Default logger class name */
0147: protected static final String DEFAULT_LOGGER_CLASS = "com.mysql.jdbc.log.StandardLogger";
0148:
0149: private final static int HISTOGRAM_BUCKETS = 20;
0150:
0151: /** Logger instance name */
0152: private static final String LOGGER_INSTANCE_NAME = "MySQL";
0153:
0154: /**
0155: * Map mysql transaction isolation level name to
0156: * java.sql.Connection.TRANSACTION_XXX
0157: */
0158: private static Map mapTransIsolationNameToValue = null;
0159:
0160: /** Null logger shared by all connections at startup */
0161: private static final Log NULL_LOGGER = new NullLogger(
0162: LOGGER_INSTANCE_NAME);
0163:
0164: private static Map roundRobinStatsMap;
0165:
0166: private static final Map serverCollationByUrl = new HashMap();
0167:
0168: private static final Map serverConfigByUrl = new HashMap();
0169:
0170: private static Timer cancelTimer;
0171: private static final Constructor JDBC_4_CONNECTION_CTOR;
0172:
0173: static {
0174: mapTransIsolationNameToValue = new HashMap(8);
0175: mapTransIsolationNameToValue.put("READ-UNCOMMITED", Constants
0176: .integerValueOf(TRANSACTION_READ_UNCOMMITTED));
0177: mapTransIsolationNameToValue.put("READ-UNCOMMITTED", Constants
0178: .integerValueOf(TRANSACTION_READ_UNCOMMITTED));
0179: mapTransIsolationNameToValue.put("READ-COMMITTED", Constants
0180: .integerValueOf(TRANSACTION_READ_COMMITTED));
0181: mapTransIsolationNameToValue.put("REPEATABLE-READ", Constants
0182: .integerValueOf(TRANSACTION_REPEATABLE_READ));
0183: mapTransIsolationNameToValue.put("SERIALIZABLE", Constants
0184: .integerValueOf(TRANSACTION_SERIALIZABLE));
0185:
0186: boolean createdNamedTimer = false;
0187:
0188: // Use reflection magic to try this on JDK's 1.5 and newer, fallback to non-named
0189: // timer on older VMs.
0190: try {
0191: Constructor ctr = Timer.class.getConstructor(new Class[] {
0192: String.class, Boolean.TYPE });
0193:
0194: cancelTimer = (Timer) ctr
0195: .newInstance(new Object[] {
0196: "MySQL Statement Cancellation Timer",
0197: Boolean.TRUE });
0198: createdNamedTimer = true;
0199: } catch (Throwable t) {
0200: createdNamedTimer = false;
0201: }
0202:
0203: if (!createdNamedTimer) {
0204: cancelTimer = new Timer(true);
0205: }
0206:
0207: if (Util.isJdbc4()) {
0208: try {
0209: JDBC_4_CONNECTION_CTOR = Class.forName(
0210: "com.mysql.jdbc.JDBC4Connection")
0211: .getConstructor(
0212: new Class[] { String.class,
0213: Integer.TYPE, Properties.class,
0214: String.class, String.class });
0215: } catch (SecurityException e) {
0216: throw new RuntimeException(e);
0217: } catch (NoSuchMethodException e) {
0218: throw new RuntimeException(e);
0219: } catch (ClassNotFoundException e) {
0220: throw new RuntimeException(e);
0221: }
0222: } else {
0223: JDBC_4_CONNECTION_CTOR = null;
0224: }
0225: }
0226:
0227: protected static SQLException appendMessageToException(
0228: SQLException sqlEx, String messageToAppend) {
0229: String origMessage = sqlEx.getMessage();
0230: String sqlState = sqlEx.getSQLState();
0231: int vendorErrorCode = sqlEx.getErrorCode();
0232:
0233: StringBuffer messageBuf = new StringBuffer(origMessage.length()
0234: + messageToAppend.length());
0235: messageBuf.append(origMessage);
0236: messageBuf.append(messageToAppend);
0237:
0238: SQLException sqlExceptionWithNewMessage = SQLError
0239: .createSQLException(messageBuf.toString(), sqlState,
0240: vendorErrorCode);
0241:
0242: //
0243: // Try and maintain the original stack trace,
0244: // only works on JDK-1.4 and newer
0245: //
0246:
0247: try {
0248: // Have to do this with reflection, otherwise older JVMs croak
0249: Method getStackTraceMethod = null;
0250: Method setStackTraceMethod = null;
0251: Object theStackTraceAsObject = null;
0252:
0253: Class stackTraceElementClass = Class
0254: .forName("java.lang.StackTraceElement");
0255: Class stackTraceElementArrayClass = Array.newInstance(
0256: stackTraceElementClass, new int[] { 0 }).getClass();
0257:
0258: getStackTraceMethod = Throwable.class.getMethod(
0259: "getStackTrace", new Class[] {});
0260:
0261: setStackTraceMethod = Throwable.class.getMethod(
0262: "setStackTrace",
0263: new Class[] { stackTraceElementArrayClass });
0264:
0265: if (getStackTraceMethod != null
0266: && setStackTraceMethod != null) {
0267: theStackTraceAsObject = getStackTraceMethod.invoke(
0268: sqlEx, new Object[0]);
0269: setStackTraceMethod.invoke(sqlExceptionWithNewMessage,
0270: new Object[] { theStackTraceAsObject });
0271: }
0272: } catch (NoClassDefFoundError noClassDefFound) {
0273:
0274: } catch (NoSuchMethodException noSuchMethodEx) {
0275:
0276: } catch (Throwable catchAll) {
0277:
0278: }
0279:
0280: return sqlExceptionWithNewMessage;
0281: }
0282:
0283: protected static Timer getCancelTimer() {
0284: return cancelTimer;
0285: }
0286:
0287: /**
0288: * Creates a connection instance -- We need to provide factory-style methods
0289: * so we can support both JDBC3 (and older) and JDBC4 runtimes, otherwise
0290: * the class verifier complains when it tries to load JDBC4-only interface
0291: * classes that are present in JDBC4 method signatures.
0292: */
0293:
0294: protected static Connection getInstance(String hostToConnectTo,
0295: int portToConnectTo, Properties info,
0296: String databaseToConnectTo, String url) throws SQLException {
0297: if (!Util.isJdbc4()) {
0298: return new ConnectionImpl(hostToConnectTo, portToConnectTo,
0299: info, databaseToConnectTo, url);
0300: }
0301:
0302: return (Connection) Util.handleNewInstance(
0303: JDBC_4_CONNECTION_CTOR, new Object[] { hostToConnectTo,
0304: Constants.integerValueOf(portToConnectTo),
0305: info, databaseToConnectTo, url });
0306: }
0307:
0308: private static synchronized int getNextRoundRobinHostIndex(
0309: String url, List hostList) {
0310: if (roundRobinStatsMap == null) {
0311: roundRobinStatsMap = new HashMap();
0312: }
0313:
0314: int[] index = (int[]) roundRobinStatsMap.get(url);
0315:
0316: if (index == null) {
0317: index = new int[1];
0318: index[0] = -1;
0319:
0320: roundRobinStatsMap.put(url, index);
0321: }
0322:
0323: index[0]++;
0324:
0325: if (index[0] >= hostList.size()) {
0326: index[0] = 0;
0327: }
0328:
0329: return index[0];
0330: }
0331:
0332: private static boolean nullSafeCompare(String s1, String s2) {
0333: if (s1 == null && s2 == null) {
0334: return true;
0335: }
0336:
0337: if (s1 == null && s2 != null) {
0338: return false;
0339: }
0340:
0341: return s1.equals(s2);
0342: }
0343:
0344: /** Are we in autoCommit mode? */
0345: private boolean autoCommit = true;
0346:
0347: /** A map of SQL to parsed prepared statement parameters. */
0348: private Map cachedPreparedStatementParams;
0349:
0350: /**
0351: * For servers > 4.1.0, what character set is the metadata returned in?
0352: */
0353: private String characterSetMetadata = null;
0354:
0355: /**
0356: * The character set we want results and result metadata returned in (null ==
0357: * results in any charset, metadata in UTF-8).
0358: */
0359: private String characterSetResultsOnServer = null;
0360:
0361: /**
0362: * Holds cached mappings to charset converters to avoid static
0363: * synchronization and at the same time save memory (each charset converter
0364: * takes approx 65K of static data).
0365: */
0366: private Map charsetConverterMap = new HashMap(CharsetMapping
0367: .getNumberOfCharsetsConfigured());
0368:
0369: /**
0370: * The mapping between MySQL charset names and the max number of chars in
0371: * them. Lazily instantiated via getMaxBytesPerChar().
0372: */
0373: private Map charsetToNumBytesMap;
0374:
0375: /** The point in time when this connection was created */
0376: private long connectionCreationTimeMillis = 0;
0377:
0378: /** ID used when profiling */
0379: private long connectionId;
0380:
0381: /** The database we're currently using (called Catalog in JDBC terms). */
0382: private String database = null;
0383:
0384: /** Internal DBMD to use for various database-version specific features */
0385: private DatabaseMetaData dbmd = null;
0386:
0387: private TimeZone defaultTimeZone;
0388:
0389: /** The event sink to use for profiling */
0390: private ProfileEventSink eventSink;
0391:
0392: private boolean executingFailoverReconnect = false;
0393:
0394: /** Are we failed-over to a non-master host */
0395: private boolean failedOver = false;
0396:
0397: /** Why was this connection implicitly closed, if known? (for diagnostics) */
0398: private Throwable forceClosedReason;
0399:
0400: /** Where was this connection implicitly closed? (for diagnostics) */
0401: private Throwable forcedClosedLocation;
0402:
0403: /** Does the server suuport isolation levels? */
0404: private boolean hasIsolationLevels = false;
0405:
0406: /** Does this version of MySQL support quoted identifiers? */
0407: private boolean hasQuotedIdentifiers = false;
0408:
0409: /** The hostname we're connected to */
0410: private String host = null;
0411:
0412: /** The list of host(s) to try and connect to */
0413: private List hostList = null;
0414:
0415: /** How many hosts are in the host list? */
0416: private int hostListSize = 0;
0417:
0418: /**
0419: * We need this 'bootstrapped', because 4.1 and newer will send fields back
0420: * with this even before we fill this dynamically from the server.
0421: */
0422: private String[] indexToCharsetMapping = CharsetMapping.INDEX_TO_CHARSET;
0423:
0424: /** The I/O abstraction interface (network conn to MySQL server */
0425: private MysqlIO io = null;
0426:
0427: private boolean isClientTzUTC = false;
0428:
0429: /** Has this connection been closed? */
0430: private boolean isClosed = true;
0431:
0432: /** Is this connection associated with a global tx? */
0433: private boolean isInGlobalTx = false;
0434:
0435: /** Is this connection running inside a JDK-1.3 VM? */
0436: private boolean isRunningOnJDK13 = false;
0437:
0438: /** isolation level */
0439: private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED;
0440:
0441: private boolean isServerTzUTC = false;
0442:
0443: /** When did the last query finish? */
0444: private long lastQueryFinishedTime = 0;
0445:
0446: /** The logger we're going to use */
0447: private Log log = NULL_LOGGER;
0448:
0449: /**
0450: * If gathering metrics, what was the execution time of the longest query so
0451: * far ?
0452: */
0453: private long longestQueryTimeMs = 0;
0454:
0455: /** Is the server configured to use lower-case table names only? */
0456: private boolean lowerCaseTableNames = false;
0457:
0458: /** When did the master fail? */
0459: private long masterFailTimeMillis = 0L;
0460:
0461: /**
0462: * The largest packet we can send (changed once we know what the server
0463: * supports, we get this at connection init).
0464: */
0465: private int maxAllowedPacket = 65536;
0466:
0467: private long maximumNumberTablesAccessed = 0;
0468:
0469: /** Has the max-rows setting been changed from the default? */
0470: private boolean maxRowsChanged = false;
0471:
0472: /** When was the last time we reported metrics? */
0473: private long metricsLastReportedMs;
0474:
0475: private long minimumNumberTablesAccessed = Long.MAX_VALUE;
0476:
0477: /** Mutex */
0478: private final Object mutex = new Object();
0479:
0480: /** The JDBC URL we're using */
0481: private String myURL = null;
0482:
0483: /** Does this connection need to be tested? */
0484: private boolean needsPing = false;
0485:
0486: private int netBufferLength = 16384;
0487:
0488: private boolean noBackslashEscapes = false;
0489:
0490: private long numberOfPreparedExecutes = 0;
0491:
0492: private long numberOfPrepares = 0;
0493:
0494: private long numberOfQueriesIssued = 0;
0495:
0496: private long numberOfResultSetsCreated = 0;
0497:
0498: private long[] numTablesMetricsHistBreakpoints;
0499:
0500: private int[] numTablesMetricsHistCounts;
0501:
0502: private long[] oldHistBreakpoints = null;
0503:
0504: private int[] oldHistCounts = null;
0505:
0506: /** A map of currently open statements */
0507: private Map openStatements;
0508:
0509: private LRUCache parsedCallableStatementCache;
0510:
0511: private boolean parserKnowsUnicode = false;
0512:
0513: /** The password we used */
0514: private String password = null;
0515:
0516: private long[] perfMetricsHistBreakpoints;
0517:
0518: private int[] perfMetricsHistCounts;
0519:
0520: /** Point of origin where this Connection was created */
0521: private Throwable pointOfOrigin;
0522:
0523: /** The port number we're connected to (defaults to 3306) */
0524: private int port = 3306;
0525:
0526: /**
0527: * Used only when testing failover functionality for regressions, causes the
0528: * failover code to not retry the master first
0529: */
0530: private boolean preferSlaveDuringFailover = false;
0531:
0532: /** Properties for this connection specified by user */
0533: protected Properties props = null;
0534:
0535: /** Number of queries we've issued since the master failed */
0536: private long queriesIssuedFailedOver = 0;
0537:
0538: /** Should we retrieve 'info' messages from the server? */
0539: private boolean readInfoMsg = false;
0540:
0541: /** Are we in read-only mode? */
0542: private boolean readOnly = false;
0543:
0544: /** Cache of ResultSet metadata */
0545: protected LRUCache resultSetMetadataCache;
0546:
0547: /** The timezone of the server */
0548: private TimeZone serverTimezoneTZ = null;
0549:
0550: /** The map of server variables that we retrieve at connection init. */
0551: private Map serverVariables = null;
0552:
0553: private long shortestQueryTimeMs = Long.MAX_VALUE;
0554:
0555: /** A map of statements that have had setMaxRows() called on them */
0556: private Map statementsUsingMaxRows;
0557:
0558: private double totalQueryTimeMs = 0;
0559:
0560: /** Are transactions supported by the MySQL server we are connected to? */
0561: private boolean transactionsSupported = false;
0562:
0563: /**
0564: * The type map for UDTs (not implemented, but used by some third-party
0565: * vendors, most notably IBM WebSphere)
0566: */
0567: private Map typeMap;
0568:
0569: /** Has ANSI_QUOTES been enabled on the server? */
0570: private boolean useAnsiQuotes = false;
0571:
0572: /** The user we're connected as */
0573: private String user = null;
0574:
0575: /**
0576: * Should we use server-side prepared statements? (auto-detected, but can be
0577: * disabled by user)
0578: */
0579: private boolean useServerPreparedStmts = false;
0580:
0581: private LRUCache serverSideStatementCheckCache;
0582: private LRUCache serverSideStatementCache;
0583: private Calendar sessionCalendar;
0584:
0585: private Calendar utcCalendar;
0586:
0587: private String origHostToConnectTo;
0588:
0589: // we don't want to be able to publicly clone this...
0590:
0591: private int origPortToConnectTo;
0592:
0593: private String origDatabaseToConnectTo;
0594:
0595: private String errorMessageEncoding = "Cp1252"; // to begin with, changes after we talk to the server
0596:
0597: private boolean usePlatformCharsetConverters;
0598:
0599: /*
0600: * For testing failover scenarios
0601: */
0602: private boolean hasTriedMasterFlag = false;
0603:
0604: /**
0605: * The comment (if any) that we'll prepend to all statements
0606: * sent to the server (to show up in "SHOW PROCESSLIST")
0607: */
0608: private String statementComment = null;
0609:
0610: /**'
0611: * For the delegate only
0612: */
0613: protected ConnectionImpl() {
0614: }
0615:
0616: /**
0617: * Creates a connection to a MySQL Server.
0618: *
0619: * @param hostToConnectTo
0620: * the hostname of the database server
0621: * @param portToConnectTo
0622: * the port number the server is listening on
0623: * @param info
0624: * a Properties[] list holding the user and password
0625: * @param databaseToConnectTo
0626: * the database to connect to
0627: * @param url
0628: * the URL of the connection
0629: * @param d
0630: * the Driver instantation of the connection
0631: * @exception SQLException
0632: * if a database access error occurs
0633: */
0634: protected ConnectionImpl(String hostToConnectTo,
0635: int portToConnectTo, Properties info,
0636: String databaseToConnectTo, String url) throws SQLException {
0637: this .charsetToNumBytesMap = new HashMap();
0638:
0639: this .connectionCreationTimeMillis = System.currentTimeMillis();
0640: this .pointOfOrigin = new Throwable();
0641:
0642: // Stash away for later, used to clone this connection for Statement.cancel
0643: // and Statement.setQueryTimeout().
0644: //
0645:
0646: this .origHostToConnectTo = hostToConnectTo;
0647: this .origPortToConnectTo = portToConnectTo;
0648: this .origDatabaseToConnectTo = databaseToConnectTo;
0649:
0650: try {
0651: Blob.class.getMethod("truncate", new Class[] { Long.TYPE });
0652:
0653: this .isRunningOnJDK13 = false;
0654: } catch (NoSuchMethodException nsme) {
0655: this .isRunningOnJDK13 = true;
0656: }
0657:
0658: this .sessionCalendar = new GregorianCalendar();
0659: this .utcCalendar = new GregorianCalendar();
0660: this .utcCalendar.setTimeZone(TimeZone.getTimeZone("GMT"));
0661:
0662: //
0663: // Normally, this code would be in initializeDriverProperties,
0664: // but we need to do this as early as possible, so we can start
0665: // logging to the 'correct' place as early as possible...this.log
0666: // points to 'NullLogger' for every connection at startup to avoid
0667: // NPEs and the overhead of checking for NULL at every logging call.
0668: //
0669: // We will reset this to the configured logger during properties
0670: // initialization.
0671: //
0672: this .log = LogFactory.getLogger(getLogger(),
0673: LOGGER_INSTANCE_NAME);
0674:
0675: // We store this per-connection, due to static synchronization
0676: // issues in Java's built-in TimeZone class...
0677: this .defaultTimeZone = Util.getDefaultTimeZone();
0678:
0679: if ("GMT".equalsIgnoreCase(this .defaultTimeZone.getID())) {
0680: this .isClientTzUTC = true;
0681: } else {
0682: this .isClientTzUTC = false;
0683: }
0684:
0685: this .openStatements = new HashMap();
0686: this .serverVariables = new HashMap();
0687: this .hostList = new ArrayList();
0688:
0689: if (hostToConnectTo == null) {
0690: this .host = "localhost";
0691: this .hostList.add(this .host);
0692: } else if (hostToConnectTo.indexOf(',') != -1) {
0693: // multiple hosts separated by commas (failover)
0694: StringTokenizer hostTokenizer = new StringTokenizer(
0695: hostToConnectTo, ",", false);
0696:
0697: while (hostTokenizer.hasMoreTokens()) {
0698: this .hostList.add(hostTokenizer.nextToken().trim());
0699: }
0700: } else {
0701: this .host = hostToConnectTo;
0702: this .hostList.add(this .host);
0703: }
0704:
0705: this .hostListSize = this .hostList.size();
0706: this .port = portToConnectTo;
0707:
0708: if (databaseToConnectTo == null) {
0709: databaseToConnectTo = "";
0710: }
0711:
0712: this .database = databaseToConnectTo;
0713: this .myURL = url;
0714: this .user = info
0715: .getProperty(NonRegisteringDriver.USER_PROPERTY_KEY);
0716: this .password = info
0717: .getProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY);
0718:
0719: if ((this .user == null) || this .user.equals("")) {
0720: this .user = "";
0721: }
0722:
0723: if (this .password == null) {
0724: this .password = "";
0725: }
0726:
0727: this .props = info;
0728: initializeDriverProperties(info);
0729:
0730: try {
0731: createNewIO(false);
0732: this .dbmd = getMetaData();
0733: } catch (SQLException ex) {
0734: cleanup(ex);
0735:
0736: // don't clobber SQL exceptions
0737: throw ex;
0738: } catch (Exception ex) {
0739: cleanup(ex);
0740:
0741: StringBuffer mesg = new StringBuffer(128);
0742:
0743: if (getParanoid()) {
0744: mesg.append("Cannot connect to MySQL server on ");
0745: mesg.append(this .host);
0746: mesg.append(":");
0747: mesg.append(this .port);
0748: mesg.append(".\n\n");
0749: mesg.append("Make sure that there is a MySQL server ");
0750: mesg
0751: .append("running on the machine/port you are trying ");
0752: mesg
0753: .append("to connect to and that the machine this software is "
0754: + "running on ");
0755: mesg.append("is able to connect to this host/port "
0756: + "(i.e. not firewalled). ");
0757: mesg
0758: .append("Also make sure that the server has not been started "
0759: + "with the --skip-networking ");
0760: mesg.append("flag.\n\n");
0761: } else {
0762: mesg.append("Unable to connect to database.");
0763: }
0764:
0765: mesg.append("Underlying exception: \n\n");
0766: mesg.append(ex.getClass().getName());
0767:
0768: if (!getParanoid()) {
0769: mesg.append(Util.stackTraceToString(ex));
0770: }
0771:
0772: throw SQLError.createSQLException(mesg.toString(),
0773: SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE);
0774: }
0775: }
0776:
0777: private void addToHistogram(int[] histogramCounts,
0778: long[] histogramBreakpoints, long value, int numberOfTimes,
0779: long currentLowerBound, long currentUpperBound) {
0780: if (histogramCounts == null) {
0781: createInitialHistogram(histogramBreakpoints,
0782: currentLowerBound, currentUpperBound);
0783: }
0784:
0785: for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
0786: if (histogramBreakpoints[i] >= value) {
0787: histogramCounts[i] += numberOfTimes;
0788:
0789: break;
0790: }
0791: }
0792: }
0793:
0794: private void addToPerformanceHistogram(long value, int numberOfTimes) {
0795: checkAndCreatePerformanceHistogram();
0796:
0797: addToHistogram(this .perfMetricsHistCounts,
0798: this .perfMetricsHistBreakpoints, value, numberOfTimes,
0799: this .shortestQueryTimeMs == Long.MAX_VALUE ? 0
0800: : this .shortestQueryTimeMs,
0801: this .longestQueryTimeMs);
0802: }
0803:
0804: private void addToTablesAccessedHistogram(long value,
0805: int numberOfTimes) {
0806: checkAndCreateTablesAccessedHistogram();
0807:
0808: addToHistogram(this .numTablesMetricsHistCounts,
0809: this .numTablesMetricsHistBreakpoints, value,
0810: numberOfTimes,
0811: this .minimumNumberTablesAccessed == Long.MAX_VALUE ? 0
0812: : this .minimumNumberTablesAccessed,
0813: this .maximumNumberTablesAccessed);
0814: }
0815:
0816: /**
0817: * Builds the map needed for 4.1.0 and newer servers that maps field-level
0818: * charset/collation info to a java character encoding name.
0819: *
0820: * @throws SQLException
0821: * DOCUMENT ME!
0822: */
0823: private void buildCollationMapping() throws SQLException {
0824: if (versionMeetsMinimum(4, 1, 0)) {
0825:
0826: TreeMap sortedCollationMap = null;
0827:
0828: if (getCacheServerConfiguration()) {
0829: synchronized (serverConfigByUrl) {
0830: sortedCollationMap = (TreeMap) serverCollationByUrl
0831: .get(getURL());
0832: }
0833: }
0834:
0835: java.sql.Statement stmt = null;
0836: java.sql.ResultSet results = null;
0837:
0838: try {
0839: if (sortedCollationMap == null) {
0840: sortedCollationMap = new TreeMap();
0841:
0842: stmt = createStatement();
0843:
0844: if (stmt.getMaxRows() != 0) {
0845: stmt.setMaxRows(0);
0846: }
0847:
0848: results = stmt.executeQuery("SHOW COLLATION");
0849:
0850: while (results.next()) {
0851: String charsetName = results.getString(2);
0852: Integer charsetIndex = Constants
0853: .integerValueOf(results.getInt(3));
0854:
0855: sortedCollationMap.put(charsetIndex,
0856: charsetName);
0857: }
0858:
0859: if (getCacheServerConfiguration()) {
0860: synchronized (serverConfigByUrl) {
0861: serverCollationByUrl.put(getURL(),
0862: sortedCollationMap);
0863: }
0864: }
0865:
0866: }
0867:
0868: // Now, merge with what we already know
0869: int highestIndex = ((Integer) sortedCollationMap
0870: .lastKey()).intValue();
0871:
0872: if (CharsetMapping.INDEX_TO_CHARSET.length > highestIndex) {
0873: highestIndex = CharsetMapping.INDEX_TO_CHARSET.length;
0874: }
0875:
0876: this .indexToCharsetMapping = new String[highestIndex + 1];
0877:
0878: for (int i = 0; i < CharsetMapping.INDEX_TO_CHARSET.length; i++) {
0879: this .indexToCharsetMapping[i] = CharsetMapping.INDEX_TO_CHARSET[i];
0880: }
0881:
0882: for (Iterator indexIter = sortedCollationMap.entrySet()
0883: .iterator(); indexIter.hasNext();) {
0884: Map.Entry indexEntry = (Map.Entry) indexIter.next();
0885:
0886: String mysqlCharsetName = (String) indexEntry
0887: .getValue();
0888:
0889: this .indexToCharsetMapping[((Integer) indexEntry
0890: .getKey()).intValue()] = CharsetMapping
0891: .getJavaEncodingForMysqlEncoding(
0892: mysqlCharsetName, this );
0893: }
0894: } catch (java.sql.SQLException e) {
0895: throw e;
0896: } finally {
0897: if (results != null) {
0898: try {
0899: results.close();
0900: } catch (java.sql.SQLException sqlE) {
0901: // ignore
0902: }
0903: }
0904:
0905: if (stmt != null) {
0906: try {
0907: stmt.close();
0908: } catch (java.sql.SQLException sqlE) {
0909: // ignore
0910: }
0911: }
0912: }
0913: } else {
0914: // Safety, we already do this as an initializer, but this makes
0915: // the intent more clear
0916: this .indexToCharsetMapping = CharsetMapping.INDEX_TO_CHARSET;
0917: }
0918: }
0919:
0920: private boolean canHandleAsServerPreparedStatement(String sql)
0921: throws SQLException {
0922: if (sql == null || sql.length() == 0) {
0923: return true;
0924: }
0925:
0926: if (getCachePreparedStatements()) {
0927: synchronized (this .serverSideStatementCheckCache) {
0928: Boolean flag = (Boolean) this .serverSideStatementCheckCache
0929: .get(sql);
0930:
0931: if (flag != null) {
0932: return flag.booleanValue();
0933: }
0934:
0935: boolean canHandle = canHandleAsServerPreparedStatementNoCache(sql);
0936:
0937: if (sql.length() < getPreparedStatementCacheSqlLimit()) {
0938: this .serverSideStatementCheckCache.put(sql,
0939: canHandle ? Boolean.TRUE : Boolean.FALSE);
0940: }
0941:
0942: return canHandle;
0943: }
0944: }
0945:
0946: return canHandleAsServerPreparedStatementNoCache(sql);
0947: }
0948:
0949: private boolean canHandleAsServerPreparedStatementNoCache(String sql)
0950: throws SQLException {
0951:
0952: // Can't use server-side prepare for CALL
0953: if (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql,
0954: "CALL")) {
0955: return false;
0956: }
0957:
0958: boolean canHandleAsStatement = true;
0959:
0960: if (!versionMeetsMinimum(5, 0, 7)
0961: && (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(
0962: sql, "SELECT")
0963: || StringUtils
0964: .startsWithIgnoreCaseAndNonAlphaNumeric(
0965: sql, "DELETE")
0966: || StringUtils
0967: .startsWithIgnoreCaseAndNonAlphaNumeric(
0968: sql, "INSERT")
0969: || StringUtils
0970: .startsWithIgnoreCaseAndNonAlphaNumeric(
0971: sql, "UPDATE") || StringUtils
0972: .startsWithIgnoreCaseAndNonAlphaNumeric(sql,
0973: "REPLACE"))) {
0974:
0975: // check for limit ?[,?]
0976:
0977: /*
0978: * The grammar for this (from the server) is: ULONG_NUM | ULONG_NUM
0979: * ',' ULONG_NUM | ULONG_NUM OFFSET_SYM ULONG_NUM
0980: */
0981:
0982: int currentPos = 0;
0983: int statementLength = sql.length();
0984: int lastPosToLook = statementLength - 7; // "LIMIT ".length()
0985: boolean allowBackslashEscapes = !this .noBackslashEscapes;
0986: char quoteChar = this .useAnsiQuotes ? '"' : '\'';
0987: boolean foundLimitWithPlaceholder = false;
0988:
0989: while (currentPos < lastPosToLook) {
0990: int limitStart = StringUtils
0991: .indexOfIgnoreCaseRespectQuotes(currentPos,
0992: sql, "LIMIT ", quoteChar,
0993: allowBackslashEscapes);
0994:
0995: if (limitStart == -1) {
0996: break;
0997: }
0998:
0999: currentPos = limitStart + 7;
1000:
1001: while (currentPos < statementLength) {
1002: char c = sql.charAt(currentPos);
1003:
1004: //
1005: // Have we reached the end
1006: // of what can be in a LIMIT clause?
1007: //
1008:
1009: if (!Character.isDigit(c)
1010: && !Character.isWhitespace(c) && c != ','
1011: && c != '?') {
1012: break;
1013: }
1014:
1015: if (c == '?') {
1016: foundLimitWithPlaceholder = true;
1017: break;
1018: }
1019:
1020: currentPos++;
1021: }
1022: }
1023:
1024: canHandleAsStatement = !foundLimitWithPlaceholder;
1025: } else if (StringUtils.startsWithIgnoreCaseAndWs(sql,
1026: "CREATE TABLE")) {
1027: canHandleAsStatement = false;
1028: } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "DO")) {
1029: canHandleAsStatement = false;
1030: } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "SET")) {
1031: canHandleAsStatement = false;
1032: }
1033:
1034: return canHandleAsStatement;
1035: }
1036:
1037: /**
1038: * Changes the user on this connection by performing a re-authentication. If
1039: * authentication fails, the connection will remain under the context of the
1040: * current user.
1041: *
1042: * @param userName
1043: * the username to authenticate with
1044: * @param newPassword
1045: * the password to authenticate with
1046: * @throws SQLException
1047: * if authentication fails, or some other error occurs while
1048: * performing the command.
1049: */
1050: public void changeUser(String userName, String newPassword)
1051: throws SQLException {
1052: if ((userName == null) || userName.equals("")) {
1053: userName = "";
1054: }
1055:
1056: if (newPassword == null) {
1057: newPassword = "";
1058: }
1059:
1060: this .io.changeUser(userName, newPassword, this .database);
1061: this .user = userName;
1062: this .password = newPassword;
1063:
1064: if (versionMeetsMinimum(4, 1, 0)) {
1065: configureClientCharacterSet(true);
1066: }
1067:
1068: setupServerForTruncationChecks();
1069: }
1070:
1071: private boolean characterSetNamesMatches(String mysqlEncodingName) {
1072: // set names is equivalent to character_set_client ..._results and ..._connection,
1073: // but we set _results later, so don't check it here.
1074:
1075: return (mysqlEncodingName != null
1076: && mysqlEncodingName
1077: .equalsIgnoreCase((String) this .serverVariables
1078: .get("character_set_client")) && mysqlEncodingName
1079: .equalsIgnoreCase((String) this .serverVariables
1080: .get("character_set_connection")));
1081: }
1082:
1083: private void checkAndCreatePerformanceHistogram() {
1084: if (this .perfMetricsHistCounts == null) {
1085: this .perfMetricsHistCounts = new int[HISTOGRAM_BUCKETS];
1086: }
1087:
1088: if (this .perfMetricsHistBreakpoints == null) {
1089: this .perfMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS];
1090: }
1091: }
1092:
1093: private void checkAndCreateTablesAccessedHistogram() {
1094: if (this .numTablesMetricsHistCounts == null) {
1095: this .numTablesMetricsHistCounts = new int[HISTOGRAM_BUCKETS];
1096: }
1097:
1098: if (this .numTablesMetricsHistBreakpoints == null) {
1099: this .numTablesMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS];
1100: }
1101: }
1102:
1103: protected void checkClosed() throws SQLException {
1104: if (this .isClosed) {
1105: StringBuffer messageBuf = new StringBuffer(
1106: "No operations allowed after connection closed.");
1107:
1108: if (this .forcedClosedLocation != null
1109: || this .forceClosedReason != null) {
1110: messageBuf.append("Connection was implicitly closed ");
1111: }
1112:
1113: if (this .forcedClosedLocation != null) {
1114: messageBuf.append("\n\n at (stack trace):\n");
1115: messageBuf.append(Util
1116: .stackTraceToString(this .forcedClosedLocation));
1117: }
1118:
1119: if (this .forceClosedReason != null) {
1120: if (this .forcedClosedLocation != null) {
1121: messageBuf.append("\n\nDue ");
1122: } else {
1123: messageBuf.append("due ");
1124: }
1125:
1126: messageBuf.append("to underlying exception/error:\n");
1127: messageBuf.append(Util
1128: .stackTraceToString(this .forceClosedReason));
1129: }
1130:
1131: throw SQLError.createSQLException(messageBuf.toString(),
1132: SQLError.SQL_STATE_CONNECTION_NOT_OPEN);
1133: }
1134: }
1135:
1136: /**
1137: * If useUnicode flag is set and explicit client character encoding isn't
1138: * specified then assign encoding from server if any.
1139: *
1140: * @throws SQLException
1141: * DOCUMENT ME!
1142: */
1143: private void checkServerEncoding() throws SQLException {
1144: if (getUseUnicode() && (getEncoding() != null)) {
1145: // spec'd by client, don't map
1146: return;
1147: }
1148:
1149: String serverEncoding = (String) this .serverVariables
1150: .get("character_set");
1151:
1152: if (serverEncoding == null) {
1153: // must be 4.1.1 or newer?
1154: serverEncoding = (String) this .serverVariables
1155: .get("character_set_server");
1156: }
1157:
1158: String mappedServerEncoding = null;
1159:
1160: if (serverEncoding != null) {
1161: mappedServerEncoding = CharsetMapping
1162: .getJavaEncodingForMysqlEncoding(serverEncoding
1163: .toUpperCase(Locale.ENGLISH), this );
1164: }
1165:
1166: //
1167: // First check if we can do the encoding ourselves
1168: //
1169: if (!getUseUnicode() && (mappedServerEncoding != null)) {
1170: SingleByteCharsetConverter converter = getCharsetConverter(mappedServerEncoding);
1171:
1172: if (converter != null) { // we know how to convert this ourselves
1173: setUseUnicode(true); // force the issue
1174: setEncoding(mappedServerEncoding);
1175:
1176: return;
1177: }
1178: }
1179:
1180: //
1181: // Now, try and find a Java I/O converter that can do
1182: // the encoding for us
1183: //
1184: if (serverEncoding != null) {
1185: if (mappedServerEncoding == null) {
1186: // We don't have a mapping for it, so try
1187: // and canonicalize the name....
1188: if (Character.isLowerCase(serverEncoding.charAt(0))) {
1189: char[] ach = serverEncoding.toCharArray();
1190: ach[0] = Character.toUpperCase(serverEncoding
1191: .charAt(0));
1192: setEncoding(new String(ach));
1193: }
1194: }
1195:
1196: if (mappedServerEncoding == null) {
1197: throw SQLError
1198: .createSQLException(
1199: "Unknown character encoding on server '"
1200: + serverEncoding
1201: + "', use 'characterEncoding=' property "
1202: + " to provide correct mapping",
1203: SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
1204: }
1205:
1206: //
1207: // Attempt to use the encoding, and bail out if it
1208: // can't be used
1209: //
1210: try {
1211: "abc".getBytes(mappedServerEncoding);
1212: setEncoding(mappedServerEncoding);
1213: setUseUnicode(true);
1214: } catch (UnsupportedEncodingException UE) {
1215: throw SQLError
1216: .createSQLException(
1217: "The driver can not map the character encoding '"
1218: + getEncoding()
1219: + "' that your server is using "
1220: + "to a character encoding your JVM understands. You "
1221: + "can specify this mapping manually by adding \"useUnicode=true\" "
1222: + "as well as \"characterEncoding=[an_encoding_your_jvm_understands]\" "
1223: + "to your JDBC URL.", "0S100");
1224: }
1225: }
1226: }
1227:
1228: /**
1229: * Set transaction isolation level to the value received from server if any.
1230: * Is called by connectionInit(...)
1231: *
1232: * @throws SQLException
1233: * DOCUMENT ME!
1234: */
1235: private void checkTransactionIsolationLevel() throws SQLException {
1236: String txIsolationName = null;
1237:
1238: if (versionMeetsMinimum(4, 0, 3)) {
1239: txIsolationName = "tx_isolation";
1240: } else {
1241: txIsolationName = "transaction_isolation";
1242: }
1243:
1244: String s = (String) this .serverVariables.get(txIsolationName);
1245:
1246: if (s != null) {
1247: Integer intTI = (Integer) mapTransIsolationNameToValue
1248: .get(s);
1249:
1250: if (intTI != null) {
1251: this .isolationLevel = intTI.intValue();
1252: }
1253: }
1254: }
1255:
1256: /**
1257: * Clobbers the physical network connection and marks
1258: * this connection as closed.
1259: *
1260: * @throws SQLException
1261: */
1262: protected void abortInternal() throws SQLException {
1263: io.forceClose();
1264: io = null;
1265: isClosed = true;
1266: cleanup(null);
1267: }
1268:
1269: /**
1270: * Destroys this connection and any underlying resources
1271: *
1272: * @param fromWhere
1273: * DOCUMENT ME!
1274: * @param whyCleanedUp
1275: * DOCUMENT ME!
1276: */
1277: private void cleanup(Throwable whyCleanedUp) {
1278: try {
1279: if ((this .io != null) && !isClosed()) {
1280: realClose(false, false, false, whyCleanedUp);
1281: } else if (this .io != null) {
1282: this .io.forceClose();
1283: }
1284: } catch (SQLException sqlEx) {
1285: // ignore, we're going away.
1286: ;
1287: }
1288:
1289: this .isClosed = true;
1290: }
1291:
1292: public void clearHasTriedMaster() {
1293: this .hasTriedMasterFlag = false;
1294: }
1295:
1296: /**
1297: * After this call, getWarnings returns null until a new warning is reported
1298: * for this connection.
1299: *
1300: * @exception SQLException
1301: * if a database access error occurs
1302: */
1303: public void clearWarnings() throws SQLException {
1304: // firstWarning = null;
1305: }
1306:
1307: /**
1308: * DOCUMENT ME!
1309: *
1310: * @param sql
1311: * DOCUMENT ME!
1312: * @return DOCUMENT ME!
1313: * @throws SQLException
1314: * DOCUMENT ME!
1315: */
1316: public PreparedStatement clientPrepareStatement(String sql)
1317: throws SQLException {
1318: return clientPrepareStatement(sql,
1319: java.sql.ResultSet.TYPE_SCROLL_SENSITIVE,
1320: java.sql.ResultSet.CONCUR_READ_ONLY);
1321: }
1322:
1323: /**
1324: * @see Connection#prepareStatement(String, int)
1325: */
1326: public java.sql.PreparedStatement clientPrepareStatement(
1327: String sql, int autoGenKeyIndex) throws SQLException {
1328: java.sql.PreparedStatement pStmt = clientPrepareStatement(sql);
1329:
1330: ((com.mysql.jdbc.PreparedStatement) pStmt)
1331: .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);
1332:
1333: return pStmt;
1334: }
1335:
1336: /**
1337: * DOCUMENT ME!
1338: *
1339: * @param sql
1340: * DOCUMENT ME!
1341: * @param resultSetType
1342: * DOCUMENT ME!
1343: * @param resultSetConcurrency
1344: * DOCUMENT ME!
1345: * @return DOCUMENT ME!
1346: * @throws SQLException
1347: * DOCUMENT ME!
1348: */
1349: public PreparedStatement clientPrepareStatement(String sql,
1350: int resultSetType, int resultSetConcurrency)
1351: throws SQLException {
1352: return clientPrepareStatement(sql, resultSetType,
1353: resultSetConcurrency, true);
1354: }
1355:
1356: protected PreparedStatement clientPrepareStatement(String sql,
1357: int resultSetType, int resultSetConcurrency,
1358: boolean processEscapeCodesIfNeeded) throws SQLException {
1359: checkClosed();
1360:
1361: String nativeSql = processEscapeCodesIfNeeded
1362: && getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql)
1363: : sql;
1364:
1365: PreparedStatement pStmt = null;
1366:
1367: if (getCachePreparedStatements()) {
1368: synchronized (this .cachedPreparedStatementParams) {
1369: PreparedStatement.ParseInfo pStmtInfo = (PreparedStatement.ParseInfo) this .cachedPreparedStatementParams
1370: .get(nativeSql);
1371:
1372: if (pStmtInfo == null) {
1373: pStmt = com.mysql.jdbc.PreparedStatement
1374: .getInstance(this , nativeSql, this .database);
1375:
1376: PreparedStatement.ParseInfo parseInfo = pStmt
1377: .getParseInfo();
1378:
1379: if (parseInfo.statementLength < getPreparedStatementCacheSqlLimit()) {
1380: if (this .cachedPreparedStatementParams.size() >= getPreparedStatementCacheSize()) {
1381: Iterator oldestIter = this .cachedPreparedStatementParams
1382: .keySet().iterator();
1383: long lruTime = Long.MAX_VALUE;
1384: String oldestSql = null;
1385:
1386: while (oldestIter.hasNext()) {
1387: String sqlKey = (String) oldestIter
1388: .next();
1389: PreparedStatement.ParseInfo lruInfo = (PreparedStatement.ParseInfo) this .cachedPreparedStatementParams
1390: .get(sqlKey);
1391:
1392: if (lruInfo.lastUsed < lruTime) {
1393: lruTime = lruInfo.lastUsed;
1394: oldestSql = sqlKey;
1395: }
1396: }
1397:
1398: if (oldestSql != null) {
1399: this .cachedPreparedStatementParams
1400: .remove(oldestSql);
1401: }
1402: }
1403:
1404: this .cachedPreparedStatementParams.put(
1405: nativeSql, pStmt.getParseInfo());
1406: }
1407: } else {
1408: pStmtInfo.lastUsed = System.currentTimeMillis();
1409: pStmt = new com.mysql.jdbc.PreparedStatement(this ,
1410: nativeSql, this .database, pStmtInfo);
1411: }
1412: }
1413: } else {
1414: pStmt = com.mysql.jdbc.PreparedStatement.getInstance(this ,
1415: nativeSql, this .database);
1416: }
1417:
1418: pStmt.setResultSetType(resultSetType);
1419: pStmt.setResultSetConcurrency(resultSetConcurrency);
1420:
1421: return pStmt;
1422: }
1423:
1424: /**
1425: * @see java.sql.Connection#prepareStatement(String, int[])
1426: */
1427: public java.sql.PreparedStatement clientPrepareStatement(
1428: String sql, int[] autoGenKeyIndexes) throws SQLException {
1429:
1430: PreparedStatement pStmt = clientPrepareStatement(sql);
1431:
1432: pStmt.setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
1433: && (autoGenKeyIndexes.length > 0));
1434:
1435: return pStmt;
1436: }
1437:
1438: /**
1439: * @see java.sql.Connection#prepareStatement(String, String[])
1440: */
1441: public java.sql.PreparedStatement clientPrepareStatement(
1442: String sql, String[] autoGenKeyColNames)
1443: throws SQLException {
1444: PreparedStatement pStmt = clientPrepareStatement(sql);
1445:
1446: pStmt.setRetrieveGeneratedKeys((autoGenKeyColNames != null)
1447: && (autoGenKeyColNames.length > 0));
1448:
1449: return pStmt;
1450: }
1451:
1452: // --------------------------JDBC 2.0-----------------------------
1453:
1454: /**
1455: * In some cases, it is desirable to immediately release a Connection's
1456: * database and JDBC resources instead of waiting for them to be
1457: * automatically released (cant think why off the top of my head) <B>Note:</B>
1458: * A Connection is automatically closed when it is garbage collected.
1459: * Certain fatal errors also result in a closed connection.
1460: *
1461: * @exception SQLException
1462: * if a database access error occurs
1463: */
1464: public void close() throws SQLException {
1465: realClose(true, true, false, null);
1466: }
1467:
1468: /**
1469: * Closes all currently open statements.
1470: *
1471: * @throws SQLException
1472: * DOCUMENT ME!
1473: */
1474: private void closeAllOpenStatements() throws SQLException {
1475: SQLException postponedException = null;
1476:
1477: if (this .openStatements != null) {
1478: List currentlyOpenStatements = new ArrayList(); // we need this to
1479: // avoid
1480: // ConcurrentModificationEx
1481:
1482: for (Iterator iter = this .openStatements.keySet()
1483: .iterator(); iter.hasNext();) {
1484: currentlyOpenStatements.add(iter.next());
1485: }
1486:
1487: int numStmts = currentlyOpenStatements.size();
1488:
1489: for (int i = 0; i < numStmts; i++) {
1490: StatementImpl stmt = (StatementImpl) currentlyOpenStatements
1491: .get(i);
1492:
1493: try {
1494: stmt.realClose(false, true);
1495: } catch (SQLException sqlEx) {
1496: postponedException = sqlEx; // throw it later, cleanup all
1497: // statements first
1498: }
1499: }
1500:
1501: if (postponedException != null) {
1502: throw postponedException;
1503: }
1504: }
1505: }
1506:
1507: private void closeStatement(java.sql.Statement stmt) {
1508: if (stmt != null) {
1509: try {
1510: stmt.close();
1511: } catch (SQLException sqlEx) {
1512: ; // ignore
1513: }
1514:
1515: stmt = null;
1516: }
1517: }
1518:
1519: /**
1520: * The method commit() makes all changes made since the previous
1521: * commit/rollback permanent and releases any database locks currently held
1522: * by the Connection. This method should only be used when auto-commit has
1523: * been disabled.
1524: * <p>
1525: * <b>Note:</b> MySQL does not support transactions, so this method is a
1526: * no-op.
1527: * </p>
1528: *
1529: * @exception SQLException
1530: * if a database access error occurs
1531: * @see setAutoCommit
1532: */
1533: public void commit() throws SQLException {
1534: synchronized (getMutex()) {
1535: checkClosed();
1536:
1537: try {
1538: // no-op if _relaxAutoCommit == true
1539: if (this .autoCommit && !getRelaxAutoCommit()) {
1540: throw SQLError
1541: .createSQLException("Can't call commit when autocommit=true");
1542: } else if (this .transactionsSupported) {
1543: if (getUseLocalSessionState()
1544: && versionMeetsMinimum(5, 0, 0)) {
1545: if (!this .io.inTransactionOnServer()) {
1546: return; // effectively a no-op
1547: }
1548: }
1549:
1550: execSQL(null, "commit", -1, null,
1551: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1552: java.sql.ResultSet.CONCUR_READ_ONLY, false,
1553: this .database, null, false);
1554: }
1555: } catch (SQLException sqlException) {
1556: if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
1557: .equals(sqlException.getSQLState())) {
1558: throw SQLError
1559: .createSQLException(
1560: "Communications link failure during commit(). Transaction resolution unknown.",
1561: SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN);
1562: }
1563:
1564: throw sqlException;
1565: } finally {
1566: this .needsPing = this .getReconnectAtTxEnd();
1567: }
1568:
1569: return;
1570: }
1571: }
1572:
1573: /**
1574: * Configures client-side properties for character set information.
1575: *
1576: * @throws SQLException
1577: * if unable to configure the specified character set.
1578: */
1579: private void configureCharsetProperties() throws SQLException {
1580: if (getEncoding() != null) {
1581: // Attempt to use the encoding, and bail out if it
1582: // can't be used
1583: try {
1584: String testString = "abc";
1585: testString.getBytes(getEncoding());
1586: } catch (UnsupportedEncodingException UE) {
1587: // Try the MySQL character encoding, then....
1588: String oldEncoding = getEncoding();
1589:
1590: setEncoding(CharsetMapping
1591: .getJavaEncodingForMysqlEncoding(oldEncoding,
1592: this ));
1593:
1594: if (getEncoding() == null) {
1595: throw SQLError
1596: .createSQLException(
1597: "Java does not support the MySQL character encoding "
1598: + " " + "encoding '"
1599: + oldEncoding + "'.",
1600: SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
1601: }
1602:
1603: try {
1604: String testString = "abc";
1605: testString.getBytes(getEncoding());
1606: } catch (UnsupportedEncodingException encodingEx) {
1607: throw SQLError
1608: .createSQLException(
1609: "Unsupported character "
1610: + "encoding '"
1611: + getEncoding() + "'.",
1612: SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
1613: }
1614: }
1615: }
1616: }
1617:
1618: /**
1619: * Sets up client character set for MySQL-4.1 and newer if the user This
1620: * must be done before any further communication with the server!
1621: *
1622: * @return true if this routine actually configured the client character
1623: * set, or false if the driver needs to use 'older' methods to
1624: * detect the character set, as it is connected to a MySQL server
1625: * older than 4.1.0
1626: * @throws SQLException
1627: * if an exception happens while sending 'SET NAMES' to the
1628: * server, or the server sends character set information that
1629: * the client doesn't know about.
1630: */
1631: private boolean configureClientCharacterSet(
1632: boolean dontCheckServerMatch) throws SQLException {
1633: String realJavaEncoding = getEncoding();
1634: boolean characterSetAlreadyConfigured = false;
1635:
1636: try {
1637: if (versionMeetsMinimum(4, 1, 0)) {
1638: characterSetAlreadyConfigured = true;
1639:
1640: setUseUnicode(true);
1641:
1642: configureCharsetProperties();
1643: realJavaEncoding = getEncoding(); // we need to do this again
1644: // to grab this for
1645: // versions > 4.1.0
1646:
1647: try {
1648:
1649: // Fault injection for testing server character set indices
1650:
1651: if (props != null
1652: && props
1653: .getProperty("com.mysql.jdbc.faultInjection.serverCharsetIndex") != null) {
1654: this .io.serverCharsetIndex = Integer
1655: .parseInt(props
1656: .getProperty("com.mysql.jdbc.faultInjection.serverCharsetIndex"));
1657: }
1658:
1659: String serverEncodingToSet = CharsetMapping.INDEX_TO_CHARSET[this .io.serverCharsetIndex];
1660:
1661: if (serverEncodingToSet == null
1662: || serverEncodingToSet.length() == 0) {
1663: if (realJavaEncoding != null) {
1664: // user knows best, try it
1665: setEncoding(realJavaEncoding);
1666: } else {
1667: throw SQLError
1668: .createSQLException(
1669: "Unknown initial character set index '"
1670: + this .io.serverCharsetIndex
1671: + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.",
1672: SQLError.SQL_STATE_GENERAL_ERROR);
1673: }
1674: }
1675:
1676: // "latin1" on MySQL-4.1.0+ is actually CP1252, not ISO8859_1
1677: if (versionMeetsMinimum(4, 1, 0)
1678: && "ISO8859_1"
1679: .equalsIgnoreCase(serverEncodingToSet)) {
1680: serverEncodingToSet = "Cp1252";
1681: }
1682:
1683: setEncoding(serverEncodingToSet);
1684:
1685: } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) {
1686: if (realJavaEncoding != null) {
1687: // user knows best, try it
1688: setEncoding(realJavaEncoding);
1689: } else {
1690: throw SQLError
1691: .createSQLException(
1692: "Unknown initial character set index '"
1693: + this .io.serverCharsetIndex
1694: + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.",
1695: SQLError.SQL_STATE_GENERAL_ERROR);
1696: }
1697: }
1698:
1699: if (getEncoding() == null) {
1700: // punt?
1701: setEncoding("ISO8859_1");
1702: }
1703:
1704: //
1705: // Has the user has 'forced' the character encoding via
1706: // driver properties?
1707: //
1708: if (getUseUnicode()) {
1709: if (realJavaEncoding != null) {
1710:
1711: //
1712: // Now, inform the server what character set we
1713: // will be using from now-on...
1714: //
1715: if (realJavaEncoding.equalsIgnoreCase("UTF-8")
1716: || realJavaEncoding
1717: .equalsIgnoreCase("UTF8")) {
1718: // charset names are case-sensitive
1719:
1720: if (!getUseOldUTF8Behavior()) {
1721: if (dontCheckServerMatch
1722: || !characterSetNamesMatches("utf8")) {
1723: execSQL(
1724: null,
1725: "SET NAMES utf8",
1726: -1,
1727: null,
1728: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1729: java.sql.ResultSet.CONCUR_READ_ONLY,
1730: false, this .database, null,
1731: false);
1732: }
1733: }
1734:
1735: setEncoding(realJavaEncoding);
1736: } /* not utf-8 */else {
1737: String mysqlEncodingName = CharsetMapping
1738: .getMysqlEncodingForJavaEncoding(
1739: realJavaEncoding
1740: .toUpperCase(Locale.ENGLISH),
1741: this );
1742:
1743: /*
1744: * if ("koi8_ru".equals(mysqlEncodingName)) { //
1745: * This has a _different_ name in 4.1...
1746: * mysqlEncodingName = "ko18r"; } else if
1747: * ("euc_kr".equals(mysqlEncodingName)) { //
1748: * Different name in 4.1 mysqlEncodingName =
1749: * "euckr"; }
1750: */
1751:
1752: if (mysqlEncodingName != null) {
1753:
1754: if (dontCheckServerMatch
1755: || !characterSetNamesMatches(mysqlEncodingName)) {
1756: execSQL(
1757: null,
1758: "SET NAMES "
1759: + mysqlEncodingName,
1760: -1,
1761: null,
1762: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1763: java.sql.ResultSet.CONCUR_READ_ONLY,
1764: false, this .database, null,
1765: false);
1766: }
1767: }
1768:
1769: // Switch driver's encoding now, since the server
1770: // knows what we're sending...
1771: //
1772: setEncoding(realJavaEncoding);
1773: }
1774: } else if (getEncoding() != null) {
1775: // Tell the server we'll use the server default charset
1776: // to send our
1777: // queries from now on....
1778: String mysqlEncodingName = CharsetMapping
1779: .getMysqlEncodingForJavaEncoding(
1780: getEncoding().toUpperCase(
1781: Locale.ENGLISH), this );
1782:
1783: if (dontCheckServerMatch
1784: || !characterSetNamesMatches(mysqlEncodingName)) {
1785: execSQL(
1786: null,
1787: "SET NAMES " + mysqlEncodingName,
1788: -1,
1789: null,
1790: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1791: java.sql.ResultSet.CONCUR_READ_ONLY,
1792: false, this .database, null, false);
1793: }
1794:
1795: realJavaEncoding = getEncoding();
1796: }
1797:
1798: }
1799:
1800: //
1801: // We know how to deal with any charset coming back from
1802: // the database, so tell the server not to do conversion
1803: // if the user hasn't 'forced' a result-set character set
1804: //
1805:
1806: String onServer = null;
1807: boolean isNullOnServer = false;
1808:
1809: if (this .serverVariables != null) {
1810: onServer = (String) this .serverVariables
1811: .get("character_set_results");
1812:
1813: isNullOnServer = onServer == null
1814: || "NULL".equalsIgnoreCase(onServer)
1815: || onServer.length() == 0;
1816: }
1817:
1818: if (getCharacterSetResults() == null) {
1819:
1820: //
1821: // Only send if needed, if we're caching server variables
1822: // we -have- to send, because we don't know what it was
1823: // before we cached them.
1824: //
1825: if (!isNullOnServer) {
1826: execSQL(null,
1827: "SET character_set_results = NULL", -1,
1828: null,
1829: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1830: java.sql.ResultSet.CONCUR_READ_ONLY,
1831: false, this .database, null, false);
1832: if (!this .usingCachedConfig) {
1833: this .serverVariables.put(
1834: JDBC_LOCAL_CHARACTER_SET_RESULTS,
1835: null);
1836: }
1837: } else {
1838: if (!this .usingCachedConfig) {
1839: this .serverVariables.put(
1840: JDBC_LOCAL_CHARACTER_SET_RESULTS,
1841: onServer);
1842: }
1843: }
1844: } else {
1845: String charsetResults = getCharacterSetResults();
1846: String mysqlEncodingName = null;
1847:
1848: if ("UTF-8".equalsIgnoreCase(charsetResults)
1849: || "UTF8".equalsIgnoreCase(charsetResults)) {
1850: mysqlEncodingName = "utf8";
1851: } else {
1852: mysqlEncodingName = CharsetMapping
1853: .getMysqlEncodingForJavaEncoding(
1854: charsetResults
1855: .toUpperCase(Locale.ENGLISH),
1856: this );
1857: }
1858:
1859: //
1860: // Only change the value if needed
1861: //
1862:
1863: if (!mysqlEncodingName
1864: .equalsIgnoreCase((String) this .serverVariables
1865: .get("character_set_results"))) {
1866: StringBuffer setBuf = new StringBuffer(
1867: "SET character_set_results = ".length()
1868: + mysqlEncodingName.length());
1869: setBuf.append("SET character_set_results = ")
1870: .append(mysqlEncodingName);
1871:
1872: execSQL(null, setBuf.toString(), -1, null,
1873: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1874: java.sql.ResultSet.CONCUR_READ_ONLY,
1875: false, this .database, null, false);
1876:
1877: if (!this .usingCachedConfig) {
1878: this .serverVariables.put(
1879: JDBC_LOCAL_CHARACTER_SET_RESULTS,
1880: mysqlEncodingName);
1881: }
1882: } else {
1883: if (!this .usingCachedConfig) {
1884: this .serverVariables.put(
1885: JDBC_LOCAL_CHARACTER_SET_RESULTS,
1886: onServer);
1887: }
1888: }
1889: }
1890:
1891: if (getConnectionCollation() != null) {
1892: StringBuffer setBuf = new StringBuffer(
1893: "SET collation_connection = ".length()
1894: + getConnectionCollation().length());
1895: setBuf.append("SET collation_connection = ")
1896: .append(getConnectionCollation());
1897:
1898: execSQL(null, setBuf.toString(), -1, null,
1899: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1900: java.sql.ResultSet.CONCUR_READ_ONLY, false,
1901: this .database, null, false);
1902: }
1903: } else {
1904: // Use what the server has specified
1905: realJavaEncoding = getEncoding(); // so we don't get
1906: // swapped out in the finally
1907: // block....
1908: }
1909: } finally {
1910: // Failsafe, make sure that the driver's notion of character
1911: // encoding matches what the user has specified.
1912: //
1913: setEncoding(realJavaEncoding);
1914: }
1915:
1916: return characterSetAlreadyConfigured;
1917: }
1918:
1919: /**
1920: * Configures the client's timezone if required.
1921: *
1922: * @throws SQLException
1923: * if the timezone the server is configured to use can't be
1924: * mapped to a Java timezone.
1925: */
1926: private void configureTimezone() throws SQLException {
1927: String configuredTimeZoneOnServer = (String) this .serverVariables
1928: .get("timezone");
1929:
1930: if (configuredTimeZoneOnServer == null) {
1931: configuredTimeZoneOnServer = (String) this .serverVariables
1932: .get("time_zone");
1933:
1934: if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
1935: configuredTimeZoneOnServer = (String) this .serverVariables
1936: .get("system_time_zone");
1937: }
1938: }
1939:
1940: if (getUseTimezone() && configuredTimeZoneOnServer != null) {
1941: // user can specify/override as property
1942: String canoncicalTimezone = getServerTimezone();
1943:
1944: if ((canoncicalTimezone == null)
1945: || (canoncicalTimezone.length() == 0)) {
1946: String serverTimezoneStr = configuredTimeZoneOnServer;
1947:
1948: try {
1949: canoncicalTimezone = TimeUtil
1950: .getCanoncialTimezone(serverTimezoneStr);
1951:
1952: if (canoncicalTimezone == null) {
1953: throw SQLError.createSQLException(
1954: "Can't map timezone '"
1955: + serverTimezoneStr + "' to "
1956: + " canonical timezone.",
1957: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1958: }
1959: } catch (IllegalArgumentException iae) {
1960: throw SQLError.createSQLException(iae.getMessage(),
1961: SQLError.SQL_STATE_GENERAL_ERROR);
1962: }
1963: }
1964:
1965: this .serverTimezoneTZ = TimeZone
1966: .getTimeZone(canoncicalTimezone);
1967:
1968: //
1969: // The Calendar class has the behavior of mapping
1970: // unknown timezones to 'GMT' instead of throwing an
1971: // exception, so we must check for this...
1972: //
1973: if (!canoncicalTimezone.equalsIgnoreCase("GMT")
1974: && this .serverTimezoneTZ.getID().equals("GMT")) {
1975: throw SQLError.createSQLException(
1976: "No timezone mapping entry for '"
1977: + canoncicalTimezone + "'",
1978: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1979: }
1980:
1981: if ("GMT".equalsIgnoreCase(this .serverTimezoneTZ.getID())) {
1982: this .isServerTzUTC = true;
1983: } else {
1984: this .isServerTzUTC = false;
1985: }
1986: }
1987: }
1988:
1989: private void createInitialHistogram(long[] breakpoints,
1990: long lowerBound, long upperBound) {
1991:
1992: double bucketSize = (((double) upperBound - (double) lowerBound) / HISTOGRAM_BUCKETS) * 1.25;
1993:
1994: if (bucketSize < 1) {
1995: bucketSize = 1;
1996: }
1997:
1998: for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
1999: breakpoints[i] = lowerBound;
2000: lowerBound += bucketSize;
2001: }
2002: }
2003:
2004: /**
2005: * Creates an IO channel to the server
2006: *
2007: * @param isForReconnect
2008: * is this request for a re-connect
2009: * @return a new MysqlIO instance connected to a server
2010: * @throws SQLException
2011: * if a database access error occurs
2012: * @throws CommunicationsException
2013: * DOCUMENT ME!
2014: */
2015: protected void createNewIO(boolean isForReconnect)
2016: throws SQLException {
2017: Properties mergedProps = exposeAsProperties(this .props);
2018:
2019: long queriesIssuedFailedOverCopy = this .queriesIssuedFailedOver;
2020: this .queriesIssuedFailedOver = 0;
2021:
2022: try {
2023: if (!getHighAvailability() && !this .failedOver) {
2024: boolean connectionGood = false;
2025: Exception connectionNotEstablishedBecause = null;
2026:
2027: int hostIndex = 0;
2028:
2029: //
2030: // TODO: Eventually, when there's enough metadata
2031: // on the server to support it, we should come up
2032: // with a smarter way to pick what server to connect
2033: // to...perhaps even making it 'pluggable'
2034: //
2035: if (getRoundRobinLoadBalance()) {
2036: hostIndex = getNextRoundRobinHostIndex(getURL(),
2037: this .hostList);
2038: }
2039:
2040: for (; hostIndex < this .hostListSize; hostIndex++) {
2041:
2042: if (hostIndex == 0) {
2043: this .hasTriedMasterFlag = true;
2044: }
2045:
2046: try {
2047: String newHostPortPair = (String) this .hostList
2048: .get(hostIndex);
2049:
2050: int newPort = 3306;
2051:
2052: String[] hostPortPair = NonRegisteringDriver
2053: .parseHostPortPair(newHostPortPair);
2054: String newHost = hostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];
2055:
2056: if (newHost == null
2057: || StringUtils
2058: .isEmptyOrWhitespaceOnly(newHost)) {
2059: newHost = "localhost";
2060: }
2061:
2062: if (hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {
2063: try {
2064: newPort = Integer
2065: .parseInt(hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);
2066: } catch (NumberFormatException nfe) {
2067: throw SQLError
2068: .createSQLException(
2069: "Illegal connection port value '"
2070: + hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]
2071: + "'",
2072: SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
2073: }
2074: }
2075:
2076: this .io = new MysqlIO(newHost, newPort,
2077: mergedProps,
2078: getSocketFactoryClassName(), this ,
2079: getSocketTimeout(),
2080: this .largeRowSizeThreshold
2081: .getValueAsInt());
2082:
2083: this .io.doHandshake(this .user, this .password,
2084: this .database);
2085: this .connectionId = this .io.getThreadId();
2086: this .isClosed = false;
2087:
2088: // save state from old connection
2089: boolean oldAutoCommit = getAutoCommit();
2090: int oldIsolationLevel = this .isolationLevel;
2091: boolean oldReadOnly = isReadOnly();
2092: String oldCatalog = getCatalog();
2093:
2094: // Server properties might be different
2095: // from previous connection, so initialize
2096: // again...
2097: initializePropsFromServer();
2098:
2099: if (isForReconnect) {
2100: // Restore state from old connection
2101: setAutoCommit(oldAutoCommit);
2102:
2103: if (this .hasIsolationLevels) {
2104: setTransactionIsolation(oldIsolationLevel);
2105: }
2106:
2107: setCatalog(oldCatalog);
2108: }
2109:
2110: if (hostIndex != 0) {
2111: setFailedOverState();
2112: queriesIssuedFailedOverCopy = 0;
2113: } else {
2114: this .failedOver = false;
2115: queriesIssuedFailedOverCopy = 0;
2116:
2117: if (this .hostListSize > 1) {
2118: setReadOnlyInternal(false);
2119: } else {
2120: setReadOnlyInternal(oldReadOnly);
2121: }
2122: }
2123:
2124: connectionGood = true;
2125:
2126: break; // low-level connection succeeded
2127: } catch (Exception EEE) {
2128: if (this .io != null) {
2129: this .io.forceClose();
2130: }
2131:
2132: connectionNotEstablishedBecause = EEE;
2133:
2134: connectionGood = false;
2135:
2136: if (EEE instanceof SQLException) {
2137: SQLException sqlEx = (SQLException) EEE;
2138:
2139: String sqlState = sqlEx.getSQLState();
2140:
2141: // If this isn't a communications failure, it will probably never succeed, so
2142: // give up right here and now ....
2143: if ((sqlState == null)
2144: || !sqlState
2145: .equals(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)) {
2146: throw sqlEx;
2147: }
2148: }
2149:
2150: // Check next host, it might be up...
2151: if (getRoundRobinLoadBalance()) {
2152: hostIndex = getNextRoundRobinHostIndex(
2153: getURL(), this .hostList) - 1 /* incremented by for loop next time around */;
2154: } else if ((this .hostListSize - 1) == hostIndex) {
2155: throw SQLError
2156: .createCommunicationsException(
2157: this ,
2158: (this .io != null) ? this .io
2159: .getLastPacketSentTimeMs()
2160: : 0, EEE);
2161: }
2162: }
2163: }
2164:
2165: if (!connectionGood) {
2166: // We've really failed!
2167: throw SQLError
2168: .createSQLException(
2169: "Could not create connection to database server due to underlying exception: '"
2170: + connectionNotEstablishedBecause
2171: + "'."
2172: + (getParanoid() ? ""
2173: : Util
2174: .stackTraceToString(connectionNotEstablishedBecause)),
2175: SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
2176: }
2177: } else {
2178: double timeout = getInitialTimeout();
2179: boolean connectionGood = false;
2180:
2181: Exception connectionException = null;
2182:
2183: int hostIndex = 0;
2184:
2185: if (getRoundRobinLoadBalance()) {
2186: hostIndex = getNextRoundRobinHostIndex(getURL(),
2187: this .hostList);
2188: }
2189:
2190: for (; (hostIndex < this .hostListSize)
2191: && !connectionGood; hostIndex++) {
2192: if (hostIndex == 0) {
2193: this .hasTriedMasterFlag = true;
2194: }
2195:
2196: if (this .preferSlaveDuringFailover
2197: && hostIndex == 0) {
2198: hostIndex++;
2199: }
2200:
2201: for (int attemptCount = 0; (attemptCount < getMaxReconnects())
2202: && !connectionGood; attemptCount++) {
2203: try {
2204: if (this .io != null) {
2205: this .io.forceClose();
2206: }
2207:
2208: String newHostPortPair = (String) this .hostList
2209: .get(hostIndex);
2210:
2211: int newPort = 3306;
2212:
2213: String[] hostPortPair = NonRegisteringDriver
2214: .parseHostPortPair(newHostPortPair);
2215: String newHost = hostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];
2216:
2217: if (newHost == null
2218: || StringUtils
2219: .isEmptyOrWhitespaceOnly(newHost)) {
2220: newHost = "localhost";
2221: }
2222:
2223: if (hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {
2224: try {
2225: newPort = Integer
2226: .parseInt(hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);
2227: } catch (NumberFormatException nfe) {
2228: throw SQLError
2229: .createSQLException(
2230: "Illegal connection port value '"
2231: + hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]
2232: + "'",
2233: SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
2234: }
2235: }
2236:
2237: this .io = new MysqlIO(newHost, newPort,
2238: mergedProps,
2239: getSocketFactoryClassName(), this ,
2240: getSocketTimeout(),
2241: this .largeRowSizeThreshold
2242: .getValueAsInt());
2243: this .io.doHandshake(this .user,
2244: this .password, this .database);
2245: pingInternal(false);
2246: this .connectionId = this .io.getThreadId();
2247: this .isClosed = false;
2248:
2249: // save state from old connection
2250: boolean oldAutoCommit = getAutoCommit();
2251: int oldIsolationLevel = this .isolationLevel;
2252: boolean oldReadOnly = isReadOnly();
2253: String oldCatalog = getCatalog();
2254:
2255: // Server properties might be different
2256: // from previous connection, so initialize
2257: // again...
2258: initializePropsFromServer();
2259:
2260: if (isForReconnect) {
2261: // Restore state from old connection
2262: setAutoCommit(oldAutoCommit);
2263:
2264: if (this .hasIsolationLevels) {
2265: setTransactionIsolation(oldIsolationLevel);
2266: }
2267:
2268: setCatalog(oldCatalog);
2269: }
2270:
2271: connectionGood = true;
2272:
2273: if (hostIndex != 0) {
2274: setFailedOverState();
2275: queriesIssuedFailedOverCopy = 0;
2276: } else {
2277: this .failedOver = false;
2278: queriesIssuedFailedOverCopy = 0;
2279:
2280: if (this .hostListSize > 1) {
2281: setReadOnlyInternal(false);
2282: } else {
2283: setReadOnlyInternal(oldReadOnly);
2284: }
2285: }
2286:
2287: break;
2288: } catch (Exception EEE) {
2289: connectionException = EEE;
2290: connectionGood = false;
2291:
2292: // Check next host, it might be up...
2293: if (getRoundRobinLoadBalance()) {
2294: hostIndex = getNextRoundRobinHostIndex(
2295: getURL(), this .hostList) - 1 /* incremented by for loop next time around */;
2296: }
2297: }
2298:
2299: if (connectionGood) {
2300: break;
2301: }
2302:
2303: if (attemptCount > 0) {
2304: try {
2305: Thread.sleep((long) timeout * 1000);
2306: } catch (InterruptedException IE) {
2307: // ignore
2308: }
2309: }
2310: } // end attempts for a single host
2311: } // end iterator for list of hosts
2312:
2313: if (!connectionGood) {
2314: // We've really failed!
2315: throw SQLError
2316: .createSQLException(
2317: "Server connection failure during transaction. Due to underlying exception: '"
2318: + connectionException
2319: + "'."
2320: + (getParanoid() ? ""
2321: : Util
2322: .stackTraceToString(connectionException))
2323: + "\nAttempted reconnect "
2324: + getMaxReconnects()
2325: + " times. Giving up.",
2326: SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
2327: }
2328: }
2329:
2330: if (getParanoid() && !getHighAvailability()
2331: && (this .hostListSize <= 1)) {
2332: this .password = null;
2333: this .user = null;
2334: }
2335:
2336: if (isForReconnect) {
2337: //
2338: // Retrieve any 'lost' prepared statements if re-connecting
2339: //
2340: Iterator statementIter = this .openStatements.values()
2341: .iterator();
2342:
2343: //
2344: // We build a list of these outside the map of open statements,
2345: // because
2346: // in the process of re-preparing, we might end up having to
2347: // close
2348: // a prepared statement, thus removing it from the map, and
2349: // generating
2350: // a ConcurrentModificationException
2351: //
2352: Stack serverPreparedStatements = null;
2353:
2354: while (statementIter.hasNext()) {
2355: Object statementObj = statementIter.next();
2356:
2357: if (statementObj instanceof ServerPreparedStatement) {
2358: if (serverPreparedStatements == null) {
2359: serverPreparedStatements = new Stack();
2360: }
2361:
2362: serverPreparedStatements.add(statementObj);
2363: }
2364: }
2365:
2366: if (serverPreparedStatements != null) {
2367: while (!serverPreparedStatements.isEmpty()) {
2368: ((ServerPreparedStatement) serverPreparedStatements
2369: .pop()).rePrepare();
2370: }
2371: }
2372: }
2373: } finally {
2374: this .queriesIssuedFailedOver = queriesIssuedFailedOverCopy;
2375:
2376: if (this .io != null && getStatementInterceptors() != null) {
2377: this .io.initializeStatementInterceptors(
2378: getStatementInterceptors(), mergedProps);
2379: }
2380: }
2381: }
2382:
2383: private void createPreparedStatementCaches() {
2384: int cacheSize = getPreparedStatementCacheSize();
2385:
2386: this .cachedPreparedStatementParams = new HashMap(cacheSize);
2387:
2388: this .serverSideStatementCheckCache = new LRUCache(cacheSize);
2389:
2390: this .serverSideStatementCache = new LRUCache(cacheSize) {
2391: protected boolean removeEldestEntry(
2392: java.util.Map.Entry eldest) {
2393: if (this .maxElements <= 1) {
2394: return false;
2395: }
2396:
2397: boolean removeIt = super .removeEldestEntry(eldest);
2398:
2399: if (removeIt) {
2400: ServerPreparedStatement ps = (ServerPreparedStatement) eldest
2401: .getValue();
2402: ps.isCached = false;
2403: ps.setClosed(false);
2404:
2405: try {
2406: ps.close();
2407: } catch (SQLException sqlEx) {
2408: // punt
2409: }
2410: }
2411:
2412: return removeIt;
2413: }
2414: };
2415: }
2416:
2417: /**
2418: * SQL statements without parameters are normally executed using Statement
2419: * objects. If the same SQL statement is executed many times, it is more
2420: * efficient to use a PreparedStatement
2421: *
2422: * @return a new Statement object
2423: * @throws SQLException
2424: * passed through from the constructor
2425: */
2426: public java.sql.Statement createStatement() throws SQLException {
2427: return createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
2428: java.sql.ResultSet.CONCUR_READ_ONLY);
2429: }
2430:
2431: /**
2432: * JDBC 2.0 Same as createStatement() above, but allows the default result
2433: * set type and result set concurrency type to be overridden.
2434: *
2435: * @param resultSetType
2436: * a result set type, see ResultSet.TYPE_XXX
2437: * @param resultSetConcurrency
2438: * a concurrency type, see ResultSet.CONCUR_XXX
2439: * @return a new Statement object
2440: * @exception SQLException
2441: * if a database-access error occurs.
2442: */
2443: public java.sql.Statement createStatement(int resultSetType,
2444: int resultSetConcurrency) throws SQLException {
2445: checkClosed();
2446:
2447: StatementImpl stmt = new com.mysql.jdbc.StatementImpl(this ,
2448: this .database);
2449: stmt.setResultSetType(resultSetType);
2450: stmt.setResultSetConcurrency(resultSetConcurrency);
2451:
2452: return stmt;
2453: }
2454:
2455: /**
2456: * @see Connection#createStatement(int, int, int)
2457: */
2458: public java.sql.Statement createStatement(int resultSetType,
2459: int resultSetConcurrency, int resultSetHoldability)
2460: throws SQLException {
2461: if (getPedantic()) {
2462: if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
2463: throw SQLError
2464: .createSQLException(
2465: "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
2466: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
2467: }
2468: }
2469:
2470: return createStatement(resultSetType, resultSetConcurrency);
2471: }
2472:
2473: protected void dumpTestcaseQuery(String query) {
2474: System.err.println(query);
2475: }
2476:
2477: protected Connection duplicate() throws SQLException {
2478: return new ConnectionImpl(this .origHostToConnectTo,
2479: this .origPortToConnectTo, this .props,
2480: this .origDatabaseToConnectTo, this .myURL);
2481: }
2482:
2483: /**
2484: * Send a query to the server. Returns one of the ResultSet objects. This is
2485: * synchronized, so Statement's queries will be serialized.
2486: *
2487: * @param callingStatement
2488: * DOCUMENT ME!
2489: * @param sql
2490: * the SQL statement to be executed
2491: * @param maxRows
2492: * DOCUMENT ME!
2493: * @param packet
2494: * DOCUMENT ME!
2495: * @param resultSetType
2496: * DOCUMENT ME!
2497: * @param resultSetConcurrency
2498: * DOCUMENT ME!
2499: * @param streamResults
2500: * DOCUMENT ME!
2501: * @param queryIsSelectOnly
2502: * DOCUMENT ME!
2503: * @param catalog
2504: * DOCUMENT ME!
2505: * @param unpackFields
2506: * DOCUMENT ME!
2507: * @return a ResultSet holding the results
2508: * @exception SQLException
2509: * if a database error occurs
2510: */
2511:
2512: // ResultSet execSQL(Statement callingStatement, String sql,
2513: // int maxRowsToRetreive, String catalog) throws SQLException {
2514: // return execSQL(callingStatement, sql, maxRowsToRetreive, null,
2515: // java.sql.ResultSet.TYPE_FORWARD_ONLY,
2516: // java.sql.ResultSet.CONCUR_READ_ONLY, catalog);
2517: // }
2518: // ResultSet execSQL(Statement callingStatement, String sql, int maxRows,
2519: // int resultSetType, int resultSetConcurrency, boolean streamResults,
2520: // boolean queryIsSelectOnly, String catalog, boolean unpackFields) throws
2521: // SQLException {
2522: // return execSQL(callingStatement, sql, maxRows, null, resultSetType,
2523: // resultSetConcurrency, streamResults, queryIsSelectOnly, catalog,
2524: // unpackFields);
2525: // }
2526: ResultSetInternalMethods execSQL(StatementImpl callingStatement,
2527: String sql, int maxRows, Buffer packet, int resultSetType,
2528: int resultSetConcurrency, boolean streamResults,
2529: String catalog, Field[] cachedMetadata) throws SQLException {
2530: return execSQL(callingStatement, sql, maxRows, packet,
2531: resultSetType, resultSetConcurrency, streamResults,
2532: catalog, cachedMetadata, false);
2533: }
2534:
2535: ResultSetInternalMethods execSQL(StatementImpl callingStatement,
2536: String sql, int maxRows, Buffer packet, int resultSetType,
2537: int resultSetConcurrency, boolean streamResults,
2538: String catalog, Field[] cachedMetadata, boolean isBatch)
2539: throws SQLException {
2540: //
2541: // Fall-back if the master is back online if we've
2542: // issued queriesBeforeRetryMaster queries since
2543: // we failed over
2544: //
2545: synchronized (this .mutex) {
2546: long queryStartTime = 0;
2547:
2548: int endOfQueryPacketPosition = 0;
2549:
2550: if (packet != null) {
2551: endOfQueryPacketPosition = packet.getPosition();
2552: }
2553:
2554: if (getGatherPerformanceMetrics()) {
2555: queryStartTime = System.currentTimeMillis();
2556: }
2557:
2558: this .lastQueryFinishedTime = 0; // we're busy!
2559:
2560: if (this .failedOver && this .autoCommit && !isBatch) {
2561: if (shouldFallBack()
2562: && !this .executingFailoverReconnect) {
2563: try {
2564: this .executingFailoverReconnect = true;
2565:
2566: createNewIO(true);
2567:
2568: String connectedHost = this .io.getHost();
2569:
2570: if ((connectedHost != null)
2571: && this .hostList.get(0).equals(
2572: connectedHost)) {
2573: this .failedOver = false;
2574: this .queriesIssuedFailedOver = 0;
2575: setReadOnlyInternal(false);
2576: }
2577: } finally {
2578: this .executingFailoverReconnect = false;
2579: }
2580: }
2581: }
2582:
2583: if ((getHighAvailability() || this .failedOver)
2584: && (this .autoCommit || getAutoReconnectForPools())
2585: && this .needsPing && !isBatch) {
2586: try {
2587: pingInternal(false);
2588:
2589: this .needsPing = false;
2590: } catch (Exception Ex) {
2591: createNewIO(true);
2592: }
2593: }
2594:
2595: try {
2596: if (packet == null) {
2597: String encoding = null;
2598:
2599: if (getUseUnicode()) {
2600: encoding = getEncoding();
2601: }
2602:
2603: return this .io.sqlQueryDirect(callingStatement,
2604: sql, encoding, null, maxRows,
2605: resultSetType, resultSetConcurrency,
2606: streamResults, catalog, cachedMetadata);
2607: }
2608:
2609: return this .io.sqlQueryDirect(callingStatement, null,
2610: null, packet, maxRows, resultSetType,
2611: resultSetConcurrency, streamResults, catalog,
2612: cachedMetadata);
2613: } catch (java.sql.SQLException sqlE) {
2614: // don't clobber SQL exceptions
2615:
2616: if (getDumpQueriesOnException()) {
2617: String extractedSql = extractSqlFromPacket(sql,
2618: packet, endOfQueryPacketPosition);
2619: StringBuffer messageBuf = new StringBuffer(
2620: extractedSql.length() + 32);
2621: messageBuf
2622: .append("\n\nQuery being executed when exception was thrown:\n\n");
2623: messageBuf.append(extractedSql);
2624:
2625: sqlE = appendMessageToException(sqlE, messageBuf
2626: .toString());
2627: }
2628:
2629: if ((getHighAvailability() || this .failedOver)) {
2630: this .needsPing = true;
2631: } else {
2632: String sqlState = sqlE.getSQLState();
2633:
2634: if ((sqlState != null)
2635: && sqlState
2636: .equals(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)) {
2637: cleanup(sqlE);
2638: }
2639: }
2640:
2641: throw sqlE;
2642: } catch (Exception ex) {
2643: if ((getHighAvailability() || this .failedOver)) {
2644: this .needsPing = true;
2645: } else if (ex instanceof IOException) {
2646: cleanup(ex);
2647: }
2648:
2649: String exceptionType = ex.getClass().getName();
2650: String exceptionMessage = ex.getMessage();
2651:
2652: if (!getParanoid()) {
2653: exceptionMessage += "\n\nNested Stack Trace:\n";
2654: exceptionMessage += Util.stackTraceToString(ex);
2655: }
2656:
2657: throw new java.sql.SQLException(
2658: "Error during query: Unexpected Exception: "
2659: + exceptionType + " message given: "
2660: + exceptionMessage,
2661: SQLError.SQL_STATE_GENERAL_ERROR);
2662: } finally {
2663: if (getMaintainTimeStats()) {
2664: this .lastQueryFinishedTime = System
2665: .currentTimeMillis();
2666: }
2667:
2668: if (this .failedOver) {
2669: this .queriesIssuedFailedOver++;
2670: }
2671:
2672: if (getGatherPerformanceMetrics()) {
2673: long queryTime = System.currentTimeMillis()
2674: - queryStartTime;
2675:
2676: registerQueryExecutionTime(queryTime);
2677: }
2678: }
2679: }
2680: }
2681:
2682: protected String extractSqlFromPacket(String possibleSqlQuery,
2683: Buffer queryPacket, int endOfQueryPacketPosition)
2684: throws SQLException {
2685:
2686: String extractedSql = null;
2687:
2688: if (possibleSqlQuery != null) {
2689: if (possibleSqlQuery.length() > getMaxQuerySizeToLog()) {
2690: StringBuffer truncatedQueryBuf = new StringBuffer(
2691: possibleSqlQuery.substring(0,
2692: getMaxQuerySizeToLog()));
2693: truncatedQueryBuf.append(Messages
2694: .getString("MysqlIO.25"));
2695: extractedSql = truncatedQueryBuf.toString();
2696: } else {
2697: extractedSql = possibleSqlQuery;
2698: }
2699: }
2700:
2701: if (extractedSql == null) {
2702: // This is probably from a client-side prepared
2703: // statement
2704:
2705: int extractPosition = endOfQueryPacketPosition;
2706:
2707: boolean truncated = false;
2708:
2709: if (endOfQueryPacketPosition > getMaxQuerySizeToLog()) {
2710: extractPosition = getMaxQuerySizeToLog();
2711: truncated = true;
2712: }
2713:
2714: extractedSql = new String(queryPacket.getByteBuffer(), 5,
2715: (extractPosition - 5));
2716:
2717: if (truncated) {
2718: extractedSql += Messages.getString("MysqlIO.25"); //$NON-NLS-1$
2719: }
2720: }
2721:
2722: return extractedSql;
2723:
2724: }
2725:
2726: /**
2727: * DOCUMENT ME!
2728: *
2729: * @throws Throwable
2730: * DOCUMENT ME!
2731: */
2732: protected void finalize() throws Throwable {
2733: cleanup(null);
2734:
2735: super .finalize();
2736: }
2737:
2738: protected StringBuffer generateConnectionCommentBlock(
2739: StringBuffer buf) {
2740: buf.append("/* conn id ");
2741: buf.append(getId());
2742: buf.append(" */ ");
2743:
2744: return buf;
2745: }
2746:
2747: public int getActiveStatementCount() {
2748: // Might not have one of these if
2749: // not tracking open resources
2750: if (this .openStatements != null) {
2751: synchronized (this .openStatements) {
2752: return this .openStatements.size();
2753: }
2754: }
2755:
2756: return 0;
2757: }
2758:
2759: /**
2760: * Gets the current auto-commit state
2761: *
2762: * @return Current state of auto-commit
2763: * @exception SQLException
2764: * if an error occurs
2765: * @see setAutoCommit
2766: */
2767: public boolean getAutoCommit() throws SQLException {
2768: return this .autoCommit;
2769: }
2770:
2771: /**
2772: * Optimization to only use one calendar per-session, or calculate it for
2773: * each call, depending on user configuration
2774: */
2775: protected Calendar getCalendarInstanceForSessionOrNew() {
2776: if (getDynamicCalendars()) {
2777: return Calendar.getInstance();
2778: }
2779:
2780: return getSessionLockedCalendar();
2781: }
2782:
2783: /**
2784: * Return the connections current catalog name, or null if no catalog name
2785: * is set, or we dont support catalogs.
2786: * <p>
2787: * <b>Note:</b> MySQL's notion of catalogs are individual databases.
2788: * </p>
2789: *
2790: * @return the current catalog name or null
2791: * @exception SQLException
2792: * if a database access error occurs
2793: */
2794: public String getCatalog() throws SQLException {
2795: return this .database;
2796: }
2797:
2798: /**
2799: * @return Returns the characterSetMetadata.
2800: */
2801: protected String getCharacterSetMetadata() {
2802: return characterSetMetadata;
2803: }
2804:
2805: /**
2806: * Returns the locally mapped instance of a charset converter (to avoid
2807: * overhead of static synchronization).
2808: *
2809: * @param javaEncodingName
2810: * the encoding name to retrieve
2811: * @return a character converter, or null if one couldn't be mapped.
2812: */
2813: SingleByteCharsetConverter getCharsetConverter(
2814: String javaEncodingName) throws SQLException {
2815: if (javaEncodingName == null) {
2816: return null;
2817: }
2818:
2819: if (this .usePlatformCharsetConverters) {
2820: return null; // we'll use Java's built-in routines for this
2821: // they're finally fast enough
2822: }
2823:
2824: SingleByteCharsetConverter converter = null;
2825:
2826: synchronized (this .charsetConverterMap) {
2827: Object asObject = this .charsetConverterMap
2828: .get(javaEncodingName);
2829:
2830: if (asObject == CHARSET_CONVERTER_NOT_AVAILABLE_MARKER) {
2831: return null;
2832: }
2833:
2834: converter = (SingleByteCharsetConverter) asObject;
2835:
2836: if (converter == null) {
2837: try {
2838: converter = SingleByteCharsetConverter.getInstance(
2839: javaEncodingName, this );
2840:
2841: if (converter == null) {
2842: this .charsetConverterMap.put(javaEncodingName,
2843: CHARSET_CONVERTER_NOT_AVAILABLE_MARKER);
2844: } else {
2845: this .charsetConverterMap.put(javaEncodingName,
2846: converter);
2847: }
2848: } catch (UnsupportedEncodingException unsupEncEx) {
2849: this .charsetConverterMap.put(javaEncodingName,
2850: CHARSET_CONVERTER_NOT_AVAILABLE_MARKER);
2851:
2852: converter = null;
2853: }
2854: }
2855: }
2856:
2857: return converter;
2858: }
2859:
2860: /**
2861: * Returns the Java character encoding name for the given MySQL server
2862: * charset index
2863: *
2864: * @param charsetIndex
2865: * @return the Java character encoding name for the given MySQL server
2866: * charset index
2867: * @throws SQLException
2868: * if the character set index isn't known by the driver
2869: */
2870: protected String getCharsetNameForIndex(int charsetIndex)
2871: throws SQLException {
2872: String charsetName = null;
2873:
2874: if (getUseOldUTF8Behavior()) {
2875: return getEncoding();
2876: }
2877:
2878: if (charsetIndex != MysqlDefs.NO_CHARSET_INFO) {
2879: try {
2880: charsetName = this .indexToCharsetMapping[charsetIndex];
2881:
2882: if ("sjis".equalsIgnoreCase(charsetName)
2883: || "MS932".equalsIgnoreCase(charsetName) /* for JDK6 */) {
2884: // Use our encoding so that code pages like Cp932 work
2885: if (CharsetMapping.isAliasForSjis(getEncoding())) {
2886: charsetName = getEncoding();
2887: }
2888: }
2889: } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) {
2890: throw SQLError.createSQLException(
2891: "Unknown character set index for field '"
2892: + charsetIndex
2893: + "' received from server.",
2894: SQLError.SQL_STATE_GENERAL_ERROR);
2895: }
2896:
2897: // Punt
2898: if (charsetName == null) {
2899: charsetName = getEncoding();
2900: }
2901: } else {
2902: charsetName = getEncoding();
2903: }
2904:
2905: return charsetName;
2906: }
2907:
2908: /**
2909: * DOCUMENT ME!
2910: *
2911: * @return Returns the defaultTimeZone.
2912: */
2913: protected TimeZone getDefaultTimeZone() {
2914: return this .defaultTimeZone;
2915: }
2916:
2917: protected String getErrorMessageEncoding() {
2918: return errorMessageEncoding;
2919: }
2920:
2921: /**
2922: * @see Connection#getHoldability()
2923: */
2924: public int getHoldability() throws SQLException {
2925: return java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT;
2926: }
2927:
2928: long getId() {
2929: return this .connectionId;
2930: }
2931:
2932: /**
2933: * NOT JDBC-Compliant, but clients can use this method to determine how long
2934: * this connection has been idle. This time (reported in milliseconds) is
2935: * updated once a query has completed.
2936: *
2937: * @return number of ms that this connection has been idle, 0 if the driver
2938: * is busy retrieving results.
2939: */
2940: public long getIdleFor() {
2941: if (this .lastQueryFinishedTime == 0) {
2942: return 0;
2943: }
2944:
2945: long now = System.currentTimeMillis();
2946: long idleTime = now - this .lastQueryFinishedTime;
2947:
2948: return idleTime;
2949: }
2950:
2951: /**
2952: * Returns the IO channel to the server
2953: *
2954: * @return the IO channel to the server
2955: * @throws SQLException
2956: * if the connection is closed.
2957: */
2958: protected MysqlIO getIO() throws SQLException {
2959: if ((this .io == null) || this .isClosed) {
2960: throw SQLError.createSQLException(
2961: "Operation not allowed on closed connection",
2962: SQLError.SQL_STATE_CONNECTION_NOT_OPEN);
2963: }
2964:
2965: return this .io;
2966: }
2967:
2968: /**
2969: * Returns the log mechanism that should be used to log information from/for
2970: * this Connection.
2971: *
2972: * @return the Log instance to use for logging messages.
2973: * @throws SQLException
2974: * if an error occurs
2975: */
2976: public Log getLog() throws SQLException {
2977: return this .log;
2978: }
2979:
2980: /**
2981: * Returns the maximum packet size the MySQL server will accept
2982: *
2983: * @return DOCUMENT ME!
2984: */
2985: int getMaxAllowedPacket() {
2986: return this .maxAllowedPacket;
2987: }
2988:
2989: protected int getMaxBytesPerChar(String javaCharsetName)
2990: throws SQLException {
2991: // TODO: Check if we can actually run this query at this point in time
2992: String charset = CharsetMapping
2993: .getMysqlEncodingForJavaEncoding(javaCharsetName, this );
2994:
2995: if (versionMeetsMinimum(4, 1, 0)) {
2996: Map mapToCheck = null;
2997:
2998: if (!getUseDynamicCharsetInfo()) {
2999: mapToCheck = CharsetMapping.STATIC_CHARSET_TO_NUM_BYTES_MAP;
3000: } else {
3001: mapToCheck = this .charsetToNumBytesMap;
3002:
3003: synchronized (this .charsetToNumBytesMap) {
3004: if (this .charsetToNumBytesMap.isEmpty()) {
3005:
3006: java.sql.Statement stmt = null;
3007: java.sql.ResultSet rs = null;
3008:
3009: try {
3010: stmt = getMetadataSafeStatement();
3011:
3012: rs = stmt
3013: .executeQuery("SHOW CHARACTER SET");
3014:
3015: while (rs.next()) {
3016: this .charsetToNumBytesMap.put(rs
3017: .getString("Charset"),
3018: Constants.integerValueOf(rs
3019: .getInt("Maxlen")));
3020: }
3021:
3022: rs.close();
3023: rs = null;
3024:
3025: stmt.close();
3026:
3027: stmt = null;
3028: } finally {
3029: if (rs != null) {
3030: rs.close();
3031: rs = null;
3032: }
3033:
3034: if (stmt != null) {
3035: stmt.close();
3036: stmt = null;
3037: }
3038: }
3039: }
3040: }
3041: }
3042:
3043: Integer mbPerChar = (Integer) mapToCheck.get(charset);
3044:
3045: if (mbPerChar != null) {
3046: return mbPerChar.intValue();
3047: }
3048:
3049: return 1; // we don't know
3050: }
3051:
3052: return 1; // we don't know
3053: }
3054:
3055: /**
3056: * A connection's database is able to provide information describing its
3057: * tables, its supported SQL grammar, its stored procedures, the
3058: * capabilities of this connection, etc. This information is made available
3059: * through a DatabaseMetaData object.
3060: *
3061: * @return a DatabaseMetaData object for this connection
3062: * @exception SQLException
3063: * if a database access error occurs
3064: */
3065: public java.sql.DatabaseMetaData getMetaData() throws SQLException {
3066: checkClosed();
3067: return com.mysql.jdbc.DatabaseMetaData.getInstance(this ,
3068: this .database);
3069: }
3070:
3071: protected java.sql.Statement getMetadataSafeStatement()
3072: throws SQLException {
3073: java.sql.Statement stmt = createStatement();
3074:
3075: if (stmt.getMaxRows() != 0) {
3076: stmt.setMaxRows(0);
3077: }
3078:
3079: stmt.setEscapeProcessing(false);
3080:
3081: return stmt;
3082: }
3083:
3084: /**
3085: * Returns the Mutex all queries are locked against
3086: *
3087: * @return DOCUMENT ME!
3088: * @throws SQLException
3089: * DOCUMENT ME!
3090: */
3091: Object getMutex() throws SQLException {
3092: if (this .io == null) {
3093: throw SQLError
3094: .createSQLException(
3095: "Connection.close() has already been called. Invalid operation in this state.",
3096: SQLError.SQL_STATE_CONNECTION_NOT_OPEN);
3097: }
3098:
3099: reportMetricsIfNeeded();
3100:
3101: return this .mutex;
3102: }
3103:
3104: /**
3105: * Returns the packet buffer size the MySQL server reported upon connection
3106: *
3107: * @return DOCUMENT ME!
3108: */
3109: int getNetBufferLength() {
3110: return this .netBufferLength;
3111: }
3112:
3113: /**
3114: * Returns the server's character set
3115: *
3116: * @return the server's character set.
3117: */
3118: public String getServerCharacterEncoding() {
3119: if (this .io.versionMeetsMinimum(4, 1, 0)) {
3120: return (String) this .serverVariables
3121: .get("character_set_server");
3122: } else {
3123: return (String) this .serverVariables.get("character_set");
3124: }
3125: }
3126:
3127: int getServerMajorVersion() {
3128: return this .io.getServerMajorVersion();
3129: }
3130:
3131: int getServerMinorVersion() {
3132: return this .io.getServerMinorVersion();
3133: }
3134:
3135: int getServerSubMinorVersion() {
3136: return this .io.getServerSubMinorVersion();
3137: }
3138:
3139: /**
3140: * DOCUMENT ME!
3141: *
3142: * @return DOCUMENT ME!
3143: */
3144: public TimeZone getServerTimezoneTZ() {
3145: return this .serverTimezoneTZ;
3146: }
3147:
3148: String getServerVariable(String variableName) {
3149: if (this .serverVariables != null) {
3150: return (String) this .serverVariables.get(variableName);
3151: }
3152:
3153: return null;
3154: }
3155:
3156: String getServerVersion() {
3157: return this .io.getServerVersion();
3158: }
3159:
3160: protected Calendar getSessionLockedCalendar() {
3161:
3162: return this .sessionCalendar;
3163: }
3164:
3165: /**
3166: * Get this Connection's current transaction isolation mode.
3167: *
3168: * @return the current TRANSACTION_ mode value
3169: * @exception SQLException
3170: * if a database access error occurs
3171: */
3172: public int getTransactionIsolation() throws SQLException {
3173:
3174: if (this .hasIsolationLevels && !getUseLocalSessionState()) {
3175: java.sql.Statement stmt = null;
3176: java.sql.ResultSet rs = null;
3177:
3178: try {
3179: stmt = getMetadataSafeStatement();
3180:
3181: String query = null;
3182:
3183: int offset = 0;
3184:
3185: if (versionMeetsMinimum(4, 0, 3)) {
3186: query = "SELECT @@session.tx_isolation";
3187: offset = 1;
3188: } else {
3189: query = "SHOW VARIABLES LIKE 'transaction_isolation'";
3190: offset = 2;
3191: }
3192:
3193: rs = stmt.executeQuery(query);
3194:
3195: if (rs.next()) {
3196: String s = rs.getString(offset);
3197:
3198: if (s != null) {
3199: Integer intTI = (Integer) mapTransIsolationNameToValue
3200: .get(s);
3201:
3202: if (intTI != null) {
3203: return intTI.intValue();
3204: }
3205: }
3206:
3207: throw SQLError.createSQLException(
3208: "Could not map transaction isolation '" + s
3209: + " to a valid JDBC level.",
3210: SQLError.SQL_STATE_GENERAL_ERROR);
3211: }
3212:
3213: throw SQLError
3214: .createSQLException(
3215: "Could not retrieve transaction isolation level from server",
3216: SQLError.SQL_STATE_GENERAL_ERROR);
3217:
3218: } finally {
3219: if (rs != null) {
3220: try {
3221: rs.close();
3222: } catch (Exception ex) {
3223: // ignore
3224: ;
3225: }
3226:
3227: rs = null;
3228: }
3229:
3230: if (stmt != null) {
3231: try {
3232: stmt.close();
3233: } catch (Exception ex) {
3234: // ignore
3235: ;
3236: }
3237:
3238: stmt = null;
3239: }
3240: }
3241: }
3242:
3243: return this .isolationLevel;
3244: }
3245:
3246: /**
3247: * JDBC 2.0 Get the type-map object associated with this connection. By
3248: * default, the map returned is empty.
3249: *
3250: * @return the type map
3251: * @throws SQLException
3252: * if a database error occurs
3253: */
3254: public synchronized java.util.Map getTypeMap() throws SQLException {
3255: if (this .typeMap == null) {
3256: this .typeMap = new HashMap();
3257: }
3258:
3259: return this .typeMap;
3260: }
3261:
3262: String getURL() {
3263: return this .myURL;
3264: }
3265:
3266: String getUser() {
3267: return this .user;
3268: }
3269:
3270: protected Calendar getUtcCalendar() {
3271: return this .utcCalendar;
3272: }
3273:
3274: /**
3275: * The first warning reported by calls on this Connection is returned.
3276: * <B>Note:</B> Sebsequent warnings will be changed to this
3277: * java.sql.SQLWarning
3278: *
3279: * @return the first java.sql.SQLWarning or null
3280: * @exception SQLException
3281: * if a database access error occurs
3282: */
3283: public SQLWarning getWarnings() throws SQLException {
3284: return null;
3285: }
3286:
3287: public boolean hasSameProperties(Connection c) {
3288: return this .props.equals(((ConnectionImpl) c).props);
3289: }
3290:
3291: public boolean hasTriedMaster() {
3292: return this .hasTriedMasterFlag;
3293: }
3294:
3295: protected void incrementNumberOfPreparedExecutes() {
3296: if (getGatherPerformanceMetrics()) {
3297: this .numberOfPreparedExecutes++;
3298:
3299: // We need to increment this, because
3300: // server-side prepared statements bypass
3301: // any execution by the connection itself...
3302: this .numberOfQueriesIssued++;
3303: }
3304: }
3305:
3306: protected void incrementNumberOfPrepares() {
3307: if (getGatherPerformanceMetrics()) {
3308: this .numberOfPrepares++;
3309: }
3310: }
3311:
3312: protected void incrementNumberOfResultSetsCreated() {
3313: if (getGatherPerformanceMetrics()) {
3314: this .numberOfResultSetsCreated++;
3315: }
3316: }
3317:
3318: /**
3319: * Initializes driver properties that come from URL or properties passed to
3320: * the driver manager.
3321: *
3322: * @param info
3323: * DOCUMENT ME!
3324: * @throws SQLException
3325: * DOCUMENT ME!
3326: */
3327: private void initializeDriverProperties(Properties info)
3328: throws SQLException {
3329: initializeProperties(info);
3330:
3331: this .usePlatformCharsetConverters = getUseJvmCharsetConverters();
3332:
3333: this .log = LogFactory.getLogger(getLogger(),
3334: LOGGER_INSTANCE_NAME);
3335:
3336: if (getProfileSql() || getUseUsageAdvisor()) {
3337: this .eventSink = ProfileEventSink.getInstance(this );
3338: }
3339:
3340: if (getCachePreparedStatements()) {
3341: createPreparedStatementCaches();
3342: }
3343:
3344: if (getNoDatetimeStringSync() && getUseTimezone()) {
3345: throw SQLError.createSQLException(
3346: "Can't enable noDatetimeSync and useTimezone configuration "
3347: + "properties at the same time",
3348: SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
3349: }
3350:
3351: if (getCacheCallableStatements()) {
3352: this .parsedCallableStatementCache = new LRUCache(
3353: getCallableStatementCacheSize());
3354: }
3355:
3356: if (getAllowMultiQueries()) {
3357: setCacheResultSetMetadata(false); // we don't handle this yet
3358: }
3359:
3360: if (getCacheResultSetMetadata()) {
3361: this .resultSetMetadataCache = new LRUCache(
3362: getMetadataCacheSize());
3363: }
3364: }
3365:
3366: /**
3367: * Sets varying properties that depend on server information. Called once we
3368: * have connected to the server.
3369: *
3370: * @param info
3371: * DOCUMENT ME!
3372: * @throws SQLException
3373: * DOCUMENT ME!
3374: */
3375: private void initializePropsFromServer() throws SQLException {
3376: setSessionVariables();
3377:
3378: //
3379: // the "boolean" type didn't come along until MySQL-4.1
3380: //
3381:
3382: if (!versionMeetsMinimum(4, 1, 0)) {
3383: setTransformedBitIsBoolean(false);
3384: }
3385:
3386: this .parserKnowsUnicode = versionMeetsMinimum(4, 1, 0);
3387:
3388: //
3389: // Users can turn off detection of server-side prepared statements
3390: //
3391: if (getUseServerPreparedStmts() && versionMeetsMinimum(4, 1, 0)) {
3392: this .useServerPreparedStmts = true;
3393:
3394: if (versionMeetsMinimum(5, 0, 0)
3395: && !versionMeetsMinimum(5, 0, 3)) {
3396: this .useServerPreparedStmts = false; // 4.1.2+ style prepared
3397: // statements
3398: // don't work on these versions
3399: }
3400: }
3401:
3402: this .serverVariables.clear();
3403:
3404: //
3405: // If version is greater than 3.21.22 get the server
3406: // variables.
3407: if (versionMeetsMinimum(3, 21, 22)) {
3408: loadServerVariables();
3409:
3410: buildCollationMapping();
3411:
3412: LicenseConfiguration.checkLicenseType(this .serverVariables);
3413:
3414: String lowerCaseTables = (String) this .serverVariables
3415: .get("lower_case_table_names");
3416:
3417: this .lowerCaseTableNames = "on"
3418: .equalsIgnoreCase(lowerCaseTables)
3419: || "1".equalsIgnoreCase(lowerCaseTables)
3420: || "2".equalsIgnoreCase(lowerCaseTables);
3421:
3422: configureTimezone();
3423:
3424: if (this .serverVariables.containsKey("max_allowed_packet")) {
3425: this .maxAllowedPacket = Integer
3426: .parseInt((String) this .serverVariables
3427: .get("max_allowed_packet"));
3428:
3429: int preferredBlobSendChunkSize = getBlobSendChunkSize();
3430:
3431: int allowedBlobSendChunkSize = Math.min(
3432: preferredBlobSendChunkSize,
3433: this .maxAllowedPacket)
3434: - ServerPreparedStatement.BLOB_STREAM_READ_BUF_SIZE
3435: - 11 /* LONG_DATA and MySQLIO packet header size */;
3436:
3437: setBlobSendChunkSize(String
3438: .valueOf(allowedBlobSendChunkSize));
3439: }
3440:
3441: if (this .serverVariables.containsKey("net_buffer_length")) {
3442: this .netBufferLength = Integer
3443: .parseInt((String) this .serverVariables
3444: .get("net_buffer_length"));
3445: }
3446:
3447: checkTransactionIsolationLevel();
3448:
3449: if (!versionMeetsMinimum(4, 1, 0)) {
3450: checkServerEncoding();
3451: }
3452:
3453: this .io.checkForCharsetMismatch();
3454:
3455: if (this .serverVariables.containsKey("sql_mode")) {
3456: int sqlMode = 0;
3457:
3458: String sqlModeAsString = (String) this .serverVariables
3459: .get("sql_mode");
3460: try {
3461: sqlMode = Integer.parseInt(sqlModeAsString);
3462: } catch (NumberFormatException nfe) {
3463: // newer versions of the server has this as a string-y
3464: // list...
3465: sqlMode = 0;
3466:
3467: if (sqlModeAsString != null) {
3468: if (sqlModeAsString.indexOf("ANSI_QUOTES") != -1) {
3469: sqlMode |= 4;
3470: }
3471:
3472: if (sqlModeAsString
3473: .indexOf("NO_BACKSLASH_ESCAPES") != -1) {
3474: this .noBackslashEscapes = true;
3475: }
3476: }
3477: }
3478:
3479: if ((sqlMode & 4) > 0) {
3480: this .useAnsiQuotes = true;
3481: } else {
3482: this .useAnsiQuotes = false;
3483: }
3484: }
3485: }
3486:
3487: this .errorMessageEncoding = CharsetMapping
3488: .getCharacterEncodingForErrorMessages(this );
3489:
3490: boolean overrideDefaultAutocommit = isAutoCommitNonDefaultOnServer();
3491:
3492: configureClientCharacterSet(false);
3493:
3494: if (versionMeetsMinimum(3, 23, 15)) {
3495: this .transactionsSupported = true;
3496:
3497: if (!overrideDefaultAutocommit) {
3498: setAutoCommit(true); // to override anything
3499: // the server is set to...reqd
3500: // by JDBC spec.
3501: }
3502: } else {
3503: this .transactionsSupported = false;
3504: }
3505:
3506: if (versionMeetsMinimum(3, 23, 36)) {
3507: this .hasIsolationLevels = true;
3508: } else {
3509: this .hasIsolationLevels = false;
3510: }
3511:
3512: this .hasQuotedIdentifiers = versionMeetsMinimum(3, 23, 6);
3513:
3514: this .io.resetMaxBuf();
3515:
3516: //
3517: // If we're using MySQL 4.1.0 or newer, we need to figure
3518: // out what character set metadata will be returned in,
3519: // and then map that to a Java encoding name.
3520: //
3521: // We've already set it, and it might be different than what
3522: // was originally on the server, which is why we use the
3523: // "special" key to retrieve it
3524: if (this .io.versionMeetsMinimum(4, 1, 0)) {
3525: String characterSetResultsOnServerMysql = (String) this .serverVariables
3526: .get(JDBC_LOCAL_CHARACTER_SET_RESULTS);
3527:
3528: if (characterSetResultsOnServerMysql == null
3529: || StringUtils.startsWithIgnoreCaseAndWs(
3530: characterSetResultsOnServerMysql, "NULL")
3531: || characterSetResultsOnServerMysql.length() == 0) {
3532: String defaultMetadataCharsetMysql = (String) this .serverVariables
3533: .get("character_set_system");
3534: String defaultMetadataCharset = null;
3535:
3536: if (defaultMetadataCharsetMysql != null) {
3537: defaultMetadataCharset = CharsetMapping
3538: .getJavaEncodingForMysqlEncoding(
3539: defaultMetadataCharsetMysql, this );
3540: } else {
3541: defaultMetadataCharset = "UTF-8";
3542: }
3543:
3544: this .characterSetMetadata = defaultMetadataCharset;
3545: } else {
3546: this .characterSetResultsOnServer = CharsetMapping
3547: .getJavaEncodingForMysqlEncoding(
3548: characterSetResultsOnServerMysql, this );
3549: this .characterSetMetadata = this .characterSetResultsOnServer;
3550: }
3551: }
3552:
3553: //
3554: // Query cache is broken wrt. multi-statements before MySQL-4.1.10
3555: //
3556:
3557: if (this .versionMeetsMinimum(4, 1, 0)
3558: && !this .versionMeetsMinimum(4, 1, 10)
3559: && getAllowMultiQueries()) {
3560: if ("ON".equalsIgnoreCase((String) this .serverVariables
3561: .get("query_cache_type"))
3562: && !"0"
3563: .equalsIgnoreCase((String) this .serverVariables
3564: .get("query_cache_size"))) {
3565: setAllowMultiQueries(false);
3566: }
3567: }
3568:
3569: //
3570: // Server can do this more efficiently for us
3571: //
3572:
3573: setupServerForTruncationChecks();
3574: }
3575:
3576: /**
3577: * Has the default autocommit value of 0 been changed on the server
3578: * via init_connect?
3579: *
3580: * @return true if autocommit is not the default of '0' on the server.
3581: *
3582: * @throws SQLException
3583: */
3584: private boolean isAutoCommitNonDefaultOnServer()
3585: throws SQLException {
3586: boolean overrideDefaultAutocommit = false;
3587:
3588: String initConnectValue = (String) this .serverVariables
3589: .get("init_connect");
3590:
3591: if (versionMeetsMinimum(4, 1, 2) && initConnectValue != null
3592: && initConnectValue.length() > 0) {
3593: if (!getElideSetAutoCommits()) {
3594: // auto-commit might have changed
3595: java.sql.ResultSet rs = null;
3596: java.sql.Statement stmt = null;
3597:
3598: try {
3599: stmt = getMetadataSafeStatement();
3600:
3601: rs = stmt
3602: .executeQuery("SELECT @@session.autocommit");
3603:
3604: if (rs.next()) {
3605: this .autoCommit = rs.getBoolean(1);
3606: if (this .autoCommit != true) {
3607: overrideDefaultAutocommit = true;
3608: }
3609: }
3610:
3611: } finally {
3612: if (rs != null) {
3613: try {
3614: rs.close();
3615: } catch (SQLException sqlEx) {
3616: // do nothing
3617: }
3618: }
3619:
3620: if (stmt != null) {
3621: try {
3622: stmt.close();
3623: } catch (SQLException sqlEx) {
3624: // do nothing
3625: }
3626: }
3627: }
3628: } else {
3629: if (this .getIO().isSetNeededForAutoCommitMode(true)) {
3630: // we're not in standard autocommit=true mode
3631: this .autoCommit = false;
3632: overrideDefaultAutocommit = true;
3633: }
3634: }
3635: }
3636:
3637: return overrideDefaultAutocommit;
3638: }
3639:
3640: protected boolean isClientTzUTC() {
3641: return this .isClientTzUTC;
3642: }
3643:
3644: /**
3645: * DOCUMENT ME!
3646: *
3647: * @return DOCUMENT ME!
3648: */
3649: public boolean isClosed() {
3650: return this .isClosed;
3651: }
3652:
3653: protected boolean isCursorFetchEnabled() throws SQLException {
3654: return (versionMeetsMinimum(5, 0, 2) && getUseCursorFetch());
3655: }
3656:
3657: public boolean isInGlobalTx() {
3658: return this .isInGlobalTx;
3659: }
3660:
3661: /**
3662: * Is this connection connected to the first host in the list if
3663: * there is a list of servers in the URL?
3664: *
3665: * @return true if this connection is connected to the first in
3666: * the list.
3667: */
3668: public synchronized boolean isMasterConnection() {
3669: return !this .failedOver;
3670: }
3671:
3672: /**
3673: * Is the server in a sql_mode that doesn't allow us to use \\ to escape
3674: * things?
3675: *
3676: * @return Returns the noBackslashEscapes.
3677: */
3678: public boolean isNoBackslashEscapesSet() {
3679: return this .noBackslashEscapes;
3680: }
3681:
3682: boolean isReadInfoMsgEnabled() {
3683: return this .readInfoMsg;
3684: }
3685:
3686: /**
3687: * Tests to see if the connection is in Read Only Mode. Note that we cannot
3688: * really put the database in read only mode, but we pretend we can by
3689: * returning the value of the readOnly flag
3690: *
3691: * @return true if the connection is read only
3692: * @exception SQLException
3693: * if a database access error occurs
3694: */
3695: public boolean isReadOnly() throws SQLException {
3696: return this .readOnly;
3697: }
3698:
3699: protected boolean isRunningOnJDK13() {
3700: return this .isRunningOnJDK13;
3701: }
3702:
3703: public synchronized boolean isSameResource(
3704: Connection otherConnection) {
3705: if (otherConnection == null) {
3706: return false;
3707: }
3708:
3709: boolean directCompare = true;
3710:
3711: String otherHost = ((ConnectionImpl) otherConnection).origHostToConnectTo;
3712: String otherOrigDatabase = ((ConnectionImpl) otherConnection).origDatabaseToConnectTo;
3713: String otherCurrentCatalog = ((ConnectionImpl) otherConnection).database;
3714:
3715: if (!nullSafeCompare(otherHost, this .origHostToConnectTo)) {
3716: directCompare = false;
3717: } else if (otherHost != null && otherHost.indexOf(',') == -1
3718: && otherHost.indexOf(':') == -1) {
3719: // need to check port numbers
3720: directCompare = (((ConnectionImpl) otherConnection).origPortToConnectTo == this .origPortToConnectTo);
3721: }
3722:
3723: if (directCompare) {
3724: if (!nullSafeCompare(otherOrigDatabase,
3725: this .origDatabaseToConnectTo)) {
3726: directCompare = false;
3727: directCompare = false;
3728: } else if (!nullSafeCompare(otherCurrentCatalog,
3729: this .database)) {
3730: directCompare = false;
3731: }
3732: }
3733:
3734: if (directCompare) {
3735: return true;
3736: }
3737:
3738: // Has the user explicitly set a resourceId?
3739: String otherResourceId = ((ConnectionImpl) otherConnection)
3740: .getResourceId();
3741: String myResourceId = getResourceId();
3742:
3743: if (otherResourceId != null || myResourceId != null) {
3744: directCompare = nullSafeCompare(otherResourceId,
3745: myResourceId);
3746:
3747: if (directCompare) {
3748: return true;
3749: }
3750: }
3751:
3752: return false;
3753: }
3754:
3755: protected boolean isServerTzUTC() {
3756: return this .isServerTzUTC;
3757: }
3758:
3759: private boolean usingCachedConfig = false;
3760:
3761: /**
3762: * Loads the result of 'SHOW VARIABLES' into the serverVariables field so
3763: * that the driver can configure itself.
3764: *
3765: * @throws SQLException
3766: * if the 'SHOW VARIABLES' query fails for any reason.
3767: */
3768: private void loadServerVariables() throws SQLException {
3769:
3770: if (getCacheServerConfiguration()) {
3771: synchronized (serverConfigByUrl) {
3772: Map cachedVariableMap = (Map) serverConfigByUrl
3773: .get(getURL());
3774:
3775: if (cachedVariableMap != null) {
3776: this .serverVariables = cachedVariableMap;
3777: this .usingCachedConfig = true;
3778:
3779: return;
3780: }
3781: }
3782: }
3783:
3784: java.sql.Statement stmt = null;
3785: java.sql.ResultSet results = null;
3786:
3787: try {
3788: stmt = createStatement();
3789: stmt.setEscapeProcessing(false);
3790:
3791: results = stmt.executeQuery("SHOW VARIABLES");
3792:
3793: while (results.next()) {
3794: this .serverVariables.put(results.getString(1), results
3795: .getString(2));
3796: }
3797:
3798: if (getCacheServerConfiguration()) {
3799: synchronized (serverConfigByUrl) {
3800: serverConfigByUrl.put(getURL(),
3801: this .serverVariables);
3802: }
3803: }
3804: } catch (SQLException e) {
3805: throw e;
3806: } finally {
3807: if (results != null) {
3808: try {
3809: results.close();
3810: } catch (SQLException sqlE) {
3811: ;
3812: }
3813: }
3814:
3815: if (stmt != null) {
3816: try {
3817: stmt.close();
3818: } catch (SQLException sqlE) {
3819: ;
3820: }
3821: }
3822: }
3823: }
3824:
3825: /**
3826: * Is the server configured to use lower-case table names only?
3827: *
3828: * @return true if lower_case_table_names is 'on'
3829: */
3830: public boolean lowerCaseTableNames() {
3831: return this .lowerCaseTableNames;
3832: }
3833:
3834: /**
3835: * Has the maxRows value changed?
3836: *
3837: * @param stmt
3838: * DOCUMENT ME!
3839: */
3840: void maxRowsChanged(StatementImpl stmt) {
3841: synchronized (this .mutex) {
3842: if (this .statementsUsingMaxRows == null) {
3843: this .statementsUsingMaxRows = new HashMap();
3844: }
3845:
3846: this .statementsUsingMaxRows.put(stmt, stmt);
3847:
3848: this .maxRowsChanged = true;
3849: }
3850: }
3851:
3852: /**
3853: * A driver may convert the JDBC sql grammar into its system's native SQL
3854: * grammar prior to sending it; nativeSQL returns the native form of the
3855: * statement that the driver would have sent.
3856: *
3857: * @param sql
3858: * a SQL statement that may contain one or more '?' parameter
3859: * placeholders
3860: * @return the native form of this statement
3861: * @exception SQLException
3862: * if a database access error occurs
3863: */
3864: public String nativeSQL(String sql) throws SQLException {
3865: if (sql == null) {
3866: return null;
3867: }
3868:
3869: Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
3870: serverSupportsConvertFn(), this );
3871:
3872: if (escapedSqlResult instanceof String) {
3873: return (String) escapedSqlResult;
3874: }
3875:
3876: return ((EscapeProcessorResult) escapedSqlResult).escapedSql;
3877: }
3878:
3879: private CallableStatement parseCallableStatement(String sql)
3880: throws SQLException {
3881: Object escapedSqlResult = EscapeProcessor.escapeSQL(sql,
3882: serverSupportsConvertFn(), this );
3883:
3884: boolean isFunctionCall = false;
3885: String parsedSql = null;
3886:
3887: if (escapedSqlResult instanceof EscapeProcessorResult) {
3888: parsedSql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
3889: isFunctionCall = ((EscapeProcessorResult) escapedSqlResult).callingStoredFunction;
3890: } else {
3891: parsedSql = (String) escapedSqlResult;
3892: isFunctionCall = false;
3893: }
3894:
3895: return CallableStatement.getInstance(this , parsedSql,
3896: this .database, isFunctionCall);
3897: }
3898:
3899: /**
3900: * DOCUMENT ME!
3901: *
3902: * @return DOCUMENT ME!
3903: */
3904: public boolean parserKnowsUnicode() {
3905: return this .parserKnowsUnicode;
3906: }
3907:
3908: /**
3909: * Detect if the connection is still good
3910: *
3911: * @throws SQLException
3912: * if the ping fails
3913: */
3914: public void ping() throws SQLException {
3915: pingInternal(true);
3916: }
3917:
3918: protected void pingInternal(boolean checkForClosedConnection)
3919: throws SQLException {
3920: if (checkForClosedConnection) {
3921: checkClosed();
3922: }
3923:
3924: // Need MySQL-3.22.1, but who uses anything older!?
3925: this .io.sendCommand(MysqlDefs.PING, null, null, false, null);
3926: }
3927:
3928: /**
3929: * DOCUMENT ME!
3930: *
3931: * @param sql
3932: * DOCUMENT ME!
3933: * @return DOCUMENT ME!
3934: * @throws SQLException
3935: * DOCUMENT ME!
3936: */
3937: public java.sql.CallableStatement prepareCall(String sql)
3938: throws SQLException {
3939:
3940: return prepareCall(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY,
3941: java.sql.ResultSet.CONCUR_READ_ONLY);
3942: }
3943:
3944: /**
3945: * JDBC 2.0 Same as prepareCall() above, but allows the default result set
3946: * type and result set concurrency type to be overridden.
3947: *
3948: * @param sql
3949: * the SQL representing the callable statement
3950: * @param resultSetType
3951: * a result set type, see ResultSet.TYPE_XXX
3952: * @param resultSetConcurrency
3953: * a concurrency type, see ResultSet.CONCUR_XXX
3954: * @return a new CallableStatement object containing the pre-compiled SQL
3955: * statement
3956: * @exception SQLException
3957: * if a database-access error occurs.
3958: */
3959: public java.sql.CallableStatement prepareCall(String sql,
3960: int resultSetType, int resultSetConcurrency)
3961: throws SQLException {
3962: if (versionMeetsMinimum(5, 0, 0)) {
3963: CallableStatement cStmt = null;
3964:
3965: if (!getCacheCallableStatements()) {
3966:
3967: cStmt = parseCallableStatement(sql);
3968: } else {
3969: synchronized (this .parsedCallableStatementCache) {
3970: CompoundCacheKey key = new CompoundCacheKey(
3971: getCatalog(), sql);
3972:
3973: CallableStatement.CallableStatementParamInfo cachedParamInfo = (CallableStatement.CallableStatementParamInfo) this .parsedCallableStatementCache
3974: .get(key);
3975:
3976: if (cachedParamInfo != null) {
3977: cStmt = CallableStatement.getInstance(this ,
3978: cachedParamInfo);
3979: } else {
3980: cStmt = parseCallableStatement(sql);
3981:
3982: cachedParamInfo = cStmt.paramInfo;
3983:
3984: this .parsedCallableStatementCache.put(key,
3985: cachedParamInfo);
3986: }
3987: }
3988: }
3989:
3990: cStmt.setResultSetType(resultSetType);
3991: cStmt.setResultSetConcurrency(resultSetConcurrency);
3992:
3993: return cStmt;
3994: }
3995:
3996: throw SQLError.createSQLException("Callable statements not "
3997: + "supported.", SQLError.SQL_STATE_DRIVER_NOT_CAPABLE);
3998: }
3999:
4000: /**
4001: * @see Connection#prepareCall(String, int, int, int)
4002: */
4003: public java.sql.CallableStatement prepareCall(String sql,
4004: int resultSetType, int resultSetConcurrency,
4005: int resultSetHoldability) throws SQLException {
4006: if (getPedantic()) {
4007: if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
4008: throw SQLError
4009: .createSQLException(
4010: "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
4011: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
4012: }
4013: }
4014:
4015: CallableStatement cStmt = (com.mysql.jdbc.CallableStatement) prepareCall(
4016: sql, resultSetType, resultSetConcurrency);
4017:
4018: return cStmt;
4019: }
4020:
4021: /**
4022: * A SQL statement with or without IN parameters can be pre-compiled and
4023: * stored in a PreparedStatement object. This object can then be used to
4024: * efficiently execute this statement multiple times.
4025: * <p>
4026: * <B>Note:</B> This method is optimized for handling parametric SQL
4027: * statements that benefit from precompilation if the driver supports
4028: * precompilation. In this case, the statement is not sent to the database
4029: * until the PreparedStatement is executed. This has no direct effect on
4030: * users; however it does affect which method throws certain
4031: * java.sql.SQLExceptions
4032: * </p>
4033: * <p>
4034: * MySQL does not support precompilation of statements, so they are handled
4035: * by the driver.
4036: * </p>
4037: *
4038: * @param sql
4039: * a SQL statement that may contain one or more '?' IN parameter
4040: * placeholders
4041: * @return a new PreparedStatement object containing the pre-compiled
4042: * statement.
4043: * @exception SQLException
4044: * if a database access error occurs.
4045: */
4046: public java.sql.PreparedStatement prepareStatement(String sql)
4047: throws SQLException {
4048: return prepareStatement(sql,
4049: java.sql.ResultSet.TYPE_FORWARD_ONLY,
4050: java.sql.ResultSet.CONCUR_READ_ONLY);
4051: }
4052:
4053: /**
4054: * @see Connection#prepareStatement(String, int)
4055: */
4056: public java.sql.PreparedStatement prepareStatement(String sql,
4057: int autoGenKeyIndex) throws SQLException {
4058: java.sql.PreparedStatement pStmt = prepareStatement(sql);
4059:
4060: ((com.mysql.jdbc.PreparedStatement) pStmt)
4061: .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);
4062:
4063: return pStmt;
4064: }
4065:
4066: /**
4067: * JDBC 2.0 Same as prepareStatement() above, but allows the default result
4068: * set type and result set concurrency type to be overridden.
4069: *
4070: * @param sql
4071: * the SQL query containing place holders
4072: * @param resultSetType
4073: * a result set type, see ResultSet.TYPE_XXX
4074: * @param resultSetConcurrency
4075: * a concurrency type, see ResultSet.CONCUR_XXX
4076: * @return a new PreparedStatement object containing the pre-compiled SQL
4077: * statement
4078: * @exception SQLException
4079: * if a database-access error occurs.
4080: */
4081: public java.sql.PreparedStatement prepareStatement(String sql,
4082: int resultSetType, int resultSetConcurrency)
4083: throws SQLException {
4084: checkClosed();
4085:
4086: //
4087: // FIXME: Create warnings if can't create results of the given
4088: // type or concurrency
4089: //
4090: PreparedStatement pStmt = null;
4091:
4092: boolean canServerPrepare = true;
4093:
4094: String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql)
4095: : sql;
4096:
4097: if (getEmulateUnsupportedPstmts()) {
4098: canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
4099: }
4100:
4101: if (this .useServerPreparedStmts && canServerPrepare) {
4102: if (this .getCachePreparedStatements()) {
4103: synchronized (this .serverSideStatementCache) {
4104: pStmt = (com.mysql.jdbc.ServerPreparedStatement) this .serverSideStatementCache
4105: .remove(sql);
4106:
4107: if (pStmt != null) {
4108: ((com.mysql.jdbc.ServerPreparedStatement) pStmt)
4109: .setClosed(false);
4110: pStmt.clearParameters();
4111: }
4112:
4113: if (pStmt == null) {
4114: try {
4115: pStmt = ServerPreparedStatement
4116: .getInstance(this , nativeSql,
4117: this .database,
4118: resultSetType,
4119: resultSetConcurrency);
4120: if (sql.length() < getPreparedStatementCacheSqlLimit()) {
4121: ((com.mysql.jdbc.ServerPreparedStatement) pStmt).isCached = true;
4122: }
4123:
4124: pStmt.setResultSetType(resultSetType);
4125: pStmt
4126: .setResultSetConcurrency(resultSetConcurrency);
4127: } catch (SQLException sqlEx) {
4128: // Punt, if necessary
4129: if (getEmulateUnsupportedPstmts()) {
4130: pStmt = clientPrepareStatement(
4131: nativeSql, resultSetType,
4132: resultSetConcurrency, false);
4133:
4134: if (sql.length() < getPreparedStatementCacheSqlLimit()) {
4135: this .serverSideStatementCheckCache
4136: .put(sql, Boolean.FALSE);
4137: }
4138: } else {
4139: throw sqlEx;
4140: }
4141: }
4142: }
4143: }
4144: } else {
4145: try {
4146: pStmt = ServerPreparedStatement.getInstance(this ,
4147: nativeSql, this .database, resultSetType,
4148: resultSetConcurrency);
4149:
4150: pStmt.setResultSetType(resultSetType);
4151: pStmt.setResultSetConcurrency(resultSetConcurrency);
4152: } catch (SQLException sqlEx) {
4153: // Punt, if necessary
4154: if (getEmulateUnsupportedPstmts()) {
4155: pStmt = clientPrepareStatement(nativeSql,
4156: resultSetType, resultSetConcurrency,
4157: false);
4158: } else {
4159: throw sqlEx;
4160: }
4161: }
4162: }
4163: } else {
4164: pStmt = clientPrepareStatement(nativeSql, resultSetType,
4165: resultSetConcurrency, false);
4166: }
4167:
4168: return pStmt;
4169: }
4170:
4171: /**
4172: * @see Connection#prepareStatement(String, int, int, int)
4173: */
4174: public java.sql.PreparedStatement prepareStatement(String sql,
4175: int resultSetType, int resultSetConcurrency,
4176: int resultSetHoldability) throws SQLException {
4177: if (getPedantic()) {
4178: if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
4179: throw SQLError
4180: .createSQLException(
4181: "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
4182: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
4183: }
4184: }
4185:
4186: return prepareStatement(sql, resultSetType,
4187: resultSetConcurrency);
4188: }
4189:
4190: /**
4191: * @see Connection#prepareStatement(String, int[])
4192: */
4193: public java.sql.PreparedStatement prepareStatement(String sql,
4194: int[] autoGenKeyIndexes) throws SQLException {
4195: java.sql.PreparedStatement pStmt = prepareStatement(sql);
4196:
4197: ((com.mysql.jdbc.PreparedStatement) pStmt)
4198: .setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
4199: && (autoGenKeyIndexes.length > 0));
4200:
4201: return pStmt;
4202: }
4203:
4204: /**
4205: * @see Connection#prepareStatement(String, String[])
4206: */
4207: public java.sql.PreparedStatement prepareStatement(String sql,
4208: String[] autoGenKeyColNames) throws SQLException {
4209: java.sql.PreparedStatement pStmt = prepareStatement(sql);
4210:
4211: ((com.mysql.jdbc.PreparedStatement) pStmt)
4212: .setRetrieveGeneratedKeys((autoGenKeyColNames != null)
4213: && (autoGenKeyColNames.length > 0));
4214:
4215: return pStmt;
4216: }
4217:
4218: /**
4219: * Closes connection and frees resources.
4220: *
4221: * @param calledExplicitly
4222: * is this being called from close()
4223: * @param issueRollback
4224: * should a rollback() be issued?
4225: * @throws SQLException
4226: * if an error occurs
4227: */
4228: protected void realClose(boolean calledExplicitly,
4229: boolean issueRollback, boolean skipLocalTeardown,
4230: Throwable reason) throws SQLException {
4231: SQLException sqlEx = null;
4232:
4233: if (this .isClosed()) {
4234: return;
4235: }
4236:
4237: this .forceClosedReason = reason;
4238:
4239: try {
4240: if (!skipLocalTeardown) {
4241: if (!getAutoCommit() && issueRollback) {
4242: try {
4243: rollback();
4244: } catch (SQLException ex) {
4245: sqlEx = ex;
4246: }
4247: }
4248:
4249: reportMetrics();
4250:
4251: if (getUseUsageAdvisor()) {
4252: if (!calledExplicitly) {
4253: String message = "Connection implicitly closed by Driver. You should call Connection.close() from your code to free resources more efficiently and avoid resource leaks.";
4254:
4255: this .eventSink.consumeEvent(new ProfilerEvent(
4256: ProfilerEvent.TYPE_WARN,
4257: "", //$NON-NLS-1$
4258: this .getCatalog(), this .getId(), -1,
4259: -1, System.currentTimeMillis(), 0,
4260: Constants.MILLIS_I18N, null,
4261: this .pointOfOrigin, message));
4262: }
4263:
4264: long connectionLifeTime = System
4265: .currentTimeMillis()
4266: - this .connectionCreationTimeMillis;
4267:
4268: if (connectionLifeTime < 500) {
4269: 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.";
4270:
4271: this .eventSink.consumeEvent(new ProfilerEvent(
4272: ProfilerEvent.TYPE_WARN,
4273: "", //$NON-NLS-1$
4274: this .getCatalog(), this .getId(), -1,
4275: -1, System.currentTimeMillis(), 0,
4276: Constants.MILLIS_I18N, null,
4277: this .pointOfOrigin, message));
4278: }
4279: }
4280:
4281: try {
4282: closeAllOpenStatements();
4283: } catch (SQLException ex) {
4284: sqlEx = ex;
4285: }
4286:
4287: if (this .io != null) {
4288: try {
4289: this .io.quit();
4290: } catch (Exception e) {
4291: ;
4292: }
4293:
4294: }
4295: } else {
4296: this .io.forceClose();
4297: }
4298: } finally {
4299: this .openStatements = null;
4300: this .io = null;
4301: ProfileEventSink.removeInstance(this );
4302: this .isClosed = true;
4303: }
4304:
4305: if (sqlEx != null) {
4306: throw sqlEx;
4307: }
4308:
4309: }
4310:
4311: protected void recachePreparedStatement(
4312: ServerPreparedStatement pstmt) throws SQLException {
4313: if (pstmt.isPoolable()) {
4314: synchronized (this .serverSideStatementCache) {
4315: this .serverSideStatementCache.put(pstmt.originalSql,
4316: pstmt);
4317: }
4318: }
4319: }
4320:
4321: /**
4322: * DOCUMENT ME!
4323: *
4324: * @param queryTimeMs
4325: */
4326: protected void registerQueryExecutionTime(long queryTimeMs) {
4327: if (queryTimeMs > this .longestQueryTimeMs) {
4328: this .longestQueryTimeMs = queryTimeMs;
4329:
4330: repartitionPerformanceHistogram();
4331: }
4332:
4333: addToPerformanceHistogram(queryTimeMs, 1);
4334:
4335: if (queryTimeMs < this .shortestQueryTimeMs) {
4336: this .shortestQueryTimeMs = (queryTimeMs == 0) ? 1
4337: : queryTimeMs;
4338: }
4339:
4340: this .numberOfQueriesIssued++;
4341:
4342: this .totalQueryTimeMs += queryTimeMs;
4343: }
4344:
4345: /**
4346: * Register a Statement instance as open.
4347: *
4348: * @param stmt
4349: * the Statement instance to remove
4350: */
4351: void registerStatement(StatementImpl stmt) {
4352: synchronized (this .openStatements) {
4353: this .openStatements.put(stmt, stmt);
4354: }
4355: }
4356:
4357: /**
4358: * @see Connection#releaseSavepoint(Savepoint)
4359: */
4360: public void releaseSavepoint(Savepoint arg0) throws SQLException {
4361: // this is a no-op
4362: }
4363:
4364: private void repartitionHistogram(int[] histCounts,
4365: long[] histBreakpoints, long currentLowerBound,
4366: long currentUpperBound) {
4367:
4368: if (oldHistCounts == null) {
4369: oldHistCounts = new int[histCounts.length];
4370: oldHistBreakpoints = new long[histBreakpoints.length];
4371: }
4372:
4373: System.arraycopy(histCounts, 0, oldHistCounts, 0,
4374: histCounts.length);
4375:
4376: System.arraycopy(histBreakpoints, 0, oldHistBreakpoints, 0,
4377: histBreakpoints.length);
4378:
4379: createInitialHistogram(histBreakpoints, currentLowerBound,
4380: currentUpperBound);
4381:
4382: for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
4383: addToHistogram(histCounts, histBreakpoints,
4384: oldHistBreakpoints[i], oldHistCounts[i],
4385: currentLowerBound, currentUpperBound);
4386: }
4387: }
4388:
4389: private void repartitionPerformanceHistogram() {
4390: checkAndCreatePerformanceHistogram();
4391:
4392: repartitionHistogram(this .perfMetricsHistCounts,
4393: this .perfMetricsHistBreakpoints,
4394: this .shortestQueryTimeMs == Long.MAX_VALUE ? 0
4395: : this .shortestQueryTimeMs,
4396: this .longestQueryTimeMs);
4397: }
4398:
4399: private void repartitionTablesAccessedHistogram() {
4400: checkAndCreateTablesAccessedHistogram();
4401:
4402: repartitionHistogram(this .numTablesMetricsHistCounts,
4403: this .numTablesMetricsHistBreakpoints,
4404: this .minimumNumberTablesAccessed == Long.MAX_VALUE ? 0
4405: : this .minimumNumberTablesAccessed,
4406: this .maximumNumberTablesAccessed);
4407: }
4408:
4409: private void reportMetrics() {
4410: if (getGatherPerformanceMetrics()) {
4411: StringBuffer logMessage = new StringBuffer(256);
4412:
4413: logMessage.append("** Performance Metrics Report **\n");
4414: logMessage.append("\nLongest reported query: "
4415: + this .longestQueryTimeMs + " ms");
4416: logMessage.append("\nShortest reported query: "
4417: + this .shortestQueryTimeMs + " ms");
4418: logMessage
4419: .append("\nAverage query execution time: "
4420: + (this .totalQueryTimeMs / this .numberOfQueriesIssued)
4421: + " ms");
4422: logMessage.append("\nNumber of statements executed: "
4423: + this .numberOfQueriesIssued);
4424: logMessage.append("\nNumber of result sets created: "
4425: + this .numberOfResultSetsCreated);
4426: logMessage.append("\nNumber of statements prepared: "
4427: + this .numberOfPrepares);
4428: logMessage
4429: .append("\nNumber of prepared statement executions: "
4430: + this .numberOfPreparedExecutes);
4431:
4432: if (this .perfMetricsHistBreakpoints != null) {
4433: logMessage.append("\n\n\tTiming Histogram:\n");
4434: int maxNumPoints = 20;
4435: int highestCount = Integer.MIN_VALUE;
4436:
4437: for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) {
4438: if (this .perfMetricsHistCounts[i] > highestCount) {
4439: highestCount = this .perfMetricsHistCounts[i];
4440: }
4441: }
4442:
4443: if (highestCount == 0) {
4444: highestCount = 1; // avoid DIV/0
4445: }
4446:
4447: for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) {
4448:
4449: if (i == 0) {
4450: logMessage
4451: .append("\n\tless than "
4452: + this .perfMetricsHistBreakpoints[i + 1]
4453: + " ms: \t"
4454: + this .perfMetricsHistCounts[i]);
4455: } else {
4456: logMessage
4457: .append("\n\tbetween "
4458: + this .perfMetricsHistBreakpoints[i]
4459: + " and "
4460: + this .perfMetricsHistBreakpoints[i + 1]
4461: + " ms: \t"
4462: + this .perfMetricsHistCounts[i]);
4463: }
4464:
4465: logMessage.append("\t");
4466:
4467: int numPointsToGraph = (int) (maxNumPoints * ((double) this .perfMetricsHistCounts[i] / (double) highestCount));
4468:
4469: for (int j = 0; j < numPointsToGraph; j++) {
4470: logMessage.append("*");
4471: }
4472:
4473: if (this .longestQueryTimeMs < this .perfMetricsHistCounts[i + 1]) {
4474: break;
4475: }
4476: }
4477:
4478: if (this .perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this .longestQueryTimeMs) {
4479: logMessage.append("\n\tbetween ");
4480: logMessage
4481: .append(this .perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]);
4482: logMessage.append(" and ");
4483: logMessage
4484: .append(this .perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]);
4485: logMessage.append(" ms: \t");
4486: logMessage
4487: .append(this .perfMetricsHistCounts[HISTOGRAM_BUCKETS - 1]);
4488: }
4489: }
4490:
4491: if (this .numTablesMetricsHistBreakpoints != null) {
4492: logMessage.append("\n\n\tTable Join Histogram:\n");
4493: int maxNumPoints = 20;
4494: int highestCount = Integer.MIN_VALUE;
4495:
4496: for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) {
4497: if (this .numTablesMetricsHistCounts[i] > highestCount) {
4498: highestCount = this .numTablesMetricsHistCounts[i];
4499: }
4500: }
4501:
4502: if (highestCount == 0) {
4503: highestCount = 1; // avoid DIV/0
4504: }
4505:
4506: for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) {
4507:
4508: if (i == 0) {
4509: logMessage
4510: .append("\n\t"
4511: + this .numTablesMetricsHistBreakpoints[i + 1]
4512: + " tables or less: \t\t"
4513: + this .numTablesMetricsHistCounts[i]);
4514: } else {
4515: logMessage
4516: .append("\n\tbetween "
4517: + this .numTablesMetricsHistBreakpoints[i]
4518: + " and "
4519: + this .numTablesMetricsHistBreakpoints[i + 1]
4520: + " tables: \t"
4521: + this .numTablesMetricsHistCounts[i]);
4522: }
4523:
4524: logMessage.append("\t");
4525:
4526: int numPointsToGraph = (int) (maxNumPoints * ((double) this .numTablesMetricsHistCounts[i] / (double) highestCount));
4527:
4528: for (int j = 0; j < numPointsToGraph; j++) {
4529: logMessage.append("*");
4530: }
4531:
4532: if (this .maximumNumberTablesAccessed < this .numTablesMetricsHistBreakpoints[i + 1]) {
4533: break;
4534: }
4535: }
4536:
4537: if (this .numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this .maximumNumberTablesAccessed) {
4538: logMessage.append("\n\tbetween ");
4539: logMessage
4540: .append(this .numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]);
4541: logMessage.append(" and ");
4542: logMessage
4543: .append(this .numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]);
4544: logMessage.append(" tables: ");
4545: logMessage
4546: .append(this .numTablesMetricsHistCounts[HISTOGRAM_BUCKETS - 1]);
4547: }
4548: }
4549:
4550: this .log.logInfo(logMessage);
4551:
4552: this .metricsLastReportedMs = System.currentTimeMillis();
4553: }
4554: }
4555:
4556: /**
4557: * Reports currently collected metrics if this feature is enabled and the
4558: * timeout has passed.
4559: */
4560: private void reportMetricsIfNeeded() {
4561: if (getGatherPerformanceMetrics()) {
4562: if ((System.currentTimeMillis() - this .metricsLastReportedMs) > getReportMetricsIntervalMillis()) {
4563: reportMetrics();
4564: }
4565: }
4566: }
4567:
4568: protected void reportNumberOfTablesAccessed(int numTablesAccessed) {
4569: if (numTablesAccessed < this .minimumNumberTablesAccessed) {
4570: this .minimumNumberTablesAccessed = numTablesAccessed;
4571: }
4572:
4573: if (numTablesAccessed > this .maximumNumberTablesAccessed) {
4574: this .maximumNumberTablesAccessed = numTablesAccessed;
4575:
4576: repartitionTablesAccessedHistogram();
4577: }
4578:
4579: addToTablesAccessedHistogram(numTablesAccessed, 1);
4580: }
4581:
4582: /**
4583: * Resets the server-side state of this connection. Doesn't work for MySQL
4584: * versions older than 4.0.6 or if isParanoid() is set (it will become a
4585: * no-op in these cases). Usually only used from connection pooling code.
4586: *
4587: * @throws SQLException
4588: * if the operation fails while resetting server state.
4589: */
4590: public void resetServerState() throws SQLException {
4591: if (!getParanoid()
4592: && ((this .io != null) && versionMeetsMinimum(4, 0, 6))) {
4593: changeUser(this .user, this .password);
4594: }
4595: }
4596:
4597: /**
4598: * The method rollback() drops all changes made since the previous
4599: * commit/rollback and releases any database locks currently held by the
4600: * Connection.
4601: *
4602: * @exception SQLException
4603: * if a database access error occurs
4604: * @see commit
4605: */
4606: public void rollback() throws SQLException {
4607: synchronized (getMutex()) {
4608: checkClosed();
4609:
4610: try {
4611: // no-op if _relaxAutoCommit == true
4612: if (this .autoCommit && !getRelaxAutoCommit()) {
4613: throw SQLError.createSQLException(
4614: "Can't call rollback when autocommit=true",
4615: SQLError.SQL_STATE_CONNECTION_NOT_OPEN);
4616: } else if (this .transactionsSupported) {
4617: try {
4618: rollbackNoChecks();
4619: } catch (SQLException sqlEx) {
4620: // We ignore non-transactional tables if told to do so
4621: if (getIgnoreNonTxTables()
4622: && (sqlEx.getErrorCode() != SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
4623: throw sqlEx;
4624: }
4625: }
4626: }
4627: } catch (SQLException sqlException) {
4628: if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
4629: .equals(sqlException.getSQLState())) {
4630: throw SQLError
4631: .createSQLException(
4632: "Communications link failure during rollback(). Transaction resolution unknown.",
4633: SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN);
4634: }
4635:
4636: throw sqlException;
4637: } finally {
4638: this .needsPing = this .getReconnectAtTxEnd();
4639: }
4640: }
4641: }
4642:
4643: /**
4644: * @see Connection#rollback(Savepoint)
4645: */
4646: public void rollback(Savepoint savepoint) throws SQLException {
4647:
4648: if (versionMeetsMinimum(4, 0, 14)
4649: || versionMeetsMinimum(4, 1, 1)) {
4650: synchronized (getMutex()) {
4651: checkClosed();
4652:
4653: try {
4654: StringBuffer rollbackQuery = new StringBuffer(
4655: "ROLLBACK TO SAVEPOINT ");
4656: rollbackQuery.append('`');
4657: rollbackQuery.append(savepoint.getSavepointName());
4658: rollbackQuery.append('`');
4659:
4660: java.sql.Statement stmt = null;
4661:
4662: try {
4663: stmt = createStatement();
4664:
4665: stmt.executeUpdate(rollbackQuery.toString());
4666: } catch (SQLException sqlEx) {
4667: int errno = sqlEx.getErrorCode();
4668:
4669: if (errno == 1181) {
4670: String msg = sqlEx.getMessage();
4671:
4672: if (msg != null) {
4673: int indexOfError153 = msg
4674: .indexOf("153");
4675:
4676: if (indexOfError153 != -1) {
4677: throw SQLError
4678: .createSQLException(
4679: "Savepoint '"
4680: + savepoint
4681: .getSavepointName()
4682: + "' does not exist",
4683: SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
4684: errno);
4685: }
4686: }
4687: }
4688:
4689: // We ignore non-transactional tables if told to do so
4690: if (getIgnoreNonTxTables()
4691: && (sqlEx.getErrorCode() != SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
4692: throw sqlEx;
4693: }
4694:
4695: if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
4696: .equals(sqlEx.getSQLState())) {
4697: throw SQLError
4698: .createSQLException(
4699: "Communications link failure during rollback(). Transaction resolution unknown.",
4700: SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN);
4701: }
4702:
4703: throw sqlEx;
4704: } finally {
4705: closeStatement(stmt);
4706: }
4707: } finally {
4708: this .needsPing = this .getReconnectAtTxEnd();
4709: }
4710: }
4711: } else {
4712: throw new NotImplemented();
4713: }
4714: }
4715:
4716: private void rollbackNoChecks() throws SQLException {
4717: if (getUseLocalSessionState() && versionMeetsMinimum(5, 0, 0)) {
4718: if (!this .io.inTransactionOnServer()) {
4719: return; // effectively a no-op
4720: }
4721: }
4722:
4723: execSQL(null, "rollback", -1, null,
4724: java.sql.ResultSet.TYPE_FORWARD_ONLY,
4725: java.sql.ResultSet.CONCUR_READ_ONLY, false,
4726: this .database, null, false);
4727: }
4728:
4729: /**
4730: * @see java.sql.Connection#prepareStatement(String)
4731: */
4732: public ServerPreparedStatement serverPrepareStatement(String sql)
4733: throws SQLException {
4734:
4735: String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql)
4736: : sql;
4737:
4738: return ServerPreparedStatement.getInstance(this , nativeSql,
4739: this .getCatalog(),
4740: java.sql.ResultSet.TYPE_SCROLL_SENSITIVE,
4741: java.sql.ResultSet.CONCUR_READ_ONLY);
4742: }
4743:
4744: /**
4745: * @see java.sql.Connection#prepareStatement(String, int)
4746: */
4747: public java.sql.PreparedStatement serverPrepareStatement(
4748: String sql, int autoGenKeyIndex) throws SQLException {
4749: String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql)
4750: : sql;
4751:
4752: PreparedStatement pStmt = ServerPreparedStatement.getInstance(
4753: this , nativeSql, this .getCatalog(),
4754: java.sql.ResultSet.TYPE_SCROLL_SENSITIVE,
4755: java.sql.ResultSet.CONCUR_READ_ONLY);
4756:
4757: pStmt
4758: .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS);
4759:
4760: return pStmt;
4761: }
4762:
4763: /**
4764: * @see java.sql.Connection#prepareStatement(String, int, int)
4765: */
4766: public java.sql.PreparedStatement serverPrepareStatement(
4767: String sql, int resultSetType, int resultSetConcurrency)
4768: throws SQLException {
4769: String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql)
4770: : sql;
4771:
4772: return ServerPreparedStatement.getInstance(this , nativeSql,
4773: this .getCatalog(), resultSetType, resultSetConcurrency);
4774: }
4775:
4776: /**
4777: * @see java.sql.Connection#prepareStatement(String, int, int, int)
4778: */
4779: public java.sql.PreparedStatement serverPrepareStatement(
4780: String sql, int resultSetType, int resultSetConcurrency,
4781: int resultSetHoldability) throws SQLException {
4782: if (getPedantic()) {
4783: if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) {
4784: throw SQLError
4785: .createSQLException(
4786: "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level",
4787: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
4788: }
4789: }
4790:
4791: return serverPrepareStatement(sql, resultSetType,
4792: resultSetConcurrency);
4793: }
4794:
4795: /**
4796: * @see java.sql.Connection#prepareStatement(String, int[])
4797: */
4798: public java.sql.PreparedStatement serverPrepareStatement(
4799: String sql, int[] autoGenKeyIndexes) throws SQLException {
4800:
4801: PreparedStatement pStmt = serverPrepareStatement(sql);
4802:
4803: pStmt.setRetrieveGeneratedKeys((autoGenKeyIndexes != null)
4804: && (autoGenKeyIndexes.length > 0));
4805:
4806: return pStmt;
4807: }
4808:
4809: /**
4810: * @see java.sql.Connection#prepareStatement(String, String[])
4811: */
4812: public java.sql.PreparedStatement serverPrepareStatement(
4813: String sql, String[] autoGenKeyColNames)
4814: throws SQLException {
4815: PreparedStatement pStmt = serverPrepareStatement(sql);
4816:
4817: pStmt.setRetrieveGeneratedKeys((autoGenKeyColNames != null)
4818: && (autoGenKeyColNames.length > 0));
4819:
4820: return pStmt;
4821: }
4822:
4823: protected boolean serverSupportsConvertFn() throws SQLException {
4824: return versionMeetsMinimum(4, 0, 2);
4825: }
4826:
4827: /**
4828: * If a connection is in auto-commit mode, than all its SQL statements will
4829: * be executed and committed as individual transactions. Otherwise, its SQL
4830: * statements are grouped into transactions that are terminated by either
4831: * commit() or rollback(). By default, new connections are in auto- commit
4832: * mode. The commit occurs when the statement completes or the next execute
4833: * occurs, whichever comes first. In the case of statements returning a
4834: * ResultSet, the statement completes when the last row of the ResultSet has
4835: * been retrieved or the ResultSet has been closed. In advanced cases, a
4836: * single statement may return multiple results as well as output parameter
4837: * values. Here the commit occurs when all results and output param values
4838: * have been retrieved.
4839: * <p>
4840: * <b>Note:</b> MySQL does not support transactions, so this method is a
4841: * no-op.
4842: * </p>
4843: *
4844: * @param autoCommitFlag -
4845: * true enables auto-commit; false disables it
4846: * @exception SQLException
4847: * if a database access error occurs
4848: */
4849: public void setAutoCommit(boolean autoCommitFlag)
4850: throws SQLException {
4851: synchronized (getMutex()) {
4852: checkClosed();
4853:
4854: if (getAutoReconnectForPools()) {
4855: setHighAvailability(true);
4856: }
4857:
4858: try {
4859: if (this .transactionsSupported) {
4860:
4861: boolean needsSetOnServer = true;
4862:
4863: if (this .getUseLocalSessionState()
4864: && this .autoCommit == autoCommitFlag) {
4865: needsSetOnServer = false;
4866: } else if (!this .getHighAvailability()) {
4867: needsSetOnServer = this .getIO()
4868: .isSetNeededForAutoCommitMode(
4869: autoCommitFlag);
4870: }
4871:
4872: // this internal value must be set first as failover depends on
4873: // it
4874: // being set to true to fail over (which is done by most
4875: // app servers and connection pools at the end of
4876: // a transaction), and the driver issues an implicit set
4877: // based on this value when it (re)-connects to a server
4878: // so the value holds across connections
4879: this .autoCommit = autoCommitFlag;
4880:
4881: if (needsSetOnServer) {
4882: execSQL(null,
4883: autoCommitFlag ? "SET autocommit=1"
4884: : "SET autocommit=0", -1, null,
4885: java.sql.ResultSet.TYPE_FORWARD_ONLY,
4886: java.sql.ResultSet.CONCUR_READ_ONLY,
4887: false, this .database, null, false);
4888: }
4889:
4890: } else {
4891: if ((autoCommitFlag == false)
4892: && !getRelaxAutoCommit()) {
4893: throw SQLError
4894: .createSQLException(
4895: "MySQL Versions Older than 3.23.15 "
4896: + "do not support transactions",
4897: SQLError.SQL_STATE_CONNECTION_NOT_OPEN);
4898: }
4899:
4900: this .autoCommit = autoCommitFlag;
4901: }
4902: } finally {
4903: if (this .getAutoReconnectForPools()) {
4904: setHighAvailability(false);
4905: }
4906: }
4907:
4908: return;
4909: }
4910: }
4911:
4912: /**
4913: * A sub-space of this Connection's database may be selected by setting a
4914: * catalog name. If the driver does not support catalogs, it will silently
4915: * ignore this request
4916: * <p>
4917: * <b>Note:</b> MySQL's notion of catalogs are individual databases.
4918: * </p>
4919: *
4920: * @param catalog
4921: * the database for this connection to use
4922: * @throws SQLException
4923: * if a database access error occurs
4924: */
4925: public void setCatalog(String catalog) throws SQLException {
4926: synchronized (getMutex()) {
4927: checkClosed();
4928:
4929: if (catalog == null) {
4930: throw SQLError.createSQLException(
4931: "Catalog can not be null",
4932: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
4933: }
4934:
4935: if (getUseLocalSessionState()) {
4936: if (this .lowerCaseTableNames) {
4937: if (this .database.equalsIgnoreCase(catalog)) {
4938: return;
4939: }
4940: } else {
4941: if (this .database.equals(catalog)) {
4942: return;
4943: }
4944: }
4945: }
4946:
4947: String quotedId = this .dbmd.getIdentifierQuoteString();
4948:
4949: if ((quotedId == null) || quotedId.equals(" ")) {
4950: quotedId = "";
4951: }
4952:
4953: StringBuffer query = new StringBuffer("USE ");
4954: query.append(quotedId);
4955: query.append(catalog);
4956: query.append(quotedId);
4957:
4958: execSQL(null, query.toString(), -1, null,
4959: java.sql.ResultSet.TYPE_FORWARD_ONLY,
4960: java.sql.ResultSet.CONCUR_READ_ONLY, false,
4961: this .database, null, false);
4962:
4963: this .database = catalog;
4964: }
4965: }
4966:
4967: /**
4968: * @param failedOver
4969: * The failedOver to set.
4970: */
4971: public synchronized void setFailedOver(boolean flag) {
4972: this .failedOver = flag;
4973: }
4974:
4975: /**
4976: * Sets state for a failed-over connection
4977: *
4978: * @throws SQLException
4979: * DOCUMENT ME!
4980: */
4981: private void setFailedOverState() throws SQLException {
4982: if (getFailOverReadOnly()) {
4983: setReadOnlyInternal(true);
4984: }
4985:
4986: this .queriesIssuedFailedOver = 0;
4987: this .failedOver = true;
4988: this .masterFailTimeMillis = System.currentTimeMillis();
4989: }
4990:
4991: /**
4992: * @see Connection#setHoldability(int)
4993: */
4994: public void setHoldability(int arg0) throws SQLException {
4995: // do nothing
4996: }
4997:
4998: public void setInGlobalTx(boolean flag) {
4999: this .isInGlobalTx = flag;
5000: }
5001:
5002: // exposed for testing
5003: /**
5004: * @param preferSlaveDuringFailover
5005: * The preferSlaveDuringFailover to set.
5006: */
5007: public void setPreferSlaveDuringFailover(boolean flag) {
5008: this .preferSlaveDuringFailover = flag;
5009: }
5010:
5011: void setReadInfoMsgEnabled(boolean flag) {
5012: this .readInfoMsg = flag;
5013: }
5014:
5015: /**
5016: * You can put a connection in read-only mode as a hint to enable database
5017: * optimizations <B>Note:</B> setReadOnly cannot be called while in the
5018: * middle of a transaction
5019: *
5020: * @param readOnlyFlag -
5021: * true enables read-only mode; false disables it
5022: * @exception SQLException
5023: * if a database access error occurs
5024: */
5025: public void setReadOnly(boolean readOnlyFlag) throws SQLException {
5026: checkClosed();
5027:
5028: // Ignore calls to this method if we're failed over and
5029: // we're configured to fail over read-only.
5030: if (this .failedOver && getFailOverReadOnly() && !readOnlyFlag) {
5031: return;
5032: }
5033:
5034: setReadOnlyInternal(readOnlyFlag);
5035: }
5036:
5037: protected void setReadOnlyInternal(boolean readOnlyFlag)
5038: throws SQLException {
5039: this .readOnly = readOnlyFlag;
5040: }
5041:
5042: /**
5043: * @see Connection#setSavepoint()
5044: */
5045: public java.sql.Savepoint setSavepoint() throws SQLException {
5046: MysqlSavepoint savepoint = new MysqlSavepoint();
5047:
5048: setSavepoint(savepoint);
5049:
5050: return savepoint;
5051: }
5052:
5053: private void setSavepoint(MysqlSavepoint savepoint)
5054: throws SQLException {
5055:
5056: if (versionMeetsMinimum(4, 0, 14)
5057: || versionMeetsMinimum(4, 1, 1)) {
5058: synchronized (getMutex()) {
5059: checkClosed();
5060:
5061: StringBuffer savePointQuery = new StringBuffer(
5062: "SAVEPOINT ");
5063: savePointQuery.append('`');
5064: savePointQuery.append(savepoint.getSavepointName());
5065: savePointQuery.append('`');
5066:
5067: java.sql.Statement stmt = null;
5068:
5069: try {
5070: stmt = createStatement();
5071:
5072: stmt.executeUpdate(savePointQuery.toString());
5073: } finally {
5074: closeStatement(stmt);
5075: }
5076: }
5077: } else {
5078: throw new NotImplemented();
5079: }
5080: }
5081:
5082: /**
5083: * @see Connection#setSavepoint(String)
5084: */
5085: public synchronized java.sql.Savepoint setSavepoint(String name)
5086: throws SQLException {
5087: MysqlSavepoint savepoint = new MysqlSavepoint(name);
5088:
5089: setSavepoint(savepoint);
5090:
5091: return savepoint;
5092: }
5093:
5094: /**
5095: *
5096: */
5097: private void setSessionVariables() throws SQLException {
5098: if (this .versionMeetsMinimum(4, 0, 0)
5099: && getSessionVariables() != null) {
5100: List variablesToSet = StringUtils.split(
5101: getSessionVariables(), ",", "\"'", "\"'", false);
5102:
5103: int numVariablesToSet = variablesToSet.size();
5104:
5105: java.sql.Statement stmt = null;
5106:
5107: try {
5108: stmt = getMetadataSafeStatement();
5109:
5110: for (int i = 0; i < numVariablesToSet; i++) {
5111: String variableValuePair = (String) variablesToSet
5112: .get(i);
5113:
5114: if (variableValuePair.startsWith("@")) {
5115: stmt.executeUpdate("SET " + variableValuePair);
5116: } else {
5117: stmt.executeUpdate("SET SESSION "
5118: + variableValuePair);
5119: }
5120: }
5121: } finally {
5122: if (stmt != null) {
5123: stmt.close();
5124: }
5125: }
5126: }
5127:
5128: }
5129:
5130: /**
5131: * DOCUMENT ME!
5132: *
5133: * @param level
5134: * DOCUMENT ME!
5135: * @throws SQLException
5136: * DOCUMENT ME!
5137: */
5138: public synchronized void setTransactionIsolation(int level)
5139: throws SQLException {
5140: checkClosed();
5141:
5142: if (this .hasIsolationLevels) {
5143: String sql = null;
5144:
5145: boolean shouldSendSet = false;
5146:
5147: if (getAlwaysSendSetIsolation()) {
5148: shouldSendSet = true;
5149: } else {
5150: if (level != this .isolationLevel) {
5151: shouldSendSet = true;
5152: }
5153: }
5154:
5155: if (getUseLocalSessionState()) {
5156: shouldSendSet = this .isolationLevel != level;
5157: }
5158:
5159: if (shouldSendSet) {
5160: switch (level) {
5161: case java.sql.Connection.TRANSACTION_NONE:
5162: throw SQLError
5163: .createSQLException("Transaction isolation level "
5164: + "NONE not supported by MySQL");
5165:
5166: case java.sql.Connection.TRANSACTION_READ_COMMITTED:
5167: sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED";
5168:
5169: break;
5170:
5171: case java.sql.Connection.TRANSACTION_READ_UNCOMMITTED:
5172: sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
5173:
5174: break;
5175:
5176: case java.sql.Connection.TRANSACTION_REPEATABLE_READ:
5177: sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ";
5178:
5179: break;
5180:
5181: case java.sql.Connection.TRANSACTION_SERIALIZABLE:
5182: sql = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE";
5183:
5184: break;
5185:
5186: default:
5187: throw SQLError
5188: .createSQLException(
5189: "Unsupported transaction "
5190: + "isolation level '"
5191: + level + "'",
5192: SQLError.SQL_STATE_DRIVER_NOT_CAPABLE);
5193: }
5194:
5195: execSQL(null, sql, -1, null,
5196: java.sql.ResultSet.TYPE_FORWARD_ONLY,
5197: java.sql.ResultSet.CONCUR_READ_ONLY, false,
5198: this .database, null, false);
5199:
5200: this .isolationLevel = level;
5201: }
5202: } else {
5203: throw SQLError
5204: .createSQLException(
5205: "Transaction Isolation Levels are "
5206: + "not supported on MySQL versions older than 3.23.36.",
5207: SQLError.SQL_STATE_DRIVER_NOT_CAPABLE);
5208: }
5209: }
5210:
5211: /**
5212: * JDBC 2.0 Install a type-map object as the default type-map for this
5213: * connection
5214: *
5215: * @param map
5216: * the type mapping
5217: * @throws SQLException
5218: * if a database error occurs.
5219: */
5220: public synchronized void setTypeMap(java.util.Map map)
5221: throws SQLException {
5222: this .typeMap = map;
5223: }
5224:
5225: private void setupServerForTruncationChecks() throws SQLException {
5226: if (getJdbcCompliantTruncation()) {
5227: if (versionMeetsMinimum(5, 0, 2)) {
5228: String currentSqlMode = (String) this .serverVariables
5229: .get("sql_mode");
5230:
5231: boolean strictTransTablesIsSet = StringUtils
5232: .indexOfIgnoreCase(currentSqlMode,
5233: "STRICT_TRANS_TABLES") != -1;
5234:
5235: if (currentSqlMode == null
5236: || currentSqlMode.length() == 0
5237: || !strictTransTablesIsSet) {
5238: StringBuffer commandBuf = new StringBuffer(
5239: "SET sql_mode='");
5240:
5241: if (currentSqlMode != null
5242: && currentSqlMode.length() > 0) {
5243: commandBuf.append(currentSqlMode);
5244: commandBuf.append(",");
5245: }
5246:
5247: commandBuf.append("STRICT_TRANS_TABLES'");
5248:
5249: execSQL(null, commandBuf.toString(), -1, null,
5250: java.sql.ResultSet.TYPE_FORWARD_ONLY,
5251: java.sql.ResultSet.CONCUR_READ_ONLY, false,
5252: this .database, null, false);
5253:
5254: setJdbcCompliantTruncation(false); // server's handling this for us now
5255: } else if (strictTransTablesIsSet) {
5256: // We didn't set it, but someone did, so we piggy back on it
5257: setJdbcCompliantTruncation(false); // server's handling this for us now
5258: }
5259:
5260: }
5261: }
5262: }
5263:
5264: /**
5265: * Should we try to connect back to the master? We try when we've been
5266: * failed over >= this.secondsBeforeRetryMaster _or_ we've issued >
5267: * this.queriesIssuedFailedOver
5268: *
5269: * @return DOCUMENT ME!
5270: */
5271: private boolean shouldFallBack() {
5272: long secondsSinceFailedOver = (System.currentTimeMillis() - this .masterFailTimeMillis) / 1000;
5273:
5274: // Done this way so we can set a condition in the debugger
5275: boolean tryFallback = ((secondsSinceFailedOver >= getSecondsBeforeRetryMaster()) || (this .queriesIssuedFailedOver >= getQueriesBeforeRetryMaster()));
5276:
5277: return tryFallback;
5278: }
5279:
5280: /**
5281: * Used by MiniAdmin to shutdown a MySQL server
5282: *
5283: * @throws SQLException
5284: * if the command can not be issued.
5285: */
5286: public void shutdownServer() throws SQLException {
5287: try {
5288: this .io.sendCommand(MysqlDefs.SHUTDOWN, null, null, false,
5289: null);
5290: } catch (Exception ex) {
5291: throw SQLError.createSQLException("Unhandled exception '"
5292: + ex.toString() + "'",
5293: SQLError.SQL_STATE_GENERAL_ERROR);
5294: }
5295: }
5296:
5297: /**
5298: * DOCUMENT ME!
5299: *
5300: * @return DOCUMENT ME!
5301: */
5302: public boolean supportsIsolationLevel() {
5303: return this .hasIsolationLevels;
5304: }
5305:
5306: /**
5307: * DOCUMENT ME!
5308: *
5309: * @return DOCUMENT ME!
5310: */
5311: public boolean supportsQuotedIdentifiers() {
5312: return this .hasQuotedIdentifiers;
5313: }
5314:
5315: /**
5316: * DOCUMENT ME!
5317: *
5318: * @return DOCUMENT ME!
5319: */
5320: public boolean supportsTransactions() {
5321: return this .transactionsSupported;
5322: }
5323:
5324: /**
5325: * Remove the given statement from the list of open statements
5326: *
5327: * @param stmt
5328: * the Statement instance to remove
5329: */
5330: void unregisterStatement(StatementImpl stmt) {
5331: if (this .openStatements != null) {
5332: synchronized (this .openStatements) {
5333: this .openStatements.remove(stmt);
5334: }
5335: }
5336: }
5337:
5338: /**
5339: * Called by statements on their .close() to let the connection know when it
5340: * is safe to set the connection back to 'default' row limits.
5341: *
5342: * @param stmt
5343: * the statement releasing it's max-rows requirement
5344: * @throws SQLException
5345: * if a database error occurs issuing the statement that sets
5346: * the limit default.
5347: */
5348: void unsetMaxRows(StatementImpl stmt) throws SQLException {
5349: synchronized (this .mutex) {
5350: if (this .statementsUsingMaxRows != null) {
5351: Object found = this .statementsUsingMaxRows.remove(stmt);
5352:
5353: if ((found != null)
5354: && (this .statementsUsingMaxRows.size() == 0)) {
5355: execSQL(null,
5356: "SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1,
5357: null, java.sql.ResultSet.TYPE_FORWARD_ONLY,
5358: java.sql.ResultSet.CONCUR_READ_ONLY, false,
5359: this .database, null, false);
5360:
5361: this .maxRowsChanged = false;
5362: }
5363: }
5364: }
5365: }
5366:
5367: boolean useAnsiQuotedIdentifiers() {
5368: return this .useAnsiQuotes;
5369: }
5370:
5371: /**
5372: * Has maxRows() been set?
5373: *
5374: * @return DOCUMENT ME!
5375: */
5376: boolean useMaxRows() {
5377: synchronized (this .mutex) {
5378: return this .maxRowsChanged;
5379: }
5380: }
5381:
5382: public boolean versionMeetsMinimum(int major, int minor,
5383: int subminor) throws SQLException {
5384: checkClosed();
5385:
5386: return this .io.versionMeetsMinimum(major, minor, subminor);
5387: }
5388:
5389: /**
5390: * Returns cached metadata (or null if not cached) for the given query,
5391: * which must match _exactly_.
5392: *
5393: * This method is synchronized by the caller on getMutex(), so if
5394: * calling this method from internal code in the driver, make sure it's
5395: * synchronized on the mutex that guards communication with the server.
5396: *
5397: * @param sql
5398: * the query that is the key to the cache
5399: *
5400: * @return metadata cached for the given SQL, or none if it doesn't
5401: * exist.
5402: */
5403: protected CachedResultSetMetaData getCachedMetaData(String sql) {
5404: if (this .resultSetMetadataCache != null) {
5405: synchronized (this .resultSetMetadataCache) {
5406: return (CachedResultSetMetaData) this .resultSetMetadataCache
5407: .get(sql);
5408: }
5409: }
5410:
5411: return null; // no cache exists
5412: }
5413:
5414: /**
5415: * Caches CachedResultSetMetaData that has been placed in the cache using
5416: * the given SQL as a key.
5417: *
5418: * This method is synchronized by the caller on getMutex(), so if
5419: * calling this method from internal code in the driver, make sure it's
5420: * synchronized on the mutex that guards communication with the server.
5421: *
5422: * @param sql the query that the metadata pertains too.
5423: * @param cachedMetaData metadata (if it exists) to populate the cache.
5424: * @param resultSet the result set to retreive metadata from, or apply to.
5425: *
5426: * @throws SQLException
5427: */
5428: protected void initializeResultsMetadataFromCache(String sql,
5429: CachedResultSetMetaData cachedMetaData,
5430: ResultSetInternalMethods resultSet) throws SQLException {
5431:
5432: if (cachedMetaData == null) {
5433:
5434: // read from results
5435: cachedMetaData = new CachedResultSetMetaData();
5436:
5437: // assume that users will use named-based
5438: // lookups
5439: resultSet.buildIndexMapping();
5440: resultSet.initializeWithMetadata();
5441:
5442: if (resultSet instanceof UpdatableResultSet) {
5443: ((UpdatableResultSet) resultSet).checkUpdatability();
5444: }
5445:
5446: resultSet.populateCachedMetaData(cachedMetaData);
5447:
5448: this .resultSetMetadataCache.put(sql, cachedMetaData);
5449: } else {
5450: resultSet.initializeFromCachedMetaData(cachedMetaData);
5451: resultSet.initializeWithMetadata();
5452:
5453: if (resultSet instanceof UpdatableResultSet) {
5454: ((UpdatableResultSet) resultSet).checkUpdatability();
5455: }
5456: }
5457: }
5458:
5459: /**
5460: * Returns the comment that will be prepended to all statements
5461: * sent to the server.
5462: *
5463: * @return the comment that will be prepended to all statements
5464: * sent to the server.
5465: */
5466: public String getStatementComment() {
5467: return this .statementComment;
5468: }
5469:
5470: /**
5471: * Sets the comment that will be prepended to all statements
5472: * sent to the server. Do not use slash-star or star-slash tokens
5473: * in the comment as these will be added by the driver itself.
5474: *
5475: * @param comment the comment that will be prepended to all statements
5476: * sent to the server.
5477: */
5478: public void setStatementComment(String comment) {
5479: this.statementComment = comment;
5480: }
5481: }
|