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

org.mariadb.jdbc.MariaDbStatement Maven / Gradle / Ivy

There is a newer version: 3.4.1
Show newest version
/*
 *
 * 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<>();
  // timeout scheduler
  private static final Logger logger = LoggerFactory.getLogger(MariaDbStatement.class);

  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("\\", "\\\\");
  }

  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.
   *
   * 
    *
  1. clear previous query state *
  2. 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy