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