0001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
0002: // Copyright (C) 2004 The jTDS Project
0003: //
0004: // This library is free software; you can redistribute it and/or
0005: // modify it under the terms of the GNU Lesser General Public
0006: // License as published by the Free Software Foundation; either
0007: // version 2.1 of the License, or (at your option) any later version.
0008: //
0009: // This library is distributed in the hope that it will be useful,
0010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: // Lesser General Public License for more details.
0013: //
0014: // You should have received a copy of the GNU Lesser General Public
0015: // License along with this library; if not, write to the Free Software
0016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: //
0018: package net.sourceforge.jtds.jdbc;
0019:
0020: import java.sql.BatchUpdateException;
0021: import java.sql.Connection;
0022: import java.sql.ResultSet;
0023: import java.sql.SQLException;
0024: import java.sql.SQLWarning;
0025: import java.sql.Types;
0026: import java.util.ArrayList;
0027: import java.util.LinkedList;
0028:
0029: /**
0030: * jTDS implementation of the java.sql.Statement interface.<p>
0031: * NB. As allowed by the JDBC standard and like most other drivers,
0032: * this implementation only allows one open result set at a time.
0033: * <p>
0034: * Implementation notes:
0035: * <p>
0036: * I experimented with allowing multiple open result sets as supported
0037: * by the origianal jTDS but rejected this approach for the following
0038: * reasons:
0039: * <ol>
0040: * <li>It is more difficult to ensure that there are no memory leaks and that
0041: * cursors are closed if multiple open sets are allowed.
0042: * <li>The use of one result set allows cursor and non cursor result sets to
0043: * be derived from exeuteQuery() or execute() and getResultSet() in the
0044: * same way that other drivers do.
0045: * </ol>
0046: * In the event of an IO failure the setClosed() method forces this statement
0047: * and associated result set to close preventing the propogation of errors.
0048: * This class includes a finalize method which increases the chances of the
0049: * statement being closed tidly in a pooled environment where the user has
0050: * forgotten to explicitly close the statement before it goes out of scope.
0051: *
0052: * @see java.sql.Statement
0053: * @see java.sql.Connection#createStatement()
0054: * @see java.sql.ResultSet
0055: *
0056: * @author Mike Hutchinson
0057: * @version $Id: JtdsStatement.java,v 1.64 2007/07/12 21:03:23 bheineman Exp $
0058: */
0059: public class JtdsStatement implements java.sql.Statement {
0060: /*
0061: * Constants used for backwards compatibility with JDK 1.3
0062: */
0063: static final int RETURN_GENERATED_KEYS = 1;
0064: static final int NO_GENERATED_KEYS = 2;
0065: static final int CLOSE_CURRENT_RESULT = 1;
0066: static final int KEEP_CURRENT_RESULT = 2;
0067: static final int CLOSE_ALL_RESULTS = 3;
0068: static final int BOOLEAN = 16;
0069: static final int DATALINK = 70;
0070: static final Integer SUCCESS_NO_INFO = new Integer(-2);
0071: static final Integer EXECUTE_FAILED = new Integer(-3);
0072: static final int DEFAULT_FETCH_SIZE = 100;
0073:
0074: /** The connection owning this statement object. */
0075: protected ConnectionJDBC2 connection;
0076: /** The TDS object used for server access. */
0077: protected TdsCore tds;
0078: /** The read query timeout in seconds */
0079: protected int queryTimeout;
0080: /** The current <code>ResultSet</code>. */
0081: protected JtdsResultSet currentResult;
0082: /** The current update count. */
0083: private int updateCount = -1;
0084: /** The fetch direction for result sets. */
0085: protected int fetchDirection = ResultSet.FETCH_FORWARD;
0086: /** The type of result sets created by this statement. */
0087: protected int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
0088: /** The concurrency of result sets created by this statement. */
0089: protected int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
0090: /** The fetch size (default 100, only used by cursor
0091: * <code>ResultSet</code>s).
0092: */
0093: protected int fetchSize = DEFAULT_FETCH_SIZE;
0094: /** The cursor name to be used for positioned updates. */
0095: protected String cursorName;
0096: /** True if this statement is closed. */
0097: protected boolean closed;
0098: /** The maximum field size (not used at present). */
0099: protected int maxFieldSize;
0100: /** The maximum number of rows to return (not used at present). */
0101: protected int maxRows;
0102: /** True if SQL statements should be preprocessed. */
0103: protected boolean escapeProcessing = true;
0104: /** SQL Diagnostic exceptions and warnings. */
0105: protected final SQLDiagnostic messages;
0106: /** Batched SQL Statement array. */
0107: protected ArrayList batchValues;
0108: /** Dummy result set for getGeneratedKeys. */
0109: protected JtdsResultSet genKeyResultSet;
0110: /**
0111: * List of queued results (update counts, possibly followed by a
0112: * <code>ResultSet</code>).
0113: */
0114: protected final LinkedList resultQueue = new LinkedList();
0115: /** List of open result sets. */
0116: protected ArrayList openResultSets;
0117: /** The cached column meta data. */
0118: protected ColInfo[] colMetaData;
0119:
0120: /**
0121: * Construct a new Statement object.
0122: *
0123: * @param connection The parent connection.
0124: * @param resultSetType The result set type for example TYPE_FORWARD_ONLY.
0125: * @param resultSetConcurrency The concurrency for example CONCUR_READ_ONLY.
0126: */
0127: JtdsStatement(ConnectionJDBC2 connection, int resultSetType,
0128: int resultSetConcurrency) throws SQLException {
0129: //
0130: // This is a good point to do common validation of the result set type
0131: //
0132: if (resultSetType < ResultSet.TYPE_FORWARD_ONLY
0133: || resultSetType > ResultSet.TYPE_SCROLL_SENSITIVE + 1) {
0134: String method;
0135: if (this instanceof JtdsCallableStatement) {
0136: method = "prepareCall";
0137: } else if (this instanceof JtdsPreparedStatement) {
0138: method = "prepareStatement";
0139: } else {
0140: method = "createStatement";
0141: }
0142: throw new SQLException(Messages.get(
0143: "error.generic.badparam", "resultSetType", method),
0144: "HY092");
0145: }
0146: //
0147: // Ditto for the result set concurrency
0148: //
0149: if (resultSetConcurrency < ResultSet.CONCUR_READ_ONLY
0150: || resultSetConcurrency > ResultSet.CONCUR_UPDATABLE + 2) {
0151: String method;
0152: if (this instanceof JtdsCallableStatement) {
0153: method = "prepareCall";
0154: } else if (this instanceof JtdsPreparedStatement) {
0155: method = "prepareStatement";
0156: } else {
0157: method = "createStatement";
0158: }
0159: throw new SQLException(Messages.get(
0160: "error.generic.badparam", "resultSetConcurrency",
0161: method), "HY092");
0162: }
0163:
0164: this .connection = connection;
0165: this .resultSetType = resultSetType;
0166: this .resultSetConcurrency = resultSetConcurrency;
0167:
0168: this .tds = connection.getCachedTds();
0169: if (this .tds == null) {
0170: this .messages = new SQLDiagnostic(connection
0171: .getServerType());
0172: this .tds = new TdsCore(this .connection, messages);
0173: } else {
0174: this .messages = tds.getMessages();
0175: }
0176: }
0177:
0178: /**
0179: * Called when this object goes out of scope to close any
0180: * <code>ResultSet</code> object and this statement.
0181: */
0182: protected void finalize() throws Throwable {
0183: super .finalize();
0184: try {
0185: close();
0186: } catch (SQLException e) {
0187: // Ignore errors
0188: }
0189: }
0190:
0191: /**
0192: * Get the Statement's TDS object.
0193: *
0194: * @return The TDS support as a <code>TdsCore</core> Object.
0195: */
0196: TdsCore getTds() {
0197: return tds;
0198: }
0199:
0200: /**
0201: * Get the statement's warnings list.
0202: *
0203: * @return The warnings list as a <code>SQLDiagnostic</code>.
0204: */
0205: SQLDiagnostic getMessages() {
0206: return messages;
0207: }
0208:
0209: /**
0210: * Check that this statement is still open.
0211: *
0212: * @throws SQLException if statement closed.
0213: */
0214: protected void checkOpen() throws SQLException {
0215: if (closed || connection == null || connection.isClosed()) {
0216: throw new SQLException(Messages.get("error.generic.closed",
0217: "Statement"), "HY010");
0218: }
0219: }
0220:
0221: /**
0222: * Check that the exception is caused by the failure to open a
0223: * cursor and not by a more serious SQL error.
0224: *
0225: * @param e the exception returned by the cursor class
0226: * @throws SQLException if exception is not due to a cursor error
0227: */
0228: protected void checkCursorException(SQLException e)
0229: throws SQLException {
0230: if (connection == null || connection.isClosed()
0231: || "HYT00".equals(e.getSQLState())
0232: || "HY008".equals(e.getSQLState())) {
0233: // Serious error or timeout so return exception to caller
0234: throw e;
0235: }
0236: if (connection.getServerType() == Driver.SYBASE) {
0237: // Allow retry for Sybase
0238: return;
0239: }
0240: //
0241: // Check cursor specific errors and ranges for SQL Server
0242: //
0243: int error = e.getErrorCode();
0244: if (error >= 16900 && error <= 16999) {
0245: // Errors in this range are all related to the cursor API.
0246: // This is true for all versions of SQL Server.
0247: return;
0248: }
0249: if (error == 6819) {
0250: // A FOR XML clause was found
0251: return;
0252: }
0253: if (error == 8654) {
0254: // A inrow textptr exists
0255: return;
0256: }
0257: if (error == 8162) {
0258: // Formal parameter '%.*ls' was defined as OUTPUT but the actual
0259: // parameter not declared OUTPUT. This happens when trying to
0260: // execute a stored procedure with output parameters via a cursor.
0261: return;
0262: }
0263: //
0264: // More serious error we should rethrow the error and
0265: // not allow the driver to re-execute sql.
0266: //
0267: throw e;
0268: }
0269:
0270: /**
0271: * Report that user tried to call a method which has not been implemented.
0272: *
0273: * @param method The method name to report in the error message.
0274: * @throws SQLException
0275: */
0276: static void notImplemented(String method) throws SQLException {
0277: throw new SQLException(Messages.get("error.generic.notimp",
0278: method), "HYC00");
0279: }
0280:
0281: /**
0282: * Close current result set (if any).
0283: */
0284: void closeCurrentResultSet() throws SQLException {
0285: try {
0286: if (currentResult != null) {
0287: currentResult.close();
0288: }
0289: // } catch (SQLException e) {
0290: // Ignore
0291: } finally {
0292: currentResult = null;
0293: }
0294: }
0295:
0296: /**
0297: * Close all result sets.
0298: */
0299: void closeAllResultSets() throws SQLException {
0300: try {
0301: if (openResultSets != null) {
0302: for (int i = 0; i < openResultSets.size(); i++) {
0303: JtdsResultSet rs = (JtdsResultSet) openResultSets
0304: .get(i);
0305: if (rs != null) {
0306: rs.close();
0307: }
0308: }
0309: }
0310: closeCurrentResultSet();
0311: } finally {
0312: openResultSets = null;
0313: }
0314: }
0315:
0316: /**
0317: * Add an SQLWarning object to the statment warnings list.
0318: *
0319: * @param w The SQLWarning to add.
0320: */
0321: void addWarning(SQLWarning w) {
0322: messages.addWarning(w);
0323: }
0324:
0325: /**
0326: * Execute the SQL batch on a MS server.
0327: *
0328: * @param size the total size of the batch
0329: * @param executeSize the maximum number of statements to send in one request
0330: * @param counts the returned update counts
0331: * @return chained exceptions linked to a <code>SQLException</code>
0332: * @throws SQLException if a serious error occurs during execution
0333: */
0334: protected SQLException executeMSBatch(int size, int executeSize,
0335: ArrayList counts) throws SQLException {
0336: SQLException sqlEx = null;
0337: for (int i = 0; i < size;) {
0338: Object value = batchValues.get(i);
0339: ++i;
0340: // Execute batch now if max size reached or end of batch
0341: boolean executeNow = (i % executeSize == 0) || i == size;
0342:
0343: tds.startBatch();
0344: tds.executeSQL((String) value, null, null, false, 0, -1,
0345: -1, executeNow);
0346:
0347: // If the batch has been sent, process the results
0348: if (executeNow) {
0349: sqlEx = tds.getBatchCounts(counts, sqlEx);
0350:
0351: // If a serious error then we stop execution now as count
0352: // is too small.
0353: if (sqlEx != null && counts.size() != i) {
0354: break;
0355: }
0356: }
0357: }
0358: return sqlEx;
0359: }
0360:
0361: /**
0362: * Execute the SQL batch on a Sybase server.
0363: * <p/>
0364: * Sybase needs to have the SQL concatenated into one TDS language packet. This method will be overriden for
0365: * <code>PreparedStatements</code>.
0366: *
0367: * @param size the total size of the batch
0368: * @param executeSize the maximum number of statements to send in one request
0369: * @param counts the returned update counts
0370: * @return chained exceptions linked to a <code>SQLException</code>
0371: * @throws SQLException if a serious error occurs during execution
0372: */
0373: protected SQLException executeSybaseBatch(int size,
0374: int executeSize, ArrayList counts) throws SQLException {
0375: StringBuffer sql = new StringBuffer(size * 32); // Make buffer reasonable size
0376: SQLException sqlEx = null;
0377:
0378: for (int i = 0; i < size;) {
0379: Object value = batchValues.get(i);
0380: ++i;
0381: // Execute batch now if max size reached or end of batch
0382: boolean executeNow = (i % executeSize == 0) || i == size;
0383:
0384: sql.append((String) value).append(' ');
0385:
0386: if (executeNow) {
0387: tds.executeSQL(sql.toString(), null, null, false, 0,
0388: -1, -1, true);
0389: sql.setLength(0);
0390: // If the batch has been sent, process the results
0391: sqlEx = tds.getBatchCounts(counts, sqlEx);
0392:
0393: // If a serious error or a server error then we stop
0394: // execution now as count is too small.
0395: if (sqlEx != null && counts.size() != i) {
0396: break;
0397: }
0398: }
0399: }
0400: return sqlEx;
0401: }
0402:
0403: /**
0404: * Executes SQL to obtain a result set.
0405: *
0406: * @param sql the SQL statement to execute
0407: * @param spName optional stored procedure name
0408: * @param params optional parameters
0409: * @param useCursor whether a cursor should be created for the SQL
0410: * @return the result set generated by the query
0411: */
0412: protected ResultSet executeSQLQuery(String sql, String spName,
0413: ParamInfo[] params, boolean useCursor) throws SQLException {
0414: String warningMessage = null;
0415:
0416: //
0417: // Try to open a cursor result set if required
0418: //
0419: if (useCursor) {
0420: try {
0421: if (connection.getServerType() == Driver.SQLSERVER) {
0422: currentResult = new MSCursorResultSet(this , sql,
0423: spName, params, resultSetType,
0424: resultSetConcurrency);
0425:
0426: return currentResult;
0427: } else {
0428: // Use client side cursor for Sybase
0429: currentResult = new CachedResultSet(this , sql,
0430: spName, params, resultSetType,
0431: resultSetConcurrency);
0432:
0433: return currentResult;
0434: }
0435: } catch (SQLException e) {
0436: checkCursorException(e);
0437: warningMessage = '[' + e.getSQLState() + "] "
0438: + e.getMessage();
0439: }
0440: }
0441:
0442: //
0443: // Could not open a cursor (or was not requested) so try a direct select
0444: //
0445: if (spName != null && connection.getUseMetadataCache()
0446: && connection.getPrepareSql() == TdsCore.PREPARE
0447: && colMetaData != null
0448: && connection.getServerType() == Driver.SQLSERVER) {
0449: // There is cached meta data available for this
0450: // prepared statement
0451: tds.setColumns(colMetaData);
0452: tds.executeSQL(sql, spName, params, true, queryTimeout,
0453: maxRows, maxFieldSize, true);
0454: } else {
0455: tds.executeSQL(sql, spName, params, false, queryTimeout,
0456: maxRows, maxFieldSize, true);
0457: }
0458:
0459: // Update warning chain if cursor was downgraded before processing results
0460: if (warningMessage != null) {
0461: addWarning(new SQLWarning(Messages.get(
0462: "warning.cursordowngraded", warningMessage),
0463: "01000"));
0464: }
0465:
0466: // Ignore update counts preceding the result set. All drivers seem to
0467: // do this.
0468: while (!tds.getMoreResults() && !tds.isEndOfResponse())
0469: ;
0470:
0471: // check for server side errors
0472: messages.checkErrors();
0473:
0474: if (tds.isResultSet()) {
0475: currentResult = new JtdsResultSet(this ,
0476: ResultSet.TYPE_FORWARD_ONLY,
0477: ResultSet.CONCUR_READ_ONLY, tds.getColumns());
0478: } else {
0479: throw new SQLException(Messages
0480: .get("error.statement.noresult"), "24000");
0481: }
0482:
0483: return currentResult;
0484: }
0485:
0486: /**
0487: * Executes any type of SQL.
0488: *
0489: * @param sql the SQL statement to execute
0490: * @param spName optional stored procedure name
0491: * @param params optional parameters
0492: * @param returnKeys whether the statement returns generated keys
0493: * @param update whether the caller is {@link #executeUpdate}
0494: * @param useCursor whether the requested result set type or concurrency
0495: * or connection properties request usage of a cursor
0496: * @return <code>true</code> if the first result is a result set
0497: * @throws SQLException if an error condition occurs
0498: */
0499: protected boolean executeSQL(String sql, String spName,
0500: ParamInfo[] params, boolean returnKeys, boolean update,
0501: boolean useCursor) throws SQLException {
0502: String warningMessage = null;
0503:
0504: //
0505: // For SQL Server, try to open a cursor result set if required
0506: // (and possible).
0507: //
0508: if (connection.getServerType() == Driver.SQLSERVER && !update
0509: && useCursor) {
0510: try {
0511: currentResult = new MSCursorResultSet(this , sql,
0512: spName, params, resultSetType,
0513: resultSetConcurrency);
0514:
0515: return true;
0516: } catch (SQLException e) {
0517: checkCursorException(e);
0518: warningMessage = '[' + e.getSQLState() + "] "
0519: + e.getMessage();
0520: }
0521: }
0522:
0523: //
0524: // We are talking to a Sybase server or we could not open a cursor
0525: // or we did not have a SELECT so just execute the SQL normally.
0526: //
0527: tds.executeSQL(sql, spName, params, false, queryTimeout,
0528: maxRows, maxFieldSize, true);
0529:
0530: if (warningMessage != null) {
0531: // Update warning chain if cursor was downgraded
0532: addWarning(new SQLWarning(Messages.get(
0533: "warning.cursordowngraded", warningMessage),
0534: "01000"));
0535: }
0536:
0537: if (processResults(returnKeys, update)) {
0538: Object nextResult = resultQueue.removeFirst();
0539:
0540: // Next result is an update count
0541: if (nextResult instanceof Integer) {
0542: updateCount = ((Integer) nextResult).intValue();
0543: return false;
0544: }
0545:
0546: // Next result is a ResultSet. Set currentResult and remove it.
0547: currentResult = (JtdsResultSet) nextResult;
0548: return true;
0549: } else {
0550: return false;
0551: }
0552: }
0553:
0554: /**
0555: * Queue up update counts into {@link #resultQueue} until the end of the
0556: * response is reached or a <code>ResultSet</code> is encountered. Calling
0557: * <code>processResults</code> while a <code>ResultSet</code> is open will
0558: * not close it, but will consume all remaining rows.
0559: *
0560: * @param returnKeys <code>true</code> if a generated keys
0561: * <code>ResultSet</code> is expected
0562: * @param update <code>true</code> if the method is called from within
0563: * <code>executeUpdate</code>
0564: * @return <code>true</code> if there are any results,
0565: * <code>false</code> otherwise
0566: * @throws SQLException if an error condition occurs
0567: */
0568: private boolean processResults(boolean returnKeys, boolean update)
0569: throws SQLException {
0570: if (!resultQueue.isEmpty()) {
0571: throw new IllegalStateException(
0572: "There should be no queued results.");
0573: }
0574:
0575: while (!tds.isEndOfResponse()) {
0576: if (!tds.getMoreResults()) {
0577: if (tds.isUpdateCount()) {
0578: if (update && connection.getLastUpdateCount()) {
0579: resultQueue.clear();
0580: }
0581: resultQueue.addLast(new Integer(tds
0582: .getUpdateCount()));
0583: }
0584: } else {
0585: if (returnKeys) {
0586: // This had better be the generated key
0587: // FIXME We could use SELECT @@IDENTITY AS jTDS_SOMETHING and check the column name to make sure
0588: if (tds.getNextRow()) {
0589: genKeyResultSet = new CachedResultSet(this , tds
0590: .getColumns(), tds.getRowData());
0591: }
0592: } else {
0593: if (update && resultQueue.isEmpty()) {
0594: // Throw exception but queue up any previous ones
0595: SQLException ex = new SQLException(Messages
0596: .get("error.statement.nocount"),
0597: "07000");
0598: ex.setNextException(messages.exceptions);
0599: throw ex;
0600: }
0601:
0602: resultQueue.add(new JtdsResultSet(this ,
0603: ResultSet.TYPE_FORWARD_ONLY,
0604: ResultSet.CONCUR_READ_ONLY, tds
0605: .getColumns()));
0606: break;
0607: }
0608: }
0609: }
0610:
0611: // Check for server side errors
0612: getMessages().checkErrors();
0613:
0614: return !resultQueue.isEmpty();
0615: }
0616:
0617: /**
0618: * Cache as many results as possible (up to the first
0619: * <code>ResultSet</code>). Called by <code>ResultSet</code>s when the
0620: * end is reached.
0621: */
0622: protected void cacheResults() throws SQLException {
0623: // Cache results
0624: processResults(false, false);
0625: }
0626:
0627: /**
0628: * Initialize the <code>Statement</code>, by cleaning up all queued and
0629: * unprocessed results. Called by all execute methods.
0630: *
0631: * @throws SQLException if an error occurs
0632: */
0633: protected void initialize() throws SQLException {
0634: updateCount = -1;
0635: resultQueue.clear();
0636: genKeyResultSet = null;
0637: tds.clearResponseQueue();
0638: // FIXME Should old exceptions found now be thrown instead of lost?
0639: messages.exceptions = null;
0640: messages.clearWarnings();
0641: closeAllResultSets();
0642: }
0643:
0644: /**
0645: * Implements the common functionality for plain statement {@link #execute}
0646: * and {#link #executeUpdate}: basic checks, cleaning up of previous
0647: * results, setting up and executing the query and loading the first
0648: * results.
0649: *
0650: * @param sql an SQL <code>INSERT</code>, <code>UPDATE</code> or
0651: * <code>DELETE</code> statement or an SQL statement that
0652: * returns nothing, such as an SQL DDL statement
0653: * @param autoGeneratedKeys a flag indicating whether auto-generated keys
0654: * should be made available for retrieval
0655: * @param update boolean flag indicating whether the caller is
0656: * {@link #executeUpdate} -- in this case an exception is
0657: * thrown if the first result is not an update count and no
0658: * cursor is created (direct execution)
0659: * @return <code>true</code> if the first result is a
0660: * <code>ResultSet</code>, <code>false</code> if it's an update
0661: * count
0662: * @see #execute
0663: * @see #executeUpdate
0664: */
0665: private boolean executeImpl(String sql, int autoGeneratedKeys,
0666: boolean update) throws SQLException {
0667: initialize();
0668:
0669: if (sql == null || sql.length() == 0) {
0670: throw new SQLException(Messages.get("error.generic.nosql"),
0671: "HY000");
0672: }
0673:
0674: boolean returnKeys;
0675: String sqlWord = "";
0676: if (escapeProcessing) {
0677: String tmp[] = SQLParser
0678: .parse(sql, null, connection, false);
0679:
0680: if (tmp[1].length() != 0) {
0681: throw new SQLException(Messages
0682: .get("error.statement.badsql"), "07000");
0683: }
0684:
0685: sql = tmp[0];
0686: sqlWord = tmp[2];
0687: } else {
0688: // Escape processing turned off so
0689: // see if we can extract "insert" from start of statement
0690: sql = sql.trim();
0691: if (sql.length() > 5) {
0692: sqlWord = sql.substring(0, 6).toLowerCase();
0693: }
0694: }
0695:
0696: if (autoGeneratedKeys == RETURN_GENERATED_KEYS) {
0697: returnKeys = "insert".equals(sqlWord);
0698: } else if (autoGeneratedKeys == NO_GENERATED_KEYS) {
0699: returnKeys = false;
0700: } else {
0701: throw new SQLException(Messages.get(
0702: "error.generic.badoption", Integer
0703: .toString(autoGeneratedKeys),
0704: "autoGeneratedKeys"), "HY092");
0705: }
0706:
0707: if (returnKeys) {
0708: if (connection.getServerType() == Driver.SQLSERVER
0709: && connection.getDatabaseMajorVersion() >= 8) {
0710: sql += " SELECT SCOPE_IDENTITY() AS ID";
0711: } else {
0712: sql += " SELECT @@IDENTITY AS ID";
0713: }
0714: }
0715:
0716: return executeSQL(sql, null, null, returnKeys, update, !update
0717: && useCursor(returnKeys, sqlWord));
0718: }
0719:
0720: /**
0721: * Determines whether a cursor should be used based on the requested result
0722: * set type and concurrency, whether a cursor name has been set, the
0723: * <code>useCursors</code> connection property has been set, the first
0724: * word in the SQL query is either SELECT or EXEC/EXECUTE and no generated
0725: * keys are returned.
0726: *
0727: * @param returnKeys indicates whether keys will be returned by the query
0728: * @param sqlWord the first word in the SQL query; can be
0729: * <code>null</code> if the caller is
0730: * {@link #executeQuery}
0731: * @return <code>true</code> if a cursor should be used, <code>false</code>
0732: * if not
0733: */
0734: protected boolean useCursor(boolean returnKeys, String sqlWord) {
0735: return (resultSetType != ResultSet.TYPE_FORWARD_ONLY
0736: || resultSetConcurrency != ResultSet.CONCUR_READ_ONLY
0737: || connection.getUseCursors() || cursorName != null)
0738: && !returnKeys
0739: && (sqlWord == null || "select".equals(sqlWord) || sqlWord
0740: .startsWith("exec"));
0741: }
0742:
0743: /**
0744: * Retrieve the default fetch size for this statement.
0745: *
0746: * @return the default fetch size for a new <code>ResultSet</code>
0747: */
0748: int getDefaultFetchSize() {
0749: return (0 < this .maxRows && this .maxRows < DEFAULT_FETCH_SIZE) ? this .maxRows
0750: : DEFAULT_FETCH_SIZE;
0751: }
0752:
0753: // ------------------ java.sql.Statement methods ----------------------
0754:
0755: public int getFetchDirection() throws SQLException {
0756: checkOpen();
0757:
0758: return this .fetchDirection;
0759: }
0760:
0761: public int getFetchSize() throws SQLException {
0762: checkOpen();
0763:
0764: return this .fetchSize;
0765: }
0766:
0767: public int getMaxFieldSize() throws SQLException {
0768: checkOpen();
0769:
0770: return this .maxFieldSize;
0771: }
0772:
0773: public int getMaxRows() throws SQLException {
0774: checkOpen();
0775:
0776: return this .maxRows;
0777: }
0778:
0779: public int getQueryTimeout() throws SQLException {
0780: checkOpen();
0781:
0782: return this .queryTimeout;
0783: }
0784:
0785: public int getResultSetConcurrency() throws SQLException {
0786: checkOpen();
0787:
0788: return this .resultSetConcurrency;
0789: }
0790:
0791: public int getResultSetHoldability() throws SQLException {
0792: checkOpen();
0793:
0794: return JtdsResultSet.HOLD_CURSORS_OVER_COMMIT;
0795: }
0796:
0797: public int getResultSetType() throws SQLException {
0798: checkOpen();
0799:
0800: return resultSetType;
0801: }
0802:
0803: public int getUpdateCount() throws SQLException {
0804: checkOpen();
0805:
0806: return updateCount;
0807: }
0808:
0809: public void cancel() throws SQLException {
0810: checkOpen();
0811:
0812: if (tds != null) {
0813: tds.cancel(false);
0814: }
0815: }
0816:
0817: public void clearBatch() throws SQLException {
0818: checkOpen();
0819:
0820: if (batchValues != null) {
0821: batchValues.clear();
0822: }
0823: }
0824:
0825: public void clearWarnings() throws SQLException {
0826: checkOpen();
0827:
0828: messages.clearWarnings();
0829: }
0830:
0831: public void close() throws SQLException {
0832: if (!closed) {
0833: SQLException closeEx = null;
0834: try {
0835: closeAllResultSets();
0836: } catch (SQLException ex) {
0837: if (!"HYT00".equals(ex.getSQLState())
0838: && !"HY008".equals(ex.getSQLState())) {
0839: // Only throw exceptions not caused by cancels or timeouts
0840: closeEx = ex;
0841: }
0842: } finally {
0843: SQLException releaseEx = null;
0844: try {
0845: if (!connection.isClosed()) {
0846: connection.releaseTds(tds);
0847: }
0848: // Check for server side errors
0849: tds.getMessages().checkErrors();
0850: } catch (SQLException ex) {
0851: // Remember any exception thrown
0852: releaseEx = ex;
0853: // Queue up any result set close exceptions
0854: if (closeEx != null) {
0855: releaseEx.setNextException(closeEx);
0856: }
0857: } finally {
0858: // Clean up everything
0859: closed = true;
0860: tds = null;
0861: connection.removeStatement(this );
0862: connection = null;
0863:
0864: // Re-throw any caught exception
0865: if (releaseEx != null) {
0866: throw releaseEx;
0867: }
0868: }
0869: }
0870: // Throw any exception caught during result set close
0871: if (closeEx != null) {
0872: throw closeEx;
0873: }
0874: }
0875: }
0876:
0877: public boolean getMoreResults() throws SQLException {
0878: checkOpen();
0879:
0880: return getMoreResults(CLOSE_ALL_RESULTS);
0881: }
0882:
0883: /**
0884: * Execute batch of SQL Statements.
0885: * <p/>
0886: * The JDBC3 standard says that the behaviour of this method must be
0887: * consistent for any DBMS. As Sybase (and to a lesser extent SQL Server)
0888: * will sometimes continue after a batch execution error, the only way to
0889: * comply with the standard is to always return an array of update counts
0890: * the same size as the batch list. Slots in the array beyond the last
0891: * executed statement are set to <code>EXECUTE_FAILED</code>.
0892: *
0893: * @return update counts as an <code>int[]</code>
0894: */
0895: public int[] executeBatch() throws SQLException,
0896: BatchUpdateException {
0897: checkOpen();
0898: initialize();
0899:
0900: if (batchValues == null || batchValues.size() == 0) {
0901: return new int[0];
0902: }
0903:
0904: int size = batchValues.size();
0905: int executeSize = connection.getBatchSize();
0906: executeSize = (executeSize == 0) ? Integer.MAX_VALUE
0907: : executeSize;
0908: SQLException sqlEx;
0909: ArrayList counts = new ArrayList(size);
0910:
0911: try {
0912: // Lock the connection, making sure the batch executes atomically. This is especially important in the
0913: // case of prepared statement batches (where we don't want the prepares rolled back before being executed)
0914: // but should also provide some level of sanity in the general case.
0915: synchronized (connection) {
0916: if (connection.getServerType() == Driver.SYBASE
0917: && connection.getTdsVersion() == Driver.TDS50) {
0918: sqlEx = executeSybaseBatch(size, executeSize,
0919: counts);
0920: } else {
0921: sqlEx = executeMSBatch(size, executeSize, counts);
0922: }
0923: }
0924:
0925: // Ensure array is the same size as the original statement list
0926: int updateCounts[] = new int[size];
0927: // Copy the update counts into the int array
0928: int results = counts.size();
0929: for (int i = 0; i < results; i++) {
0930: updateCounts[i] = ((Integer) counts.get(i)).intValue();
0931: }
0932: // Pad any remaining slots with EXECUTE_FAILED
0933: for (int i = results; i < updateCounts.length; i++) {
0934: updateCounts[i] = EXECUTE_FAILED.intValue();
0935: }
0936:
0937: // See if we should return an exception
0938: if (sqlEx != null) {
0939: BatchUpdateException batchEx = new BatchUpdateException(
0940: sqlEx.getMessage(), sqlEx.getSQLState(), sqlEx
0941: .getErrorCode(), updateCounts);
0942: // Chain any other exceptions
0943: batchEx.setNextException(sqlEx.getNextException());
0944: throw batchEx;
0945: }
0946: return updateCounts;
0947: } catch (BatchUpdateException ex) {
0948: // If it's a BatchUpdateException let it go
0949: throw ex;
0950: } catch (SQLException ex) {
0951: // An SQLException can only occur while sending the batch
0952: // (getBatchCounts() doesn't throw SQLExceptions), so we have to
0953: // end the batch and return the partial results
0954: // FIXME What should we send here to flush out the batch?
0955: // Come to think of it, is there any circumstance under which this
0956: // could actually happen without the connection getting closed?
0957: // No counts will have been returned either as last packet will not
0958: // have been sent.
0959: throw new BatchUpdateException(ex.getMessage(), ex
0960: .getSQLState(), ex.getErrorCode(), new int[0]);
0961: } finally {
0962: clearBatch();
0963: }
0964: }
0965:
0966: public void setFetchDirection(int direction) throws SQLException {
0967: checkOpen();
0968: switch (direction) {
0969: case ResultSet.FETCH_UNKNOWN:
0970: case ResultSet.FETCH_REVERSE:
0971: case ResultSet.FETCH_FORWARD:
0972: this .fetchDirection = direction;
0973: break;
0974:
0975: default:
0976: throw new SQLException(Messages.get(
0977: "error.generic.badoption", Integer
0978: .toString(direction), "direction"), "24000");
0979: }
0980: }
0981:
0982: public void setFetchSize(int rows) throws SQLException {
0983: checkOpen();
0984:
0985: if (rows < 0) {
0986: throw new SQLException(Messages.get(
0987: "error.generic.optltzero", "setFetchSize"), "HY092");
0988: } else if (maxRows > 0 && rows > maxRows) {
0989: throw new SQLException(Messages
0990: .get("error.statement.gtmaxrows"), "HY092");
0991: }
0992: if (rows == 0) {
0993: rows = getDefaultFetchSize();
0994: }
0995: this .fetchSize = rows;
0996: }
0997:
0998: public void setMaxFieldSize(int max) throws SQLException {
0999: checkOpen();
1000:
1001: if (max < 0) {
1002: throw new SQLException(Messages.get(
1003: "error.generic.optltzero", "setMaxFieldSize"),
1004: "HY092");
1005: }
1006:
1007: maxFieldSize = max;
1008: }
1009:
1010: public void setMaxRows(int max) throws SQLException {
1011: checkOpen();
1012:
1013: if (max < 0) {
1014: throw new SQLException(Messages.get(
1015: "error.generic.optltzero", "setMaxRows"), "HY092");
1016: }
1017: if (max > 0 && max < this .fetchSize) {
1018: // Just for consistency with setFetchSize()
1019: this .fetchSize = max;
1020: }
1021: this .maxRows = max;
1022: }
1023:
1024: public void setQueryTimeout(int seconds) throws SQLException {
1025: checkOpen();
1026:
1027: if (seconds < 0) {
1028: throw new SQLException(Messages.get(
1029: "error.generic.optltzero", "setQueryTimeout"),
1030: "HY092");
1031: }
1032:
1033: this .queryTimeout = seconds;
1034: }
1035:
1036: public boolean getMoreResults(int current) throws SQLException {
1037: checkOpen();
1038:
1039: switch (current) {
1040: case CLOSE_ALL_RESULTS:
1041: updateCount = -1;
1042: closeAllResultSets();
1043: break;
1044: case CLOSE_CURRENT_RESULT:
1045: updateCount = -1;
1046: closeCurrentResultSet();
1047: break;
1048: case KEEP_CURRENT_RESULT:
1049: updateCount = -1;
1050: // If there is an open result set it is transferred to
1051: // the list of open result sets. For JtdsResultSet
1052: // result sets we cache the remaining data. For CachedResultSet
1053: // result sets the data is already cached.
1054: if (openResultSets == null) {
1055: openResultSets = new ArrayList();
1056: }
1057: if (currentResult instanceof MSCursorResultSet
1058: || currentResult instanceof CachedResultSet) {
1059: // NB. Due to restrictions on the way API cursors are
1060: // created, MSCursorResultSet can never be followed by
1061: // any other result sets, update counts or return variables.
1062: openResultSets.add(currentResult);
1063: } else if (currentResult != null) {
1064: currentResult.cacheResultSetRows();
1065: openResultSets.add(currentResult);
1066: }
1067: currentResult = null;
1068: break;
1069: default:
1070: throw new SQLException(Messages.get(
1071: "error.generic.badoption", Integer
1072: .toString(current), "current"), "HY092");
1073: }
1074:
1075: // Check for server side errors
1076: messages.checkErrors();
1077:
1078: // Dequeue any results
1079: if (!resultQueue.isEmpty() || processResults(false, false)) {
1080: Object nextResult = resultQueue.removeFirst();
1081:
1082: // Next result is an update count
1083: if (nextResult instanceof Integer) {
1084: updateCount = ((Integer) nextResult).intValue();
1085: return false;
1086: }
1087:
1088: // Next result is a ResultSet. Set currentResult and remove it.
1089: currentResult = (JtdsResultSet) nextResult;
1090: return true;
1091: } else {
1092: return false;
1093: }
1094: }
1095:
1096: public void setEscapeProcessing(boolean enable) throws SQLException {
1097: checkOpen();
1098:
1099: this .escapeProcessing = enable;
1100: }
1101:
1102: public int executeUpdate(String sql) throws SQLException {
1103: return executeUpdate(sql, NO_GENERATED_KEYS);
1104: }
1105:
1106: public void addBatch(String sql) throws SQLException {
1107: checkOpen();
1108:
1109: if (sql == null) {
1110: throw new NullPointerException();
1111: }
1112:
1113: if (batchValues == null) {
1114: batchValues = new ArrayList();
1115: }
1116:
1117: if (escapeProcessing) {
1118: String tmp[] = SQLParser
1119: .parse(sql, null, connection, false);
1120:
1121: if (tmp[1].length() != 0) {
1122: throw new SQLException(Messages
1123: .get("error.statement.badsql"), "07000");
1124: }
1125:
1126: sql = tmp[0];
1127: }
1128:
1129: batchValues.add(sql);
1130: }
1131:
1132: public void setCursorName(String name) throws SQLException {
1133: checkOpen();
1134: this .cursorName = name;
1135: if (name != null) {
1136: // Reset statement type to JDBC 1 default.
1137: this .resultSetType = ResultSet.TYPE_FORWARD_ONLY;
1138: this .fetchSize = 1; // Needed for positioned updates
1139: }
1140: }
1141:
1142: public boolean execute(String sql) throws SQLException {
1143: checkOpen();
1144:
1145: return executeImpl(sql, NO_GENERATED_KEYS, false);
1146: }
1147:
1148: public int executeUpdate(String sql, int autoGeneratedKeys)
1149: throws SQLException {
1150: checkOpen();
1151:
1152: executeImpl(sql, autoGeneratedKeys, true);
1153:
1154: int res = getUpdateCount();
1155: return res == -1 ? 0 : res;
1156: }
1157:
1158: public boolean execute(String sql, int autoGeneratedKeys)
1159: throws SQLException {
1160: checkOpen();
1161:
1162: return executeImpl(sql, autoGeneratedKeys, false);
1163: }
1164:
1165: public int executeUpdate(String sql, int[] columnIndexes)
1166: throws SQLException {
1167: checkOpen();
1168:
1169: if (columnIndexes == null) {
1170: throw new SQLException(Messages.get(
1171: "error.generic.nullparam", "executeUpdate"),
1172: "HY092");
1173: } else if (columnIndexes.length != 1) {
1174: throw new SQLException(Messages.get(
1175: "error.generic.needcolindex", "executeUpdate"),
1176: "HY092");
1177: }
1178:
1179: return executeUpdate(sql, RETURN_GENERATED_KEYS);
1180: }
1181:
1182: public boolean execute(String sql, int[] columnIndexes)
1183: throws SQLException {
1184: checkOpen();
1185:
1186: if (columnIndexes == null) {
1187: throw new SQLException(Messages.get(
1188: "error.generic.nullparam", "execute"), "HY092");
1189: } else if (columnIndexes.length != 1) {
1190: throw new SQLException(Messages.get(
1191: "error.generic.needcolindex", "execute"), "HY092");
1192: }
1193:
1194: return executeImpl(sql, RETURN_GENERATED_KEYS, false);
1195: }
1196:
1197: public Connection getConnection() throws SQLException {
1198: checkOpen();
1199:
1200: return this .connection;
1201: }
1202:
1203: public ResultSet getGeneratedKeys() throws SQLException {
1204: checkOpen();
1205:
1206: if (genKeyResultSet == null) {
1207: String colNames[] = { "ID" };
1208: int colTypes[] = { Types.INTEGER };
1209: //
1210: // Return an empty result set
1211: //
1212: CachedResultSet rs = new CachedResultSet(this , colNames,
1213: colTypes);
1214: rs.setConcurrency(ResultSet.CONCUR_READ_ONLY);
1215: genKeyResultSet = rs;
1216: }
1217:
1218: return genKeyResultSet;
1219: }
1220:
1221: public ResultSet getResultSet() throws SQLException {
1222: checkOpen();
1223: //
1224: if (currentResult instanceof MSCursorResultSet
1225: || currentResult instanceof CachedResultSet) {
1226: return currentResult;
1227: }
1228: //
1229: // See if we are returning a forward read only resultset
1230: //
1231: if (currentResult == null
1232: || (resultSetType == ResultSet.TYPE_FORWARD_ONLY && resultSetConcurrency == ResultSet.CONCUR_READ_ONLY)) {
1233: return currentResult;
1234: }
1235: //
1236: // OK Now create a CachedResultSet based on the existng result set.
1237: //
1238: currentResult = new CachedResultSet(currentResult, true);
1239:
1240: return currentResult;
1241: }
1242:
1243: public SQLWarning getWarnings() throws SQLException {
1244: checkOpen();
1245:
1246: return messages.getWarnings();
1247: }
1248:
1249: public int executeUpdate(String sql, String[] columnNames)
1250: throws SQLException {
1251: checkOpen();
1252:
1253: if (columnNames == null) {
1254: throw new SQLException(Messages.get(
1255: "error.generic.nullparam", "executeUpdate"),
1256: "HY092");
1257: } else if (columnNames.length != 1) {
1258: throw new SQLException(Messages.get(
1259: "error.generic.needcolname", "executeUpdate"),
1260: "HY092");
1261: }
1262:
1263: return executeUpdate(sql, RETURN_GENERATED_KEYS);
1264: }
1265:
1266: public boolean execute(String sql, String[] columnNames)
1267: throws SQLException {
1268: checkOpen();
1269:
1270: if (columnNames == null) {
1271: throw new SQLException(Messages.get(
1272: "error.generic.nullparam", "execute"), "HY092");
1273: } else if (columnNames.length != 1) {
1274: throw new SQLException(Messages.get(
1275: "error.generic.needcolname", "execute"), "HY092");
1276: }
1277:
1278: return executeImpl(sql, RETURN_GENERATED_KEYS, false);
1279: }
1280:
1281: public ResultSet executeQuery(String sql) throws SQLException {
1282: checkOpen();
1283: initialize();
1284:
1285: if (sql == null || sql.length() == 0) {
1286: throw new SQLException(Messages.get("error.generic.nosql"),
1287: "HY000");
1288: }
1289: if (escapeProcessing) {
1290: String tmp[] = SQLParser
1291: .parse(sql, null, connection, false);
1292:
1293: if (tmp[1].length() != 0) {
1294: throw new SQLException(Messages
1295: .get("error.statement.badsql"), "07000");
1296: }
1297:
1298: sql = tmp[0];
1299: }
1300:
1301: return this .executeSQLQuery(sql, null, null, useCursor(false,
1302: null));
1303: }
1304: }
|