All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mysql.cj.jdbc.ConnectionImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2002, 2024, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by
 * the Free Software Foundation.
 *
 * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in
 * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the
 * separately licensed software that they have either included with the program or referenced in the documentation.
 *
 * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * 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, version 2.0, 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.cj.jdbc;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLPermission;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Struct;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import com.mysql.cj.CacheAdapter;
import com.mysql.cj.CacheAdapterFactory;
import com.mysql.cj.LicenseConfiguration;
import com.mysql.cj.Messages;
import com.mysql.cj.NativeSession;
import com.mysql.cj.NoSubInterceptorWrapper;
import com.mysql.cj.PreparedQuery;
import com.mysql.cj.QueryInfo;
import com.mysql.cj.ServerVersion;
import com.mysql.cj.Session.SessionEventListener;
import com.mysql.cj.conf.HostInfo;
import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm;
import com.mysql.cj.conf.PropertyKey;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.exceptions.CJCommunicationsException;
import com.mysql.cj.exceptions.CJException;
import com.mysql.cj.exceptions.ExceptionFactory;
import com.mysql.cj.exceptions.ExceptionInterceptor;
import com.mysql.cj.exceptions.ExceptionInterceptorChain;
import com.mysql.cj.exceptions.MysqlErrorNumbers;
import com.mysql.cj.exceptions.PasswordExpiredException;
import com.mysql.cj.exceptions.UnableToConnectException;
import com.mysql.cj.interceptors.QueryInterceptor;
import com.mysql.cj.jdbc.exceptions.SQLError;
import com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping;
import com.mysql.cj.jdbc.ha.MultiHostConnectionProxy;
import com.mysql.cj.jdbc.ha.MultiHostMySQLConnection;
import com.mysql.cj.jdbc.interceptors.ConnectionLifecycleInterceptor;
import com.mysql.cj.jdbc.result.CachedResultSetMetaData;
import com.mysql.cj.jdbc.result.CachedResultSetMetaDataImpl;
import com.mysql.cj.jdbc.result.ResultSetFactory;
import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
import com.mysql.cj.jdbc.result.UpdatableResultSet;
import com.mysql.cj.log.ProfilerEvent;
import com.mysql.cj.protocol.ServerSessionStateController;
import com.mysql.cj.protocol.SocksProxySocketFactory;
import com.mysql.cj.protocol.a.NativeProtocol;
import com.mysql.cj.telemetry.TelemetryAttribute;
import com.mysql.cj.telemetry.TelemetryScope;
import com.mysql.cj.telemetry.TelemetrySpan;
import com.mysql.cj.telemetry.TelemetrySpanName;
import com.mysql.cj.util.LRUCache;
import com.mysql.cj.util.StringUtils;
import com.mysql.cj.util.Util;

/**
 * A Connection represents a session with a specific database. Within the context of a Connection, SQL statements are executed and results are returned.
 *
 * 

* 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. *

*/ public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable { private static final long serialVersionUID = 4009476458425101761L; private static final SQLPermission SET_NETWORK_TIMEOUT_PERM = new SQLPermission("setNetworkTimeout"); private static final SQLPermission ABORT_PERM = new SQLPermission("abort"); @Override public String getHost() { return this.session.getHostInfo().getHost(); } private JdbcConnection parentProxy = null; private JdbcConnection topProxy = null; private MultiHostConnectionProxy realProxy = null; private final Lock lock = new ReentrantLock(); @Override public Lock getLock() { return this.lock; } @Override public boolean isProxySet() { return this.topProxy != null; } @Override public void setProxy(JdbcConnection proxy) { if (this.parentProxy == null) { // Only set this once. this.parentProxy = proxy; } this.topProxy = proxy; this.realProxy = this.topProxy instanceof MultiHostMySQLConnection ? ((MultiHostMySQLConnection) proxy).getThisAsProxy() : null; } // this connection has to be proxied when using multi-host settings so that statements get routed to the right physical connection // (works as "logical" connection) private JdbcConnection getProxy() { return this.topProxy != null ? this.topProxy : this; } @Override public JdbcConnection getMultiHostSafeProxy() { return getProxy(); } @Override public JdbcConnection getMultiHostParentProxy() { return this.parentProxy; } @Override public JdbcConnection getActiveMySQLConnection() { return this; } @Override public Lock getConnectionLock() { return this.realProxy != null ? this.realProxy.getLock() : getProxy().getLock(); } /** * Used as a key for caching callable/prepared statements which (may) depend on current database. */ static class CompoundCacheKey { final String componentOne; final String componentTwo; final int hashCode; CompoundCacheKey(String partOne, String partTwo) { this.componentOne = partOne; this.componentTwo = partTwo; int hc = 17; hc = 31 * hc + (this.componentOne != null ? this.componentOne.hashCode() : 0); hc = 31 * hc + (this.componentTwo != null ? this.componentTwo.hashCode() : 0); this.hashCode = hc; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && CompoundCacheKey.class.isAssignableFrom(obj.getClass())) { CompoundCacheKey another = (CompoundCacheKey) obj; if (this.componentOne == null ? another.componentOne == null : this.componentOne.equals(another.componentOne)) { return this.componentTwo == null ? another.componentTwo == null : this.componentTwo.equals(another.componentTwo); } } return false; } @Override public int hashCode() { return this.hashCode; } } /** * Map mysql transaction isolation level name to java.sql.Connection.TRANSACTION_XXX. */ private static Map mapTransIsolationNameToValue = null; static { mapTransIsolationNameToValue = new HashMap<>(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); } protected static Map roundRobinStatsMap; private List connectionLifecycleInterceptors; private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY; private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY; /** * Creates a connection instance. * * @param hostInfo * {@link HostInfo} instance * @return new {@link ConnectionImpl} instance * @throws SQLException * if a database access error occurs */ public static JdbcConnection getInstance(HostInfo hostInfo) throws SQLException { return new ConnectionImpl(hostInfo); } /** A cache of SQL to parsed prepared statement parameters. */ private CacheAdapter queryInfoCache; /** The database we're currently using. */ private String database = null; /** Internal DBMD to use for various database-version specific features */ private DatabaseMetaData dbmd = null; private NativeSession session = null; /** Is this connection associated with a global tx? */ private boolean isInGlobalTx = false; /** isolation level */ private int isolationLevel = java.sql.Connection.TRANSACTION_READ_COMMITTED; /** * An array of currently open statements. * Copy-on-write used here to avoid ConcurrentModificationException when statements unregister themselves while we iterate over the list. */ private final CopyOnWriteArrayList openStatements = new CopyOnWriteArrayList<>(); private LRUCache parsedCallableStatementCache; private final Lock parsedCallableStatementCacheLock = new ReentrantLock(); /** The password we used */ private String password = null; /** Properties for this connection specified by user */ protected Properties props = null; /** Are we in read-only mode? */ private boolean readOnly = false; /** Cache of ResultSet metadata */ protected LRUCache resultSetMetadataCache; protected final Lock resultSetMetadataCacheLock = new ReentrantLock(); /** * The type map for UDTs (not implemented, but used by some third-party * vendors, most notably IBM WebSphere) */ private Map> typeMap; /** The user we're connected as */ private String user = null; private LRUCache serverSideStatementCheckCache; private final Lock serverSideStatementCheckCacheLock = new ReentrantLock(); private LRUCache serverSideStatementCache; private HostInfo origHostInfo; private String origHostToConnectTo; // we don't want to be able to publicly clone this... private int origPortToConnectTo; private List queryInterceptors; protected JdbcPropertySet propertySet; private RuntimeProperty autoReconnectForPools; private RuntimeProperty cachePrepStmts; private RuntimeProperty autoReconnect; private RuntimeProperty useUsageAdvisor; private RuntimeProperty reconnectAtTxEnd; private RuntimeProperty emulateUnsupportedPstmts; private RuntimeProperty ignoreNonTxTables; private RuntimeProperty pedantic; private RuntimeProperty prepStmtCacheSqlLimit; private RuntimeProperty useLocalSessionState; private RuntimeProperty useServerPrepStmts; private RuntimeProperty processEscapeCodesForPrepStmts; private RuntimeProperty useLocalTransactionState; private RuntimeProperty disconnectOnExpiredPasswords; private RuntimeProperty readOnlyPropagatesToServer; protected ResultSetFactory nullStatementResultSetFactory; TelemetrySpan connectionSpan = null; /** * ' * For the delegate only */ protected ConnectionImpl() { } /** * Creates a connection to a MySQL Server. * * @param hostInfo * the {@link HostInfo} instance that contains the host, user and connections attributes for this connection * @exception SQLException * if a database access error occurs */ public ConnectionImpl(HostInfo hostInfo) throws SQLException { try { // Stash away for later, used to clone this connection for Statement.cancel and Statement.setQueryTimeout(). this.origHostInfo = hostInfo; this.origHostToConnectTo = hostInfo.getHost(); this.origPortToConnectTo = hostInfo.getPort(); this.database = hostInfo.getDatabase(); this.user = hostInfo.getUser(); this.password = hostInfo.getPassword(); this.props = hostInfo.exposeAsProperties(); this.propertySet = new JdbcPropertySetImpl(); this.propertySet.initializeProperties(this.props); // We need Session ASAP to get access to central driver functionality this.nullStatementResultSetFactory = new ResultSetFactory(this, null); this.session = new NativeSession(hostInfo, this.propertySet); this.session.addListener(this); // listen for session status changes } catch (CJException e) { throw SQLExceptionsMapping.translateException(e, getExceptionInterceptor()); } this.connectionSpan = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.CONNECTION_CREATE); this.session.getTelemetryHandler().addLinkTarget(this.connectionSpan); try (TelemetryScope scope = this.connectionSpan.makeCurrent()) { this.connectionSpan.setAttribute(TelemetryAttribute.DB_CONNECTION_STRING, getURL()); this.connectionSpan.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); this.connectionSpan.setAttribute(TelemetryAttribute.DB_USER, getUser()); this.connectionSpan.setAttribute(TelemetryAttribute.SERVER_ADDRESS, this.origHostToConnectTo); this.connectionSpan.setAttribute(TelemetryAttribute.SERVER_PORT, this.origPortToConnectTo); this.connectionSpan.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); this.connectionSpan.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); try { String exceptionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.exceptionInterceptors).getStringValue(); if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) { this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses, this.props, this.session.getLog()); } // we can't cache fixed values here because properties are still not initialized with user provided values this.autoReconnectForPools = this.propertySet.getBooleanProperty(PropertyKey.autoReconnectForPools); this.cachePrepStmts = this.propertySet.getBooleanProperty(PropertyKey.cachePrepStmts); this.autoReconnect = this.propertySet.getBooleanProperty(PropertyKey.autoReconnect); this.useUsageAdvisor = this.propertySet.getBooleanProperty(PropertyKey.useUsageAdvisor); this.reconnectAtTxEnd = this.propertySet.getBooleanProperty(PropertyKey.reconnectAtTxEnd); this.emulateUnsupportedPstmts = this.propertySet.getBooleanProperty(PropertyKey.emulateUnsupportedPstmts); this.ignoreNonTxTables = this.propertySet.getBooleanProperty(PropertyKey.ignoreNonTxTables); this.pedantic = this.propertySet.getBooleanProperty(PropertyKey.pedantic); this.prepStmtCacheSqlLimit = this.propertySet.getIntegerProperty(PropertyKey.prepStmtCacheSqlLimit); this.useLocalSessionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalSessionState); this.useServerPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.useServerPrepStmts); this.processEscapeCodesForPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.processEscapeCodesForPrepStmts); this.useLocalTransactionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalTransactionState); this.disconnectOnExpiredPasswords = this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords); this.readOnlyPropagatesToServer = this.propertySet.getBooleanProperty(PropertyKey.readOnlyPropagatesToServer); if (this.cachePrepStmts.getValue()) { createPreparedStatementCaches(); } if (this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) { this.parsedCallableStatementCache = new LRUCache<>(this.propertySet.getIntegerProperty(PropertyKey.callableStmtCacheSize).getValue()); } if (this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue()) { this.propertySet.getProperty(PropertyKey.cacheResultSetMetadata).setValue(false); // we don't handle this yet } if (this.propertySet.getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { this.resultSetMetadataCache = new LRUCache<>(this.propertySet.getIntegerProperty(PropertyKey.metadataCacheSize).getValue()); } if (this.propertySet.getStringProperty(PropertyKey.socksProxyHost).getStringValue() != null) { this.propertySet.getProperty(PropertyKey.socketFactory).setValue(SocksProxySocketFactory.class.getName()); } this.dbmd = getMetaData(false, false); initializeSafeQueryInterceptors(); } catch (CJException e) { throw SQLExceptionsMapping.translateException(e, getExceptionInterceptor()); } try { createNewIO(false); unSafeQueryInterceptors(); AbandonedConnectionCleanupThread.trackConnection(this, getSession().getNetworkResources()); SocketAddress socketAddress = this.session.getRemoteSocketAddress(); if (InetSocketAddress.class.isInstance(socketAddress)) { InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_PEER_ADDRESS, inetSocketAddress.getHostName()); this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_PEER_PORT, inetSocketAddress.getPort()); this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_TRANSPORT, TelemetryAttribute.NETWORK_TRANSPORT_TCP); } if (this.propertySet.getStringProperty(PropertyKey.socketFactory).getValue().equalsIgnoreCase("com.mysql.cj.protocol.NamedPipeSocketFactory")) { this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_TRANSPORT, TelemetryAttribute.NETWORK_TRANSPORT_PIPE); } else if (StringUtils.indexOfIgnoreCase(socketAddress.getClass().getName(), "UNIXSocket") >= 0) { this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_TRANSPORT, TelemetryAttribute.NETWORK_TRANSPORT_UNIX); } } catch (SQLException ex) { cleanup(ex); // don't clobber SQL exceptions throw ex; } catch (Exception ex) { cleanup(ex); throw SQLError.createSQLException( this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() ? Messages.getString("Connection.0") : Messages.getString("Connection.1", new Object[] { this.session.getHostInfo().getHost(), this.session.getHostInfo().getPort() }), MysqlErrorNumbers.SQLSTATE_MYSQL_COMMUNICATION_LINK_FAILURE, ex, getExceptionInterceptor()); } } catch (Throwable t) { this.connectionSpan.setError(t); throw t; } finally { this.connectionSpan.end(); } } @Override public JdbcPropertySet getPropertySet() { return this.propertySet; } @Override public void unSafeQueryInterceptors() throws SQLException { this.queryInterceptors = this.queryInterceptors.stream().map(NoSubInterceptorWrapper.class::cast).map(NoSubInterceptorWrapper::getUnderlyingInterceptor) .collect(Collectors.toCollection(LinkedList::new)); if (this.session != null) { this.session.setQueryInterceptors(this.queryInterceptors); } } @Override public void initializeSafeQueryInterceptors() throws SQLException { this.queryInterceptors = Util .loadClasses(QueryInterceptor.class, this.propertySet.getStringProperty(PropertyKey.queryInterceptors).getStringValue(), "MysqlIo.BadQueryInterceptor", getExceptionInterceptor()) .stream().map(o -> new NoSubInterceptorWrapper(o.init(this, this.props, this.session.getLog()))) .collect(Collectors.toCollection(LinkedList::new)); } @Override public List getQueryInterceptorsInstances() { return this.queryInterceptors; } private boolean canHandleAsServerPreparedStatement(String sql) throws SQLException { if (sql == null || sql.length() == 0) { return true; } if (!this.useServerPrepStmts.getValue()) { return false; } boolean allowMultiQueries = this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue() || this.propertySet.getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue(); if (this.cachePrepStmts.getValue()) { this.serverSideStatementCheckCacheLock.lock(); try { Boolean flag = this.serverSideStatementCheckCache.get(sql); if (flag != null) { return flag.booleanValue(); } boolean canHandle = StringUtils.canHandleAsServerPreparedStatementNoCache(sql, getServerVersion(), allowMultiQueries, this.session.getServerSession().isNoBackslashEscapesSet(), this.session.getServerSession().useAnsiQuotedIdentifiers()); if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) { this.serverSideStatementCheckCache.put(sql, canHandle ? Boolean.TRUE : Boolean.FALSE); } return canHandle; } finally { this.serverSideStatementCheckCacheLock.unlock(); } } return StringUtils.canHandleAsServerPreparedStatementNoCache(sql, getServerVersion(), allowMultiQueries, this.session.getServerSession().isNoBackslashEscapesSet(), this.session.getServerSession().useAnsiQuotedIdentifiers()); } @Override public void changeUser(String userName, String newPassword) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); if (userName == null || userName.equals("")) { userName = ""; } if (newPassword == null) { newPassword = ""; } try { this.session.changeUser(userName, newPassword, this.database); } catch (CJException ex) { // After Bug#16241992 fix the server doesn't return to previous credentials if COM_CHANGE_USER attempt failed. if ("28000".equals(ex.getSQLState())) { cleanup(ex); } throw ex; } this.user = userName; this.password = newPassword; this.session.getServerSession().getCharsetSettings().configurePostHandshake(true); this.session.setSessionVariables(); handleAutoCommitDefaults(); setupServerForTruncationChecks(); } finally { connectionLock.unlock(); } } @Override public void checkClosed() { this.session.checkClosed(); } @Override public void throwConnectionClosedException() throws SQLException { SQLException ex = SQLError.createSQLException(Messages.getString("Connection.2"), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_CONNECTION_DOES_NOT_EXIST, getExceptionInterceptor()); if (this.session.getForceClosedReason() != null) { ex.initCause(this.session.getForceClosedReason()); } throw ex; } /** * Set transaction isolation level to the value received from server if any. * Is called by connectionInit(...) */ private void checkTransactionIsolationLevel() { String s = this.session.getServerSession().getServerVariable("transaction_isolation"); if (s == null) { s = this.session.getServerSession().getServerVariable("tx_isolation"); } if (s != null) { Integer intTI = mapTransIsolationNameToValue.get(s); if (intTI != null) { this.isolationLevel = intTI.intValue(); } } } @Override public void abortInternal() throws SQLException { this.session.forceClose(); } @Override public void cleanup(Throwable whyCleanedUp) { try { if (this.session != null) { if (isClosed()) { this.session.forceClose(); } else { doClose(whyCleanedUp, CloseOption.IMPLICIT, CloseOption.PROPAGATE); } } } catch (SQLException | CJException sqlEx) { // ignore, we're going away. } } @Override public void clearWarnings() throws SQLException { // firstWarning = null; } @Override public java.sql.PreparedStatement clientPrepareStatement(String sql) throws SQLException { return clientPrepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } @Override public java.sql.PreparedStatement clientPrepareStatement(String sql, int autoGenKeyIndex) throws SQLException { java.sql.PreparedStatement pStmt = clientPrepareStatement(sql); ((ClientPreparedStatement) pStmt).setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS); return pStmt; } @Override 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 { String nativeSql = processEscapeCodesIfNeeded && this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; ClientPreparedStatement pStmt = null; if (this.cachePrepStmts.getValue()) { QueryInfo pStmtInfo = this.queryInfoCache.get(nativeSql); if (pStmtInfo == null) { pStmt = ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database); this.queryInfoCache.put(nativeSql, pStmt.getQueryInfo()); } else { pStmt = ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, pStmtInfo); } } else { pStmt = ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database); } pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); return pStmt; } @Override public java.sql.PreparedStatement clientPrepareStatement(String sql, int[] autoGenKeyIndexes) throws SQLException { ClientPreparedStatement pStmt = (ClientPreparedStatement) clientPrepareStatement(sql); pStmt.setRetrieveGeneratedKeys(autoGenKeyIndexes != null && autoGenKeyIndexes.length > 0); return pStmt; } @Override public java.sql.PreparedStatement clientPrepareStatement(String sql, String[] autoGenKeyColNames) throws SQLException { ClientPreparedStatement pStmt = (ClientPreparedStatement) clientPrepareStatement(sql); pStmt.setRetrieveGeneratedKeys(autoGenKeyColNames != null && autoGenKeyColNames.length > 0); return pStmt; } @Override public java.sql.PreparedStatement clientPrepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return clientPrepareStatement(sql, resultSetType, resultSetConcurrency, true); } @Override public void close() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.connectionLifecycleInterceptors != null) { for (ConnectionLifecycleInterceptor cli : this.connectionLifecycleInterceptors) { cli.close(); } } doClose(null, CloseOption.ROLLBACK, CloseOption.PROPAGATE); } finally { connectionLock.unlock(); } } /** * Closes all currently open statements. * * @throws SQLException * if a database access error occurs */ private void closeAllOpenStatements() throws SQLException { SQLException postponedException = null; for (JdbcStatement stmt : this.openStatements) { try { stmt.doClose(CloseOption.IMPLICIT, CloseOption.PROPAGATE, CloseOption.NO_CACHE); } 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; } } @Override public void commit() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock iter = new IterateBlock( this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(ConnectionLifecycleInterceptor each) throws SQLException { if (!each.commit()) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } if (this.session.getServerSession().isAutoCommit()) { throw SQLError.createSQLException(Messages.getString("Connection.3"), getExceptionInterceptor()); } if (this.useLocalTransactionState.getValue()) { if (!this.session.getServerSession().inTransactionOnServer()) { return; // effectively a no-op } } TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.ROLLBACK); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_ROLLBACK); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_ROLLBACK); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); this.session.execSQL(null, "COMMIT", -1, null, false, this.nullStatementResultSetFactory, null, false); } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } catch (SQLException sqlException) { if (MysqlErrorNumbers.SQLSTATE_MYSQL_COMMUNICATION_LINK_FAILURE.equals(sqlException.getSQLState())) { throw SQLError.createSQLException(Messages.getString("Connection.4"), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlException; } finally { this.session.setNeedsPing(this.reconnectAtTxEnd.getValue()); } } finally { connectionLock.unlock(); } return; } @Override public void createNewIO(boolean isForReconnect) { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { // Synchronization Not needed for *new* connections, but definitely 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... try { if (!this.autoReconnect.getValue()) { connectOneTryOnly(isForReconnect); return; } connectWithRetries(isForReconnect); } catch (SQLException ex) { throw ExceptionFactory.createException(UnableToConnectException.class, ex.getMessage(), ex); } } finally { connectionLock.unlock(); } } private void connectWithRetries(boolean isForReconnect) throws SQLException { double timeout = this.propertySet.getIntegerProperty(PropertyKey.initialTimeout).getValue(); boolean connectionGood = false; Exception connectionException = null; for (int attemptCount = 0; attemptCount < this.propertySet.getIntegerProperty(PropertyKey.maxReconnects).getValue() && !connectionGood; attemptCount++) { try { this.session.forceClose(); JdbcConnection c = getProxy(); this.session.connect(this.origHostInfo, this.user, this.password, this.database, getLoginTimeout(), c); pingInternal(false, 0); boolean oldAutoCommit; int oldIsolationLevel; boolean oldReadOnly; String oldDb; Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { // save state from old connection oldAutoCommit = getAutoCommit(); oldIsolationLevel = this.isolationLevel; oldReadOnly = isReadOnly(false); oldDb = getDatabase(); this.session.setQueryInterceptors(this.queryInterceptors); } finally { connectionLock.unlock(); } // Server properties might be different from previous connection, so initialize again... initializePropsFromServer(); if (isForReconnect) { // Restore state from old connection setAutoCommit(oldAutoCommit); setTransactionIsolation(oldIsolationLevel); setDatabase(oldDb); setReadOnly(oldReadOnly); } connectionGood = true; break; } catch (UnableToConnectException rejEx) { close(); this.session.getProtocol().getSocketConnection().forceClose(); } catch (Exception e) { connectionException = e; 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[] { this.propertySet.getIntegerProperty(PropertyKey.maxReconnects).getValue() }), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_SQL_CLIENT_UNABLE_TO_ESTABLISH_SQL_CONNECTION, connectionException, getExceptionInterceptor()); throw chainedEx; } if (this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() && !this.autoReconnect.getValue()) { this.password = null; this.user = null; } if (isForReconnect) { // // Retrieve any 'lost' prepared statements if re-connecting // Iterator statementIter = this.openStatements.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 serverPreparedStatements = null; while (statementIter.hasNext()) { JdbcStatement statementObj = statementIter.next(); if (statementObj instanceof ServerPreparedStatement) { if (serverPreparedStatements == null) { serverPreparedStatements = new Stack<>(); } serverPreparedStatements.add(statementObj); } } if (serverPreparedStatements != null) { while (!serverPreparedStatements.isEmpty()) { ((ServerPreparedStatement) serverPreparedStatements.pop()).rePrepare(); } } } } private void connectOneTryOnly(boolean isForReconnect) throws SQLException { Exception connectionNotEstablishedBecause = null; try { JdbcConnection c = getProxy(); this.session.connect(this.origHostInfo, this.user, this.password, this.database, getLoginTimeout(), c); // save state from old connection boolean oldAutoCommit = getAutoCommit(); int oldIsolationLevel = this.isolationLevel; boolean oldReadOnly = isReadOnly(false); String oldDb = getDatabase(); this.session.setQueryInterceptors(this.queryInterceptors); // Server properties might be different from previous connection, so initialize again... initializePropsFromServer(); if (isForReconnect) { // Restore state from old connection setAutoCommit(oldAutoCommit); setTransactionIsolation(oldIsolationLevel); setDatabase(oldDb); setReadOnly(oldReadOnly); } return; } catch (UnableToConnectException rejEx) { close(); NativeProtocol protocol = this.session.getProtocol(); if (protocol != null) { protocol.getSocketConnection().forceClose(); } throw rejEx; } catch (Exception e) { if ((e instanceof PasswordExpiredException || e instanceof SQLException && ((SQLException) e).getErrorCode() == MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD) && !this.disconnectOnExpiredPasswords.getValue()) { return; } if (this.session != null) { this.session.forceClose(); } connectionNotEstablishedBecause = e; if (e instanceof SQLException) { throw (SQLException) e; } if (e.getCause() != null && e.getCause() instanceof SQLException) { throw (SQLException) e.getCause(); } if (e instanceof CJException) { throw (CJException) e; } SQLException chainedEx = SQLError.createSQLException(Messages.getString("Connection.UnableToConnect"), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_SQL_CLIENT_UNABLE_TO_ESTABLISH_SQL_CONNECTION, getExceptionInterceptor()); chainedEx.initCause(connectionNotEstablishedBecause); throw chainedEx; } } private int getLoginTimeout() { int loginTimeoutSecs = DriverManager.getLoginTimeout(); if (loginTimeoutSecs <= 0) { return 0; } return loginTimeoutSecs * 1000; } private void createPreparedStatementCaches() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { int cacheSize = this.propertySet.getIntegerProperty(PropertyKey.prepStmtCacheSize).getValue(); String queryInfoCacheFactory = this.propertySet.getStringProperty(PropertyKey.queryInfoCacheFactory).getValue(); @SuppressWarnings("unchecked") CacheAdapterFactory cacheFactory = Util.getInstance(CacheAdapterFactory.class, queryInfoCacheFactory, null, null, getExceptionInterceptor()); this.queryInfoCache = cacheFactory.getInstance(connectionLock, this.origHostInfo.getDatabaseUrl(), cacheSize, this.prepStmtCacheSqlLimit.getValue()); if (this.useServerPrepStmts.getValue()) { this.serverSideStatementCheckCache = new LRUCache<>(cacheSize); this.serverSideStatementCache = new LRUCache(cacheSize) { private static final long serialVersionUID = 7692318650375988114L; @Override protected boolean removeEldestEntry(java.util.Map.Entry eldest) { if (this.maxElements <= 1) { return false; } boolean removeIt = super.removeEldestEntry(eldest); if (removeIt) { ServerPreparedStatement ps = eldest.getValue(); ps.isCached = false; ps.setClosed(false); try { ps.doClose(CloseOption.PROPAGATE, CloseOption.NO_CACHE); } catch (SQLException sqlEx) { // punt } } return removeIt; } }; } } finally { connectionLock.unlock(); } } @Override public java.sql.Statement createStatement() throws SQLException { return createStatement(DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } @Override public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { StatementImpl stmt = new StatementImpl(getMultiHostSafeProxy(), this.database); stmt.setResultSetType(resultSetType); stmt.setResultSetConcurrency(resultSetConcurrency); return stmt; } @Override public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (this.pedantic.getValue()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException("HOLD_CUSRORS_OVER_COMMIT is only supported holdability level", MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } return createStatement(resultSetType, resultSetConcurrency); } @Override public int getActiveStatementCount() { return this.openStatements.size(); } @Override public boolean getAutoCommit() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { return this.session.getServerSession().isAutoCommit(); } finally { connectionLock.unlock(); } } @Override public String getCatalog() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { return this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA ? null : this.database; } finally { connectionLock.unlock(); } } @Override public String getCharacterSetMetadata() { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { return this.session.getServerSession().getCharsetSettings().getMetadataEncoding(); } finally { connectionLock.unlock(); } } @Override public int getHoldability() throws SQLException { return java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT; } @Override public long getId() { return this.session.getThreadId(); } /** * 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. */ @Override public long getIdleFor() { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { return this.session.getIdleFor(); } finally { connectionLock.unlock(); } } @Override 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(); } com.mysql.cj.jdbc.DatabaseMetaData dbmeta = com.mysql.cj.jdbc.DatabaseMetaData.getInstance(getMultiHostSafeProxy(), this.database, checkForInfoSchema, this.nullStatementResultSetFactory); if (getSession() != null && getSession().getProtocol() != null) { dbmeta.setMetadataEncoding(getSession().getServerSession().getCharsetSettings().getMetadataEncoding()); dbmeta.setMetadataCollationIndex(getSession().getServerSession().getCharsetSettings().getMetadataCollationIndex()); } return dbmeta; } @Override public java.sql.Statement getMetadataSafeStatement() throws SQLException { return getMetadataSafeStatement(0); } public java.sql.Statement getMetadataSafeStatement(int maxRows) throws SQLException { java.sql.Statement stmt = createStatement(); stmt.setMaxRows(maxRows == -1 ? 0 : maxRows); stmt.setEscapeProcessing(false); if (stmt.getFetchSize() != 0) { stmt.setFetchSize(0); } return stmt; } @Override public ServerVersion getServerVersion() { return this.session.getServerSession().getServerVersion(); } @Override public int getTransactionIsolation() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (!this.useLocalSessionState.getValue()) { String s = this.session.queryServerVariable( versionMeetsMinimum(8, 0, 3) || versionMeetsMinimum(5, 7, 20) && !versionMeetsMinimum(8, 0, 0) ? "@@session.transaction_isolation" : "@@session.tx_isolation"); if (s != null) { Integer intTI = mapTransIsolationNameToValue.get(s); if (intTI != null) { this.isolationLevel = intTI.intValue(); return this.isolationLevel; } throw SQLError.createSQLException(Messages.getString("Connection.12", new Object[] { s }), MysqlErrorNumbers.SQLSTATE_CONNJ_GENERAL_ERROR, getExceptionInterceptor()); } throw SQLError.createSQLException(Messages.getString("Connection.13"), MysqlErrorNumbers.SQLSTATE_CONNJ_GENERAL_ERROR, getExceptionInterceptor()); } return this.isolationLevel; } finally { connectionLock.unlock(); } } @Override public java.util.Map> getTypeMap() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.typeMap == null) { this.typeMap = new HashMap<>(); } return this.typeMap; } finally { connectionLock.unlock(); } } @Override public String getURL() { return this.origHostInfo.getDatabaseUrl(); } @Override public String getUser() { return this.user; } @Override public SQLWarning getWarnings() throws SQLException { return null; } @Override public boolean hasSameProperties(JdbcConnection c) { return this.props.equals(c.getProperties()); } @Override public Properties getProperties() { return this.props; } /** * Sets varying properties that depend on server information. Called once we have connected to the server. * * @throws SQLException * if a database access error occurs */ private void initializePropsFromServer() throws SQLException { String connectionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.connectionLifecycleInterceptors).getStringValue(); this.connectionLifecycleInterceptors = null; if (connectionInterceptorClasses != null) { try { this.connectionLifecycleInterceptors = Util .loadClasses(ConnectionLifecycleInterceptor.class, connectionInterceptorClasses, "Connection.badLifecycleInterceptor", getExceptionInterceptor()) .stream().map(i -> i.init(this, this.props, this.session.getLog())).collect(Collectors.toCollection(LinkedList::new)); } catch (CJException e) { throw SQLExceptionsMapping.translateException(e, getExceptionInterceptor()); } } this.session.setSessionVariables(); this.session.loadServerVariables(getConnectionLock(), this.dbmd.getDriverVersion()); this.autoIncrementIncrement = this.session.getServerSession().getServerVariable("auto_increment_increment", 1); try { LicenseConfiguration.checkLicenseType(this.session.getServerSession().getServerVariables()); } catch (CJException e) { throw SQLError.createSQLException(e.getMessage(), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_SQL_CLIENT_UNABLE_TO_ESTABLISH_SQL_CONNECTION, getExceptionInterceptor()); } this.session.getProtocol().initServerSession(); checkTransactionIsolationLevel(); handleAutoCommitDefaults(); ((com.mysql.cj.jdbc.DatabaseMetaData) this.dbmd).setMetadataEncoding(this.session.getServerSession().getCharsetSettings().getMetadataEncoding()); ((com.mysql.cj.jdbc.DatabaseMetaData) this.dbmd) .setMetadataCollationIndex(this.session.getServerSession().getCharsetSettings().getMetadataCollationIndex()); // // Server can do this more efficiently for us // setupServerForTruncationChecks(); } /** * Resets a default auto-commit value of 0 to 1, as required by JDBC specification. * Takes into account that the default auto-commit value of 0 may have been changed on the server via init_connect. * * @throws SQLException * if a database access error occurs */ private void handleAutoCommitDefaults() throws SQLException { boolean resetAutoCommitDefault = false; // Server Bug#66884 (SERVER_STATUS is always initiated with SERVER_STATUS_AUTOCOMMIT=1) invalidates "elideSetAutoCommits" feature. // TODO Turn this feature back on as soon as the server bug is fixed. Consider making it version specific. // if (!getPropertySet().getBooleanReadableProperty(PropertyKey.elideSetAutoCommits).getValue()) { String initConnectValue = this.session.getServerSession().getServerVariable("init_connect"); if (initConnectValue != null && initConnectValue.length() > 0) { // auto-commit might have changed String s = this.session.queryServerVariable("@@session.autocommit"); if (s != null) { this.session.getServerSession().setAutoCommit(Boolean.parseBoolean(s)); if (!this.session.getServerSession().isAutoCommit()) { resetAutoCommitDefault = true; } } } else { // reset it anyway, the server may have been initialized with --autocommit=0 resetAutoCommitDefault = true; } //} else if (getSession().isSetNeededForAutoCommitMode(true)) { // // we're not in standard autocommit=true mode // this.session.setAutoCommit(false); // resetAutoCommitDefault = true; //} if (resetAutoCommitDefault) { try { setAutoCommit(true); // required by JDBC spec } catch (SQLException ex) { if (ex.getErrorCode() != MysqlErrorNumbers.ER_MUST_CHANGE_PASSWORD || this.disconnectOnExpiredPasswords.getValue()) { throw ex; } } } } @Override public boolean isClosed() { return this.session.isClosed(); } @Override public boolean isInGlobalTx() { return this.isInGlobalTx; } @Override public boolean isSourceConnection() { return false; // handled higher up } @Override public boolean isReadOnly() throws SQLException { return isReadOnly(true); } @Override public boolean isReadOnly(boolean useSessionStatus) throws SQLException { if (useSessionStatus && !this.session.isClosed() && versionMeetsMinimum(5, 6, 5) && !this.useLocalSessionState.getValue() && this.readOnlyPropagatesToServer.getValue()) { String s = this.session.queryServerVariable( versionMeetsMinimum(8, 0, 3) || versionMeetsMinimum(5, 7, 20) && !versionMeetsMinimum(8, 0, 0) ? "@@session.transaction_read_only" : "@@session.tx_read_only"); if (s != null) { return Integer.parseInt(s) != 0; // mysql has a habit of tri+ state booleans } } return this.readOnly; } @Override public boolean isSameResource(JdbcConnection otherConnection) { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (otherConnection == null) { return false; } boolean directCompare = true; String otherHost = ((ConnectionImpl) otherConnection).origHostToConnectTo; String otherOrigDatabase = ((ConnectionImpl) otherConnection).origHostInfo.getDatabase(); String otherCurrentDb = ((ConnectionImpl) otherConnection).database; if (!StringUtils.nullSafeEqual(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 (!StringUtils.nullSafeEqual(otherOrigDatabase, this.origHostInfo.getDatabase()) || !StringUtils.nullSafeEqual(otherCurrentDb, this.database)) { directCompare = false; } } if (directCompare) { return true; } // Has the user explicitly set a resourceId? String otherResourceId = ((ConnectionImpl) otherConnection).getPropertySet().getStringProperty(PropertyKey.resourceId).getValue(); String myResourceId = this.propertySet.getStringProperty(PropertyKey.resourceId).getValue(); if (otherResourceId != null || myResourceId != null) { directCompare = StringUtils.nullSafeEqual(otherResourceId, myResourceId); if (directCompare) { return true; } } return false; } finally { connectionLock.unlock(); } } private int autoIncrementIncrement = 0; @Override public int getAutoIncrementIncrement() { return this.autoIncrementIncrement; } @Override public boolean lowerCaseTableNames() { return this.session.getServerSession().isLowerCaseTableNames(); } @Override public String nativeSQL(String sql) throws SQLException { if (sql == null) { return null; } Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, getMultiHostSafeProxy().getSession().getServerSession().getSessionTimeZone(), getMultiHostSafeProxy().getSession().getServerSession().getCapabilities().serverSupportsFracSecs(), getMultiHostSafeProxy().getSession().getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); if (escapedSqlResult instanceof String) { return (String) escapedSqlResult; } return ((EscapeProcessorResult) escapedSqlResult).escapedSql; } private CallableStatement parseCallableStatement(String sql) throws SQLException { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, getMultiHostSafeProxy().getSession().getServerSession().getSessionTimeZone(), getMultiHostSafeProxy().getSession().getServerSession().getCapabilities().serverSupportsFracSecs(), getMultiHostSafeProxy().getSession().getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); 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(getMultiHostSafeProxy(), parsedSql, this.database, isFunctionCall); } @Override public void ping() throws SQLException { pingInternal(true, 0); } @Override public void pingInternal(boolean checkForClosedConnection, int timeoutMillis) throws SQLException { this.session.ping(checkForClosedConnection, timeoutMillis); } @Override public java.sql.CallableStatement prepareCall(String sql) throws SQLException { return prepareCall(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } @Override public java.sql.CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.ROUTINE_PREPARE); try (TelemetryScope scope = span.makeCurrent()) { CallableStatement cStmt = null; if (!this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) { cStmt = parseCallableStatement(sql); } else { this.parsedCallableStatementCacheLock.lock(); try { CompoundCacheKey key = new CompoundCacheKey(getDatabase(), sql); CallableStatement.CallableStatementParamInfo cachedParamInfo = this.parsedCallableStatementCache.get(key); if (cachedParamInfo != null) { cStmt = CallableStatement.getInstance(getMultiHostSafeProxy(), cachedParamInfo); } else { cStmt = parseCallableStatement(sql); cachedParamInfo = cStmt.paramInfo; this.parsedCallableStatementCache.put(key, cachedParamInfo); } } finally { this.parsedCallableStatementCacheLock.unlock(); } } cStmt.setResultSetType(resultSetType); cStmt.setResultSetConcurrency(resultSetConcurrency); String dbOperation = cStmt.getQueryInfo().getStatementKeyword(); span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); return cStmt; } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } finally { connectionLock.unlock(); } } @Override public java.sql.CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (this.pedantic.getValue()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException(Messages.getString("Connection.17"), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } CallableStatement cStmt = (com.mysql.cj.jdbc.CallableStatement) prepareCall(sql, resultSetType, resultSetConcurrency); return cStmt; } @Override public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } @Override public java.sql.PreparedStatement prepareStatement(String sql, int autoGenKeyIndex) throws SQLException { java.sql.PreparedStatement pStmt = prepareStatement(sql); ((ClientPreparedStatement) pStmt).setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS); return pStmt; } @Override public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_PREPARE); try (TelemetryScope scope = span.makeCurrent()) { // // FIXME: Create warnings if can't create results of the given type or concurrency // ClientPreparedStatement pStmt = null; boolean canServerPrepare = true; String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; if (this.useServerPrepStmts.getValue() && this.emulateUnsupportedPstmts.getValue()) { canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); } if (this.useServerPrepStmts.getValue() && canServerPrepare) { if (this.cachePrepStmts.getValue()) { this.serverSideStatementCheckCacheLock.lock(); try { pStmt = this.serverSideStatementCache.remove(new CompoundCacheKey(this.database, sql)); if (pStmt != null) { ((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).setClosed(false); pStmt.clearParameters(); pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } if (pStmt == null) { try { pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) { ((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).isCacheable = true; } pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } catch (SQLException sqlEx) { // Punt, if necessary if (this.emulateUnsupportedPstmts.getValue()) { pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) { this.serverSideStatementCheckCache.put(sql, Boolean.FALSE); } } else { throw sqlEx; } } } } finally { this.serverSideStatementCheckCacheLock.unlock(); } } else { try { pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); } catch (SQLException sqlEx) { // Punt, if necessary if (this.emulateUnsupportedPstmts.getValue()) { pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } else { throw sqlEx; } } } } else { pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } String dbOperation = pStmt.getQueryInfo().getStatementKeyword(); span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); return pStmt; } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } finally { connectionLock.unlock(); } } @Override public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (this.pedantic.getValue()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException(Messages.getString("Connection.17"), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } return prepareStatement(sql, resultSetType, resultSetConcurrency); } @Override public java.sql.PreparedStatement prepareStatement(String sql, int[] autoGenKeyIndexes) throws SQLException { java.sql.PreparedStatement pStmt = prepareStatement(sql); ((ClientPreparedStatement) pStmt).setRetrieveGeneratedKeys(autoGenKeyIndexes != null && autoGenKeyIndexes.length > 0); return pStmt; } @Override public java.sql.PreparedStatement prepareStatement(String sql, String[] autoGenKeyColNames) throws SQLException { java.sql.PreparedStatement pStmt = prepareStatement(sql); ((ClientPreparedStatement) pStmt).setRetrieveGeneratedKeys(autoGenKeyColNames != null && autoGenKeyColNames.length > 0); return pStmt; } /** * Close the connection and release resources. By default the close is considered explicit, does not roll back the transaction, does not propagate to * dependents and is processed normally (not forced). */ @Override public void doClose(Throwable reason, CloseOption... options) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (isClosed()) { return; } SQLException sqlEx = null; this.session.setForceClosedReason(reason); try { if (this.propertySet.getBooleanProperty(PropertyKey.gatherPerfMetrics).getValue()) { this.session.getProtocol().getMetricsHolder().reportMetrics(this.session.getLog()); } if (this.useUsageAdvisor.getValue()) { if (CloseOption.IMPLICIT.in(options)) { this.session.getProfilerEventHandler().processEvent(ProfilerEvent.TYPE_USAGE, this.session, null, null, 0, new Throwable(), Messages.getString("Connection.18")); } if (System.currentTimeMillis() - this.session.getConnectionCreationTimeMillis() < 500) { this.session.getProfilerEventHandler().processEvent(ProfilerEvent.TYPE_USAGE, this.session, null, null, 0, new Throwable(), Messages.getString("Connection.19")); } } if (!getAutoCommit() && CloseOption.ROLLBACK.in(options)) { try { rollback(); } catch (SQLException e) { sqlEx = e; } } if (CloseOption.PROPAGATE.in(options)) { try { closeAllOpenStatements(); } catch (SQLException ex) { sqlEx = ex; } } if (CloseOption.FORCED.in(options)) { this.session.forceClose(); } else { this.session.quit(); } if (this.queryInterceptors != null) { this.queryInterceptors.forEach(QueryInterceptor::destroy); } if (this.exceptionInterceptor != null) { this.exceptionInterceptor.destroy(); } } finally { this.openStatements.clear(); this.queryInterceptors = null; this.exceptionInterceptor = null; this.nullStatementResultSetFactory = null; this.session.getTelemetryHandler().removeLinkTarget(this.connectionSpan); } if (sqlEx != null) { throw sqlEx; } } finally { connectionLock.unlock(); } } @Override public void recachePreparedStatement(JdbcPreparedStatement pstmt) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.cachePrepStmts.getValue() && pstmt.isPoolable()) { this.serverSideStatementCheckCacheLock.lock(); try { Object oldServerPrepStmt = this.serverSideStatementCache.put( new CompoundCacheKey(pstmt.getCurrentDatabase(), ((PreparedQuery) pstmt.getQuery()).getOriginalSql()), (ServerPreparedStatement) pstmt); if (oldServerPrepStmt != null && oldServerPrepStmt != pstmt) { ((ServerPreparedStatement) oldServerPrepStmt).isCached = false; ((ServerPreparedStatement) oldServerPrepStmt).setClosed(false); ((ServerPreparedStatement) oldServerPrepStmt).doClose(CloseOption.PROPAGATE, CloseOption.NO_CACHE); } } finally { this.serverSideStatementCheckCacheLock.unlock(); } } } finally { connectionLock.unlock(); } } @Override public void decachePreparedStatement(JdbcPreparedStatement pstmt) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.cachePrepStmts.getValue()) { this.serverSideStatementCheckCacheLock.lock(); try { this.serverSideStatementCache.remove(new CompoundCacheKey(pstmt.getCurrentDatabase(), ((PreparedQuery) pstmt.getQuery()).getOriginalSql())); } finally { this.serverSideStatementCheckCacheLock.unlock(); } } } finally { connectionLock.unlock(); } } @Override public void registerStatement(JdbcStatement stmt) { this.openStatements.addIfAbsent(stmt); } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); StringBuilder releaseSavepointQuery = new StringBuilder("RELEASE SAVEPOINT "); releaseSavepointQuery .append(StringUtils.quoteIdentifier(savepoint.getSavepointName(), this.session.getIdentifierQuoteString(), this.pedantic.getValue())); java.sql.Statement stmt = null; try { stmt = getMetadataSafeStatement(); stmt.executeUpdate(releaseSavepointQuery.toString()); } finally { closeStatement(stmt); } } finally { connectionLock.unlock(); } } @Override public void resetServerState() throws SQLException { if (!this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() && this.session != null) { TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.CONNECTION_RESET); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_CONNECTION_STRING, getURL()); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.SERVER_ADDRESS, this.origHostToConnectTo); span.setAttribute(TelemetryAttribute.SERVER_PORT, this.origPortToConnectTo); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); this.session.getServerSession().getCharsetSettings().configurePreHandshake(true); this.session.resetSessionState(); this.session.getServerSession().getCharsetSettings().configurePostHandshake(true); this.session.setSessionVariables(); handleAutoCommitDefaults(); setupServerForTruncationChecks(); } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } } @Override public void rollback() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock iter = new IterateBlock( this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(ConnectionLifecycleInterceptor each) throws SQLException { if (!each.rollback()) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } if (this.session.getServerSession().isAutoCommit()) { throw SQLError.createSQLException(Messages.getString("Connection.20"), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_CONNECTION_DOES_NOT_EXIST, getExceptionInterceptor()); } try { rollbackNoChecks(); } catch (SQLException sqlEx) { // We ignore non-transactional tables if told to do so if (this.ignoreNonTxTables.getInitialValue() && sqlEx.getErrorCode() == MysqlErrorNumbers.ER_WARNING_NOT_COMPLETE_ROLLBACK) { return; } throw sqlEx; } } catch (SQLException sqlException) { if (MysqlErrorNumbers.SQLSTATE_MYSQL_COMMUNICATION_LINK_FAILURE.equals(sqlException.getSQLState())) { throw SQLError.createSQLException(Messages.getString("Connection.21"), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlException; } finally { this.session.setNeedsPing(this.reconnectAtTxEnd.getValue()); } } finally { connectionLock.unlock(); } } @Override public void rollback(final Savepoint savepoint) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock iter = new IterateBlock( this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(ConnectionLifecycleInterceptor each) throws SQLException { if (!each.rollback(savepoint)) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } StringBuilder rollbackQuery = new StringBuilder("ROLLBACK TO SAVEPOINT "); rollbackQuery .append(StringUtils.quoteIdentifier(savepoint.getSavepointName(), this.session.getIdentifierQuoteString(), this.pedantic.getValue())); 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(Messages.getString("Connection.22", new Object[] { savepoint.getSavepointName() }), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, errno, getExceptionInterceptor()); } } } // We ignore non-transactional tables if told to do so if (this.ignoreNonTxTables.getValue() && sqlEx.getErrorCode() != MysqlErrorNumbers.ER_WARNING_NOT_COMPLETE_ROLLBACK) { throw sqlEx; } if (MysqlErrorNumbers.SQLSTATE_MYSQL_COMMUNICATION_LINK_FAILURE.equals(sqlEx.getSQLState())) { throw SQLError.createSQLException(Messages.getString("Connection.23"), MysqlErrorNumbers.SQLSTATE_CONNECTION_EXCEPTION_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlEx; } finally { closeStatement(stmt); } } finally { this.session.setNeedsPing(this.reconnectAtTxEnd.getValue()); } } finally { connectionLock.unlock(); } } private void rollbackNoChecks() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.useLocalTransactionState.getValue()) { if (!this.session.getServerSession().inTransactionOnServer()) { return; // effectively a no-op } } TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.ROLLBACK); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_ROLLBACK); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_ROLLBACK); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); this.session.execSQL(null, "ROLLBACK", -1, null, false, this.nullStatementResultSetFactory, null, false); } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } finally { connectionLock.unlock(); } } @Override public java.sql.PreparedStatement serverPrepareStatement(String sql) throws SQLException { String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; return ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, getDatabase(), DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); } @Override public java.sql.PreparedStatement serverPrepareStatement(String sql, int autoGenKeyIndex) throws SQLException { String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; ClientPreparedStatement pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, getDatabase(), DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY); pStmt.setRetrieveGeneratedKeys(autoGenKeyIndex == java.sql.Statement.RETURN_GENERATED_KEYS); return pStmt; } @Override public java.sql.PreparedStatement serverPrepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; return ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, getDatabase(), resultSetType, resultSetConcurrency); } @Override public java.sql.PreparedStatement serverPrepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (this.pedantic.getValue()) { if (resultSetHoldability != java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT) { throw SQLError.createSQLException(Messages.getString("Connection.17"), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } return serverPrepareStatement(sql, resultSetType, resultSetConcurrency); } @Override public java.sql.PreparedStatement serverPrepareStatement(String sql, int[] autoGenKeyIndexes) throws SQLException { ClientPreparedStatement pStmt = (ClientPreparedStatement) serverPrepareStatement(sql); pStmt.setRetrieveGeneratedKeys(autoGenKeyIndexes != null && autoGenKeyIndexes.length > 0); return pStmt; } @Override public java.sql.PreparedStatement serverPrepareStatement(String sql, String[] autoGenKeyColNames) throws SQLException { ClientPreparedStatement pStmt = (ClientPreparedStatement) serverPrepareStatement(sql); pStmt.setRetrieveGeneratedKeys(autoGenKeyColNames != null && autoGenKeyColNames.length > 0); return pStmt; } @Override public void setAutoCommit(final boolean autoCommitFlag) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); if (this.connectionLifecycleInterceptors != null) { IterateBlock iter = new IterateBlock( this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(ConnectionLifecycleInterceptor each) throws SQLException { if (!each.setAutoCommit(autoCommitFlag)) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } if (this.autoReconnectForPools.getValue()) { this.autoReconnect.setValue(true); } boolean isAutoCommit = this.session.getServerSession().isAutoCommit(); try { boolean needsSetOnServer = true; if (this.useLocalSessionState.getValue() && isAutoCommit == autoCommitFlag) { needsSetOnServer = false; } else if (!this.autoReconnect.getValue()) { needsSetOnServer = getSession().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.session.getServerSession().setAutoCommit(autoCommitFlag); if (needsSetOnServer) { TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "autocommit"); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); this.session.execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, false, this.nullStatementResultSetFactory, null, false); } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } } catch (CJCommunicationsException e) { throw e; } catch (CJException e) { // Reset to current autocommit value in case of an exception different than a communication exception occurs. this.session.getServerSession().setAutoCommit(isAutoCommit); // Update the stacktrace. throw SQLError.createSQLException(e.getMessage(), e.getSQLState(), e.getVendorCode(), e.isTransient(), e, getExceptionInterceptor()); } finally { if (this.autoReconnectForPools.getValue()) { this.autoReconnect.setValue(false); } } return; } finally { connectionLock.unlock(); } } @Override public void setCatalog(final String catalog) throws SQLException { if (this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.CATALOG) { setDatabase(catalog); } } @Override public void setDatabase(final String db) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); if (db == null) { throw SQLError.createSQLException("Database can not be null", MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (this.connectionLifecycleInterceptors != null) { IterateBlock iter = new IterateBlock( this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(ConnectionLifecycleInterceptor each) throws SQLException { if (!each.setDatabase(db)) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } if (this.useLocalSessionState.getValue()) { if (this.session.getServerSession().isLowerCaseTableNames()) { if (this.database.equalsIgnoreCase(db)) { return; } } else if (this.database.equals(db)) { return; } } TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.USE_DATABASE); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, db); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_USE); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_USE + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); String quotedId = this.session.getIdentifierQuoteString(); StringBuilder query = new StringBuilder("USE "); query.append(StringUtils.quoteIdentifier(db, quotedId, this.pedantic.getValue())); this.session.execSQL(null, query.toString(), -1, null, false, this.nullStatementResultSetFactory, null, false); this.database = db; } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } finally { connectionLock.unlock(); } } @Override public String getDatabase() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { return this.database; } finally { connectionLock.unlock(); } } @Override public void setFailedOver(boolean flag) { // handled higher up } @Override public void setHoldability(int arg0) throws SQLException { // do nothing } @Override public void setInGlobalTx(boolean flag) { this.isInGlobalTx = flag; } @Override public void setReadOnly(boolean readOnlyFlag) throws SQLException { setReadOnlyInternal(readOnlyFlag); } @Override public void setReadOnlyInternal(boolean readOnlyFlag) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { // note this this is safe even inside a transaction if (this.readOnlyPropagatesToServer.getValue() && versionMeetsMinimum(5, 6, 5)) { if (!this.useLocalSessionState.getValue() || readOnlyFlag != this.readOnly) { TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_TRANSACTION_ACCESS_MODE, readOnlyFlag ? "read-only" : "read-write"); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); this.session.execSQL(null, "SET SESSION TRANSACTION " + (readOnlyFlag ? "READ ONLY" : "READ WRITE"), -1, null, false, this.nullStatementResultSetFactory, null, false); } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } } this.readOnly = readOnlyFlag; } finally { connectionLock.unlock(); } } @Override public java.sql.Savepoint setSavepoint() throws SQLException { MysqlSavepoint savepoint = new MysqlSavepoint(getExceptionInterceptor()); setSavepoint(savepoint); return savepoint; } private void setSavepoint(MysqlSavepoint savepoint) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); StringBuilder savePointQuery = new StringBuilder("SAVEPOINT "); savePointQuery.append(StringUtils.quoteIdentifier(savepoint.getSavepointName(), this.session.getIdentifierQuoteString(), this.pedantic.getValue())); java.sql.Statement stmt = null; try { stmt = getMetadataSafeStatement(); stmt.executeUpdate(savePointQuery.toString()); } finally { closeStatement(stmt); } } finally { connectionLock.unlock(); } } @Override public java.sql.Savepoint setSavepoint(String name) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { MysqlSavepoint savepoint = new MysqlSavepoint(name, getExceptionInterceptor()); setSavepoint(savepoint); return savepoint; } finally { connectionLock.unlock(); } } @Override public void setTransactionIsolation(int level) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); String sql = null; boolean shouldSendSet = false; if (this.propertySet.getBooleanProperty(PropertyKey.alwaysSendSetIsolation).getValue()) { shouldSendSet = true; } else if (level != this.isolationLevel) { shouldSendSet = true; } if (this.useLocalSessionState.getValue()) { shouldSendSet = this.isolationLevel != level; } if (shouldSendSet) { TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_TRANSACTION_ISOLATION); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); switch (level) { case java.sql.Connection.TRANSACTION_NONE: throw SQLError.createSQLException(Messages.getString("Connection.24"), 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(Messages.getString("Connection.25", new Object[] { level }), MysqlErrorNumbers.SQLSTATE_CONNJ_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); } this.session.execSQL(null, sql, -1, null, false, this.nullStatementResultSetFactory, null, false); this.isolationLevel = level; } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } } finally { connectionLock.unlock(); } } @Override public void setTypeMap(java.util.Map> map) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { this.typeMap = map; } finally { connectionLock.unlock(); } } private void setupServerForTruncationChecks() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { RuntimeProperty jdbcCompliantTruncation = this.propertySet.getProperty(PropertyKey.jdbcCompliantTruncation); if (jdbcCompliantTruncation.getValue()) { String currentSqlMode = this.session.getServerSession().getServerVariable("sql_mode"); boolean strictTransTablesIsSet = StringUtils.indexOfIgnoreCase(currentSqlMode, "STRICT_TRANS_TABLES") != -1; if (currentSqlMode == null || currentSqlMode.length() == 0 || !strictTransTablesIsSet) { TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "sql_mode"); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); StringBuilder commandBuf = new StringBuilder("SET sql_mode='"); if (currentSqlMode != null && currentSqlMode.length() > 0) { commandBuf.append(currentSqlMode); commandBuf.append(","); } commandBuf.append("STRICT_TRANS_TABLES'"); this.session.execSQL(null, commandBuf.toString(), -1, null, false, this.nullStatementResultSetFactory, null, false); jdbcCompliantTruncation.setValue(false); // server's handling this for us now } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } else if (strictTransTablesIsSet) { // We didn't set it, but someone did, so we piggy back on it jdbcCompliantTruncation.setValue(false); // server's handling this for us now } } } finally { connectionLock.unlock(); } } @Override public void shutdownServer() throws SQLException { try { this.session.shutdownServer(); } catch (CJException ex) { SQLException sqlEx = SQLError.createSQLException(Messages.getString("Connection.UnhandledExceptionDuringShutdown"), MysqlErrorNumbers.SQLSTATE_CONNJ_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } } @Override public void unregisterStatement(JdbcStatement stmt) { this.openStatements.remove(stmt); } public boolean versionMeetsMinimum(int major, int minor, int subminor) { return this.session.versionMeetsMinimum(major, minor, subminor); } @Override public CachedResultSetMetaData getCachedMetaData(String sql) { if (this.resultSetMetadataCache != null) { this.resultSetMetadataCacheLock.lock(); try { return this.resultSetMetadataCache.get(sql); } finally { this.resultSetMetadataCacheLock.unlock(); } } return null; // no cache exists } @Override public void initializeResultsMetadataFromCache(String sql, CachedResultSetMetaData cachedMetaData, ResultSetInternalMethods resultSet) throws SQLException { if (cachedMetaData == null) { // read from results cachedMetaData = new CachedResultSetMetaDataImpl(); // assume that users will use named-based lookups resultSet.getColumnDefinition().buildIndexMapping(); resultSet.initializeWithMetadata(); if (resultSet instanceof UpdatableResultSet) { ((UpdatableResultSet) resultSet).checkUpdatability(); } resultSet.populateCachedMetaData(cachedMetaData); this.resultSetMetadataCache.put(sql, cachedMetaData); } else { resultSet.getColumnDefinition().initializeFrom(cachedMetaData); resultSet.initializeWithMetadata(); if (resultSet instanceof UpdatableResultSet) { ((UpdatableResultSet) resultSet).checkUpdatability(); } } } @Override public String getStatementComment() { return this.session.getQueryComment(); } @Override public void setStatementComment(String comment) { this.session.setQueryComment(comment); } @Override public void transactionBegun() { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.connectionLifecycleInterceptors != null) { this.connectionLifecycleInterceptors.stream().forEach(ConnectionLifecycleInterceptor::transactionBegun); } } finally { connectionLock.unlock(); } } @Override public void transactionCompleted() { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.connectionLifecycleInterceptors != null) { this.connectionLifecycleInterceptors.stream().forEach(ConnectionLifecycleInterceptor::transactionCompleted); } } finally { connectionLock.unlock(); } } @Override public boolean storesLowerCaseTableName() { return this.session.getServerSession().storesLowerCaseTableNames(); } private ExceptionInterceptor exceptionInterceptor; @Override public ExceptionInterceptor getExceptionInterceptor() { return this.exceptionInterceptor; } @Override public boolean isServerLocal() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { try { return this.session.isServerLocal(getSession()); } catch (CJException ex) { SQLException sqlEx = SQLExceptionsMapping.translateException(ex, getExceptionInterceptor()); throw sqlEx; } } finally { connectionLock.unlock(); } } @Override public int getSessionMaxRows() { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { return this.session.getSessionMaxRows(); } finally { connectionLock.unlock(); } } @Override public void setSessionMaxRows(int max) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); if (this.session.getSessionMaxRows() != max) { TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "sql_select_limit"); try (TelemetryScope scope = span.makeCurrent()) { span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); span.setAttribute(TelemetryAttribute.DB_USER, getUser()); span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); this.session.setSessionMaxRows(max); this.session.execSQL(null, "SET sql_select_limit=" + (this.session.getSessionMaxRows() == -1 ? "DEFAULT" : this.session.getSessionMaxRows()), -1, null, false, this.nullStatementResultSetFactory, null, false); } catch (Throwable t) { span.setError(t); throw t; } finally { span.end(); } } } finally { connectionLock.unlock(); } } @Override public void setSchema(String schema) throws SQLException { checkClosed(); if (this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) { setDatabase(schema); } } @Override public String getSchema() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); return this.propertySet.getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA ? this.database : null; } finally { connectionLock.unlock(); } } @Override public void abort(Executor executor) throws SQLException { SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(ABORT_PERM); } if (executor == null) { throw SQLError.createSQLException(Messages.getString("Connection.26"), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } executor.execute(() -> { try { abortInternal(); } catch (SQLException e) { throw new RuntimeException(e); } }); } @Override public void setNetworkTimeout(Executor executor, final int milliseconds) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(SET_NETWORK_TIMEOUT_PERM); } if (executor == null) { throw SQLError.createSQLException(Messages.getString("Connection.26"), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } checkClosed(); executor.execute(new NetworkTimeoutSetter(this, milliseconds)); } finally { connectionLock.unlock(); } } private static class NetworkTimeoutSetter implements Runnable { private final WeakReference connRef; private final int milliseconds; public NetworkTimeoutSetter(JdbcConnection conn, int milliseconds) { this.connRef = new WeakReference<>(conn); this.milliseconds = milliseconds; } @Override public void run() { JdbcConnection conn = this.connRef.get(); if (conn != null) { Lock connectionLock = conn.getConnectionLock(); connectionLock.lock(); try { ((NativeSession) conn.getSession()).setSocketTimeout(this.milliseconds); } finally { connectionLock.unlock(); } } } } @Override public int getNetworkTimeout() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { checkClosed(); return this.session.getSocketTimeout(); } finally { connectionLock.unlock(); } } @Override public Clob createClob() { return new com.mysql.cj.jdbc.Clob(getExceptionInterceptor()); } @Override public Blob createBlob() { return new com.mysql.cj.jdbc.Blob(getExceptionInterceptor()); } @Override public NClob createNClob() { return new com.mysql.cj.jdbc.NClob(getExceptionInterceptor()); } @Override public SQLXML createSQLXML() throws SQLException { return new MysqlSQLXML(getExceptionInterceptor()); } @Override public boolean isValid(int timeout) throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (isClosed()) { return false; } try { try { pingInternal(false, timeout * 1000); } catch (Throwable t) { try { abortInternal(); } catch (Throwable ignoreThrown) { // we're dead now anyway } return false; } } catch (Throwable t) { return false; } return true; } finally { connectionLock.unlock(); } } private ClientInfoProvider infoProvider; @Override public ClientInfoProvider getClientInfoProviderImpl() throws SQLException { Lock connectionLock = getConnectionLock(); connectionLock.lock(); try { if (this.infoProvider == null) { String clientInfoProvider = this.propertySet.getStringProperty(PropertyKey.clientInfoProvider).getStringValue(); try { this.infoProvider = Util.getInstance(ClientInfoProvider.class, clientInfoProvider, null, null, getExceptionInterceptor()); } catch (CJException e1) { if (ClassNotFoundException.class.isInstance(e1.getCause())) { // Retry with package name prepended. try { this.infoProvider = Util.getInstance(ClientInfoProvider.class, "com.mysql.cj.jdbc." + clientInfoProvider, null, null, getExceptionInterceptor()); } catch (CJException e2) { throw SQLExceptionsMapping.translateException(e1, getExceptionInterceptor()); } } else { throw SQLExceptionsMapping.translateException(e1, getExceptionInterceptor()); } } this.infoProvider.initialize(this, this.props); } return this.infoProvider; } finally { connectionLock.unlock(); } } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { try { getClientInfoProviderImpl().setClientInfo(this, name, value); } catch (SQLClientInfoException ciEx) { throw ciEx; } catch (SQLException | CJException sqlEx) { SQLClientInfoException clientInfoEx = new SQLClientInfoException(); clientInfoEx.initCause(sqlEx); throw clientInfoEx; } } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { try { getClientInfoProviderImpl().setClientInfo(this, properties); } catch (SQLClientInfoException ciEx) { throw ciEx; } catch (SQLException | CJException sqlEx) { SQLClientInfoException clientInfoEx = new SQLClientInfoException(); clientInfoEx.initCause(sqlEx); throw clientInfoEx; } } @Override public String getClientInfo(String name) throws SQLException { return getClientInfoProviderImpl().getClientInfo(this, name); } @Override public Properties getClientInfo() throws SQLException { return getClientInfoProviderImpl().getClientInfo(this); } @Override public java.sql.Array createArrayOf(String typeName, Object[] elements) throws SQLException { throw SQLError.createSQLFeatureNotSupportedException(); } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { throw SQLError.createSQLFeatureNotSupportedException(); } @Override public T unwrap(java.lang.Class iface) throws java.sql.SQLException { try { // This works for classes that aren't actually wrapping // anything return iface.cast(this); } catch (ClassCastException cce) { throw SQLError.createSQLException("Unable to unwrap to " + iface.toString(), MysqlErrorNumbers.SQLSTATE_CONNJ_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } @Override public boolean isWrapperFor(Class iface) throws SQLException { // This works for classes that aren't actually wrapping // anything return iface.isInstance(this); } @Override public NativeSession getSession() { return this.session; } @Override public String getHostPortPair() { return this.origHostInfo.getHostPortPair(); } @Override public void handleNormalClose() { try { close(); } catch (SQLException e) { ExceptionFactory.createException(e.getMessage(), e); } } @Override public void handleReconnect() { createNewIO(true); } @Override public void handleCleanup(Throwable whyCleanedUp) { cleanup(whyCleanedUp); } @Override public ServerSessionStateController getServerSessionStateController() { return this.session.getServerSession().getServerSessionStateController(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy