blob: aaf720bb08830f57c550362ad0376069d1ccd330 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc;
import static org.mariadb.jdbc.util.constants.Capabilities.LOCAL_FILES;
import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
import org.mariadb.jdbc.client.ColumnDecoder;
import org.mariadb.jdbc.client.Completion;
import org.mariadb.jdbc.client.DataType;
import org.mariadb.jdbc.client.result.CompleteResult;
import org.mariadb.jdbc.client.result.Result;
import org.mariadb.jdbc.export.ExceptionFactory;
import org.mariadb.jdbc.message.client.QueryPacket;
import org.mariadb.jdbc.message.server.OkPacket;
import org.mariadb.jdbc.util.NativeSql;
import org.mariadb.jdbc.util.constants.ColumnFlags;
import org.mariadb.jdbc.util.constants.ServerStatus;
/** Statement implementation */
public class Statement implements java.sql.Statement {
private List<String> batchQueries;
/** result-set type */
protected final int resultSetType;
/** concurrency */
protected final int resultSetConcurrency;
/** thread safe locker */
protected final ReentrantLock lock;
/** can use server query timeout */
protected final boolean canUseServerTimeout;
/** can use server row limitation */
protected final boolean canUseServerMaxRows;
/** connection */
protected final Connection con;
/** required query timeout */
protected int queryTimeout;
/** maximum row number */
protected long maxRows;
/** fetch size */
protected int fetchSize;
/** automatic generated keys result required */
protected int autoGeneratedKeys;
/** close statement on resultset completion */
protected boolean closeOnCompletion;
/** closed flag */
protected boolean closed;
/** escape processing */
protected boolean escape;
/** last execution results */
protected List<Completion> results;
/** current results */
protected Completion currResult;
/** streaming load data infile data */
protected InputStream localInfileInputStream;
/**
* Constructor
*
* @param con connection
* @param lock thread safe locker
* @param canUseServerTimeout can use server timeout
* @param canUseServerMaxRows can use server row number limitation
* @param autoGeneratedKeys automatic generated keys result required
* @param resultSetType result-set type
* @param resultSetConcurrency concurrency
* @param defaultFetchSize fetch size
*/
public Statement(
Connection con,
ReentrantLock lock,
boolean canUseServerTimeout,
boolean canUseServerMaxRows,
int autoGeneratedKeys,
int resultSetType,
int resultSetConcurrency,
int defaultFetchSize) {
this.con = con;
this.lock = lock;
this.resultSetConcurrency = resultSetConcurrency;
this.resultSetType = resultSetType;
this.autoGeneratedKeys = autoGeneratedKeys;
this.canUseServerTimeout = canUseServerTimeout;
this.canUseServerMaxRows = canUseServerMaxRows;
this.fetchSize = defaultFetchSize;
}
private ExceptionFactory exceptionFactory() {
return con.getExceptionFactory().of(this);
}
/**
* Set current local infile stream
*
* @param inputStream stream
* @throws SQLException if statement is already closed
*/
public void setLocalInfileInputStream(InputStream inputStream) throws SQLException {
checkNotClosed();
localInfileInputStream = inputStream;
}
/**
* Executes the given SQL statement, which returns a single <code>ResultSet</code> object.
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql an SQL statement to be sent to the database, typically a static SQL <code>SELECT
* </code> statement
* @return a <code>ResultSet</code> object that contains the data produced by the given query;
* never <code>null</code>
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the given SQL statement produces anything other than a single
* <code>ResultSet</code> object, the method is called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>
* @throws java.sql.SQLTimeoutException when the driver has determined that the timeout value that
* was specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
*/
@Override
public ResultSet executeQuery(String sql) throws SQLException {
executeInternal(sql, Statement.NO_GENERATED_KEYS);
currResult = results.remove(0);
if (currResult instanceof Result) return (Result) currResult;
return new CompleteResult(new ColumnDecoder[0], new byte[0][], con.getContext());
}
/**
* Executes the given SQL statement, which may be an <code>INSERT</code>, <code>UPDATE</code>, or
* <code>DELETE</code> statement or an SQL statement that returns nothing, such as an SQL DDL
* statement.
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as <code>INSERT</code>,
* <code>UPDATE</code> or <code>DELETE</code>; or an SQL statement that returns nothing, such
* as a DDL statement.
* @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0
* for SQL statements that return nothing
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the given SQL statement produces a <code>ResultSet</code> object,
* the method is called on a <code>PreparedStatement</code> or <code>CallableStatement</code>
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
*/
@Override
public int executeUpdate(String sql) throws SQLException {
return executeUpdate(sql, Statement.NO_GENERATED_KEYS);
}
/**
* Releases this <code>Statement</code> object's database and JDBC resources immediately instead
* of waiting for this to happen when it is automatically closed. It is generally good practice to
* release resources as soon as you are finished with them to avoid tying up database resources.
*
* <p>Calling the method <code>close</code> on a <code>Statement</code> object that is already
* closed has no effect.
*
* <p><B>Note:</B>When a <code>Statement</code> object is closed, its current <code>ResultSet
* </code> object, if one exists, is also closed.
*
* @throws SQLException if a database access error occurs
*/
@Override
public void close() throws SQLException {
if (!closed) {
closed = true;
if (currResult != null && currResult instanceof Result) {
((Result) currResult).closeFromStmtClose(lock);
}
// close result-set
if (results != null && !results.isEmpty()) {
for (Completion completion : results) {
if (completion instanceof Result) {
((Result) completion).closeFromStmtClose(lock);
}
}
}
}
}
/**
* Abort current command result if streaming. result-set will be incomplete and closed, but ensure
* connection state
*/
public void abort() {
lock.lock();
try {
if (!closed) {
closed = true;
if (currResult != null && currResult instanceof Result) {
((Result) currResult).abort();
}
// close result-set
if (results != null) {
for (Completion completion : results) {
if (completion instanceof Result) {
((Result) completion).abort();
}
}
}
}
} finally {
lock.unlock();
}
}
/**
* Retrieves the maximum number of bytes that can be returned for character and binary column
* values in a <code>ResultSet</code> object produced by this <code>Statement</code> object. This
* limit applies only to <code>BINARY</code>, <code>VARBINARY</code>, <code>LONGVARBINARY</code>,
* <code>CHAR</code>, <code>VARCHAR</code>, <code>NCHAR</code>, <code>NVARCHAR</code>, <code>
* LONGNVARCHAR</code> and <code>LONGVARCHAR</code> columns. If the limit is exceeded, the excess
* data is silently discarded.
*
* @return the current column size limit for columns storing character and binary values; zero
* means there is no limit
* @see #setMaxFieldSize
*/
@Override
public int getMaxFieldSize() {
return 0;
}
/**
* NOT SUPPORTED.
*
* @see #getMaxFieldSize
*/
@Override
public void setMaxFieldSize(int max) {}
/**
* Retrieves the maximum number of rows that a <code>ResultSet</code> object produced by this
* <code>Statement</code> object can contain. If this limit is exceeded, the excess rows are
* silently dropped.
*
* @return the current maximum number of rows for a <code>ResultSet</code> object produced by this
* <code>Statement</code> object; zero means there is no limit
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @see #setMaxRows
*/
@Override
public int getMaxRows() throws SQLException {
checkNotClosed();
return (int) maxRows;
}
/**
* Sets the limit for the maximum number of rows that any <code>ResultSet</code> object generated
* by this <code>Statement</code> object can contain to the given number. If the limit is
* exceeded, the excess rows are silently dropped.
*
* @param max the new max rows limit; zero means there is no limit
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code> or the condition {@code max >= 0} is not satisfied
* @see #getMaxRows
*/
@Override
public void setMaxRows(int max) throws SQLException {
checkNotClosed();
if (max < 0) {
throw exceptionFactory().create("max rows cannot be negative : asked for " + max, "42000");
}
maxRows = max;
}
/**
* Sets escape processing on or off. If escape scanning is on (the default), the driver will do
* escape substitution before sending the SQL statement to the database.
*
* <p>The {@code Connection} and {@code DataSource} property {@code escapeProcessing} may be used
* to change the default escape processing behavior. A value of true (the default) enables escape
* Processing for all {@code Statement} objects. A value of false disables escape processing for
* all {@code Statement} objects. The {@code setEscapeProcessing} method may be used to specify
* the escape processing behavior for an individual {@code Statement} object.
*
* <p>Note: Since prepared statements have usually been parsed prior to making this call,
* disabling escape processing for <code>PreparedStatements</code> objects will have no effect.
*
* @param enable <code>true</code> to enable escape processing; <code>false</code> to disable it
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
*/
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
checkNotClosed();
this.escape = enable;
}
/**
* Retrieves the number of seconds the driver will wait for a <code>Statement</code> object to
* execute. If the limit is exceeded, a <code>SQLException</code> is thrown.
*
* @return the current query timeout limit in seconds; zero means there is no limit
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @see #setQueryTimeout
*/
@Override
public int getQueryTimeout() throws SQLException {
checkNotClosed();
return queryTimeout;
}
/**
* Sets the number of seconds the driver will wait for a <code>Statement</code> object to execute
* to the given number of seconds. By default, there is no limit on the amount of time allowed for
* a running statement to complete. If the limit is exceeded, an <code>SQLTimeoutException</code>
* is thrown. A JDBC driver must apply this limit to the <code>execute</code>, <code>executeQuery
* </code> and <code>executeUpdate</code> methods.
*
* <p><strong>Note:</strong> JDBC driver implementations may also apply this limit to {@code
* ResultSet} methods (consult your driver vendor documentation for details).
*
* <p><strong>Note:</strong> In the case of {@code Statement} batching, it is implementation
* defined whether the time-out is applied to individual SQL commands added via the {@code
* addBatch} method or to the entire batch of SQL commands invoked by the {@code executeBatch}
* method (consult your driver vendor documentation for details).
*
* @param seconds the new query timeout limit in seconds; zero means there is no limit
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code> or the condition {@code seconds >= 0} is not satisfied
* @see #getQueryTimeout
*/
@Override
public void setQueryTimeout(int seconds) throws SQLException {
if (seconds < 0) {
throw exceptionFactory()
.create("Query timeout cannot be negative : asked for " + seconds, "42000");
}
this.queryTimeout = seconds;
}
/**
* Cancels this <code>Statement</code> 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.
*
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
*/
@Override
public void cancel() throws SQLException {
checkNotClosed();
boolean locked = lock.tryLock();
// if any query is active, lock is set.
// this avoids trying to execute a KILL QUERY if no query is running.
if (!locked) {
con.cancelCurrentQuery();
} else {
lock.unlock();
}
}
/**
* Retrieves the first warning reported by calls on this <code>Statement</code> object. Subsequent
* <code>Statement</code> object warnings will be chained to this <code>SQLWarning</code> object.
*
* <p>The warning chain is automatically cleared each time a statement is (re)executed. This
* method may not be called on a closed <code>Statement</code> object; doing so will cause an
* <code>SQLException</code> to be thrown.
*
* <p><B>Note:</B> If you are processing a <code>ResultSet</code> object, any warnings associated
* with reads on that <code>ResultSet</code> object will be chained on it rather than on the
* <code>Statement</code> object that produced it.
*
* @return the first <code>SQLWarning</code> object or <code>null</code> if there are no warnings
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
*/
@Override
public SQLWarning getWarnings() throws SQLException {
return con.getWarnings();
}
/**
* Clears all the warnings reported on this <code>Statement</code> object. After a call to this
* method, the method <code>getWarnings</code> will return <code>null</code> until a new warning
* is reported for this <code>Statement</code> object.
*/
@Override
public void clearWarnings() {
con.getContext().setWarning(0);
}
/**
* Sets the SQL cursor name to the given <code>String</code>, which will be used by subsequent
* <code>Statement</code> object <code>execute</code> methods. This name can then be used in SQL
* positioned update or delete statements to identify the current row in the <code>ResultSet
* </code> object generated by this statement. If the database does not support positioned
* update/delete, this method is a noop. To ensure that a cursor has the proper isolation level to
* support updates, the cursor's <code>SELECT</code> statement should have the form <code>
* SELECT FOR UPDATE</code>. If <code>FOR UPDATE</code> is not present, positioned updates may
* fail.
*
* <p><B>Note:</B> By definition, the execution of positioned updates and deletes must be done by
* a different <code>Statement</code> object than the one that generated the <code>ResultSet
* </code> object being used for positioning. Also, cursor names must be unique within a
* connection.
*
* @param name the new cursor name, which must be unique within a connection
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
*/
@Override
public void setCursorName(String name) throws SQLException {
throw exceptionFactory().notSupported("Cursors are not supported");
}
/**
* Executes the given SQL statement, which may return multiple results. In some (uncommon)
* situations, a single SQL statement may return multiple result sets and/or update counts.
* Normally you can ignore this unless you are (1) executing a stored procedure that you know may
* return multiple results or (2) you are dynamically executing an unknown SQL string.
*
* <p>The <code>execute</code> method executes an SQL statement and indicates the form of the
* first result. You must then use the methods <code>getResultSet</code> or <code>getUpdateCount
* </code> to retrieve the result, and <code>getMoreResults</code> to move to any subsequent
* result(s).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql any SQL statement
* @return <code>true</code> if the first result is a <code>ResultSet</code> object; <code>false
* </code> if it is an update count or there are no results
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the method is called on a <code>PreparedStatement</code> or <code>
* CallableStatement</code>
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
*/
@Override
public boolean execute(String sql) throws SQLException {
return execute(sql, Statement.NO_GENERATED_KEYS);
}
/**
* Retrieves the current result as a <code>ResultSet</code> object. This method should be called
* only once per result.
*
* @return the current result as a <code>ResultSet</code> object or <code>null</code> if the
* result is an update count or there are no more results
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @see #execute
*/
@Override
public ResultSet getResultSet() throws SQLException {
checkNotClosed();
if (currResult instanceof Result) {
return (Result) currResult;
}
return null;
}
/**
* Retrieves the current result as an update count; if the result is a <code>ResultSet</code>
* object or there are no more results, -1 is returned. This method should be called only once per
* result.
*
* @return the current result as an update count; -1 if the current result is a <code>ResultSet
* </code> object or there are no more results
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @see #execute
*/
@Override
public int getUpdateCount() throws SQLException {
checkNotClosed();
if (currResult instanceof OkPacket) {
return (int) ((OkPacket) currResult).getAffectedRows();
}
return -1;
}
/**
* Moves to this <code>Statement</code> object's next result, returns <code>true</code> if it is a
* <code>ResultSet</code> object, and implicitly closes any current <code>ResultSet</code>
* object(s) obtained with the method <code>getResultSet</code>.
*
* <p>There are no more results when the following is true:
*
* <PRE>{@code
* // stmt is a Statement object
* ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
* }</PRE>
*
* @return <code>true</code> if the next result is a <code>ResultSet</code> object; <code>false
* </code> if it is an update count or there are no more results
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @see #execute
*/
@Override
public boolean getMoreResults() throws SQLException {
return getMoreResults(Statement.CLOSE_CURRENT_RESULT);
}
/**
* Retrieves the direction for fetching rows from database tables that is the default for result
* sets generated from this <code>Statement</code> object. If this <code>Statement</code> object
* has not set a fetch direction by calling the method <code>setFetchDirection</code>, the return
* value is implementation-specific.
*
* @return the default fetch direction for result sets generated from this <code>Statement</code>
* object
* @see #setFetchDirection
* @since 1.2
*/
@Override
public int getFetchDirection() {
return ResultSet.FETCH_FORWARD;
}
/**
* Gives the driver a hint as to the direction in which rows will be processed in <code>ResultSet
* </code> objects created using this <code>Statement</code> object. The default value is <code>
* ResultSet.FETCH_FORWARD</code>.
*
* <p>Note that this method sets the default fetch direction for result sets generated by this
* <code>Statement</code> object. Each result set has its own methods for getting and setting its
* own fetch direction.
*
* @param direction the initial direction for processing rows
*/
@Override
public void setFetchDirection(int direction) {
// not supported
}
/**
* Retrieves the number of result set rows that is the default fetch size for <code>ResultSet
* </code> objects generated from this <code>Statement</code> object. If this <code>Statement
* </code> object has not set a fetch size by calling the method <code>setFetchSize</code>, the
* return value is implementation-specific.
*
* @return the default fetch size for result sets generated from this <code>Statement</code>
* object
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @see #setFetchSize
*/
@Override
public int getFetchSize() throws SQLException {
checkNotClosed();
return this.fetchSize;
}
/**
* Gives the JDBC driver a hint as to the number of rows that should be fetched from the database
* when more rows are needed for <code>ResultSet</code> objects generated by this <code>Statement
* </code>. If the value specified is zero, then the hint is ignored. The default value is zero.
*
* @param rows the number of rows to fetch
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code> or the condition {@code rows >= 0} is not satisfied.
* @see #getFetchSize
* @since 1.2
*/
@Override
public void setFetchSize(int rows) throws SQLException {
if (rows < 0) {
throw exceptionFactory().create("invalid fetch size");
}
this.fetchSize = rows;
}
/**
* Retrieves the result set concurrency for <code>ResultSet</code> objects generated by this
* <code>Statement</code> object.
*
* @return either <code>ResultSet.CONCUR_READ_ONLY</code> or <code>ResultSet.CONCUR_UPDATABLE
* </code>
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
*/
@Override
public int getResultSetConcurrency() throws SQLException {
checkNotClosed();
return this.resultSetConcurrency;
}
/**
* Retrieves the result set type for <code>ResultSet</code> objects generated by this <code>
* Statement</code> object.
*
* @return one of <code>ResultSet.TYPE_FORWARD_ONLY</code>, <code>
* ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
* @since 1.2
*/
@Override
public int getResultSetType() {
return this.resultSetType;
}
/**
* Adds the given SQL command to the current list of commands for this <code>Statement</code>
* object. The commands in this list can be executed as a batch by calling the method <code>
* executeBatch</code>.
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql typically this is an SQL <code>INSERT</code> or <code>UPDATE</code> statement
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the driver does not support batch updates, the method is called on
* a <code>PreparedStatement</code> or <code>CallableStatement</code>
* @see #executeBatch
* @see DatabaseMetaData#supportsBatchUpdates
*/
@Override
public void addBatch(String sql) throws SQLException {
if (sql == null) {
throw exceptionFactory().create("null cannot be set to addBatch(String sql)");
}
if (batchQueries == null) batchQueries = new ArrayList<>();
batchQueries.add(escape ? NativeSql.parse(sql, con.getContext()) : sql);
}
/**
* Empties this <code>Statement</code> object's current list of SQL commands.
*
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code> or the driver does not support batch updates
* @see #addBatch
* @see DatabaseMetaData#supportsBatchUpdates
* @since 1.2
*/
@Override
public void clearBatch() throws SQLException {
checkNotClosed();
if (batchQueries == null) {
batchQueries = new ArrayList<>();
} else {
batchQueries.clear();
}
}
/**
* Submits a batch of commands to the database for execution and if all commands execute
* successfully, returns an array of update counts. The <code>int</code> elements of the array
* that is returned are ordered to correspond to the commands in the batch, which are ordered
* according to the order in which they were added to the batch. The elements in the array
* returned by the method <code>executeBatch</code> may be one of the following:
*
* <OL>
* <LI>A number greater than or equal to zero -- indicates that the command was processed
* successfully and is an update count giving the number of rows in the database that were
* affected by the command's execution
* <LI>A value of <code>SUCCESS_NO_INFO</code> -- indicates that the command was processed
* successfully but that the number of rows affected is unknown
* <p>If one of the commands in a batch update fails to execute properly, this method throws
* a <code>BatchUpdateException</code>, and a JDBC driver may or may not continue to process
* the remaining commands in the batch. However, the driver's behavior must be consistent
* with a particular DBMS, either always continuing to process commands or never continuing
* to process commands. If the driver continues processing after a failure, the array
* returned by the method <code>BatchUpdateException.getUpdateCounts</code> will contain as
* many elements as there are commands in the batch, and at least one of the elements will
* be the following:
* <LI>A value of <code>EXECUTE_FAILED</code> -- indicates that the command failed to execute
* successfully and occurs only if a driver continues to process commands after a command
* fails
* </OL>
*
* <p>The possible implementations and return values have been modified in the Java 2 SDK,
* Standard Edition, version 1.3 to accommodate the option of continuing to process commands in a
* batch update after a <code>BatchUpdateException</code> object has been thrown.
*
* @return an array of update counts containing one element for each command in the batch. The
* elements of the array are ordered according to the order in which commands were added to
* the batch.
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code> or the driver does not support batch statements. Throws {@link
* BatchUpdateException} (a subclass of <code>SQLException</code>) if one of the commands sent
* to the database fails to execute properly or attempts to return a result set.
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @see #addBatch
* @see DatabaseMetaData#supportsBatchUpdates
* @since 1.2
*/
@Override
public int[] executeBatch() throws SQLException {
checkNotClosed();
if (batchQueries == null || batchQueries.isEmpty()) return new int[0];
lock.lock();
try {
// ensure pipelining is possible (no LOAD DATA/XML INFILE commands)
boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
if (possibleLoadLocal) {
possibleLoadLocal = false;
for (int i = 0; i < batchQueries.size(); i++) {
String sql = batchQueries.get(i).toUpperCase(Locale.ROOT);
if (sql.contains(" LOCAL ") && sql.contains("LOAD") && sql.contains(" INFILE")) {
possibleLoadLocal = true;
break;
}
}
}
List<Completion> res =
possibleLoadLocal ? executeInternalBatchStandard() : executeInternalBatchPipeline();
results = res;
int[] updates = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
if (res.get(i) instanceof OkPacket) {
updates[i] = (int) ((OkPacket) res.get(i)).getAffectedRows();
} else {
updates[i] = org.mariadb.jdbc.Statement.SUCCESS_NO_INFO;
}
}
currResult = results.remove(0);
batchQueries.clear();
return updates;
} finally {
lock.unlock();
}
}
/**
* Retrieves the <code>Connection</code> object that produced this <code>Statement</code> object.
*
* @return the connection that produced this statement
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @since 1.2
*/
@Override
public Connection getConnection() throws SQLException {
checkNotClosed();
return con;
}
/**
* Moves to this <code>Statement</code> object's next result, deals with any current <code>
* ResultSet</code> object(s) according to the instructions specified by the given flag, and
* returns <code>true</code> if the next result is a <code>ResultSet</code> object.
*
* <p>There are no more results when the following is true:
*
* <PRE>{@code
* // stmt is a Statement object
* ((stmt.getMoreResults(current) == false) && (stmt.getUpdateCount() == -1))
* }</PRE>
*
* @param current one of the following <code>Statement</code> constants indicating what should
* happen to current <code>ResultSet</code> objects obtained using the method <code>
* getResultSet</code>: <code>Statement.CLOSE_CURRENT_RESULT</code>, <code>
* Statement.KEEP_CURRENT_RESULT</code>, or <code>Statement.CLOSE_ALL_RESULTS</code>
* @return <code>true</code> if the next result is a <code>ResultSet</code> object; <code>false
* </code> if it is an update count or there are no more results
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code> or the argument supplied is not one of the following: <code>
* Statement.CLOSE_CURRENT_RESULT</code>, <code>Statement.KEEP_CURRENT_RESULT</code> or <code>
* Statement.CLOSE_ALL_RESULTS</code>
* @throws SQLFeatureNotSupportedException if <code>DatabaseMetaData.supportsMultipleOpenResults
* </code> returns <code>false</code> and either <code>Statement.KEEP_CURRENT_RESULT</code> or
* <code>Statement.CLOSE_ALL_RESULTS</code> are supplied as the argument.
* @see #execute
* @since 1.4
*/
@Override
public boolean getMoreResults(int current) throws SQLException {
checkNotClosed();
if (currResult instanceof ResultSet) {
lock.lock();
try {
Result result = (Result) currResult;
if (current == java.sql.Statement.CLOSE_CURRENT_RESULT) {
result.close();
} else {
result.fetchRemaining();
}
if (result.streaming()
&& (con.getContext().getServerStatus() & ServerStatus.MORE_RESULTS_EXISTS) > 0) {
con.getClient()
.readStreamingResults(
results,
fetchSize,
maxRows,
resultSetConcurrency,
resultSetType,
closeOnCompletion);
}
} finally {
lock.unlock();
}
}
if (results.size() > 0) {
currResult = results.remove(0);
return (currResult instanceof Result);
}
currResult = null;
return false;
}
/**
* Permit to streaming result to fetch remaining results.
*
* @throws SQLException if socket error occurs.
*/
public void fetchRemaining() throws SQLException {
if (currResult != null && currResult instanceof ResultSet) {
Result result = (Result) currResult;
result.fetchRemaining();
if (result.streaming()
&& (con.getContext().getServerStatus() & ServerStatus.MORE_RESULTS_EXISTS) > 0) {
con.getClient()
.readStreamingResults(
results, 0, 0L, resultSetConcurrency, resultSetType, closeOnCompletion);
}
}
}
/**
* Retrieves any auto-generated keys created as a result of executing this <code>Statement</code>
* object. If this <code>Statement</code> object did not generate any keys, an empty <code>
* ResultSet</code> object is returned.
*
* <p><B>Note:</B>If the columns which represent the auto-generated keys were not specified, the
* JDBC driver implementation will determine the columns which best represent the auto-generated
* keys.
*
* @return a <code>ResultSet</code> object containing the auto-generated key(s) generated by the
* execution of this <code>Statement</code> object
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>Statement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @since 1.4
*/
@Override
public ResultSet getGeneratedKeys() throws SQLException {
checkNotClosed();
if (autoGeneratedKeys != java.sql.Statement.RETURN_GENERATED_KEYS) {
throw new SQLException(
"Cannot return generated keys: query was not set with Statement.RETURN_GENERATED_KEYS");
}
if (currResult instanceof OkPacket) {
OkPacket ok = ((OkPacket) currResult);
if (ok.getLastInsertId() != 0) {
List<String[]> insertIds = new ArrayList<>();
insertIds.add(new String[] {String.valueOf(ok.getLastInsertId())});
for (Completion result : results) {
if (result instanceof OkPacket) {
insertIds.add(new String[] {String.valueOf(((OkPacket) result).getLastInsertId())});
}
}
String[][] ids = insertIds.toArray(new String[0][]);
return CompleteResult.createResultSet(
"insert_id",
DataType.BIGINT,
ids,
con.getContext(),
ColumnFlags.AUTO_INCREMENT | ColumnFlags.UNSIGNED);
}
}
return new CompleteResult(new ColumnDecoder[0], new byte[0][], con.getContext());
}
/**
* Executes the given SQL statement and signals the driver with the given flag about whether the
* auto-generated keys produced by this <code>Statement</code> object should be made available for
* retrieval. The driver will ignore the flag if the SQL statement is not an <code>INSERT</code>
* statement, or an SQL statement able to return auto-generated keys (the list of such statements
* is vendor-specific).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as <code>INSERT</code>,
* <code>UPDATE</code> or <code>DELETE</code>; or an SQL statement that returns nothing, such
* as a DDL statement.
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be made available
* for retrieval; one of the following constants: <code>Statement.RETURN_GENERATED_KEYS</code>
* <code>Statement.NO_GENERATED_KEYS</code>
* @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0
* for SQL statements that return nothing
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the given SQL statement returns a <code>ResultSet</code> object,
* the given constant is not one of those allowed, the method is called on a <code>
* PreparedStatement</code> or <code>CallableStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method with a
* constant of Statement.RETURN_GENERATED_KEYS
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @since 1.4
*/
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
executeInternal(sql, autoGeneratedKeys);
currResult = results.remove(0);
if (currResult instanceof Result) {
throw exceptionFactory()
.create("the given SQL statement produces an unexpected ResultSet object", "HY000");
}
return (int) ((OkPacket) currResult).getAffectedRows();
}
private void executeInternal(String sql, int autoGeneratedKeys) throws SQLException {
checkNotClosed();
lock.lock();
try {
this.autoGeneratedKeys = autoGeneratedKeys;
String cmd = escapeTimeout(sql);
results =
con.getClient()
.execute(
new QueryPacket(cmd, localInfileInputStream),
this,
fetchSize,
maxRows,
resultSetConcurrency,
resultSetType,
closeOnCompletion,
false);
} finally {
localInfileInputStream = null;
lock.unlock();
}
}
/**
* Executes the given SQL statement and signals the driver that the auto-generated keys indicated
* in the given array should be made available for retrieval. This array contains the indexes of
* the columns in the target table that contain the auto-generated keys that should be made
* available. The driver will ignore the array if the SQL statement is not an <code>INSERT</code>
* statement, or an SQL statement able to return auto-generated keys (the list of such statements
* is vendor-specific).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as <code>INSERT</code>,
* <code>UPDATE</code> or <code>DELETE</code>; or an SQL statement that returns nothing, such
* as a DDL statement.
* @param columnIndexes an array of column indexes indicating the columns that should be returned
* from the inserted row
* @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0
* for SQL statements that return nothing
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the SQL statement returns a <code>ResultSet</code> object,the
* second argument supplied to this method is not an <code>int</code> array whose elements are
* valid column indexes, the method is called on a <code>PreparedStatement</code> or <code>
* CallableStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @since 1.4
*/
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
}
/**
* Executes the given SQL statement and signals the driver that the auto-generated keys indicated
* in the given array should be made available for retrieval. This array contains the names of the
* columns in the target table that contain the auto-generated keys that should be made available.
* The driver will ignore the array if the SQL statement is not an <code>INSERT</code> statement,
* or an SQL statement able to return auto-generated keys (the list of such statements is
* vendor-specific).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as <code>INSERT</code>,
* <code>UPDATE</code> or <code>DELETE</code>; or an SQL statement that returns nothing, such
* as a DDL statement.
* @param columnNames an array of the names of the columns that should be returned from the
* inserted row
* @return either the row count for <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE
* </code> statements, or 0 for SQL statements that return nothing
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the SQL statement returns a <code>ResultSet</code> object, the
* second argument supplied to this method is not a <code>String</code> array whose elements
* are valid column names, the method is called on a <code>PreparedStatement</code> or <code>
* CallableStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @since 1.4
*/
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
}
/**
* Executes the given SQL statement, which may return multiple results, and signals the driver
* that any auto-generated keys should be made available for retrieval. The driver will ignore
* this signal if the SQL statement is not an <code>INSERT</code> statement, or an SQL statement
* able to return auto-generated keys (the list of such statements is vendor-specific).
*
* <p>In some (uncommon) situations, a single SQL statement may return multiple result sets and/or
* update counts. Normally you can ignore this unless you are (1) executing a stored procedure
* that you know may return multiple results or (2) you are dynamically executing an unknown SQL
* string.
*
* <p>The <code>execute</code> method executes an SQL statement and indicates the form of the
* first result. You must then use the methods <code>getResultSet</code> or <code>getUpdateCount
* </code> to retrieve the result, and <code>getMoreResults</code> to move to any subsequent
* result(s).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql any SQL statement
* @param autoGeneratedKeys a constant indicating whether auto-generated keys should be made
* available for retrieval using the method <code>getGeneratedKeys</code>; one of the
* following constants: <code>Statement.RETURN_GENERATED_KEYS</code> or <code>
* Statement.NO_GENERATED_KEYS</code>
* @return <code>true</code> if the first result is a <code>ResultSet</code> object; <code>false
* </code> if it is an update count or there are no results
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the second parameter supplied to this method is not <code>
* Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>, the
* method is called on a <code>PreparedStatement</code> or <code>CallableStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method with a
* constant of Statement.RETURN_GENERATED_KEYS
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
* @see #getGeneratedKeys
* @since 1.4
*/
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
executeInternal(sql, autoGeneratedKeys);
currResult = results.remove(0);
return currResult instanceof Result;
}
/**
* Build sql command to execute :
*
* <ul>
* <li>Execute escape substitution if needed
* <li>add query timeout prefix if server permits it
* <li>add max row limit prefix if server permits it
* </ul>
*
* @param sql sql command
* @return sql command to execute
* @throws SQLException if fails to escape sql
*/
protected String escapeTimeout(final String sql) throws SQLException {
String escapedSql = escape ? NativeSql.parse(sql, con.getContext()) : sql;
if (queryTimeout != 0 && canUseServerTimeout) {
if (canUseServerMaxRows && maxRows > 0) {
return "SET STATEMENT max_statement_time="
+ queryTimeout
+ ", SQL_SELECT_LIMIT="
+ maxRows
+ " FOR "
+ escapedSql;
}
return "SET STATEMENT max_statement_time=" + queryTimeout + " FOR " + escapedSql;
}
if (canUseServerMaxRows && maxRows > 0) {
return "SET STATEMENT SQL_SELECT_LIMIT=" + maxRows + " FOR " + escapedSql;
}
return escapedSql;
}
/**
* Executes the given SQL statement, which may return multiple results, and signals the driver
* that the auto-generated keys indicated in the given array should be made available for
* retrieval. This array contains the indexes of the columns in the target table that contain the
* auto-generated keys that should be made available. The driver will ignore the array if the SQL
* statement is not an <code>INSERT</code> statement, or an SQL statement able to return
* auto-generated keys (the list of such statements is vendor-specific).
*
* <p>Under some (uncommon) situations, a single SQL statement may return multiple result sets
* and/or update counts. Normally you can ignore this unless you are (1) executing a stored
* procedure that you know may return multiple results or (2) you are dynamically executing an
* unknown SQL string.
*
* <p>The <code>execute</code> method executes an SQL statement and indicates the form of the
* first result. You must then use the methods <code>getResultSet</code> or <code>getUpdateCount
* </code> to retrieve the result, and <code>getMoreResults</code> to move to any subsequent
* result(s).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql any SQL statement
* @param columnIndexes an array of the indexes of the columns in the inserted row that should be
* made available for retrieval by a call to the method <code>getGeneratedKeys</code>
* @return <code>true</code> if the first result is a <code>ResultSet</code> object; <code>false
* </code> if it is an update count or there are no results
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>, the elements in the <code>int</code> array passed to this method
* are not valid column indexes, the method is called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
* @since 1.4
*/
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
return execute(sql, Statement.RETURN_GENERATED_KEYS);
}
/**
* Executes the given SQL statement, which may return multiple results, and signals the driver
* that the auto-generated keys indicated in the given array should be made available for
* retrieval. This array contains the names of the columns in the target table that contain the
* auto-generated keys that should be made available. The driver will ignore the array if the SQL
* statement is not an <code>INSERT</code> statement, or an SQL statement able to return
* auto-generated keys (the list of such statements is vendor-specific).
*
* <p>In some (uncommon) situations, a single SQL statement may return multiple result sets and/or
* update counts. Normally you can ignore this unless you are (1) executing a stored procedure
* that you know may return multiple results or (2) you are dynamically executing an unknown SQL
* string.
*
* <p>The <code>execute</code> method executes an SQL statement and indicates the form of the
* first result. You must then use the methods <code>getResultSet</code> or <code>getUpdateCount
* </code> to retrieve the result, and <code>getMoreResults</code> to move to any subsequent
* result(s).
*
* <p><strong>Note:</strong>This method cannot be called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>.
*
* @param sql any SQL statement
* @param columnNames an array of the names of the columns in the inserted row that should be made
* available for retrieval by a call to the method <code>getGeneratedKeys</code>
* @return <code>true</code> if the next result is a <code>ResultSet</code> object; <code>false
* </code> if it is an update count or there are no more results
* @throws SQLException if a database access error occurs, this method is called on a closed
* <code>Statement</code>,the elements of the <code>String</code> array passed to this method
* are not valid column names, the method is called on a <code>PreparedStatement</code> or
* <code>CallableStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @throws SQLTimeoutException when the driver has determined that the timeout value that was
* specified by the {@code setQueryTimeout} method has been exceeded and has at least
* attempted to cancel the currently running {@code Statement}
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
* @see #getGeneratedKeys
* @since 1.4
*/
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
return execute(sql, Statement.RETURN_GENERATED_KEYS);
}
/**
* Retrieves the result set holdability for <code>ResultSet</code> objects generated by this
* <code>Statement</code> object.
*
* @return either <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or <code>
* ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
* @since 1.4
*/
@Override
public int getResultSetHoldability() {
return ResultSet.HOLD_CURSORS_OVER_COMMIT;
}
/**
* Retrieves whether this <code>Statement</code> object has been closed. A <code>Statement</code>
* is closed if the method close has been called on it, or if it is automatically closed.
*
* @return true if this <code>Statement</code> object is closed; false if it is still open
*/
@Override
public boolean isClosed() {
return closed;
}
/**
* Returns a value indicating whether the <code>Statement</code> is poolable or not.
*
* @return <code>true</code> if the <code>Statement</code> is poolable; <code>false</code>
* otherwise
* @throws SQLException if this method is called on a closed <code>Statement</code>
* @see java.sql.Statement#setPoolable(boolean) setPoolable(boolean)
*/
@Override
public boolean isPoolable() throws SQLException {
checkNotClosed();
return false;
}
/**
* Requests that a <code>Statement</code> be pooled or not pooled. The value specified is a hint
* to the statement pool implementation indicating whether the application wants the statement to
* be pooled. It is up to the statement pool manager whether the hint is used.
*
* <p>The poolable value of a statement is applicable to both internal statement caches
* implemented by the driver and external statement caches implemented by application servers and
* other applications.
*
* <p>By default, a <code>Statement</code> is not poolable when created, and a <code>
* PreparedStatement</code> and <code>CallableStatement</code> are poolable when created.
*
* @param poolable requests that the statement be pooled if true and that the statement not be
* pooled if false
* @throws SQLException if this method is called on a closed <code>Statement</code>
*/
@Override
public void setPoolable(boolean poolable) throws SQLException {
checkNotClosed();
}
/**
* Specifies that this {@code Statement} will be closed when all its dependent result sets are
* closed. If execution of the {@code Statement} does not produce any result sets, this method has
* no effect.
*
* <p><strong>Note:</strong> Multiple calls to {@code closeOnCompletion} do not toggle the effect
* on this {@code Statement}. However, a call to {@code closeOnCompletion} does affect both the
* subsequent execution of statements, and statements that currently have open, dependent, result
* sets.
*
* @throws SQLException if this method is called on a closed {@code Statement}
*/
@Override
public void closeOnCompletion() throws SQLException {
checkNotClosed();
this.closeOnCompletion = true;
}
/**
* Returns a value indicating whether this {@code Statement} will be closed when all its dependent
* result sets are closed.
*
* @return {@code true} if the {@code Statement} will be closed when all of its dependent result
* sets are closed; {@code false} otherwise
* @throws SQLException if this method is called on a closed {@code Statement}
*/
@Override
public boolean isCloseOnCompletion() throws SQLException {
checkNotClosed();
return closeOnCompletion;
}
/**
* Returns an object that implements the given interface to allow access to non-standard methods,
* or standard methods not exposed by the proxy.
*
* <p>If the receiver implements the interface then the result is the receiver or a proxy for the
* receiver. If the receiver is a wrapper and the wrapped object implements the interface then the
* result is the wrapped object or a proxy for the wrapped object. Otherwise, return the result of
* calling <code>unwrap</code> recursively on the wrapped object or a proxy for that result. 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. Maybe a proxy for the actual implementing
* object.
* @throws SQLException If no object found that implements the interface
*/
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (isWrapperFor(iface)) {
return (T) this;
}
throw exceptionFactory()
.create("The receiver is not a wrapper and does not implement the interface", "42000");
}
/**
* 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 iface a Class defining an interface.
* @return true if this implements the interface or directly or indirectly wraps an object that
* does.
*/
@Override
public boolean isWrapperFor(Class<?> iface) {
if (iface == null) return false;
return iface.isInstance(this);
}
/**
* Check if statement is closed, and throw exception if so.
*
* @throws SQLException if statement close
*/
protected void checkNotClosed() throws SQLException {
if (closed) {
throw exceptionFactory().create("Cannot do an operation on a closed statement");
}
}
/**
* Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL
* statement that returns nothing, such as an SQL DDL statement. This method should be used when
* the returned row count may exceed Integer.MAX_VALUE.
*
* @param sql sql command
* @return update counts
* @throws SQLException if any error occur during execution
*/
@Override
public long executeLargeUpdate(String sql) throws SQLException {
return executeLargeUpdate(sql, Statement.NO_GENERATED_KEYS);
}
/**
* Identical to executeLargeUpdate(String sql), with a flag that indicate that autoGeneratedKeys
* (primary key fields with "auto_increment") generated id's must be retrieved.
*
* <p>Those id's will be available using getGeneratedKeys() method.
*
* @param sql sql command
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be made available
* for retrieval; one of the following constants: Statement.RETURN_GENERATED_KEYS
* Statement.NO_GENERATED_KEYS
* @return update counts
* @throws SQLException if any error occur during execution
*/
@Override
public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
executeInternal(sql, autoGeneratedKeys);
currResult = results.remove(0);
if (currResult instanceof Result) {
throw exceptionFactory()
.create("the given SQL statement produces an unexpected ResultSet object", "HY000");
}
return ((OkPacket) currResult).getAffectedRows();
}
/**
* Identical to executeLargeUpdate(String sql, int autoGeneratedKeys) with autoGeneratedKeys =
* Statement.RETURN_GENERATED_KEYS set.
*
* @param sql sql command
* @param columnIndexes column Indexes
* @return update counts
* @throws SQLException if any error occur during execution
*/
@Override
public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
return executeLargeUpdate(sql, java.sql.Statement.RETURN_GENERATED_KEYS);
}
/**
* Identical to executeLargeUpdate(String sql, int autoGeneratedKeys) with autoGeneratedKeys =
* Statement.RETURN_GENERATED_KEYS set.
*
* @param sql sql command
* @param columnNames columns names
* @return update counts
* @throws SQLException if any error occur during execution
*/
@Override
public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
return executeLargeUpdate(sql, java.sql.Statement.RETURN_GENERATED_KEYS);
}
/**
* Retrieves the maximum number of rows that a ResultSet object produced by this Statement object
* can contain. If this limit is exceeded, the excess rows are silently dropped.
*
* @throws SQLException if this method is called on a closed Statement
* @return the current maximum number of rows for a ResultSet object produced by this Statement
* object; zero means there is no limit
*/
@Override
public long getLargeMaxRows() throws SQLException {
checkNotClosed();
return maxRows;
}
/**
* Sets the limit for the maximum number of rows that any ResultSet object generated by this
* Statement object can contain to the given number. If the limit is exceeded, the excess rows are
* silently dropped.
*
* @param max the new max rows limit; zero means there is no limit
* @throws SQLException if the condition max &gt;= 0 is not satisfied
*/
@Override
public void setLargeMaxRows(long max) throws SQLException {
checkNotClosed();
if (max < 0) {
throw exceptionFactory().create("max rows cannot be negative : asked for " + max, "42000");
}
maxRows = max;
}
/**
* Retrieves the current result as an update count; if the result is a ResultSet object or there
* are no more results, -1 is returned.
*
* @throws SQLException if this method is called on a closed Statement
* @return last update count
*/
@Override
public long getLargeUpdateCount() throws SQLException {
checkNotClosed();
if (currResult instanceof OkPacket) {
return (int) ((OkPacket) currResult).getAffectedRows();
}
return -1;
}
/**
* Execute batch, like executeBatch(), with returning results with long[]. For when row count may
* exceed Integer.MAX_VALUE.
*
* @return an array of update counts (one element for each command in the batch)
* @throws SQLException if a database error occur.
*/
@Override
public long[] executeLargeBatch() throws SQLException {
checkNotClosed();
if (batchQueries == null || batchQueries.isEmpty()) return new long[0];
lock.lock();
try {
// ensure pipelining is possible (no LOAD DATA/XML INFILE commands)
boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
if (possibleLoadLocal) {
for (int i = 0; i < batchQueries.size(); i++) {
String sql = batchQueries.get(i).toUpperCase(Locale.ROOT);
if (sql.contains(" LOCAL ") && sql.contains("LOAD") && sql.contains(" INFILE")) {
break;
}
}
possibleLoadLocal = false;
}
List<Completion> res =
possibleLoadLocal ? executeInternalBatchStandard() : executeInternalBatchPipeline();
results = res;
long[] updates = new long[res.size()];
for (int i = 0; i < res.size(); i++) {
updates[i] = ((OkPacket) res.get(i)).getAffectedRows();
}
currResult = results.remove(0);
batchQueries.clear();
return updates;
} finally {
lock.unlock();
}
}
/**
* Execute batch pipelining commands (sending all client message, then reading results) (batches
* cannot contain results-set, so cannot fill receiving socket buffer while sending buffer is
* full)
*
* @return results
* @throws SQLException if any error occurs
*/
public List<Completion> executeInternalBatchPipeline() throws SQLException {
QueryPacket[] packets = new QueryPacket[batchQueries.size()];
for (int i = 0; i < batchQueries.size(); i++) {
String sql = batchQueries.get(i);
packets[i] = new QueryPacket(sql);
}
return con.getClient()
.executePipeline(
packets,
this,
0,
0L,
ResultSet.CONCUR_READ_ONLY,
ResultSet.TYPE_FORWARD_ONLY,
closeOnCompletion,
false);
}
/**
* basic implementation Send batch query per query.
*
* @return results
* @throws SQLException if any error occurs
*/
public List<Completion> executeInternalBatchStandard() throws SQLException {
List<Completion> results = new ArrayList<>();
try {
for (String batchQuery : batchQueries) {
results.addAll(
con.getClient()
.execute(
new QueryPacket(batchQuery, localInfileInputStream),
this,
0,
0L,
ResultSet.CONCUR_READ_ONLY,
ResultSet.TYPE_FORWARD_ONLY,
closeOnCompletion,
false));
}
return results;
} catch (SQLException sqle) {
int[] updateCounts = new int[batchQueries.size()];
for (int i = 0; i < Math.min(results.size(), updateCounts.length); i++) {
Completion completion = results.get(i);
updateCounts[i] =
completion instanceof OkPacket ? (int) ((OkPacket) completion).getAffectedRows() : 0;
}
throw new BatchUpdateException(
sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), updateCounts, sqle);
} finally {
localInfileInputStream = null;
}
}
}