org.mariadb.jdbc.Statement Maven / Gradle / Ivy
Show all versions of mariadb-java-client Show documentation
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2023 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.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.ClientParser;
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 static final Pattern identifierPattern =
Pattern.compile("[0-9a-zA-Z$_\\u0080-\\uFFFF]*", Pattern.UNICODE_CASE | Pattern.CANON_EQ);
private static final Pattern escapePattern = Pattern.compile("[\u0000'\"\b\n\r\t\u001A\\\\]");
private static final Map mapper = new HashMap<>();
static {
mapper.put("\u0000", "\\0");
mapper.put("'", "\\\\'");
mapper.put("\"", "\\\\\"");
mapper.put("\b", "\\\\b");
mapper.put("\n", "\\\\n");
mapper.put("\r", "\\\\r");
mapper.put("\t", "\\\\t");
mapper.put("\u001A", "\\\\Z");
mapper.put("\\", "\\\\");
}
/** 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;
protected Boolean isInsertDuplicate = null;
/** required query timeout */
protected int queryTimeout;
/** maximum row number */
protected long maxRows;
protected String lastSql;
/** 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 results;
/** current results */
protected Completion currResult;
/** streaming load data infile data */
protected InputStream localInfileInputStream;
private List batchQueries;
/**
* 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 ResultSet
object.
*
* Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql an SQL statement to be sent to the database, typically a static SQL SELECT
*
statement
* @return a ResultSet
object that contains the data produced by the given query;
* never null
* @throws SQLException if a database access error occurs, this method is called on a closed
* Statement
, the given SQL statement produces anything other than a single
* ResultSet
object, the method is called on a PreparedStatement
or
* CallableStatement
* @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(), resultSetType);
}
/**
* 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.
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT
,
* UPDATE
or DELETE
; 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
* Statement
, the given SQL statement produces a ResultSet
object,
* the method is called on a PreparedStatement
or CallableStatement
* @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 Statement
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.
*
*
Calling the method close
on a Statement
object that is already
* closed has no effect.
*
*
Note:When a Statement
object is closed, its current ResultSet
*
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 ResultSet
object produced by this Statement
object. This
* limit applies only to BINARY
, VARBINARY
, LONGVARBINARY
,
* CHAR
, VARCHAR
, NCHAR
, NVARCHAR
,
* LONGNVARCHAR
and LONGVARCHAR
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 ResultSet
object produced by this
* Statement
object can contain. If this limit is exceeded, the excess rows are
* silently dropped.
*
* @return the current maximum number of rows for a ResultSet
object produced by this
* Statement
object; zero means there is no limit
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
* @see #setMaxRows
*/
@Override
public int getMaxRows() throws SQLException {
checkNotClosed();
return (int) 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 a database access error occurs, this method is called on a closed
* Statement
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.
*
*
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.
*
*
Note: Since prepared statements have usually been parsed prior to making this call,
* disabling escape processing for PreparedStatements
objects will have no effect.
*
* @param enable true
to enable escape processing; false
to disable it
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
*/
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
checkNotClosed();
this.escape = enable;
}
/**
* Retrieves the number of seconds the driver will wait for a Statement
object to
* execute. If the limit is exceeded, a SQLException
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
* Statement
* @see #setQueryTimeout
*/
@Override
public int getQueryTimeout() throws SQLException {
checkNotClosed();
return queryTimeout;
}
/**
* Sets the number of seconds the driver will wait for a Statement
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 SQLTimeoutException
* is thrown. A JDBC driver must apply this limit to the execute
, executeQuery
*
and executeUpdate
methods.
*
*
Note: JDBC driver implementations may also apply this limit to {@code
* ResultSet} methods (consult your driver vendor documentation for details).
*
*
Note: 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
* Statement
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 Statement
object if both the DBMS and driver support aborting an SQL
* statement. This method can be used by one thread to cancel a statement that is being executed
* by another thread.
*
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
* @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 Statement
object. Subsequent
* Statement
object warnings will be chained to this SQLWarning
object.
*
*
The warning chain is automatically cleared each time a statement is (re)executed. This
* method may not be called on a closed Statement
object; doing so will cause an
* SQLException
to be thrown.
*
*
Note: If you are processing a ResultSet
object, any warnings associated
* with reads on that ResultSet
object will be chained on it rather than on the
* Statement
object that produced it.
*
* @return the first SQLWarning
object or null
if there are no warnings
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
*/
@Override
public SQLWarning getWarnings() throws SQLException {
return con.getWarnings();
}
/**
* Clears all the warnings reported on this Statement
object. After a call to this
* method, the method getWarnings
will return null
until a new warning
* is reported for this Statement
object.
*/
@Override
public void clearWarnings() {
con.getContext().setWarning(0);
}
/**
* Sets the SQL cursor name to the given String
, which will be used by subsequent
* Statement
object execute
methods. This name can then be used in SQL
* positioned update or delete statements to identify the current row in the ResultSet
*
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 SELECT
statement should have the form
* SELECT FOR UPDATE
. If FOR UPDATE
is not present, positioned updates may
* fail.
*
*
Note: By definition, the execution of positioned updates and deletes must be done by
* a different Statement
object than the one that generated the ResultSet
*
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
* Statement
* @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.
*
*
The execute
method executes an SQL statement and indicates the form of the
* first result. You must then use the methods getResultSet
or getUpdateCount
*
to retrieve the result, and getMoreResults
to move to any subsequent
* result(s).
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql any SQL statement
* @return true
if the first result is a ResultSet
object; false
*
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
* Statement
, the method is called on a PreparedStatement
or
* CallableStatement
* @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 ResultSet
object. This method should be called
* only once per result.
*
* @return the current result as a ResultSet
object or null
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
* Statement
* @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 ResultSet
* 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 ResultSet
*
object or there are no more results
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
* @see #execute
*/
@Override
public int getUpdateCount() throws SQLException {
checkNotClosed();
if (currResult instanceof OkPacket) {
return (int) ((OkPacket) currResult).getAffectedRows();
}
return -1;
}
/**
* Moves to this Statement
object's next result, returns true
if it is a
* ResultSet
object, and implicitly closes any current ResultSet
* object(s) obtained with the method getResultSet
.
*
*
There are no more results when the following is true:
*
*
{@code
* // stmt is a Statement object
* ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
* }
*
* @return true
if the next result is a ResultSet
object; false
*
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
* Statement
* @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 Statement
object. If this Statement
object
* has not set a fetch direction by calling the method setFetchDirection
, the return
* value is implementation-specific.
*
* @return the default fetch direction for result sets generated from this Statement
* 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 ResultSet
*
objects created using this Statement
object. The default value is
* ResultSet.FETCH_FORWARD
.
*
* Note that this method sets the default fetch direction for result sets generated by this
* Statement
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 ResultSet
*
objects generated from this Statement
object. If this Statement
*
object has not set a fetch size by calling the method setFetchSize
, the
* return value is implementation-specific.
*
* @return the default fetch size for result sets generated from this Statement
* object
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
* @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 ResultSet
objects generated by this Statement
*
. If the value specified is zero, then the hint is ignored. The default value is zero.
*
* @param rows the number of rows to fetch
* @throws SQLException if a database access error occurs, this method is called on a closed
* Statement
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 ResultSet
objects generated by this
* Statement
object.
*
* @return either ResultSet.CONCUR_READ_ONLY
or ResultSet.CONCUR_UPDATABLE
*
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
*/
@Override
public int getResultSetConcurrency() throws SQLException {
checkNotClosed();
return this.resultSetConcurrency;
}
/**
* Retrieves the result set type for ResultSet
objects generated by this
* Statement
object.
*
* @return one of ResultSet.TYPE_FORWARD_ONLY
,
* ResultSet.TYPE_SCROLL_INSENSITIVE
, or ResultSet.TYPE_SCROLL_SENSITIVE
* @since 1.2
*/
@Override
public int getResultSetType() {
return this.resultSetType;
}
/**
* Adds the given SQL command to the current list of commands for this Statement
* object. The commands in this list can be executed as a batch by calling the method
* executeBatch
.
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql typically this is an SQL INSERT
or UPDATE
statement
* @throws SQLException if a database access error occurs, this method is called on a closed
* Statement
, the driver does not support batch updates, the method is called on
* a PreparedStatement
or CallableStatement
* @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 Statement
object's current list of SQL commands.
*
* @throws SQLException if a database access error occurs, this method is called on a closed
* Statement
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 int
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 executeBatch
may be one of the following:
*
*
* - 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
*
- A value of
SUCCESS_NO_INFO
-- indicates that the command was processed
* successfully but that the number of rows affected is unknown
* If one of the commands in a batch update fails to execute properly, this method throws
* a BatchUpdateException
, 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 BatchUpdateException.getUpdateCounts
will contain as
* many elements as there are commands in the batch, and at least one of the elements will
* be the following:
*
- A value of
EXECUTE_FAILED
-- indicates that the command failed to execute
* successfully and occurs only if a driver continues to process commands after a command
* fails
*
*
* 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 BatchUpdateException
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
* Statement
or the driver does not support batch statements. Throws {@link
* BatchUpdateException} (a subclass of SQLException
) 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 {
this.lastSql = batchQueries.get(0);
this.autoGeneratedKeys = java.sql.Statement.RETURN_GENERATED_KEYS;
// ensure pipelining is possible (no LOAD DATA/XML INFILE commands)
boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
if (possibleLoadLocal) {
possibleLoadLocal = false;
for (String batchQuery : batchQueries) {
String sql = batchQuery.toUpperCase(Locale.ROOT);
if (sql.contains(" LOCAL ") && sql.contains("LOAD") && sql.contains(" INFILE")) {
possibleLoadLocal = true;
break;
}
}
}
List 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);
return updates;
} catch (SQLException e) {
results = null;
currResult = null;
throw e;
} finally {
batchQueries.clear();
lock.unlock();
}
}
/**
* Retrieves the Connection
object that produced this Statement
object.
*
* @return the connection that produced this statement
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
* @since 1.2
*/
@Override
public Connection getConnection() throws SQLException {
checkNotClosed();
return con;
}
/**
* Moves to this Statement
object's next result, deals with any current
* ResultSet
object(s) according to the instructions specified by the given flag, and
* returns true
if the next result is a ResultSet
object.
*
* There are no more results when the following is true:
*
*
{@code
* // stmt is a Statement object
* ((stmt.getMoreResults(current) == false) && (stmt.getUpdateCount() == -1))
* }
*
* @param current one of the following Statement
constants indicating what should
* happen to current ResultSet
objects obtained using the method
* getResultSet
: Statement.CLOSE_CURRENT_RESULT
,
* Statement.KEEP_CURRENT_RESULT
, or Statement.CLOSE_ALL_RESULTS
* @return true
if the next result is a ResultSet
object; false
*
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
* Statement
or the argument supplied is not one of the following:
* Statement.CLOSE_CURRENT_RESULT
, Statement.KEEP_CURRENT_RESULT
or
* Statement.CLOSE_ALL_RESULTS
* @throws SQLFeatureNotSupportedException if DatabaseMetaData.supportsMultipleOpenResults
*
returns false
and either Statement.KEEP_CURRENT_RESULT
or
* Statement.CLOSE_ALL_RESULTS
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 Statement
* object. If this Statement
object did not generate any keys, an empty
* ResultSet
object is returned.
*
* Note: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 ResultSet
object containing the auto-generated key(s) generated by the
* execution of this Statement
object
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
* @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");
}
List insertIds = new ArrayList<>();
if (con.getContext().getConf().returnMultiValuesGeneratedIds()
&& !checkIfInsertDuplicateCommand(getLastSql())) {
// For compatibility with 2.x connector
long autoIncrement = con.getContext().getAutoIncrement();
if (currResult instanceof OkPacket) {
OkPacket ok = ((OkPacket) currResult);
if (ok.getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(ok.getLastInsertId())});
if (ok.getAffectedRows() > 1) {
for (int i = 1; i < ok.getAffectedRows(); i++) {
insertIds.add(
new String[] {String.valueOf(ok.getLastInsertId() + i * autoIncrement)});
}
}
}
}
if (results != null) {
for (Completion result : results) {
if (result instanceof OkPacket) {
OkPacket ok = ((OkPacket) result);
if (ok.getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(ok.getLastInsertId())});
if (ok.getAffectedRows() > 1) {
for (int i = 1; i < ok.getAffectedRows(); i++) {
insertIds.add(
new String[] {String.valueOf(ok.getLastInsertId() + i * autoIncrement)});
}
}
}
}
}
}
} else {
// standard behavior
if (currResult instanceof OkPacket && ((OkPacket) currResult).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) currResult).getLastInsertId())});
}
if (results != null) {
for (Completion result : results) {
if (result instanceof OkPacket && ((OkPacket) result).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) result).getLastInsertId())});
}
}
}
}
if (insertIds.isEmpty()) {
return new CompleteResult(
new ColumnDecoder[0], new byte[0][], con.getContext(), resultSetType);
}
String[][] ids = insertIds.toArray(new String[0][]);
return CompleteResult.createResultSet(
"insert_id",
DataType.BIGINT,
ids,
con.getContext(),
ColumnFlags.AUTO_INCREMENT | ColumnFlags.UNSIGNED,
resultSetType);
}
protected boolean checkIfInsertDuplicateCommand(String sql) {
if (isInsertDuplicate == null) {
if (sql == null) {
isInsertDuplicate = false;
} else {
ClientParser parser =
ClientParser.parameterParts(
sql, (con.getContext().getServerStatus() & ServerStatus.NO_BACKSLASH_ESCAPES) > 0);
isInsertDuplicate = parser.isInsertDuplicate();
}
}
return isInsertDuplicate;
}
/**
* Executes the given SQL statement and signals the driver with the given flag about whether the
* auto-generated keys produced by this Statement
object should be made available for
* retrieval. The driver will ignore the flag if the SQL statement is not an INSERT
* statement, or an SQL statement able to return auto-generated keys (the list of such statements
* is vendor-specific).
*
* Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT
,
* UPDATE
or DELETE
; 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: Statement.RETURN_GENERATED_KEYS
* Statement.NO_GENERATED_KEYS
* @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
* Statement
, the given SQL statement returns a ResultSet
object,
* the given constant is not one of those allowed, the method is called on a
* PreparedStatement
or CallableStatement
* @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.lastSql = sql;
this.autoGeneratedKeys = autoGeneratedKeys;
String cmd = escapeTimeout(sql);
results =
con.getClient()
.execute(
new QueryPacket(cmd, localInfileInputStream),
this,
fetchSize,
maxRows,
resultSetConcurrency,
resultSetType,
closeOnCompletion,
false);
} catch (SQLException e) {
results = null;
currResult = null;
throw e;
} 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 INSERT
* statement, or an SQL statement able to return auto-generated keys (the list of such statements
* is vendor-specific).
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT
,
* UPDATE
or DELETE
; 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
* Statement
, the SQL statement returns a ResultSet
object,the
* second argument supplied to this method is not an int
array whose elements are
* valid column indexes, the method is called on a PreparedStatement
or
* CallableStatement
* @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 INSERT
statement,
* or an SQL statement able to return auto-generated keys (the list of such statements is
* vendor-specific).
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT
,
* UPDATE
or DELETE
; 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 INSERT
, UPDATE
, or DELETE
*
statements, or 0 for SQL statements that return nothing
* @throws SQLException if a database access error occurs, this method is called on a closed
* Statement
, the SQL statement returns a ResultSet
object, the
* second argument supplied to this method is not a String
array whose elements
* are valid column names, the method is called on a PreparedStatement
or
* CallableStatement
* @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 INSERT
statement, or an SQL statement
* able to return auto-generated keys (the list of such statements is vendor-specific).
*
*
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.
*
*
The execute
method executes an SQL statement and indicates the form of the
* first result. You must then use the methods getResultSet
or getUpdateCount
*
to retrieve the result, and getMoreResults
to move to any subsequent
* result(s).
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @param sql any SQL statement
* @param autoGeneratedKeys a constant indicating whether auto-generated keys should be made
* available for retrieval using the method getGeneratedKeys
; one of the
* following constants: Statement.RETURN_GENERATED_KEYS
or
* Statement.NO_GENERATED_KEYS
* @return true
if the first result is a ResultSet
object; false
*
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
* Statement
, the second parameter supplied to this method is not
* Statement.RETURN_GENERATED_KEYS
or Statement.NO_GENERATED_KEYS
, the
* method is called on a PreparedStatement
or CallableStatement
* @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 :
*
*
* - Execute escape substitution if needed
*
- add query timeout prefix if server permits it
*
- add max row limit prefix if server permits it
*
*
* @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 INSERT
statement, or an SQL statement able to return
* auto-generated keys (the list of such statements is vendor-specific).
*
* 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.
*
*
The execute
method executes an SQL statement and indicates the form of the
* first result. You must then use the methods getResultSet
or getUpdateCount
*
to retrieve the result, and getMoreResults
to move to any subsequent
* result(s).
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @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 getGeneratedKeys
* @return true
if the first result is a ResultSet
object; false
*
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
* Statement
, the elements in the int
array passed to this method
* are not valid column indexes, the method is called on a PreparedStatement
or
* CallableStatement
* @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 INSERT
statement, or an SQL statement able to return
* auto-generated keys (the list of such statements is vendor-specific).
*
*
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.
*
*
The execute
method executes an SQL statement and indicates the form of the
* first result. You must then use the methods getResultSet
or getUpdateCount
*
to retrieve the result, and getMoreResults
to move to any subsequent
* result(s).
*
*
Note:This method cannot be called on a PreparedStatement
or
* CallableStatement
.
*
* @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 getGeneratedKeys
* @return true
if the next result is a ResultSet
object; false
*
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
* Statement
,the elements of the String
array passed to this method
* are not valid column names, the method is called on a PreparedStatement
or
* CallableStatement
* @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 ResultSet
objects generated by this
* Statement
object.
*
* @return either ResultSet.HOLD_CURSORS_OVER_COMMIT
or
* ResultSet.CLOSE_CURSORS_AT_COMMIT
* @since 1.4
*/
@Override
public int getResultSetHoldability() {
return ResultSet.HOLD_CURSORS_OVER_COMMIT;
}
/**
* Retrieves whether this Statement
object has been closed. A Statement
* is closed if the method close has been called on it, or if it is automatically closed.
*
* @return true if this Statement
object is closed; false if it is still open
*/
@Override
public boolean isClosed() {
return closed;
}
/**
* Returns a value indicating whether the Statement
is poolable or not.
*
* @return true
if the Statement
is poolable; false
* otherwise
* @throws SQLException if this method is called on a closed Statement
* @see java.sql.Statement#setPoolable(boolean) setPoolable(boolean)
*/
@Override
public boolean isPoolable() throws SQLException {
checkNotClosed();
return false;
}
/**
* Requests that a Statement
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.
*
*
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.
*
*
By default, a Statement
is not poolable when created, and a
* PreparedStatement
and CallableStatement
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 Statement
*/
@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.
*
*
Note: 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;
// set closeOnCompletion on last result-set
if (results != null && results.size() > 0) {
for (int i = results.size(); i > 0; i--) {
Completion completion = results.get(i - 1);
if (completion instanceof Result) {
((Result) completion).closeOnCompletion();
return;
}
}
} else {
if (currResult != null && currResult instanceof ResultSet) {
Result res = (Result) currResult;
if (res.streaming() || res.loaded()) {
res.closeOnCompletion();
}
}
}
}
/**
* 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.
*
*
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 unwrap
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 SQLException
*
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 unwrap(Class 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
* isWrapperFor
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
* unwrap
so that callers can use this method to avoid expensive unwrap
* calls that may fail. If this method returns true then calling unwrap
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.
*
* 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 >= 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 {
this.lastSql = batchQueries.get(0);
this.autoGeneratedKeys = java.sql.Statement.RETURN_GENERATED_KEYS;
// ensure pipelining is possible (no LOAD DATA/XML INFILE commands)
boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
if (possibleLoadLocal) {
possibleLoadLocal = false;
for (String batchQuery : batchQueries) {
String sql = batchQuery.toUpperCase(Locale.ROOT);
if (sql.contains(" LOCAL ") && sql.contains("LOAD") && sql.contains(" INFILE")) {
possibleLoadLocal = true;
break;
}
}
}
List 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);
return updates;
} catch (SQLException e) {
results = null;
currResult = null;
throw e;
} finally {
batchQueries.clear();
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 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 executeInternalBatchStandard() throws SQLException {
List 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;
}
}
/**
* Enquote String value.
*
* @param val string value to enquote
* @return enquoted string value
*/
// @Override when not supporting java 8
public String enquoteLiteral(String val) {
Matcher matcher = escapePattern.matcher(val);
StringBuffer escapedVal = new StringBuffer("'");
while (matcher.find()) {
matcher.appendReplacement(escapedVal, mapper.get(matcher.group()));
}
matcher.appendTail(escapedVal);
escapedVal.append("'");
return escapedVal.toString();
}
/**
* Escaped identifier according to MariaDB requirement.
*
* @param identifier identifier
* @param alwaysQuote indicate if identifier must be enquoted even if not necessary.
* @return return escaped identifier, quoted when necessary or indicated with alwaysQuote.
* @see mariadb identifier name
* @throws SQLException if containing u0000 character
*/
// @Override when not supporting java 8
public String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException {
if (isSimpleIdentifier(identifier)) {
return alwaysQuote ? "`" + identifier + "`" : identifier;
} else {
if (identifier.contains("\u0000")) {
throw exceptionFactory().create("Invalid name - containing u0000 character", "42000");
}
if (identifier.matches("^`.+`$")) {
identifier = identifier.substring(1, identifier.length() - 1);
}
return "`" + identifier.replace("`", "``") + "`";
}
}
/**
* Retrieves whether identifier is a simple SQL identifier. The first character is an alphabetic
* character from a through z, or from A through Z The string only contains alphanumeric
* characters or the characters "_" and "$"
*
* @param identifier identifier
* @return true if identifier doesn't have to be quoted
* @see mariadb identifier name
*/
// @Override when not supporting java 8
public boolean isSimpleIdentifier(String identifier) {
return identifier != null
&& !identifier.isEmpty()
&& identifierPattern.matcher(identifier).matches();
}
public String getLastSql() {
return lastSql;
}
/**
* Enquote utf8 value.
*
* @param val value to enquote
* @return enquoted String value
*/
// @Override when not supporting java 8
public String enquoteNCharLiteral(String val) {
return "N'" + val.replace("'", "''") + "'";
}
}