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

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

// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab

package org.mariadb.jdbc;

import static org.mariadb.jdbc.util.constants.Capabilities.LOCAL_FILES;

import java.io.InputStream;
import java.sql.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.mariadb.jdbc.client.ColumnDecoder;
import org.mariadb.jdbc.client.Completion;
import org.mariadb.jdbc.client.DataType;
import org.mariadb.jdbc.client.result.CompleteResult;
import org.mariadb.jdbc.client.result.Result;
import org.mariadb.jdbc.export.ExceptionFactory;
import org.mariadb.jdbc.message.client.QueryPacket;
import org.mariadb.jdbc.message.server.OkPacket;
import org.mariadb.jdbc.util.NativeSql;
import org.mariadb.jdbc.util.constants.ColumnFlags;
import org.mariadb.jdbc.util.constants.ServerStatus;

/** Statement implementation */
public class Statement implements java.sql.Statement {
  private static final Pattern identifierPattern =
      Pattern.compile("[0-9a-zA-Z\\$_\\u0080-\\uFFFF]*", Pattern.UNICODE_CASE | Pattern.CANON_EQ);
  private static final Pattern escapePattern = Pattern.compile("[\u0000'\"\b\n\r\t\u001A\\\\]");
  private static final Map mapper = new HashMap<>();

  static {
    mapper.put("\u0000", "\\0");
    mapper.put("'", "\\\\'");
    mapper.put("\"", "\\\\\"");
    mapper.put("\b", "\\\\b");
    mapper.put("\n", "\\\\n");
    mapper.put("\r", "\\\\r");
    mapper.put("\t", "\\\\t");
    mapper.put("\u001A", "\\\\Z");
    mapper.put("\\", "\\\\");
  }

  private List batchQueries;

  /** result-set type */
  protected final int resultSetType;
  /** concurrency */
  protected final int resultSetConcurrency;
  /** thread safe locker */
  protected final ReentrantLock lock;
  /** can use server query timeout */
  protected final boolean canUseServerTimeout;
  /** can use server row limitation */
  protected final boolean canUseServerMaxRows;
  /** connection */
  protected final Connection con;
  /** required query timeout */
  protected int queryTimeout;
  /** maximum row number */
  protected long maxRows;
  /** fetch size */
  protected int fetchSize;
  /** automatic generated keys result required */
  protected int autoGeneratedKeys;
  /** close statement on resultset completion */
  protected boolean closeOnCompletion;
  /** closed flag */
  protected boolean closed;
  /** escape processing */
  protected boolean escape;
  /** last execution results */
  protected List results;
  /** current results */
  protected Completion currResult;
  /** streaming load data infile data */
  protected InputStream localInfileInputStream;

  /**
   * Constructor
   *
   * @param con connection
   * @param lock thread safe locker
   * @param canUseServerTimeout can use server timeout
   * @param canUseServerMaxRows can use server row number limitation
   * @param autoGeneratedKeys automatic generated keys result required
   * @param resultSetType result-set type
   * @param resultSetConcurrency concurrency
   * @param defaultFetchSize fetch size
   */
  public Statement(
      Connection con,
      ReentrantLock lock,
      boolean canUseServerTimeout,
      boolean canUseServerMaxRows,
      int autoGeneratedKeys,
      int resultSetType,
      int resultSetConcurrency,
      int defaultFetchSize) {
    this.con = con;
    this.lock = lock;
    this.resultSetConcurrency = resultSetConcurrency;
    this.resultSetType = resultSetType;
    this.autoGeneratedKeys = autoGeneratedKeys;
    this.canUseServerTimeout = canUseServerTimeout;
    this.canUseServerMaxRows = canUseServerMaxRows;
    this.fetchSize = defaultFetchSize;
  }

  private ExceptionFactory exceptionFactory() {
    return con.getExceptionFactory().of(this);
  }

  /**
   * Set current local infile stream
   *
   * @param inputStream stream
   * @throws SQLException if statement is already closed
   */
  public void setLocalInfileInputStream(InputStream inputStream) throws SQLException {
    checkNotClosed();
    localInfileInputStream = inputStream;
  }

  /**
   * Executes the given SQL statement, which returns a single ResultSet object.
   *
   * 

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql an SQL statement to be sent to the database, typically a static SQL SELECT * statement * @return a ResultSet object that contains the data produced by the given query; * never null * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the given SQL statement produces anything other than a single * ResultSet object, the method is called on a PreparedStatement or * CallableStatement * @throws java.sql.SQLTimeoutException when the driver has determined that the timeout value that * was specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} */ @Override public ResultSet executeQuery(String sql) throws SQLException { executeInternal(sql, Statement.NO_GENERATED_KEYS); currResult = results.remove(0); if (currResult instanceof Result) return (Result) currResult; return new CompleteResult(new ColumnDecoder[0], new byte[0][], con.getContext()); } /** * Executes the given SQL statement, which may be an INSERT, UPDATE, or * DELETE statement or an SQL statement that returns nothing, such as an SQL DDL * statement. * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT, * UPDATE or DELETE; or an SQL statement that returns nothing, such * as a DDL statement. * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 * for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the given SQL statement produces a ResultSet object, * the method is called on a PreparedStatement or CallableStatement * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} */ @Override public int executeUpdate(String sql) throws SQLException { return executeUpdate(sql, Statement.NO_GENERATED_KEYS); } /** * Releases this Statement object's database and JDBC resources immediately instead * of waiting for this to happen when it is automatically closed. It is generally good practice to * release resources as soon as you are finished with them to avoid tying up database resources. * *

Calling the method close on a Statement object that is already * closed has no effect. * *

Note:When a Statement object is closed, its current ResultSet * object, if one exists, is also closed. * * @throws SQLException if a database access error occurs */ @Override public void close() throws SQLException { if (!closed) { closed = true; if (currResult != null && currResult instanceof Result) { ((Result) currResult).closeFromStmtClose(lock); } // close result-set if (results != null && !results.isEmpty()) { for (Completion completion : results) { if (completion instanceof Result) { ((Result) completion).closeFromStmtClose(lock); } } } } } /** * Abort current command result if streaming. result-set will be incomplete and closed, but ensure * connection state */ public void abort() { lock.lock(); try { if (!closed) { closed = true; if (currResult != null && currResult instanceof Result) { ((Result) currResult).abort(); } // close result-set if (results != null) { for (Completion completion : results) { if (completion instanceof Result) { ((Result) completion).abort(); } } } } } finally { lock.unlock(); } } /** * Retrieves the maximum number of bytes that can be returned for character and binary column * values in a ResultSet object produced by this Statement object. This * limit applies only to BINARY, VARBINARY, LONGVARBINARY, * CHAR, VARCHAR, NCHAR, NVARCHAR, * LONGNVARCHAR and LONGVARCHAR columns. If the limit is exceeded, the excess * data is silently discarded. * * @return the current column size limit for columns storing character and binary values; zero * means there is no limit * @see #setMaxFieldSize */ @Override public int getMaxFieldSize() { return 0; } /** * NOT SUPPORTED. * * @see #getMaxFieldSize */ @Override public void setMaxFieldSize(int max) {} /** * Retrieves the maximum number of rows that a ResultSet object produced by this * Statement object can contain. If this limit is exceeded, the excess rows are * silently dropped. * * @return the current maximum number of rows for a ResultSet object produced by this * Statement object; zero means there is no limit * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @see #setMaxRows */ @Override public int getMaxRows() throws SQLException { checkNotClosed(); return (int) maxRows; } /** * Sets the limit for the maximum number of rows that any ResultSet object generated * by this Statement object can contain to the given number. If the limit is * exceeded, the excess rows are silently dropped. * * @param max the new max rows limit; zero means there is no limit * @throws SQLException if a database access error occurs, this method is called on a closed * Statement or the condition {@code max >= 0} is not satisfied * @see #getMaxRows */ @Override public void setMaxRows(int max) throws SQLException { checkNotClosed(); if (max < 0) { throw exceptionFactory().create("max rows cannot be negative : asked for " + max, "42000"); } maxRows = max; } /** * Sets escape processing on or off. If escape scanning is on (the default), the driver will do * escape substitution before sending the SQL statement to the database. * *

The {@code Connection} and {@code DataSource} property {@code escapeProcessing} may be used * to change the default escape processing behavior. A value of true (the default) enables escape * Processing for all {@code Statement} objects. A value of false disables escape processing for * all {@code Statement} objects. The {@code setEscapeProcessing} method may be used to specify * the escape processing behavior for an individual {@code Statement} object. * *

Note: Since prepared statements have usually been parsed prior to making this call, * disabling escape processing for PreparedStatements objects will have no effect. * * @param enable true to enable escape processing; false to disable it * @throws SQLException if a database access error occurs or this method is called on a closed * Statement */ @Override public void setEscapeProcessing(boolean enable) throws SQLException { checkNotClosed(); this.escape = enable; } /** * Retrieves the number of seconds the driver will wait for a Statement object to * execute. If the limit is exceeded, a SQLException is thrown. * * @return the current query timeout limit in seconds; zero means there is no limit * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @see #setQueryTimeout */ @Override public int getQueryTimeout() throws SQLException { checkNotClosed(); return queryTimeout; } /** * Sets the number of seconds the driver will wait for a Statement object to execute * to the given number of seconds. By default, there is no limit on the amount of time allowed for * a running statement to complete. If the limit is exceeded, an SQLTimeoutException * is thrown. A JDBC driver must apply this limit to the execute, executeQuery * and executeUpdate methods. * *

Note: JDBC driver implementations may also apply this limit to {@code * ResultSet} methods (consult your driver vendor documentation for details). * *

Note: In the case of {@code Statement} batching, it is implementation * defined whether the time-out is applied to individual SQL commands added via the {@code * addBatch} method or to the entire batch of SQL commands invoked by the {@code executeBatch} * method (consult your driver vendor documentation for details). * * @param seconds the new query timeout limit in seconds; zero means there is no limit * @throws SQLException if a database access error occurs, this method is called on a closed * Statement or the condition {@code seconds >= 0} is not satisfied * @see #getQueryTimeout */ @Override public void setQueryTimeout(int seconds) throws SQLException { if (seconds < 0) { throw exceptionFactory() .create("Query timeout cannot be negative : asked for " + seconds, "42000"); } this.queryTimeout = seconds; } /** * Cancels this Statement object if both the DBMS and driver support aborting an SQL * statement. This method can be used by one thread to cancel a statement that is being executed * by another thread. * * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method */ @Override public void cancel() throws SQLException { checkNotClosed(); boolean locked = lock.tryLock(); // if any query is active, lock is set. // this avoids trying to execute a KILL QUERY if no query is running. if (!locked) { con.cancelCurrentQuery(); } else { lock.unlock(); } } /** * Retrieves the first warning reported by calls on this Statement object. Subsequent * Statement object warnings will be chained to this SQLWarning object. * *

The warning chain is automatically cleared each time a statement is (re)executed. This * method may not be called on a closed Statement object; doing so will cause an * SQLException to be thrown. * *

Note: If you are processing a ResultSet object, any warnings associated * with reads on that ResultSet object will be chained on it rather than on the * Statement object that produced it. * * @return the first SQLWarning object or null if there are no warnings * @throws SQLException if a database access error occurs or this method is called on a closed * Statement */ @Override public SQLWarning getWarnings() throws SQLException { return con.getWarnings(); } /** * Clears all the warnings reported on this Statement object. After a call to this * method, the method getWarnings will return null until a new warning * is reported for this Statement object. */ @Override public void clearWarnings() { con.getContext().setWarning(0); } /** * Sets the SQL cursor name to the given String, which will be used by subsequent * Statement object execute methods. This name can then be used in SQL * positioned update or delete statements to identify the current row in the ResultSet * object generated by this statement. If the database does not support positioned * update/delete, this method is a noop. To ensure that a cursor has the proper isolation level to * support updates, the cursor's SELECT statement should have the form * SELECT FOR UPDATE. If FOR UPDATE is not present, positioned updates may * fail. * *

Note: By definition, the execution of positioned updates and deletes must be done by * a different Statement object than the one that generated the ResultSet * object being used for positioning. Also, cursor names must be unique within a * connection. * * @param name the new cursor name, which must be unique within a connection * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method */ @Override public void setCursorName(String name) throws SQLException { throw exceptionFactory().notSupported("Cursors are not supported"); } /** * Executes the given SQL statement, which may return multiple results. In some (uncommon) * situations, a single SQL statement may return multiple result sets and/or update counts. * Normally you can ignore this unless you are (1) executing a stored procedure that you know may * return multiple results or (2) you are dynamically executing an unknown SQL string. * *

The execute method executes an SQL statement and indicates the form of the * first result. You must then use the methods getResultSet or getUpdateCount * to retrieve the result, and getMoreResults to move to any subsequent * result(s). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql any SQL statement * @return true if the first result is a ResultSet object; false * if it is an update count or there are no results * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the method is called on a PreparedStatement or * CallableStatement * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @see #getResultSet * @see #getUpdateCount * @see #getMoreResults */ @Override public boolean execute(String sql) throws SQLException { return execute(sql, Statement.NO_GENERATED_KEYS); } /** * Retrieves the current result as a ResultSet object. This method should be called * only once per result. * * @return the current result as a ResultSet object or null if the * result is an update count or there are no more results * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @see #execute */ @Override public ResultSet getResultSet() throws SQLException { checkNotClosed(); if (currResult instanceof Result) { return (Result) currResult; } return null; } /** * Retrieves the current result as an update count; if the result is a ResultSet * object or there are no more results, -1 is returned. This method should be called only once per * result. * * @return the current result as an update count; -1 if the current result is a ResultSet * object or there are no more results * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @see #execute */ @Override public int getUpdateCount() throws SQLException { checkNotClosed(); if (currResult instanceof OkPacket) { return (int) ((OkPacket) currResult).getAffectedRows(); } return -1; } /** * Moves to this Statement object's next result, returns true if it is a * ResultSet object, and implicitly closes any current ResultSet * object(s) obtained with the method getResultSet. * *

There are no more results when the following is true: * *

{@code
   * // stmt is a Statement object
   * ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
   * }
* * @return true if the next result is a ResultSet object; false * if it is an update count or there are no more results * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @see #execute */ @Override public boolean getMoreResults() throws SQLException { return getMoreResults(Statement.CLOSE_CURRENT_RESULT); } /** * Retrieves the direction for fetching rows from database tables that is the default for result * sets generated from this Statement object. If this Statement object * has not set a fetch direction by calling the method setFetchDirection, the return * value is implementation-specific. * * @return the default fetch direction for result sets generated from this Statement * object * @see #setFetchDirection * @since 1.2 */ @Override public int getFetchDirection() { return ResultSet.FETCH_FORWARD; } /** * Gives the driver a hint as to the direction in which rows will be processed in ResultSet * objects created using this Statement object. The default value is * ResultSet.FETCH_FORWARD. * *

Note that this method sets the default fetch direction for result sets generated by this * Statement object. Each result set has its own methods for getting and setting its * own fetch direction. * * @param direction the initial direction for processing rows */ @Override public void setFetchDirection(int direction) { // not supported } /** * Retrieves the number of result set rows that is the default fetch size for ResultSet * objects generated from this Statement object. If this Statement * object has not set a fetch size by calling the method setFetchSize, the * return value is implementation-specific. * * @return the default fetch size for result sets generated from this Statement * object * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @see #setFetchSize */ @Override public int getFetchSize() throws SQLException { checkNotClosed(); return this.fetchSize; } /** * Gives the JDBC driver a hint as to the number of rows that should be fetched from the database * when more rows are needed for ResultSet objects generated by this Statement * . If the value specified is zero, then the hint is ignored. The default value is zero. * * @param rows the number of rows to fetch * @throws SQLException if a database access error occurs, this method is called on a closed * Statement or the condition {@code rows >= 0} is not satisfied. * @see #getFetchSize * @since 1.2 */ @Override public void setFetchSize(int rows) throws SQLException { if (rows < 0) { throw exceptionFactory().create("invalid fetch size"); } this.fetchSize = rows; } /** * Retrieves the result set concurrency for ResultSet objects generated by this * Statement object. * * @return either ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE * * @throws SQLException if a database access error occurs or this method is called on a closed * Statement */ @Override public int getResultSetConcurrency() throws SQLException { checkNotClosed(); return this.resultSetConcurrency; } /** * Retrieves the result set type for ResultSet objects generated by this * Statement object. * * @return one of ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE * @since 1.2 */ @Override public int getResultSetType() { return this.resultSetType; } /** * Adds the given SQL command to the current list of commands for this Statement * object. The commands in this list can be executed as a batch by calling the method * executeBatch. * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql typically this is an SQL INSERT or UPDATE statement * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the driver does not support batch updates, the method is called on * a PreparedStatement or CallableStatement * @see #executeBatch * @see DatabaseMetaData#supportsBatchUpdates */ @Override public void addBatch(String sql) throws SQLException { if (sql == null) { throw exceptionFactory().create("null cannot be set to addBatch(String sql)"); } if (batchQueries == null) batchQueries = new ArrayList<>(); batchQueries.add(escape ? NativeSql.parse(sql, con.getContext()) : sql); } /** * Empties this Statement object's current list of SQL commands. * * @throws SQLException if a database access error occurs, this method is called on a closed * Statement or the driver does not support batch updates * @see #addBatch * @see DatabaseMetaData#supportsBatchUpdates * @since 1.2 */ @Override public void clearBatch() throws SQLException { checkNotClosed(); if (batchQueries == null) { batchQueries = new ArrayList<>(); } else { batchQueries.clear(); } } /** * Submits a batch of commands to the database for execution and if all commands execute * successfully, returns an array of update counts. The int elements of the array * that is returned are ordered to correspond to the commands in the batch, which are ordered * according to the order in which they were added to the batch. The elements in the array * returned by the method executeBatch may be one of the following: * *

    *
  1. A number greater than or equal to zero -- indicates that the command was processed * successfully and is an update count giving the number of rows in the database that were * affected by the command's execution *
  2. A value of SUCCESS_NO_INFO -- indicates that the command was processed * successfully but that the number of rows affected is unknown *

    If one of the commands in a batch update fails to execute properly, this method throws * a BatchUpdateException, and a JDBC driver may or may not continue to process * the remaining commands in the batch. However, the driver's behavior must be consistent * with a particular DBMS, either always continuing to process commands or never continuing * to process commands. If the driver continues processing after a failure, the array * returned by the method BatchUpdateException.getUpdateCounts will contain as * many elements as there are commands in the batch, and at least one of the elements will * be the following: *

  3. A value of EXECUTE_FAILED -- indicates that the command failed to execute * successfully and occurs only if a driver continues to process commands after a command * fails *
* *

The possible implementations and return values have been modified in the Java 2 SDK, * Standard Edition, version 1.3 to accommodate the option of continuing to process commands in a * batch update after a BatchUpdateException object has been thrown. * * @return an array of update counts containing one element for each command in the batch. The * elements of the array are ordered according to the order in which commands were added to * the batch. * @throws SQLException if a database access error occurs, this method is called on a closed * Statement or the driver does not support batch statements. Throws {@link * BatchUpdateException} (a subclass of SQLException) if one of the commands sent * to the database fails to execute properly or attempts to return a result set. * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @see #addBatch * @see DatabaseMetaData#supportsBatchUpdates * @since 1.2 */ @Override public int[] executeBatch() throws SQLException { checkNotClosed(); if (batchQueries == null || batchQueries.isEmpty()) return new int[0]; lock.lock(); try { // ensure pipelining is possible (no LOAD DATA/XML INFILE commands) boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES); if (possibleLoadLocal) { possibleLoadLocal = false; for (int i = 0; i < batchQueries.size(); i++) { String sql = batchQueries.get(i).toUpperCase(Locale.ROOT); if (sql.contains(" LOCAL ") && sql.contains("LOAD") && sql.contains(" INFILE")) { possibleLoadLocal = true; break; } } } List res = possibleLoadLocal ? executeInternalBatchStandard() : executeInternalBatchPipeline(); results = res; int[] updates = new int[res.size()]; for (int i = 0; i < res.size(); i++) { if (res.get(i) instanceof OkPacket) { updates[i] = (int) ((OkPacket) res.get(i)).getAffectedRows(); } else { updates[i] = org.mariadb.jdbc.Statement.SUCCESS_NO_INFO; } } currResult = results.remove(0); batchQueries.clear(); return updates; } finally { lock.unlock(); } } /** * Retrieves the Connection object that produced this Statement object. * * @return the connection that produced this statement * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @since 1.2 */ @Override public Connection getConnection() throws SQLException { checkNotClosed(); return con; } /** * Moves to this Statement object's next result, deals with any current * ResultSet object(s) according to the instructions specified by the given flag, and * returns true if the next result is a ResultSet object. * *

There are no more results when the following is true: * *

{@code
   * // stmt is a Statement object
   * ((stmt.getMoreResults(current) == false) && (stmt.getUpdateCount() == -1))
   * }
* * @param current one of the following Statement constants indicating what should * happen to current ResultSet objects obtained using the method * getResultSet: Statement.CLOSE_CURRENT_RESULT, * Statement.KEEP_CURRENT_RESULT, or Statement.CLOSE_ALL_RESULTS * @return true if the next result is a ResultSet object; false * if it is an update count or there are no more results * @throws SQLException if a database access error occurs, this method is called on a closed * Statement or the argument supplied is not one of the following: * Statement.CLOSE_CURRENT_RESULT, Statement.KEEP_CURRENT_RESULT or * Statement.CLOSE_ALL_RESULTS * @throws SQLFeatureNotSupportedException if DatabaseMetaData.supportsMultipleOpenResults * returns false and either Statement.KEEP_CURRENT_RESULT or * Statement.CLOSE_ALL_RESULTS are supplied as the argument. * @see #execute * @since 1.4 */ @Override public boolean getMoreResults(int current) throws SQLException { checkNotClosed(); if (currResult instanceof ResultSet) { lock.lock(); try { Result result = (Result) currResult; if (current == java.sql.Statement.CLOSE_CURRENT_RESULT) { result.close(); } else { result.fetchRemaining(); } if (result.streaming() && (con.getContext().getServerStatus() & ServerStatus.MORE_RESULTS_EXISTS) > 0) { con.getClient() .readStreamingResults( results, fetchSize, maxRows, resultSetConcurrency, resultSetType, closeOnCompletion); } } finally { lock.unlock(); } } if (results.size() > 0) { currResult = results.remove(0); return (currResult instanceof Result); } currResult = null; return false; } /** * Permit to streaming result to fetch remaining results. * * @throws SQLException if socket error occurs. */ public void fetchRemaining() throws SQLException { if (currResult != null && currResult instanceof ResultSet) { Result result = (Result) currResult; result.fetchRemaining(); if (result.streaming() && (con.getContext().getServerStatus() & ServerStatus.MORE_RESULTS_EXISTS) > 0) { con.getClient() .readStreamingResults( results, 0, 0L, resultSetConcurrency, resultSetType, closeOnCompletion); } } } /** * Retrieves any auto-generated keys created as a result of executing this Statement * object. If this Statement object did not generate any keys, an empty * ResultSet object is returned. * *

Note:If the columns which represent the auto-generated keys were not specified, the * JDBC driver implementation will determine the columns which best represent the auto-generated * keys. * * @return a ResultSet object containing the auto-generated key(s) generated by the * execution of this Statement object * @throws SQLException if a database access error occurs or this method is called on a closed * Statement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method * @since 1.4 */ @Override public ResultSet getGeneratedKeys() throws SQLException { checkNotClosed(); if (autoGeneratedKeys != java.sql.Statement.RETURN_GENERATED_KEYS) { throw new SQLException( "Cannot return generated keys: query was not set with Statement.RETURN_GENERATED_KEYS"); } if (currResult instanceof OkPacket) { OkPacket ok = ((OkPacket) currResult); if (ok.getLastInsertId() != 0) { List insertIds = new ArrayList<>(); insertIds.add(new String[] {String.valueOf(ok.getLastInsertId())}); for (Completion result : results) { if (result instanceof OkPacket) { insertIds.add(new String[] {String.valueOf(((OkPacket) result).getLastInsertId())}); } } String[][] ids = insertIds.toArray(new String[0][]); return CompleteResult.createResultSet( "insert_id", DataType.BIGINT, ids, con.getContext(), ColumnFlags.AUTO_INCREMENT | ColumnFlags.UNSIGNED); } } return new CompleteResult(new ColumnDecoder[0], new byte[0][], con.getContext()); } /** * Executes the given SQL statement and signals the driver with the given flag about whether the * auto-generated keys produced by this Statement object should be made available for * retrieval. The driver will ignore the flag if the SQL statement is not an INSERT * statement, or an SQL statement able to return auto-generated keys (the list of such statements * is vendor-specific). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT, * UPDATE or DELETE; or an SQL statement that returns nothing, such * as a DDL statement. * @param autoGeneratedKeys a flag indicating whether auto-generated keys should be made available * for retrieval; one of the following constants: Statement.RETURN_GENERATED_KEYS * Statement.NO_GENERATED_KEYS * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 * for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the given SQL statement returns a ResultSet object, * the given constant is not one of those allowed, the method is called on a * PreparedStatement or CallableStatement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method with a * constant of Statement.RETURN_GENERATED_KEYS * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @since 1.4 */ @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { executeInternal(sql, autoGeneratedKeys); currResult = results.remove(0); if (currResult instanceof Result) { throw exceptionFactory() .create("the given SQL statement produces an unexpected ResultSet object", "HY000"); } return (int) ((OkPacket) currResult).getAffectedRows(); } private void executeInternal(String sql, int autoGeneratedKeys) throws SQLException { checkNotClosed(); lock.lock(); try { this.autoGeneratedKeys = autoGeneratedKeys; String cmd = escapeTimeout(sql); results = con.getClient() .execute( new QueryPacket(cmd, localInfileInputStream), this, fetchSize, maxRows, resultSetConcurrency, resultSetType, closeOnCompletion, false); } finally { localInfileInputStream = null; lock.unlock(); } } /** * Executes the given SQL statement and signals the driver that the auto-generated keys indicated * in the given array should be made available for retrieval. This array contains the indexes of * the columns in the target table that contain the auto-generated keys that should be made * available. The driver will ignore the array if the SQL statement is not an INSERT * statement, or an SQL statement able to return auto-generated keys (the list of such statements * is vendor-specific). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT, * UPDATE or DELETE; or an SQL statement that returns nothing, such * as a DDL statement. * @param columnIndexes an array of column indexes indicating the columns that should be returned * from the inserted row * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 * for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the SQL statement returns a ResultSet object,the * second argument supplied to this method is not an int array whose elements are * valid column indexes, the method is called on a PreparedStatement or * CallableStatement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @since 1.4 */ @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); } /** * Executes the given SQL statement and signals the driver that the auto-generated keys indicated * in the given array should be made available for retrieval. This array contains the names of the * columns in the target table that contain the auto-generated keys that should be made available. * The driver will ignore the array if the SQL statement is not an INSERT statement, * or an SQL statement able to return auto-generated keys (the list of such statements is * vendor-specific). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql an SQL Data Manipulation Language (DML) statement, such as INSERT, * UPDATE or DELETE; or an SQL statement that returns nothing, such * as a DDL statement. * @param columnNames an array of the names of the columns that should be returned from the * inserted row * @return either the row count for INSERT, UPDATE, or DELETE * statements, or 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the SQL statement returns a ResultSet object, the * second argument supplied to this method is not a String array whose elements * are valid column names, the method is called on a PreparedStatement or * CallableStatement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @since 1.4 */ @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); } /** * Executes the given SQL statement, which may return multiple results, and signals the driver * that any auto-generated keys should be made available for retrieval. The driver will ignore * this signal if the SQL statement is not an INSERT statement, or an SQL statement * able to return auto-generated keys (the list of such statements is vendor-specific). * *

In some (uncommon) situations, a single SQL statement may return multiple result sets and/or * update counts. Normally you can ignore this unless you are (1) executing a stored procedure * that you know may return multiple results or (2) you are dynamically executing an unknown SQL * string. * *

The execute method executes an SQL statement and indicates the form of the * first result. You must then use the methods getResultSet or getUpdateCount * to retrieve the result, and getMoreResults to move to any subsequent * result(s). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql any SQL statement * @param autoGeneratedKeys a constant indicating whether auto-generated keys should be made * available for retrieval using the method getGeneratedKeys; one of the * following constants: Statement.RETURN_GENERATED_KEYS or * Statement.NO_GENERATED_KEYS * @return true if the first result is a ResultSet object; false * if it is an update count or there are no results * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the second parameter supplied to this method is not * Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS, the * method is called on a PreparedStatement or CallableStatement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method with a * constant of Statement.RETURN_GENERATED_KEYS * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @see #getResultSet * @see #getUpdateCount * @see #getMoreResults * @see #getGeneratedKeys * @since 1.4 */ @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { executeInternal(sql, autoGeneratedKeys); currResult = results.remove(0); return currResult instanceof Result; } /** * Build sql command to execute : * *

    *
  • Execute escape substitution if needed *
  • add query timeout prefix if server permits it *
  • add max row limit prefix if server permits it *
* * @param sql sql command * @return sql command to execute * @throws SQLException if fails to escape sql */ protected String escapeTimeout(final String sql) throws SQLException { String escapedSql = escape ? NativeSql.parse(sql, con.getContext()) : sql; if (queryTimeout != 0 && canUseServerTimeout) { if (canUseServerMaxRows && maxRows > 0) { return "SET STATEMENT max_statement_time=" + queryTimeout + ", SQL_SELECT_LIMIT=" + maxRows + " FOR " + escapedSql; } return "SET STATEMENT max_statement_time=" + queryTimeout + " FOR " + escapedSql; } if (canUseServerMaxRows && maxRows > 0) { return "SET STATEMENT SQL_SELECT_LIMIT=" + maxRows + " FOR " + escapedSql; } return escapedSql; } /** * Executes the given SQL statement, which may return multiple results, and signals the driver * that the auto-generated keys indicated in the given array should be made available for * retrieval. This array contains the indexes of the columns in the target table that contain the * auto-generated keys that should be made available. The driver will ignore the array if the SQL * statement is not an INSERT statement, or an SQL statement able to return * auto-generated keys (the list of such statements is vendor-specific). * *

Under some (uncommon) situations, a single SQL statement may return multiple result sets * and/or update counts. Normally you can ignore this unless you are (1) executing a stored * procedure that you know may return multiple results or (2) you are dynamically executing an * unknown SQL string. * *

The execute method executes an SQL statement and indicates the form of the * first result. You must then use the methods getResultSet or getUpdateCount * to retrieve the result, and getMoreResults to move to any subsequent * result(s). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql any SQL statement * @param columnIndexes an array of the indexes of the columns in the inserted row that should be * made available for retrieval by a call to the method getGeneratedKeys * @return true if the first result is a ResultSet object; false * if it is an update count or there are no results * @throws SQLException if a database access error occurs, this method is called on a closed * Statement, the elements in the int array passed to this method * are not valid column indexes, the method is called on a PreparedStatement or * CallableStatement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @see #getResultSet * @see #getUpdateCount * @see #getMoreResults * @since 1.4 */ @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { return execute(sql, Statement.RETURN_GENERATED_KEYS); } /** * Executes the given SQL statement, which may return multiple results, and signals the driver * that the auto-generated keys indicated in the given array should be made available for * retrieval. This array contains the names of the columns in the target table that contain the * auto-generated keys that should be made available. The driver will ignore the array if the SQL * statement is not an INSERT statement, or an SQL statement able to return * auto-generated keys (the list of such statements is vendor-specific). * *

In some (uncommon) situations, a single SQL statement may return multiple result sets and/or * update counts. Normally you can ignore this unless you are (1) executing a stored procedure * that you know may return multiple results or (2) you are dynamically executing an unknown SQL * string. * *

The execute method executes an SQL statement and indicates the form of the * first result. You must then use the methods getResultSet or getUpdateCount * to retrieve the result, and getMoreResults to move to any subsequent * result(s). * *

Note:This method cannot be called on a PreparedStatement or * CallableStatement. * * @param sql any SQL statement * @param columnNames an array of the names of the columns in the inserted row that should be made * available for retrieval by a call to the method getGeneratedKeys * @return true if the next result is a ResultSet object; false * if it is an update count or there are no more results * @throws SQLException if a database access error occurs, this method is called on a closed * Statement,the elements of the String array passed to this method * are not valid column names, the method is called on a PreparedStatement or * CallableStatement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @see #getResultSet * @see #getUpdateCount * @see #getMoreResults * @see #getGeneratedKeys * @since 1.4 */ @Override public boolean execute(String sql, String[] columnNames) throws SQLException { return execute(sql, Statement.RETURN_GENERATED_KEYS); } /** * Retrieves the result set holdability for ResultSet objects generated by this * Statement object. * * @return either ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @since 1.4 */ @Override public int getResultSetHoldability() { return ResultSet.HOLD_CURSORS_OVER_COMMIT; } /** * Retrieves whether this Statement object has been closed. A Statement * is closed if the method close has been called on it, or if it is automatically closed. * * @return true if this Statement object is closed; false if it is still open */ @Override public boolean isClosed() { return closed; } /** * Returns a value indicating whether the Statement is poolable or not. * * @return true if the Statement is poolable; false * otherwise * @throws SQLException if this method is called on a closed Statement * @see java.sql.Statement#setPoolable(boolean) setPoolable(boolean) */ @Override public boolean isPoolable() throws SQLException { checkNotClosed(); return false; } /** * Requests that a Statement be pooled or not pooled. The value specified is a hint * to the statement pool implementation indicating whether the application wants the statement to * be pooled. It is up to the statement pool manager whether the hint is used. * *

The poolable value of a statement is applicable to both internal statement caches * implemented by the driver and external statement caches implemented by application servers and * other applications. * *

By default, a Statement is not poolable when created, and a * PreparedStatement and CallableStatement are poolable when created. * * @param poolable requests that the statement be pooled if true and that the statement not be * pooled if false * @throws SQLException if this method is called on a closed Statement */ @Override public void setPoolable(boolean poolable) throws SQLException { checkNotClosed(); } /** * Specifies that this {@code Statement} will be closed when all its dependent result sets are * closed. If execution of the {@code Statement} does not produce any result sets, this method has * no effect. * *

Note: Multiple calls to {@code closeOnCompletion} do not toggle the effect * on this {@code Statement}. However, a call to {@code closeOnCompletion} does affect both the * subsequent execution of statements, and statements that currently have open, dependent, result * sets. * * @throws SQLException if this method is called on a closed {@code Statement} */ @Override public void closeOnCompletion() throws SQLException { checkNotClosed(); this.closeOnCompletion = true; } /** * Returns a value indicating whether this {@code Statement} will be closed when all its dependent * result sets are closed. * * @return {@code true} if the {@code Statement} will be closed when all of its dependent result * sets are closed; {@code false} otherwise * @throws SQLException if this method is called on a closed {@code Statement} */ @Override public boolean isCloseOnCompletion() throws SQLException { checkNotClosed(); return closeOnCompletion; } /** * Returns an object that implements the given interface to allow access to non-standard methods, * or standard methods not exposed by the proxy. * *

If the receiver implements the interface then the result is the receiver or a proxy for the * receiver. If the receiver is a wrapper and the wrapped object implements the interface then the * result is the wrapped object or a proxy for the wrapped object. Otherwise, return the result of * calling unwrap recursively on the wrapped object or a proxy for that result. If * the receiver is not a wrapper and does not implement the interface, then an SQLException * is thrown. * * @param iface A Class defining an interface that the result must implement. * @return an object that implements the interface. Maybe a proxy for the actual implementing * object. * @throws SQLException If no object found that implements the interface */ @Override @SuppressWarnings("unchecked") public T unwrap(Class iface) throws SQLException { if (isWrapperFor(iface)) { return (T) this; } throw exceptionFactory() .create("The receiver is not a wrapper and does not implement the interface", "42000"); } /** * Returns true if this either implements the interface argument or is directly or indirectly a * wrapper for an object that does. Returns false otherwise. If this implements the interface then * return true, else if this is a wrapper then return the result of recursively calling * isWrapperFor on the wrapped object. If this does not implement the interface and is not * a wrapper, return false. This method should be implemented as a low-cost operation compared to * unwrap so that callers can use this method to avoid expensive unwrap * calls that may fail. If this method returns true then calling unwrap with the same * argument should succeed. * * @param iface a Class defining an interface. * @return true if this implements the interface or directly or indirectly wraps an object that * does. */ @Override public boolean isWrapperFor(Class iface) { if (iface == null) return false; return iface.isInstance(this); } /** * Check if statement is closed, and throw exception if so. * * @throws SQLException if statement close */ protected void checkNotClosed() throws SQLException { if (closed) { throw exceptionFactory().create("Cannot do an operation on a closed statement"); } } /** * Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL * statement that returns nothing, such as an SQL DDL statement. This method should be used when * the returned row count may exceed Integer.MAX_VALUE. * * @param sql sql command * @return update counts * @throws SQLException if any error occur during execution */ @Override public long executeLargeUpdate(String sql) throws SQLException { return executeLargeUpdate(sql, Statement.NO_GENERATED_KEYS); } /** * Identical to executeLargeUpdate(String sql), with a flag that indicate that autoGeneratedKeys * (primary key fields with "auto_increment") generated id's must be retrieved. * *

Those id's will be available using getGeneratedKeys() method. * * @param sql sql command * @param autoGeneratedKeys a flag indicating whether auto-generated keys should be made available * for retrieval; one of the following constants: Statement.RETURN_GENERATED_KEYS * Statement.NO_GENERATED_KEYS * @return update counts * @throws SQLException if any error occur during execution */ @Override public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { executeInternal(sql, autoGeneratedKeys); currResult = results.remove(0); if (currResult instanceof Result) { throw exceptionFactory() .create("the given SQL statement produces an unexpected ResultSet object", "HY000"); } return ((OkPacket) currResult).getAffectedRows(); } /** * Identical to executeLargeUpdate(String sql, int autoGeneratedKeys) with autoGeneratedKeys = * Statement.RETURN_GENERATED_KEYS set. * * @param sql sql command * @param columnIndexes column Indexes * @return update counts * @throws SQLException if any error occur during execution */ @Override public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { return executeLargeUpdate(sql, java.sql.Statement.RETURN_GENERATED_KEYS); } /** * Identical to executeLargeUpdate(String sql, int autoGeneratedKeys) with autoGeneratedKeys = * Statement.RETURN_GENERATED_KEYS set. * * @param sql sql command * @param columnNames columns names * @return update counts * @throws SQLException if any error occur during execution */ @Override public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { return executeLargeUpdate(sql, java.sql.Statement.RETURN_GENERATED_KEYS); } /** * Retrieves the maximum number of rows that a ResultSet object produced by this Statement object * can contain. If this limit is exceeded, the excess rows are silently dropped. * * @throws SQLException if this method is called on a closed Statement * @return the current maximum number of rows for a ResultSet object produced by this Statement * object; zero means there is no limit */ @Override public long getLargeMaxRows() throws SQLException { checkNotClosed(); return maxRows; } /** * Sets the limit for the maximum number of rows that any ResultSet object generated by this * Statement object can contain to the given number. If the limit is exceeded, the excess rows are * silently dropped. * * @param max the new max rows limit; zero means there is no limit * @throws SQLException if the condition max >= 0 is not satisfied */ @Override public void setLargeMaxRows(long max) throws SQLException { checkNotClosed(); if (max < 0) { throw exceptionFactory().create("max rows cannot be negative : asked for " + max, "42000"); } maxRows = max; } /** * Retrieves the current result as an update count; if the result is a ResultSet object or there * are no more results, -1 is returned. * * @throws SQLException if this method is called on a closed Statement * @return last update count */ @Override public long getLargeUpdateCount() throws SQLException { checkNotClosed(); if (currResult instanceof OkPacket) { return (int) ((OkPacket) currResult).getAffectedRows(); } return -1; } /** * Execute batch, like executeBatch(), with returning results with long[]. For when row count may * exceed Integer.MAX_VALUE. * * @return an array of update counts (one element for each command in the batch) * @throws SQLException if a database error occur. */ @Override public long[] executeLargeBatch() throws SQLException { checkNotClosed(); if (batchQueries == null || batchQueries.isEmpty()) return new long[0]; lock.lock(); try { // ensure pipelining is possible (no LOAD DATA/XML INFILE commands) boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES); if (possibleLoadLocal) { for (int i = 0; i < batchQueries.size(); i++) { String sql = batchQueries.get(i).toUpperCase(Locale.ROOT); if (sql.contains(" LOCAL ") && sql.contains("LOAD") && sql.contains(" INFILE")) { break; } } possibleLoadLocal = false; } List res = possibleLoadLocal ? executeInternalBatchStandard() : executeInternalBatchPipeline(); results = res; long[] updates = new long[res.size()]; for (int i = 0; i < res.size(); i++) { updates[i] = ((OkPacket) res.get(i)).getAffectedRows(); } currResult = results.remove(0); batchQueries.clear(); return updates; } finally { lock.unlock(); } } /** * Execute batch pipelining commands (sending all client message, then reading results) (batches * cannot contain results-set, so cannot fill receiving socket buffer while sending buffer is * full) * * @return results * @throws SQLException if any error occurs */ public List executeInternalBatchPipeline() throws SQLException { QueryPacket[] packets = new QueryPacket[batchQueries.size()]; for (int i = 0; i < batchQueries.size(); i++) { String sql = batchQueries.get(i); packets[i] = new QueryPacket(sql); } return con.getClient() .executePipeline( packets, this, 0, 0L, ResultSet.CONCUR_READ_ONLY, ResultSet.TYPE_FORWARD_ONLY, closeOnCompletion, false); } /** * basic implementation Send batch query per query. * * @return results * @throws SQLException if any error occurs */ public List executeInternalBatchStandard() throws SQLException { List results = new ArrayList<>(); try { for (String batchQuery : batchQueries) { results.addAll( con.getClient() .execute( new QueryPacket(batchQuery, localInfileInputStream), this, 0, 0L, ResultSet.CONCUR_READ_ONLY, ResultSet.TYPE_FORWARD_ONLY, closeOnCompletion, false)); } return results; } catch (SQLException sqle) { int[] updateCounts = new int[batchQueries.size()]; for (int i = 0; i < Math.min(results.size(), updateCounts.length); i++) { Completion completion = results.get(i); updateCounts[i] = completion instanceof OkPacket ? (int) ((OkPacket) completion).getAffectedRows() : 0; } throw new BatchUpdateException( sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), updateCounts, sqle); } finally { localInfileInputStream = null; } } /** * Enquote String value. * * @param val string value to enquote * @return enquoted string value * @throws SQLException -not possible- */ @Override 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 */ @Override public String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException { if (isSimpleIdentifier(identifier)) { return alwaysQuote ? "`" + identifier + "`" : identifier; } else { if (identifier.contains("\u0000")) { throw exceptionFactory().create("Invalid name - containing u0000 character", "42000"); } if (identifier.matches("^`.+`$")) { identifier = identifier.substring(1, identifier.length() - 1); } return "`" + identifier.replace("`", "``") + "`"; } } /** * Retrieves whether identifier is a simple SQL identifier. The first character is an alphabetic * character from a through z, or from A through Z The string only contains alphanumeric * characters or the characters "_" and "$" * * @param identifier identifier * @return true if identifier doesn't have to be quoted * @see mariadb identifier name * @throws SQLException exception */ @Override 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 - */ @Override public String enquoteNCharLiteral(String val) throws SQLException { return "N'" + val.replace("'", "''") + "'"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy