DBStatement.java
/*
* Copyright 2013 Gregory Graham.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nz.co.gregs.dbvolution.databases;
import java.sql.*;
import nz.co.gregs.dbvolution.exceptions.LoopDetectedInRecursiveSQL;
import java.util.ArrayList;
import java.util.List;
import nz.co.gregs.dbvolution.databases.DBDatabaseImplementation.ResponseToException;
import static nz.co.gregs.dbvolution.databases.DBDatabaseImplementation.ResponseToException.*;
import nz.co.gregs.dbvolution.databases.connections.DBConnection;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.exceptions.UnableToCreateDatabaseConnectionException;
import nz.co.gregs.dbvolution.exceptions.UnableToFindJDBCDriver;
import nz.co.gregs.dbvolution.internal.query.QueryTimeout;
import nz.co.gregs.dbvolution.internal.query.StatementDetails;
import nz.co.gregs.dbvolution.utility.StringCheck;
import nz.co.gregs.regexi.Regex;
import nz.co.gregs.regexi.internal.PartialRegex;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Encapsulates the JDBC Connection and Statement classes.
*
* <p>
* You should not need to create a DBStatement as they are managed by the
* DBDatabase internally.
*
* <p>
* DBStatement simplifies the JDBC interface by managing the connection and
* statement simultaneously. When the statement is closed so is the connection
* ensuring minimalist usage of the database.
*
* <p>
* Mostly this is a thin wrapper around DBDatabase, Connection, and Statement
* objects
*
*
* @author Gregory Graham
*/
public class DBStatement implements AutoCloseable {
static final private Log LOG = LogFactory.getLog(DBStatement.class);
private Statement internalStatement;
final DBDatabase database;
private DBConnection connection;
private boolean isClosed = false;
private final List<String> localBatchList = new ArrayList<String>();
private final Long TIMEOUT_IN_MILLISECONDS = 10000L;
/**
* Creates a statement object for the given DBDatabase and Connection.
*
* @param db the target database
* @param connection the connection to the database
*/
public DBStatement(DBDatabase db, DBConnection connection) {
this.database = db;
this.connection = connection;
}
/**
* Executes the given SQL statement, which returns a single ResultSet object.
*
* @param details the full details of the query including the SQL to be
* executed
* @return a ResultSet the results, if any, of the query
* @throws SQLException database exceptions
*/
public ResultSet executeQuery(StatementDetails details) throws SQLException {
return executeQueryWithRecovery(details);
}
public ResultSet executeQueryWithRecovery(StatementDetails details) throws SQLException {
String sql = details.getSql();
String label = details.getLabel();
final String logSQL = "EXECUTING QUERY \"" + label + "\" on " + this.database.getJdbcURL() + ": \n" + sql;
database.printSQLIfRequested(logSQL);
ResultSet executeQuery = null;
try {
executeQuery = executeQueryWithTimeout(details);
} catch (SQLException exp) {
try {
var statementDetails = details.copy().withLabel("UNLABELLED QUERY").withException(exp);
statementDetails.setIgnoreExceptions(details.isIgnoreExceptions());
executeQuery = addFeatureAndAttemptQueryAgain(statementDetails);
} catch (LoopDetectedInRecursiveSQL loop) {
throw loop;
} catch (SQLException ex) {
throw ex;
} catch (Exception ex) {
throw new SQLException(ex);
}
}
return executeQuery;
}
private ResultSet executeQueryWithTimeout(StatementDetails details) throws SQLException {
final Long timeoutTime = details.getTimeout();
QueryTimeout timer = new QueryTimeout(details, timeoutTime);
ResultSet queryResult = null;
try {
queryResult = executeQueryWithInternalStatement(details);
timer.noLongerRequired();
if (timer.queryTimedOut()) {
throw new SQLTimeoutException("Execution Timed Out");
}
} finally {
timer.noLongerRequired();
}
return queryResult;
}
private ResultSet executeQueryWithInternalStatement(StatementDetails details) throws SQLException {
return getInternalStatement().executeQuery(details.getSql());
}
private ResultSet addFeatureAndAttemptQueryAgain(StatementDetails details) throws SQLException, Exception, LoopDetectedInRecursiveSQL {
ResultSet executeQuery;
final Exception exp = details.getException();
final QueryIntention intent = details.getIntention();
if (!checkForBrokenConnection(exp)) {
try {
ResponseToException response = handleResponseFromFixingException(exp, intent, details);
if (response.equals(SKIPQUERY)) {
return null;
}
} catch (LoopDetectedInRecursiveSQL loop) {
throw loop;
} catch (Exception ex) {
if (intent.is(QueryIntention.CHECK_TABLE_EXISTS)) {
// Checking the table will generate exceptions that we don't need to investigate
} else if (details.isIgnoreExceptions()) {
// do nothing
} else {
// LOG.info("REPEATED EXCEPTIONS FROM: " + sql, exp);
}
Exception ex1 = exp;
while (!ex1.getMessage().equals(ex.getMessage())) {
ResponseToException response = handleResponseFromFixingException(exp, intent, details);
if (response.equals(ResponseToException.SKIPQUERY)) {
return null;
}
}
throw new SQLException(ex);
}
}
try {
executeQuery = executeQueryWithTimeout(details);
return executeQuery;
} catch (SQLException exp2) {
if (exp.getMessage().equals(exp2.getMessage())) {
throw exp;
} else {
final StatementDetails statementDetails = details.copy().withLabel("RETRYING FAILED SQL").withException(exp2);
executeQuery = addFeatureAndAttemptQueryAgain(statementDetails);
return executeQuery;
}
}
}
/**
* 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.
*
* @param string string
* @return either (1) the row count for SQL Data Manipulation Language (DML)
* statements or (2) 0 for SQL statements that return nothing 1 Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int executeUpdate(String string) throws SQLException {
database.printSQLIfRequested(string);
int executeUpdate = getInternalStatement().executeUpdate(string);
return executeUpdate;
}
/**
* Closes the Statement and the Connection.
*
* <p>
* Also informs the DBDatabase that there is one less connection to the
* database.
*
* 1 Database exceptions may be thrown
*
* @throws java.sql.SQLException java.sql.SQLException
*/
@Override
public void close() throws SQLException {
isClosed = true;
try {
database.unusedConnection(getConnection());
} catch (SQLException e) {
// Someone please tell me how you are supposed to cope
// with an exception during the close method????????
LOG.warn("Exception occurred during close(): " + e.getMessage(), e);
}
closeInternalStatement();
}
private void closeInternalStatement() {
Statement statementToClose;
synchronized (this) {
statementToClose = internalStatement;
internalStatement = null;
}
if (statementToClose != null) {
try {
statementToClose.close();
} catch (SQLException e) {
// Someone please tell me how you are supposed to cope
// with an exception during the close method????????
LOG.warn("Exception occurred during close(): No action required");
LOG.warn("Exception occurred during close(): " + e.getMessage(), e);
}
}
}
/**
* 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. 1 Database exceptions may be
* thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getMaxFieldSize() throws SQLException {
return getInternalStatement().getMaxFieldSize();
}
/**
* Sets the limit for the maximum number of bytes that can be returned for
* character and binary column values in a ResultSet object produced by this
* Statement object. This limit applies only to BINARY, VARBINARY,
* LONGVARBINARY, CHAR, VARCHAR, NCHAR, NVARCHAR, LONGNVARCHAR and LONGVARCHAR
* fields. If the limit is exceeded, the excess data is silently discarded.
* For maximum portability, use values greater than 256.
*
*
* 1 Database exceptions may be thrown
*
* @param i i
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setMaxFieldSize(int i) throws SQLException {
getInternalStatement().setMaxFieldSize(i);
}
/**
* 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 <code>ResultSet</code>
* object produced by this <code>Statement</code> object; zero means there is
* no limit 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getMaxRows() throws SQLException {
return getInternalStatement().getMaxRows();
}
/**
* 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.
*
*
* 1 Database exceptions may be thrown
*
* @param i i
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setMaxRows(int i) throws SQLException {
getInternalStatement().setMaxRows(i);
}
/**
* Sets escape processing on or off. If escape scanning is on (the default),
* the driver will do escape substitution before sending the SQL statement to
* the database. Note: Since prepared statements have usually been parsed
* prior to making this call, disabling escape processing for
* PreparedStatements objects will have no effect.
*
*
* 1 Database exceptions may be thrown
*
* @param bln bln
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setEscapeProcessing(boolean bln) throws SQLException {
getInternalStatement().setEscapeProcessing(bln);
}
/**
* 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 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getQueryTimeout() throws SQLException {
return getInternalStatement().getQueryTimeout();
}
/**
* 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.
*
*
* 1 Database exceptions may be thrown
*
* @param i i
* @throws java.sql.SQLException Database exceptions may be thrown
*/
public void setQueryTimeout(int i) throws SQLException {
getInternalStatement().setQueryTimeout(i);
}
/**
* 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. 1 Database exceptions
* may be thrown
*
* @throws SQLException Database exceptions may be thrown
*/
public synchronized void cancel() throws SQLException {
try {
getInternalStatement().cancel();
if (database.getDefinition().willCloseConnectionOnStatementCancel()) {
replaceBrokenConnection();
}
} catch (SQLException | UnableToCreateDatabaseConnectionException | UnableToFindJDBCDriver ex) {
}
}
/**
* Discards the current connection, replaces it, and creates a new statement.
*
* <p>
* Call this after canceling or closing a statement for a database that close
* the connection with the statement. Use {@link DBDefinition#willCloseConnectionOnStatementCancel()
* } to detect this situation.
*
* @throws SQLException exceptions from connecting to the database and
* creating a Statement.
* @throws UnableToCreateDatabaseConnectionException may be thrown if there is
* an issue with connecting.
* @throws UnableToFindJDBCDriver may be thrown if the JDBCDriver is not on
* the class path. DBvolution includes several JDBCDrivers already but Oracle
* and MS SQLserver, in particular, need to be added to the path if you wish
* to work with those databases.
*/
protected synchronized void replaceBrokenConnection() throws SQLException, UnableToCreateDatabaseConnectionException, UnableToFindJDBCDriver {
try {
database.discardConnection(connection);
connection = database.getConnection();
if (internalStatement != null) {
try {
internalStatement.close();
} catch (SQLException exp) {
LOG.debug(this, exp);
}
internalStatement = null;
getInternalStatement();
}
} catch (SQLException sqlex) {
throw sqlex;
}
}
/**
* Retrieves the first warning reported by calls on this Statement object.
* Subsequent Statement object warnings will be chained to this SQLWarning
* object.
*
* <p>
* 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.
*
*
* @return the first <code>SQLWarning</code> object or <code>null</code> if
* there are no warnings 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public SQLWarning getWarnings() throws SQLException {
return getInternalStatement().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.
*
* 1 Database exceptions may be thrown
*
* @throws java.sql.SQLException java.sql.SQLException
*/
public void clearWarnings() throws SQLException {
getInternalStatement().clearWarnings();
}
/**
* Sets the SQL cursor name to the given String, which will be used by
* subsequent Statement object execute methods.
*
* <P>
* This name can then be used in SQL positioned update or delete statements to
* identify the current row in the ResultSet object generated by this
* statement. If the database does not support positioned update/delete, this
* method is a noop. To insure that a cursor has the proper isolation level to
* support updates, the cursor's SELECT statement should have the form SELECT
* FOR UPDATE. If FOR UPDATE is not present, positioned updates may fail.
*
* @param string string
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setCursorName(String string) throws SQLException {
getInternalStatement().setCursorName(string);
}
/**
* Executes the given SQL statement, which may return multiple results.
*
* <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 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).
*
*
* @param label the display name for this execution
* @param queryIntention the expected outcome of this execution
* @param sql the actual SQL to execute
* @throws SQLException Database exceptions may be thrown
*/
public void execute(String label, QueryIntention queryIntention, String sql) throws SQLException {
execute(new StatementDetails(label, queryIntention, sql, this));
}
/**
* Executes the given SQL statement, which may return multiple results.
*
* <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 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).
*
*
* @param queryIntention the expected outcome of this execution
* @param sql the actual SQL to execute
* @throws SQLException Database exceptions may be thrown
*/
public void execute(QueryIntention queryIntention, String sql) throws SQLException {
execute(new StatementDetails(queryIntention.toString(), queryIntention, sql, this));
}
/**
* Executes the given SQL statement, which may return multiple results.
*
* <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 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).
*
* @param details the full details of the query including the SQL to be
* executed
*
* @throws SQLException Database exceptions may be thrown
*/
public void execute(StatementDetails details) throws SQLException {
executeWithRecovery(details);
}
private void executeWithRecovery(StatementDetails details) throws SQLException {
details.setDBStatement(this);
String sql = details.getSql();
final String logSQL = "EXECUTING on " + database.getLabel() + ": " + sql;
database.printSQLIfRequested(logSQL);
LOG.debug(logSQL);
try {
executeWithTimeout(details);
} catch (SQLException exp) {
StatementDetails statementDetails
= details.copy()
.withLabel("RETRY EXECUTE")
.withException(exp);
addFeatureAndAttemptExecuteAgain(statementDetails);
}
}
private void executeWithTimeout(StatementDetails details) throws SQLException {
final Long timeoutTime = this.getTIMEOUT_IN_MILLISECONDS();
QueryTimeout timer = new QueryTimeout(details, timeoutTime);
try {
executeOnInternalStatement(details);
timer.noLongerRequired();
if (timer.queryTimedOut()) {
throw new SQLTimeoutException("Execution Timed Out");
}
} finally {
timer.noLongerRequired();
}
}
private void executeOnInternalStatement(StatementDetails details) throws UnableToCreateDatabaseConnectionException, SQLException, UnableToFindJDBCDriver {
Statement stmt = getInternalStatement();
details.execute(stmt);
}
static final Regex DROP_INTENTION_MATCHER = Regex.startingAnywhere().literal("DROP").toRegex();
static final Regex DROP_EXCEPTION_MATCHER = Regex.startingAnywhere().beginCaseInsensitiveSection().anyOf("does not exist", "doesn't exist").endCaseInsensitiveSection().toRegex();
private void addFeatureAndAttemptExecuteAgain(StatementDetails details) throws SQLException {
details.setDBStatement(this);
String sql = details.getSql();
Exception exp = details.getException();
QueryIntention intent = details.getIntention();
if (DROP_INTENTION_MATCHER.matchesWithinString(details.getIntention().name())
&& DROP_EXCEPTION_MATCHER.matchesWithinString(exp.getMessage())) {
// discard as we've tried to drop something that doesn't exist and that's ok
LOG.info("Attempted to drop an entity that doesn't exist - continuing: " + exp.getMessage());
} else {
if (!checkForBrokenConnection(exp)) {
try {
ResponseToException response = handleResponseFromFixingException(exp, intent, details);
if (response.equals(ResponseToException.SKIPQUERY)) {
return;
}
} catch (Exception ex) {
throw new SQLException("Failed To Add Support On " + database.getJdbcURL() + " For SQL: " + exp.getMessage() + " : Original Query: " + sql, ex);
}
}
try {
executeWithTimeout(details);
} catch (SQLException exp2) {
if (!exp.getMessage().equals(exp2.getMessage())) {
addFeatureAndAttemptExecuteAgain(details);
} else {
throw new SQLException(exp);
}
}
}
}
public ResponseToException handleResponseFromFixingException(Exception exp, QueryIntention intent, StatementDetails details) throws Exception {
details.setDBStatement(this);
try {
ResponseToException response = database.addFeatureToFixException(exp, intent, details);
switch (response) {
case SKIPQUERY:
return SKIPQUERY;
case REQUERY:
return REQUERY;
case REPLACECONNECTION:
replaceBrokenConnection();
return REQUERY;
case EMULATE_RECURSIVE_QUERY:
throw new LoopDetectedInRecursiveSQL();
default:
break;
}
} catch (Exception exc) {
throw exc;
}
return NOT_HANDLED;
}
/**
* Retrieves the current result as a ResultSet object.
*
* <p>
* 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 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public ResultSet getResultSet() throws SQLException {
return getInternalStatement().getResultSet();
}
/**
* Retrieves the current result as an update count; if the result is a
* ResultSet object or there are no more results, -1 is returned.
* <p>
* 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. 1 Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getUpdateCount() throws SQLException {
return getInternalStatement().getUpdateCount();
}
/**
* 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.
* <p>
* There are no more results when the following is true:
* <p>
* <code>
* // stmt is a Statement object ((stmt.getMoreResults() == false) &&
* (stmt.getUpdateCount() == -1))
* </code>
*
*
* @return true if the next result is a ResultSet object; false if it is an
* update count or there are no more results 1 Database exceptions may be
* thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public boolean getMoreResults() throws SQLException {
return getInternalStatement().getMoreResults();
}
/**
* Gives the driver a hint as to the direction in which rows will be processed
* in ResultSet objects created using this Statement object.
* <P>
* The default value is ResultSet.FETCH_FORWARD.
*
*
* 1 Database exceptions may be thrown
*
* @param i i
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setFetchDirection(int i) throws SQLException {
getInternalStatement().setFetchDirection(i);
}
/**
* Retrieves the direction for fetching rows from database tables that is the
* default for result sets generated from this Statement object.
* <P>
* 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 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getFetchDirection() throws SQLException {
return getInternalStatement().getFetchDirection();
}
/**
* 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.
* <P>
* If the value specified is zero, then the hint is ignored. The default value
* is zero.
*
*
* 1 Database exceptions may be thrown
*
* @param i i
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setFetchSize(int i) throws SQLException {
getInternalStatement().setFetchSize(i);
}
/**
* Retrieves the number of result set rows that is the default fetch size for
* ResultSet objects generated from this Statement object.
* <P>
* 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
*
* 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getFetchSize() throws SQLException {
return getInternalStatement().getFetchSize();
}
/**
* Retrieves the result set concurrency for ResultSet objects generated by
* this Statement object.
*
*
* @return either ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE 1
* Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getResultSetConcurrency() throws SQLException {
return getInternalStatement().getResultSetConcurrency();
}
/**
* 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 1
* Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getResultSetType() throws SQLException {
return getInternalStatement().getResultSetType();
}
/**
* 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.
*
*
* 1 Database exceptions may be thrown
*
* @param string string
* @throws java.sql.SQLException java.sql.SQLException
*/
public void addBatch(String string) throws SQLException {
localBatchList.add(string);
getInternalStatement().addBatch(string);
}
/**
* Empties this Statement object's current list of SQL commands.
*
* 1 Database exceptions may be thrown
*
* @throws java.sql.SQLException java.sql.SQLException
*/
public void clearBatch() throws SQLException {
localBatchList.clear();
getInternalStatement().clearBatch();
}
/**
* Submits a batch of commands to the database for execution and if all
* commands execute successfully, returns an array of update counts.
* <P>
* 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: <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><li>A value of
* SUCCESS_NO_INFO -- indicates that the command was processed successfully
* but that the number of rows affected is unknown </li><li>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</li></ol><p>
* 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 a value of EXECUTE_FAILED.
* <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
* 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. 1 Database exceptions may be
* thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int[] executeBatch() throws SQLException {
if (database.isPrintSQLBeforeExecuting()) {
localBatchList.stream().forEach((t) -> database.printSQLIfRequested(t));
}
return getInternalStatement().executeBatch();
}
/**
* Retrieves the Connection object that produced this Statement object.
*
*
* @return the connection that produced this statement 1 Database exceptions
* may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public DBConnection getConnection() throws SQLException {
return connection;
}
/**
* 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.
* <p>
* There are no more results when the following is true:</p>
* <code>
*
* (statement.getMoreResults(current) == false)
* && (statement.getUpdateCount() == -1))
* </code>
*
* @param i i
* @return true if the next result is a ResultSet object; false if it is an
* update count or there are no more results. 1 Database exceptions may be
* thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public boolean getMoreResults(int i) throws SQLException {
return getInternalStatement().getMoreResults();
}
/**
* Retrieves any auto-generated keys created as a result of executing this
* Statement object.
* <p>
* If this Statement object did not generate any keys, an empty ResultSet
* object is returned.
*
*
* @return a ResultSet object containing the auto-generated key(s) generated
* by the execution of this Statement object 1 Database exceptions may be
* thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public ResultSet getGeneratedKeys() throws SQLException {
return getInternalStatement().getGeneratedKeys();
}
/**
* 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.
* <P>
* The driver will ignore the flag if the SQL statement is not an INSERT
* statement, or an SQL statement able to return auto-generated keys (the list
* of such statements is vendor-specific).
*
* @param string string
* @param i i
* @return either (1) the row count for SQL Data Manipulation Language (DML)
* statements or (2) 0 for SQL statements that return nothing 1 Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int executeUpdate(String string, int i) throws SQLException {
database.printSQLIfRequested(string);
return getInternalStatement().executeUpdate(string, i);
}
/**
* 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.
* <P>
* This array contains the indexes of the columns in the target table that
* contain the auto-generated keys that should be made available. The driver
* will ignore the array if the SQL statement is not an INSERT statement, or
* an SQL statement able to return auto-generated keys (the list of such
* statements is vendor-specific).
*
* @param string string
* @param ints ints
* @return either (1) the row count for SQL Data Manipulation Language (DML)
* statements or (2) 0 for SQL statements that return nothing 1 Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int executeUpdate(String string, int[] ints) throws SQLException {
database.printSQLIfRequested(string);
return getInternalStatement().executeUpdate(string, ints);
}
/**
* 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.
* <p>
* This array contains the names of the columns in the target table that
* contain the auto-generated keys that should be made available. The driver
* will ignore the array if the SQL statement is not an INSERT statement, or
* an SQL statement able to return auto-generated keys (the list of such
* statements is vendor-specific).
*
* @param string string
* @param strings strings
* @return either the row count for INSERT, UPDATE, or DELETE statements, or 0
* for SQL statements that return nothing 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int executeUpdate(String string, String[] strings) throws SQLException {
final String logSQL = "EXECUTING UPDATE: " + string;
database.printSQLIfRequested(logSQL);
LOG.debug(logSQL);
return getInternalStatement().executeUpdate(string, strings);
}
/**
* 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 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public int getResultSetHoldability() throws SQLException {
return getInternalStatement().getResultSetHoldability();
}
/**
* 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
* 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public boolean isClosed() throws SQLException {
if (database.getDefinition().supportsStatementIsClosed()) {
return getInternalStatement().isClosed();
} else {
return isClosed;
}
}
/**
* Requests that a Statement be pooled or not pooled.
* <P>
* 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 as to whether the hint is used. The
* pool-able 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 Statement is not pool-able when created, and a
* PreparedStatement and CallableStatement are poolable when created.
*
*
* 1 Database exceptions may be thrown
*
* @param bln bln
* @throws java.sql.SQLException java.sql.SQLException
*/
public void setPoolable(boolean bln) throws SQLException {
getInternalStatement().setPoolable(bln);
}
/**
* Returns a value indicating whether the Statement is poolable or not.
*
*
* @return true if the Statement is poolable; false otherwise
*
* 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public boolean isPoolable() throws SQLException {
return getInternalStatement().isPoolable();
}
/**
* Returns an object that implements the given interface to allow access to
* non-standard methods, or standard methods not exposed by the proxy.
*
* If the receiver implements the interface then the result is the receiver or
* a proxy for the receiver. If the receiver is a wrapper and the wrapped
* object implements the interface then the result is the wrapped object or a
* proxy for the wrapped object. Otherwise return the the result of calling
* <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.
* @param <T> the required interface.
* @return an object that implements the interface. May be a proxy for the
* actual implementing object. 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public <T> T unwrap(Class<T> iface) throws SQLException {
return getInternalStatement().unwrap(iface);
}
/**
* 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.
* @throws java.sql.SQLException if an error occurs while determining whether
* this is a wrapper for an object with the given interface.
* @since 1.6
*/
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return getInternalStatement().isWrapperFor(iface);
}
/**
* Indicates that a batch has been added.
*
*
* @return TRUE if the batch has un-executed entries, otherwise FALSE.
*/
public boolean getBatchHasEntries() {
return !getBatchIsEmpty();
}
/**
* Indicates that a batch has been added.
*
*
* @return TRUE if the batch has un-executed entries, otherwise FALSE.
*/
public boolean getBatchIsEmpty() {
return localBatchList.isEmpty();
}
/**
* Unsupported.
*
* 1 Database exceptions may be thrown
*
* @throws java.sql.SQLException java.sql.SQLException
*/
public void closeOnCompletion() throws SQLException {
throw new UnsupportedOperationException("DBStatement does not support closeOnCompletion() yet."); //To change body of generated methods, choose Tools | Templates.
}
/**
* Unsupported.
*
*
* @return unsupported 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public boolean isCloseOnCompletion() throws SQLException {
throw new UnsupportedOperationException("DBStatement does not support closeOnCompletion() yet."); //To change body of generated methods, choose Tools | Templates.
}
/**
*
* @return the internalStatement
* @throws java.sql.SQLException database errors
*/
protected synchronized Statement getInternalStatement() throws SQLException {
if (connection == null) {
replaceBrokenConnection();
}
if (connection.isClosed()) {
replaceBrokenConnection();
}
if (this.internalStatement == null) {
this.setInternalStatement(connection.getInternalStatement());
}
return this.internalStatement;
}
/**
* @param realStatement the internalStatement to set
*/
protected synchronized void setInternalStatement(Statement realStatement) {
this.internalStatement = realStatement;
}
//Could not create connection to database server
private static final PartialRegex COULDNT_CREATE_CONNECTION_REGEX = Regex.empty()
.literalCaseInsensitive("create").anyCharacter().zeroOrMore().literalCaseInsensitive("connection");
private static final PartialRegex CONNECTION_FAILED_REGEX = Regex.empty()
.literalCaseInsensitive("connection").anyCharacter().optionalMany().literalCaseInsensitive("failed");
private static final PartialRegex CONNECTION_BROKEN_REGEX = Regex.empty()
.literalCaseInsensitive("connection").anyCharacter().optionalMany().literalCaseInsensitive("broken");
private static final PartialRegex CONNECTION_CLOSED_REGEX = Regex.empty()
.literalCaseInsensitive("connection").anyCharacter().optionalMany().literalCaseInsensitive("closed");
private static final PartialRegex CONNECTION_RESET_REGEX = Regex.empty()
.literalCaseInsensitive("connection").anyCharacter().optionalMany().literalCaseInsensitive("reset");
private static final PartialRegex STATEMENT_BROKEN_REGEX = Regex.empty()
.literalCaseInsensitive("statement").anyCharacter().optionalMany().literalCaseInsensitive("broken");
private static final PartialRegex STATEMENT_CLOSED_REGEX = Regex.empty()
.literalCaseInsensitive("statement").anyCharacter().optionalMany().literalCaseInsensitive("closed");
private static final PartialRegex INSUFFICIENT_MEMORY_REGEX = Regex.empty()
.literalCaseInsensitive("There is insufficient system memory in resource pool ")
.charactersWrappedBy('\'')
.literalCaseInsensitive(" to run this query.");
private static final Regex ALL_KNOWN_ERRORS = Regex.startOrGroup()
.add(CONNECTION_BROKEN_REGEX)
.add(CONNECTION_CLOSED_REGEX)
.add(CONNECTION_FAILED_REGEX)
.add(CONNECTION_RESET_REGEX)
.add(COULDNT_CREATE_CONNECTION_REGEX)
.add(INSUFFICIENT_MEMORY_REGEX)
.add(STATEMENT_BROKEN_REGEX)
.add(STATEMENT_CLOSED_REGEX)
.endOrGroup().toRegex();
private boolean checkForBrokenConnection(Exception originalExc) throws SQLException {
Throwable exp = originalExc;
while (exp != null) {
String message = exp.getMessage();
if (StringCheck.isNotEmptyNorNull(message)) {
if (ALL_KNOWN_ERRORS.matchesWithinString(message)) {
replaceBrokenConnection();
return true;
}
}
exp = exp.getCause();
}
return false;
}
private Long getTIMEOUT_IN_MILLISECONDS() {
return TIMEOUT_IN_MILLISECONDS;
}
}