/*
Copyright (C) 2002 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.mysql.jdbc;
import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A Statement object is used for executing a static SQL statement and
* obtaining the results produced by it.
*
* <p>
* Only one ResultSet per Statement can be open at any point in time.
* Therefore, if the reading of one ResultSet is interleaved with the reading
* of another, each must have been generated by different Statements. All
* statement execute methods implicitly close a statement's current ResultSet
* if an open one exists.
* </p>
*
* @author Mark Matthews
* @version $Id: Statement.java,v 1.20.2.16 2003/12/24 05:16:26 mmatthew Exp $
*
* @see java.sql.Statement
* @see ResultSet
*/
public class Statement implements java.sql.Statement {
/** The connection that created us */
protected Connection connection = null;
/** Holds batched commands */
protected List batchedArgs;
/** List of currently-open ResultSets */
protected List openResults = new ArrayList();
/** The next result set */
protected ResultSet nextResults = null;
/** The current results */
protected ResultSet results = null;
/** The warnings chain. */
protected SQLWarning warningChain = null;
/** The pending warnings chain */
protected SQLWarning pendingWarnings = null;
/** The character converter to use (if available) */
protected SingleByteCharsetConverter charConverter = null;
/** The character encoding to use (if available) */
protected String charEncoding = null;
/** The catalog in use */
protected String currentCatalog = null;
/** Should we process escape codes? */
protected boolean doEscapeProcessing = true;
/** Has this statement been closed? */
protected boolean isClosed = false;
/** Has someone changed this for this statement? */
protected boolean maxRowsChanged = false;
/** Are we in pedantic mode? */
protected boolean pedantic = false;
/** The max field size for this statement */
protected int maxFieldSize = MysqlIO.getMaxBuf();
/**
* The maximum number of rows to return for this statement (-1 means _all_
* rows)
*/
protected int maxRows = -1;
/** The concurrency for this result set (updatable or not) */
protected int resultSetConcurrency = 0;
/** The type of this result set (scroll sensitive or in-sensitive) */
protected int resultSetType = 0;
/** The timeout for a query */
protected int timeout = 0;
/** The auto_increment value for the last insert */
protected long lastInsertId = -1;
/** The update count for this statement */
protected long updateCount = -1;
/** The number of rows to fetch at a time (currently ignored) */
private int fetchSize = 0;
/** Does the server support CAST/CONVERT? */
private boolean serverSupportsConvertFn;
/**
* Constructor for a Statement.
*
* @param c the Connection instantation that creates us
* @param catalog the database name in use when we were created
*
* @throws SQLException if an error occurs.
*/
public Statement(Connection c, String catalog) throws SQLException {
if (Driver.TRACE) {
Object[] args = { c };
Debug.methodCall(this, "constructor", args);
}
if ((c == null) || ((com.mysql.jdbc.Connection) c).isClosed()) {
throw new SQLException("Connection is closed.", "08003");
}
this.connection = c;
this.currentCatalog = catalog;
this.pedantic = this.connection.isPedantic();
this.serverSupportsConvertFn = this.connection.getIO().versionMeetsMinimum(4, 0, 2);
//
// Adjust, if we know it
//
if (connection != null) {
maxFieldSize = connection.getMaxAllowedPacket();
}
if (this.connection.useUnicode()) {
this.charEncoding = connection.getEncoding();
this.charConverter = this.connection.getCharsetConverter(this.charEncoding);
}
int maxRowsConn = this.connection.getMaxRows();
if (maxRowsConn != -1) {
setMaxRows(maxRowsConn);
}
}
/**
* JDBC 2.0 Return the Connection that produced the Statement.
*
* @return the Connection that produced the Statement
*
* @throws SQLException if an error occurs
*/
public java.sql.Connection getConnection() throws SQLException {
return (java.sql.Connection) connection;
}
/**
* setCursorName defines the SQL cursor name that will be used by
* subsequent execute methods. This name can then be used in SQL
* positioned update/delete statements to identify the current row in the
* ResultSet generated by this statement. If a database doesn't support
* positioned update/delete, this method is a no-op.
*
* <p>
* <b>Note:</b> This MySQL driver does not support cursors.
* </p>
*
* @param name the new cursor name
*
* @exception java.sql.SQLException if a database access error occurs
*/
public void setCursorName(String name) throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = { name };
Debug.methodCall(this, "setCursorName", args);
}
// No-op
}
/**
* If escape scanning is on (the default), the driver will do escape
* substitution before sending the SQL to the database.
*
* @param enable true to enable; false to disable
*
* @exception java.sql.SQLException if a database access error occurs
*/
public void setEscapeProcessing(boolean enable)
throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = { new Boolean(enable) };
Debug.methodCall(this, "setEscapeProcessing", args);
}
doEscapeProcessing = enable;
}
//--------------------------JDBC 2.0-----------------------------
/**
* JDBC 2.0 Give a hint as to the direction in which the rows in a result
* set will be processed. The hint applies only to result sets created
* using this Statement object. The default value is
* ResultSet.FETCH_FORWARD.
*
* @param direction the initial direction for processing rows
*
* @exception SQLException if a database-access error occurs or direction
* is not one of ResultSet.FETCH_FORWARD,
* ResultSet.FETCH_REVERSE, or ResultSet.FETCH_UNKNOWN
*/
public void setFetchDirection(int direction) throws SQLException {
switch (direction) {
case java.sql.ResultSet.FETCH_FORWARD:
case java.sql.ResultSet.FETCH_REVERSE:
case java.sql.ResultSet.FETCH_UNKNOWN:
break;
default:
throw new SQLException("Illegal value for setFetchDirection()",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
}
/**
* JDBC 2.0 Determine the fetch direction.
*
* @return the default fetch direction
*
* @exception SQLException if a database-access error occurs
*/
public int getFetchDirection() throws SQLException {
return java.sql.ResultSet.FETCH_FORWARD;
}
/**
* JDBC 2.0 Give the JDBC driver a hint as to the number of rows that
* should be fetched from the database when more rows are needed. The
* number of rows specified only affects result sets created using this
* statement. If the value specified is zero, then the hint is ignored.
* The default value is zero.
*
* @param rows the number of rows to fetch
*
* @exception SQLException if a database-access error occurs, or the
* condition 0 <= rows <= this.getMaxRows() is not
* satisfied.
*/
public void setFetchSize(int rows) throws SQLException {
if (((rows < 0) && (rows != Integer.MIN_VALUE))
|| ((maxRows != 0) && (maxRows != -1)
&& (rows > this.getMaxRows()))) {
throw new SQLException("Illegal value for setFetchSize()", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
fetchSize = rows;
}
/**
* JDBC 2.0 Determine the default fetch size.
*
* @return the number of rows to fetch at a time
*
* @throws SQLException if an error occurs
*/
public int getFetchSize() throws SQLException {
return fetchSize;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws SQLException DOCUMENT ME!
*/
public java.sql.ResultSet getGeneratedKeys() throws SQLException {
Field[] fields = new Field[1];
fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17);
ArrayList rowSet = new ArrayList();
long beginAt = getLastInsertID();
int numKeys = getUpdateCount();
String serverInfo = this.results.getServerInfo();
//
// Only parse server info messages for 'REPLACE'
// queries
//
if ((numKeys > 0)
&& this.results.getFirstCharOfQuery() == 'R'
&& (serverInfo != null)
&& (serverInfo.length() > 0)) {
numKeys = getRecordCountFromInfo(serverInfo);
}
if ((beginAt > 0) && (numKeys > 0)) {
for (int i = 0; i < numKeys; i++) {
byte[][] row = new byte[1][];
row[0] = Long.toString(beginAt++).getBytes();
rowSet.add(row);
}
}
return new com.mysql.jdbc.ResultSet(currentCatalog, fields,
new RowDataStatic(rowSet), connection);
}
/**
* getLastInsertID returns the value of the auto_incremented key after an
* executeQuery() or excute() call.
*
* <p>
* This gets around the un-threadsafe behavior of "select LAST_INSERT_ID()"
* which is tied to the Connection that created this Statement, and
* therefore could have had many INSERTS performed before one gets a
* chance to call "select LAST_INSERT_ID()".
* </p>
*
* @return the last update ID.
*/
public long getLastInsertID() {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getLastInsertID", args);
}
return lastInsertId;
}
/**
* getLongUpdateCount returns the current result as an update count, if the
* result is a ResultSet or there are no more results, -1 is returned. It
* should only be called once per result.
*
* <p>
* This method returns longs as MySQL server versions newer than 3.22.4
* return 64-bit values for update counts
* </p>
*
* @return the current update count.
*/
public long getLongUpdateCount() {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getLongUpdateCount", args);
}
if (results == null) {
return -1;
}
if (results.reallyResult()) {
return -1;
}
return updateCount;
}
/**
* Sets the maxFieldSize
*
* @param max the new max column size limit; zero means unlimited
*
* @exception SQLException if size exceeds buffer size
* @throws SQLException DOCUMENT ME!
*/
public void setMaxFieldSize(int max) throws SQLException {
if (Driver.TRACE) {
Object[] args = { new Integer(max) };
Debug.methodCall(this, "setMaxFieldSize", args);
}
if (max < 0) {
throw new SQLException("Illegal value for setMaxFieldSize()",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
int maxBuf = (connection != null) ? connection.getMaxAllowedPacket()
: MysqlIO.getMaxBuf();
if (max > maxBuf) {
throw new java.sql.SQLException(
"Can not set max field size > max allowed packet: " + maxBuf,
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
} else {
maxFieldSize = max;
}
}
/**
* The maxFieldSize limit (in bytes) is the maximum amount of data returned
* for any column value; it only applies to BINARY, VARBINARY,
* LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR columns. If the limit is
* exceeded, the excess data is silently discarded.
*
* @return the current max column size limit; zero means unlimited
*
* @exception java.sql.SQLException if a database access error occurs
*/
public int getMaxFieldSize() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getMaxFieldSize", args);
}
return maxFieldSize;
}
/**
* Set the maximum number of rows
*
* @param max the new max rows limit; zero means unlimited
*
* @exception java.sql.SQLException if a database access error occurs
*
* @see getMaxRows
*/
public void setMaxRows(int max) throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = { new Integer(max) };
Debug.methodCall(this, "setMaxRows", args);
}
if ((max > MysqlDefs.MAX_ROWS) || (max < 0)) {
throw new java.sql.SQLException("setMaxRows() out of range. " + max
+ " > " + MysqlDefs.MAX_ROWS + ".", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
if (max == 0) {
max = -1;
}
this.maxRows = max;
this.maxRowsChanged = true;
if (maxRows == -1) {
connection.unsetMaxRows(this);
this.maxRowsChanged = false;
} else {
// Most people don't use setMaxRows()
// so don't penalize them
// with the extra query it takes
// to do it efficiently unless we need
// to.
connection.maxRowsChanged(this);
}
}
/**
* The maxRows limit is set to limit the number of rows that any ResultSet
* can contain. If the limit is exceeded, the excess rows are silently
* dropped.
*
* @return the current maximum row limit; zero means unlimited
*
* @exception java.sql.SQLException if a database access error occurs
*/
public int getMaxRows() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getMaxRows", args);
}
if (maxRows <= 0) {
return 0;
} else {
return maxRows;
}
}
/**
* getMoreResults moves to a Statement's next result. If it returns true,
* this result is a ResulSet.
*
* @return true if the next ResultSet is valid
*
* @exception java.sql.SQLException if a database access error occurs
*/
public boolean getMoreResults() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getMoreResults", args);
}
return getMoreResults(CLOSE_CURRENT_RESULT);
}
/**
* @see Statement#getMoreResults(int)
*/
public synchronized boolean getMoreResults(int current)
throws SQLException {
switch (current) {
case Statement.CLOSE_CURRENT_RESULT:
if (results != null) {
results.close();
}
break;
case Statement.CLOSE_ALL_RESULTS:
if (results != null) {
results.close();
}
closeAllOpenResults();
break;
case Statement.KEEP_CURRENT_RESULT:
openResults.add(results);
break;
default:
throw new SQLException("Illegal flag for getMoreResults(int)",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
results = nextResults;
nextResults = null;
return ((results != null) && results.reallyResult()) ? true : false;
}
/**
* Sets the queryTimeout limit
*
* @param seconds - the new query timeout limit in seconds
*
* @exception SQLException if a database access error occurs
*/
public void setQueryTimeout(int seconds) throws SQLException {
if (Driver.TRACE) {
Object[] args = { new Integer(seconds) };
Debug.methodCall(this, "setQueryTimeout", args);
}
if (seconds < 0) {
throw new SQLException("Illegal value for setQueryTimeout()",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
timeout = seconds;
}
/**
* The queryTimeout limit is the number of seconds the driver will wait for
* a Statement to execute. If the limit is exceeded, a
* java.sql.SQLException is thrown.
*
* @return the current query timeout limit in seconds; 0 = unlimited
*
* @exception java.sql.SQLException if a database access error occurs
*/
public int getQueryTimeout() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getQueryTimeout", args);
}
return timeout;
}
/**
* getResultSet returns the current result as a ResultSet. It should only
* be called once per result.
*
* @return the current result set; null if there are no more
*
* @exception java.sql.SQLException if a database access error occurs
* (why?)
*/
public synchronized java.sql.ResultSet getResultSet()
throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getResultSet", args);
}
return ((results != null) && results.reallyResult())
? (java.sql.ResultSet) results : null;
}
/**
* JDBC 2.0 Determine the result set concurrency.
*
* @return CONCUR_UPDATABLE or CONCUR_READONLY
*
* @throws SQLException if an error occurs
*/
public int getResultSetConcurrency() throws SQLException {
return resultSetConcurrency;
}
/**
* @see Statement#getResultSetHoldability()
*/
public int getResultSetHoldability() throws SQLException {
return ResultSet.HOLD_CURSORS_OVER_COMMIT;
}
/**
* JDBC 2.0 Determine the result set type.
*
* @return the ResultSet type (SCROLL_SENSITIVE or SCROLL_INSENSITIVE)
*
* @throws SQLException if an error occurs.
*/
public int getResultSetType() throws SQLException {
return resultSetType;
}
/**
* getUpdateCount returns the current result as an update count, if the
* result is a ResultSet or there are no more results, -1 is returned. It
* should only be called once per result.
*
* @return the current result as an update count.
*
* @exception java.sql.SQLException if a database access error occurs
*/
public synchronized int getUpdateCount() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getUpdateCount", args);
}
if (results == null) {
return -1;
}
if (results.reallyResult()) {
return -1;
}
int truncatedUpdateCount = 0;
if (results.getUpdateCount() > Integer.MAX_VALUE) {
truncatedUpdateCount = Integer.MAX_VALUE;
} else {
truncatedUpdateCount = (int) results.getUpdateCount();
}
return truncatedUpdateCount;
}
/**
* The first warning reported by calls on this Statement is returned. A
* Statement's execute methods clear its java.sql.SQLWarning chain.
* Subsequent Statement warnings will be chained to this
* java.sql.SQLWarning.
*
* <p>
* The Warning chain is automatically cleared each time a statement is
* (re)executed.
* </p>
*
* <p>
* <B>Note:</B> If you are processing a ResultSet then any warnings
* associated with ResultSet reads will be chained on the ResultSet
* object.
* </p>
*
* @return the first java.sql.SQLWarning on null
*
* @exception java.sql.SQLException if a database access error occurs
*/
public synchronized java.sql.SQLWarning getWarnings()
throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "getWarnings", args);
}
return warningChain;
}
/**
* DOCUMENT ME!
*
* @param sql DOCUMENT ME!
*
* @throws SQLException DOCUMENT ME!
*/
public synchronized void addBatch(String sql) throws SQLException {
if (batchedArgs == null) {
batchedArgs = new ArrayList();
}
if (sql != null) {
batchedArgs.add(sql);
}
}
/**
* Cancel can be used by one thread to cancel a statement that is being
* executed by another thread. However this driver is synchronous, so
* this really has no meaning - we define it as a no-op (i.e. you can't
* cancel, but there is no error if you try.)
*
* @exception java.sql.SQLException only because thats the spec.
*/
public void cancel() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "cancel", args);
}
// No-op
}
/**
* JDBC 2.0 Make the set of commands in the current batch empty. This
* method is optional.
*
* @exception SQLException if a database-access error occurs, or the driver
* does not support batch statements
*/
public synchronized void clearBatch() throws SQLException {
if (batchedArgs != null) {
batchedArgs.clear();
}
}
/**
* After this call, getWarnings returns null until a new warning is
* reported for this Statement.
*
* @exception java.sql.SQLException if a database access error occurs
* (why?)
*/
public synchronized void clearWarnings() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "clearWarnings", args);
}
this.warningChain = this.pendingWarnings;
this.pendingWarnings = null;
}
/**
* In many cases, it is desirable to immediately release a Statement's
* database and JDBC resources instead of waiting for this to happen when
* it is automatically closed. The close method provides this immediate
* release.
*
* <p>
* <B>Note:</B> A Statement is automatically closed when it is garbage
* collected. When a Statement is closed, its current ResultSet, if one
* exists, is also closed.
* </p>
*
* @exception java.sql.SQLException if a database access error occurs
*/
public synchronized void close() throws java.sql.SQLException {
if (Driver.TRACE) {
Object[] args = new Object[0];
Debug.methodCall(this, "close", args);
}
if (this.isClosed) {
return;
}
if (results != null) {
try {
results.close();
} catch (Exception ex) {
;
}
}
if (this.maxRowsChanged && this.connection != null) {
this.connection.unsetMaxRows(this);
}
this.results = null;
this.connection = null;
this.warningChain = null;
this.isClosed = true;
this.closeAllOpenResults();
this.openResults = null;
}
/**
* Execute a SQL statement that may return multiple results. We don't have
* to worry about this since we do not support multiple ResultSets. You
* can use getResultSet or getUpdateCount to retrieve the result.
*
* @param sql any SQL statement
*
* @return true if the next result is a ResulSet, false if it is an update
* count or there are no more results
*
* @exception SQLException if a database access error occurs
*/
public synchronized boolean execute(String sql) throws SQLException {
if (Driver.TRACE) {
Object[] args = { sql };
Debug.methodCall(this, "execute", args);
}
char firstNonWsChar = StringUtils.firstNonWsCharUc(sql);
if (connection.isReadOnly()) {
if (firstNonWsChar != 'S') {
throw new SQLException("Connection is read-only. "
+ "Queries leading to data modification are not allowed",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
}
checkClosed();
if (this.doEscapeProcessing) {
sql = EscapeProcessor.escapeSQL(sql, this.serverSupportsConvertFn);
}
if (results != null) {
results.close();
}
ResultSet rs = null;
// If there isn't a limit clause in the SQL
// then limit the number of rows to return in
// an efficient manner. Only do this if
// setMaxRows() hasn't been used on any Statements
// generated from the current Connection (saves
// a query, and network traffic).
synchronized (connection.getMutex()) {
clearWarnings();
String oldCatalog = null;
if (!connection.getCatalog().equals(currentCatalog)) {
oldCatalog = connection.getCatalog();
connection.setCatalog(currentCatalog);
}
boolean isSelect = (firstNonWsChar == 'S');
//
// Only apply max_rows to selects
//
if (connection.useMaxRows()) {
int rowLimit = -1;
if (isSelect) {
if (sql.toUpperCase().indexOf("LIMIT") != -1) {
rowLimit = this.maxRows;
} else {
if (maxRows <= 0) {
connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT",
-1, this.currentCatalog);
} else {
connection.execSQL("SET OPTION SQL_SELECT_LIMIT="
+ maxRows, -1, this.currentCatalog);
}
}
} else {
connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT",
-1, this.currentCatalog);
}
// Finally, execute the query
rs = connection.execSQL(sql, rowLimit, resultSetConcurrency,
createStreamingResultSet(), isSelect,
this.currentCatalog);
} else {
rs = connection.execSQL(sql, -1, resultSetConcurrency,
createStreamingResultSet(), isSelect,
this.currentCatalog);
}
if (oldCatalog != null) {
connection.setCatalog(oldCatalog);
}
}
lastInsertId = rs.getUpdateID();
if (rs != null) {
this.results = rs;
}
rs.setFirstCharOfQuery(firstNonWsChar);
rs.setConnection(connection);
rs.setResultSetType(resultSetType);
rs.setResultSetConcurrency(resultSetConcurrency);
return ((rs != null) && rs.reallyResult());
}
/**
* @see Statement#execute(String, int)
*/
public boolean execute(String sql, int returnGeneratedKeys)
throws SQLException {
if (returnGeneratedKeys == Statement.RETURN_GENERATED_KEYS) {
checkClosed();
synchronized (this.connection.getMutex()) {
// If this is a 'REPLACE' query, we need to be able to parse
// the 'info' message returned from the server to determine
// the actual number of keys generated.
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
this.connection.setReadInfoMsgEnabled(true);
try {
return execute(sql);
} finally {
this.connection.setReadInfoMsgEnabled(readInfoMsgState);
}
}
} else {
return execute(sql);
}
}
/**
* @see Statement#execute(String, int[])
*/
public boolean execute(String sql, int[] generatedKeyIndices)
throws SQLException {
if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) {
checkClosed();
synchronized (this.connection.getMutex()) {
// If this is a 'REPLACE' query, we need to be able to parse
// the 'info' message returned from the server to determine
// the actual number of keys generated.
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
this.connection.setReadInfoMsgEnabled(true);
try {
return execute(sql);
} finally {
this.connection.setReadInfoMsgEnabled(readInfoMsgState);
}
}
} else {
return execute(sql);
}
}
/**
* @see Statement#execute(String, String[])
*/
public boolean execute(String sql, String[] generatedKeyNames)
throws SQLException {
if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) {
checkClosed();
synchronized (this.connection.getMutex()) {
// If this is a 'REPLACE' query, we need to be able to parse
// the 'info' message returned from the server to determine
// the actual number of keys generated.
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
this.connection.setReadInfoMsgEnabled(true);
try {
return execute(sql);
} finally {
this.connection.setReadInfoMsgEnabled(readInfoMsgState);
}
}
} else {
return execute(sql);
}
}
/**
* JDBC 2.0 Submit a batch of commands to the database for execution. This
* method is optional.
*
* @return an array of update counts containing one element for each
* command in the batch. The array is ordered according to the
* order in which commands were inserted into the batch
*
* @exception SQLException if a database-access error occurs, or the driver
* does not support batch statements
*/
public synchronized int[] executeBatch() throws SQLException {
if (connection.isReadOnly()) {
throw new SQLException("Connection is read-only. "
+ "Queries leading to data modification are not allowed",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
try {
clearWarnings();
int[] updateCounts = null;
if (batchedArgs != null) {
int nbrCommands = batchedArgs.size();
updateCounts = new int[nbrCommands];
for (int i = 0; i < nbrCommands; i++) {
updateCounts[i] = -3;
}
SQLException sqlEx = null;
int commandIndex = 0;
for (commandIndex = 0; commandIndex < nbrCommands;
commandIndex++) {
try {
updateCounts[commandIndex] = executeUpdate((String) batchedArgs
.get(commandIndex), false);
} catch (SQLException ex) {
updateCounts[commandIndex] = EXECUTE_FAILED;
if (this.connection.continueBatchOnError()) {
sqlEx = ex;
} else {
int[] newUpdateCounts = new int[commandIndex];
System.arraycopy(updateCounts, 0, newUpdateCounts,
0, commandIndex);
throw new java.sql.BatchUpdateException(ex
.getMessage(), ex.getSQLState(),
ex.getErrorCode(), newUpdateCounts);
}
}
}
if (sqlEx != null) {
throw new java.sql.BatchUpdateException(sqlEx.getMessage(),
sqlEx.getSQLState(), sqlEx.getErrorCode(), updateCounts);
}
}
return (updateCounts != null) ? updateCounts : new int[0];
} finally {
clearBatch();
}
}
/**
* Execute a SQL statement that retruns a single ResultSet
*
* @param sql typically a static SQL SELECT statement
*
* @return a ResulSet that contains the data produced by the query
*
* @exception SQLException if a database access error occurs
*/
public synchronized java.sql.ResultSet executeQuery(String sql)
throws SQLException {
if (Driver.TRACE) {
Object[] args = { sql };
Debug.methodCall(this, "executeQuery", args);
}
checkClosed();
if (this.doEscapeProcessing) {
sql = EscapeProcessor.escapeSQL(sql, this.serverSupportsConvertFn);
}
char firstStatementChar = StringUtils.firstNonWsCharUc(sql);
if ((firstStatementChar == 'I') || (firstStatementChar == 'U')
|| (firstStatementChar == 'D') || (firstStatementChar == 'A')
|| (firstStatementChar == 'C')) {
if (StringUtils.startsWithIgnoreCaseAndWs(sql, "INSERT")
|| StringUtils.startsWithIgnoreCaseAndWs(sql, "UPDATE")
|| StringUtils.startsWithIgnoreCaseAndWs(sql, "DELETE")
|| StringUtils.startsWithIgnoreCaseAndWs(sql, "DROP")
|| StringUtils.startsWithIgnoreCaseAndWs(sql, "CREATE")
|| StringUtils.startsWithIgnoreCaseAndWs(sql, "ALTER")) {
throw new SQLException("Can not issue data manipulation statements with executeQuery()",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
}
if (results != null) {
results.close();
}
// If there isn't a limit clause in the SQL
// then limit the number of rows to return in
// an efficient manner. Only do this if
// setMaxRows() hasn't been used on any Statements
// generated from the current Connection (saves
// a query, and network traffic).
synchronized (connection.getMutex()) {
clearWarnings();
String oldCatalog = null;
if (!connection.getCatalog().equals(currentCatalog)) {
oldCatalog = connection.getCatalog();
connection.setCatalog(currentCatalog);
}
if (connection.useMaxRows()) {
// We need to execute this all together
// So synchronize on the Connection's mutex (because
// even queries going through there synchronize
// on the connection
if (sql.toUpperCase().indexOf("LIMIT") != -1) {
results = connection.execSQL(sql, maxRows,
resultSetConcurrency, createStreamingResultSet(),
true, this.currentCatalog);
} else {
if (maxRows <= 0) {
connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT",
-1, this.currentCatalog);
} else {
connection.execSQL("SET OPTION SQL_SELECT_LIMIT="
+ maxRows, -1, this.currentCatalog);
}
results = connection.execSQL(sql, -1, resultSetConcurrency,
createStreamingResultSet(), true,
this.currentCatalog);
if (oldCatalog != null) {
connection.setCatalog(oldCatalog);
}
}
} else {
results = connection.execSQL(sql, -1, resultSetConcurrency,
createStreamingResultSet(), true, this.currentCatalog);
}
if (oldCatalog != null) {
connection.setCatalog(oldCatalog);
}
}
lastInsertId = results.getUpdateID();
nextResults = results;
results.setConnection(connection);
results.setResultSetType(resultSetType);
results.setResultSetConcurrency(resultSetConcurrency);
results.setStatement(this);
if (!results.reallyResult()) {
if (!connection.getAutoCommit()) {
connection.rollback();
}
throw new SQLException("Can not issue INSERT/UPDATE/DELETE with executeQuery()",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
return (java.sql.ResultSet) results;
}
/**
* Execute a SQL INSERT, UPDATE or DELETE statement. In addition SQL
* statements that return nothing such as SQL DDL statements can be
* executed Any IDs generated for AUTO_INCREMENT fields can be retrieved
* by casting this Statement to org.gjt.mm.mysql.Statement and calling the
* getLastInsertID() method.
*
* @param sql a SQL statement
*
* @return either a row count, or 0 for SQL commands
*
* @exception SQLException if a database access error occurs
*/
public synchronized int executeUpdate(String sql) throws SQLException {
return executeUpdate(sql, true);
}
private synchronized int executeUpdate(String sql, boolean clearWarnings) throws SQLException {
if (Driver.TRACE) {
Object[] args = { sql };
Debug.methodCall(this, "executeUpdate", args);
}
if (connection.isReadOnly()) {
throw new SQLException("Connection is read-only. "
+ "Queries leading to data modification are not allowed",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
char firstStatementChar = StringUtils.firstNonWsCharUc(sql);
if ((firstStatementChar == 'S')
&& StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) {
throw new SQLException("Can not issue SELECT via executeUpdate()",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}
checkClosed();
if (this.doEscapeProcessing) {
sql = EscapeProcessor.escapeSQL(sql, this.serverSupportsConvertFn);
}
// The checking and changing of catalogs
// must happen in sequence, so synchronize
// on the same mutex that _conn is using
ResultSet rs = null;
synchronized (connection.getMutex()) {
if (clearWarnings) {
clearWarnings();
}
String oldCatalog = null;
if (!connection.getCatalog().equals(currentCatalog)) {
oldCatalog = connection.getCatalog();
connection.setCatalog(currentCatalog);
}
//
// Only apply max_rows to selects
//
if (connection.useMaxRows()) {
connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1,
this.currentCatalog);
}
rs = connection.execSQL(sql, -1,
java.sql.ResultSet.CONCUR_READ_ONLY, false, false,
this.currentCatalog);
rs.setConnection(connection);
if (oldCatalog != null) {
connection.setCatalog(oldCatalog);
}
}
this.results = rs;
rs.setFirstCharOfQuery(firstStatementChar);
updateCount = rs.getUpdateCount();
int truncatedUpdateCount = 0;
if (updateCount > Integer.MAX_VALUE) {
truncatedUpdateCount = Integer.MAX_VALUE;
} else {
truncatedUpdateCount = (int) updateCount;
}
lastInsertId = rs.getUpdateID();
return truncatedUpdateCount;
}
/**
* @see Statement#executeUpdate(String, int)
*/
public int executeUpdate(String sql, int returnGeneratedKeys)
throws SQLException {
if (returnGeneratedKeys == Statement.RETURN_GENERATED_KEYS) {
checkClosed();
synchronized (this.connection.getMutex()) {
// If this is a 'REPLACE' query, we need to be able to parse
// the 'info' message returned from the server to determine
// the actual number of keys generated.
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
this.connection.setReadInfoMsgEnabled(true);
try {
return executeUpdate(sql);
} finally {
this.connection.setReadInfoMsgEnabled(readInfoMsgState);
}
}
} else {
return executeUpdate(sql);
}
}
/**
* @see Statement#executeUpdate(String, int[])
*/
public int executeUpdate(String sql, int[] generatedKeyIndices)
throws SQLException {
if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) {
checkClosed();
synchronized (this.connection.getMutex()) {
// If this is a 'REPLACE' query, we need to be able to parse
// the 'info' message returned from the server to determine
// the actual number of keys generated.
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
this.connection.setReadInfoMsgEnabled(true);
try {
return executeUpdate(sql);
} finally {
this.connection.setReadInfoMsgEnabled(readInfoMsgState);
}
}
} else {
return executeUpdate(sql);
}
}
/**
* @see Statement#executeUpdate(String, String[])
*/
public int executeUpdate(String sql, String[] generatedKeyNames)
throws SQLException {
if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) {
checkClosed();
synchronized (this.connection.getMutex()) {
// If this is a 'REPLACE' query, we need to be able to parse
// the 'info' message returned from the server to determine
// the actual number of keys generated.
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
this.connection.setReadInfoMsgEnabled(true);
try {
return executeUpdate(sql);
} finally {
this.connection.setReadInfoMsgEnabled(readInfoMsgState);
}
}
} else {
return executeUpdate(sql);
}
}
/**
* Checks if closed() has been called, and throws an exception if so
*
* @throws SQLException if this statement has been closed
*/
protected void checkClosed() throws SQLException {
if (this.isClosed) {
throw new SQLException("No operations allowed after statement closed",
"08003");
}
}
/**
* Close any open result sets that have been 'held open'
*/
protected void closeAllOpenResults() {
if (this.openResults != null) {
for (Iterator iter = this.openResults.iterator(); iter.hasNext();) {
ResultSet element = (ResultSet) iter.next();
try {
element.close();
} catch (SQLException sqlEx) {
AssertionFailedException.shouldNotHappen(sqlEx);
}
}
this.openResults.clear();
}
}
/**
* We only stream result sets when they are forward-only, read-only, and
* the fetch size has been set to Integer.MIN_VALUE
*
* @return true if this result set should be streamed row at-a-time, rather
* than read all at once.
*/
protected boolean createStreamingResultSet() {
return ((resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY)
&& (resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
&& (fetchSize == Integer.MIN_VALUE));
}
/**
* Sets the concurrency for result sets generated by this statement
*
* @param concurrencyFlag DOCUMENT ME!
*/
void setResultSetConcurrency(int concurrencyFlag) {
resultSetConcurrency = concurrencyFlag;
}
/**
* Sets the result set type for result sets generated by this statement
*
* @param typeFlag DOCUMENT ME!
*/
void setResultSetType(int typeFlag) {
resultSetType = typeFlag;
}
protected void addWarning(SQLWarning warning) {
if (this.pendingWarnings == null) {
this.pendingWarnings = warning;
} else {
// find the 'end'... this could be handled more
// efficiently, but the only thing that currently
// sets 'client-side' warnings is prepared statements
// when clipping +/- INF and NaN.
SQLWarning lastWarning = this.pendingWarnings;
while (lastWarning.getNextWarning() != null) {
lastWarning = lastWarning.getNextWarning();
}
lastWarning.setNextWarning(warning);
}
}
/**
* Parses actual record count from 'info' message
*
* @param serverInfo DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private int getRecordCountFromInfo(String serverInfo) {
StringBuffer recordsBuf = new StringBuffer();
int recordsCount = 0;
int duplicatesCount = 0;
char c = (char) 0;
int length = serverInfo.length();
int i = 0;
for (; i < length; i++) {
c = serverInfo.charAt(i);
if (Character.isDigit(c)) {
break;
}
}
recordsBuf.append(c);
i++;
for (; i < length; i++) {
c = serverInfo.charAt(i);
if (!Character.isDigit(c)) {
break;
}
recordsBuf.append(c);
}
recordsCount = Integer.parseInt(recordsBuf.toString());
StringBuffer duplicatesBuf = new StringBuffer();
for (; i < length; i++) {
c = serverInfo.charAt(i);
if (Character.isDigit(c)) {
break;
}
}
duplicatesBuf.append(c);
i++;
for (; i < length; i++) {
c = serverInfo.charAt(i);
if (!Character.isDigit(c)) {
break;
}
duplicatesBuf.append(c);
}
duplicatesCount = Integer.parseInt(duplicatesBuf.toString());
return recordsCount - duplicatesCount;
}
}