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

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

There is a newer version: 8.0.33
Show newest version
/*
 * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * 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 also distributed with certain software (including but not
 * limited to OpenSSL) 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 included with MySQL.
 *
 * 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.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.sql.BatchUpdateException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import com.mysql.cj.CancelQueryTask;
import com.mysql.cj.CharsetMapping;
import com.mysql.cj.Messages;
import com.mysql.cj.MysqlType;
import com.mysql.cj.NativeSession;
import com.mysql.cj.ParseInfo;
import com.mysql.cj.PingTarget;
import com.mysql.cj.Query;
import com.mysql.cj.Session;
import com.mysql.cj.SimpleQuery;
import com.mysql.cj.TransactionEventHandler;
import com.mysql.cj.conf.HostInfo;
import com.mysql.cj.conf.PropertyDefinitions;
import com.mysql.cj.conf.PropertyKey;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.exceptions.AssertionFailedException;
import com.mysql.cj.exceptions.CJException;
import com.mysql.cj.exceptions.CJOperationNotSupportedException;
import com.mysql.cj.exceptions.CJTimeoutException;
import com.mysql.cj.exceptions.ExceptionFactory;
import com.mysql.cj.exceptions.ExceptionInterceptor;
import com.mysql.cj.exceptions.MysqlErrorNumbers;
import com.mysql.cj.exceptions.OperationCancelledException;
import com.mysql.cj.exceptions.StatementIsClosedException;
import com.mysql.cj.jdbc.exceptions.MySQLStatementCancelledException;
import com.mysql.cj.jdbc.exceptions.MySQLTimeoutException;
import com.mysql.cj.jdbc.exceptions.SQLError;
import com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping;
import com.mysql.cj.jdbc.result.CachedResultSetMetaData;
import com.mysql.cj.jdbc.result.ResultSetFactory;
import com.mysql.cj.jdbc.result.ResultSetImpl;
import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
import com.mysql.cj.log.ProfilerEvent;
import com.mysql.cj.protocol.Message;
import com.mysql.cj.protocol.ProtocolEntityFactory;
import com.mysql.cj.protocol.Resultset;
import com.mysql.cj.protocol.Resultset.Type;
import com.mysql.cj.protocol.a.NativeConstants;
import com.mysql.cj.protocol.a.NativeMessageBuilder;
import com.mysql.cj.protocol.a.result.ByteArrayRow;
import com.mysql.cj.protocol.a.result.ResultsetRowsStatic;
import com.mysql.cj.result.DefaultColumnDefinition;
import com.mysql.cj.result.Field;
import com.mysql.cj.result.Row;
import com.mysql.cj.util.StringUtils;
import com.mysql.cj.util.Util;

/**
 * A Statement object is used for executing a static SQL statement and obtaining
 * the results produced by it.
 * 
 * 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.
 */
public class StatementImpl implements JdbcStatement {
    protected static final String PING_MARKER = "/* ping */";

    protected NativeMessageBuilder commandBuilder = new NativeMessageBuilder(); // TODO use shared builder

    public final static byte USES_VARIABLES_FALSE = 0;

    public final static byte USES_VARIABLES_TRUE = 1;

    public final static byte USES_VARIABLES_UNKNOWN = -1;

    /** The character encoding to use (if available) */
    protected String charEncoding = null;

    /** The connection that created us */
    protected volatile JdbcConnection connection = null;

    /** Should we process escape codes? */
    protected boolean doEscapeProcessing = true;

    /** 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 = (Integer) PropertyDefinitions.getPropertyDefinition(PropertyKey.maxAllowedPacket).getDefaultValue();

    /**
     * The maximum number of rows to return for this statement (-1 means _all_
     * rows)
     */
    public int maxRows = -1;

    /** Set of currently-open ResultSets */
    protected Set openResults = new HashSet<>();

    /** Are we in pedantic mode? */
    protected boolean pedantic = false;

    /** 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 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;

    /**
     * Should this statement hold results open over .close() irregardless of
     * connection's setting?
     */
    protected boolean holdResultsOpenOverClose = false;

    protected ArrayList batchedGeneratedKeys = null;

    protected boolean retrieveGeneratedKeys = false;

    protected boolean continueBatchOnError = false;

    protected PingTarget pingTarget = null;

    protected ExceptionInterceptor exceptionInterceptor;

    /** Whether or not the last query was of the form ON DUPLICATE KEY UPDATE */
    protected boolean lastQueryIsOnDupKeyUpdate = false;

    /** Are we currently closing results implicitly (internally)? */
    private boolean isImplicitlyClosingResults = false;

    protected RuntimeProperty dontTrackOpenResources;
    protected RuntimeProperty dumpQueriesOnException;
    protected boolean logSlowQueries = false;
    protected RuntimeProperty rewriteBatchedStatements;
    protected RuntimeProperty maxAllowedPacket;
    protected boolean dontCheckOnDuplicateKeyUpdateInSQL;
    protected RuntimeProperty sendFractionalSeconds;

    protected ResultSetFactory resultSetFactory;

    protected Query query;
    protected NativeSession session = null;

    /**
     * Constructor for a Statement.
     * 
     * @param c
     *            the Connection instance that creates us
     * @param db
     *            the database name in use when we were created
     * 
     * @throws SQLException
     *             if an error occurs.
     */
    public StatementImpl(JdbcConnection c, String db) throws SQLException {
        if ((c == null) || c.isClosed()) {
            throw SQLError.createSQLException(Messages.getString("Statement.0"), MysqlErrorNumbers.SQL_STATE_CONNECTION_NOT_OPEN, null);
        }

        this.connection = c;
        this.session = (NativeSession) c.getSession();
        this.exceptionInterceptor = c.getExceptionInterceptor();

        try {
            initQuery();
        } catch (CJException e) {
            throw SQLExceptionsMapping.translateException(e, getExceptionInterceptor());
        }

        this.query.setCurrentDatabase(db);

        JdbcPropertySet pset = c.getPropertySet();

        this.dontTrackOpenResources = pset.getBooleanProperty(PropertyKey.dontTrackOpenResources);
        this.dumpQueriesOnException = pset.getBooleanProperty(PropertyKey.dumpQueriesOnException);
        this.continueBatchOnError = pset.getBooleanProperty(PropertyKey.continueBatchOnError).getValue();
        this.pedantic = pset.getBooleanProperty(PropertyKey.pedantic).getValue();
        this.rewriteBatchedStatements = pset.getBooleanProperty(PropertyKey.rewriteBatchedStatements);
        this.charEncoding = pset.getStringProperty(PropertyKey.characterEncoding).getValue();
        this.profileSQL = pset.getBooleanProperty(PropertyKey.profileSQL).getValue();
        this.useUsageAdvisor = pset.getBooleanProperty(PropertyKey.useUsageAdvisor).getValue();
        this.logSlowQueries = pset.getBooleanProperty(PropertyKey.logSlowQueries).getValue();
        this.maxAllowedPacket = pset.getIntegerProperty(PropertyKey.maxAllowedPacket);
        this.dontCheckOnDuplicateKeyUpdateInSQL = pset.getBooleanProperty(PropertyKey.dontCheckOnDuplicateKeyUpdateInSQL).getValue();
        this.sendFractionalSeconds = pset.getBooleanProperty(PropertyKey.sendFractionalSeconds);
        this.doEscapeProcessing = pset.getBooleanProperty(PropertyKey.enableEscapeProcessing).getValue();

        this.maxFieldSize = this.maxAllowedPacket.getValue();

        if (!this.dontTrackOpenResources.getValue()) {
            c.registerStatement(this);
        }

        int defaultFetchSize = pset.getIntegerProperty(PropertyKey.defaultFetchSize).getValue();
        if (defaultFetchSize != 0) {
            setFetchSize(defaultFetchSize);
        }

        int maxRowsConn = pset.getIntegerProperty(PropertyKey.maxRows).getValue();

        if (maxRowsConn != -1) {
            setMaxRows(maxRowsConn);
        }

        this.holdResultsOpenOverClose = pset.getBooleanProperty(PropertyKey.holdResultsOpenOverStatementClose).getValue();

        this.resultSetFactory = new ResultSetFactory(this.connection, this);
    }

    protected void initQuery() {
        this.query = new SimpleQuery(this.session);
    }

    @Override
    public void addBatch(String sql) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if (sql != null) {
                this.query.addBatch(sql);
            }
        }
    }

    @Override
    public void addBatch(Object batch) {
        this.query.addBatch(batch);
    }

    @Override
    public List getBatchedArgs() {
        return this.query.getBatchedArgs();
    }

    @Override
    public void cancel() throws SQLException {
        if (!this.query.getStatementExecuting().get()) {
            return;
        }

        if (!this.isClosed && this.connection != null) {
            JdbcConnection cancelConn = null;
            java.sql.Statement cancelStmt = null;

            try {
                HostInfo hostInfo = this.session.getHostInfo();
                String database = hostInfo.getDatabase();
                String user = StringUtils.isNullOrEmpty(hostInfo.getUser()) ? "" : hostInfo.getUser();
                String password = StringUtils.isNullOrEmpty(hostInfo.getPassword()) ? "" : hostInfo.getPassword();
                NativeSession newSession = new NativeSession(this.session.getHostInfo(), this.session.getPropertySet());
                newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() {
                    @Override
                    public void transactionCompleted() {
                    }

                    @Override
                    public void transactionBegun() {
                    }
                });
                newSession.sendCommand(new NativeMessageBuilder().buildComQuery(newSession.getSharedSendPacket(), "KILL QUERY " + this.session.getThreadId()),
                        false, 0);
                setCancelStatus(CancelStatus.CANCELED_BY_USER);
            } catch (IOException e) {
                throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor);
            } 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
     * 
     * @return connection
     * @throws StatementIsClosedException
     *             if this statement has been closed
     */
    protected JdbcConnection checkClosed() {
        JdbcConnection c = this.connection;

        if (c == null) {
            throw ExceptionFactory.createException(StatementIsClosedException.class, Messages.getString("Statement.AlreadyClosed"), getExceptionInterceptor());
        }

        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") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "UPDATE")
                    || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DELETE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DROP")
                    || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "CREATE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "ALTER")
                    || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "TRUNCATE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "RENAME")) {
                throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            }
        }
    }

    /**
     * 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"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }

        if (sql.length() == 0) {
            throw SQLError.createSQLException(Messages.getString("Statement.61"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }
    }

    @Override
    public void clearBatch() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            this.query.clearBatchedArgs();
        }
    }

    @Override
    public void clearBatchedArgs() {
        this.query.clearBatchedArgs();
    }

    @Override
    public void clearWarnings() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            setClearWarningsCalled(true);
            this.warningChain = null;
            // TODO souldn't we also clear warnings from _server_ ?
        }
    }

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

* Note: A Statement is automatically closed when it is garbage collected. When a Statement is closed, its current ResultSet, if one exists, is also * closed. *

* * @exception SQLException * if a database access error occurs */ @Override public void close() throws SQLException { realClose(true, true); } /** * Close any open result sets that have been 'held open' * * @throws SQLException * if an error occurs */ protected void closeAllOpenResults() throws SQLException { JdbcConnection locallyScopedConn = this.connection; if (locallyScopedConn == null) { return; // already closed } synchronized (locallyScopedConn.getConnectionMutex()) { if (this.openResults != null) { for (ResultSetInternalMethods element : this.openResults) { try { element.realClose(false); } catch (SQLException sqlEx) { AssertionFailedException.shouldNotHappen(sqlEx); } } this.openResults.clear(); } } } /** * Close all result sets in this statement. This includes multi-results * * @throws SQLException * if a database access error occurs */ protected void implicitlyCloseAllOpenResults() throws SQLException { this.isImplicitlyClosingResults = true; try { if (!(this.holdResultsOpenOverClose || this.dontTrackOpenResources.getValue())) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } } finally { this.isImplicitlyClosingResults = false; } } @Override public void removeOpenResultSet(ResultSetInternalMethods rs) { try { synchronized (checkClosed().getConnectionMutex()) { if (this.openResults != null) { this.openResults.remove(rs); } boolean hasMoreResults = rs.getNextResultset() != null; // clear the current results or GGK results if (this.results == rs && !hasMoreResults) { this.results = null; } if (this.generatedKeysResults == rs) { this.generatedKeysResults = null; } // trigger closeOnCompletion if: // a) the result set removal wasn't triggered internally // b) there are no additional results if (!this.isImplicitlyClosingResults && !hasMoreResults) { checkAndPerformCloseOnCompletionAction(); } } } catch (StatementIsClosedException e) { // we can't break the interface, having this be no-op in case of error is ok } } @Override public int getOpenResultSetCount() { try { synchronized (checkClosed().getConnectionMutex()) { if (this.openResults != null) { return this.openResults.size(); } return 0; } } catch (StatementIsClosedException e) { // we can't break the interface, having this be no-op in case of error is ok return 0; } } /** * Check if all ResultSets generated by this statement are closed. If so, * close this statement. */ private void checkAndPerformCloseOnCompletionAction() { try { synchronized (checkClosed().getConnectionMutex()) { if (isCloseOnCompletion() && !this.dontTrackOpenResources.getValue() && getOpenResultSetCount() == 0 && (this.results == null || !this.results.hasRows() || this.results.isClosed()) && (this.generatedKeysResults == null || !this.generatedKeysResults.hasRows() || this.generatedKeysResults.isClosed())) { realClose(false, false); } } } catch (SQLException e) { } } /** * @param sql * query * @return result set * @throws SQLException * if a database access error occurs or this method is called on a closed Statement */ private ResultSetInternalMethods createResultSetUsingServerFetch(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { java.sql.PreparedStatement pStmt = this.connection.prepareStatement(sql, this.query.getResultType().getIntValue(), this.resultSetConcurrency); pStmt.setFetchSize(this.query.getResultFetchSize()); 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 = ((StatementImpl) pStmt).getResultSetInternal(); rs.setStatementUsedForFetchingRows((ClientPreparedStatement) 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() { return ((this.query.getResultType() == Type.FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this.query.getResultFetchSize() == Integer.MIN_VALUE)); } private Resultset.Type originalResultSetType = Type.FORWARD_ONLY; private int originalFetchSize = 0; @Override public void enableStreamingResults() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.originalResultSetType = this.query.getResultType(); this.originalFetchSize = this.query.getResultFetchSize(); setFetchSize(Integer.MIN_VALUE); setResultSetType(Type.FORWARD_ONLY); } } @Override public void disableStreamingResults() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.query.getResultFetchSize() == Integer.MIN_VALUE && this.query.getResultType() == Type.FORWARD_ONLY) { setFetchSize(this.originalFetchSize); setResultSetType(this.originalResultSetType); } } } /** * 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(). * * @param con * created this statement * @throws SQLException * if a database error occurs */ protected void setupStreamingTimeout(JdbcConnection con) throws SQLException { int netTimeoutForStreamingResults = this.session.getPropertySet().getIntegerProperty(PropertyKey.netTimeoutForStreamingResults).getValue(); if (createStreamingResultSet() && netTimeoutForStreamingResults > 0) { executeSimpleNonQuery(con, "SET net_write_timeout=" + netTimeoutForStreamingResults); } } @Override public CancelQueryTask startQueryTimer(Query stmtToCancel, int timeout) { return this.query.startQueryTimer(stmtToCancel, timeout); } @Override public void stopQueryTimer(CancelQueryTask timeoutTask, boolean rethrowCancelReason, boolean checkCancelTimeout) { this.query.stopQueryTimer(timeoutTask, rethrowCancelReason, checkCancelTimeout); } @Override public boolean execute(String sql) throws SQLException { return executeInternal(sql, false); } private boolean executeInternal(String sql, boolean returnGeneratedKeys) throws SQLException { JdbcConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { checkClosed(); checkNullOrEmptyQuery(sql); resetCancelledState(); implicitlyCloseAllOpenResults(); if (sql.charAt(0) == '/') { if (sql.startsWith(PING_MARKER)) { doPingInstead(); return true; } } char firstNonWsChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); boolean maybeSelect = firstNonWsChar == 'S'; this.retrieveGeneratedKeys = returnGeneratedKeys; this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys && firstNonWsChar == 'I' && containsOnDuplicateKeyInString(sql); if (!maybeSelect && locallyScopedConn.isReadOnly()) { throw SQLError.createSQLException(Messages.getString("Statement.27") + Messages.getString("Statement.28"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } try { setupStreamingTimeout(locallyScopedConn); if (this.doEscapeProcessing) { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getServerTimeZone(), this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; } CachedResultSetMetaData cachedMetaData = null; ResultSetInternalMethods rs = null; this.batchedGeneratedKeys = null; if (useServerFetch()) { rs = createResultSetUsingServerFetch(sql); } else { CancelQueryTask timeoutTask = null; String oldDb = null; try { timeoutTask = startQueryTimer(this, getTimeoutInMillis()); if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { oldDb = locallyScopedConn.getDatabase(); locallyScopedConn.setDatabase(getCurrentDatabase()); } // Check if we have cached metadata for this query... if (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { cachedMetaData = locallyScopedConn.getCachedMetaData(sql); } // Only apply max_rows to selects locallyScopedConn.setSessionMaxRows(maybeSelect ? this.maxRows : -1); statementBegins(); rs = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, null, createStreamingResultSet(), getResultSetFactory(), cachedMetaData, false); if (timeoutTask != null) { stopQueryTimer(timeoutTask, true, true); timeoutTask = null; } } catch (CJTimeoutException | OperationCancelledException e) { throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); } finally { stopQueryTimer(timeoutTask, false, false); if (oldDb != null) { locallyScopedConn.setDatabase(oldDb); } } } if (rs != null) { this.lastInsertId = rs.getUpdateID(); this.results = rs; rs.setFirstCharOfQuery(firstNonWsChar); if (rs.hasRows()) { if (cachedMetaData != null) { locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); } else if (this.session.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); } } } return ((rs != null) && rs.hasRows()); } finally { this.query.getStatementExecuting().set(false); } } } @Override public void statementBegins() { this.query.statementBegins(); } @Override public void resetCancelledState() { synchronized (checkClosed().getConnectionMutex()) { this.query.resetCancelledState(); } } @Override public boolean execute(String sql, int returnGeneratedKeys) throws SQLException { return executeInternal(sql, returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS); } @Override public boolean execute(String sql, int[] generatedKeyIndices) throws SQLException { return executeInternal(sql, generatedKeyIndices != null && generatedKeyIndices.length > 0); } @Override public boolean execute(String sql, String[] generatedKeyNames) throws SQLException { return executeInternal(sql, generatedKeyNames != null && generatedKeyNames.length > 0); } @Override public int[] executeBatch() throws SQLException { return Util.truncateAndConvertToInt(executeBatchInternal()); } protected long[] executeBatchInternal() throws SQLException { JdbcConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { if (locallyScopedConn.isReadOnly()) { throw SQLError.createSQLException(Messages.getString("Statement.34") + Messages.getString("Statement.35"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } implicitlyCloseAllOpenResults(); List batchedArgs = this.query.getBatchedArgs(); if (batchedArgs == null || batchedArgs.size() == 0) { return new long[0]; } // we timeout the entire batch, not individual statements int individualStatementTimeout = getTimeoutInMillis(); setTimeoutInMillis(0); CancelQueryTask 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.. long[] updateCounts = null; if (batchedArgs != null) { int nbrCommands = batchedArgs.size(); this.batchedGeneratedKeys = new ArrayList<>(batchedArgs.size()); boolean multiQueriesEnabled = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.allowMultiQueries).getValue(); if (multiQueriesEnabled || (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue() && nbrCommands > 4)) { return executeBatchUsingMultiQueries(multiQueriesEnabled, nbrCommands, individualStatementTimeout); } timeoutTask = startQueryTimer(this, individualStatementTimeout); updateCounts = new long[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) batchedArgs.get(commandIndex); updateCounts[commandIndex] = executeUpdateInternal(sql, true, true); if (timeoutTask != null) { // we need to check the cancel state on each iteration to generate timeout exception if needed checkCancelTimeout(); } // limit one generated key per OnDuplicateKey statement getBatchedGeneratedKeys(this.results.getFirstCharOfQuery() == 'I' && 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 { long[] newUpdateCounts = new long[commandIndex]; if (hasDeadlockOrTimeoutRolledBackTx(ex)) { for (int i = 0; i < newUpdateCounts.length; i++) { newUpdateCounts[i] = java.sql.Statement.EXECUTE_FAILED; } } else { System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex); } sqlEx = ex; break; //throw SQLError.createBatchUpdateException(ex, newUpdateCounts, getExceptionInterceptor()); } } } if (sqlEx != null) { throw SQLError.createBatchUpdateException(sqlEx, updateCounts, getExceptionInterceptor()); } } if (timeoutTask != null) { stopQueryTimer(timeoutTask, true, true); timeoutTask = null; } return (updateCounts != null) ? updateCounts : new long[0]; } finally { this.query.getStatementExecuting().set(false); } } finally { stopQueryTimer(timeoutTask, false, false); resetCancelledState(); setTimeoutInMillis(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 false; 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. * * @param multiQueriesEnabled * is multi-queries syntax allowed? * @param nbrCommands * number of queries in a batch * @param individualStatementTimeout * timeout for a single query in a batch * * @return update counts in the same manner as executeBatch() * @throws SQLException * if a database access error occurs or this method is called on a closed PreparedStatement */ private long[] executeBatchUsingMultiQueries(boolean multiQueriesEnabled, int nbrCommands, int individualStatementTimeout) throws SQLException { JdbcConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { if (!multiQueriesEnabled) { this.session.enableMultiQueries(); } java.sql.Statement batchStmt = null; CancelQueryTask timeoutTask = null; try { long[] updateCounts = new long[nbrCommands]; for (int i = 0; i < nbrCommands; i++) { updateCounts[i] = JdbcStatement.EXECUTE_FAILED; } int commandIndex = 0; StringBuilder queryBuf = new StringBuilder(); batchStmt = locallyScopedConn.createStatement(); timeoutTask = startQueryTimer((StatementImpl) batchStmt, individualStatementTimeout); int counter = 0; String connectionEncoding = locallyScopedConn.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(); int numberOfBytesPerChar = StringUtils.startsWithIgnoreCase(connectionEncoding, "utf") ? 3 : (CharsetMapping.isMultibyteCharset(connectionEncoding) ? 2 : 1); 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.query.getBatchedArgs().get(commandIndex); if (((((queryBuf.length() + nextQuery.length()) * numberOfBytesPerChar) + 1 /* for semicolon */ + NativeConstants.HEADER_LENGTH) * escapeAdjust) + 32 > this.maxAllowedPacket.getValue()) { try { batchStmt.execute(queryBuf.toString(), java.sql.Statement.RETURN_GENERATED_KEYS); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(commandIndex, argumentSetsInBatchSoFar, updateCounts, ex); } counter = processMultiCountsAndKeys((StatementImpl) batchStmt, counter, updateCounts); queryBuf = new StringBuilder(); argumentSetsInBatchSoFar = 0; } queryBuf.append(nextQuery); queryBuf.append(";"); argumentSetsInBatchSoFar++; } if (queryBuf.length() > 0) { try { batchStmt.execute(queryBuf.toString(), java.sql.Statement.RETURN_GENERATED_KEYS); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(commandIndex - 1, argumentSetsInBatchSoFar, updateCounts, ex); } counter = processMultiCountsAndKeys((StatementImpl) batchStmt, counter, updateCounts); } if (timeoutTask != null) { stopQueryTimer(timeoutTask, true, true); timeoutTask = null; } if (sqlEx != null) { throw SQLError.createBatchUpdateException(sqlEx, updateCounts, getExceptionInterceptor()); } return (updateCounts != null) ? updateCounts : new long[0]; } finally { stopQueryTimer(timeoutTask, false, false); resetCancelledState(); try { if (batchStmt != null) { batchStmt.close(); } } finally { if (!multiQueriesEnabled) { this.session.disableMultiQueries(); } } } } } protected int processMultiCountsAndKeys(StatementImpl batchedStatement, int updateCountCounter, long[] updateCounts) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { updateCounts[updateCountCounter++] = batchedStatement.getLargeUpdateCount(); 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.getLargeUpdateCount() != -1) { updateCounts[updateCountCounter++] = batchedStatement.getLargeUpdateCount(); 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, long[] updateCounts, SQLException ex) throws BatchUpdateException, SQLException { for (int j = endOfBatchIndex; j > endOfBatchIndex - numValuesPerBatch; j--) { updateCounts[j] = EXECUTE_FAILED; } if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) && !hasDeadlockOrTimeoutRolledBackTx(ex)) { return ex; } // else: throw the exception immediately long[] newUpdateCounts = new long[endOfBatchIndex]; System.arraycopy(updateCounts, 0, newUpdateCounts, 0, endOfBatchIndex); throw SQLError.createBatchUpdateException(ex, newUpdateCounts, getExceptionInterceptor()); } @Override public java.sql.ResultSet executeQuery(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { JdbcConnection locallyScopedConn = this.connection; this.retrieveGeneratedKeys = false; checkNullOrEmptyQuery(sql); resetCancelledState(); implicitlyCloseAllOpenResults(); if (sql.charAt(0) == '/') { if (sql.startsWith(PING_MARKER)) { doPingInstead(); return this.results; } } setupStreamingTimeout(locallyScopedConn); if (this.doEscapeProcessing) { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getServerTimeZone(), this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; } char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); checkForDml(sql, firstStatementChar); CachedResultSetMetaData cachedMetaData = null; if (useServerFetch()) { this.results = createResultSetUsingServerFetch(sql); return this.results; } CancelQueryTask timeoutTask = null; String oldDb = null; try { timeoutTask = startQueryTimer(this, getTimeoutInMillis()); if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { oldDb = locallyScopedConn.getDatabase(); locallyScopedConn.setDatabase(getCurrentDatabase()); } // // Check if we have cached metadata for this query... // if (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { cachedMetaData = locallyScopedConn.getCachedMetaData(sql); } locallyScopedConn.setSessionMaxRows(this.maxRows); statementBegins(); this.results = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, null, createStreamingResultSet(), getResultSetFactory(), cachedMetaData, false); if (timeoutTask != null) { stopQueryTimer(timeoutTask, true, true); timeoutTask = null; } } catch (CJTimeoutException | OperationCancelledException e) { throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); } finally { this.query.getStatementExecuting().set(false); stopQueryTimer(timeoutTask, false, false); if (oldDb != null) { locallyScopedConn.setDatabase(oldDb); } } this.lastInsertId = this.results.getUpdateID(); if (cachedMetaData != null) { locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); } else { if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); } } return this.results; } } protected void doPingInstead() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.pingTarget != null) { try { this.pingTarget.doPing(); } catch (SQLException e) { throw e; } catch (Exception e) { throw SQLError.createSQLException(e.getMessage(), MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE, e, getExceptionInterceptor()); } } else { this.connection.ping(); } ResultSetInternalMethods fakeSelectOneResultSet = generatePingResultSet(); this.results = fakeSelectOneResultSet; } } protected ResultSetInternalMethods generatePingResultSet() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { String encoding = this.session.getServerSession().getCharacterSetMetadata(); int collationIndex = this.session.getServerSession().getMetadataCollationIndex(); Field[] fields = { new Field(null, "1", collationIndex, encoding, MysqlType.BIGINT, 1) }; ArrayList rows = new ArrayList<>(); byte[] colVal = new byte[] { (byte) '1' }; rows.add(new ByteArrayRow(new byte[][] { colVal }, getExceptionInterceptor())); return this.resultSetFactory.createFromResultsetRows(ResultSet.CONCUR_READ_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, new ResultsetRowsStatic(rows, new DefaultColumnDefinition(fields))); } } public void executeSimpleNonQuery(JdbcConnection c, String nonQuery) throws SQLException { synchronized (c.getConnectionMutex()) { ((NativeSession) c.getSession()).execSQL(this, nonQuery, -1, null, false, getResultSetFactory(), null, false).close(); } } @Override public int executeUpdate(String sql) throws SQLException { return Util.truncateAndConvertToInt(executeLargeUpdate(sql)); } protected long executeUpdateInternal(String sql, boolean isBatch, boolean returnGeneratedKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { JdbcConnection locallyScopedConn = this.connection; checkNullOrEmptyQuery(sql); resetCancelledState(); char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); this.retrieveGeneratedKeys = returnGeneratedKeys; this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys && firstStatementChar == 'I' && containsOnDuplicateKeyInString(sql); ResultSetInternalMethods rs = null; if (this.doEscapeProcessing) { Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getServerTimeZone(), this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; } if (locallyScopedConn.isReadOnly(false)) { throw SQLError.createSQLException(Messages.getString("Statement.42") + Messages.getString("Statement.43"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) { throw SQLError.createSQLException(Messages.getString("Statement.46"), "01S03", getExceptionInterceptor()); } implicitlyCloseAllOpenResults(); // The checking and changing of databases must happen in sequence, so synchronize on the same mutex that _conn is using CancelQueryTask timeoutTask = null; String oldDb = null; try { timeoutTask = startQueryTimer(this, getTimeoutInMillis()); if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { oldDb = locallyScopedConn.getDatabase(); locallyScopedConn.setDatabase(getCurrentDatabase()); } // // Only apply max_rows to selects // locallyScopedConn.setSessionMaxRows(-1); statementBegins(); // null database: force read of field info on DML rs = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, -1, null, false, getResultSetFactory(), null, isBatch); if (timeoutTask != null) { stopQueryTimer(timeoutTask, true, true); timeoutTask = null; } } catch (CJTimeoutException | OperationCancelledException e) { throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); } finally { stopQueryTimer(timeoutTask, false, false); if (oldDb != null) { locallyScopedConn.setDatabase(oldDb); } if (!isBatch) { this.query.getStatementExecuting().set(false); } } this.results = rs; rs.setFirstCharOfQuery(firstStatementChar); this.updateCount = rs.getUpdateCount(); this.lastInsertId = rs.getUpdateID(); return this.updateCount; } } @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { return Util.truncateAndConvertToInt(executeLargeUpdate(sql, autoGeneratedKeys)); } @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { return Util.truncateAndConvertToInt(executeLargeUpdate(sql, columnIndexes)); } @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { return Util.truncateAndConvertToInt(executeLargeUpdate(sql, columnNames)); } @Override public java.sql.Connection getConnection() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.connection; } } @Override public int getFetchDirection() throws SQLException { return java.sql.ResultSet.FETCH_FORWARD; } @Override public int getFetchSize() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.query.getResultFetchSize(); } } @Override public java.sql.ResultSet getGeneratedKeys() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (!this.retrieveGeneratedKeys) { throw SQLError.createSQLException(Messages.getString("Statement.GeneratedKeysNotRequested"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (this.batchedGeneratedKeys == null) { if (this.lastQueryIsOnDupKeyUpdate) { return this.generatedKeysResults = getGeneratedKeysInternal(1); } return this.generatedKeysResults = getGeneratedKeysInternal(); } String encoding = this.session.getServerSession().getCharacterSetMetadata(); int collationIndex = this.session.getServerSession().getMetadataCollationIndex(); Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", collationIndex, encoding, MysqlType.BIGINT_UNSIGNED, 20); this.generatedKeysResults = this.resultSetFactory.createFromResultsetRows(ResultSet.CONCUR_READ_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, new ResultsetRowsStatic(this.batchedGeneratedKeys, new DefaultColumnDefinition(fields))); 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 ResultSetInternalMethods getGeneratedKeysInternal() throws SQLException { long numKeys = getLargeUpdateCount(); return getGeneratedKeysInternal(numKeys); } protected ResultSetInternalMethods getGeneratedKeysInternal(long numKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { String encoding = this.session.getServerSession().getCharacterSetMetadata(); int collationIndex = this.session.getServerSession().getMetadataCollationIndex(); Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", collationIndex, encoding, MysqlType.BIGINT_UNSIGNED, 20); ArrayList rowSet = new ArrayList<>(); long beginAt = getLastInsertID(); 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(); } } } ResultSetImpl gkRs = this.resultSetFactory.createFromResultsetRows(ResultSet.CONCUR_READ_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, new ResultsetRowsStatic(rowSet, new DefaultColumnDefinition(fields))); return gkRs; } } /** * getLastInsertID returns the value of the auto_incremented key after an * executeQuery() or excute() call. * *

* 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()". *

* * @return the last update ID. */ public long getLastInsertID() { synchronized (checkClosed().getConnectionMutex()) { return this.lastInsertId; } } /** * 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. * *

* This method returns longs as MySQL server returns 64-bit values for update counts *

* * @return the current update count. */ public long getLongUpdateCount() { synchronized (checkClosed().getConnectionMutex()) { if (this.results == null) { return -1; } if (this.results.hasRows()) { return -1; } return this.updateCount; } } @Override public int getMaxFieldSize() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.maxFieldSize; } } @Override public int getMaxRows() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.maxRows <= 0) { return 0; } return this.maxRows; } } @Override public boolean getMoreResults() throws SQLException { return getMoreResults(CLOSE_CURRENT_RESULT); } @Override public boolean getMoreResults(int current) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.results == null) { return false; } boolean streamingMode = createStreamingResultSet(); if (streamingMode) { if (this.results.hasRows()) { 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 = (ResultSetInternalMethods) this.results.getNextResultset(); switch (current) { case java.sql.Statement.CLOSE_CURRENT_RESULT: if (this.results != null) { if (!(streamingMode || this.dontTrackOpenResources.getValue())) { this.results.realClose(false); } this.results.clearNextResultset(); } break; case java.sql.Statement.CLOSE_ALL_RESULTS: if (this.results != null) { if (!(streamingMode || this.dontTrackOpenResources.getValue())) { this.results.realClose(false); } this.results.clearNextResultset(); } closeAllOpenResults(); break; case java.sql.Statement.KEEP_CURRENT_RESULT: if (!this.dontTrackOpenResources.getValue()) { this.openResults.add(this.results); } this.results.clearNextResultset(); // nobody besides us should // ever need this value... break; default: throw SQLError.createSQLException(Messages.getString("Statement.19"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } this.results = nextResultSet; if (this.results == null) { this.updateCount = -1; this.lastInsertId = -1; } else if (this.results.hasRows()) { this.updateCount = -1; this.lastInsertId = -1; } else { this.updateCount = this.results.getUpdateCount(); this.lastInsertId = this.results.getUpdateID(); } boolean moreResults = (this.results != null) && this.results.hasRows(); if (!moreResults) { checkAndPerformCloseOnCompletionAction(); } return moreResults; } } @Override public int getQueryTimeout() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return getTimeoutInMillis() / 1000; } } /** * Parses actual record count from 'info' message * * @param serverInfo * server info message * @return records count */ private long getRecordCountFromInfo(String serverInfo) { StringBuilder recordsBuf = new StringBuilder(); long recordsCount = 0; long 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 = Long.parseLong(recordsBuf.toString()); StringBuilder duplicatesBuf = new StringBuilder(); 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 = Long.parseLong(duplicatesBuf.toString()); return recordsCount - duplicatesCount; } @Override public java.sql.ResultSet getResultSet() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return ((this.results != null) && this.results.hasRows()) ? (java.sql.ResultSet) this.results : null; } } @Override public int getResultSetConcurrency() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.resultSetConcurrency; } } @Override public int getResultSetHoldability() throws SQLException { return java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT; } protected ResultSetInternalMethods getResultSetInternal() { try { synchronized (checkClosed().getConnectionMutex()) { return this.results; } } catch (StatementIsClosedException e) { return this.results; // you end up with the same thing as before, you'll get exception when actually trying to use it } } @Override public int getResultSetType() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.query.getResultType().getIntValue(); } } @Override public int getUpdateCount() throws SQLException { return Util.truncateAndConvertToInt(getLargeUpdateCount()); } @Override public java.sql.SQLWarning getWarnings() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (isClearWarningsCalled()) { return null; } SQLWarning pendingWarningsFromServer = this.session.getProtocol().convertShowWarningsToSQLWarnings(0, false); if (this.warningChain != null) { this.warningChain.setNextWarning(pendingWarningsFromServer); } else { this.warningChain = pendingWarningsFromServer; } return this.warningChain; } } /** * Closes this statement, and frees resources. * * @param calledExplicitly * was this called from close()? * @param closeOpenResults * should open result sets be closed? * * @throws SQLException * if an error occurs */ protected void realClose(boolean calledExplicitly, boolean closeOpenResults) throws SQLException { JdbcConnection locallyScopedConn = this.connection; if (locallyScopedConn == null || this.isClosed) { return; // already closed } // do it ASAP to reduce the chance of calling this method concurrently from ConnectionImpl.closeAllOpenStatements() if (!this.dontTrackOpenResources.getValue()) { locallyScopedConn.unregisterStatement(this); } if (this.useUsageAdvisor) { if (!calledExplicitly) { this.session.getProfilerEventHandler().processEvent(ProfilerEvent.TYPE_USAGE, this.session, this, null, 0, new Throwable(), Messages.getString("Statement.63")); } } if (closeOpenResults) { closeOpenResults = !(this.holdResultsOpenOverClose || this.dontTrackOpenResources.getValue()); } 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(); } this.isClosed = true; closeQuery(); this.results = null; this.generatedKeysResults = null; this.connection = null; this.session = null; this.warningChain = null; this.openResults = null; this.batchedGeneratedKeys = null; this.pingTarget = null; this.resultSetFactory = null; } @Override public void setCursorName(String name) throws SQLException { // No-op } @Override public void setEscapeProcessing(boolean enable) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.doEscapeProcessing = enable; } } @Override 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"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } @Override public void setFetchSize(int rows) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (((rows < 0) && (rows != Integer.MIN_VALUE)) || ((this.maxRows > 0) && (rows > this.getMaxRows()))) { throw SQLError.createSQLException(Messages.getString("Statement.7"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } this.query.setResultFetchSize(rows); } } @Override public void setHoldResultsOpenOverClose(boolean holdResultsOpenOverClose) { try { synchronized (checkClosed().getConnectionMutex()) { this.holdResultsOpenOverClose = holdResultsOpenOverClose; } } catch (StatementIsClosedException e) { // FIXME: can't break interface at this point } } @Override public void setMaxFieldSize(int max) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (max < 0) { throw SQLError.createSQLException(Messages.getString("Statement.11"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } int maxBuf = this.maxAllowedPacket.getValue(); if (max > maxBuf) { throw SQLError.createSQLException(Messages.getString("Statement.13", new Object[] { Long.valueOf(maxBuf) }), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } this.maxFieldSize = max; } } @Override public void setMaxRows(int max) throws SQLException { setLargeMaxRows(max); } @Override public void setQueryTimeout(int seconds) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (seconds < 0) { throw SQLError.createSQLException(Messages.getString("Statement.21"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } setTimeoutInMillis(seconds * 1000); } } /** * Sets the concurrency for result sets generated by this statement * * @param concurrencyFlag * concurrency flag * @throws SQLException * if a database access error occurs or this method is called on a closed PreparedStatement */ void setResultSetConcurrency(int concurrencyFlag) throws SQLException { try { synchronized (checkClosed().getConnectionMutex()) { this.resultSetConcurrency = concurrencyFlag; // updating resultset factory because concurrency is cached there this.resultSetFactory = new ResultSetFactory(this.connection, this); } } catch (StatementIsClosedException 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 * {@link com.mysql.cj.protocol.Resultset.Type} * @throws SQLException * if a database access error occurs or this method is called on a closed PreparedStatement */ void setResultSetType(Resultset.Type typeFlag) throws SQLException { try { synchronized (checkClosed().getConnectionMutex()) { this.query.setResultType(typeFlag); // updating resultset factory because type is cached there this.resultSetFactory = new ResultSetFactory(this.connection, this); } } catch (StatementIsClosedException e) { // FIXME: Can't break interface atm, we'll get the exception later when you try and do something useful with a closed statement... } } void setResultSetType(int typeFlag) throws SQLException { this.query.setResultType(Type.fromValue(typeFlag, Type.FORWARD_ONLY)); } 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 { rs = maxKeys == 0 ? getGeneratedKeysInternal() : getGeneratedKeysInternal(maxKeys); while (rs.next()) { this.batchedGeneratedKeys.add(new ByteArrayRow(new byte[][] { rs.getBytes(1) }, getExceptionInterceptor())); } } finally { this.isImplicitlyClosingResults = true; try { if (rs != null) { rs.close(); } } finally { this.isImplicitlyClosingResults = false; } } } } } private boolean useServerFetch() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.session.getPropertySet().getBooleanProperty(PropertyKey.useCursorFetch).getValue() && this.query.getResultFetchSize() > 0 && this.query.getResultType() == Type.FORWARD_ONLY; } } @Override public boolean isClosed() throws SQLException { JdbcConnection locallyScopedConn = this.connection; if (locallyScopedConn == null) { return true; } synchronized (locallyScopedConn.getConnectionMutex()) { return this.isClosed; } } private boolean isPoolable = false; @Override public boolean isPoolable() throws SQLException { checkClosed(); return this.isPoolable; } @Override public void setPoolable(boolean poolable) throws SQLException { checkClosed(); this.isPoolable = poolable; } @Override public boolean isWrapperFor(Class iface) throws SQLException { checkClosed(); // This works for classes that aren't actually wrapping anything return iface.isInstance(this); } @Override public T unwrap(Class iface) throws SQLException { try { // This works for classes that aren't actually wrapping anything return iface.cast(this); } catch (ClassCastException cce) { throw SQLError.createSQLException(Messages.getString("Common.UnableToUnwrap", new Object[] { iface.toString() }), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } protected static 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; } @Override public InputStream getLocalInfileInputStream() { return this.session.getLocalInfileInputStream(); } @Override public void setLocalInfileInputStream(InputStream stream) { this.session.setLocalInfileInputStream(stream); } @Override public void setPingTarget(PingTarget pingTarget) { this.pingTarget = pingTarget; } @Override public ExceptionInterceptor getExceptionInterceptor() { return this.exceptionInterceptor; } protected boolean containsOnDuplicateKeyInString(String sql) { return ParseInfo.getOnDuplicateKeyLocation(sql, this.dontCheckOnDuplicateKeyUpdateInSQL, this.rewriteBatchedStatements.getValue(), this.session.getServerSession().isNoBackslashEscapesSet()) != -1; } private boolean closeOnCompletion = false; @Override public void closeOnCompletion() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.closeOnCompletion = true; } } @Override public boolean isCloseOnCompletion() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.closeOnCompletion; } } @Override public long[] executeLargeBatch() throws SQLException { return executeBatchInternal(); } @Override public long executeLargeUpdate(String sql) throws SQLException { return executeUpdateInternal(sql, false, false); } @Override public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { return executeUpdateInternal(sql, false, autoGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS); } @Override public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { return executeUpdateInternal(sql, false, columnIndexes != null && columnIndexes.length > 0); } @Override public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { return executeUpdateInternal(sql, false, columnNames != null && columnNames.length > 0); } @Override public long getLargeMaxRows() throws SQLException { // Max rows is limited by MySQLDefs.MAX_ROWS anyway... return getMaxRows(); } @Override public long getLargeUpdateCount() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.results == null) { return -1; } if (this.results.hasRows()) { return -1; } return this.results.getUpdateCount(); } } @Override public void setLargeMaxRows(long max) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if ((max > MAX_ROWS) || (max < 0)) { throw SQLError.createSQLException(Messages.getString("Statement.15") + max + " > " + MAX_ROWS + ".", MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (max == 0) { max = -1; } this.maxRows = (int) max; } } @Override public String getCurrentDatabase() { return this.query.getCurrentDatabase(); } public long getServerStatementId() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, Messages.getString("Statement.65")); } @Override @SuppressWarnings("unchecked") public ProtocolEntityFactory getResultSetFactory() { return (ProtocolEntityFactory) this.resultSetFactory; } @Override public int getId() { return this.query.getId(); } @Override public void setCancelStatus(CancelStatus cs) { this.query.setCancelStatus(cs); } @Override public void checkCancelTimeout() { this.query.checkCancelTimeout(); } @Override public Session getSession() { return this.session; } @Override public Object getCancelTimeoutMutex() { return this.query.getCancelTimeoutMutex(); } @Override public void closeQuery() { if (this.query != null) { this.query.closeQuery(); } } @Override public int getResultFetchSize() { return this.query.getResultFetchSize(); } @Override public void setResultFetchSize(int fetchSize) { this.query.setResultFetchSize(fetchSize); } @Override public Resultset.Type getResultType() { return this.query.getResultType(); } @Override public void setResultType(Resultset.Type resultSetType) { this.query.setResultType(resultSetType); } @Override public int getTimeoutInMillis() { return this.query.getTimeoutInMillis(); } @Override public void setTimeoutInMillis(int timeoutInMillis) { this.query.setTimeoutInMillis(timeoutInMillis); } @Override public long getExecuteTime() { return this.query.getExecuteTime(); } @Override public void setExecuteTime(long executeTime) { this.query.setExecuteTime(executeTime); } @Override public AtomicBoolean getStatementExecuting() { return this.query.getStatementExecuting(); } @Override public void setCurrentDatabase(String currentDb) { this.query.setCurrentDatabase(currentDb); } @Override public boolean isClearWarningsCalled() { return this.query.isClearWarningsCalled(); } @Override public void setClearWarningsCalled(boolean clearWarningsCalled) { this.query.setClearWarningsCalled(clearWarningsCalled); } @Override public Query getQuery() { return this.query; } }