/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception <http://www.mysql.com/about/legal/licensing/foss-exception.html>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.jdbc; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.sql.Blob; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLPermission; import java.sql.SQLWarning; import java.sql.Savepoint; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Stack; import java.util.TimeZone; import java.util.Timer; import java.util.TreeMap; import java.util.concurrent.Executor; import com.mysql.jdbc.PreparedStatement.ParseInfo; import com.mysql.jdbc.log.Log; import com.mysql.jdbc.log.LogFactory; import com.mysql.jdbc.log.LogUtils; import com.mysql.jdbc.log.NullLogger; import com.mysql.jdbc.profiler.ProfilerEvent; import com.mysql.jdbc.profiler.ProfilerEventHandler; import com.mysql.jdbc.util.LRUCache; /** * A Connection represents a session with a specific database. Within the * context of a Connection, SQL statements are executed and results are * returned. * <P> * A Connection's database is able to provide information describing its tables, * its supported SQL grammar, its stored procedures, the capabilities of this * connection, etc. This information is obtained with the getMetaData method. * </p> * * @author Mark Matthews * @version $Id$ * @see java.sql.Connection */ public class ConnectionImpl extends ConnectionPropertiesImpl implements MySQLConnection { private static final long serialVersionUID = 2877471301981509474L; private static final SQLPermission SET_NETWORK_TIMEOUT_PERM = new SQLPermission("setNetworkTimeout"); private static final SQLPermission ABORT_PERM = new SQLPermission("abort"); private static final String JDBC_LOCAL_CHARACTER_SET_RESULTS = "jdbc.local.character_set_results"; public String getHost() { return host; } private MySQLConnection proxy = null; private InvocationHandler realProxy = null; public boolean isProxySet(){ return this.proxy != null; } public void setProxy(MySQLConnection proxy) { this.proxy = proxy; } public void setRealProxy(InvocationHandler proxy) { this.realProxy = proxy; } // We have to proxy ourselves when we're load balanced so that // statements get routed to the right physical connection // (when load balanced, we're a "logical" connection) private MySQLConnection getProxy() { return (proxy != null) ? proxy : (MySQLConnection) this; } public MySQLConnection getLoadBalanceSafeProxy() { return this.getProxy(); } public Object getConnectionMutex() { return (this.realProxy != null) ? this.realProxy : this; } class ExceptionInterceptorChain implements ExceptionInterceptor { List<Extension> interceptors; ExceptionInterceptorChain(String interceptorClasses) throws SQLException { interceptors = Util.loadExtensions(ConnectionImpl.this, props, interceptorClasses, "Connection.BadExceptionInterceptor", this); } void addRingZero(ExceptionInterceptor interceptor) throws SQLException { interceptors.add(0, interceptor); } public SQLException interceptException(SQLException sqlEx, Connection conn) { if (interceptors != null) { Iterator<Extension> iter = interceptors.iterator(); while (iter.hasNext()) { sqlEx = ((ExceptionInterceptor)iter.next()).interceptException(sqlEx, ConnectionImpl.this); } } return sqlEx; } public void destroy() { if (interceptors != null) { Iterator<Extension> iter = interceptors.iterator(); while (iter.hasNext()) { ((ExceptionInterceptor)iter.next()).destroy(); } } } public void init(Connection conn, Properties properties) throws SQLException { if (interceptors != null) { Iterator<Extension> iter = interceptors.iterator(); while (iter.hasNext()) { ((ExceptionInterceptor)iter.next()).init(conn, properties); } } } } /** * Used as a key for caching callable statements which (may) depend on * current catalog...In 5.0.x, they don't (currently), but stored procedure * names soon will, so current catalog is a (hidden) component of the name. */ static class CompoundCacheKey { String componentOne; String componentTwo; int hashCode; CompoundCacheKey(String partOne, String partTwo) { this.componentOne = partOne; this.componentTwo = partTwo; // Handle first component (in most cases, currentCatalog) // being NULL.... this.hashCode = (((this.componentOne != null) ? this.componentOne : "") + this.componentTwo).hashCode(); } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { if (obj instanceof CompoundCacheKey) { CompoundCacheKey another = (CompoundCacheKey) obj; boolean firstPartEqual = false; if (this.componentOne == null) { firstPartEqual = (another.componentOne == null); } else { firstPartEqual = this.componentOne .equals(another.componentOne); } return (firstPartEqual && this.componentTwo .equals(another.componentTwo)); } return false; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ public int hashCode() { return this.hashCode; } } /** * Marker for character set converter not being available (not written, * multibyte, etc) Used to prevent multiple instantiation requests. */ private static final Object CHARSET_CONVERTER_NOT_AVAILABLE_MARKER = new Object(); /** * The mapping between MySQL charset names and Java charset names. * Initialized by loadCharacterSetMapping() */ public static Map<?, ?> charsetMap; /** Default logger class name */ protected static final String DEFAULT_LOGGER_CLASS = "com.mysql.jdbc.log.StandardLogger"; private final static int HISTOGRAM_BUCKETS = 20; /** Logger instance name */ private static final String LOGGER_INSTANCE_NAME = "MySQL"; /** * Map mysql transaction isolation level name to * java.sql.Connection.TRANSACTION_XXX */ private static Map<String, Integer> mapTransIsolationNameToValue = null; /** Null logger shared by all connections at startup */ private static final Log NULL_LOGGER = new NullLogger(LOGGER_INSTANCE_NAME); protected static Map<?, ?> roundRobinStatsMap; private static final Map<String, Map<Long, String>> serverCollationByUrl = new HashMap<String, Map<Long,String>>(); /** * Map for Java charsets of user defined charsets. We can't map them statically, because * they can be different for different server URLs. */ private static final Map<String, Map<Integer, String>> serverJavaCharsetByUrl = new HashMap<String, Map<Integer,String>>(); /** * Map for user defined charsets. We can't map them statically, because * they can be different for different server URLs. */ private static final Map<String, Map<Integer, String>> serverCustomCharsetByUrl = new HashMap<String, Map<Integer,String>>(); /** * Map for user defined charsets. We can't map them statically, because * they can be different for different server URLs. */ private static final Map<String, Map<String, Integer>> serverCustomMblenByUrl = new HashMap<String, Map<String, Integer>>(); private CacheAdapter<String, Map<String, String>> serverConfigCache; private long queryTimeCount; private double queryTimeSum; private double queryTimeSumSquares; private double queryTimeMean; private transient Timer cancelTimer; private List<Extension> connectionLifecycleInterceptors; private static final Constructor<?> JDBC_4_CONNECTION_CTOR; private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY; private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY; static { mapTransIsolationNameToValue = new HashMap<String, Integer>(8); mapTransIsolationNameToValue.put("READ-UNCOMMITED", TRANSACTION_READ_UNCOMMITTED); mapTransIsolationNameToValue.put("READ-UNCOMMITTED", TRANSACTION_READ_UNCOMMITTED); mapTransIsolationNameToValue.put("READ-COMMITTED", TRANSACTION_READ_COMMITTED); mapTransIsolationNameToValue.put("REPEATABLE-READ", TRANSACTION_REPEATABLE_READ); mapTransIsolationNameToValue.put("SERIALIZABLE", TRANSACTION_SERIALIZABLE); if (Util.isJdbc4()) { try { JDBC_4_CONNECTION_CTOR = Class.forName( "com.mysql.jdbc.JDBC4Connection").getConstructor( new Class[] { String.class, Integer.TYPE, Properties.class, String.class, String.class }); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } else { JDBC_4_CONNECTION_CTOR = null; } } protected static SQLException appendMessageToException(SQLException sqlEx, String messageToAppend, ExceptionInterceptor interceptor) { String origMessage = sqlEx.getMessage(); String sqlState = sqlEx.getSQLState(); int vendorErrorCode = sqlEx.getErrorCode(); StringBuffer messageBuf = new StringBuffer(origMessage.length() + messageToAppend.length()); messageBuf.append(origMessage); messageBuf.append(messageToAppend); SQLException sqlExceptionWithNewMessage = SQLError.createSQLException(messageBuf .toString(), sqlState, vendorErrorCode, interceptor); // // Try and maintain the original stack trace, // only works on JDK-1.4 and newer // try { // Have to do this with reflection, otherwise older JVMs croak Method getStackTraceMethod = null; Method setStackTraceMethod = null; Object theStackTraceAsObject = null; Class<?> stackTraceElementClass = Class.forName("java.lang.StackTraceElement"); Class<?> stackTraceElementArrayClass = Array.newInstance( stackTraceElementClass, new int[] { 0 }).getClass(); getStackTraceMethod = Throwable.class.getMethod("getStackTrace", new Class[] {}); setStackTraceMethod = Throwable.class.getMethod("setStackTrace", new Class[] { stackTraceElementArrayClass }); if (getStackTraceMethod != null && setStackTraceMethod != null) { theStackTraceAsObject = getStackTraceMethod.invoke(sqlEx, new Object[0]); setStackTraceMethod.invoke(sqlExceptionWithNewMessage, new Object[] { theStackTraceAsObject }); } } catch (NoClassDefFoundError noClassDefFound) { } catch (NoSuchMethodException noSuchMethodEx) { } catch (Throwable catchAll) { } return sqlExceptionWithNewMessage; } public Timer getCancelTimer() { synchronized (getConnectionMutex()) { if (cancelTimer == null) { boolean createdNamedTimer = false; // Use reflection magic to try this on JDK's 1.5 and newer, fallback to non-named // timer on older VMs. try { Constructor<Timer> ctr = Timer.class.getConstructor(new Class[] {String.class, Boolean.TYPE}); cancelTimer = ctr.newInstance(new Object[] { "MySQL Statement Cancellation Timer", Boolean.TRUE}); createdNamedTimer = true; } catch (Throwable t) { createdNamedTimer = false; } if (!createdNamedTimer) { cancelTimer = new Timer(true); } } return cancelTimer; } } /** * Creates a connection instance -- We need to provide factory-style methods * so we can support both JDBC3 (and older) and JDBC4 runtimes, otherwise * the class verifier complains when it tries to load JDBC4-only interface * classes that are present in JDBC4 method signatures. */ protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException { if (!Util.isJdbc4()) { return new ConnectionImpl(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url); } return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, new Object[] { hostToConnectTo, Integer.valueOf(portToConnectTo), info, databaseToConnectTo, url }, null); } private static final Random random = new Random(); /** * * @param url * @param hostList * @return */ protected static synchronized int getNextRoundRobinHostIndex(String url, List<?> hostList) { // we really do "random" here, because you don't get even // distribution when this is coupled with connection pools int indexRange = hostList.size(); int index = random.nextInt(indexRange); return index; } private static boolean nullSafeCompare(String s1, String s2) { if (s1 == null && s2 == null) { return true; } if (s1 == null && s2 != null) { return false; } return s1 != null && s1.equals(s2); } /** Are we in autoCommit mode? */ private boolean autoCommit = true; /** A cache of SQL to parsed prepared statement parameters. */ private CacheAdapter<String, ParseInfo> cachedPreparedStatementParams; /** * For servers > 4.1.0, what character set is the metadata returned in? */ private String characterSetMetadata = null; /** * The character set we want results and result metadata returned in (null == * results in any charset, metadata in UTF-8). */ private String characterSetResultsOnServer = null; /** * Holds cached mappings to charset converters to avoid static * synchronization and at the same time save memory (each charset converter * takes approx 65K of static data). */ private Map<String, Object> charsetConverterMap = new HashMap<String, Object>(CharsetMapping .getNumberOfCharsetsConfigured()); /** The point in time when this connection was created */ private long connectionCreationTimeMillis = 0; /** ID used when profiling */ private long connectionId; /** The database we're currently using (called Catalog in JDBC terms). */ private String database = null; /** Internal DBMD to use for various database-version specific features */ private DatabaseMetaData dbmd = null; private TimeZone defaultTimeZone; /** The event sink to use for profiling */ private ProfilerEventHandler eventSink; /** Why was this connection implicitly closed, if known? (for diagnostics) */ private Throwable forceClosedReason; /** Does the server suuport isolation levels? */ private boolean hasIsolationLevels = false; /** Does this version of MySQL support quoted identifiers? */ private boolean hasQuotedIdentifiers = false; /** The hostname we're connected to */ private String host = null; /** * We need this 'bootstrapped', because 4.1 and newer will send fields back * with this even before we fill this dynamically from the server. */ public Map<Integer, String> indexToJavaCharset = new HashMap<Integer, String>(); public Map<Integer, String> indexToCustomMysqlCharset = new HashMap<Integer, String>(); private Map<String, Integer> mysqlCharsetToCustomMblen = new HashMap<String, Integer>(); /** The I/O abstraction interface (network conn to MySQL server */ private transient MysqlIO io = null; private boolean isClientTzUTC = false; /** Has this connection been closed? */ private boolean isClosed = true; /** Is this connection associated with a global tx? */ private boolean isInGlobalTx = false; /** Is this connection running inside a JDK-1.3 VM? */ private boolean isRunningOnJDK13 = false; /** isolation level */ private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED; private boolean isServerTzUTC = false; /** When did the last query finish? */ private long lastQueryFinishedTime = 0; /** The logger we're going to use */ private transient Log log = NULL_LOGGER; /** * If gathering metrics, what was the execution time of the longest query so * far ? */ private long longestQueryTimeMs = 0; /** Is the server configured to use lower-case table names only? */ private boolean lowerCaseTableNames = false; /** When did the master fail? */ // private long masterFailTimeMillis = 0L; private long maximumNumberTablesAccessed = 0; /** Has the max-rows setting been changed from the default? */ private boolean maxRowsChanged = false; /** When was the last time we reported metrics? */ private long metricsLastReportedMs; private long minimumNumberTablesAccessed = Long.MAX_VALUE; /** The JDBC URL we're using */ private String myURL = null; /** Does this connection need to be tested? */ private boolean needsPing = false; private int netBufferLength = 16384; private boolean noBackslashEscapes = false; private long numberOfPreparedExecutes = 0; private long numberOfPrepares = 0; private long numberOfQueriesIssued = 0; private long numberOfResultSetsCreated = 0; private long[] numTablesMetricsHistBreakpoints; private int[] numTablesMetricsHistCounts; private long[] oldHistBreakpoints = null; private int[] oldHistCounts = null; /** A map of currently open statements */ private Map<Statement, Statement> openStatements; private LRUCache parsedCallableStatementCache; private boolean parserKnowsUnicode = false; /** The password we used */ private String password = null; private long[] perfMetricsHistBreakpoints; private int[] perfMetricsHistCounts; /** Point of origin where this Connection was created */ private String pointOfOrigin; /** The port number we're connected to (defaults to 3306) */ private int port = 3306; /** Properties for this connection specified by user */ protected Properties props = null; /** Should we retrieve 'info' messages from the server? */ private boolean readInfoMsg = false; /** Are we in read-only mode? */ private boolean readOnly = false; /** Cache of ResultSet metadata */ protected LRUCache resultSetMetadataCache; /** The timezone of the server */ private TimeZone serverTimezoneTZ = null; /** The map of server variables that we retrieve at connection init. */ private Map<String, String> serverVariables = null; private long shortestQueryTimeMs = Long.MAX_VALUE; /** A map of statements that have had setMaxRows() called on them */ private Map<Statement, Statement> statementsUsingMaxRows; private double totalQueryTimeMs = 0; /** Are transactions supported by the MySQL server we are connected to? */ private boolean transactionsSupported = false; /** * The type map for UDTs (not implemented, but used by some third-party * vendors, most notably IBM WebSphere) */ private Map<String,Class<?>> typeMap; /** Has ANSI_QUOTES been enabled on the server? */ private boolean useAnsiQuotes = false; /** The user we're connected as */ private String user = null; /** * Should we use server-side prepared statements? (auto-detected, but can be * disabled by user) */ private boolean useServerPreparedStmts = false; private LRUCache serverSideStatementCheckCache; private LRUCache serverSideStatementCache; private Calendar sessionCalendar; private Calendar utcCalendar; private String origHostToConnectTo; // we don't want to be able to publicly clone this... private int origPortToConnectTo; private String origDatabaseToConnectTo; private String errorMessageEncoding = "Cp1252"; // to begin with, changes after we talk to the server private boolean usePlatformCharsetConverters; /* * For testing failover scenarios */ private boolean hasTriedMasterFlag = false; /** * The comment (if any) that we'll prepend to all statements * sent to the server (to show up in "SHOW PROCESSLIST") */ private String statementComment = null; private boolean storesLowerCaseTableName; private List<StatementInterceptorV2> statementInterceptors; /** * If a CharsetEncoder is required for escaping. Needed for SJIS and related * problems with \u00A5. */ private boolean requiresEscapingEncoder; private String hostPortPair; /**' * For the delegate only */ protected ConnectionImpl() { } /** * Creates a connection to a MySQL Server. * * @param hostToConnectTo * the hostname of the database server * @param portToConnectTo * the port number the server is listening on * @param info * a Properties[] list holding the user and password * @param databaseToConnectTo * the database to connect to * @param url * the URL of the connection * @param d * the Driver instantation of the connection * @exception SQLException * if a database access error occurs */ protected ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException { this.connectionCreationTimeMillis = System.currentTimeMillis(); if (databaseToConnectTo == null) { databaseToConnectTo = ""; } // Stash away for later, used to clone this connection for Statement.cancel // and Statement.setQueryTimeout(). // this.origHostToConnectTo = hostToConnectTo; this.origPortToConnectTo = portToConnectTo; this.origDatabaseToConnectTo = databaseToConnectTo; try { Blob.class.getMethod("truncate", new Class[] {Long.TYPE}); this.isRunningOnJDK13 = false; } catch (NoSuchMethodException nsme) { this.isRunningOnJDK13 = true; } this.sessionCalendar = new GregorianCalendar(); this.utcCalendar = new GregorianCalendar(); this.utcCalendar.setTimeZone(TimeZone.getTimeZone("GMT")); // // Normally, this code would be in initializeDriverProperties, // but we need to do this as early as possible, so we can start // logging to the 'correct' place as early as possible...this.log // points to 'NullLogger' for every connection at startup to avoid // NPEs and the overhead of checking for NULL at every logging call. // // We will reset this to the configured logger during properties // initialization. // this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor()); // We store this per-connection, due to static synchronization // issues in Java's built-in TimeZone class... this.defaultTimeZone = Util.getDefaultTimeZone(); if ("GMT".equalsIgnoreCase(this.defaultTimeZone.getID())) { this.isClientTzUTC = true; } else { this.isClientTzUTC = false; } this.openStatements = new HashMap<Statement, Statement>(); if (NonRegisteringDriver.isHostPropertiesList(hostToConnectTo)) { Properties hostSpecificProps = NonRegisteringDriver.expandHostKeyValues(hostToConnectTo); Enumeration<?> propertyNames = hostSpecificProps.propertyNames(); while (propertyNames.hasMoreElements()) { String propertyName = propertyNames.nextElement().toString(); String propertyValue = hostSpecificProps.getProperty(propertyName); info.setProperty(propertyName, propertyValue); } } else { if (hostToConnectTo == null) { this.host = "localhost"; this.hostPortPair = this.host + ":" + portToConnectTo; } else { this.host = hostToConnectTo; if (hostToConnectTo.indexOf(":") == -1) { this.hostPortPair = this.host + ":" + portToConnectTo; } else { this.hostPortPair = this.host; } } } this.port = portToConnectTo; this.database = databaseToConnectTo; this.myURL = url; this.user = info.getProperty(NonRegisteringDriver.USER_PROPERTY_KEY); this.password = info .getProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY); if ((this.user == null) || this.user.equals("")) { this.user = ""; } if (this.password == null) { this.password = ""; } this.props = info; initializeDriverProperties(info); if (getUseUsageAdvisor()) { this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable()); } else { this.pointOfOrigin = ""; } try { this.dbmd = getMetaData(false, false); initializeSafeStatementInterceptors(); createNewIO(false); unSafeStatementInterceptors(); } catch (SQLException ex) { cleanup(ex); // don't clobber SQL exceptions throw ex; } catch (Exception ex) { cleanup(ex); StringBuffer mesg = new StringBuffer(128); if (!getParanoid()) { mesg.append("Cannot connect to MySQL server on "); mesg.append(this.host); mesg.append(":"); mesg.append(this.port); mesg.append(".\n\n"); mesg.append("Make sure that there is a MySQL server "); mesg.append("running on the machine/port you are trying "); mesg .append("to connect to and that the machine this software is " + "running on "); mesg.append("is able to connect to this host/port " + "(i.e. not firewalled). "); mesg .append("Also make sure that the server has not been started " + "with the --skip-networking "); mesg.append("flag.\n\n"); } else { mesg.append("Unable to connect to database."); } SQLException sqlEx = SQLError.createSQLException(mesg.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } NonRegisteringDriver.trackConnection(this); } public void unSafeStatementInterceptors() throws SQLException { ArrayList<StatementInterceptorV2> unSafedStatementInterceptors = new ArrayList<StatementInterceptorV2>(this.statementInterceptors.size()); for (int i = 0; i < this.statementInterceptors.size(); i++) { NoSubInterceptorWrapper wrappedInterceptor = (NoSubInterceptorWrapper) this.statementInterceptors.get(i); unSafedStatementInterceptors.add(wrappedInterceptor.getUnderlyingInterceptor()); } this.statementInterceptors = unSafedStatementInterceptors; if (this.io != null) { this.io.setStatementInterceptors(this.statementInterceptors); } } public void initializeSafeStatementInterceptors() throws SQLException { this.isClosed = false; List<Extension> unwrappedInterceptors = Util.loadExtensions(this, this.props, getStatementInterceptors(), "MysqlIo.BadStatementInterceptor", getExceptionInterceptor()); this.statementInterceptors = new ArrayList<StatementInterceptorV2>(unwrappedInterceptors.size()); for (int i = 0; i < unwrappedInterceptors.size(); i++) { Extension interceptor = unwrappedInterceptors.get(i); // adapt older versions of statement interceptors, handle the case where something wants v2 // functionality but wants to run with an older driver if (interceptor instanceof StatementInterceptor) { if (ReflectiveStatementInterceptorAdapter.getV2PostProcessMethod(interceptor.getClass()) != null) { this.statementInterceptors.add(new NoSubInterceptorWrapper(new ReflectiveStatementInterceptorAdapter((StatementInterceptor) interceptor))); } else { this.statementInterceptors.add(new NoSubInterceptorWrapper(new V1toV2StatementInterceptorAdapter((StatementInterceptor) interceptor))); } } else { this.statementInterceptors.add(new NoSubInterceptorWrapper((StatementInterceptorV2)interceptor)); } } } public List<StatementInterceptorV2> getStatementInterceptorsInstances() { return this.statementInterceptors; } private void addToHistogram(int[] histogramCounts, long[] histogramBreakpoints, long value, int numberOfTimes, long currentLowerBound, long currentUpperBound) { if (histogramCounts == null) { createInitialHistogram(histogramBreakpoints, currentLowerBound, currentUpperBound); } else { for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { if (histogramBreakpoints[i] >= value) { histogramCounts[i] += numberOfTimes; break; } } } } private void addToPerformanceHistogram(long value, int numberOfTimes) { checkAndCreatePerformanceHistogram(); addToHistogram(this.perfMetricsHistCounts, this.perfMetricsHistBreakpoints, value, numberOfTimes, this.shortestQueryTimeMs == Long.MAX_VALUE ? 0 : this.shortestQueryTimeMs, this.longestQueryTimeMs); } private void addToTablesAccessedHistogram(long value, int numberOfTimes) { checkAndCreateTablesAccessedHistogram(); addToHistogram(this.numTablesMetricsHistCounts, this.numTablesMetricsHistBreakpoints, value, numberOfTimes, this.minimumNumberTablesAccessed == Long.MAX_VALUE ? 0 : this.minimumNumberTablesAccessed, this.maximumNumberTablesAccessed); } /** * Builds the map needed for 4.1.0 and newer servers that maps field-level * charset/collation info to a java character encoding name. * * @throws SQLException * DOCUMENT ME! */ private void buildCollationMapping() throws SQLException { HashMap<Integer, String> javaCharset = null; if (versionMeetsMinimum(4, 1, 0)) { TreeMap<Long, String> sortedCollationMap = null; HashMap<Integer, String> customCharset = null; HashMap<String, Integer> customMblen = null; if (getCacheServerConfiguration()) { synchronized (serverCollationByUrl) { sortedCollationMap = (TreeMap<Long, String>) serverCollationByUrl.get(getURL()); javaCharset = (HashMap<Integer, String>) serverJavaCharsetByUrl.get(getURL()); customCharset = (HashMap<Integer, String>) serverCustomCharsetByUrl.get(getURL()); customMblen = (HashMap<String, Integer>) serverCustomMblenByUrl.get(getURL()); } } java.sql.Statement stmt = null; java.sql.ResultSet results = null; try { if (sortedCollationMap == null) { sortedCollationMap = new TreeMap<Long, String>(); javaCharset = new HashMap<Integer, String>(); customCharset = new HashMap<Integer, String>(); customMblen = new HashMap<String, Integer>(); stmt = getMetadataSafeStatement(); try { results = stmt.executeQuery("SHOW COLLATION"); if (versionMeetsMinimum(5, 0, 0)) { Util.resultSetToMap(sortedCollationMap, results, 3, 2); } else { while (results.next()) { sortedCollationMap.put(results.getLong(3), results.getString(2)); } } } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } for (Iterator<Map.Entry<Long, String>> indexIter = sortedCollationMap.entrySet().iterator(); indexIter.hasNext();) { Map.Entry<Long, String> indexEntry = indexIter.next(); int collationIndex = indexEntry.getKey().intValue(); String charsetName = indexEntry.getValue(); javaCharset.put(collationIndex, getJavaEncodingForMysqlEncoding(charsetName)); // if no static map for charsetIndex // or server has a different mapping then our static map, // adding it to custom map if (collationIndex >= CharsetMapping.MAP_SIZE || !charsetName.equals(CharsetMapping.STATIC_INDEX_TO_MYSQL_CHARSET_MAP.get(collationIndex))) { customCharset.put(collationIndex, charsetName); } // if no static map for charsetName adding to custom map if (!CharsetMapping.STATIC_CHARSET_TO_NUM_BYTES_MAP.containsKey(charsetName) && !CharsetMapping.STATIC_4_0_CHARSET_TO_NUM_BYTES_MAP.containsKey(charsetName)) { customMblen.put(charsetName, null); } } // if there is a number of custom charsets we should execute SHOW CHARACTER SET to know theirs mblen if (customMblen.size() > 0) { try { results = stmt.executeQuery("SHOW CHARACTER SET"); while (results.next()) { String charsetName = results.getString("Charset"); if (customMblen.containsKey(charsetName)) { customMblen.put(charsetName, results.getInt("Maxlen")); } } } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } } if (getCacheServerConfiguration()) { synchronized (serverCollationByUrl) { serverCollationByUrl.put(getURL(), sortedCollationMap); serverJavaCharsetByUrl.put(getURL(), javaCharset); serverCustomCharsetByUrl.put(getURL(), customCharset); serverCustomMblenByUrl.put(getURL(), customMblen); } } } this.indexToJavaCharset = Collections.unmodifiableMap(javaCharset); this.indexToCustomMysqlCharset = Collections.unmodifiableMap(customCharset); this.mysqlCharsetToCustomMblen = Collections.unmodifiableMap(customMblen); } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } finally { if (results != null) { try { results.close(); } catch (java.sql.SQLException sqlE) { // ignore } } if (stmt != null) { try { stmt.close(); } catch (java.sql.SQLException sqlE) { // ignore } } } } else { javaCharset = new HashMap<Integer, String>(); for (int i = 0; i < CharsetMapping.INDEX_TO_CHARSET.length; i++) { javaCharset.put(i, CharsetMapping.INDEX_TO_CHARSET[i]); } this.indexToJavaCharset = Collections.unmodifiableMap(javaCharset); } } public String getJavaEncodingForMysqlEncoding(String mysqlEncoding) throws SQLException { if (versionMeetsMinimum(4, 1, 0) && "latin1".equalsIgnoreCase(mysqlEncoding)) { return "Cp1252"; } return CharsetMapping.MYSQL_TO_JAVA_CHARSET_MAP.get(mysqlEncoding); } private boolean canHandleAsServerPreparedStatement(String sql) throws SQLException { if (sql == null || sql.length() == 0) { return true; } if (!this.useServerPreparedStmts) { return false; } if (getCachePreparedStatements()) { synchronized (this.serverSideStatementCheckCache) { Boolean flag = (Boolean)this.serverSideStatementCheckCache.get(sql); if (flag != null) { return flag.booleanValue(); } boolean canHandle = canHandleAsServerPreparedStatementNoCache(sql); if (sql.length() < getPreparedStatementCacheSqlLimit()) { this.serverSideStatementCheckCache.put(sql, canHandle ? Boolean.TRUE : Boolean.FALSE); } return canHandle; } } return canHandleAsServerPreparedStatementNoCache(sql); } private boolean canHandleAsServerPreparedStatementNoCache(String sql) throws SQLException { // Can't use server-side prepare for CALL if (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "CALL")) { return false; } boolean canHandleAsStatement = true; if (!versionMeetsMinimum(5, 0, 7) && (StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "SELECT") || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "DELETE") || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "INSERT") || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "UPDATE") || StringUtils.startsWithIgnoreCaseAndNonAlphaNumeric(sql, "REPLACE"))) { // check for limit ?[,?] /* * The grammar for this (from the server) is: ULONG_NUM | ULONG_NUM * ',' ULONG_NUM | ULONG_NUM OFFSET_SYM ULONG_NUM */ int currentPos = 0; int statementLength = sql.length(); int lastPosToLook = statementLength - 7; // "LIMIT ".length() boolean allowBackslashEscapes = !this.noBackslashEscapes; char quoteChar = this.useAnsiQuotes ? '"' : '\''; boolean foundLimitWithPlaceholder = false; while (currentPos < lastPosToLook) { int limitStart = StringUtils.indexOfIgnoreCaseRespectQuotes( currentPos, sql, "LIMIT ", quoteChar, allowBackslashEscapes); if (limitStart == -1) { break; } currentPos = limitStart + 7; while (currentPos < statementLength) { char c = sql.charAt(currentPos); // // Have we reached the end // of what can be in a LIMIT clause? // if (!Character.isDigit(c) && !Character.isWhitespace(c) && c != ',' && c != '?') { break; } if (c == '?') { foundLimitWithPlaceholder = true; break; } currentPos++; } } canHandleAsStatement = !foundLimitWithPlaceholder; } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "CREATE TABLE")) { canHandleAsStatement = false; } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "DO")) { canHandleAsStatement = false; } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "SET")) { canHandleAsStatement = false; } return canHandleAsStatement; } /** * Changes the user on this connection by performing a re-authentication. If * authentication fails, the connection will remain under the context of the * current user. * * @param userName * the username to authenticate with * @param newPassword * the password to authenticate with * @throws SQLException * if authentication fails, or some other error occurs while * performing the command. */ public void changeUser(String userName, String newPassword) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); if ((userName == null) || userName.equals("")) { userName = ""; } if (newPassword == null) { newPassword = ""; } this.io.changeUser(userName, newPassword, this.database); this.user = userName; this.password = newPassword; if (versionMeetsMinimum(4, 1, 0)) { configureClientCharacterSet(true); } setSessionVariables(); setupServerForTruncationChecks(); } } private boolean characterSetNamesMatches(String mysqlEncodingName) { // set names is equivalent to character_set_client ..._results and ..._connection, // but we set _results later, so don't check it here. return (mysqlEncodingName != null && mysqlEncodingName.equalsIgnoreCase(this.serverVariables.get("character_set_client")) && mysqlEncodingName.equalsIgnoreCase(this.serverVariables.get("character_set_connection"))); } private void checkAndCreatePerformanceHistogram() { if (this.perfMetricsHistCounts == null) { this.perfMetricsHistCounts = new int[HISTOGRAM_BUCKETS]; } if (this.perfMetricsHistBreakpoints == null) { this.perfMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS]; } } private void checkAndCreateTablesAccessedHistogram() { if (this.numTablesMetricsHistCounts == null) { this.numTablesMetricsHistCounts = new int[HISTOGRAM_BUCKETS]; } if (this.numTablesMetricsHistBreakpoints == null) { this.numTablesMetricsHistBreakpoints = new long[HISTOGRAM_BUCKETS]; } } public void checkClosed() throws SQLException { if (this.isClosed) { throwConnectionClosedException(); } } public void throwConnectionClosedException() throws SQLException { StringBuffer messageBuf = new StringBuffer( "No operations allowed after connection closed."); SQLException ex = SQLError.createSQLException(messageBuf.toString(), SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); if (this.forceClosedReason != null) { ex.initCause(this.forceClosedReason); } throw ex; } /** * If useUnicode flag is set and explicit client character encoding isn't * specified then assign encoding from server if any. * * @throws SQLException * DOCUMENT ME! */ private void checkServerEncoding() throws SQLException { if (getUseUnicode() && (getEncoding() != null)) { // spec'd by client, don't map return; } String serverEncoding = this.serverVariables.get("character_set"); if (serverEncoding == null) { // must be 4.1.1 or newer? serverEncoding = this.serverVariables.get("character_set_server"); } String mappedServerEncoding = null; if (serverEncoding != null) { try { mappedServerEncoding = getJavaEncodingForMysqlEncoding(serverEncoding .toUpperCase(Locale.ENGLISH)); } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } } // // First check if we can do the encoding ourselves // if (!getUseUnicode() && (mappedServerEncoding != null)) { SingleByteCharsetConverter converter = getCharsetConverter(mappedServerEncoding); if (converter != null) { // we know how to convert this ourselves setUseUnicode(true); // force the issue setEncoding(mappedServerEncoding); return; } } // // Now, try and find a Java I/O converter that can do // the encoding for us // if (serverEncoding != null) { if (mappedServerEncoding == null) { // We don't have a mapping for it, so try // and canonicalize the name.... if (Character.isLowerCase(serverEncoding.charAt(0))) { char[] ach = serverEncoding.toCharArray(); ach[0] = Character.toUpperCase(serverEncoding.charAt(0)); setEncoding(new String(ach)); } } if (mappedServerEncoding == null) { throw SQLError.createSQLException("Unknown character encoding on server '" + serverEncoding + "', use 'characterEncoding=' property " + " to provide correct mapping", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor()); } // // Attempt to use the encoding, and bail out if it // can't be used // try { StringUtils.getBytes("abc",mappedServerEncoding); setEncoding(mappedServerEncoding); setUseUnicode(true); } catch (UnsupportedEncodingException UE) { throw SQLError.createSQLException( "The driver can not map the character encoding '" + getEncoding() + "' that your server is using " + "to a character encoding your JVM understands. You " + "can specify this mapping manually by adding \"useUnicode=true\" " + "as well as \"characterEncoding=[an_encoding_your_jvm_understands]\" " + "to your JDBC URL.", "0S100", getExceptionInterceptor()); } } } /** * Set transaction isolation level to the value received from server if any. * Is called by connectionInit(...) * * @throws SQLException * DOCUMENT ME! */ private void checkTransactionIsolationLevel() throws SQLException { String txIsolationName = null; if (versionMeetsMinimum(4, 0, 3)) { txIsolationName = "tx_isolation"; } else { txIsolationName = "transaction_isolation"; } String s = this.serverVariables.get(txIsolationName); if (s != null) { Integer intTI = mapTransIsolationNameToValue.get(s); if (intTI != null) { this.isolationLevel = intTI.intValue(); } } } /** * Clobbers the physical network connection and marks * this connection as closed. * * @throws SQLException */ public void abortInternal() throws SQLException { if (this.io != null) { try { this.io.forceClose(); } catch (Throwable t) { // can't do anything about it, and we're forcibly aborting } this.io = null; } this.isClosed = true; } /** * Destroys this connection and any underlying resources * * @param fromWhere * DOCUMENT ME! * @param whyCleanedUp * DOCUMENT ME! */ private void cleanup(Throwable whyCleanedUp) { try { if ((this.io != null) && !isClosed()) { realClose(false, false, false, whyCleanedUp); } else if (this.io != null) { this.io.forceClose(); } } catch (SQLException sqlEx) { // ignore, we're going away. ; } this.isClosed = true; } public void clearHasTriedMaster() { this.hasTriedMasterFlag = false; } /** * After this call, getWarnings returns null until a new warning is reported * for this connection. * * @exception SQLException * if a database access error occurs */ public void clearWarnings() throws SQLException { // firstWarning = null; } /** * DOCUMENT ME! * * @param sql * DOCUMENT ME! * @return DOCUMENT ME! * @throws SQLException * DOCUMENT ME! */ public java.sql.PreparedStatement clientPrepareStatement(String sql) throws SQLException { return clientPrepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } /** * @see Connection#prepareStatement(String, int) */ public java.sql.PreparedStatement clientPrepareStatement(String sql, int autoGenKeyIndex) throws SQLException { java.sql.PreparedStatement pStmt = clientPrepareStatement(sql); ((com.mysql.jdbc.PreparedStatement) pStmt) .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS); return pStmt; } /** * DOCUMENT ME! * * @param sql * DOCUMENT ME! * @param resultSetType * DOCUMENT ME! * @param resultSetConcurrency * DOCUMENT ME! * @return DOCUMENT ME! * @throws SQLException * DOCUMENT ME! */ public java.sql.PreparedStatement clientPrepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true); } public java.sql.PreparedStatement clientPrepareStatement(String sql, int resultSetType, int resultSetConcurrency, boolean processEscapeCodesIfNeeded) throws SQLException { checkClosed(); String nativeSql = processEscapeCodesIfNeeded && getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; PreparedStatement pStmt = null; if (getCachePreparedStatements()) { PreparedStatement.ParseInfo pStmtInfo = this.cachedPreparedStatementParams.get(nativeSql); if (pStmtInfo == null) { pStmt = com.mysql.jdbc.PreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.database); this.cachedPreparedStatementParams.put(nativeSql, pStmt .getParseInfo()); } else { pStmt = new com.mysql.jdbc.PreparedStatement(getLoadBalanceSafeProxy(), nativeSql, this.database, pStmtInfo); } } else { pStmt = com.mysql.jdbc.PreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.database); } pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); return pStmt; } /** * @see java.sql.Connection#prepareStatement(String, int[]) */ public java.sql.PreparedStatement clientPrepareStatement(String sql, int[] autoGenKeyIndexes) throws SQLException { PreparedStatement pStmt = (PreparedStatement) clientPrepareStatement(sql); pStmt .setRetrieveGeneratedKeys((autoGenKeyIndexes != null) && (autoGenKeyIndexes.length > 0)); return pStmt; } /** * @see java.sql.Connection#prepareStatement(String, String[]) */ public java.sql.PreparedStatement clientPrepareStatement(String sql, String[] autoGenKeyColNames) throws SQLException { PreparedStatement pStmt = (PreparedStatement) clientPrepareStatement(sql); pStmt .setRetrieveGeneratedKeys((autoGenKeyColNames != null) && (autoGenKeyColNames.length > 0)); return pStmt; } public java.sql.PreparedStatement clientPrepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true); } // --------------------------JDBC 2.0----------------------------- /** * In some cases, it is desirable to immediately release a Connection's * database and JDBC resources instead of waiting for them to be * automatically released (cant think why off the top of my head) <B>Note:</B> * A Connection is automatically closed when it is garbage collected. * Certain fatal errors also result in a closed connection. * * @exception SQLException * if a database access error occurs */ public void close() throws SQLException { synchronized (getConnectionMutex()) { if (this.connectionLifecycleInterceptors != null) { new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { ((ConnectionLifecycleInterceptor)each).close(); } }.doForAll(); } realClose(true, true, false, null); } } /** * Closes all currently open statements. * * @throws SQLException * DOCUMENT ME! */ private void closeAllOpenStatements() throws SQLException { SQLException postponedException = null; if (this.openStatements != null) { List<Statement> currentlyOpenStatements = new ArrayList<Statement>(); // we need this to // avoid // ConcurrentModificationEx for (Iterator<Statement> iter = this.openStatements.keySet().iterator(); iter.hasNext();) { currentlyOpenStatements.add(iter.next()); } int numStmts = currentlyOpenStatements.size(); for (int i = 0; i < numStmts; i++) { StatementImpl stmt = (StatementImpl) currentlyOpenStatements.get(i); try { stmt.realClose(false, true); } catch (SQLException sqlEx) { postponedException = sqlEx; // throw it later, cleanup all // statements first } } if (postponedException != null) { throw postponedException; } } } private void closeStatement(java.sql.Statement stmt) { if (stmt != null) { try { stmt.close(); } catch (SQLException sqlEx) { ; // ignore } stmt = null; } } /** * The method commit() makes all changes made since the previous * commit/rollback permanent and releases any database locks currently held * by the Connection. This method should only be used when auto-commit has * been disabled. * <p> * <b>Note:</b> MySQL does not support transactions, so this method is a * no-op. * </p> * * @exception SQLException * if a database access error occurs * @see setAutoCommit */ public void commit() throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor)each).commit()) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } // no-op if _relaxAutoCommit == true if (this.autoCommit && !getRelaxAutoCommit()) { throw SQLError.createSQLException("Can't call commit when autocommit=true", getExceptionInterceptor()); } else if (this.transactionsSupported) { if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) { if (!this.io.inTransactionOnServer()) { return; // effectively a no-op } } execSQL(null, "commit", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } } catch (SQLException sqlException) { if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE .equals(sqlException.getSQLState())) { throw SQLError .createSQLException( "Communications link failure during commit(). Transaction resolution unknown.", SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlException; } finally { this.needsPing = this.getReconnectAtTxEnd(); } } return; } /** * Configures client-side properties for character set information. * * @throws SQLException * if unable to configure the specified character set. */ private void configureCharsetProperties() throws SQLException { if (getEncoding() != null) { // Attempt to use the encoding, and bail out if it // can't be used try { String testString = "abc"; StringUtils.getBytes(testString, getEncoding()); } catch (UnsupportedEncodingException UE) { // Try the MySQL character encoding, then.... String oldEncoding = getEncoding(); try { setEncoding(getJavaEncodingForMysqlEncoding(oldEncoding)); } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } if (getEncoding() == null) { throw SQLError.createSQLException( "Java does not support the MySQL character encoding " + " " + "encoding '" + oldEncoding + "'.", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor()); } try { String testString = "abc"; StringUtils.getBytes(testString, getEncoding()); } catch (UnsupportedEncodingException encodingEx) { throw SQLError.createSQLException("Unsupported character " + "encoding '" + getEncoding() + "'.", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor()); } } } } /** * Sets up client character set for MySQL-4.1 and newer if the user This * must be done before any further communication with the server! * * @return true if this routine actually configured the client character * set, or false if the driver needs to use 'older' methods to * detect the character set, as it is connected to a MySQL server * older than 4.1.0 * @throws SQLException * if an exception happens while sending 'SET NAMES' to the * server, or the server sends character set information that * the client doesn't know about. */ private boolean configureClientCharacterSet(boolean dontCheckServerMatch) throws SQLException { String realJavaEncoding = getEncoding(); boolean characterSetAlreadyConfigured = false; try { if (versionMeetsMinimum(4, 1, 0)) { characterSetAlreadyConfigured = true; setUseUnicode(true); configureCharsetProperties(); realJavaEncoding = getEncoding(); // we need to do this again // to grab this for // versions > 4.1.0 try { // Fault injection for testing server character set indices if (props != null && props.getProperty("com.mysql.jdbc.faultInjection.serverCharsetIndex") != null) { this.io.serverCharsetIndex = Integer.parseInt( props.getProperty( "com.mysql.jdbc.faultInjection.serverCharsetIndex")); } String serverEncodingToSet = CharsetMapping.INDEX_TO_CHARSET[this.io.serverCharsetIndex]; if (serverEncodingToSet == null || serverEncodingToSet.length() == 0) { if (realJavaEncoding != null) { // user knows best, try it setEncoding(realJavaEncoding); } else { throw SQLError.createSQLException( "Unknown initial character set index '" + this.io.serverCharsetIndex + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } // "latin1" on MySQL-4.1.0+ is actually CP1252, not ISO8859_1 if (versionMeetsMinimum(4, 1, 0) && "ISO8859_1".equalsIgnoreCase(serverEncodingToSet)) { serverEncodingToSet = "Cp1252"; } if ("UnicodeBig".equalsIgnoreCase(serverEncodingToSet) || "UTF-16".equalsIgnoreCase(serverEncodingToSet) || "UTF-16LE".equalsIgnoreCase(serverEncodingToSet) || "UTF-32".equalsIgnoreCase(serverEncodingToSet) ) { serverEncodingToSet = "UTF-8"; } setEncoding(serverEncodingToSet); } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) { if (realJavaEncoding != null) { // user knows best, try it setEncoding(realJavaEncoding); } else { throw SQLError.createSQLException( "Unknown initial character set index '" + this.io.serverCharsetIndex + "' received from server. Initial client character set can be forced via the 'characterEncoding' property.", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } if (getEncoding() == null) { // punt? setEncoding("ISO8859_1"); } // // Has the user has 'forced' the character encoding via // driver properties? // if (getUseUnicode()) { if (realJavaEncoding != null) { // // Now, inform the server what character set we // will be using from now-on... // if (realJavaEncoding.equalsIgnoreCase("UTF-8") || realJavaEncoding.equalsIgnoreCase("UTF8")) { // charset names are case-sensitive boolean utf8mb4Supported = versionMeetsMinimum(5, 5, 2); boolean useutf8mb4 = false; if (utf8mb4Supported) { useutf8mb4 = (this.io.serverCharsetIndex == 45); } if (!getUseOldUTF8Behavior()) { if (dontCheckServerMatch || !characterSetNamesMatches("utf8") || (utf8mb4Supported && !characterSetNamesMatches("utf8mb4"))) { execSQL(null, "SET NAMES " + (useutf8mb4 ? "utf8mb4" : "utf8"), -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } } else { execSQL(null, "SET NAMES latin1", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } setEncoding(realJavaEncoding); } /* not utf-8 */else { String mysqlEncodingName = CharsetMapping .getMysqlEncodingForJavaEncoding( realJavaEncoding .toUpperCase(Locale.ENGLISH), this); /* * if ("koi8_ru".equals(mysqlEncodingName)) { // * This has a _different_ name in 4.1... * mysqlEncodingName = "ko18r"; } else if * ("euc_kr".equals(mysqlEncodingName)) { // * Different name in 4.1 mysqlEncodingName = * "euckr"; } */ if (mysqlEncodingName != null) { if (dontCheckServerMatch || !characterSetNamesMatches(mysqlEncodingName)) { execSQL(null, "SET NAMES " + mysqlEncodingName, -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } } // Switch driver's encoding now, since the server // knows what we're sending... // setEncoding(realJavaEncoding); } } else if (getEncoding() != null) { // Tell the server we'll use the server default charset // to send our // queries from now on.... String mysqlEncodingName = getServerCharacterEncoding(); if(getUseOldUTF8Behavior()){ mysqlEncodingName = "latin1"; } boolean ucs2 = false; if ( "ucs2".equalsIgnoreCase(mysqlEncodingName) || "utf16".equalsIgnoreCase(mysqlEncodingName) || "utf16le".equalsIgnoreCase(mysqlEncodingName) || "utf32".equalsIgnoreCase(mysqlEncodingName)) { mysqlEncodingName = "utf8"; ucs2 = true; if (getCharacterSetResults() == null) { setCharacterSetResults("UTF-8"); } } if (dontCheckServerMatch || !characterSetNamesMatches(mysqlEncodingName) || ucs2) { try { execSQL(null, "SET NAMES " + mysqlEncodingName, -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } } realJavaEncoding = getEncoding(); } } // // We know how to deal with any charset coming back from // the database, so tell the server not to do conversion // if the user hasn't 'forced' a result-set character set // String onServer = null; boolean isNullOnServer = false; if (this.serverVariables != null) { onServer = this.serverVariables.get("character_set_results"); isNullOnServer = onServer == null || "NULL".equalsIgnoreCase(onServer) || onServer.length() == 0; } if (getCharacterSetResults() == null) { // // Only send if needed, if we're caching server variables // we -have- to send, because we don't know what it was // before we cached them. // if (!isNullOnServer) { try { execSQL(null, "SET character_set_results = NULL", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } if (!this.usingCachedConfig) { this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, null); } } else { if (!this.usingCachedConfig) { this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, onServer); } } } else { if(getUseOldUTF8Behavior()){ try { execSQL(null, "SET NAMES " + "latin1", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } } String charsetResults = getCharacterSetResults(); String mysqlEncodingName = null; if ("UTF-8".equalsIgnoreCase(charsetResults) || "UTF8".equalsIgnoreCase(charsetResults)) { mysqlEncodingName = "utf8"; } else if ("null".equalsIgnoreCase(charsetResults)) { mysqlEncodingName = "NULL"; } else { mysqlEncodingName = CharsetMapping .getMysqlEncodingForJavaEncoding(charsetResults .toUpperCase(Locale.ENGLISH), this); } // // Only change the value if needed // if (mysqlEncodingName == null) { throw SQLError.createSQLException( "Can't map "+charsetResults+" given for characterSetResults to a supported MySQL encoding.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (!mysqlEncodingName.equalsIgnoreCase( this.serverVariables.get("character_set_results"))) { StringBuffer setBuf = new StringBuffer( "SET character_set_results = ".length() + mysqlEncodingName.length()); setBuf.append("SET character_set_results = ").append( mysqlEncodingName); try { execSQL(null, setBuf.toString(), -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } if (!this.usingCachedConfig) { this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, mysqlEncodingName); } // We have to set errorMessageEncoding according to new value // of charsetResults for server version 5.5 and higher if (versionMeetsMinimum(5, 5, 0)) { this.errorMessageEncoding = charsetResults; } } else { if (!this.usingCachedConfig) { this.serverVariables.put(JDBC_LOCAL_CHARACTER_SET_RESULTS, onServer); } } } if (getConnectionCollation() != null) { StringBuffer setBuf = new StringBuffer( "SET collation_connection = ".length() + getConnectionCollation().length()); setBuf.append("SET collation_connection = ").append( getConnectionCollation()); try { execSQL(null, setBuf.toString(), -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } } } else { // Use what the server has specified realJavaEncoding = getEncoding(); // so we don't get // swapped out in the finally // block.... } } finally { // Failsafe, make sure that the driver's notion of character // encoding matches what the user has specified. // setEncoding(realJavaEncoding); } /** * Check if we need a CharsetEncoder for escaping codepoints that are * transformed to backslash (0x5c) in the connection encoding. */ try { CharsetEncoder enc = Charset.forName(getEncoding()).newEncoder(); CharBuffer cbuf = CharBuffer.allocate(1); ByteBuffer bbuf = ByteBuffer.allocate(1); cbuf.put("\u00a5"); cbuf.position(0); enc.encode(cbuf, bbuf, true); if(bbuf.get(0) == '\\') { requiresEscapingEncoder = true; } else { cbuf.clear(); bbuf.clear(); cbuf.put("\u20a9"); cbuf.position(0); enc.encode(cbuf, bbuf, true); if(bbuf.get(0) == '\\') { requiresEscapingEncoder = true; } } } catch(java.nio.charset.UnsupportedCharsetException ucex) { // fallback to String API - for Java 1.4 try { byte bbuf[] = StringUtils.getBytes("\u00a5", getEncoding()); if (bbuf[0] == '\\') { requiresEscapingEncoder = true; } else { bbuf = StringUtils.getBytes("\u20a9", getEncoding()); if (bbuf[0] == '\\') { requiresEscapingEncoder = true; } } } catch(UnsupportedEncodingException ueex) { throw SQLError.createSQLException("Unable to use encoding: " + getEncoding(), SQLError.SQL_STATE_GENERAL_ERROR, ueex, getExceptionInterceptor()); } } return characterSetAlreadyConfigured; } /** * Configures the client's timezone if required. * * @throws SQLException * if the timezone the server is configured to use can't be * mapped to a Java timezone. */ private void configureTimezone() throws SQLException { String configuredTimeZoneOnServer = this.serverVariables.get("timezone"); if (configuredTimeZoneOnServer == null) { configuredTimeZoneOnServer = this.serverVariables.get("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverVariables.get("system_time_zone"); } } String canoncicalTimezone = getServerTimezone(); if ((getUseTimezone() || !getUseLegacyDatetimeCode()) && configuredTimeZoneOnServer != null) { // user can override this with driver properties, so don't detect if that's the case if (canoncicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canoncicalTimezone)) { try { canoncicalTimezone = TimeUtil .getCanoncialTimezone(configuredTimeZoneOnServer, getExceptionInterceptor()); if (canoncicalTimezone == null) { throw SQLError.createSQLException("Can't map timezone '" + configuredTimeZoneOnServer + "' to " + " canonical timezone.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } catch (IllegalArgumentException iae) { throw SQLError.createSQLException(iae.getMessage(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } } else { canoncicalTimezone = getServerTimezone(); } if (canoncicalTimezone != null && canoncicalTimezone.length() > 0) { this.serverTimezoneTZ = TimeZone.getTimeZone(canoncicalTimezone); // // The Calendar class has the behavior of mapping // unknown timezones to 'GMT' instead of throwing an // exception, so we must check for this... // if (!canoncicalTimezone.equalsIgnoreCase("GMT") && this.serverTimezoneTZ.getID().equals("GMT")) { throw SQLError.createSQLException("No timezone mapping entry for '" + canoncicalTimezone + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if ("GMT".equalsIgnoreCase(this.serverTimezoneTZ.getID())) { this.isServerTzUTC = true; } else { this.isServerTzUTC = false; } } } private void createInitialHistogram(long[] breakpoints, long lowerBound, long upperBound) { double bucketSize = (((double) upperBound - (double) lowerBound) / HISTOGRAM_BUCKETS) * 1.25; if (bucketSize < 1) { bucketSize = 1; } for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { breakpoints[i] = lowerBound; lowerBound += bucketSize; } } /** * Creates an IO channel to the server * * @param isForReconnect * is this request for a re-connect * @return a new MysqlIO instance connected to a server * @throws SQLException * if a database access error occurs * @throws CommunicationsException * DOCUMENT ME! */ public void createNewIO(boolean isForReconnect) throws SQLException { synchronized (getConnectionMutex()) { // Synchronization Not needed for *new* connections, but defintely for // connections going through fail-over, since we might get the // new connection up and running *enough* to start sending // cached or still-open server-side prepared statements over // to the backend before we get a chance to re-prepare them... Properties mergedProps = exposeAsProperties(this.props); if (!getHighAvailability()) { connectOneTryOnly(isForReconnect, mergedProps); return; } connectWithRetries(isForReconnect, mergedProps); } } private void connectWithRetries(boolean isForReconnect, Properties mergedProps) throws SQLException { double timeout = getInitialTimeout(); boolean connectionGood = false; Exception connectionException = null; for (int attemptCount = 0; (attemptCount < getMaxReconnects()) && !connectionGood; attemptCount++) { try { if (this.io != null) { this.io.forceClose(); } coreConnect(mergedProps); pingInternal(false, 0); boolean oldAutoCommit; int oldIsolationLevel; boolean oldReadOnly; String oldCatalog; synchronized (getConnectionMutex()) { this.connectionId = this.io.getThreadId(); this.isClosed = false; // save state from old connection oldAutoCommit = getAutoCommit(); oldIsolationLevel = this.isolationLevel; oldReadOnly = isReadOnly(false); oldCatalog = getCatalog(); this.io.setStatementInterceptors(this.statementInterceptors); } // Server properties might be different // from previous connection, so initialize // again... initializePropsFromServer(); if (isForReconnect) { // Restore state from old connection setAutoCommit(oldAutoCommit); if (this.hasIsolationLevels) { setTransactionIsolation(oldIsolationLevel); } setCatalog(oldCatalog); setReadOnly(oldReadOnly); } connectionGood = true; break; } catch (Exception EEE) { connectionException = EEE; connectionGood = false; } if (connectionGood) { break; } if (attemptCount > 0) { try { Thread.sleep((long) timeout * 1000); } catch (InterruptedException IE) { // ignore } } } // end attempts for a single host if (!connectionGood) { // We've really failed! SQLException chainedEx = SQLError.createSQLException( Messages.getString("Connection.UnableToConnectWithRetries", new Object[] {Integer.valueOf(getMaxReconnects())}), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor()); chainedEx.initCause(connectionException); throw chainedEx; } if (getParanoid() && !getHighAvailability()) { this.password = null; this.user = null; } if (isForReconnect) { // // Retrieve any 'lost' prepared statements if re-connecting // Iterator<Statement> statementIter = this.openStatements.values().iterator(); // // We build a list of these outside the map of open statements, // because // in the process of re-preparing, we might end up having to // close // a prepared statement, thus removing it from the map, and // generating // a ConcurrentModificationException // Stack<Statement> serverPreparedStatements = null; while (statementIter.hasNext()) { Statement statementObj = statementIter.next(); if (statementObj instanceof ServerPreparedStatement) { if (serverPreparedStatements == null) { serverPreparedStatements = new Stack<Statement>(); } serverPreparedStatements.add(statementObj); } } if (serverPreparedStatements != null) { while (!serverPreparedStatements.isEmpty()) { ((ServerPreparedStatement) serverPreparedStatements .pop()).rePrepare(); } } } } private void coreConnect(Properties mergedProps) throws SQLException, IOException { int newPort = 3306; String newHost = "localhost"; String protocol = mergedProps.getProperty(NonRegisteringDriver.PROTOCOL_PROPERTY_KEY); if (protocol != null) { // "new" style URL if ("tcp".equalsIgnoreCase(protocol)) { newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY)); newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306")); } else if ("pipe".equalsIgnoreCase(protocol)) { setSocketFactoryClassName(NamedPipeSocketFactory.class.getName()); String path = mergedProps.getProperty(NonRegisteringDriver.PATH_PROPERTY_KEY); if (path != null) { mergedProps.setProperty(NamedPipeSocketFactory.NAMED_PIPE_PROP_NAME, path); } } else { // normalize for all unknown protocols newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY)); newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306")); } } else { String[] parsedHostPortPair = NonRegisteringDriver .parseHostPortPair(this.hostPortPair); newHost = parsedHostPortPair[NonRegisteringDriver.HOST_NAME_INDEX]; newHost = normalizeHost(newHost); if (parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) { newPort = parsePortNumber(parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]); } } this.port = newPort; this.host = newHost; this.io = new MysqlIO(newHost, newPort, mergedProps, getSocketFactoryClassName(), getProxy(), getSocketTimeout(), this.largeRowSizeThreshold.getValueAsInt()); this.io.doHandshake(this.user, this.password, this.database); } private String normalizeHost(String hostname) { if (hostname == null || StringUtils.isEmptyOrWhitespaceOnly(hostname)) { return "localhost"; } return hostname; } private int parsePortNumber(String portAsString) throws SQLException { int portNumber = 3306; try { portNumber = Integer .parseInt(portAsString); } catch (NumberFormatException nfe) { throw SQLError.createSQLException( "Illegal connection port value '" + portAsString + "'", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor()); } return portNumber; } private void connectOneTryOnly(boolean isForReconnect, Properties mergedProps) throws SQLException { Exception connectionNotEstablishedBecause = null; try { coreConnect(mergedProps); this.connectionId = this.io.getThreadId(); this.isClosed = false; // save state from old connection boolean oldAutoCommit = getAutoCommit(); int oldIsolationLevel = this.isolationLevel; boolean oldReadOnly = isReadOnly(false); String oldCatalog = getCatalog(); this.io.setStatementInterceptors(this.statementInterceptors); // Server properties might be different // from previous connection, so initialize // again... initializePropsFromServer(); if (isForReconnect) { // Restore state from old connection setAutoCommit(oldAutoCommit); if (this.hasIsolationLevels) { setTransactionIsolation(oldIsolationLevel); } setCatalog(oldCatalog); setReadOnly(oldReadOnly); } return; } catch (Exception EEE) { if (EEE instanceof SQLException && ((SQLException)EEE).getErrorCode() == MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD && !getDisconnectOnExpiredPasswords()) { return; } if (this.io != null) { this.io.forceClose(); } connectionNotEstablishedBecause = EEE; if (EEE instanceof SQLException) { throw (SQLException)EEE; } SQLException chainedEx = SQLError.createSQLException( Messages.getString("Connection.UnableToConnect"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor()); chainedEx.initCause(connectionNotEstablishedBecause); throw chainedEx; } } private void createPreparedStatementCaches() throws SQLException { synchronized (getConnectionMutex()) { int cacheSize = getPreparedStatementCacheSize(); try { Class<?> factoryClass; factoryClass = Class.forName(getParseInfoCacheFactory()); @SuppressWarnings("unchecked") CacheAdapterFactory<String, ParseInfo> cacheFactory = ((CacheAdapterFactory<String, ParseInfo>)factoryClass.newInstance()); this.cachedPreparedStatementParams = cacheFactory.getInstance(this, myURL, getPreparedStatementCacheSize(), getPreparedStatementCacheSqlLimit(), props); } catch (ClassNotFoundException e) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.CantFindCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}), getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } catch (InstantiationException e) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}), getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } catch (IllegalAccessException e) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}), getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } if (getUseServerPreparedStmts()) { this.serverSideStatementCheckCache = new LRUCache(cacheSize); this.serverSideStatementCache = new LRUCache(cacheSize) { private static final long serialVersionUID = 7692318650375988114L; protected boolean removeEldestEntry(java.util.Map.Entry<Object, Object> eldest) { if (this.maxElements <= 1) { return false; } boolean removeIt = super.removeEldestEntry(eldest); if (removeIt) { ServerPreparedStatement ps = (ServerPreparedStatement)eldest.getValue(); ps.isCached = false; ps.setClosed(false); try { ps.close(); } catch (SQLException sqlEx) { // punt } } return removeIt; } }; } } } /** * SQL statements without parameters are normally executed using Statement * objects. If the same SQL statement is executed many times, it is more * efficient to use a PreparedStatement * * @return a new Statement object * @throws SQLException * passed through from the constructor */ public java.sql.Statement createStatement() throws SQLException { return createStatement(DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } /** * JDBC 2.0 Same as createStatement() above, but allows the default result * set type and result set concurrency type to be overridden. * * @param resultSetType * a result set type, see ResultSet.TYPE_XXX * @param resultSetConcurrency * a concurrency type, see ResultSet.CONCUR_XXX * @return a new Statement object * @exception SQLException * if a database-access error occurs. */ public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { checkClosed(); StatementImpl stmt = new StatementImpl(getLoadBalanceSafeProxy(), this.database); stmt.setResultSetType(resultSetType); stmt.setResultSetConcurrency(resultSetConcurrency); return stmt; } /** * @see Connection#createStatement(int, int, int) */ public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (getPedantic()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException( "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } return createStatement(resultSetType, resultSetConcurrency); } public void dumpTestcaseQuery(String query) { System.err.println(query); } public Connection duplicate() throws SQLException { return new ConnectionImpl( this.origHostToConnectTo, this.origPortToConnectTo, this.props, this.origDatabaseToConnectTo, this.myURL); } /** * Send a query to the server. Returns one of the ResultSet objects. This is * synchronized, so Statement's queries will be serialized. * * @param callingStatement * DOCUMENT ME! * @param sql * the SQL statement to be executed * @param maxRows * DOCUMENT ME! * @param packet * DOCUMENT ME! * @param resultSetType * DOCUMENT ME! * @param resultSetConcurrency * DOCUMENT ME! * @param streamResults * DOCUMENT ME! * @param queryIsSelectOnly * DOCUMENT ME! * @param catalog * DOCUMENT ME! * @param unpackFields * DOCUMENT ME! * @return a ResultSet holding the results * @exception SQLException * if a database error occurs */ // ResultSet execSQL(Statement callingStatement, String sql, // int maxRowsToRetreive, String catalog) throws SQLException { // return execSQL(callingStatement, sql, maxRowsToRetreive, null, // java.sql.ResultSet.TYPE_FORWARD_ONLY, // DEFAULT_RESULT_SET_CONCURRENCY, catalog); // } // ResultSet execSQL(Statement callingStatement, String sql, int maxRows, // int resultSetType, int resultSetConcurrency, boolean streamResults, // boolean queryIsSelectOnly, String catalog, boolean unpackFields) throws // SQLException { // return execSQL(callingStatement, sql, maxRows, null, resultSetType, // resultSetConcurrency, streamResults, queryIsSelectOnly, catalog, // unpackFields); // } public ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows, Buffer packet, int resultSetType, int resultSetConcurrency, boolean streamResults, String catalog, Field[] cachedMetadata) throws SQLException { return execSQL(callingStatement, sql, maxRows, packet, resultSetType, resultSetConcurrency, streamResults, catalog, cachedMetadata, false); } public ResultSetInternalMethods execSQL(StatementImpl callingStatement, String sql, int maxRows, Buffer packet, int resultSetType, int resultSetConcurrency, boolean streamResults, String catalog, Field[] cachedMetadata, boolean isBatch) throws SQLException { synchronized (getConnectionMutex()) { // // Fall-back if the master is back online if we've // issued queriesBeforeRetryMaster queries since // we failed over // long queryStartTime = 0; int endOfQueryPacketPosition = 0; if (packet != null) { endOfQueryPacketPosition = packet.getPosition(); } if (getGatherPerformanceMetrics()) { queryStartTime = System.currentTimeMillis(); } this.lastQueryFinishedTime = 0; // we're busy! if ((getHighAvailability()) && (this.autoCommit || getAutoReconnectForPools()) && this.needsPing && !isBatch) { try { pingInternal(false, 0); this.needsPing = false; } catch (Exception Ex) { createNewIO(true); } } try { if (packet == null) { String encoding = null; if (getUseUnicode()) { encoding = getEncoding(); } return this.io.sqlQueryDirect(callingStatement, sql, encoding, null, maxRows, resultSetType, resultSetConcurrency, streamResults, catalog, cachedMetadata); } return this.io.sqlQueryDirect(callingStatement, null, null, packet, maxRows, resultSetType, resultSetConcurrency, streamResults, catalog, cachedMetadata); } catch (java.sql.SQLException sqlE) { // don't clobber SQL exceptions if (getDumpQueriesOnException()) { String extractedSql = extractSqlFromPacket(sql, packet, endOfQueryPacketPosition); StringBuffer messageBuf = new StringBuffer(extractedSql .length() + 32); messageBuf .append("\n\nQuery being executed when exception was thrown:\n"); messageBuf.append(extractedSql); messageBuf.append("\n\n"); sqlE = appendMessageToException(sqlE, messageBuf.toString(), getExceptionInterceptor()); } if ((getHighAvailability())) { this.needsPing = true; } else { String sqlState = sqlE.getSQLState(); if ((sqlState != null) && sqlState .equals(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)) { cleanup(sqlE); } } throw sqlE; } catch (Exception ex) { if (getHighAvailability()) { this.needsPing = true; } else if (ex instanceof IOException) { cleanup(ex); } SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.UnexpectedException"), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } finally { if (getMaintainTimeStats()) { this.lastQueryFinishedTime = System.currentTimeMillis(); } if (getGatherPerformanceMetrics()) { long queryTime = System.currentTimeMillis() - queryStartTime; registerQueryExecutionTime(queryTime); } } } } public String extractSqlFromPacket(String possibleSqlQuery, Buffer queryPacket, int endOfQueryPacketPosition) throws SQLException { String extractedSql = null; if (possibleSqlQuery != null) { if (possibleSqlQuery.length() > getMaxQuerySizeToLog()) { StringBuffer truncatedQueryBuf = new StringBuffer( possibleSqlQuery.substring(0, getMaxQuerySizeToLog())); truncatedQueryBuf.append(Messages.getString("MysqlIO.25")); extractedSql = truncatedQueryBuf.toString(); } else { extractedSql = possibleSqlQuery; } } if (extractedSql == null) { // This is probably from a client-side prepared // statement int extractPosition = endOfQueryPacketPosition; boolean truncated = false; if (endOfQueryPacketPosition > getMaxQuerySizeToLog()) { extractPosition = getMaxQuerySizeToLog(); truncated = true; } extractedSql = StringUtils.toString(queryPacket.getByteBuffer(), 5, (extractPosition - 5)); if (truncated) { extractedSql += Messages.getString("MysqlIO.25"); //$NON-NLS-1$ } } return extractedSql; } public StringBuffer generateConnectionCommentBlock(StringBuffer buf) { buf.append("/* conn id "); buf.append(getId()); buf.append(" clock: "); buf.append(System.currentTimeMillis()); buf.append(" */ "); return buf; } public int getActiveStatementCount() { // Might not have one of these if // not tracking open resources if (this.openStatements != null) { synchronized (this.openStatements) { return this.openStatements.size(); } } return 0; } /** * Gets the current auto-commit state * * @return Current state of auto-commit * @exception SQLException * if an error occurs * @see setAutoCommit */ public boolean getAutoCommit() throws SQLException { synchronized (getConnectionMutex()) { return this.autoCommit; } } /** * Optimization to only use one calendar per-session, or calculate it for * each call, depending on user configuration */ public Calendar getCalendarInstanceForSessionOrNew() { if (getDynamicCalendars()) { return Calendar.getInstance(); } return getSessionLockedCalendar(); } /** * Return the connections current catalog name, or null if no catalog name * is set, or we dont support catalogs. * <p> * <b>Note:</b> MySQL's notion of catalogs are individual databases. * </p> * * @return the current catalog name or null * @exception SQLException * if a database access error occurs */ public String getCatalog() throws SQLException { synchronized (getConnectionMutex()) { return this.database; } } /** * @return Returns the characterSetMetadata. */ public String getCharacterSetMetadata() { synchronized (getConnectionMutex()) { return this.characterSetMetadata; } } /** * Returns the locally mapped instance of a charset converter (to avoid * overhead of static synchronization). * * @param javaEncodingName * the encoding name to retrieve * @return a character converter, or null if one couldn't be mapped. */ public SingleByteCharsetConverter getCharsetConverter( String javaEncodingName) throws SQLException { if (javaEncodingName == null) { return null; } if (this.usePlatformCharsetConverters) { return null; // we'll use Java's built-in routines for this // they're finally fast enough } SingleByteCharsetConverter converter = null; synchronized (this.charsetConverterMap) { Object asObject = this.charsetConverterMap .get(javaEncodingName); if (asObject == CHARSET_CONVERTER_NOT_AVAILABLE_MARKER) { return null; } converter = (SingleByteCharsetConverter)asObject; if (converter == null) { try { converter = SingleByteCharsetConverter.getInstance( javaEncodingName, this); if (converter == null) { this.charsetConverterMap.put(javaEncodingName, CHARSET_CONVERTER_NOT_AVAILABLE_MARKER); } else { this.charsetConverterMap.put(javaEncodingName, converter); } } catch (UnsupportedEncodingException unsupEncEx) { this.charsetConverterMap.put(javaEncodingName, CHARSET_CONVERTER_NOT_AVAILABLE_MARKER); converter = null; } } } return converter; } /** * Returns the Java character encoding name for the given MySQL server * charset index * * @param charsetIndex * @return the Java character encoding name for the given MySQL server * charset index * @throws SQLException * if the character set index isn't known by the driver */ public String getCharsetNameForIndex(int charsetIndex) throws SQLException { String charsetName = null; if (getUseOldUTF8Behavior()) { return getEncoding(); } if (charsetIndex != MysqlDefs.NO_CHARSET_INFO) { try { charsetName = this.indexToJavaCharset.get(charsetIndex); // checking against static maps if no custom charset found if (charsetName==null) charsetName = CharsetMapping.INDEX_TO_CHARSET[charsetIndex]; if (this.characterEncodingIsAliasForSjis) { if ("sjis".equalsIgnoreCase(charsetName) || "MS932".equalsIgnoreCase(charsetName) /* for JDK6 */) { // Use our encoding so that code pages like Cp932 work charsetName = getEncoding(); } } } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) { throw SQLError.createSQLException( "Unknown character set index for field '" + charsetIndex + "' received from server.", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } // Punt if (charsetName == null) { charsetName = getEncoding(); } } else { charsetName = getEncoding(); } return charsetName; } /** * DOCUMENT ME! * * @return Returns the defaultTimeZone. */ public TimeZone getDefaultTimeZone() { return this.defaultTimeZone; } public String getErrorMessageEncoding() { return errorMessageEncoding; } /** * @see Connection#getHoldability() */ public int getHoldability() throws SQLException { return java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT; } public long getId() { return this.connectionId; } /** * NOT JDBC-Compliant, but clients can use this method to determine how long * this connection has been idle. This time (reported in milliseconds) is * updated once a query has completed. * * @return number of ms that this connection has been idle, 0 if the driver * is busy retrieving results. */ public long getIdleFor() { synchronized (getConnectionMutex()) { if (this.lastQueryFinishedTime == 0) { return 0; } long now = System.currentTimeMillis(); long idleTime = now - this.lastQueryFinishedTime; return idleTime; } } /** * Returns the IO channel to the server * * @return the IO channel to the server * @throws SQLException * if the connection is closed. */ public MysqlIO getIO() throws SQLException { if ((this.io == null) || this.isClosed) { throw SQLError.createSQLException( "Operation not allowed on closed connection", SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); } return this.io; } /** * Returns the log mechanism that should be used to log information from/for * this Connection. * * @return the Log instance to use for logging messages. * @throws SQLException * if an error occurs */ public Log getLog() throws SQLException { return this.log; } public int getMaxBytesPerChar(String javaCharsetName) throws SQLException { return getMaxBytesPerChar(null, javaCharsetName); } public int getMaxBytesPerChar(Integer charsetIndex, String javaCharsetName) throws SQLException { String charset = null; try { // if we can get it by charsetIndex just doing it // getting charset name from dynamic maps in connection; // we do it before checking against static maps because custom charset on server // can be mapped to index from our static map key's diapason charset = this.indexToCustomMysqlCharset.get(charsetIndex); // checking against static maps if no custom charset found if (charset==null) charset = CharsetMapping.STATIC_INDEX_TO_MYSQL_CHARSET_MAP.get(charsetIndex); // if we didn't find charset name by index if (charset == null) { charset = CharsetMapping.getMysqlEncodingForJavaEncoding(javaCharsetName, this); if ((this.io.serverCharsetIndex == 33) && (versionMeetsMinimum(5, 5, 3)) && (javaCharsetName.equalsIgnoreCase("UTF-8"))) { //Avoid UTF8mb4 charset = "utf8"; } } // checking against dynamic maps in connection Integer mblen = this.mysqlCharsetToCustomMblen.get(charset); // checking against static maps if (mblen == null) mblen = CharsetMapping.STATIC_CHARSET_TO_NUM_BYTES_MAP.get(charset); if (mblen == null) mblen = CharsetMapping.STATIC_4_0_CHARSET_TO_NUM_BYTES_MAP.get(charset); if (mblen != null) return mblen.intValue(); } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } return 1; // we don't know } /** * A connection's database is able to provide information describing its * tables, its supported SQL grammar, its stored procedures, the * capabilities of this connection, etc. This information is made available * through a DatabaseMetaData object. * * @return a DatabaseMetaData object for this connection * @exception SQLException * if a database access error occurs */ public java.sql.DatabaseMetaData getMetaData() throws SQLException { return getMetaData(true, true); } private java.sql.DatabaseMetaData getMetaData(boolean checkClosed, boolean checkForInfoSchema) throws SQLException { if (checkClosed) { checkClosed(); } return com.mysql.jdbc.DatabaseMetaData.getInstance(getLoadBalanceSafeProxy(), this.database, checkForInfoSchema); } public java.sql.Statement getMetadataSafeStatement() throws SQLException { java.sql.Statement stmt = createStatement(); if (stmt.getMaxRows() != 0) { stmt.setMaxRows(0); } stmt.setEscapeProcessing(false); if (stmt.getFetchSize() != 0) { stmt.setFetchSize(0); } return stmt; } /** * Returns the packet buffer size the MySQL server reported upon connection * * @return DOCUMENT ME! */ public int getNetBufferLength() { return this.netBufferLength; } /** * Returns the server's character set * * @return the server's character set. */ public String getServerCharacterEncoding() { if (this.io.versionMeetsMinimum(4, 1, 0)) { String charset = this.indexToCustomMysqlCharset.get(this.io.serverCharsetIndex); if (charset == null) charset = CharsetMapping.STATIC_INDEX_TO_MYSQL_CHARSET_MAP.get(this.io.serverCharsetIndex); return charset != null ? charset : this.serverVariables.get("character_set_server"); } return this.serverVariables.get("character_set"); } public int getServerMajorVersion() { return this.io.getServerMajorVersion(); } public int getServerMinorVersion() { return this.io.getServerMinorVersion(); } public int getServerSubMinorVersion() { return this.io.getServerSubMinorVersion(); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public TimeZone getServerTimezoneTZ() { return this.serverTimezoneTZ; } public String getServerVariable(String variableName) { if (this.serverVariables != null) { return this.serverVariables.get(variableName); } return null; } public String getServerVersion() { return this.io.getServerVersion(); } public Calendar getSessionLockedCalendar() { return this.sessionCalendar; } /** * Get this Connection's current transaction isolation mode. * * @return the current TRANSACTION_ mode value * @exception SQLException * if a database access error occurs */ public int getTransactionIsolation() throws SQLException { synchronized (getConnectionMutex()) { if (this.hasIsolationLevels && !getUseLocalSessionState()) { java.sql.Statement stmt = null; java.sql.ResultSet rs = null; try { stmt = getMetadataSafeStatement(); String query = null; int offset = 0; if (versionMeetsMinimum(4, 0, 3)) { query = "SELECT @@session.tx_isolation"; offset = 1; } else { query = "SHOW VARIABLES LIKE 'transaction_isolation'"; offset = 2; } rs = stmt.executeQuery(query); if (rs.next()) { String s = rs.getString(offset); if (s != null) { Integer intTI = mapTransIsolationNameToValue.get(s); if (intTI != null) { return intTI.intValue(); } } throw SQLError.createSQLException( "Could not map transaction isolation '" + s + " to a valid JDBC level.", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } throw SQLError.createSQLException( "Could not retrieve transaction isolation level from server", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } finally { if (rs != null) { try { rs.close(); } catch (Exception ex) { // ignore ; } rs = null; } if (stmt != null) { try { stmt.close(); } catch (Exception ex) { // ignore ; } stmt = null; } } } return this.isolationLevel; } } /** * JDBC 2.0 Get the type-map object associated with this connection. By * default, the map returned is empty. * * @return the type map * @throws SQLException * if a database error occurs */ public java.util.Map<String,Class<?>> getTypeMap() throws SQLException { synchronized (getConnectionMutex()) { if (this.typeMap == null) { this.typeMap = new HashMap<String,Class<?>>(); } return this.typeMap; } } public String getURL() { return this.myURL; } public String getUser() { return this.user; } public Calendar getUtcCalendar() { return this.utcCalendar; } /** * The first warning reported by calls on this Connection is returned. * <B>Note:</B> Sebsequent warnings will be changed to this * java.sql.SQLWarning * * @return the first java.sql.SQLWarning or null * @exception SQLException * if a database access error occurs */ public SQLWarning getWarnings() throws SQLException { return null; } public boolean hasSameProperties(Connection c) { return this.props.equals(c.getProperties()); } public Properties getProperties() { return this.props; } public boolean hasTriedMaster() { return this.hasTriedMasterFlag; } public void incrementNumberOfPreparedExecutes() { if (getGatherPerformanceMetrics()) { this.numberOfPreparedExecutes++; // We need to increment this, because // server-side prepared statements bypass // any execution by the connection itself... this.numberOfQueriesIssued++; } } public void incrementNumberOfPrepares() { if (getGatherPerformanceMetrics()) { this.numberOfPrepares++; } } public void incrementNumberOfResultSetsCreated() { if (getGatherPerformanceMetrics()) { this.numberOfResultSetsCreated++; } } /** * Initializes driver properties that come from URL or properties passed to * the driver manager. * * @param info * DOCUMENT ME! * @throws SQLException * DOCUMENT ME! */ private void initializeDriverProperties(Properties info) throws SQLException { initializeProperties(info); String exceptionInterceptorClasses = getExceptionInterceptors(); if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) { this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses); this.exceptionInterceptor.init(this, info); } this.usePlatformCharsetConverters = getUseJvmCharsetConverters(); this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor()); if (getProfileSql() || getUseUsageAdvisor()) { this.eventSink = ProfilerEventHandlerFactory.getInstance(getLoadBalanceSafeProxy()); } if (getCachePreparedStatements()) { createPreparedStatementCaches(); } if (getNoDatetimeStringSync() && getUseTimezone()) { throw SQLError.createSQLException( "Can't enable noDatetimeStringSync and useTimezone configuration " + "properties at the same time", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE, getExceptionInterceptor()); } if (getCacheCallableStatements()) { this.parsedCallableStatementCache = new LRUCache( getCallableStatementCacheSize()); } if (getAllowMultiQueries()) { setCacheResultSetMetadata(false); // we don't handle this yet } if (getCacheResultSetMetadata()) { this.resultSetMetadataCache = new LRUCache( getMetadataCacheSize()); } } /** * Sets varying properties that depend on server information. Called once we * have connected to the server. * * @param info * DOCUMENT ME! * @throws SQLException * DOCUMENT ME! */ private void initializePropsFromServer() throws SQLException { String connectionInterceptorClasses = getConnectionLifecycleInterceptors(); this.connectionLifecycleInterceptors = null; if (connectionInterceptorClasses != null) { this.connectionLifecycleInterceptors = Util.loadExtensions(this, this.props, connectionInterceptorClasses, "Connection.badLifecycleInterceptor", getExceptionInterceptor()); } setSessionVariables(); // // the "boolean" type didn't come along until MySQL-4.1 // if (!versionMeetsMinimum(4, 1, 0)) { setTransformedBitIsBoolean(false); } this.parserKnowsUnicode = versionMeetsMinimum(4, 1, 0); // // Users can turn off detection of server-side prepared statements // if (getUseServerPreparedStmts() && versionMeetsMinimum(4, 1, 0)) { this.useServerPreparedStmts = true; if (versionMeetsMinimum(5, 0, 0) && !versionMeetsMinimum(5, 0, 3)) { this.useServerPreparedStmts = false; // 4.1.2+ style prepared // statements // don't work on these versions } } // // If version is greater than 3.21.22 get the server // variables. if (versionMeetsMinimum(3, 21, 22)) { loadServerVariables(); if (versionMeetsMinimum(5, 0, 2)) { this.autoIncrementIncrement = getServerVariableAsInt("auto_increment_increment", 1); } else { this.autoIncrementIncrement = 1; } buildCollationMapping(); LicenseConfiguration.checkLicenseType(this.serverVariables); String lowerCaseTables = this.serverVariables.get("lower_case_table_names"); this.lowerCaseTableNames = "on".equalsIgnoreCase(lowerCaseTables) || "1".equalsIgnoreCase(lowerCaseTables) || "2".equalsIgnoreCase(lowerCaseTables); this.storesLowerCaseTableName = "1".equalsIgnoreCase(lowerCaseTables) || "on".equalsIgnoreCase(lowerCaseTables); configureTimezone(); if (this.serverVariables.containsKey("max_allowed_packet")) { int serverMaxAllowedPacket = getServerVariableAsInt("max_allowed_packet", -1); // use server value if maxAllowedPacket hasn't been given, or max_allowed_packet is smaller if (serverMaxAllowedPacket != -1 && (serverMaxAllowedPacket < getMaxAllowedPacket() || getMaxAllowedPacket() <= 0)) setMaxAllowedPacket(serverMaxAllowedPacket); else if (serverMaxAllowedPacket == -1 && getMaxAllowedPacket() == -1) setMaxAllowedPacket(65535); int preferredBlobSendChunkSize = getBlobSendChunkSize(); int allowedBlobSendChunkSize = Math.min(preferredBlobSendChunkSize, getMaxAllowedPacket()) - ServerPreparedStatement.BLOB_STREAM_READ_BUF_SIZE - 11 /* LONG_DATA and MySQLIO packet header size */; setBlobSendChunkSize(String.valueOf(allowedBlobSendChunkSize)); } if (this.serverVariables.containsKey("net_buffer_length")) { this.netBufferLength = getServerVariableAsInt("net_buffer_length", 16 * 1024); } checkTransactionIsolationLevel(); if (!versionMeetsMinimum(4, 1, 0)) { checkServerEncoding(); } this.io.checkForCharsetMismatch(); if (this.serverVariables.containsKey("sql_mode")) { int sqlMode = 0; String sqlModeAsString = this.serverVariables.get("sql_mode"); try { sqlMode = Integer.parseInt(sqlModeAsString); } catch (NumberFormatException nfe) { // newer versions of the server has this as a string-y // list... sqlMode = 0; if (sqlModeAsString != null) { if (sqlModeAsString.indexOf("ANSI_QUOTES") != -1) { sqlMode |= 4; } if (sqlModeAsString.indexOf("NO_BACKSLASH_ESCAPES") != -1) { this.noBackslashEscapes = true; } } } if ((sqlMode & 4) > 0) { this.useAnsiQuotes = true; } else { this.useAnsiQuotes = false; } } } try { this.errorMessageEncoding = CharsetMapping.getCharacterEncodingForErrorMessages(this); } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } boolean overrideDefaultAutocommit = isAutoCommitNonDefaultOnServer(); configureClientCharacterSet(false); if (versionMeetsMinimum(3, 23, 15)) { this.transactionsSupported = true; if (!overrideDefaultAutocommit) { try { setAutoCommit(true); // to override anything // the server is set to...reqd // by JDBC spec. } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } } } else { this.transactionsSupported = false; } if (versionMeetsMinimum(3, 23, 36)) { this.hasIsolationLevels = true; } else { this.hasIsolationLevels = false; } this.hasQuotedIdentifiers = versionMeetsMinimum(3, 23, 6); this.io.resetMaxBuf(); // // If we're using MySQL 4.1.0 or newer, we need to figure // out what character set metadata will be returned in, // and then map that to a Java encoding name. // // We've already set it, and it might be different than what // was originally on the server, which is why we use the // "special" key to retrieve it if (this.io.versionMeetsMinimum(4, 1, 0)) { String characterSetResultsOnServerMysql = this.serverVariables.get(JDBC_LOCAL_CHARACTER_SET_RESULTS); if (characterSetResultsOnServerMysql == null || StringUtils.startsWithIgnoreCaseAndWs( characterSetResultsOnServerMysql, "NULL") || characterSetResultsOnServerMysql.length() == 0) { String defaultMetadataCharsetMysql = this.serverVariables.get("character_set_system"); String defaultMetadataCharset = null; if (defaultMetadataCharsetMysql != null) { defaultMetadataCharset = getJavaEncodingForMysqlEncoding(defaultMetadataCharsetMysql); } else { defaultMetadataCharset = "UTF-8"; } this.characterSetMetadata = defaultMetadataCharset; } else { this.characterSetResultsOnServer = getJavaEncodingForMysqlEncoding(characterSetResultsOnServerMysql); this.characterSetMetadata = this.characterSetResultsOnServer; } } else { this.characterSetMetadata = getEncoding(); } // // Query cache is broken wrt. multi-statements before MySQL-4.1.10 // if (versionMeetsMinimum(4, 1, 0) && !this.versionMeetsMinimum(4, 1, 10) && getAllowMultiQueries()) { if (isQueryCacheEnabled()) { setAllowMultiQueries(false); } } if (versionMeetsMinimum(5, 0, 0) && (getUseLocalTransactionState() || getElideSetAutoCommits()) && isQueryCacheEnabled() && !versionMeetsMinimum(6, 0, 10)) { // Can't trust the server status flag on the wire if query cache is enabled, // due to Bug#36326 setUseLocalTransactionState(false); setElideSetAutoCommits(false); } // // Server can do this more efficiently for us // setupServerForTruncationChecks(); } private boolean isQueryCacheEnabled() { return "ON".equalsIgnoreCase(this.serverVariables.get("query_cache_type")) && !"0".equalsIgnoreCase(this.serverVariables.get("query_cache_size")); } private int getServerVariableAsInt(String variableName, int fallbackValue) throws SQLException { try { return Integer.parseInt(this.serverVariables.get(variableName)); } catch (NumberFormatException nfe) { getLog().logWarn(Messages.getString("Connection.BadValueInServerVariables", new Object[] {variableName, this.serverVariables.get(variableName), Integer.valueOf(fallbackValue)})); return fallbackValue; } } /** * Has the default autocommit value of 0 been changed on the server * via init_connect? * * @return true if autocommit is not the default of '0' on the server. * * @throws SQLException */ private boolean isAutoCommitNonDefaultOnServer() throws SQLException { boolean overrideDefaultAutocommit = false; String initConnectValue = this.serverVariables.get("init_connect"); if (versionMeetsMinimum(4, 1, 2) && initConnectValue != null && initConnectValue.length() > 0) { if (!getElideSetAutoCommits()) { // auto-commit might have changed java.sql.ResultSet rs = null; java.sql.Statement stmt = null; try { stmt = getMetadataSafeStatement(); rs = stmt.executeQuery("SELECT @@session.autocommit"); if (rs.next()) { this.autoCommit = rs.getBoolean(1); if (this.autoCommit != true) { overrideDefaultAutocommit = true; } } } finally { if (rs != null) { try { rs.close(); } catch (SQLException sqlEx) { // do nothing } } if (stmt != null) { try { stmt.close(); } catch (SQLException sqlEx) { // do nothing } } } } else { if (this.getIO().isSetNeededForAutoCommitMode(true)) { // we're not in standard autocommit=true mode this.autoCommit = false; overrideDefaultAutocommit = true; } } } return overrideDefaultAutocommit; } public boolean isClientTzUTC() { return this.isClientTzUTC; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public boolean isClosed() { return this.isClosed; } public boolean isCursorFetchEnabled() throws SQLException { return (versionMeetsMinimum(5, 0, 2) && getUseCursorFetch()); } public boolean isInGlobalTx() { return this.isInGlobalTx; } /** * Is this connection connected to the first host in the list if * there is a list of servers in the URL? * * @return true if this connection is connected to the first in * the list. */ public boolean isMasterConnection() { synchronized (getConnectionMutex()) { return false; // handled higher up } } /** * Is the server in a sql_mode that doesn't allow us to use \\ to escape * things? * * @return Returns the noBackslashEscapes. */ public boolean isNoBackslashEscapesSet() { return this.noBackslashEscapes; } public boolean isReadInfoMsgEnabled() { return this.readInfoMsg; } /** * Tests to see if the connection is in Read Only Mode. Note that prior to 5.6, * we cannot really put the database in read only mode, but we pretend we can by * returning the value of the readOnly flag * * @return true if the connection is read only * @exception SQLException if a database access error occurs */ public boolean isReadOnly() throws SQLException { return isReadOnly(true); } /** * Tests to see if the connection is in Read Only Mode. Note that prior to 5.6, * we cannot really put the database in read only mode, but we pretend we can by * returning the value of the readOnly flag * * @param useSessionStatus in some cases, for example when restoring connection with autoReconnect=true, * we can rely only on saved readOnly state, so use useSessionStatus=false in that case * * @return true if the connection is read only * @exception SQLException if a database access error occurs */ public boolean isReadOnly(boolean useSessionStatus) throws SQLException { if (useSessionStatus && !this.isClosed && versionMeetsMinimum(5, 6, 5) && !getUseLocalSessionState()) { java.sql.Statement stmt = null; java.sql.ResultSet rs = null; try { try { stmt = getMetadataSafeStatement(); rs = stmt.executeQuery("select @@session.tx_read_only"); if (rs.next()) { return rs.getInt(1) != 0; // mysql has a habit of tri+ state booleans } } catch (SQLException ex1) { if (ex1.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw SQLError.createSQLException( "Could not retrieve transation read-only status server", SQLError.SQL_STATE_GENERAL_ERROR, ex1, getExceptionInterceptor()); } } } finally { if (rs != null) { try { rs.close(); } catch (Exception ex) { // ignore } rs = null; } if (stmt != null) { try { stmt.close(); } catch (Exception ex) { // ignore } stmt = null; } } } return this.readOnly; } public boolean isRunningOnJDK13() { return this.isRunningOnJDK13; } public boolean isSameResource(Connection otherConnection) { synchronized (getConnectionMutex()) { if (otherConnection == null) { return false; } boolean directCompare = true; String otherHost = ((ConnectionImpl)otherConnection).origHostToConnectTo; String otherOrigDatabase = ((ConnectionImpl)otherConnection).origDatabaseToConnectTo; String otherCurrentCatalog = ((ConnectionImpl)otherConnection).database; if (!nullSafeCompare(otherHost, this.origHostToConnectTo)) { directCompare = false; } else if (otherHost != null && otherHost.indexOf(',') == -1 && otherHost.indexOf(':') == -1) { // need to check port numbers directCompare = (((ConnectionImpl)otherConnection).origPortToConnectTo == this.origPortToConnectTo); } if (directCompare) { if (!nullSafeCompare(otherOrigDatabase, this.origDatabaseToConnectTo)) { directCompare = false; directCompare = false; } else if (!nullSafeCompare(otherCurrentCatalog, this.database)) { directCompare = false; } } if (directCompare) { return true; } // Has the user explicitly set a resourceId? String otherResourceId = ((ConnectionImpl)otherConnection).getResourceId(); String myResourceId = getResourceId(); if (otherResourceId != null || myResourceId != null) { directCompare = nullSafeCompare(otherResourceId, myResourceId); if (directCompare) { return true; } } return false; } } public boolean isServerTzUTC() { return this.isServerTzUTC; } private boolean usingCachedConfig = false; private void createConfigCacheIfNeeded() throws SQLException { synchronized (getConnectionMutex()) { if (this.serverConfigCache != null) { return; } try { Class<?> factoryClass; factoryClass = Class.forName(getServerConfigCacheFactory()); @SuppressWarnings("unchecked") CacheAdapterFactory<String, Map<String, String>> cacheFactory = ((CacheAdapterFactory<String, Map<String, String>>)factoryClass.newInstance()); this.serverConfigCache = cacheFactory.getInstance(this, myURL, Integer.MAX_VALUE, Integer.MAX_VALUE, props); ExceptionInterceptor evictOnCommsError = new ExceptionInterceptor() { public void init(Connection conn, Properties config) throws SQLException { } public void destroy() { } @SuppressWarnings("synthetic-access") public SQLException interceptException(SQLException sqlEx, Connection conn) { if (sqlEx.getSQLState() != null && sqlEx.getSQLState().startsWith("08")) { serverConfigCache.invalidate(getURL()); } return null; }}; if (this.exceptionInterceptor == null) { this.exceptionInterceptor = evictOnCommsError; } else { ((ExceptionInterceptorChain)this.exceptionInterceptor).addRingZero(evictOnCommsError); } } catch (ClassNotFoundException e) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.CantFindCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}), getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } catch (InstantiationException e) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}), getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } catch (IllegalAccessException e) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.CantLoadCacheFactory", new Object[] {getParseInfoCacheFactory(), "parseInfoCacheFactory"}), getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } } } private final static String SERVER_VERSION_STRING_VAR_NAME = "server_version_string"; /** * Loads the result of 'SHOW VARIABLES' into the serverVariables field so * that the driver can configure itself. * * @throws SQLException * if the 'SHOW VARIABLES' query fails for any reason. */ private void loadServerVariables() throws SQLException { if (getCacheServerConfiguration()) { createConfigCacheIfNeeded(); Map<String, String> cachedVariableMap = serverConfigCache.get(getURL()); if (cachedVariableMap != null) { String cachedServerVersion = cachedVariableMap.get(SERVER_VERSION_STRING_VAR_NAME); if (cachedServerVersion != null && this.io.getServerVersion() != null && cachedServerVersion.equals(this.io.getServerVersion())) { this.serverVariables = cachedVariableMap; this.usingCachedConfig = true; return; } serverConfigCache.invalidate(getURL()); } } java.sql.Statement stmt = null; java.sql.ResultSet results = null; try { stmt = getMetadataSafeStatement(); String version = this.dbmd.getDriverVersion(); if (version != null && version.indexOf('*') != -1) { StringBuffer buf = new StringBuffer(version.length() + 10); for (int i = 0; i < version.length(); i++) { char c = version.charAt(i); if (c == '*') { buf.append("[star]"); } else { buf.append(c); } } version = buf.toString(); } String versionComment = (this.getParanoid() || version == null) ? "" : "/* " + version + " */"; String query = versionComment + "SHOW VARIABLES"; if (versionMeetsMinimum(5, 0, 3)) { query = versionComment + "SHOW VARIABLES WHERE Variable_name ='language'" + " OR Variable_name = 'net_write_timeout'" + " OR Variable_name = 'interactive_timeout'" + " OR Variable_name = 'wait_timeout'" + " OR Variable_name = 'character_set_client'" + " OR Variable_name = 'character_set_connection'" + " OR Variable_name = 'character_set'" + " OR Variable_name = 'character_set_server'" + " OR Variable_name = 'tx_isolation'" + " OR Variable_name = 'transaction_isolation'" + " OR Variable_name = 'character_set_results'" + " OR Variable_name = 'timezone'" + " OR Variable_name = 'time_zone'" + " OR Variable_name = 'system_time_zone'" + " OR Variable_name = 'lower_case_table_names'" + " OR Variable_name = 'max_allowed_packet'" + " OR Variable_name = 'net_buffer_length'" + " OR Variable_name = 'sql_mode'" + " OR Variable_name = 'query_cache_type'" + " OR Variable_name = 'query_cache_size'" + " OR Variable_name = 'init_connect'"; } this.serverVariables = new HashMap<String, String>(); try { results = stmt.executeQuery(query); while (results.next()) { this.serverVariables.put(results.getString(1), results .getString(2)); } results.close(); results = null; } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } if (versionMeetsMinimum(5, 0, 2)) { try { results = stmt.executeQuery(versionComment + "SELECT @@session.auto_increment_increment"); if (results.next()) { this.serverVariables.put("auto_increment_increment", results.getString(1)); } } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || getDisconnectOnExpiredPasswords()) { throw ex; } } } if (getCacheServerConfiguration()) { this.serverVariables.put(SERVER_VERSION_STRING_VAR_NAME, this.io.getServerVersion()); serverConfigCache.put(getURL(), this.serverVariables); this.usingCachedConfig = true; } } catch (SQLException e) { throw e; } finally { if (results != null) { try { results.close(); } catch (SQLException sqlE) { ; } } if (stmt != null) { try { stmt.close(); } catch (SQLException sqlE) { ; } } } } private int autoIncrementIncrement = 0; public int getAutoIncrementIncrement() { return this.autoIncrementIncrement; } /** * Is the server configured to use lower-case table names only? * * @return true if lower_case_table_names is 'on' */ public boolean lowerCaseTableNames() { return this.lowerCaseTableNames; } /** * Has the maxRows value changed? * * @param stmt * DOCUMENT ME! */ public void maxRowsChanged(Statement stmt) { synchronized (getConnectionMutex()) { if (this.statementsUsingMaxRows == null) { this.statementsUsingMaxRows = new HashMap<Statement, Statement>(); } this.statementsUsingMaxRows.put(stmt, stmt); this.maxRowsChanged = true; } } /** * A driver may convert the JDBC sql grammar into its system's native SQL * grammar prior to sending it; nativeSQL returns the native form of the * statement that the driver would have sent. * * @param sql * a SQL statement that may contain one or more '?' parameter * placeholders * @return the native form of this statement * @exception SQLException * if a database access error occurs */ public String nativeSQL(String sql) throws SQLException { if (sql == null) { return null; } Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, serverSupportsConvertFn(), getLoadBalanceSafeProxy()); if (escapedSqlResult instanceof String) { return (String) escapedSqlResult; } return ((EscapeProcessorResult) escapedSqlResult).escapedSql; } private CallableStatement parseCallableStatement(String sql) throws SQLException { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, serverSupportsConvertFn(), getLoadBalanceSafeProxy()); boolean isFunctionCall = false; String parsedSql = null; if (escapedSqlResult instanceof EscapeProcessorResult) { parsedSql = ((EscapeProcessorResult) escapedSqlResult).escapedSql; isFunctionCall = ((EscapeProcessorResult) escapedSqlResult).callingStoredFunction; } else { parsedSql = (String) escapedSqlResult; isFunctionCall = false; } return CallableStatement.getInstance(getLoadBalanceSafeProxy(), parsedSql, this.database, isFunctionCall); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public boolean parserKnowsUnicode() { return this.parserKnowsUnicode; } /** * Detect if the connection is still good * * @throws SQLException * if the ping fails */ public void ping() throws SQLException { pingInternal(true, 0); } public void pingInternal(boolean checkForClosedConnection, int timeoutMillis) throws SQLException { if (checkForClosedConnection) { checkClosed(); } long pingMillisLifetime = getSelfDestructOnPingSecondsLifetime(); int pingMaxOperations = getSelfDestructOnPingMaxOperations(); if ((pingMillisLifetime > 0 && (System.currentTimeMillis() - this.connectionCreationTimeMillis) > pingMillisLifetime) || (pingMaxOperations > 0 && pingMaxOperations <= this.io .getCommandCount())) { close(); throw SQLError.createSQLException(Messages .getString("Connection.exceededConnectionLifetime"), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor()); } // Need MySQL-3.22.1, but who uses anything older!? this.io.sendCommand(MysqlDefs.PING, null, null, false, null, timeoutMillis); } /** * DOCUMENT ME! * * @param sql * DOCUMENT ME! * @return DOCUMENT ME! * @throws SQLException * DOCUMENT ME! */ public java.sql.CallableStatement prepareCall(String sql) throws SQLException { return prepareCall(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } /** * JDBC 2.0 Same as prepareCall() above, but allows the default result set * type and result set concurrency type to be overridden. * * @param sql * the SQL representing the callable statement * @param resultSetType * a result set type, see ResultSet.TYPE_XXX * @param resultSetConcurrency * a concurrency type, see ResultSet.CONCUR_XXX * @return a new CallableStatement object containing the pre-compiled SQL * statement * @exception SQLException * if a database-access error occurs. */ public java.sql.CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { if (versionMeetsMinimum(5, 0, 0)) { CallableStatement cStmt = null; if (!getCacheCallableStatements()) { cStmt = parseCallableStatement(sql); } else { synchronized (this.parsedCallableStatementCache) { CompoundCacheKey key = new CompoundCacheKey(getCatalog(), sql); CallableStatement.CallableStatementParamInfo cachedParamInfo = (CallableStatement.CallableStatementParamInfo) this.parsedCallableStatementCache .get(key); if (cachedParamInfo != null) { cStmt = CallableStatement.getInstance(getLoadBalanceSafeProxy(), cachedParamInfo); } else { cStmt = parseCallableStatement(sql); synchronized (cStmt) { cachedParamInfo = cStmt.paramInfo; } this.parsedCallableStatementCache.put(key, cachedParamInfo); } } } cStmt.setResultSetType(resultSetType); cStmt.setResultSetConcurrency(resultSetConcurrency); return cStmt; } throw SQLError.createSQLException("Callable statements not " + "supported.", SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); } /** * @see Connection#prepareCall(String, int, int, int) */ public java.sql.CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (getPedantic()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException( "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } CallableStatement cStmt = (com.mysql.jdbc.CallableStatement) prepareCall( sql, resultSetType, resultSetConcurrency); return cStmt; } /** * A SQL statement with or without IN parameters can be pre-compiled and * stored in a PreparedStatement object. This object can then be used to * efficiently execute this statement multiple times. * <p> * <B>Note:</B> This method is optimized for handling parametric SQL * statements that benefit from precompilation if the driver supports * precompilation. In this case, the statement is not sent to the database * until the PreparedStatement is executed. This has no direct effect on * users; however it does affect which method throws certain * java.sql.SQLExceptions * </p> * <p> * MySQL does not support precompilation of statements, so they are handled * by the driver. * </p> * * @param sql * a SQL statement that may contain one or more '?' IN parameter * placeholders * @return a new PreparedStatement object containing the pre-compiled * statement. * @exception SQLException * if a database access error occurs. */ public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } /** * @see Connection#prepareStatement(String, int) */ public java.sql.PreparedStatement prepareStatement(String sql, int autoGenKeyIndex) throws SQLException { java.sql.PreparedStatement pStmt = prepareStatement(sql); ((com.mysql.jdbc.PreparedStatement) pStmt) .setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS); return pStmt; } /** * JDBC 2.0 Same as prepareStatement() above, but allows the default result * set type and result set concurrency type to be overridden. * * @param sql * the SQL query containing place holders * @param resultSetType * a result set type, see ResultSet.TYPE_XXX * @param resultSetConcurrency * a concurrency type, see ResultSet.CONCUR_XXX * @return a new PreparedStatement object containing the pre-compiled SQL * statement * @exception SQLException * if a database-access error occurs. */ public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); // // FIXME: Create warnings if can't create results of the given // type or concurrency // PreparedStatement pStmt = null; boolean canServerPrepare = true; String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) { canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); } if (this.useServerPreparedStmts && canServerPrepare) { if (this.getCachePreparedStatements()) { synchronized (this.serverSideStatementCache) { pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql); if (pStmt != null) { ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false); pStmt.clearParameters(); } if (pStmt == null) { try { pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); if (sql.length() < getPreparedStatementCacheSqlLimit()) { ((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true; } pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } catch (SQLException sqlEx) { // Punt, if necessary if (getEmulateUnsupportedPstmts()) { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); if (sql.length() < getPreparedStatementCacheSqlLimit()) { this.serverSideStatementCheckCache.put(sql, Boolean.FALSE); } } else { throw sqlEx; } } } } } else { try { pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } catch (SQLException sqlEx) { // Punt, if necessary if (getEmulateUnsupportedPstmts()) { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } else { throw sqlEx; } } } } else { pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } return pStmt; } } /** * @see Connection#prepareStatement(String, int, int, int) */ public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (getPedantic()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException( "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } return prepareStatement(sql, resultSetType, resultSetConcurrency); } /** * @see Connection#prepareStatement(String, int[]) */ public java.sql.PreparedStatement prepareStatement(String sql, int[] autoGenKeyIndexes) throws SQLException { java.sql.PreparedStatement pStmt = prepareStatement(sql); ((com.mysql.jdbc.PreparedStatement) pStmt) .setRetrieveGeneratedKeys((autoGenKeyIndexes != null) && (autoGenKeyIndexes.length > 0)); return pStmt; } /** * @see Connection#prepareStatement(String, String[]) */ public java.sql.PreparedStatement prepareStatement(String sql, String[] autoGenKeyColNames) throws SQLException { java.sql.PreparedStatement pStmt = prepareStatement(sql); ((com.mysql.jdbc.PreparedStatement) pStmt) .setRetrieveGeneratedKeys((autoGenKeyColNames != null) && (autoGenKeyColNames.length > 0)); return pStmt; } /** * Closes connection and frees resources. * * @param calledExplicitly * is this being called from close() * @param issueRollback * should a rollback() be issued? * @throws SQLException * if an error occurs */ public void realClose(boolean calledExplicitly, boolean issueRollback, boolean skipLocalTeardown, Throwable reason) throws SQLException { SQLException sqlEx = null; if (this.isClosed()) { return; } this.forceClosedReason = reason; try { if (!skipLocalTeardown) { if (!getAutoCommit() && issueRollback) { try { rollback(); } catch (SQLException ex) { sqlEx = ex; } } reportMetrics(); if (getUseUsageAdvisor()) { if (!calledExplicitly) { String message = "Connection implicitly closed by Driver. You should call Connection.close() from your code to free resources more efficiently and avoid resource leaks."; this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$ this.getCatalog(), this.getId(), -1, -1, System .currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, message)); } long connectionLifeTime = System.currentTimeMillis() - this.connectionCreationTimeMillis; if (connectionLifeTime < 500) { 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."; this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$ this.getCatalog(), this.getId(), -1, -1, System .currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, message)); } } try { closeAllOpenStatements(); } catch (SQLException ex) { sqlEx = ex; } if (this.io != null) { try { this.io.quit(); } catch (Exception e) { ; } } } else { this.io.forceClose(); } if (this.statementInterceptors != null) { for (int i = 0; i < this.statementInterceptors.size(); i++) { this.statementInterceptors.get(i).destroy(); } } if (this.exceptionInterceptor != null) { this.exceptionInterceptor.destroy(); } } finally { this.openStatements = null; this.io = null; this.statementInterceptors = null; this.exceptionInterceptor = null; ProfilerEventHandlerFactory.removeInstance(this); synchronized (getConnectionMutex()) { if (this.cancelTimer != null) { this.cancelTimer.cancel(); } } this.isClosed = true; } if (sqlEx != null) { throw sqlEx; } } public void recachePreparedStatement(ServerPreparedStatement pstmt) throws SQLException { synchronized (getConnectionMutex()) { if (pstmt.isPoolable()) { synchronized (this.serverSideStatementCache) { this.serverSideStatementCache.put(pstmt.originalSql, pstmt); } } } } /** * DOCUMENT ME! * * @param queryTimeMs */ public void registerQueryExecutionTime(long queryTimeMs) { if (queryTimeMs > this.longestQueryTimeMs) { this.longestQueryTimeMs = queryTimeMs; repartitionPerformanceHistogram(); } addToPerformanceHistogram(queryTimeMs, 1); if (queryTimeMs < this.shortestQueryTimeMs) { this.shortestQueryTimeMs = (queryTimeMs == 0) ? 1 : queryTimeMs; } this.numberOfQueriesIssued++; this.totalQueryTimeMs += queryTimeMs; } /** * Register a Statement instance as open. * * @param stmt * the Statement instance to remove */ public void registerStatement(Statement stmt) { synchronized (this.openStatements) { this.openStatements.put(stmt, stmt); } } /** * @see Connection#releaseSavepoint(Savepoint) */ public void releaseSavepoint(Savepoint arg0) throws SQLException { // this is a no-op } private void repartitionHistogram(int[] histCounts, long[] histBreakpoints, long currentLowerBound, long currentUpperBound) { if (oldHistCounts == null) { oldHistCounts = new int[histCounts.length]; oldHistBreakpoints = new long[histBreakpoints.length]; } System.arraycopy(histCounts, 0, oldHistCounts, 0, histCounts.length); System.arraycopy(histBreakpoints, 0, oldHistBreakpoints, 0, histBreakpoints.length); createInitialHistogram(histBreakpoints, currentLowerBound, currentUpperBound); for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { addToHistogram(histCounts, histBreakpoints, oldHistBreakpoints[i], oldHistCounts[i], currentLowerBound, currentUpperBound); } } private void repartitionPerformanceHistogram() { checkAndCreatePerformanceHistogram(); repartitionHistogram(this.perfMetricsHistCounts, this.perfMetricsHistBreakpoints, this.shortestQueryTimeMs == Long.MAX_VALUE ? 0 : this.shortestQueryTimeMs, this.longestQueryTimeMs); } private void repartitionTablesAccessedHistogram() { checkAndCreateTablesAccessedHistogram(); repartitionHistogram(this.numTablesMetricsHistCounts, this.numTablesMetricsHistBreakpoints, this.minimumNumberTablesAccessed == Long.MAX_VALUE ? 0 : this.minimumNumberTablesAccessed, this.maximumNumberTablesAccessed); } private void reportMetrics() { if (getGatherPerformanceMetrics()) { StringBuffer logMessage = new StringBuffer(256); logMessage.append("** Performance Metrics Report **\n"); logMessage.append("\nLongest reported query: " + this.longestQueryTimeMs + " ms"); logMessage.append("\nShortest reported query: " + this.shortestQueryTimeMs + " ms"); logMessage .append("\nAverage query execution time: " + (this.totalQueryTimeMs / this.numberOfQueriesIssued) + " ms"); logMessage.append("\nNumber of statements executed: " + this.numberOfQueriesIssued); logMessage.append("\nNumber of result sets created: " + this.numberOfResultSetsCreated); logMessage.append("\nNumber of statements prepared: " + this.numberOfPrepares); logMessage.append("\nNumber of prepared statement executions: " + this.numberOfPreparedExecutes); if (this.perfMetricsHistBreakpoints != null) { logMessage.append("\n\n\tTiming Histogram:\n"); int maxNumPoints = 20; int highestCount = Integer.MIN_VALUE; for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) { if (this.perfMetricsHistCounts[i] > highestCount) { highestCount = this.perfMetricsHistCounts[i]; } } if (highestCount == 0) { highestCount = 1; // avoid DIV/0 } for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) { if (i == 0) { logMessage.append("\n\tless than " + this.perfMetricsHistBreakpoints[i + 1] + " ms: \t" + this.perfMetricsHistCounts[i]); } else { logMessage.append("\n\tbetween " + this.perfMetricsHistBreakpoints[i] + " and " + this.perfMetricsHistBreakpoints[i + 1] + " ms: \t" + this.perfMetricsHistCounts[i]); } logMessage.append("\t"); int numPointsToGraph = (int) (maxNumPoints * ((double) this.perfMetricsHistCounts[i] / (double) highestCount)); for (int j = 0; j < numPointsToGraph; j++) { logMessage.append("*"); } if (this.longestQueryTimeMs < this.perfMetricsHistCounts[i + 1]) { break; } } if (this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this.longestQueryTimeMs) { logMessage.append("\n\tbetween "); logMessage .append(this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]); logMessage.append(" and "); logMessage .append(this.perfMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]); logMessage.append(" ms: \t"); logMessage .append(this.perfMetricsHistCounts[HISTOGRAM_BUCKETS - 1]); } } if (this.numTablesMetricsHistBreakpoints != null) { logMessage.append("\n\n\tTable Join Histogram:\n"); int maxNumPoints = 20; int highestCount = Integer.MIN_VALUE; for (int i = 0; i < (HISTOGRAM_BUCKETS); i++) { if (this.numTablesMetricsHistCounts[i] > highestCount) { highestCount = this.numTablesMetricsHistCounts[i]; } } if (highestCount == 0) { highestCount = 1; // avoid DIV/0 } for (int i = 0; i < (HISTOGRAM_BUCKETS - 1); i++) { if (i == 0) { logMessage.append("\n\t" + this.numTablesMetricsHistBreakpoints[i + 1] + " tables or less: \t\t" + this.numTablesMetricsHistCounts[i]); } else { logMessage.append("\n\tbetween " + this.numTablesMetricsHistBreakpoints[i] + " and " + this.numTablesMetricsHistBreakpoints[i + 1] + " tables: \t" + this.numTablesMetricsHistCounts[i]); } logMessage.append("\t"); int numPointsToGraph = (int) (maxNumPoints * ((double) this.numTablesMetricsHistCounts[i] / (double) highestCount)); for (int j = 0; j < numPointsToGraph; j++) { logMessage.append("*"); } if (this.maximumNumberTablesAccessed < this.numTablesMetricsHistBreakpoints[i + 1]) { break; } } if (this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2] < this.maximumNumberTablesAccessed) { logMessage.append("\n\tbetween "); logMessage .append(this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 2]); logMessage.append(" and "); logMessage .append(this.numTablesMetricsHistBreakpoints[HISTOGRAM_BUCKETS - 1]); logMessage.append(" tables: "); logMessage .append(this.numTablesMetricsHistCounts[HISTOGRAM_BUCKETS - 1]); } } this.log.logInfo(logMessage); this.metricsLastReportedMs = System.currentTimeMillis(); } } /** * Reports currently collected metrics if this feature is enabled and the * timeout has passed. */ protected void reportMetricsIfNeeded() { if (getGatherPerformanceMetrics()) { if ((System.currentTimeMillis() - this.metricsLastReportedMs) > getReportMetricsIntervalMillis()) { reportMetrics(); } } } public void reportNumberOfTablesAccessed(int numTablesAccessed) { if (numTablesAccessed < this.minimumNumberTablesAccessed) { this.minimumNumberTablesAccessed = numTablesAccessed; } if (numTablesAccessed > this.maximumNumberTablesAccessed) { this.maximumNumberTablesAccessed = numTablesAccessed; repartitionTablesAccessedHistogram(); } addToTablesAccessedHistogram(numTablesAccessed, 1); } /** * Resets the server-side state of this connection. Doesn't work for MySQL * versions older than 4.0.6 or if isParanoid() is set (it will become a * no-op in these cases). Usually only used from connection pooling code. * * @throws SQLException * if the operation fails while resetting server state. */ public void resetServerState() throws SQLException { if (!getParanoid() && ((this.io != null) && versionMeetsMinimum(4, 0, 6))) { changeUser(this.user, this.password); } } /** * The method rollback() drops all changes made since the previous * commit/rollback and releases any database locks currently held by the * Connection. * * @exception SQLException * if a database access error occurs * @see commit */ public void rollback() throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor)each).rollback()) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } // no-op if _relaxAutoCommit == true if (this.autoCommit && !getRelaxAutoCommit()) { throw SQLError.createSQLException( "Can't call rollback when autocommit=true", SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); } else if (this.transactionsSupported) { try { rollbackNoChecks(); } catch (SQLException sqlEx) { // We ignore non-transactional tables if told to do so if (getIgnoreNonTxTables() && (sqlEx.getErrorCode() == SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) { return; } throw sqlEx; } } } catch (SQLException sqlException) { if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE .equals(sqlException.getSQLState())) { throw SQLError.createSQLException( "Communications link failure during rollback(). Transaction resolution unknown.", SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlException; } finally { this.needsPing = this.getReconnectAtTxEnd(); } } } /** * @see Connection#rollback(Savepoint) */ public void rollback(final Savepoint savepoint) throws SQLException { synchronized (getConnectionMutex()) { if (versionMeetsMinimum(4, 0, 14) || versionMeetsMinimum(4, 1, 1)) { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor)each).rollback(savepoint)) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } StringBuffer rollbackQuery = new StringBuffer( "ROLLBACK TO SAVEPOINT "); rollbackQuery.append('`'); rollbackQuery.append(savepoint.getSavepointName()); rollbackQuery.append('`'); java.sql.Statement stmt = null; try { stmt = getMetadataSafeStatement(); stmt.executeUpdate(rollbackQuery.toString()); } catch (SQLException sqlEx) { int errno = sqlEx.getErrorCode(); if (errno == 1181) { String msg = sqlEx.getMessage(); if (msg != null) { int indexOfError153 = msg.indexOf("153"); if (indexOfError153 != -1) { throw SQLError.createSQLException("Savepoint '" + savepoint.getSavepointName() + "' does not exist", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, errno, getExceptionInterceptor()); } } } // We ignore non-transactional tables if told to do so if (getIgnoreNonTxTables() && (sqlEx.getErrorCode() != SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) { throw sqlEx; } if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE .equals(sqlEx.getSQLState())) { throw SQLError.createSQLException( "Communications link failure during rollback(). Transaction resolution unknown.", SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlEx; } finally { closeStatement(stmt); } } finally { this.needsPing = this.getReconnectAtTxEnd(); } } else { throw SQLError.notImplemented(); } } } private void rollbackNoChecks() throws SQLException { if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) { if (!this.io.inTransactionOnServer()) { return; // effectively a no-op } } execSQL(null, "rollback", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } /** * @see java.sql.Connection#prepareStatement(String) */ public java.sql.PreparedStatement serverPrepareStatement(String sql) throws SQLException { String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; return ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.getCatalog(), DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } /** * @see java.sql.Connection#prepareStatement(String, int) */ public java.sql.PreparedStatement serverPrepareStatement(String sql, int autoGenKeyIndex) throws SQLException { String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; PreparedStatement pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.getCatalog(), DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); pStmt.setRetrieveGeneratedKeys( autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS); return pStmt; } /** * @see java.sql.Connection#prepareStatement(String, int, int) */ public java.sql.PreparedStatement serverPrepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; return ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.getCatalog(), resultSetType, resultSetConcurrency); } /** * @see java.sql.Connection#prepareStatement(String, int, int, int) */ public java.sql.PreparedStatement serverPrepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (getPedantic()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException( "HOLD_CUSRORS_OVER_COMMIT is only supported holdability level", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } return serverPrepareStatement(sql, resultSetType, resultSetConcurrency); } /** * @see java.sql.Connection#prepareStatement(String, int[]) */ public java.sql.PreparedStatement serverPrepareStatement(String sql, int[] autoGenKeyIndexes) throws SQLException { PreparedStatement pStmt = (PreparedStatement) serverPrepareStatement(sql); pStmt .setRetrieveGeneratedKeys((autoGenKeyIndexes != null) && (autoGenKeyIndexes.length > 0)); return pStmt; } /** * @see java.sql.Connection#prepareStatement(String, String[]) */ public java.sql.PreparedStatement serverPrepareStatement(String sql, String[] autoGenKeyColNames) throws SQLException { PreparedStatement pStmt = (PreparedStatement) serverPrepareStatement(sql); pStmt .setRetrieveGeneratedKeys((autoGenKeyColNames != null) && (autoGenKeyColNames.length > 0)); return pStmt; } public boolean serverSupportsConvertFn() throws SQLException { return versionMeetsMinimum(4, 0, 2); } /** * If a connection is in auto-commit mode, than all its SQL statements will * be executed and committed as individual transactions. Otherwise, its SQL * statements are grouped into transactions that are terminated by either * commit() or rollback(). By default, new connections are in auto- commit * mode. The commit occurs when the statement completes or the next execute * occurs, whichever comes first. In the case of statements returning a * ResultSet, the statement completes when the last row of the ResultSet has * been retrieved or the ResultSet has been closed. In advanced cases, a * single statement may return multiple results as well as output parameter * values. Here the commit occurs when all results and output param values * have been retrieved. * <p> * <b>Note:</b> MySQL does not support transactions, so this method is a * no-op. * </p> * * @param autoCommitFlag - * true enables auto-commit; false disables it * @exception SQLException * if a database access error occurs */ public void setAutoCommit(final boolean autoCommitFlag) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor)each).setAutoCommit(autoCommitFlag)) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } if (getAutoReconnectForPools()) { setHighAvailability(true); } try { if (this.transactionsSupported) { boolean needsSetOnServer = true; if (this.getUseLocalSessionState() && this.autoCommit == autoCommitFlag) { needsSetOnServer = false; } else if (!this.getHighAvailability()) { needsSetOnServer = this.getIO() .isSetNeededForAutoCommitMode(autoCommitFlag); } // this internal value must be set first as failover depends on // it // being set to true to fail over (which is done by most // app servers and connection pools at the end of // a transaction), and the driver issues an implicit set // based on this value when it (re)-connects to a server // so the value holds across connections this.autoCommit = autoCommitFlag; if (needsSetOnServer) { execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } } else { if ((autoCommitFlag == false) && !getRelaxAutoCommit()) { throw SQLError.createSQLException("MySQL Versions Older than 3.23.15 " + "do not support transactions", SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); } this.autoCommit = autoCommitFlag; } } finally { if (this.getAutoReconnectForPools()) { setHighAvailability(false); } } return; } } /** * A sub-space of this Connection's database may be selected by setting a * catalog name. If the driver does not support catalogs, it will silently * ignore this request * <p> * <b>Note:</b> MySQL's notion of catalogs are individual databases. * </p> * * @param catalog * the database for this connection to use * @throws SQLException * if a database access error occurs */ public void setCatalog(final String catalog) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); if (catalog == null) { throw SQLError.createSQLException("Catalog can not be null", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor)each).setCatalog(catalog)) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } if (getUseLocalSessionState()) { if (this.lowerCaseTableNames) { if (this.database.equalsIgnoreCase(catalog)) { return; } } else { if (this.database.equals(catalog)) { return; } } } String quotedId = this.dbmd.getIdentifierQuoteString(); if ((quotedId == null) || quotedId.equals(" ")) { quotedId = ""; } StringBuffer query = new StringBuffer("USE "); query.append(quotedId); query.append(catalog); query.append(quotedId); execSQL(null, query.toString(), -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); this.database = catalog; } } /** * @param failedOver * The failedOver to set. */ public void setFailedOver(boolean flag) { synchronized (getConnectionMutex()) { // handled higher up } } /** * @see Connection#setHoldability(int) */ public void setHoldability(int arg0) throws SQLException { // do nothing } public void setInGlobalTx(boolean flag) { this.isInGlobalTx = flag; } // exposed for testing /** * @param preferSlaveDuringFailover * The preferSlaveDuringFailover to set. */ public void setPreferSlaveDuringFailover(boolean flag) { // no-op, handled further up in the wrapper } public void setReadInfoMsgEnabled(boolean flag) { this.readInfoMsg = flag; } /** * You can put a connection in read-only mode as a hint to enable database * optimizations <B>Note:</B> setReadOnly cannot be called while in the * middle of a transaction * * @param readOnlyFlag - * true enables read-only mode; false disables it * @exception SQLException * if a database access error occurs */ public void setReadOnly(boolean readOnlyFlag) throws SQLException { checkClosed(); setReadOnlyInternal(readOnlyFlag); } public void setReadOnlyInternal(boolean readOnlyFlag) throws SQLException { // note this this is safe even inside a transaction if (versionMeetsMinimum(5, 6, 5)) { if (!getUseLocalSessionState() || (readOnlyFlag != this.readOnly)) { execSQL(null, "set session transaction " + (readOnlyFlag ? "read only" : "read write"), -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } } this.readOnly = readOnlyFlag; } /** * @see Connection#setSavepoint() */ public java.sql.Savepoint setSavepoint() throws SQLException { MysqlSavepoint savepoint = new MysqlSavepoint(getExceptionInterceptor()); setSavepoint(savepoint); return savepoint; } private void setSavepoint(MysqlSavepoint savepoint) throws SQLException { synchronized (getConnectionMutex()) { if (versionMeetsMinimum(4, 0, 14) || versionMeetsMinimum(4, 1, 1)) { checkClosed(); StringBuffer savePointQuery = new StringBuffer("SAVEPOINT "); savePointQuery.append('`'); savePointQuery.append(savepoint.getSavepointName()); savePointQuery.append('`'); java.sql.Statement stmt = null; try { stmt = getMetadataSafeStatement(); stmt.executeUpdate(savePointQuery.toString()); } finally { closeStatement(stmt); } } else { throw SQLError.notImplemented(); } } } /** * @see Connection#setSavepoint(String) */ public java.sql.Savepoint setSavepoint(String name) throws SQLException { synchronized (getConnectionMutex()) { MysqlSavepoint savepoint = new MysqlSavepoint(name, getExceptionInterceptor()); setSavepoint(savepoint); return savepoint; } } /** * */ private void setSessionVariables() throws SQLException { if (this.versionMeetsMinimum(4, 0, 0) && getSessionVariables() != null) { List<String> variablesToSet = StringUtils.split(getSessionVariables(), ",", "\"'", "\"'", false); int numVariablesToSet = variablesToSet.size(); java.sql.Statement stmt = null; try { stmt = getMetadataSafeStatement(); for (int i = 0; i < numVariablesToSet; i++) { String variableValuePair = variablesToSet.get(i); if (variableValuePair.startsWith("@")) { stmt.executeUpdate("SET " + variableValuePair); } else { stmt.executeUpdate("SET SESSION " + variableValuePair); } } } finally { if (stmt != null) { stmt.close(); } } } } /** * DOCUMENT ME! * * @param level * DOCUMENT ME! * @throws SQLException * DOCUMENT ME! */ public void setTransactionIsolation(int level) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); if (this.hasIsolationLevels) { String sql = null; boolean shouldSendSet = false; if (getAlwaysSendSetIsolation()) { shouldSendSet = true; } else { if (level != this.isolationLevel) { shouldSendSet = true; } } if (getUseLocalSessionState()) { shouldSendSet = this.isolationLevel != level; } if (shouldSendSet) { switch (level) { case java.sql.Connection.TRANSACTION_NONE: throw SQLError.createSQLException("Transaction isolation level " + "NONE not supported by MySQL", getExceptionInterceptor()); case java.sql.Connection.TRANSACTION_READ_COMMITTED: sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"; break; case java.sql.Connection.TRANSACTION_READ_UNCOMMITTED: sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; break; case java.sql.Connection.TRANSACTION_REPEATABLE_READ: sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"; break; case java.sql.Connection.TRANSACTION_SERIALIZABLE: sql = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE"; break; default: throw SQLError.createSQLException("Unsupported transaction " + "isolation level '" + level + "'", SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); } execSQL(null, sql, -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY,false, this.database, null, false); this.isolationLevel = level; } } else { throw SQLError.createSQLException("Transaction Isolation Levels are " + "not supported on MySQL versions older than 3.23.36.", SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); } } } /** * JDBC 2.0 Install a type-map object as the default type-map for this * connection * * @param map * the type mapping * @throws SQLException * if a database error occurs. */ public void setTypeMap(java.util.Map<String,Class<?>> map) throws SQLException { synchronized (getConnectionMutex()) { this.typeMap = map; } } private void setupServerForTruncationChecks() throws SQLException { if (getJdbcCompliantTruncation()) { if (versionMeetsMinimum(5, 0, 2)) { String currentSqlMode = this.serverVariables.get("sql_mode"); boolean strictTransTablesIsSet = StringUtils.indexOfIgnoreCase(currentSqlMode, "STRICT_TRANS_TABLES") != -1; if (currentSqlMode == null || currentSqlMode.length() == 0 || !strictTransTablesIsSet) { StringBuffer commandBuf = new StringBuffer("SET sql_mode='"); if (currentSqlMode != null && currentSqlMode.length() > 0) { commandBuf.append(currentSqlMode); commandBuf.append(","); } commandBuf.append("STRICT_TRANS_TABLES'"); execSQL(null, commandBuf.toString(), -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); setJdbcCompliantTruncation(false); // server's handling this for us now } else if (strictTransTablesIsSet) { // We didn't set it, but someone did, so we piggy back on it setJdbcCompliantTruncation(false); // server's handling this for us now } } } } /** * Used by MiniAdmin to shutdown a MySQL server * * @throws SQLException * if the command can not be issued. */ public void shutdownServer() throws SQLException { try { this.io.sendCommand(MysqlDefs.SHUTDOWN, null, null, false, null, 0); } catch (Exception ex) { SQLException sqlEx = SQLError.createSQLException( Messages.getString("Connection.UnhandledExceptionDuringShutdown"), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public boolean supportsIsolationLevel() { return this.hasIsolationLevels; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public boolean supportsQuotedIdentifiers() { return this.hasQuotedIdentifiers; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public boolean supportsTransactions() { return this.transactionsSupported; } /** * Remove the given statement from the list of open statements * * @param stmt * the Statement instance to remove */ public void unregisterStatement(Statement stmt) { if (this.openStatements != null) { synchronized (this.openStatements) { this.openStatements.remove(stmt); } } } /** * Called by statements on their .close() to let the connection know when it * is safe to set the connection back to 'default' row limits. * * @param stmt * the statement releasing it's max-rows requirement * @throws SQLException * if a database error occurs issuing the statement that sets * the limit default. */ public void unsetMaxRows(Statement stmt) throws SQLException { synchronized (getConnectionMutex()) { if (this.statementsUsingMaxRows != null) { Object found = this.statementsUsingMaxRows.remove(stmt); if ((found != null) && (this.statementsUsingMaxRows.size() == 0)) { execSQL(null, "SET SQL_SELECT_LIMIT=DEFAULT", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); this.maxRowsChanged = false; } } } } public boolean useAnsiQuotedIdentifiers() { synchronized (getConnectionMutex()) { return this.useAnsiQuotes; } } /** * Has maxRows() been set? * * @return DOCUMENT ME! */ public boolean useMaxRows() { synchronized (getConnectionMutex()) { return this.maxRowsChanged; } } public boolean versionMeetsMinimum(int major, int minor, int subminor) throws SQLException { checkClosed(); return this.io.versionMeetsMinimum(major, minor, subminor); } /** * Returns cached metadata (or null if not cached) for the given query, * which must match _exactly_. * * This method is synchronized by the caller on getMutex(), so if * calling this method from internal code in the driver, make sure it's * synchronized on the mutex that guards communication with the server. * * @param sql * the query that is the key to the cache * * @return metadata cached for the given SQL, or none if it doesn't * exist. */ public CachedResultSetMetaData getCachedMetaData(String sql) { if (this.resultSetMetadataCache != null) { synchronized (this.resultSetMetadataCache) { return (CachedResultSetMetaData) this.resultSetMetadataCache .get(sql); } } return null; // no cache exists } /** * Caches CachedResultSetMetaData that has been placed in the cache using * the given SQL as a key. * * This method is synchronized by the caller on getMutex(), so if * calling this method from internal code in the driver, make sure it's * synchronized on the mutex that guards communication with the server. * * @param sql the query that the metadata pertains too. * @param cachedMetaData metadata (if it exists) to populate the cache. * @param resultSet the result set to retreive metadata from, or apply to. * * @throws SQLException */ public void initializeResultsMetadataFromCache(String sql, CachedResultSetMetaData cachedMetaData, ResultSetInternalMethods resultSet) throws SQLException { if (cachedMetaData == null) { // read from results cachedMetaData = new CachedResultSetMetaData(); // assume that users will use named-based // lookups resultSet.buildIndexMapping(); resultSet.initializeWithMetadata(); if (resultSet instanceof UpdatableResultSet) { ((UpdatableResultSet)resultSet).checkUpdatability(); } resultSet.populateCachedMetaData(cachedMetaData); this.resultSetMetadataCache.put(sql, cachedMetaData); } else { resultSet.initializeFromCachedMetaData(cachedMetaData); resultSet.initializeWithMetadata(); if (resultSet instanceof UpdatableResultSet) { ((UpdatableResultSet)resultSet).checkUpdatability(); } } } /** * Returns the comment that will be prepended to all statements * sent to the server. * * @return the comment that will be prepended to all statements * sent to the server. */ public String getStatementComment() { return this.statementComment; } /** * Sets the comment that will be prepended to all statements * sent to the server. Do not use slash-star or star-slash tokens * in the comment as these will be added by the driver itself. * * @param comment the comment that will be prepended to all statements * sent to the server. */ public void setStatementComment(String comment) { this.statementComment = comment; } public void reportQueryTime(long millisOrNanos) { synchronized (getConnectionMutex()) { this.queryTimeCount++; this.queryTimeSum += millisOrNanos; this.queryTimeSumSquares += (millisOrNanos * millisOrNanos); this.queryTimeMean = ((this.queryTimeMean * (this.queryTimeCount - 1)) + millisOrNanos) / this.queryTimeCount; } } public boolean isAbonormallyLongQuery(long millisOrNanos) { synchronized (getConnectionMutex()) { if (this.queryTimeCount < 15) { return false; // need a minimum amount for this to make sense } double stddev = Math.sqrt((this.queryTimeSumSquares - ((this.queryTimeSum*this.queryTimeSum) / this.queryTimeCount)) / (this.queryTimeCount - 1)); return millisOrNanos > (this.queryTimeMean + 5 * stddev); } } public void initializeExtension(Extension ex) throws SQLException { ex.init(this, this.props); } public void transactionBegun() throws SQLException { synchronized (getConnectionMutex()) { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { ((ConnectionLifecycleInterceptor)each).transactionBegun(); } }; iter.doForAll(); } } } public void transactionCompleted() throws SQLException { synchronized (getConnectionMutex()) { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { void forEach(Extension each) throws SQLException { ((ConnectionLifecycleInterceptor)each).transactionCompleted(); } }; iter.doForAll(); } } } public boolean storesLowerCaseTableName() { return storesLowerCaseTableName; } private ExceptionInterceptor exceptionInterceptor; public ExceptionInterceptor getExceptionInterceptor() { return this.exceptionInterceptor; } public boolean getRequiresEscapingEncoder() { return requiresEscapingEncoder; } public boolean isServerLocal() throws SQLException { synchronized (getConnectionMutex()) { SocketFactory factory = getIO().socketFactory; if (factory instanceof SocketMetadata) { return ((SocketMetadata)factory).isLocallyConnected(this); } getLog().logWarn(Messages.getString("Connection.NoMetadataOnSocketFactory")); return false; } } // JDBC-4.1 // until we flip catalog/schema, this is a no-op public void setSchema(String schema) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); } } // JDBC-4.1 public String getSchema() throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); return null; } } /** * Terminates an open connection. Calling <code>abort</code> results in: * <ul> * <li>The connection marked as closed * <li>Closes any physical connection to the database * <li>Releases resources used by the connection * <li>Insures that any thread that is currently accessing the connection * will either progress to completion or throw an <code>SQLException</code>. * </ul> * <p> * Calling <code>abort</code> marks the connection closed and releases any * resources. Calling <code>abort</code> on a closed connection is a * no-op. * <p> * It is possible that the aborting and releasing of the resources that are * held by the connection can take an extended period of time. When the * <code>abort</code> method returns, the connection will have been marked as * closed and the <code>Executor</code> that was passed as a parameter to abort * may still be executing tasks to release resources. * <p> * This method checks to see that there is an <code>SQLPermission</code> * object before allowing the method to proceed. If a * <code>SecurityManager</code> exists and its * <code>checkPermission</code> method denies calling <code>abort</code>, * this method throws a * <code>java.lang.SecurityException</code>. * @param executor The <code>Executor</code> implementation which will * be used by <code>abort</code>. * @throws java.sql.SQLException if a database access error occurs or * the {@code executor} is {@code null}, * @throws java.lang.SecurityException if a security manager exists and its * <code>checkPermission</code> method denies calling <code>abort</code> * @see SecurityManager#checkPermission * @see Executor * @since 1.7 */ public void abort(Executor executor) throws SQLException { SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(ABORT_PERM); } if (executor == null) { throw SQLError.createSQLException("Executor can not be null", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } executor.execute(new Runnable() { public void run() { try { abortInternal(); } catch (SQLException e) { throw new RuntimeException(e); } } }); } // JDBC-4.1 public void setNetworkTimeout(Executor executor, final int milliseconds) throws SQLException { synchronized (getConnectionMutex()) { SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(SET_NETWORK_TIMEOUT_PERM); } if (executor == null) { throw SQLError.createSQLException("Executor can not be null", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } checkClosed(); final MysqlIO mysqlIo = this.io; executor.execute(new Runnable() { public void run() { setSocketTimeout(milliseconds); // for re-connects try { mysqlIo.setSocketTimeout(milliseconds); } catch (SQLException e) { throw new RuntimeException(e); } }}); } } // JDBC-4.1 public int getNetworkTimeout() throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); return getSocketTimeout(); } } }