/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception <http://www.mysql.com/about/legal/licensing/foss-exception.html>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.jdbc; import java.io.InputStream; import java.math.BigInteger; import java.sql.BatchUpdateException; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Types; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import com.mysql.jdbc.exceptions.MySQLStatementCancelledException; import com.mysql.jdbc.exceptions.MySQLTimeoutException; import com.mysql.jdbc.log.LogUtils; import com.mysql.jdbc.profiler.ProfilerEvent; import com.mysql.jdbc.profiler.ProfilerEventHandler; /** * A Statement object is used for executing a static SQL statement and obtaining * the results produced by it. * * <p> * Only one ResultSet per Statement can be open at any point in time. Therefore, * if the reading of one ResultSet is interleaved with the reading of another, * each must have been generated by different Statements. All statement execute * methods implicitly close a statement's current ResultSet if an open one * exists. * </p> * * @author Mark Matthews * @version $Id: Statement.java 4624 2005-11-28 14:24:29 -0600 (Mon, 28 Nov * 2005) mmatthews $ * * @see java.sql.Statement * @see ResultSetInternalMethods */ public class StatementImpl implements Statement { protected static final String PING_MARKER = "/* ping */"; /** * Thread used to implement query timeouts...Eventually we could be more * efficient and have one thread with timers, but this is a straightforward * and simple way to implement a feature that isn't used all that often. */ class CancelTask extends TimerTask { long connectionId = 0; String origHost = ""; SQLException caughtWhileCancelling = null; StatementImpl toCancel; Properties origConnProps = null; String origConnURL = ""; CancelTask(StatementImpl cancellee) throws SQLException { connectionId = cancellee.connectionId; origHost = connection.getHost(); toCancel = cancellee; origConnProps = new Properties(); Properties props = connection.getProperties(); Enumeration<?> keys = props.propertyNames(); while (keys.hasMoreElements()) { String key = keys.nextElement().toString(); origConnProps.setProperty(key, props.getProperty(key)); } origConnURL = connection.getURL(); } public void run() { Thread cancelThread = new Thread() { public void run() { Connection cancelConn = null; java.sql.Statement cancelStmt = null; try { if (connection.getQueryTimeoutKillsConnection()) { toCancel.wasCancelled = true; toCancel.wasCancelledByTimeout = true; connection.realClose(false, false, true, new MySQLStatementCancelledException(Messages.getString("Statement.ConnectionKilledDueToTimeout"))); } else { synchronized (cancelTimeoutMutex) { if (origConnURL.equals(connection.getURL())) { //All's fine cancelConn = connection.duplicate(); cancelStmt = cancelConn.createStatement(); cancelStmt.execute("KILL QUERY " + connectionId); } else { try { cancelConn = (Connection) DriverManager.getConnection(origConnURL, origConnProps); cancelStmt = cancelConn.createStatement(); cancelStmt.execute("KILL QUERY " + connectionId); } catch (NullPointerException npe){ //Log this? "Failed to connect to " + origConnURL + " and KILL query" } } toCancel.wasCancelled = true; toCancel.wasCancelledByTimeout = true; } } } catch (SQLException sqlEx) { caughtWhileCancelling = sqlEx; } catch (NullPointerException npe) { // Case when connection closed while starting to cancel // We can't easily synchronize this, because then one thread // can't cancel() a running query // ignore, we shouldn't re-throw this, because the connection's // already closed, so the statement has been timed out. } finally { if (cancelStmt != null) { try { cancelStmt.close(); } catch (SQLException sqlEx) { throw new RuntimeException(sqlEx.toString()); } } if (cancelConn != null) { try { cancelConn.close(); } catch (SQLException sqlEx) { throw new RuntimeException(sqlEx.toString()); } } toCancel = null; origConnProps = null; origConnURL = null; } } }; cancelThread.start(); } } /** Mutex to prevent race between returning query results and noticing that we're timed-out or cancelled. */ protected Object cancelTimeoutMutex = new Object(); /** Used to generate IDs when profiling. */ static int statementCounter = 1; public final static byte USES_VARIABLES_FALSE = 0; public final static byte USES_VARIABLES_TRUE = 1; public final static byte USES_VARIABLES_UNKNOWN = -1; protected boolean wasCancelled = false; protected boolean wasCancelledByTimeout = false; /** Holds batched commands */ protected List<Object> batchedArgs; /** The character converter to use (if available) */ protected SingleByteCharsetConverter charConverter = null; /** The character encoding to use (if available) */ protected String charEncoding = null; /** The connection that created us */ protected volatile MySQLConnection connection = null; protected long connectionId = 0; /** The catalog in use */ protected String currentCatalog = null; /** Should we process escape codes? */ protected boolean doEscapeProcessing = true; /** If we're profiling, where should events go to? */ protected ProfilerEventHandler eventSink = null; /** The number of rows to fetch at a time (currently ignored) */ private int fetchSize = 0; /** Has this statement been closed? */ protected boolean isClosed = false; /** The auto_increment value for the last insert */ protected long lastInsertId = -1; /** The max field size for this statement */ protected int maxFieldSize = MysqlIO.getMaxBuf(); /** * The maximum number of rows to return for this statement (-1 means _all_ * rows) */ protected int maxRows = -1; /** Has someone changed this for this statement? */ protected boolean maxRowsChanged = false; /** Set of currently-open ResultSets */ protected Set<ResultSetInternalMethods> openResults = new HashSet<ResultSetInternalMethods>(); /** Are we in pedantic mode? */ protected boolean pedantic = false; /** * Where this statement was created, only used if profileSql or * useUsageAdvisor set to true. */ protected String pointOfOrigin; /** Should we profile? */ protected boolean profileSQL = false; /** The current results */ protected ResultSetInternalMethods results = null; protected ResultSetInternalMethods generatedKeysResults = null; /** The concurrency for this result set (updatable or not) */ protected int resultSetConcurrency = 0; /** The type of this result set (scroll sensitive or in-sensitive) */ protected int resultSetType = 0; /** Used to identify this statement when profiling. */ protected int statementId; /** The timeout for a query */ protected int timeoutInMillis = 0; /** The update count for this statement */ protected long updateCount = -1; /** Should we use the usage advisor? */ protected boolean useUsageAdvisor = false; /** The warnings chain. */ protected SQLWarning warningChain = null; /** Has clearWarnings() been called? */ protected boolean clearWarningsCalled = false; /** * Should this statement hold results open over .close() irregardless of * connection's setting? */ protected boolean holdResultsOpenOverClose = false; protected ArrayList<ResultSetRow> batchedGeneratedKeys = null; protected boolean retrieveGeneratedKeys = false; protected boolean continueBatchOnError = false; protected PingTarget pingTarget = null; protected boolean useLegacyDatetimeCode; private ExceptionInterceptor exceptionInterceptor; /** Whether or not the last query was of the form ON DUPLICATE KEY UPDATE */ protected boolean lastQueryIsOnDupKeyUpdate = false; /** Are we currently executing a statement? */ protected final AtomicBoolean statementExecuting = new AtomicBoolean(false); /** * Constructor for a Statement. * * @param c * the Connection instantation that creates us * @param catalog * the database name in use when we were created * * @throws SQLException * if an error occurs. */ public StatementImpl(MySQLConnection c, String catalog) throws SQLException { if ((c == null) || c.isClosed()) { throw SQLError.createSQLException( Messages.getString("Statement.0"), //$NON-NLS-1$ SQLError.SQL_STATE_CONNECTION_NOT_OPEN, null); //$NON-NLS-1$ //$NON-NLS-2$ } this.connection = c; this.connectionId = this.connection.getId(); this.exceptionInterceptor = this.connection .getExceptionInterceptor(); this.currentCatalog = catalog; this.pedantic = this.connection.getPedantic(); this.continueBatchOnError = this.connection.getContinueBatchOnError(); this.useLegacyDatetimeCode = this.connection.getUseLegacyDatetimeCode(); if (!this.connection.getDontTrackOpenResources()) { this.connection.registerStatement(this); } // // Adjust, if we know it // if (this.connection != null) { this.maxFieldSize = this.connection.getMaxAllowedPacket(); int defaultFetchSize = this.connection.getDefaultFetchSize(); if (defaultFetchSize != 0) { setFetchSize(defaultFetchSize); } if (this.connection.getUseUnicode()) { this.charEncoding = this.connection.getEncoding(); this.charConverter = this.connection.getCharsetConverter(this.charEncoding); } boolean profiling = this.connection.getProfileSql() || this.connection.getUseUsageAdvisor() || this.connection.getLogSlowQueries(); if (this.connection.getAutoGenerateTestcaseScript() || profiling) { this.statementId = statementCounter++; } if (profiling) { this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable()); this.profileSQL = this.connection.getProfileSql(); this.useUsageAdvisor = this.connection.getUseUsageAdvisor(); this.eventSink = ProfilerEventHandlerFactory.getInstance(this.connection); } int maxRowsConn = this.connection.getMaxRows(); if (maxRowsConn != -1) { setMaxRows(maxRowsConn); } this.holdResultsOpenOverClose = this.connection.getHoldResultsOpenOverStatementClose(); } version5013OrNewer = this.connection.versionMeetsMinimum(5, 0, 13); } /** * DOCUMENT ME! * * @param sql * DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! */ public void addBatch(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.batchedArgs == null) { this.batchedArgs = new ArrayList<Object>(); } if (sql != null) { this.batchedArgs.add(sql); } } } /** Get the batched args as added by the addBatch method(s). * The list is unmodifiable and might contain any combination of String, * BatchParams, or BatchedBindValues depending on how the parameters were * batched. * @return an unmodifiable List of batched args */ public List<Object> getBatchedArgs() { return batchedArgs==null?null:Collections.unmodifiableList(batchedArgs); } /** * Cancels this Statement object if both the DBMS and driver support * aborting an SQL statement. This method can be used by one thread to * cancel a statement that is being executed by another thread. */ public void cancel() throws SQLException { if (!this.statementExecuting.get()) { return; } if (!this.isClosed && this.connection != null && this.connection.versionMeetsMinimum(5, 0, 0)) { Connection cancelConn = null; java.sql.Statement cancelStmt = null; try { cancelConn = this.connection.duplicate(); cancelStmt = cancelConn.createStatement(); cancelStmt.execute("KILL QUERY " + this.connection.getIO().getThreadId()); this.wasCancelled = true; } finally { if (cancelStmt != null) { cancelStmt.close(); } if (cancelConn != null) { cancelConn.close(); } } } } // --------------------------JDBC 2.0----------------------------- /** * Checks if closed() has been called, and throws an exception if so * * @throws SQLException * if this statement has been closed */ protected MySQLConnection checkClosed() throws SQLException { MySQLConnection c = this.connection; if (c == null) { throw SQLError.createSQLException(Messages .getString("Statement.49"), //$NON-NLS-1$ SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); //$NON-NLS-1$ } return c; } /** * Checks if the given SQL query with the given first non-ws char is a DML * statement. Throws an exception if it is. * * @param sql * the SQL to check * @param firstStatementChar * the UC first non-ws char of the statement * * @throws SQLException * if the statement contains DML */ protected void checkForDml(String sql, char firstStatementChar) throws SQLException { if ((firstStatementChar == 'I') || (firstStatementChar == 'U') || (firstStatementChar == 'D') || (firstStatementChar == 'A') || (firstStatementChar == 'C') || (firstStatementChar == 'T') || (firstStatementChar == 'R')) { String noCommentSql = StringUtils.stripComments(sql, "'\"", "'\"", true, false, true, true); if (StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "INSERT") //$NON-NLS-1$ || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "UPDATE") //$NON-NLS-1$ || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DELETE") //$NON-NLS-1$ || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DROP") //$NON-NLS-1$ || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "CREATE") //$NON-NLS-1$ || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "ALTER") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "TRUNCATE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "RENAME") ) { //$NON-NLS-1$ throw SQLError.createSQLException(Messages .getString("Statement.57"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } } } /** * Method checkNullOrEmptyQuery. * * @param sql * the SQL to check * * @throws SQLException * if query is null or empty. */ protected void checkNullOrEmptyQuery(String sql) throws SQLException { if (sql == null) { throw SQLError.createSQLException(Messages .getString("Statement.59"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$ } if (sql.length() == 0) { throw SQLError.createSQLException(Messages .getString("Statement.61"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * JDBC 2.0 Make the set of commands in the current batch empty. This method * is optional. * * @exception SQLException * if a database-access error occurs, or the driver does not * support batch statements */ public void clearBatch() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.batchedArgs != null) { this.batchedArgs.clear(); } } } /** * After this call, getWarnings returns null until a new warning is reported * for this Statement. * * @exception SQLException * if a database access error occurs (why?) */ public void clearWarnings() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.clearWarningsCalled = true; this.warningChain = null; } } /** * In many cases, it is desirable to immediately release a Statement's * database and JDBC resources instead of waiting for this to happen when it * is automatically closed. The close method provides this immediate * release. * * <p> * <B>Note:</B> A Statement is automatically closed when it is garbage * collected. When a Statement is closed, its current ResultSet, if one * exists, is also closed. * </p> * * @exception SQLException * if a database access error occurs */ public void close() throws SQLException { try { synchronized (checkClosed().getConnectionMutex()) { realClose(true, true); } } catch (SQLException sqlEx) { if (SQLError.SQL_STATE_CONNECTION_NOT_OPEN.equals(sqlEx.getSQLState())) { return; } throw sqlEx; } } /** * Close any open result sets that have been 'held open' */ protected void closeAllOpenResults() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.openResults != null) { for (ResultSetInternalMethods element : this.openResults) { try { element.realClose(false); } catch (SQLException sqlEx) { AssertionFailedException.shouldNotHappen(sqlEx); } } this.openResults.clear(); } } } public void removeOpenResultSet(ResultSet rs) { try { synchronized (checkClosed().getConnectionMutex()) { if (this.openResults != null) { this.openResults.remove(rs); } } } catch (SQLException e) { // we can't break the interface, having this be no-op in case of error is ok } } public int getOpenResultSetCount() { try { synchronized (checkClosed().getConnectionMutex()) { if (this.openResults != null) { return this.openResults.size(); } return 0; } } catch (SQLException e) { // we can't break the interface, having this be no-op in case of error is ok return 0; } } /** * @param sql * @return */ private ResultSetInternalMethods createResultSetUsingServerFetch(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { java.sql.PreparedStatement pStmt = this.connection.prepareStatement( sql, this.resultSetType, this.resultSetConcurrency); pStmt.setFetchSize(this.fetchSize); if (this.maxRows > -1) { pStmt.setMaxRows(this.maxRows); } statementBegins(); pStmt.execute(); // // Need to be able to get resultset irrespective if we issued DML or // not to make this work. // ResultSetInternalMethods rs = ((com.mysql.jdbc.StatementImpl) pStmt) .getResultSetInternal(); rs .setStatementUsedForFetchingRows((com.mysql.jdbc.PreparedStatement) pStmt); this.results = rs; return rs; } } /** * We only stream result sets when they are forward-only, read-only, and the * fetch size has been set to Integer.MIN_VALUE * * @return true if this result set should be streamed row at-a-time, rather * than read all at once. */ protected boolean createStreamingResultSet() { try { synchronized (checkClosed().getConnectionMutex()) { return ((this.resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this.fetchSize == Integer.MIN_VALUE)); } } catch (SQLException e) { // we can't break the interface, having this be no-op in case of error is ok return false; } } private int originalResultSetType = 0; private int originalFetchSize = 0; /* (non-Javadoc) * @see com.mysql.jdbc.IStatement#enableStreamingResults() */ public void enableStreamingResults() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.originalResultSetType = this.resultSetType; this.originalFetchSize = this.fetchSize; setFetchSize(Integer.MIN_VALUE); setResultSetType(ResultSet.TYPE_FORWARD_ONLY); } } public void disableStreamingResults() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.fetchSize == Integer.MIN_VALUE && this.resultSetType == ResultSet.TYPE_FORWARD_ONLY) { setFetchSize(this.originalFetchSize); setResultSetType(this.originalResultSetType); } } } /** * Execute a SQL statement that may return multiple results. We don't have * to worry about this since we do not support multiple ResultSets. You can * use getResultSet or getUpdateCount to retrieve the result. * * @param sql * any SQL statement * * @return true if the next result is a ResulSet, false if it is an update * count or there are no more results * * @exception SQLException * if a database access error occurs */ public boolean execute(String sql) throws SQLException { return execute(sql, false); } private boolean execute(String sql, boolean returnGeneratedKeys) throws SQLException { MySQLConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { this.retrieveGeneratedKeys = returnGeneratedKeys; lastQueryIsOnDupKeyUpdate = false; if (returnGeneratedKeys) lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyInString(sql); resetCancelledState(); checkNullOrEmptyQuery(sql); checkClosed(); char firstNonWsChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); boolean isSelect = true; if (firstNonWsChar != 'S') { isSelect = false; if (locallyScopedConn.isReadOnly()) { throw SQLError.createSQLException(Messages .getString("Statement.27") //$NON-NLS-1$ + Messages.getString("Statement.28"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } } boolean doStreaming = createStreamingResultSet(); try { // Adjust net_write_timeout to a higher value if we're // streaming result sets. More often than not, someone runs into // an issue where they blow net_write_timeout when using this // feature, and if they're willing to hold a result set open // for 30 seconds or more, one more round-trip isn't going to hurt // // This is reset by RowDataDynamic.close(). if (doStreaming && locallyScopedConn.getNetTimeoutForStreamingResults() > 0) { executeSimpleNonQuery(locallyScopedConn, "SET net_write_timeout=" + locallyScopedConn.getNetTimeoutForStreamingResults()); } if (this.doEscapeProcessing) { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, locallyScopedConn.serverSupportsConvertFn(), locallyScopedConn); if (escapedSqlResult instanceof String) { sql = (String) escapedSqlResult; } else { sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql; } } if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } if (sql.charAt(0) == '/') { if (sql.startsWith(PING_MARKER)) { doPingInstead(); return true; } } CachedResultSetMetaData cachedMetaData = null; ResultSetInternalMethods rs = null; // If there isn't a limit clause in the SQL // then limit the number of rows to return in // an efficient manner. Only do this if // setMaxRows() hasn't been used on any Statements // generated from the current Connection (saves // a query, and network traffic). this.batchedGeneratedKeys = null; if (useServerFetch()) { rs = createResultSetUsingServerFetch(sql); } else { CancelTask timeoutTask = null; String oldCatalog = null; try { if (locallyScopedConn.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } if (!locallyScopedConn.getCatalog().equals( this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Check if we have cached metadata for this query... // Field[] cachedFields = null; if (locallyScopedConn.getCacheResultSetMetadata()) { cachedMetaData = locallyScopedConn.getCachedMetaData(sql); if (cachedMetaData != null) { cachedFields = cachedMetaData.fields; } } // // Only apply max_rows to selects // if (locallyScopedConn.useMaxRows()) { int rowLimit = -1; if (isSelect) { if (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1) { //$NON-NLS-1$ rowLimit = this.maxRows; } else { if (this.maxRows <= 0) { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } else { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=" + this.maxRows); } } } else { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } statementBegins(); // Finally, execute the query rs = locallyScopedConn.execSQL(this, sql, rowLimit, null, this.resultSetType, this.resultSetConcurrency, doStreaming, this.currentCatalog, cachedFields); } else { statementBegins(); rs = locallyScopedConn.execSQL(this, sql, -1, null, this.resultSetType, this.resultSetConcurrency, doStreaming, this.currentCatalog, cachedFields); } if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); timeoutTask = null; } synchronized (this.cancelTimeoutMutex) { if (this.wasCancelled) { SQLException cause = null; if (this.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } else { cause = new MySQLStatementCancelledException(); } resetCancelledState(); throw cause; } } } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } } } if (rs != null) { this.lastInsertId = rs.getUpdateID(); this.results = rs; rs.setFirstCharOfQuery(firstNonWsChar); if (rs.reallyResult()) { if (cachedMetaData != null) { locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); } else { if (this.connection.getCacheResultSetMetadata()) { locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); } } } } return ((rs != null) && rs.reallyResult()); } finally { this.statementExecuting.set(false); } } } protected void statementBegins() { this.clearWarningsCalled = false; this.statementExecuting.set(true); } protected void resetCancelledState() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.cancelTimeoutMutex == null) { return; } synchronized (this.cancelTimeoutMutex) { this.wasCancelled = false; this.wasCancelledByTimeout = false; } } } /** * @see StatementImpl#execute(String, int) */ public boolean execute(String sql, int returnGeneratedKeys) throws SQLException { if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS) { checkClosed(); MySQLConnection locallyScopedConn = this.connection; synchronized (locallyScopedConn.getConnectionMutex()) { // If this is a 'REPLACE' query, we need to be able to parse // the 'info' message returned from the server to determine // the actual number of keys generated. boolean readInfoMsgState = this.connection .isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); try { return execute(sql, true); } finally { locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState); } } } return execute(sql); } /** * @see StatementImpl#execute(String, int[]) */ public boolean execute(String sql, int[] generatedKeyIndices) throws SQLException { MySQLConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) { this.retrieveGeneratedKeys = true; // If this is a 'REPLACE' query, we need to be able to parse // the 'info' message returned from the server to determine // the actual number of keys generated. boolean readInfoMsgState = locallyScopedConn .isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); try { return execute(sql, true); } finally { locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState); } } return execute(sql); } } /** * @see StatementImpl#execute(String, String[]) */ public boolean execute(String sql, String[] generatedKeyNames) throws SQLException { MySQLConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) { this.retrieveGeneratedKeys = true; // If this is a 'REPLACE' query, we need to be able to parse // the 'info' message returned from the server to determine // the actual number of keys generated. boolean readInfoMsgState = this.connection .isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); try { return execute(sql, true); } finally { locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState); } } return execute(sql); } } /** * JDBC 2.0 Submit a batch of commands to the database for execution. This * method is optional. * * @return an array of update counts containing one element for each command * in the batch. The array is ordered according to the order in * which commands were inserted into the batch * * @exception SQLException * if a database-access error occurs, or the driver does not * support batch statements * @throws java.sql.BatchUpdateException * DOCUMENT ME! */ public int[] executeBatch() throws SQLException { MySQLConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { if (locallyScopedConn.isReadOnly()) { throw SQLError.createSQLException(Messages .getString("Statement.34") //$NON-NLS-1$ + Messages.getString("Statement.35"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } if (this.batchedArgs == null || this.batchedArgs.size() == 0) { return new int[0]; } // we timeout the entire batch, not individual statements int individualStatementTimeout = this.timeoutInMillis; this.timeoutInMillis = 0; CancelTask timeoutTask = null; try { resetCancelledState(); statementBegins(); try { this.retrieveGeneratedKeys = true; // The JDBC spec doesn't forbid this, but doesn't provide for it either...we do.. int[] updateCounts = null; if (this.batchedArgs != null) { int nbrCommands = this.batchedArgs.size(); this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(this.batchedArgs.size()); boolean multiQueriesEnabled = locallyScopedConn.getAllowMultiQueries(); if (locallyScopedConn.versionMeetsMinimum(4, 1, 1) && (multiQueriesEnabled || (locallyScopedConn.getRewriteBatchedStatements() && nbrCommands > 4))) { return executeBatchUsingMultiQueries(multiQueriesEnabled, nbrCommands, individualStatementTimeout); } if (locallyScopedConn.getEnableQueryTimeouts() && individualStatementTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, individualStatementTimeout); } updateCounts = new int[nbrCommands]; for (int i = 0; i < nbrCommands; i++) { updateCounts[i] = -3; } SQLException sqlEx = null; int commandIndex = 0; for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { try { String sql = (String) this.batchedArgs.get(commandIndex); updateCounts[commandIndex] = executeUpdate(sql, true, true); // limit one generated key per OnDuplicateKey statement getBatchedGeneratedKeys(containsOnDuplicateKeyInString(sql) ? 1 : 0); } catch (SQLException ex) { updateCounts[commandIndex] = EXECUTE_FAILED; if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) && !hasDeadlockOrTimeoutRolledBackTx(ex)) { sqlEx = ex; } else { int[] newUpdateCounts = new int[commandIndex]; if (hasDeadlockOrTimeoutRolledBackTx(ex)) { for (int i = 0; i < newUpdateCounts.length; i++) { newUpdateCounts[i] = Statement.EXECUTE_FAILED; } } else { System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex); } throw new java.sql.BatchUpdateException(ex .getMessage(), ex.getSQLState(), ex .getErrorCode(), newUpdateCounts); } } } if (sqlEx != null) { throw new java.sql.BatchUpdateException(sqlEx .getMessage(), sqlEx.getSQLState(), sqlEx .getErrorCode(), updateCounts); } } if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); timeoutTask = null; } return (updateCounts != null) ? updateCounts : new int[0]; } finally { this.statementExecuting.set(false); } } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } resetCancelledState(); this.timeoutInMillis = individualStatementTimeout; clearBatch(); } } } protected final boolean hasDeadlockOrTimeoutRolledBackTx(SQLException ex) { int vendorCode = ex.getErrorCode(); switch (vendorCode) { case MysqlErrorNumbers.ER_LOCK_DEADLOCK: case MysqlErrorNumbers.ER_LOCK_TABLE_FULL: return true; case MysqlErrorNumbers.ER_LOCK_WAIT_TIMEOUT: return !version5013OrNewer; default: return false; } } /** * Rewrites batch into a single query to send to the server. This method * will constrain each batch to be shorter than max_allowed_packet on the * server. * * @return update counts in the same manner as executeBatch() * @throws SQLException */ private int[] executeBatchUsingMultiQueries(boolean multiQueriesEnabled, int nbrCommands, int individualStatementTimeout) throws SQLException { MySQLConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { if (!multiQueriesEnabled) { locallyScopedConn.getIO().enableMultiQueries(); } java.sql.Statement batchStmt = null; CancelTask timeoutTask = null; try { int[] updateCounts = new int[nbrCommands]; for (int i = 0; i < nbrCommands; i++) { updateCounts[i] = -3; } int commandIndex = 0; StringBuffer queryBuf = new StringBuffer(); batchStmt = locallyScopedConn.createStatement(); if (locallyScopedConn.getEnableQueryTimeouts() && individualStatementTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask((StatementImpl)batchStmt); locallyScopedConn.getCancelTimer().schedule(timeoutTask, individualStatementTimeout); } int counter = 0; int numberOfBytesPerChar = 1; String connectionEncoding = locallyScopedConn.getEncoding(); if (StringUtils.startsWithIgnoreCase(connectionEncoding, "utf")) { numberOfBytesPerChar = 3; } else if (CharsetMapping.isMultibyteCharset(connectionEncoding)) { numberOfBytesPerChar = 2; } int escapeAdjust = 1; batchStmt.setEscapeProcessing(this.doEscapeProcessing); if (this.doEscapeProcessing) { escapeAdjust = 2; /* We assume packet _could_ grow by this amount, as we're not sure how big statement will end up after escape processing */ } SQLException sqlEx = null; int argumentSetsInBatchSoFar = 0; for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { String nextQuery = (String) this.batchedArgs.get(commandIndex); if (((((queryBuf.length() + nextQuery.length()) * numberOfBytesPerChar) + 1 /* for semicolon */ + MysqlIO.HEADER_LENGTH) * escapeAdjust) + 32 > this.connection .getMaxAllowedPacket()) { try { batchStmt.execute(queryBuf.toString(), Statement.RETURN_GENERATED_KEYS); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(commandIndex, argumentSetsInBatchSoFar, updateCounts, ex); } counter = processMultiCountsAndKeys((StatementImpl)batchStmt, counter, updateCounts); queryBuf = new StringBuffer(); argumentSetsInBatchSoFar = 0; } queryBuf.append(nextQuery); queryBuf.append(";"); argumentSetsInBatchSoFar++; } if (queryBuf.length() > 0) { try { batchStmt.execute(queryBuf.toString(), Statement.RETURN_GENERATED_KEYS); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(commandIndex - 1, argumentSetsInBatchSoFar, updateCounts, ex); } counter = processMultiCountsAndKeys((StatementImpl)batchStmt, counter, updateCounts); } if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); timeoutTask = null; } if (sqlEx != null) { throw new java.sql.BatchUpdateException(sqlEx .getMessage(), sqlEx.getSQLState(), sqlEx .getErrorCode(), updateCounts); } return (updateCounts != null) ? updateCounts : new int[0]; } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } resetCancelledState(); try { if (batchStmt != null) { batchStmt.close(); } } finally { if (!multiQueriesEnabled) { locallyScopedConn.getIO().disableMultiQueries(); } } } } } protected int processMultiCountsAndKeys( StatementImpl batchedStatement, int updateCountCounter, int[] updateCounts) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { updateCounts[updateCountCounter++] = batchedStatement.getUpdateCount(); boolean doGenKeys = this.batchedGeneratedKeys != null; byte[][] row = null; if (doGenKeys) { long generatedKey = batchedStatement.getLastInsertID(); row = new byte[1][]; row[0] = StringUtils.getBytes(Long.toString(generatedKey)); this.batchedGeneratedKeys.add(new ByteArrayRow(row, getExceptionInterceptor())); } while (batchedStatement.getMoreResults() || batchedStatement.getUpdateCount() != -1) { updateCounts[updateCountCounter++] = batchedStatement.getUpdateCount(); if (doGenKeys) { long generatedKey = batchedStatement.getLastInsertID(); row = new byte[1][]; row[0] = StringUtils.getBytes(Long.toString(generatedKey)); this.batchedGeneratedKeys.add(new ByteArrayRow(row, getExceptionInterceptor())); } } return updateCountCounter; } } protected SQLException handleExceptionForBatch(int endOfBatchIndex, int numValuesPerBatch, int[] updateCounts, SQLException ex) throws BatchUpdateException { SQLException sqlEx; for (int j = endOfBatchIndex; j > endOfBatchIndex - numValuesPerBatch; j--) { updateCounts[j] = EXECUTE_FAILED; } if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) && !hasDeadlockOrTimeoutRolledBackTx(ex)) { sqlEx = ex; } else { int[] newUpdateCounts = new int[endOfBatchIndex]; System.arraycopy(updateCounts, 0, newUpdateCounts, 0, endOfBatchIndex); BatchUpdateException batchException = new BatchUpdateException(ex .getMessage(), ex.getSQLState(), ex .getErrorCode(), newUpdateCounts); batchException.initCause(ex); throw batchException; } return sqlEx; } /** * Execute a SQL statement that retruns a single ResultSet * * @param sql * typically a static SQL SELECT statement * * @return a ResulSet that contains the data produced by the query * * @exception SQLException * if a database access error occurs */ public java.sql.ResultSet executeQuery(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; this.retrieveGeneratedKeys = false; resetCancelledState(); checkNullOrEmptyQuery(sql); boolean doStreaming = createStreamingResultSet(); // Adjust net_write_timeout to a higher value if we're // streaming result sets. More often than not, someone runs into // an issue where they blow net_write_timeout when using this // feature, and if they're willing to hold a result set open // for 30 seconds or more, one more round-trip isn't going to hurt // // This is reset by RowDataDynamic.close(). if (doStreaming && this.connection.getNetTimeoutForStreamingResults() > 0) { executeSimpleNonQuery(locallyScopedConn, "SET net_write_timeout=" + this.connection.getNetTimeoutForStreamingResults()); } if (this.doEscapeProcessing) { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, locallyScopedConn.serverSupportsConvertFn(), this.connection); if (escapedSqlResult instanceof String) { sql = (String) escapedSqlResult; } else { sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql; } } char firstStatementChar = StringUtils.firstNonWsCharUc(sql, findStartOfStatement(sql)); if (sql.charAt(0) == '/') { if (sql.startsWith(PING_MARKER)) { doPingInstead(); return this.results; } } checkForDml(sql, firstStatementChar); if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } CachedResultSetMetaData cachedMetaData = null; // If there isn't a limit clause in the SQL // then limit the number of rows to return in // an efficient manner. Only do this if // setMaxRows() hasn't been used on any Statements // generated from the current Connection (saves // a query, and network traffic). if (useServerFetch()) { this.results = createResultSetUsingServerFetch(sql); return this.results; } CancelTask timeoutTask = null; String oldCatalog = null; try { if (locallyScopedConn.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Check if we have cached metadata for this query... // Field[] cachedFields = null; if (locallyScopedConn.getCacheResultSetMetadata()) { cachedMetaData = locallyScopedConn.getCachedMetaData(sql); if (cachedMetaData != null) { cachedFields = cachedMetaData.fields; } } if (locallyScopedConn.useMaxRows()) { // We need to execute this all together // So synchronize on the Connection's mutex (because // even queries going through there synchronize // on the connection if (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1) { //$NON-NLS-1$ this.results = locallyScopedConn.execSQL(this, sql, this.maxRows, null, this.resultSetType, this.resultSetConcurrency, doStreaming, this.currentCatalog, cachedFields); } else { if (this.maxRows <= 0) { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } else { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=" + this.maxRows); } statementBegins(); this.results = locallyScopedConn.execSQL(this, sql, -1, null, this.resultSetType, this.resultSetConcurrency, doStreaming, this.currentCatalog, cachedFields); if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } } } else { statementBegins(); this.results = locallyScopedConn.execSQL(this, sql, -1, null, this.resultSetType, this.resultSetConcurrency, doStreaming, this.currentCatalog, cachedFields); } if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); timeoutTask = null; } synchronized (this.cancelTimeoutMutex) { if (this.wasCancelled) { SQLException cause = null; if (this.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } else { cause = new MySQLStatementCancelledException(); } resetCancelledState(); throw cause; } } } finally { this.statementExecuting.set(false); if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } } this.lastInsertId = this.results.getUpdateID(); if (cachedMetaData != null) { locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); } else { if (this.connection.getCacheResultSetMetadata()) { locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); } } return this.results; } } protected void doPingInstead() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.pingTarget != null) { this.pingTarget.doPing(); } else { this.connection.ping(); } ResultSetInternalMethods fakeSelectOneResultSet = generatePingResultSet(); this.results = fakeSelectOneResultSet; } } protected ResultSetInternalMethods generatePingResultSet() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Field[] fields = { new Field(null, "1", Types.BIGINT, 1) }; ArrayList<ResultSetRow> rows = new ArrayList<ResultSetRow>(); byte[] colVal = new byte[] { (byte) '1' }; rows.add(new ByteArrayRow(new byte[][] { colVal }, getExceptionInterceptor())); return (ResultSetInternalMethods) DatabaseMetaData.buildResultSet(fields, rows, this.connection); } } protected void executeSimpleNonQuery(MySQLConnection c, String nonQuery) throws SQLException { c.execSQL(this, nonQuery, -1, null, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, false, this.currentCatalog, null, false).close(); } /** * Execute a SQL INSERT, UPDATE or DELETE statement. In addition SQL * statements that return nothing such as SQL DDL statements can be executed * Any IDs generated for AUTO_INCREMENT fields can be retrieved by casting * this Statement to org.gjt.mm.mysql.Statement and calling the * getLastInsertID() method. * * @param sql * a SQL statement * * @return either a row count, or 0 for SQL commands * * @exception SQLException * if a database access error occurs */ public int executeUpdate(String sql) throws SQLException { return executeUpdate(sql, false, false); } protected int executeUpdate(String sql, boolean isBatch, boolean returnGeneratedKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); ResultSetInternalMethods rs = null; this.retrieveGeneratedKeys = returnGeneratedKeys; resetCancelledState(); checkNullOrEmptyQuery(sql); if (this.doEscapeProcessing) { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.connection.serverSupportsConvertFn(), this.connection); if (escapedSqlResult instanceof String) { sql = (String) escapedSqlResult; } else { sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql; } } if (locallyScopedConn.isReadOnly(false)) { throw SQLError.createSQLException(Messages .getString("Statement.42") //$NON-NLS-1$ + Messages.getString("Statement.43"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } if (StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) { //$NON-NLS-1$ throw SQLError.createSQLException(Messages .getString("Statement.46"), //$NON-NLS-1$ "01S03", getExceptionInterceptor()); //$NON-NLS-1$ } if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } // The checking and changing of catalogs // must happen in sequence, so synchronize // on the same mutex that _conn is using CancelTask timeoutTask = null; String oldCatalog = null; try { if (locallyScopedConn.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Only apply max_rows to selects // if (locallyScopedConn.useMaxRows()) { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } statementBegins(); rs = locallyScopedConn.execSQL(this, sql, -1, null, java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY, false, this.currentCatalog, null /* force read of field info on DML */, isBatch); if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); timeoutTask = null; } synchronized (this.cancelTimeoutMutex) { if (this.wasCancelled) { SQLException cause = null; if (this.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } else { cause = new MySQLStatementCancelledException(); } resetCancelledState(); throw cause; } } } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } if (!isBatch) { this.statementExecuting.set(false); } } this.results = rs; rs.setFirstCharOfQuery(firstStatementChar); this.updateCount = rs.getUpdateCount(); int truncatedUpdateCount = 0; if (this.updateCount > Integer.MAX_VALUE) { truncatedUpdateCount = Integer.MAX_VALUE; } else { truncatedUpdateCount = (int) this.updateCount; } this.lastInsertId = rs.getUpdateID(); return truncatedUpdateCount; } } /** * @see StatementImpl#executeUpdate(String, int) */ public int executeUpdate(String sql, int returnGeneratedKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS) { MySQLConnection locallyScopedConn = this.connection; // If this is a 'REPLACE' query, we need to be able to parse // the 'info' message returned from the server to determine // the actual number of keys generated. boolean readInfoMsgState = locallyScopedConn .isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); try { return executeUpdate(sql, false, true); } finally { locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState); } } return executeUpdate(sql); } } /** * @see StatementImpl#executeUpdate(String, int[]) */ public int executeUpdate(String sql, int[] generatedKeyIndices) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) { checkClosed(); MySQLConnection locallyScopedConn = this.connection; // If this is a 'REPLACE' query, we need to be able to parse // the 'info' message returned from the server to determine // the actual number of keys generated. boolean readInfoMsgState = locallyScopedConn .isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); try { return executeUpdate(sql, false, true); } finally { locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState); } } return executeUpdate(sql); } } /** * @see StatementImpl#executeUpdate(String, String[]) */ public int executeUpdate(String sql, String[] generatedKeyNames) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) { MySQLConnection locallyScopedConn = this.connection; // If this is a 'REPLACE' query, we need to be able to parse // the 'info' message returned from the server to determine // the actual number of keys generated. boolean readInfoMsgState = this.connection .isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); try { return executeUpdate(sql, false, true); } finally { locallyScopedConn.setReadInfoMsgEnabled(readInfoMsgState); } } return executeUpdate(sql); } } /** * Optimization to only use one calendar per-session, or calculate it for * each call, depending on user configuration */ protected Calendar getCalendarInstanceForSessionOrNew() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.connection != null) { return this.connection.getCalendarInstanceForSessionOrNew(); } // punt, no connection around return new GregorianCalendar(); } } /** * JDBC 2.0 Return the Connection that produced the Statement. * * @return the Connection that produced the Statement * * @throws SQLException * if an error occurs */ public java.sql.Connection getConnection() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.connection; } } /** * JDBC 2.0 Determine the fetch direction. * * @return the default fetch direction * * @exception SQLException * if a database-access error occurs */ public int getFetchDirection() throws SQLException { return java.sql.ResultSet.FETCH_FORWARD; } /** * JDBC 2.0 Determine the default fetch size. * * @return the number of rows to fetch at a time * * @throws SQLException * if an error occurs */ public int getFetchSize() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.fetchSize; } } /** * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! */ public java.sql.ResultSet getGeneratedKeys() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (!this.retrieveGeneratedKeys) { throw SQLError.createSQLException(Messages.getString("Statement.GeneratedKeysNotRequested"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (this.batchedGeneratedKeys == null) { if (lastQueryIsOnDupKeyUpdate) { return getGeneratedKeysInternal(1); } return getGeneratedKeysInternal(); } Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17); //$NON-NLS-1$ //$NON-NLS-2$ fields[0].setConnection(this.connection); this.generatedKeysResults = com.mysql.jdbc.ResultSetImpl.getInstance(this.currentCatalog, fields, new RowDataStatic(this.batchedGeneratedKeys), this.connection, this, false); return this.generatedKeysResults; } } /* * Needed because there's no concept of super.super to get to this * implementation from ServerPreparedStatement when dealing with batched * updates. */ protected java.sql.ResultSet getGeneratedKeysInternal() throws SQLException { int numKeys = getUpdateCount(); return getGeneratedKeysInternal(numKeys); } protected java.sql.ResultSet getGeneratedKeysInternal(int numKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17); //$NON-NLS-1$ //$NON-NLS-2$ fields[0].setConnection(this.connection); fields[0].setUseOldNameMetadata(true); ArrayList<ResultSetRow> rowSet = new ArrayList<ResultSetRow>(); long beginAt = getLastInsertID(); if (beginAt < 0) { // looking at an UNSIGNED BIGINT that has overflowed fields[0].setUnsigned(); } if (this.results != null) { String serverInfo = this.results.getServerInfo(); // // Only parse server info messages for 'REPLACE' // queries // if ((numKeys > 0) && (this.results.getFirstCharOfQuery() == 'R') && (serverInfo != null) && (serverInfo.length() > 0)) { numKeys = getRecordCountFromInfo(serverInfo); } if ((beginAt != 0 /* BIGINT UNSIGNED can wrap the protocol representation */) && (numKeys > 0)) { for (int i = 0; i < numKeys; i++) { byte[][] row = new byte[1][]; if (beginAt > 0) { row[0] = StringUtils.getBytes(Long.toString(beginAt)); } else { byte[] asBytes = new byte[8]; asBytes[7] = (byte) (beginAt & 0xff); asBytes[6] = (byte) (beginAt >>> 8); asBytes[5] = (byte) (beginAt >>> 16); asBytes[4] = (byte) (beginAt >>> 24); asBytes[3] = (byte) (beginAt >>> 32); asBytes[2] = (byte) (beginAt >>> 40); asBytes[1] = (byte) (beginAt >>> 48); asBytes[0] = (byte) (beginAt >>> 56); BigInteger val = new BigInteger(1, asBytes); row[0] = val.toString().getBytes(); } rowSet.add(new ByteArrayRow(row, getExceptionInterceptor())); beginAt += this.connection.getAutoIncrementIncrement(); } } } com.mysql.jdbc.ResultSetImpl gkRs = com.mysql.jdbc.ResultSetImpl.getInstance(this.currentCatalog, fields, new RowDataStatic(rowSet), this.connection, this, false); this.openResults.add(gkRs); return gkRs; } } /** * Returns the id used when profiling * * @return the id used when profiling. */ protected int getId() { return this.statementId; } /** * getLastInsertID returns the value of the auto_incremented key after an * executeQuery() or excute() call. * * <p> * This gets around the un-threadsafe behavior of "select LAST_INSERT_ID()" * which is tied to the Connection that created this Statement, and * therefore could have had many INSERTS performed before one gets a chance * to call "select LAST_INSERT_ID()". * </p> * * @return the last update ID. */ public long getLastInsertID() { try { synchronized (checkClosed().getConnectionMutex()) { return this.lastInsertId; } } catch (SQLException e) { throw new RuntimeException(e); // evolve interface to throw SQLException } } /** * getLongUpdateCount returns the current result as an update count, if the * result is a ResultSet or there are no more results, -1 is returned. It * should only be called once per result. * * <p> * This method returns longs as MySQL server versions newer than 3.22.4 * return 64-bit values for update counts * </p> * * @return the current update count. */ public long getLongUpdateCount() { try { synchronized (checkClosed().getConnectionMutex()) { if (this.results == null) { return -1; } if (this.results.reallyResult()) { return -1; } return this.updateCount; } } catch (SQLException e) { throw new RuntimeException(e); // evolve interface to throw SQLException } } /** * The maxFieldSize limit (in bytes) is the maximum amount of data returned * for any column value; it only applies to BINARY, VARBINARY, * LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR columns. If the limit is * exceeded, the excess data is silently discarded. * * @return the current max column size limit; zero means unlimited * * @exception SQLException * if a database access error occurs */ public int getMaxFieldSize() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.maxFieldSize; } } /** * The maxRows limit is set to limit the number of rows that any ResultSet * can contain. If the limit is exceeded, the excess rows are silently * dropped. * * @return the current maximum row limit; zero means unlimited * * @exception SQLException * if a database access error occurs */ public int getMaxRows() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.maxRows <= 0) { return 0; } return this.maxRows; } } /** * getMoreResults moves to a Statement's next result. If it returns true, * this result is a ResulSet. * * @return true if the next ResultSet is valid * * @exception SQLException * if a database access error occurs */ public boolean getMoreResults() throws SQLException { return getMoreResults(CLOSE_CURRENT_RESULT); } /** * @see StatementImpl#getMoreResults(int) */ public boolean getMoreResults(int current) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.results == null) { return false; } boolean streamingMode = createStreamingResultSet(); if (streamingMode) { if (this.results.reallyResult()) { while (this.results.next()); // need to drain remaining rows to get to server status // which tells us whether more results actually exist or not } } ResultSetInternalMethods nextResultSet = this.results.getNextResultSet(); switch (current) { case java.sql.Statement.CLOSE_CURRENT_RESULT: if (this.results != null) { if (!streamingMode) { this.results.close(); } this.results.clearNextResult(); } break; case java.sql.Statement.CLOSE_ALL_RESULTS: if (this.results != null) { if (!streamingMode) { this.results.close(); } this.results.clearNextResult(); } closeAllOpenResults(); break; case java.sql.Statement.KEEP_CURRENT_RESULT: if (!this.connection.getDontTrackOpenResources()) { this.openResults.add(this.results); } this.results.clearNextResult(); // nobody besides us should // ever need this value... break; default: throw SQLError.createSQLException(Messages .getString("Statement.19"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } this.results = nextResultSet; if (this.results == null) { this.updateCount = -1; this.lastInsertId = -1; } else if (this.results.reallyResult()) { this.updateCount = -1; this.lastInsertId = -1; } else { this.updateCount = this.results.getUpdateCount(); this.lastInsertId = this.results.getUpdateID(); } return ((this.results != null) && this.results.reallyResult()) ? true : false; } } /** * The queryTimeout limit is the number of seconds the driver will wait for * a Statement to execute. If the limit is exceeded, a SQLException is * thrown. * * @return the current query timeout limit in seconds; 0 = unlimited * * @exception SQLException * if a database access error occurs */ public int getQueryTimeout() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.timeoutInMillis / 1000; } } /** * Parses actual record count from 'info' message * * @param serverInfo * DOCUMENT ME! * * @return DOCUMENT ME! */ private int getRecordCountFromInfo(String serverInfo) { StringBuffer recordsBuf = new StringBuffer(); int recordsCount = 0; int duplicatesCount = 0; char c = (char) 0; int length = serverInfo.length(); int i = 0; for (; i < length; i++) { c = serverInfo.charAt(i); if (Character.isDigit(c)) { break; } } recordsBuf.append(c); i++; for (; i < length; i++) { c = serverInfo.charAt(i); if (!Character.isDigit(c)) { break; } recordsBuf.append(c); } recordsCount = Integer.parseInt(recordsBuf.toString()); StringBuffer duplicatesBuf = new StringBuffer(); for (; i < length; i++) { c = serverInfo.charAt(i); if (Character.isDigit(c)) { break; } } duplicatesBuf.append(c); i++; for (; i < length; i++) { c = serverInfo.charAt(i); if (!Character.isDigit(c)) { break; } duplicatesBuf.append(c); } duplicatesCount = Integer.parseInt(duplicatesBuf.toString()); return recordsCount - duplicatesCount; } /** * getResultSet returns the current result as a ResultSet. It should only be * called once per result. * * @return the current result set; null if there are no more * * @exception SQLException * if a database access error occurs (why?) */ public java.sql.ResultSet getResultSet() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return ((this.results != null) && this.results.reallyResult()) ? (java.sql.ResultSet) this.results : null; } } /** * JDBC 2.0 Determine the result set concurrency. * * @return CONCUR_UPDATABLE or CONCUR_READONLY * * @throws SQLException * if an error occurs */ public int getResultSetConcurrency() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.resultSetConcurrency; } } /** * @see StatementImpl#getResultSetHoldability() */ public int getResultSetHoldability() throws SQLException { return java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT; } protected ResultSetInternalMethods getResultSetInternal() { try { synchronized (checkClosed().getConnectionMutex()) { return this.results; } } catch (SQLException e) { return this.results; // you end up with the same thing as before, you'll get exception when actually trying to use it } } /** * JDBC 2.0 Determine the result set type. * * @return the ResultSet type (SCROLL_SENSITIVE or SCROLL_INSENSITIVE) * * @throws SQLException * if an error occurs. */ public int getResultSetType() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.resultSetType; } } /** * getUpdateCount returns the current result as an update count, if the * result is a ResultSet or there are no more results, -1 is returned. It * should only be called once per result. * * @return the current result as an update count. * * @exception SQLException * if a database access error occurs */ public int getUpdateCount() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.results == null) { return -1; } if (this.results.reallyResult()) { return -1; } int truncatedUpdateCount = 0; if (this.results.getUpdateCount() > Integer.MAX_VALUE) { truncatedUpdateCount = Integer.MAX_VALUE; } else { truncatedUpdateCount = (int) this.results.getUpdateCount(); } return truncatedUpdateCount; } } /** * The first warning reported by calls on this Statement is returned. A * Statement's execute methods clear its java.sql.SQLWarning chain. * Subsequent Statement warnings will be chained to this * java.sql.SQLWarning. * * <p> * The Warning chain is automatically cleared each time a statement is * (re)executed. * </p> * * <p> * <B>Note:</B> If you are processing a ResultSet then any warnings * associated with ResultSet reads will be chained on the ResultSet object. * </p> * * @return the first java.sql.SQLWarning or null * * @exception SQLException * if a database access error occurs */ public java.sql.SQLWarning getWarnings() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.clearWarningsCalled) { return null; } if (this.connection.versionMeetsMinimum(4, 1, 0)) { SQLWarning pendingWarningsFromServer = SQLError .convertShowWarningsToSQLWarnings(this.connection); if (this.warningChain != null) { this.warningChain.setNextWarning(pendingWarningsFromServer); } else { this.warningChain = pendingWarningsFromServer; } return this.warningChain; } return this.warningChain; } } /** * Closes this statement, and frees resources. * * @param calledExplicitly * was this called from close()? * * @throws SQLException * if an error occurs */ protected void realClose(boolean calledExplicitly, boolean closeOpenResults) throws SQLException { MySQLConnection locallyScopedConn; try { locallyScopedConn = checkClosed(); } catch (SQLException sqlEx) { return; // already closed } synchronized (locallyScopedConn.getConnectionMutex()) { if (this.useUsageAdvisor) { if (!calledExplicitly) { String message = Messages.getString("Statement.63") //$NON-NLS-1$ + Messages.getString("Statement.64"); //$NON-NLS-1$ this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_WARN, "", //$NON-NLS-1$ this.currentCatalog, this.connectionId, this.getId(), -1, System.currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, message)); } } if (closeOpenResults) { closeOpenResults = !this.holdResultsOpenOverClose; } if (closeOpenResults) { if (this.results != null) { try { this.results.close(); } catch (Exception ex) { ; } } if (this.generatedKeysResults != null) { try { this.generatedKeysResults.close(); } catch (Exception ex) { ; } } closeAllOpenResults(); } if (this.connection != null) { if (this.maxRowsChanged) { this.connection.unsetMaxRows(this); } if (!this.connection.getDontTrackOpenResources()) { this.connection.unregisterStatement(this); } } this.isClosed = true; this.results = null; this.generatedKeysResults = null; this.connection = null; this.warningChain = null; this.openResults = null; this.batchedGeneratedKeys = null; this.localInfileInputStream = null; this.pingTarget = null; } } /** * setCursorName defines the SQL cursor name that will be used by subsequent * execute methods. This name can then be used in SQL positioned * update/delete statements to identify the current row in the ResultSet * generated by this statement. If a database doesn't support positioned * update/delete, this method is a no-op. * * <p> * <b>Note:</b> This MySQL driver does not support cursors. * </p> * * @param name * the new cursor name * * @exception SQLException * if a database access error occurs */ public void setCursorName(String name) throws SQLException { // No-op } /** * If escape scanning is on (the default), the driver will do escape * substitution before sending the SQL to the database. * * @param enable * true to enable; false to disable * * @exception SQLException * if a database access error occurs */ public void setEscapeProcessing(boolean enable) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.doEscapeProcessing = enable; } } /** * JDBC 2.0 Give a hint as to the direction in which the rows in a result * set will be processed. The hint applies only to result sets created using * this Statement object. The default value is ResultSet.FETCH_FORWARD. * * @param direction * the initial direction for processing rows * * @exception SQLException * if a database-access error occurs or direction is not one * of ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or * ResultSet.FETCH_UNKNOWN */ public void setFetchDirection(int direction) throws SQLException { switch (direction) { case java.sql.ResultSet.FETCH_FORWARD: case java.sql.ResultSet.FETCH_REVERSE: case java.sql.ResultSet.FETCH_UNKNOWN: break; default: throw SQLError.createSQLException( Messages.getString("Statement.5"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } } /** * JDBC 2.0 Give the JDBC driver a hint as to the number of rows that should * be fetched from the database when more rows are needed. The number of * rows specified only affects result sets created using this statement. If * the value specified is zero, then the hint is ignored. The default value * is zero. * * @param rows * the number of rows to fetch * * @exception SQLException * if a database-access error occurs, or the condition 0 * <= rows <= this.getMaxRows() is not satisfied. */ public void setFetchSize(int rows) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (((rows < 0) && (rows != Integer.MIN_VALUE)) || ((this.maxRows != 0) && (this.maxRows != -1) && (rows > this .getMaxRows()))) { throw SQLError.createSQLException( Messages.getString("Statement.7"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$ } this.fetchSize = rows; } } public void setHoldResultsOpenOverClose(boolean holdResultsOpenOverClose) { try { synchronized (checkClosed().getConnectionMutex()) { this.holdResultsOpenOverClose = holdResultsOpenOverClose; } } catch (SQLException e) { // FIXME: can't break interface at this point } } /** * Sets the maxFieldSize * * @param max * the new max column size limit; zero means unlimited * * @exception SQLException * if size exceeds buffer size */ public void setMaxFieldSize(int max) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (max < 0) { throw SQLError.createSQLException(Messages .getString("Statement.11"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } int maxBuf = (this.connection != null) ? this.connection .getMaxAllowedPacket() : MysqlIO.getMaxBuf(); if (max > maxBuf) { throw SQLError.createSQLException(Messages.getString( "Statement.13", //$NON-NLS-1$ new Object[] { Long.valueOf(maxBuf) }), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } this.maxFieldSize = max; } } /** * Set the maximum number of rows * * @param max * the new max rows limit; zero means unlimited * * @exception SQLException * if a database access error occurs * * @see getMaxRows */ public void setMaxRows(int max) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if ((max > MysqlDefs.MAX_ROWS) || (max < 0)) { throw SQLError .createSQLException( Messages.getString("Statement.15") + max //$NON-NLS-1$ + " > " //$NON-NLS-1$ //$NON-NLS-2$ + MysqlDefs.MAX_ROWS + ".", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ //$NON-NLS-2$ } if (max == 0) { max = -1; } this.maxRows = max; this.maxRowsChanged = true; if (this.maxRows == -1) { this.connection.unsetMaxRows(this); this.maxRowsChanged = false; } else { // Most people don't use setMaxRows() // so don't penalize them // with the extra query it takes // to do it efficiently unless we need // to. this.connection.maxRowsChanged(this); } } } /** * Sets the queryTimeout limit * * @param seconds - * the new query timeout limit in seconds * * @exception SQLException * if a database access error occurs */ public void setQueryTimeout(int seconds) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (seconds < 0) { throw SQLError.createSQLException(Messages .getString("Statement.21"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } this.timeoutInMillis = seconds * 1000; } } /** * Sets the concurrency for result sets generated by this statement * * @param concurrencyFlag * DOCUMENT ME! */ void setResultSetConcurrency(int concurrencyFlag) { try { synchronized (checkClosed().getConnectionMutex()) { this.resultSetConcurrency = concurrencyFlag; } } catch (SQLException e) { // FIXME: Can't break interface atm, we'll get the exception later when // you try and do something useful with a closed statement... } } /** * Sets the result set type for result sets generated by this statement * * @param typeFlag * DOCUMENT ME! */ void setResultSetType(int typeFlag) { try { synchronized (checkClosed().getConnectionMutex()) { this.resultSetType = typeFlag; } } catch (SQLException e) { // FIXME: Can't break interface atm, we'll get the exception later when // you try and do something useful with a closed statement... } } protected void getBatchedGeneratedKeys(java.sql.Statement batchedStatement) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.retrieveGeneratedKeys) { java.sql.ResultSet rs = null; try { rs = batchedStatement.getGeneratedKeys(); while (rs.next()) { this.batchedGeneratedKeys .add(new ByteArrayRow(new byte[][] { rs.getBytes(1) }, getExceptionInterceptor())); } } finally { if (rs != null) { rs.close(); } } } } } protected void getBatchedGeneratedKeys(int maxKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.retrieveGeneratedKeys) { java.sql.ResultSet rs = null; try { if (maxKeys == 0) rs = getGeneratedKeysInternal(); else rs = getGeneratedKeysInternal(maxKeys); while (rs.next()) { this.batchedGeneratedKeys .add(new ByteArrayRow(new byte[][] { rs.getBytes(1) }, getExceptionInterceptor())); } } finally { if (rs != null) { rs.close(); } } } } } /** * @return */ private boolean useServerFetch() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.connection.isCursorFetchEnabled() && this.fetchSize > 0 && this.resultSetConcurrency == ResultSet.CONCUR_READ_ONLY && this.resultSetType == ResultSet.TYPE_FORWARD_ONLY; } } public boolean isClosed() throws SQLException { try { synchronized (checkClosed().getConnectionMutex()) { return this.isClosed; } } catch (SQLException sqlEx) { if (SQLError.SQL_STATE_CONNECTION_NOT_OPEN.equals(sqlEx.getSQLState())) { return true; } throw sqlEx; } } private boolean isPoolable = true; public boolean isPoolable() throws SQLException { return this.isPoolable; } public void setPoolable(boolean poolable) throws SQLException { this.isPoolable = poolable; } /** * Returns true if this either implements the interface argument or is directly or indirectly a wrapper * for an object that does. Returns false otherwise. If this implements the interface then return true, * else if this is a wrapper then return the result of recursively calling <code>isWrapperFor</code> on the wrapped * object. If this does not implement the interface and is not a wrapper, return false. * This method should be implemented as a low-cost operation compared to <code>unwrap</code> so that * callers can use this method to avoid expensive <code>unwrap</code> calls that may fail. If this method * returns true then calling <code>unwrap</code> with the same argument should succeed. * * @param interfaces a Class defining an interface. * @return true if this implements the interface or directly or indirectly wraps an object that does. * @throws java.sql.SQLException if an error occurs while determining whether this is a wrapper * for an object with the given interface. * @since 1.6 */ public boolean isWrapperFor(Class<?> iface) throws SQLException { checkClosed(); // This works for classes that aren't actually wrapping // anything return iface.isInstance(this); } /** * Returns an object that implements the given interface to allow access to non-standard methods, * or standard methods not exposed by the proxy. * The result may be either the object found to implement the interface or a proxy for that object. * If the receiver implements the interface then that is the object. If the receiver is a wrapper * and the wrapped object implements the interface then that is the object. Otherwise the object is * the result of calling <code>unwrap</code> recursively on the wrapped object. If the receiver is not a * wrapper and does not implement the interface, then an <code>SQLException</code> is thrown. * * @param iface A Class defining an interface that the result must implement. * @return an object that implements the interface. May be a proxy for the actual implementing object. * @throws java.sql.SQLException If no object found that implements the interface * @since 1.6 */ public Object unwrap(Class<?> iface) throws java.sql.SQLException { try { // This works for classes that aren't actually wrapping // anything return Util.cast(iface, this); } catch (ClassCastException cce) { throw SQLError.createSQLException("Unable to unwrap to " + iface.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } protected int findStartOfStatement(String sql) { int statementStartPos = 0; if (StringUtils.startsWithIgnoreCaseAndWs(sql, "/*")) { statementStartPos = sql.indexOf("*/"); if (statementStartPos == -1) { statementStartPos = 0; } else { statementStartPos += 2; } } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "--") || StringUtils.startsWithIgnoreCaseAndWs(sql, "#")) { statementStartPos = sql.indexOf('\n'); if (statementStartPos == -1) { statementStartPos = sql.indexOf('\r'); if (statementStartPos == -1) { statementStartPos = 0; } } } return statementStartPos; } private InputStream localInfileInputStream; protected final boolean version5013OrNewer; public InputStream getLocalInfileInputStream() { return this.localInfileInputStream; } public void setLocalInfileInputStream(InputStream stream) { this.localInfileInputStream = stream; } public void setPingTarget(PingTarget pingTarget) { this.pingTarget = pingTarget; } public ExceptionInterceptor getExceptionInterceptor() { return this.exceptionInterceptor; } protected boolean containsOnDuplicateKeyInString(String sql) { return getOnDuplicateKeyLocation(sql) != -1; } protected int getOnDuplicateKeyLocation(String sql) { return StringUtils.indexOfIgnoreCaseRespectMarker(0, sql, "ON DUPLICATE KEY UPDATE ", "\"'`", "\"'`", !this.connection.isNoBackslashEscapesSet()); } private boolean closeOnCompletion; public void closeOnCompletion() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { closeOnCompletion = true; } } public boolean isCloseOnCompletion() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return closeOnCompletion; } } }