/* 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; } }