org.mariadb.jdbc.MariaDbStatement Maven / Gradle / Ivy
Show all versions of mariadb-java-client Show documentation
/*
*
* MariaDB Client for Java
*
* Copyright (c) 2012-2014 Monty Program Ab.
* Copyright (c) 2015-2019 MariaDB Ab.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to Monty Program Ab [email protected].
*
* This particular MariaDB Client for Java file is work
* derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
* the following copyright and notice provisions:
*
* Copyright (c) 2009-2011, Marcus Eriksson
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* Neither the name of the driver nor the names of its contributors may not be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
*/
package org.mariadb.jdbc;
import org.mariadb.jdbc.internal.com.read.dao.*;
import org.mariadb.jdbc.internal.com.read.resultset.*;
import org.mariadb.jdbc.internal.logging.*;
import org.mariadb.jdbc.internal.protocol.*;
import org.mariadb.jdbc.internal.util.*;
import org.mariadb.jdbc.internal.util.exceptions.*;
import org.mariadb.jdbc.internal.util.scheduler.*;
import org.mariadb.jdbc.util.*;
import java.io.*;
import java.nio.charset.*;
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.regex.*;
public class MariaDbStatement implements Statement, Cloneable {
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("\\", "\\\\");
}
// timeout scheduler
private static final Logger logger = LoggerFactory.getLogger(MariaDbStatement.class);
protected final ReentrantLock lock;
protected final int resultSetScrollType;
protected final int resultSetConcurrency;
protected final Options options;
protected final boolean canUseServerTimeout;
/** the protocol used to talk to the server. */
protected Protocol protocol;
/** the Connection object. */
protected MariaDbConnection connection;
protected volatile boolean closed = false;
protected int queryTimeout;
protected long maxRows;
protected Results results;
protected int fetchSize;
protected volatile boolean executing;
private ScheduledExecutorService timeoutScheduler;
// are warnings cleared?
private boolean warningsCleared;
private boolean mustCloseOnCompletion = false;
private List batchQueries;
private Future> timerTaskFuture;
private boolean isTimedout;
private int maxFieldSize;
/**
* Creates a new Statement.
*
* @param connection the connection to return in getConnection.
* @param resultSetScrollType one of the following ResultSet
constants:
* ResultSet.TYPE_FORWARD_ONLY
, ResultSet.TYPE_SCROLL_INSENSITIVE
, or
* ResultSet.TYPE_SCROLL_SENSITIVE
* @param resultSetConcurrency a concurrency type; one of ResultSet.CONCUR_READ_ONLY
* or ResultSet.CONCUR_UPDATABLE
*/
public MariaDbStatement(
MariaDbConnection connection, int resultSetScrollType, int resultSetConcurrency) {
this.protocol = connection.getProtocol();
this.connection = connection;
this.canUseServerTimeout = connection.canUseServerTimeout();
this.resultSetScrollType = resultSetScrollType;
this.resultSetConcurrency = resultSetConcurrency;
this.lock = this.connection.lock;
this.options = this.protocol.getOptions();
this.fetchSize = this.options.defaultFetchSize;
}
/**
* Clone statement.
*
* @param connection connection
* @return Clone statement.
* @throws CloneNotSupportedException if any error occur.
*/
public MariaDbStatement clone(MariaDbConnection connection) throws CloneNotSupportedException {
MariaDbStatement clone = (MariaDbStatement) super.clone();
clone.connection = connection;
clone.protocol = connection.getProtocol();
clone.timerTaskFuture = null;
clone.batchQueries = new ArrayList<>();
clone.closed = false;
clone.warningsCleared = true;
clone.maxRows = 0;
clone.fetchSize = this.options.defaultFetchSize;
return clone;
}
// Part of query prolog - setup timeout timer
protected void setTimerTask(boolean isBatch) {
assert (timerTaskFuture == null);
if (timeoutScheduler == null) {
timeoutScheduler = SchedulerServiceProviderHolder.getTimeoutScheduler();
}
timerTaskFuture =
timeoutScheduler.schedule(
() -> {
try {
isTimedout = true;
if (!isBatch) {
protocol.cancelCurrentQuery();
}
protocol.interrupt();
} catch (Throwable e) {
// eat
}
},
queryTimeout,
TimeUnit.SECONDS);
}
/**
* Command prolog.
*
*
* - clear previous query state
*
- launch timeout timer if needed
*
*
* @param isBatch is batch
* @throws SQLException if statement is closed
*/
protected void executeQueryPrologue(boolean isBatch) throws SQLException {
executing = true;
if (closed) {
throw new SQLException("execute() is called on closed statement");
}
protocol.prolog(maxRows, protocol.getProxy() != null, connection, this);
if (queryTimeout != 0 && (!canUseServerTimeout || isBatch)) {
setTimerTask(isBatch);
}
}
private void stopTimeoutTask() {
if (timerTaskFuture != null) {
if (!timerTaskFuture.cancel(true)) {
// could not cancel, task either started or already finished
// we must now wait for task to finish to ensure state modifications are done
try {
timerTaskFuture.get();
} catch (InterruptedException e) {
// reset interrupt status
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
// ignore error, likely due to interrupting during cancel
}
// we don't catch the exception if already canceled, that would indicate we tried
// to cancel in parallel (which this code currently is not designed for)
}
timerTaskFuture = null;
}
}
/**
* Reset timeout after query, re-throw SQL exception.
*
* @param sqle current exception
* @return SQLException exception with new message in case of timer timeout.
*/
protected SQLException executeExceptionEpilogue(SQLException sqle) {
// if has a failover, closing the statement
if (sqle.getSQLState() != null && sqle.getSQLState().startsWith("08")) {
try {
close();
} catch (SQLException sqlee) {
// eat exception
}
}
if (sqle.getErrorCode() == 1148 && !options.allowLocalInfile) {
return new SQLFeatureNotSupportedException(
"(conn:"
+ getServerThreadId()
+ ") Usage of LOCAL INFILE is disabled. "
+ "To use it enable it via the connection property allowLocalInfile=true",
"42000",
1148,
sqle);
}
if (isTimedout) {
return new SQLTimeoutException(
"(conn:" + getServerThreadId() + ") Query timed out", "JZ0002", 1317, sqle);
}
SQLException sqlException =
ExceptionMapper.getException(sqle, connection, this, queryTimeout != 0);
logger.error("error executing query", sqlException);
return sqlException;
}
protected void executeEpilogue() {
stopTimeoutTask();
isTimedout = false;
executing = false;
}
protected void executeBatchEpilogue() {
executing = false;
stopTimeoutTask();
isTimedout = false;
clearBatch();
}
private SQLException handleFailoverAndTimeout(SQLException sqle) {
// if has a failover, closing the statement
if (sqle.getSQLState() != null && sqle.getSQLState().startsWith("08")) {
try {
close();
} catch (SQLException sqlee) {
// eat exception
}
}
if (isTimedout) {
sqle =
new SQLTimeoutException(
"(conn:" + getServerThreadId() + ") Query timed out", "JZ0002", 1317, sqle);
}
return sqle;
}
protected BatchUpdateException executeBatchExceptionEpilogue(SQLException initialSqle, int size) {
SQLException sqle = handleFailoverAndTimeout(initialSqle);
int[] ret;
if (results == null || !results.commandEnd()) {
ret = new int[size];
Arrays.fill(ret, Statement.EXECUTE_FAILED);
} else {
ret = results.getCmdInformation().getUpdateCounts();
}
sqle = ExceptionMapper.getException(sqle, connection, this, queryTimeout != 0);
logger.error("error executing query", sqle);
return new BatchUpdateException(
sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), ret, sqle);
}
/**
* Executes a query.
*
* @param sql the query
* @param fetchSize fetch size
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be returned; one
* of Statement.RETURN_GENERATED_KEYS
or Statement.NO_GENERATED_KEYS
* @return true if there was a result set, false otherwise.
* @throws SQLException the error description
*/
private boolean executeInternal(String sql, int fetchSize, int autoGeneratedKeys)
throws SQLException {
lock.lock();
try {
executeQueryPrologue(false);
results =
new Results(
this,
fetchSize,
false,
1,
false,
resultSetScrollType,
resultSetConcurrency,
autoGeneratedKeys,
protocol.getAutoIncrementIncrement(),
sql,
null);
protocol.executeQuery(
protocol.isMasterConnection(), results, getTimeoutSql(Utils.nativeSql(sql, protocol)));
results.commandEnd();
return results.getResultSet() != null;
} catch (SQLException exception) {
throw executeExceptionEpilogue(exception);
} finally {
executeEpilogue();
lock.unlock();
}
}
/**
* Enquote String value.
*
* @param val string value to enquote
* @return enquoted string value
* @throws SQLException -not possible-
*/
public String enquoteLiteral(String val) throws SQLException {
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
*/
public String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException {
if (isSimpleIdentifier(identifier)) {
return alwaysQuote ? "`" + identifier + "`" : identifier;
} else {
if (identifier.contains("\u0000")) {
throw new SQLException("Invalid name - containing u0000 character");
}
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
* @throws SQLException exception
*/
public boolean isSimpleIdentifier(String identifier) throws SQLException {
return identifier != null
&& !identifier.isEmpty()
&& identifierPattern.matcher(identifier).matches();
}
/**
* Enquote utf8 value.
*
* @param val value to enquote
* @return enquoted String value
* @throws SQLException - not possible -
*/
public String enquoteNCharLiteral(String val) throws SQLException {
return "N'" + val.replace("'", "''") + "'";
}
private String getTimeoutSql(String sql) {
if (queryTimeout != 0 && canUseServerTimeout) {
return "SET STATEMENT max_statement_time=" + queryTimeout + " FOR " + sql;
}
return sql;
}
/**
* ! This method is for test only ! This permit sending query using specific charset.
*
* @param sql sql
* @param charset charset
* @return boolean if execution went well
* @throws SQLException if any exception occur
*/
public boolean testExecute(String sql, Charset charset) throws SQLException {
lock.lock();
try {
executeQueryPrologue(false);
results =
new Results(
this,
fetchSize,
false,
1,
false,
resultSetScrollType,
resultSetConcurrency,
Statement.NO_GENERATED_KEYS,
protocol.getAutoIncrementIncrement(),
sql,
null);
protocol.executeQuery(
protocol.isMasterConnection(),
results,
getTimeoutSql(Utils.nativeSql(sql, protocol)),
charset);
results.commandEnd();
return results.getResultSet() != null;
} catch (SQLException exception) {
throw executeExceptionEpilogue(exception);
} finally {
executeEpilogue();
lock.unlock();
}
}
/**
* executes a query.
*
* @param sql the query
* @return true if there was a result set, false otherwise.
* @throws SQLException if the query could not be sent to server
*/
public boolean execute(String sql) throws SQLException {
return executeInternal(sql, fetchSize, Statement.NO_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 getInternalMoreResults
to move to any
* subsequent result(s).
*
* @param sql any SQL statement
* @param autoGeneratedKeys a constant indicating whether auto-generated keys should be made
* available for retrieval using the methodgetGeneratedKeys
; 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
or the second parameter supplied to this method is not
* Statement.RETURN_GENERATED_KEYS
or Statement.NO_GENERATED_KEYS
.
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
* @see #getGeneratedKeys
*/
public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
return executeInternal(sql, fetchSize, autoGeneratedKeys);
}
/**
* 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 getInternalMoreResults
to move
* to any subsequent result(s).
*
* @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
or the elements in the int
array passed to this method
* are not valid column indexes
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
*/
public boolean execute(final String sql, final int[] columnIndexes) throws SQLException {
return executeInternal(sql, fetchSize, 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 getInternalMoreResults
to move to any
* subsequent result(s).
*
* @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
or the elements of the String
array passed to this
* method are not valid column names
* @see #getResultSet
* @see #getUpdateCount
* @see #getMoreResults
* @see #getGeneratedKeys
*/
public boolean execute(final String sql, final String[] columnNames) throws SQLException {
return executeInternal(sql, fetchSize, Statement.RETURN_GENERATED_KEYS);
}
/**
* executes a select query.
*
* @param sql the query to send to the server
* @return a result set
* @throws SQLException if something went wrong
*/
public ResultSet executeQuery(String sql) throws SQLException {
if (executeInternal(sql, fetchSize, Statement.NO_GENERATED_KEYS)) {
return results.getResultSet();
}
return SelectResultSet.createEmptyResultSet();
}
/**
* Executes an update.
*
* @param sql the update query.
* @return update count
* @throws SQLException if the query could not be sent to server.
*/
public int executeUpdate(String sql) throws SQLException {
if (executeInternal(sql, fetchSize, Statement.NO_GENERATED_KEYS)) {
return 0;
}
return getUpdateCount();
}
/**
* 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).
*
* @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, or
* the given constant is not one of those allowed
*/
public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
if (executeInternal(sql, fetchSize, autoGeneratedKeys)) {
return 0;
}
return getUpdateCount();
}
/**
* 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).
*
* @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, or the
* second argument supplied to this method is not an int
array whose elements are
* valid column indexes
*/
public int executeUpdate(final String sql, final 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).
*
* @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, or the
* second argument supplied to this method is not a String
array whose elements
* are valid column names
*/
public int executeUpdate(final String sql, final String[] columnNames) throws SQLException {
return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
}
/**
* 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 {
if (executeInternal(sql, fetchSize, Statement.NO_GENERATED_KEYS)) {
return 0;
}
return getLargeUpdateCount();
}
/**
* 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 {
if (executeInternal(sql, fetchSize, autoGeneratedKeys)) {
return 0;
}
return getLargeUpdateCount();
}
/**
* 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, 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, Statement.RETURN_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
*/
public void close() throws SQLException {
lock.lock();
try {
closed = true;
if (results != null) {
if (results.getFetchSize() != 0) {
skipMoreResults();
}
results.close();
}
protocol = null;
if (connection == null
|| connection.pooledConnection == null
|| connection.pooledConnection.noStmtEventListeners()) {
return;
}
connection.pooledConnection.fireStatementClosed(this);
} 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
*/
public int getMaxFieldSize() {
return maxFieldSize;
}
/**
* Sets the limit for 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
fields. If the limit is
* exceeded, the excess data is silently discarded. For maximum portability, use values greater
* than 256.
*
* @param max the new column size limit in bytes; zero means there is no limit
* @see #getMaxFieldSize
*/
public void setMaxFieldSize(final int max) {
maxFieldSize = 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
* @see #setMaxRows
*/
public int getMaxRows() {
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 the condition max >= 0 is not satisfied
* @see #getMaxRows
*/
public void setMaxRows(final int max) throws SQLException {
if (max < 0) {
throw new SQLException("max rows cannot be negative : asked for " + max);
}
maxRows = 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
*/
@Override
public long getLargeMaxRows() {
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 {
if (max < 0) {
throw new SQLException("max rows cannot be negative : setLargeMaxRows value is " + max);
}
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. 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
*/
public void setEscapeProcessing(final boolean enable) {
// not handled
}
/**
* 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
* @see #setQueryTimeout
*/
public int getQueryTimeout() {
return queryTimeout;
}
/**
* Sets the number of seconds the driver will wait for a Statement
object to execute
* to the given number of seconds. If the limit is exceeded, an SQLException
is
* thrown. A JDBC driver must apply this limit to the execute
, executeQuery
*
and executeUpdate
methods.
*
* @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 seconds >= 0 is not satisfied
* @see #getQueryTimeout
*/
public void setQueryTimeout(final int seconds) throws SQLException {
if (seconds < 0) {
throw new SQLException("Query timeout rows cannot be negative : asked for " + seconds);
}
this.queryTimeout = seconds;
}
/**
* Sets the inputStream that will be used for the next execute that uses "LOAD DATA LOCAL INFILE".
* The name specified as local file/URL will be ignored.
*
* @param inputStream inputStream instance, that will be used to send data to server
* @throws SQLException if statement is closed
*/
public void setLocalInfileInputStream(InputStream inputStream) throws SQLException {
checkClose();
protocol.setLocalInfileInputStream(inputStream);
}
/**
* 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.
*
*
In case there is result-set from this Statement that are still streaming data from server,
* will cancel streaming.
*
* @throws SQLException if a database access error occurs or this method is called on a closed
* Statement
*/
public void cancel() throws SQLException {
checkClose();
boolean locked = lock.tryLock();
try {
if (executing) {
protocol.cancelCurrentQuery();
} else if (results != null
&& results.getFetchSize() != 0
&& !results.isFullyLoaded(protocol)) {
try {
protocol.cancelCurrentQuery();
skipMoreResults();
} catch (SQLException e) {
// eat exception
}
results.removeFetchSize();
}
} catch (SQLException e) {
logger.error("error cancelling query", e);
ExceptionMapper.throwException(e, connection, this);
} finally {
if (locked) {
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
*/
public SQLWarning getWarnings() throws SQLException {
checkClose();
if (!warningsCleared) {
return this.connection.getWarnings();
}
return null;
}
/**
* 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.
*/
public void clearWarnings() {
warningsCleared = true;
}
/**
* 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 insure 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
*/
public void setCursorName(final String name) throws SQLException {
throw ExceptionMapper.getFeatureNotSupportedException("Cursors are not supported");
}
/**
* Gets the connection that created this statement.
*
* @return the connection
*/
public MariaDbConnection getConnection() {
return this.connection;
}
/**
* 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
*/
public ResultSet getGeneratedKeys() throws SQLException {
if (results != null) {
return results.getGeneratedKeys(protocol);
}
return SelectResultSet.createEmptyResultSet();
}
/**
* 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
*/
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
* @since 1.6
*/
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
* @see Statement#setPoolable(boolean) setPoolable(boolean)
* @since 1.6
*/
@Override
public boolean isPoolable() {
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 applicaiton wants the statement to
* be pooled. It is up to the statement pool manager as to 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
* @since 1.6
*/
@Override
public void setPoolable(final boolean poolable) {
// not handled
}
/**
* 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
*/
public ResultSet getResultSet() throws SQLException {
checkClose();
return results != null ? results.getResultSet() : 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
*/
public int getUpdateCount() {
if (results != null && results.getCmdInformation() != null && !results.isBatch()) {
return results.getCmdInformation().getUpdateCount();
}
return -1;
}
/**
* Retrieves the current result as an update count; if the result is a ResultSet object or there
* are no more results, -1 is returned.
*
* @return last update count
*/
@Override
public long getLargeUpdateCount() {
if (results != null && results.getCmdInformation() != null && !results.isBatch()) {
return results.getCmdInformation().getLargeUpdateCount();
}
return -1;
}
protected void skipMoreResults() throws SQLException {
try {
protocol.skip();
warningsCleared = false;
connection.reenableWarnings();
} catch (SQLException e) {
logger.debug("error skipMoreResults", e);
ExceptionMapper.throwException(e, connection, this);
}
}
/**
* 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:
*
*
// stmt is a Statement object
* ((stmt.getInternalMoreResults() == 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
*/
public boolean getMoreResults() throws SQLException {
return getMoreResults(Statement.CLOSE_CURRENT_RESULT);
}
/**
* 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:
*
* // stmt is a Statement object
* ((stmt.getInternalMoreResults(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
* @see #execute
*/
public boolean getMoreResults(final int current) throws SQLException {
// if fetch size is set to read fully, other resultSet are put in cache
checkClose();
return results != null && results.getMoreResults(current, protocol);
}
/**
* 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
*/
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
* @see #getFetchDirection
* @since 1.2
*/
public void setFetchDirection(final int direction) {
// not implemented
}
/**
* 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
* @see #setFetchSize
*/
public int getFetchSize() {
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 rows >= 0
is not satisfied.
* @see #getFetchSize
*/
public void setFetchSize(final int rows) throws SQLException {
if (rows < 0 && rows != Integer.MIN_VALUE) {
throw new SQLException("invalid fetch size");
} else if (rows == Integer.MIN_VALUE) {
// for compatibility Integer.MIN_VALUE is transform to 0 => streaming
this.fetchSize = 1;
return;
}
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
*
* @since 1.2
*/
public int getResultSetConcurrency() {
return 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
*/
public int getResultSetType() {
return resultSetScrollType;
}
/**
* Adds the given SQL command to the current list of commands for this Statement
* object. The send in this list can be executed as a batch by calling the method
* executeBatch
.
*
* @param sql typically this is a SQL INSERT
or UPDATE
statement
* @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 #executeBatch
* @see DatabaseMetaData#supportsBatchUpdates
*/
public void addBatch(final String sql) throws SQLException {
if (batchQueries == null) {
batchQueries = new ArrayList<>();
}
if (sql == null) {
throw ExceptionMapper.getSqlException("null cannot be set to addBatch( String sql)");
}
batchQueries.add(sql);
}
/**
* Empties this Statement
object's current list of SQL send.
*
* @see #addBatch
* @see DatabaseMetaData#supportsBatchUpdates
* @since 1.2
*/
public void clearBatch() {
if (batchQueries != null) {
batchQueries.clear();
}
}
/**
* Execute statements. depending on option, queries mays be rewritten :
*
*
those queries will be rewritten if possible to INSERT INTO ... VALUES (...) ; INSERT INTO
* ... VALUES (...);
*
*
if option rewriteBatchedStatements is set to true, rewritten to INSERT INTO ... VALUES
* (...), (...);
*
* @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 send 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 send sent to
* the database fails to execute properly or attempts to return a result set.
* @see #addBatch
* @see DatabaseMetaData#supportsBatchUpdates
* @since 1.3
*/
public int[] executeBatch() throws SQLException {
checkClose();
int size;
if (batchQueries == null || (size = batchQueries.size()) == 0) {
return new int[0];
}
lock.lock();
try {
internalBatchExecution(size);
return results.getCmdInformation().getUpdateCounts();
} catch (SQLException initialSqlEx) {
throw executeBatchExceptionEpilogue(initialSqlEx, size);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* 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 {
checkClose();
int size;
if (batchQueries == null || (size = batchQueries.size()) == 0) {
return new long[0];
}
lock.lock();
try {
internalBatchExecution(size);
return results.getCmdInformation().getLargeUpdateCounts();
} catch (SQLException initialSqlEx) {
throw executeBatchExceptionEpilogue(initialSqlEx, size);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* Internal batch execution.
*
* @param size expected result-set size
* @throws SQLException throw exception if batch error occur
*/
private void internalBatchExecution(int size) throws SQLException {
executeQueryPrologue(true);
results =
new Results(
this,
0,
true,
size,
false,
resultSetScrollType,
resultSetConcurrency,
Statement.RETURN_GENERATED_KEYS,
protocol.getAutoIncrementIncrement(),
null,
null);
protocol.executeBatchStmt(protocol.isMasterConnection(), results, batchQueries);
results.commandEnd();
}
/**
* 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 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. May be a proxy for the actual implementing
* object.
* @throws SQLException If no object found that implements the interface
* @since 1.6
*/
@SuppressWarnings("unchecked")
public T unwrap(final Class iface) throws SQLException {
try {
if (isWrapperFor(iface)) {
return (T) this;
} else {
throw new SQLException(
"The receiver is not a wrapper and does not implement the interface");
}
} catch (Exception e) {
throw new SQLException("The receiver is not a wrapper and does not implement the interface");
}
}
/**
* 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 interfaceOrWrapper a Class defining an interface.
* @return true if this implements the interface or directly or indirectly wraps an object that
* does.
* @throws SQLException if an error occurs while determining whether this is a wrapper for an
* object with the given interface.
* @since 1.6
*/
public boolean isWrapperFor(final Class> interfaceOrWrapper) throws SQLException {
return interfaceOrWrapper.isInstance(this);
}
public void closeOnCompletion() {
mustCloseOnCompletion = true;
}
public boolean isCloseOnCompletion() {
return mustCloseOnCompletion;
}
/**
* Check that close on completion is asked, and close if so.
*
* @param resultSet resultSet
* @throws SQLException if close has error
*/
public void checkCloseOnCompletion(ResultSet resultSet) throws SQLException {
if (mustCloseOnCompletion
&& !closed
&& results != null
&& resultSet.equals(results.getResultSet())) {
close();
}
}
/**
* Check if statement is closed, and throw exception if so.
*
* @throws SQLException if statement close
*/
protected void checkClose() throws SQLException {
if (closed) {
throw new SQLException("Cannot do an operation on a closed statement");
}
}
/**
* Permit to retrieve current connection thread id, or -1 if unknown.
*
* @return current connection thread id.
*/
public long getServerThreadId() {
return (protocol != null) ? protocol.getServerThreadId() : -1;
}
}