0001: /*
0002: Copyright (C) 2002-2007 MySQL AB
0003:
0004: This program is free software; you can redistribute it and/or modify
0005: it under the terms of version 2 of the GNU General Public License as
0006: published by the Free Software Foundation.
0007:
0008: There are special exceptions to the terms and conditions of the GPL
0009: as it is applied to this software. View the full text of the
0010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
0011: software distribution.
0012:
0013: This program is distributed in the hope that it will be useful,
0014: but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: GNU General Public License for more details.
0017:
0018: You should have received a copy of the GNU General Public License
0019: along with this program; if not, write to the Free Software
0020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0021:
0022:
0023:
0024: */
0025: package com.mysql.jdbc;
0026:
0027: import com.mysql.jdbc.exceptions.MySQLStatementCancelledException;
0028: import com.mysql.jdbc.exceptions.NotYetImplementedException;
0029: import com.mysql.jdbc.exceptions.MySQLTimeoutException;
0030: import com.mysql.jdbc.profiler.ProfileEventSink;
0031: import com.mysql.jdbc.profiler.ProfilerEvent;
0032: import com.mysql.jdbc.util.LRUCache;
0033:
0034: import java.io.InputStream;
0035: import java.sql.BatchUpdateException;
0036: import java.sql.ResultSet;
0037: import java.sql.SQLException;
0038: import java.sql.SQLWarning;
0039: import java.sql.Types;
0040:
0041: import java.util.ArrayList;
0042: import java.util.Calendar;
0043: import java.util.GregorianCalendar;
0044: import java.util.Iterator;
0045: import java.util.List;
0046: import java.util.Map;
0047: import java.util.TimerTask;
0048:
0049: /**
0050: * A Statement object is used for executing a static SQL statement and obtaining
0051: * the results produced by it.
0052: *
0053: * <p>
0054: * Only one ResultSet per Statement can be open at any point in time. Therefore,
0055: * if the reading of one ResultSet is interleaved with the reading of another,
0056: * each must have been generated by different Statements. All statement execute
0057: * methods implicitly close a statement's current ResultSet if an open one
0058: * exists.
0059: * </p>
0060: *
0061: * @author Mark Matthews
0062: * @version $Id: Statement.java 4624 2005-11-28 14:24:29 -0600 (Mon, 28 Nov
0063: * 2005) mmatthews $
0064: *
0065: * @see java.sql.Statement
0066: * @see ResultSetInternalMethods
0067: */
0068: public class StatementImpl implements Statement {
0069: protected static final String PING_MARKER = "/* ping */";
0070:
0071: /**
0072: * Thread used to implement query timeouts...Eventually we could be more
0073: * efficient and have one thread with timers, but this is a straightforward
0074: * and simple way to implement a feature that isn't used all that often.
0075: */
0076: class CancelTask extends TimerTask {
0077:
0078: long connectionId = 0;
0079: SQLException caughtWhileCancelling = null;
0080: StatementImpl toCancel;
0081:
0082: CancelTask(StatementImpl cancellee) throws SQLException {
0083: connectionId = connection.getIO().getThreadId();
0084: toCancel = cancellee;
0085: }
0086:
0087: public void run() {
0088:
0089: Thread cancelThread = new Thread() {
0090:
0091: public void run() {
0092: Connection cancelConn = null;
0093: java.sql.Statement cancelStmt = null;
0094:
0095: try {
0096: synchronized (cancelTimeoutMutex) {
0097: cancelConn = connection.duplicate();
0098: cancelStmt = cancelConn.createStatement();
0099: cancelStmt.execute("KILL QUERY "
0100: + connectionId);
0101: toCancel.wasCancelled = true;
0102: toCancel.wasCancelledByTimeout = true;
0103: }
0104: } catch (SQLException sqlEx) {
0105: caughtWhileCancelling = sqlEx;
0106: } catch (NullPointerException npe) {
0107: // Case when connection closed while starting to cancel
0108: // We can't easily synchronize this, because then one thread
0109: // can't cancel() a running query
0110:
0111: // ignore, we shouldn't re-throw this, because the connection's
0112: // already closed, so the statement has been timed out.
0113: } finally {
0114: if (cancelStmt != null) {
0115: try {
0116: cancelStmt.close();
0117: } catch (SQLException sqlEx) {
0118: throw new RuntimeException(sqlEx
0119: .toString());
0120: }
0121: }
0122:
0123: if (cancelConn != null) {
0124: try {
0125: cancelConn.close();
0126: } catch (SQLException sqlEx) {
0127: throw new RuntimeException(sqlEx
0128: .toString());
0129: }
0130: }
0131: }
0132: }
0133: };
0134:
0135: cancelThread.start();
0136: }
0137: }
0138:
0139: /** Mutex to prevent race between returning query results and noticing
0140: that we're timed-out or cancelled. */
0141:
0142: protected Object cancelTimeoutMutex = new Object();
0143:
0144: /** Used to generate IDs when profiling. */
0145: protected static int statementCounter = 1;
0146:
0147: public final static byte USES_VARIABLES_FALSE = 0;
0148:
0149: public final static byte USES_VARIABLES_TRUE = 1;
0150:
0151: public final static byte USES_VARIABLES_UNKNOWN = -1;
0152:
0153: protected boolean wasCancelled = false;
0154: protected boolean wasCancelledByTimeout = false;
0155:
0156: /** Holds batched commands */
0157: protected List batchedArgs;
0158:
0159: /** The character converter to use (if available) */
0160: protected SingleByteCharsetConverter charConverter = null;
0161:
0162: /** The character encoding to use (if available) */
0163: protected String charEncoding = null;
0164:
0165: /** The connection that created us */
0166: protected ConnectionImpl connection = null;
0167:
0168: protected long connectionId = 0;
0169:
0170: /** The catalog in use */
0171: protected String currentCatalog = null;
0172:
0173: /** Should we process escape codes? */
0174: protected boolean doEscapeProcessing = true;
0175:
0176: /** If we're profiling, where should events go to? */
0177: protected ProfileEventSink eventSink = null;
0178:
0179: /** The number of rows to fetch at a time (currently ignored) */
0180: private int fetchSize = 0;
0181:
0182: /** Has this statement been closed? */
0183: protected boolean isClosed = false;
0184:
0185: /** The auto_increment value for the last insert */
0186: protected long lastInsertId = -1;
0187:
0188: /** The max field size for this statement */
0189: protected int maxFieldSize = MysqlIO.getMaxBuf();
0190:
0191: /**
0192: * The maximum number of rows to return for this statement (-1 means _all_
0193: * rows)
0194: */
0195: protected int maxRows = -1;
0196:
0197: /** Has someone changed this for this statement? */
0198: protected boolean maxRowsChanged = false;
0199:
0200: /** List of currently-open ResultSets */
0201: protected List openResults = new ArrayList();
0202:
0203: /** Are we in pedantic mode? */
0204: protected boolean pedantic = false;
0205:
0206: /**
0207: * Where this statement was created, only used if profileSql or
0208: * useUsageAdvisor set to true.
0209: */
0210: protected Throwable pointOfOrigin;
0211:
0212: /** Should we profile? */
0213: protected boolean profileSQL = false;
0214:
0215: /** The current results */
0216: protected ResultSetInternalMethods results = null;
0217:
0218: /** The concurrency for this result set (updatable or not) */
0219: protected int resultSetConcurrency = 0;
0220:
0221: /** The type of this result set (scroll sensitive or in-sensitive) */
0222: protected int resultSetType = 0;
0223:
0224: /** Used to identify this statement when profiling. */
0225: protected int statementId;
0226:
0227: /** The timeout for a query */
0228: protected int timeoutInMillis = 0;
0229:
0230: /** The update count for this statement */
0231: protected long updateCount = -1;
0232:
0233: /** Should we use the usage advisor? */
0234: protected boolean useUsageAdvisor = false;
0235:
0236: /** The warnings chain. */
0237: protected SQLWarning warningChain = null;
0238:
0239: /**
0240: * Should this statement hold results open over .close() irregardless of
0241: * connection's setting?
0242: */
0243: protected boolean holdResultsOpenOverClose = false;
0244:
0245: protected ArrayList batchedGeneratedKeys = null;
0246:
0247: protected boolean retrieveGeneratedKeys = false;
0248:
0249: protected boolean continueBatchOnError = false;
0250:
0251: protected PingTarget pingTarget = null;
0252:
0253: /**
0254: * Constructor for a Statement.
0255: *
0256: * @param c
0257: * the Connection instantation that creates us
0258: * @param catalog
0259: * the database name in use when we were created
0260: *
0261: * @throws SQLException
0262: * if an error occurs.
0263: */
0264: public StatementImpl(ConnectionImpl c, String catalog)
0265: throws SQLException {
0266: if ((c == null) || c.isClosed()) {
0267: throw SQLError.createSQLException(Messages
0268: .getString("Statement.0"), //$NON-NLS-1$
0269: SQLError.SQL_STATE_CONNECTION_NOT_OPEN); //$NON-NLS-1$ //$NON-NLS-2$
0270: }
0271:
0272: this .connection = c;
0273: this .connectionId = this .connection.getId();
0274:
0275: this .currentCatalog = catalog;
0276: this .pedantic = this .connection.getPedantic();
0277: this .continueBatchOnError = this .connection
0278: .getContinueBatchOnError();
0279:
0280: if (!this .connection.getDontTrackOpenResources()) {
0281: this .connection.registerStatement(this );
0282: }
0283:
0284: //
0285: // Adjust, if we know it
0286: //
0287:
0288: if (this .connection != null) {
0289: this .maxFieldSize = this .connection.getMaxAllowedPacket();
0290:
0291: int defaultFetchSize = this .connection
0292: .getDefaultFetchSize();
0293:
0294: if (defaultFetchSize != 0) {
0295: setFetchSize(defaultFetchSize);
0296: }
0297: }
0298:
0299: if (this .connection.getUseUnicode()) {
0300: this .charEncoding = this .connection.getEncoding();
0301:
0302: this .charConverter = this .connection
0303: .getCharsetConverter(this .charEncoding);
0304: }
0305:
0306: boolean profiling = this .connection.getProfileSql()
0307: || this .connection.getUseUsageAdvisor();
0308:
0309: if (this .connection.getAutoGenerateTestcaseScript()
0310: || profiling) {
0311: this .statementId = statementCounter++;
0312: }
0313:
0314: if (profiling) {
0315: this .pointOfOrigin = new Throwable();
0316: this .profileSQL = this .connection.getProfileSql();
0317: this .useUsageAdvisor = this .connection.getUseUsageAdvisor();
0318: this .eventSink = ProfileEventSink
0319: .getInstance(this .connection);
0320: }
0321:
0322: int maxRowsConn = this .connection.getMaxRows();
0323:
0324: if (maxRowsConn != -1) {
0325: setMaxRows(maxRowsConn);
0326: }
0327: }
0328:
0329: /**
0330: * DOCUMENT ME!
0331: *
0332: * @param sql
0333: * DOCUMENT ME!
0334: *
0335: * @throws SQLException
0336: * DOCUMENT ME!
0337: */
0338: public synchronized void addBatch(String sql) throws SQLException {
0339: if (this .batchedArgs == null) {
0340: this .batchedArgs = new ArrayList();
0341: }
0342:
0343: if (sql != null) {
0344: this .batchedArgs.add(sql);
0345: }
0346: }
0347:
0348: /**
0349: * Cancels this Statement object if both the DBMS and driver support
0350: * aborting an SQL statement. This method can be used by one thread to
0351: * cancel a statement that is being executed by another thread.
0352: */
0353: public void cancel() throws SQLException {
0354: if (!this .isClosed && this .connection != null
0355: && this .connection.versionMeetsMinimum(5, 0, 0)) {
0356: Connection cancelConn = null;
0357: java.sql.Statement cancelStmt = null;
0358:
0359: try {
0360: cancelConn = this .connection.duplicate();
0361: cancelStmt = cancelConn.createStatement();
0362: cancelStmt.execute("KILL QUERY "
0363: + this .connection.getIO().getThreadId());
0364: this .wasCancelled = true;
0365: } finally {
0366: if (cancelStmt != null) {
0367: cancelStmt.close();
0368: }
0369:
0370: if (cancelConn != null) {
0371: cancelConn.close();
0372: }
0373: }
0374:
0375: }
0376: }
0377:
0378: // --------------------------JDBC 2.0-----------------------------
0379:
0380: /**
0381: * Checks if closed() has been called, and throws an exception if so
0382: *
0383: * @throws SQLException
0384: * if this statement has been closed
0385: */
0386: protected void checkClosed() throws SQLException {
0387: if (this .isClosed) {
0388: throw SQLError.createSQLException(Messages
0389: .getString("Statement.49"), //$NON-NLS-1$
0390: SQLError.SQL_STATE_CONNECTION_NOT_OPEN); //$NON-NLS-1$
0391: }
0392: }
0393:
0394: /**
0395: * Checks if the given SQL query with the given first non-ws char is a DML
0396: * statement. Throws an exception if it is.
0397: *
0398: * @param sql
0399: * the SQL to check
0400: * @param firstStatementChar
0401: * the UC first non-ws char of the statement
0402: *
0403: * @throws SQLException
0404: * if the statement contains DML
0405: */
0406: protected void checkForDml(String sql, char firstStatementChar)
0407: throws SQLException {
0408: if ((firstStatementChar == 'I') || (firstStatementChar == 'U')
0409: || (firstStatementChar == 'D')
0410: || (firstStatementChar == 'A')
0411: || (firstStatementChar == 'C')) {
0412: String noCommentSql = StringUtils.stripComments(sql, "'\"",
0413: "'\"", true, false, true, true);
0414:
0415: if (StringUtils.startsWithIgnoreCaseAndWs(noCommentSql,
0416: "INSERT") //$NON-NLS-1$
0417: || StringUtils.startsWithIgnoreCaseAndWs(
0418: noCommentSql, "UPDATE") //$NON-NLS-1$
0419: || StringUtils.startsWithIgnoreCaseAndWs(
0420: noCommentSql, "DELETE") //$NON-NLS-1$
0421: || StringUtils.startsWithIgnoreCaseAndWs(
0422: noCommentSql, "DROP") //$NON-NLS-1$
0423: || StringUtils.startsWithIgnoreCaseAndWs(
0424: noCommentSql, "CREATE") //$NON-NLS-1$
0425: || StringUtils.startsWithIgnoreCaseAndWs(
0426: noCommentSql, "ALTER")) { //$NON-NLS-1$
0427: throw SQLError.createSQLException(Messages
0428: .getString("Statement.57"), //$NON-NLS-1$
0429: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
0430: }
0431: }
0432: }
0433:
0434: /**
0435: * Method checkNullOrEmptyQuery.
0436: *
0437: * @param sql
0438: * the SQL to check
0439: *
0440: * @throws SQLException
0441: * if query is null or empty.
0442: */
0443: protected void checkNullOrEmptyQuery(String sql)
0444: throws SQLException {
0445: if (sql == null) {
0446: throw SQLError.createSQLException(Messages
0447: .getString("Statement.59"), //$NON-NLS-1$
0448: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$ //$NON-NLS-2$
0449: }
0450:
0451: if (sql.length() == 0) {
0452: throw SQLError.createSQLException(Messages
0453: .getString("Statement.61"), //$NON-NLS-1$
0454: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$ //$NON-NLS-2$
0455: }
0456: }
0457:
0458: /**
0459: * JDBC 2.0 Make the set of commands in the current batch empty. This method
0460: * is optional.
0461: *
0462: * @exception SQLException
0463: * if a database-access error occurs, or the driver does not
0464: * support batch statements
0465: */
0466: public synchronized void clearBatch() throws SQLException {
0467: if (this .batchedArgs != null) {
0468: this .batchedArgs.clear();
0469: }
0470: }
0471:
0472: /**
0473: * After this call, getWarnings returns null until a new warning is reported
0474: * for this Statement.
0475: *
0476: * @exception SQLException
0477: * if a database access error occurs (why?)
0478: */
0479: public void clearWarnings() throws SQLException {
0480: this .warningChain = null;
0481: }
0482:
0483: /**
0484: * In many cases, it is desirable to immediately release a Statement's
0485: * database and JDBC resources instead of waiting for this to happen when it
0486: * is automatically closed. The close method provides this immediate
0487: * release.
0488: *
0489: * <p>
0490: * <B>Note:</B> A Statement is automatically closed when it is garbage
0491: * collected. When a Statement is closed, its current ResultSet, if one
0492: * exists, is also closed.
0493: * </p>
0494: *
0495: * @exception SQLException
0496: * if a database access error occurs
0497: */
0498: public void close() throws SQLException {
0499: realClose(true, true);
0500: }
0501:
0502: /**
0503: * Close any open result sets that have been 'held open'
0504: */
0505: protected void closeAllOpenResults() {
0506: if (this .openResults != null) {
0507: for (Iterator iter = this .openResults.iterator(); iter
0508: .hasNext();) {
0509: ResultSetInternalMethods element = (ResultSetInternalMethods) iter
0510: .next();
0511:
0512: try {
0513: element.realClose(false);
0514: } catch (SQLException sqlEx) {
0515: AssertionFailedException.shouldNotHappen(sqlEx);
0516: }
0517: }
0518:
0519: this .openResults.clear();
0520: }
0521: }
0522:
0523: /**
0524: * @param sql
0525: * @return
0526: */
0527: private ResultSetInternalMethods createResultSetUsingServerFetch(
0528: String sql) throws SQLException {
0529: java.sql.PreparedStatement pStmt = this .connection
0530: .prepareStatement(sql, this .resultSetType,
0531: this .resultSetConcurrency);
0532:
0533: pStmt.setFetchSize(this .fetchSize);
0534:
0535: if (this .maxRows > -1) {
0536: pStmt.setMaxRows(this .maxRows);
0537: }
0538:
0539: pStmt.execute();
0540:
0541: //
0542: // Need to be able to get resultset irrespective if we issued DML or
0543: // not to make this work.
0544: //
0545: ResultSetInternalMethods rs = ((com.mysql.jdbc.StatementImpl) pStmt)
0546: .getResultSetInternal();
0547:
0548: rs
0549: .setStatementUsedForFetchingRows((com.mysql.jdbc.PreparedStatement) pStmt);
0550:
0551: this .results = rs;
0552:
0553: return rs;
0554: }
0555:
0556: /**
0557: * We only stream result sets when they are forward-only, read-only, and the
0558: * fetch size has been set to Integer.MIN_VALUE
0559: *
0560: * @return true if this result set should be streamed row at-a-time, rather
0561: * than read all at once.
0562: */
0563: protected boolean createStreamingResultSet() {
0564: return ((this .resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY)
0565: && (this .resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this .fetchSize == Integer.MIN_VALUE));
0566: }
0567:
0568: private int originalResultSetType = 0;
0569: private int originalFetchSize = 0;
0570:
0571: /* (non-Javadoc)
0572: * @see com.mysql.jdbc.IStatement#enableStreamingResults()
0573: */
0574: public void enableStreamingResults() throws SQLException {
0575: this .originalResultSetType = this .resultSetType;
0576: this .originalFetchSize = this .fetchSize;
0577:
0578: setFetchSize(Integer.MIN_VALUE);
0579: setResultSetType(ResultSet.TYPE_FORWARD_ONLY);
0580: }
0581:
0582: public void disableStreamingResults() throws SQLException {
0583: if (this .fetchSize == Integer.MIN_VALUE
0584: && this .resultSetType == ResultSet.TYPE_FORWARD_ONLY) {
0585: setFetchSize(this .originalFetchSize);
0586: setResultSetType(this .originalResultSetType);
0587: }
0588: }
0589:
0590: /**
0591: * Execute a SQL statement that may return multiple results. We don't have
0592: * to worry about this since we do not support multiple ResultSets. You can
0593: * use getResultSet or getUpdateCount to retrieve the result.
0594: *
0595: * @param sql
0596: * any SQL statement
0597: *
0598: * @return true if the next result is a ResulSet, false if it is an update
0599: * count or there are no more results
0600: *
0601: * @exception SQLException
0602: * if a database access error occurs
0603: */
0604: public boolean execute(String sql) throws SQLException {
0605: checkClosed();
0606:
0607: ConnectionImpl locallyScopedConn = this .connection;
0608:
0609: synchronized (locallyScopedConn.getMutex()) {
0610: resetCancelledState();
0611:
0612: checkNullOrEmptyQuery(sql);
0613:
0614: checkClosed();
0615:
0616: char firstNonWsChar = StringUtils.firstNonWsCharUc(sql);
0617:
0618: boolean isSelect = true;
0619:
0620: if (firstNonWsChar != 'S') {
0621: isSelect = false;
0622:
0623: if (locallyScopedConn.isReadOnly()) {
0624: throw SQLError.createSQLException(Messages
0625: .getString("Statement.27") //$NON-NLS-1$
0626: + Messages.getString("Statement.28"), //$NON-NLS-1$
0627: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
0628: }
0629: }
0630:
0631: boolean doStreaming = createStreamingResultSet();
0632:
0633: // Adjust net_write_timeout to a higher value if we're
0634: // streaming result sets. More often than not, someone runs into
0635: // an issue where they blow net_write_timeout when using this
0636: // feature, and if they're willing to hold a result set open
0637: // for 30 seconds or more, one more round-trip isn't going to hurt
0638: //
0639: // This is reset by RowDataDynamic.close().
0640:
0641: if (doStreaming
0642: && this .connection
0643: .getNetTimeoutForStreamingResults() > 0) {
0644: executeSimpleNonQuery(
0645: locallyScopedConn,
0646: "SET net_write_timeout="
0647: + this .connection
0648: .getNetTimeoutForStreamingResults());
0649: }
0650:
0651: if (this .doEscapeProcessing) {
0652: Object escapedSqlResult = EscapeProcessor.escapeSQL(
0653: sql, locallyScopedConn
0654: .serverSupportsConvertFn(),
0655: locallyScopedConn);
0656:
0657: if (escapedSqlResult instanceof String) {
0658: sql = (String) escapedSqlResult;
0659: } else {
0660: sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
0661: }
0662: }
0663:
0664: if (this .results != null) {
0665: if (!locallyScopedConn
0666: .getHoldResultsOpenOverStatementClose()) {
0667: this .results.realClose(false);
0668: }
0669: }
0670:
0671: if (firstNonWsChar == '/') {
0672: if (sql.startsWith(PING_MARKER)) {
0673: doPingInstead();
0674:
0675: return true;
0676: }
0677: }
0678:
0679: CachedResultSetMetaData cachedMetaData = null;
0680:
0681: ResultSetInternalMethods rs = null;
0682:
0683: // If there isn't a limit clause in the SQL
0684: // then limit the number of rows to return in
0685: // an efficient manner. Only do this if
0686: // setMaxRows() hasn't been used on any Statements
0687: // generated from the current Connection (saves
0688: // a query, and network traffic).
0689:
0690: this .batchedGeneratedKeys = null;
0691:
0692: if (useServerFetch()) {
0693: rs = createResultSetUsingServerFetch(sql);
0694: } else {
0695: CancelTask timeoutTask = null;
0696:
0697: String oldCatalog = null;
0698:
0699: try {
0700: if (locallyScopedConn.getEnableQueryTimeouts()
0701: && this .timeoutInMillis != 0
0702: && locallyScopedConn.versionMeetsMinimum(5,
0703: 0, 0)) {
0704: timeoutTask = new CancelTask(this );
0705: ConnectionImpl.getCancelTimer().schedule(
0706: timeoutTask, this .timeoutInMillis);
0707: }
0708:
0709: if (!locallyScopedConn.getCatalog().equals(
0710: this .currentCatalog)) {
0711: oldCatalog = locallyScopedConn.getCatalog();
0712: locallyScopedConn
0713: .setCatalog(this .currentCatalog);
0714: }
0715:
0716: //
0717: // Check if we have cached metadata for this query...
0718: //
0719:
0720: Field[] cachedFields = null;
0721:
0722: if (locallyScopedConn.getCacheResultSetMetadata()) {
0723: cachedMetaData = locallyScopedConn
0724: .getCachedMetaData(sql);
0725:
0726: if (cachedMetaData != null) {
0727: cachedFields = cachedMetaData.fields;
0728: }
0729: }
0730:
0731: //
0732: // Only apply max_rows to selects
0733: //
0734: if (locallyScopedConn.useMaxRows()) {
0735: int rowLimit = -1;
0736:
0737: if (isSelect) {
0738: if (StringUtils.indexOfIgnoreCase(sql,
0739: "LIMIT") != -1) { //$NON-NLS-1$
0740: rowLimit = this .maxRows;
0741: } else {
0742: if (this .maxRows <= 0) {
0743: executeSimpleNonQuery(
0744: locallyScopedConn,
0745: "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
0746: } else {
0747: executeSimpleNonQuery(
0748: locallyScopedConn,
0749: "SET OPTION SQL_SELECT_LIMIT="
0750: + this .maxRows);
0751: }
0752: }
0753: } else {
0754: executeSimpleNonQuery(locallyScopedConn,
0755: "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
0756: }
0757:
0758: // Finally, execute the query
0759: rs = locallyScopedConn.execSQL(this , sql,
0760: rowLimit, null, this .resultSetType,
0761: this .resultSetConcurrency, doStreaming,
0762: this .currentCatalog, cachedFields);
0763: } else {
0764: rs = locallyScopedConn.execSQL(this , sql, -1,
0765: null, this .resultSetType,
0766: this .resultSetConcurrency, doStreaming,
0767: this .currentCatalog, cachedFields);
0768: }
0769:
0770: if (timeoutTask != null) {
0771: if (timeoutTask.caughtWhileCancelling != null) {
0772: throw timeoutTask.caughtWhileCancelling;
0773: }
0774:
0775: timeoutTask.cancel();
0776: timeoutTask = null;
0777: }
0778:
0779: synchronized (this .cancelTimeoutMutex) {
0780: if (this .wasCancelled) {
0781: SQLException cause = null;
0782:
0783: if (this .wasCancelledByTimeout) {
0784: cause = new MySQLTimeoutException();
0785: } else {
0786: cause = new MySQLStatementCancelledException();
0787: }
0788:
0789: resetCancelledState();
0790:
0791: throw cause;
0792: }
0793: }
0794: } finally {
0795: if (timeoutTask != null) {
0796: timeoutTask.cancel();
0797: }
0798:
0799: if (oldCatalog != null) {
0800: locallyScopedConn.setCatalog(oldCatalog);
0801: }
0802: }
0803: }
0804:
0805: if (rs != null) {
0806: this .lastInsertId = rs.getUpdateID();
0807:
0808: this .results = rs;
0809:
0810: rs.setFirstCharOfQuery(firstNonWsChar);
0811:
0812: if (rs.reallyResult()) {
0813: if (cachedMetaData != null) {
0814: locallyScopedConn
0815: .initializeResultsMetadataFromCache(
0816: sql, cachedMetaData,
0817: this .results);
0818: } else {
0819: if (this .connection.getCacheResultSetMetadata()) {
0820: locallyScopedConn
0821: .initializeResultsMetadataFromCache(
0822: sql,
0823: null /* will be created */,
0824: this .results);
0825: }
0826: }
0827: }
0828: }
0829:
0830: return ((rs != null) && rs.reallyResult());
0831: }
0832: }
0833:
0834: protected synchronized void resetCancelledState() {
0835: if (this .cancelTimeoutMutex == null) {
0836: return;
0837: }
0838:
0839: synchronized (this .cancelTimeoutMutex) {
0840: this .wasCancelled = false;
0841: this .wasCancelledByTimeout = false;
0842: }
0843: }
0844:
0845: /**
0846: * @see StatementImpl#execute(String, int)
0847: */
0848: public boolean execute(String sql, int returnGeneratedKeys)
0849: throws SQLException {
0850:
0851: if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS) {
0852: checkClosed();
0853:
0854: ConnectionImpl locallyScopedConn = this .connection;
0855:
0856: synchronized (locallyScopedConn.getMutex()) {
0857: // If this is a 'REPLACE' query, we need to be able to parse
0858: // the 'info' message returned from the server to determine
0859: // the actual number of keys generated.
0860: boolean readInfoMsgState = this .connection
0861: .isReadInfoMsgEnabled();
0862: locallyScopedConn.setReadInfoMsgEnabled(true);
0863:
0864: try {
0865: return execute(sql);
0866: } finally {
0867: locallyScopedConn
0868: .setReadInfoMsgEnabled(readInfoMsgState);
0869: }
0870: }
0871: }
0872:
0873: return execute(sql);
0874: }
0875:
0876: /**
0877: * @see StatementImpl#execute(String, int[])
0878: */
0879: public boolean execute(String sql, int[] generatedKeyIndices)
0880: throws SQLException {
0881: if ((generatedKeyIndices != null)
0882: && (generatedKeyIndices.length > 0)) {
0883: checkClosed();
0884:
0885: ConnectionImpl locallyScopedConn = this .connection;
0886:
0887: synchronized (locallyScopedConn.getMutex()) {
0888: // If this is a 'REPLACE' query, we need to be able to parse
0889: // the 'info' message returned from the server to determine
0890: // the actual number of keys generated.
0891: boolean readInfoMsgState = locallyScopedConn
0892: .isReadInfoMsgEnabled();
0893: locallyScopedConn.setReadInfoMsgEnabled(true);
0894:
0895: try {
0896: return execute(sql);
0897: } finally {
0898: locallyScopedConn
0899: .setReadInfoMsgEnabled(readInfoMsgState);
0900: }
0901: }
0902: }
0903:
0904: return execute(sql);
0905: }
0906:
0907: /**
0908: * @see StatementImpl#execute(String, String[])
0909: */
0910: public boolean execute(String sql, String[] generatedKeyNames)
0911: throws SQLException {
0912: if ((generatedKeyNames != null)
0913: && (generatedKeyNames.length > 0)) {
0914: checkClosed();
0915:
0916: ConnectionImpl locallyScopedConn = this .connection;
0917:
0918: synchronized (locallyScopedConn.getMutex()) {
0919: // If this is a 'REPLACE' query, we need to be able to parse
0920: // the 'info' message returned from the server to determine
0921: // the actual number of keys generated.
0922: boolean readInfoMsgState = this .connection
0923: .isReadInfoMsgEnabled();
0924: locallyScopedConn.setReadInfoMsgEnabled(true);
0925:
0926: try {
0927: return execute(sql);
0928: } finally {
0929: locallyScopedConn
0930: .setReadInfoMsgEnabled(readInfoMsgState);
0931: }
0932: }
0933: }
0934:
0935: return execute(sql);
0936: }
0937:
0938: /**
0939: * JDBC 2.0 Submit a batch of commands to the database for execution. This
0940: * method is optional.
0941: *
0942: * @return an array of update counts containing one element for each command
0943: * in the batch. The array is ordered according to the order in
0944: * which commands were inserted into the batch
0945: *
0946: * @exception SQLException
0947: * if a database-access error occurs, or the driver does not
0948: * support batch statements
0949: * @throws java.sql.BatchUpdateException
0950: * DOCUMENT ME!
0951: */
0952: public synchronized int[] executeBatch() throws SQLException {
0953: checkClosed();
0954:
0955: ConnectionImpl locallyScopedConn = this .connection;
0956:
0957: if (locallyScopedConn.isReadOnly()) {
0958: throw SQLError.createSQLException(Messages
0959: .getString("Statement.34") //$NON-NLS-1$
0960: + Messages.getString("Statement.35"), //$NON-NLS-1$
0961: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
0962: }
0963:
0964: if (this .results != null) {
0965: if (!locallyScopedConn
0966: .getHoldResultsOpenOverStatementClose()) {
0967: this .results.realClose(false);
0968: }
0969: }
0970:
0971: synchronized (locallyScopedConn.getMutex()) {
0972: if (this .batchedArgs == null
0973: || this .batchedArgs.size() == 0) {
0974: return new int[0];
0975: }
0976:
0977: // we timeout the entire batch, not individual statements
0978: int individualStatementTimeout = this .timeoutInMillis;
0979: this .timeoutInMillis = 0;
0980:
0981: CancelTask timeoutTask = null;
0982:
0983: try {
0984: resetCancelledState();
0985:
0986: this .retrieveGeneratedKeys = true;
0987:
0988: int[] updateCounts = null;
0989:
0990: if (this .batchedArgs != null) {
0991: int nbrCommands = this .batchedArgs.size();
0992:
0993: this .batchedGeneratedKeys = new ArrayList(
0994: this .batchedArgs.size());
0995:
0996: boolean multiQueriesEnabled = locallyScopedConn
0997: .getAllowMultiQueries();
0998:
0999: if (locallyScopedConn.versionMeetsMinimum(4, 1, 1)
1000: && (multiQueriesEnabled || (locallyScopedConn
1001: .getRewriteBatchedStatements() && nbrCommands > 4))) {
1002: return executeBatchUsingMultiQueries(
1003: multiQueriesEnabled, nbrCommands,
1004: individualStatementTimeout);
1005: }
1006:
1007: if (locallyScopedConn.getEnableQueryTimeouts()
1008: && individualStatementTimeout != 0
1009: && locallyScopedConn.versionMeetsMinimum(5,
1010: 0, 0)) {
1011: timeoutTask = new CancelTask(this );
1012: ConnectionImpl.getCancelTimer()
1013: .schedule(timeoutTask,
1014: individualStatementTimeout);
1015: }
1016:
1017: updateCounts = new int[nbrCommands];
1018:
1019: for (int i = 0; i < nbrCommands; i++) {
1020: updateCounts[i] = -3;
1021: }
1022:
1023: SQLException sqlEx = null;
1024:
1025: int commandIndex = 0;
1026:
1027: for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
1028: try {
1029: updateCounts[commandIndex] = executeUpdate(
1030: (String) this .batchedArgs
1031: .get(commandIndex), true);
1032: getBatchedGeneratedKeys();
1033: } catch (SQLException ex) {
1034: updateCounts[commandIndex] = EXECUTE_FAILED;
1035:
1036: if (this .continueBatchOnError
1037: && !(ex instanceof MySQLTimeoutException)
1038: && !(ex instanceof MySQLStatementCancelledException)) {
1039: sqlEx = ex;
1040: } else {
1041: int[] newUpdateCounts = new int[commandIndex];
1042: System.arraycopy(updateCounts, 0,
1043: newUpdateCounts, 0,
1044: commandIndex);
1045:
1046: throw new java.sql.BatchUpdateException(
1047: ex.getMessage(), ex
1048: .getSQLState(), ex
1049: .getErrorCode(),
1050: newUpdateCounts);
1051: }
1052: }
1053: }
1054:
1055: if (sqlEx != null) {
1056: throw new java.sql.BatchUpdateException(sqlEx
1057: .getMessage(), sqlEx.getSQLState(),
1058: sqlEx.getErrorCode(), updateCounts);
1059: }
1060: }
1061:
1062: if (timeoutTask != null) {
1063: if (timeoutTask.caughtWhileCancelling != null) {
1064: throw timeoutTask.caughtWhileCancelling;
1065: }
1066:
1067: timeoutTask.cancel();
1068: timeoutTask = null;
1069: }
1070:
1071: return (updateCounts != null) ? updateCounts
1072: : new int[0];
1073: } finally {
1074:
1075: if (timeoutTask != null) {
1076: timeoutTask.cancel();
1077: }
1078:
1079: resetCancelledState();
1080:
1081: this .timeoutInMillis = individualStatementTimeout;
1082: this .retrieveGeneratedKeys = false;
1083:
1084: clearBatch();
1085: }
1086: }
1087: }
1088:
1089: /**
1090: * Rewrites batch into a single query to send to the server. This method
1091: * will constrain each batch to be shorter than max_allowed_packet on the
1092: * server.
1093: *
1094: * @return update counts in the same manner as executeBatch()
1095: * @throws SQLException
1096: */
1097: private int[] executeBatchUsingMultiQueries(
1098: boolean multiQueriesEnabled, int nbrCommands,
1099: int individualStatementTimeout) throws SQLException {
1100:
1101: ConnectionImpl locallyScopedConn = this .connection;
1102:
1103: if (!multiQueriesEnabled) {
1104: locallyScopedConn.getIO().enableMultiQueries();
1105: }
1106:
1107: java.sql.Statement batchStmt = null;
1108:
1109: CancelTask timeoutTask = null;
1110:
1111: try {
1112: int[] updateCounts = new int[nbrCommands];
1113:
1114: for (int i = 0; i < nbrCommands; i++) {
1115: updateCounts[i] = -3;
1116: }
1117:
1118: int commandIndex = 0;
1119:
1120: StringBuffer queryBuf = new StringBuffer();
1121:
1122: batchStmt = locallyScopedConn.createStatement();
1123:
1124: if (locallyScopedConn.getEnableQueryTimeouts()
1125: && individualStatementTimeout != 0
1126: && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
1127: timeoutTask = new CancelTask((StatementImpl) batchStmt);
1128: ConnectionImpl.getCancelTimer().schedule(timeoutTask,
1129: individualStatementTimeout);
1130: }
1131:
1132: int counter = 0;
1133:
1134: int numberOfBytesPerChar = 1;
1135:
1136: String connectionEncoding = locallyScopedConn.getEncoding();
1137:
1138: if (StringUtils.startsWithIgnoreCase(connectionEncoding,
1139: "utf")) {
1140: numberOfBytesPerChar = 3;
1141: } else if (CharsetMapping
1142: .isMultibyteCharset(connectionEncoding)) {
1143: numberOfBytesPerChar = 2;
1144: }
1145:
1146: int escapeAdjust = 1;
1147:
1148: if (this .doEscapeProcessing) {
1149: escapeAdjust = 2; /* We assume packet _could_ grow by this amount, as we're not
1150: sure how big statement will end up after
1151: escape processing */
1152: }
1153:
1154: SQLException sqlEx = null;
1155:
1156: int argumentSetsInBatchSoFar = 0;
1157:
1158: for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) {
1159: String nextQuery = (String) this .batchedArgs
1160: .get(commandIndex);
1161:
1162: if (((((queryBuf.length() + nextQuery.length()) * numberOfBytesPerChar) + 1 /* for semicolon */
1163: + MysqlIO.HEADER_LENGTH) * escapeAdjust) + 32 > this .connection
1164: .getMaxAllowedPacket()) {
1165: try {
1166: batchStmt.execute(queryBuf.toString());
1167: } catch (SQLException ex) {
1168: sqlEx = handleExceptionForBatch(commandIndex,
1169: argumentSetsInBatchSoFar, updateCounts,
1170: ex);
1171: }
1172:
1173: counter = processMultiCountsAndKeys(
1174: (StatementImpl) batchStmt, counter,
1175: updateCounts);
1176:
1177: queryBuf = new StringBuffer();
1178: argumentSetsInBatchSoFar = 0;
1179: }
1180:
1181: queryBuf.append(nextQuery);
1182: queryBuf.append(";");
1183: argumentSetsInBatchSoFar++;
1184: }
1185:
1186: if (queryBuf.length() > 0) {
1187: try {
1188: batchStmt.execute(queryBuf.toString());
1189: } catch (SQLException ex) {
1190: sqlEx = handleExceptionForBatch(commandIndex - 1,
1191: argumentSetsInBatchSoFar, updateCounts, ex);
1192: }
1193:
1194: counter = processMultiCountsAndKeys(
1195: (StatementImpl) batchStmt, counter,
1196: updateCounts);
1197: }
1198:
1199: if (timeoutTask != null) {
1200: if (timeoutTask.caughtWhileCancelling != null) {
1201: throw timeoutTask.caughtWhileCancelling;
1202: }
1203:
1204: timeoutTask.cancel();
1205: timeoutTask = null;
1206: }
1207:
1208: if (sqlEx != null) {
1209: throw new java.sql.BatchUpdateException(sqlEx
1210: .getMessage(), sqlEx.getSQLState(), sqlEx
1211: .getErrorCode(), updateCounts);
1212: }
1213:
1214: return (updateCounts != null) ? updateCounts : new int[0];
1215: } finally {
1216: if (timeoutTask != null) {
1217: timeoutTask.cancel();
1218: }
1219:
1220: resetCancelledState();
1221:
1222: try {
1223: if (batchStmt != null) {
1224: batchStmt.close();
1225: }
1226: } finally {
1227: if (!multiQueriesEnabled) {
1228: locallyScopedConn.getIO().disableMultiQueries();
1229: }
1230: }
1231: }
1232: }
1233:
1234: protected int processMultiCountsAndKeys(
1235: StatementImpl batchedStatement, int updateCountCounter,
1236: int[] updateCounts) throws SQLException {
1237: updateCounts[updateCountCounter++] = batchedStatement
1238: .getUpdateCount();
1239:
1240: boolean doGenKeys = this .batchedGeneratedKeys != null;
1241:
1242: long generatedKeyStart = 0;
1243: byte[][] row = null;
1244:
1245: if (doGenKeys) {
1246: generatedKeyStart = batchedStatement.getLastInsertID();
1247:
1248: row = new byte[1][];
1249: row[0] = Long.toString(generatedKeyStart++).getBytes();
1250: this .batchedGeneratedKeys.add(new ByteArrayRow(row));
1251: }
1252:
1253: while (batchedStatement.getMoreResults()
1254: || batchedStatement.getUpdateCount() != -1) {
1255: updateCounts[updateCountCounter++] = batchedStatement
1256: .getUpdateCount();
1257:
1258: if (doGenKeys) {
1259: row = new byte[1][];
1260: row[0] = Long.toString(generatedKeyStart++).getBytes();
1261: this .batchedGeneratedKeys.add(new ByteArrayRow(row));
1262: }
1263: }
1264:
1265: return updateCountCounter;
1266: }
1267:
1268: protected SQLException handleExceptionForBatch(int endOfBatchIndex,
1269: int numValuesPerBatch, int[] updateCounts, SQLException ex)
1270: throws BatchUpdateException {
1271: SQLException sqlEx;
1272:
1273: for (int j = endOfBatchIndex; j > endOfBatchIndex
1274: - numValuesPerBatch; j--) {
1275: updateCounts[j] = EXECUTE_FAILED;
1276: }
1277:
1278: if (this .continueBatchOnError
1279: && !(ex instanceof MySQLTimeoutException)
1280: && !(ex instanceof MySQLStatementCancelledException)) {
1281: sqlEx = ex;
1282: } else {
1283: int[] newUpdateCounts = new int[endOfBatchIndex];
1284: System.arraycopy(updateCounts, 0, newUpdateCounts, 0,
1285: endOfBatchIndex);
1286:
1287: throw new java.sql.BatchUpdateException(ex.getMessage(), ex
1288: .getSQLState(), ex.getErrorCode(), newUpdateCounts);
1289: }
1290:
1291: return sqlEx;
1292: }
1293:
1294: /**
1295: * Execute a SQL statement that retruns a single ResultSet
1296: *
1297: * @param sql
1298: * typically a static SQL SELECT statement
1299: *
1300: * @return a ResulSet that contains the data produced by the query
1301: *
1302: * @exception SQLException
1303: * if a database access error occurs
1304: */
1305: public java.sql.ResultSet executeQuery(String sql)
1306: throws SQLException {
1307: checkClosed();
1308:
1309: ConnectionImpl locallyScopedConn = this .connection;
1310:
1311: synchronized (locallyScopedConn.getMutex()) {
1312: resetCancelledState();
1313:
1314: checkNullOrEmptyQuery(sql);
1315:
1316: boolean doStreaming = createStreamingResultSet();
1317:
1318: // Adjust net_write_timeout to a higher value if we're
1319: // streaming result sets. More often than not, someone runs into
1320: // an issue where they blow net_write_timeout when using this
1321: // feature, and if they're willing to hold a result set open
1322: // for 30 seconds or more, one more round-trip isn't going to hurt
1323: //
1324: // This is reset by RowDataDynamic.close().
1325:
1326: if (doStreaming
1327: && this .connection
1328: .getNetTimeoutForStreamingResults() > 0) {
1329: executeSimpleNonQuery(
1330: locallyScopedConn,
1331: "SET net_write_timeout="
1332: + this .connection
1333: .getNetTimeoutForStreamingResults());
1334: }
1335:
1336: if (this .doEscapeProcessing) {
1337: Object escapedSqlResult = EscapeProcessor.escapeSQL(
1338: sql, locallyScopedConn
1339: .serverSupportsConvertFn(),
1340: this .connection);
1341:
1342: if (escapedSqlResult instanceof String) {
1343: sql = (String) escapedSqlResult;
1344: } else {
1345: sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
1346: }
1347: }
1348:
1349: char firstStatementChar = StringUtils.firstNonWsCharUc(sql,
1350: findStartOfStatement(sql));
1351:
1352: if (sql.charAt(0) == '/') {
1353: if (sql.startsWith(PING_MARKER)) {
1354: doPingInstead();
1355:
1356: return this .results;
1357: }
1358: }
1359:
1360: checkForDml(sql, firstStatementChar);
1361:
1362: if (this .results != null) {
1363: if (!locallyScopedConn
1364: .getHoldResultsOpenOverStatementClose()) {
1365: this .results.realClose(false);
1366: }
1367: }
1368:
1369: CachedResultSetMetaData cachedMetaData = null;
1370:
1371: // If there isn't a limit clause in the SQL
1372: // then limit the number of rows to return in
1373: // an efficient manner. Only do this if
1374: // setMaxRows() hasn't been used on any Statements
1375: // generated from the current Connection (saves
1376: // a query, and network traffic).
1377:
1378: if (useServerFetch()) {
1379: this .results = createResultSetUsingServerFetch(sql);
1380:
1381: return this .results;
1382: }
1383:
1384: CancelTask timeoutTask = null;
1385:
1386: String oldCatalog = null;
1387:
1388: try {
1389: if (locallyScopedConn.getEnableQueryTimeouts()
1390: && this .timeoutInMillis != 0
1391: && locallyScopedConn.versionMeetsMinimum(5, 0,
1392: 0)) {
1393: timeoutTask = new CancelTask(this );
1394: ConnectionImpl.getCancelTimer().schedule(
1395: timeoutTask, this .timeoutInMillis);
1396: }
1397:
1398: if (!locallyScopedConn.getCatalog().equals(
1399: this .currentCatalog)) {
1400: oldCatalog = locallyScopedConn.getCatalog();
1401: locallyScopedConn.setCatalog(this .currentCatalog);
1402: }
1403:
1404: //
1405: // Check if we have cached metadata for this query...
1406: //
1407:
1408: Field[] cachedFields = null;
1409:
1410: if (locallyScopedConn.getCacheResultSetMetadata()) {
1411: cachedMetaData = locallyScopedConn
1412: .getCachedMetaData(sql);
1413:
1414: if (cachedMetaData != null) {
1415: cachedFields = cachedMetaData.fields;
1416: }
1417: }
1418:
1419: if (locallyScopedConn.useMaxRows()) {
1420: // We need to execute this all together
1421: // So synchronize on the Connection's mutex (because
1422: // even queries going through there synchronize
1423: // on the connection
1424: if (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1) { //$NON-NLS-1$
1425: this .results = locallyScopedConn.execSQL(this ,
1426: sql, this .maxRows, null,
1427: this .resultSetType,
1428: this .resultSetConcurrency, doStreaming,
1429: this .currentCatalog, cachedFields);
1430: } else {
1431: if (this .maxRows <= 0) {
1432: executeSimpleNonQuery(locallyScopedConn,
1433: "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
1434: } else {
1435: executeSimpleNonQuery(locallyScopedConn,
1436: "SET OPTION SQL_SELECT_LIMIT="
1437: + this .maxRows);
1438: }
1439:
1440: this .results = locallyScopedConn.execSQL(this ,
1441: sql, -1, null, this .resultSetType,
1442: this .resultSetConcurrency, doStreaming,
1443: this .currentCatalog, cachedFields);
1444:
1445: if (oldCatalog != null) {
1446: locallyScopedConn.setCatalog(oldCatalog);
1447: }
1448: }
1449: } else {
1450: this .results = locallyScopedConn.execSQL(this , sql,
1451: -1, null, this .resultSetType,
1452: this .resultSetConcurrency, doStreaming,
1453: this .currentCatalog, cachedFields);
1454: }
1455:
1456: if (timeoutTask != null) {
1457: if (timeoutTask.caughtWhileCancelling != null) {
1458: throw timeoutTask.caughtWhileCancelling;
1459: }
1460:
1461: timeoutTask.cancel();
1462: timeoutTask = null;
1463: }
1464:
1465: synchronized (this .cancelTimeoutMutex) {
1466: if (this .wasCancelled) {
1467: SQLException cause = null;
1468:
1469: if (this .wasCancelledByTimeout) {
1470: cause = new MySQLTimeoutException();
1471: } else {
1472: cause = new MySQLStatementCancelledException();
1473: }
1474:
1475: resetCancelledState();
1476:
1477: throw cause;
1478: }
1479: }
1480: } finally {
1481: if (timeoutTask != null) {
1482: timeoutTask.cancel();
1483: }
1484:
1485: if (oldCatalog != null) {
1486: locallyScopedConn.setCatalog(oldCatalog);
1487: }
1488: }
1489:
1490: this .lastInsertId = this .results.getUpdateID();
1491:
1492: if (cachedMetaData != null) {
1493: locallyScopedConn.initializeResultsMetadataFromCache(
1494: sql, cachedMetaData, this .results);
1495: } else {
1496: if (this .connection.getCacheResultSetMetadata()) {
1497: locallyScopedConn
1498: .initializeResultsMetadataFromCache(sql,
1499: null /* will be created */,
1500: this .results);
1501: }
1502: }
1503:
1504: return this .results;
1505: }
1506: }
1507:
1508: protected void doPingInstead() throws SQLException {
1509: if (this .pingTarget != null) {
1510: this .pingTarget.doPing();
1511: } else {
1512: this .connection.ping();
1513: }
1514:
1515: ResultSetInternalMethods fakeSelectOneResultSet = generatePingResultSet();
1516: this .results = fakeSelectOneResultSet;
1517: }
1518:
1519: protected ResultSetInternalMethods generatePingResultSet()
1520: throws SQLException {
1521: Field[] fields = { new Field(null, "1", Types.BIGINT, 1) };
1522: ArrayList rows = new ArrayList();
1523: byte[] colVal = new byte[] { (byte) '1' };
1524:
1525: rows.add(new ByteArrayRow(new byte[][] { colVal }));
1526:
1527: return (ResultSetInternalMethods) DatabaseMetaData
1528: .buildResultSet(fields, rows, this .connection);
1529: }
1530:
1531: protected void executeSimpleNonQuery(ConnectionImpl c,
1532: String nonQuery) throws SQLException {
1533: c.execSQL(this , nonQuery, -1, null,
1534: ResultSet.TYPE_FORWARD_ONLY,
1535: ResultSet.CONCUR_READ_ONLY, false, this .currentCatalog,
1536: null, false).close();
1537: }
1538:
1539: /**
1540: * Execute a SQL INSERT, UPDATE or DELETE statement. In addition SQL
1541: * statements that return nothing such as SQL DDL statements can be executed
1542: * Any IDs generated for AUTO_INCREMENT fields can be retrieved by casting
1543: * this Statement to org.gjt.mm.mysql.Statement and calling the
1544: * getLastInsertID() method.
1545: *
1546: * @param sql
1547: * a SQL statement
1548: *
1549: * @return either a row count, or 0 for SQL commands
1550: *
1551: * @exception SQLException
1552: * if a database access error occurs
1553: */
1554: public int executeUpdate(String sql) throws SQLException {
1555: return executeUpdate(sql, false);
1556: }
1557:
1558: protected int executeUpdate(String sql, boolean isBatch)
1559: throws SQLException {
1560: checkClosed();
1561:
1562: ConnectionImpl locallyScopedConn = this .connection;
1563:
1564: char firstStatementChar = StringUtils.firstNonWsCharUc(sql,
1565: findStartOfStatement(sql));
1566:
1567: ResultSetInternalMethods rs = null;
1568:
1569: synchronized (locallyScopedConn.getMutex()) {
1570: resetCancelledState();
1571:
1572: checkNullOrEmptyQuery(sql);
1573:
1574: if (this .doEscapeProcessing) {
1575: Object escapedSqlResult = EscapeProcessor.escapeSQL(
1576: sql, this .connection.serverSupportsConvertFn(),
1577: this .connection);
1578:
1579: if (escapedSqlResult instanceof String) {
1580: sql = (String) escapedSqlResult;
1581: } else {
1582: sql = ((EscapeProcessorResult) escapedSqlResult).escapedSql;
1583: }
1584: }
1585:
1586: if (locallyScopedConn.isReadOnly()) {
1587: throw SQLError.createSQLException(Messages
1588: .getString("Statement.42") //$NON-NLS-1$
1589: + Messages.getString("Statement.43"), //$NON-NLS-1$
1590: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
1591: }
1592:
1593: if (StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) { //$NON-NLS-1$
1594: throw SQLError.createSQLException(Messages
1595: .getString("Statement.46"), //$NON-NLS-1$
1596: "01S03"); //$NON-NLS-1$
1597: }
1598:
1599: if (this .results != null) {
1600: if (!locallyScopedConn
1601: .getHoldResultsOpenOverStatementClose()) {
1602: this .results.realClose(false);
1603: }
1604: }
1605:
1606: // The checking and changing of catalogs
1607: // must happen in sequence, so synchronize
1608: // on the same mutex that _conn is using
1609:
1610: CancelTask timeoutTask = null;
1611:
1612: String oldCatalog = null;
1613:
1614: try {
1615: if (locallyScopedConn.getEnableQueryTimeouts()
1616: && this .timeoutInMillis != 0
1617: && locallyScopedConn.versionMeetsMinimum(5, 0,
1618: 0)) {
1619: timeoutTask = new CancelTask(this );
1620: ConnectionImpl.getCancelTimer().schedule(
1621: timeoutTask, this .timeoutInMillis);
1622: }
1623:
1624: if (!locallyScopedConn.getCatalog().equals(
1625: this .currentCatalog)) {
1626: oldCatalog = locallyScopedConn.getCatalog();
1627: locallyScopedConn.setCatalog(this .currentCatalog);
1628: }
1629:
1630: //
1631: // Only apply max_rows to selects
1632: //
1633: if (locallyScopedConn.useMaxRows()) {
1634: executeSimpleNonQuery(locallyScopedConn,
1635: "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
1636: }
1637:
1638: rs = locallyScopedConn.execSQL(this , sql, -1, null,
1639: java.sql.ResultSet.TYPE_FORWARD_ONLY,
1640: java.sql.ResultSet.CONCUR_READ_ONLY, false,
1641: this .currentCatalog,
1642: null /* force read of field info on DML */,
1643: isBatch);
1644:
1645: if (timeoutTask != null) {
1646: if (timeoutTask.caughtWhileCancelling != null) {
1647: throw timeoutTask.caughtWhileCancelling;
1648: }
1649:
1650: timeoutTask.cancel();
1651: timeoutTask = null;
1652: }
1653:
1654: synchronized (this .cancelTimeoutMutex) {
1655: if (this .wasCancelled) {
1656: SQLException cause = null;
1657:
1658: if (this .wasCancelledByTimeout) {
1659: cause = new MySQLTimeoutException();
1660: } else {
1661: cause = new MySQLStatementCancelledException();
1662: }
1663:
1664: resetCancelledState();
1665:
1666: throw cause;
1667: }
1668: }
1669: } finally {
1670: if (timeoutTask != null) {
1671: timeoutTask.cancel();
1672: }
1673:
1674: if (oldCatalog != null) {
1675: locallyScopedConn.setCatalog(oldCatalog);
1676: }
1677: }
1678: }
1679:
1680: this .results = rs;
1681:
1682: rs.setFirstCharOfQuery(firstStatementChar);
1683:
1684: this .updateCount = rs.getUpdateCount();
1685:
1686: int truncatedUpdateCount = 0;
1687:
1688: if (this .updateCount > Integer.MAX_VALUE) {
1689: truncatedUpdateCount = Integer.MAX_VALUE;
1690: } else {
1691: truncatedUpdateCount = (int) this .updateCount;
1692: }
1693:
1694: this .lastInsertId = rs.getUpdateID();
1695:
1696: return truncatedUpdateCount;
1697: }
1698:
1699: /**
1700: * @see StatementImpl#executeUpdate(String, int)
1701: */
1702: public int executeUpdate(String sql, int returnGeneratedKeys)
1703: throws SQLException {
1704: if (returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS) {
1705: checkClosed();
1706:
1707: ConnectionImpl locallyScopedConn = this .connection;
1708:
1709: synchronized (locallyScopedConn.getMutex()) {
1710: // If this is a 'REPLACE' query, we need to be able to parse
1711: // the 'info' message returned from the server to determine
1712: // the actual number of keys generated.
1713: boolean readInfoMsgState = locallyScopedConn
1714: .isReadInfoMsgEnabled();
1715: locallyScopedConn.setReadInfoMsgEnabled(true);
1716:
1717: try {
1718: return executeUpdate(sql);
1719: } finally {
1720: locallyScopedConn
1721: .setReadInfoMsgEnabled(readInfoMsgState);
1722: }
1723: }
1724: }
1725:
1726: return executeUpdate(sql);
1727: }
1728:
1729: /**
1730: * @see StatementImpl#executeUpdate(String, int[])
1731: */
1732: public int executeUpdate(String sql, int[] generatedKeyIndices)
1733: throws SQLException {
1734: if ((generatedKeyIndices != null)
1735: && (generatedKeyIndices.length > 0)) {
1736: checkClosed();
1737:
1738: ConnectionImpl locallyScopedConn = this .connection;
1739:
1740: synchronized (locallyScopedConn.getMutex()) {
1741: // If this is a 'REPLACE' query, we need to be able to parse
1742: // the 'info' message returned from the server to determine
1743: // the actual number of keys generated.
1744: boolean readInfoMsgState = locallyScopedConn
1745: .isReadInfoMsgEnabled();
1746: locallyScopedConn.setReadInfoMsgEnabled(true);
1747:
1748: try {
1749: return executeUpdate(sql);
1750: } finally {
1751: locallyScopedConn
1752: .setReadInfoMsgEnabled(readInfoMsgState);
1753: }
1754: }
1755: }
1756:
1757: return executeUpdate(sql);
1758: }
1759:
1760: /**
1761: * @see StatementImpl#executeUpdate(String, String[])
1762: */
1763: public int executeUpdate(String sql, String[] generatedKeyNames)
1764: throws SQLException {
1765: if ((generatedKeyNames != null)
1766: && (generatedKeyNames.length > 0)) {
1767: checkClosed();
1768:
1769: ConnectionImpl locallyScopedConn = this .connection;
1770:
1771: synchronized (locallyScopedConn.getMutex()) {
1772: // If this is a 'REPLACE' query, we need to be able to parse
1773: // the 'info' message returned from the server to determine
1774: // the actual number of keys generated.
1775: boolean readInfoMsgState = this .connection
1776: .isReadInfoMsgEnabled();
1777: locallyScopedConn.setReadInfoMsgEnabled(true);
1778:
1779: try {
1780: return executeUpdate(sql);
1781: } finally {
1782: locallyScopedConn
1783: .setReadInfoMsgEnabled(readInfoMsgState);
1784: }
1785: }
1786: }
1787:
1788: return executeUpdate(sql);
1789: }
1790:
1791: /**
1792: * Optimization to only use one calendar per-session, or calculate it for
1793: * each call, depending on user configuration
1794: */
1795: protected Calendar getCalendarInstanceForSessionOrNew() {
1796: if (this .connection != null) {
1797: return this .connection.getCalendarInstanceForSessionOrNew();
1798: } else {
1799: // punt, no connection around
1800: return new GregorianCalendar();
1801: }
1802: }
1803:
1804: /**
1805: * JDBC 2.0 Return the Connection that produced the Statement.
1806: *
1807: * @return the Connection that produced the Statement
1808: *
1809: * @throws SQLException
1810: * if an error occurs
1811: */
1812: public java.sql.Connection getConnection() throws SQLException {
1813: return this .connection;
1814: }
1815:
1816: /**
1817: * JDBC 2.0 Determine the fetch direction.
1818: *
1819: * @return the default fetch direction
1820: *
1821: * @exception SQLException
1822: * if a database-access error occurs
1823: */
1824: public int getFetchDirection() throws SQLException {
1825: return java.sql.ResultSet.FETCH_FORWARD;
1826: }
1827:
1828: /**
1829: * JDBC 2.0 Determine the default fetch size.
1830: *
1831: * @return the number of rows to fetch at a time
1832: *
1833: * @throws SQLException
1834: * if an error occurs
1835: */
1836: public int getFetchSize() throws SQLException {
1837: return this .fetchSize;
1838: }
1839:
1840: /**
1841: * DOCUMENT ME!
1842: *
1843: * @return DOCUMENT ME!
1844: *
1845: * @throws SQLException
1846: * DOCUMENT ME!
1847: */
1848: public synchronized java.sql.ResultSet getGeneratedKeys()
1849: throws SQLException {
1850: if (this .batchedGeneratedKeys == null) {
1851: return getGeneratedKeysInternal();
1852: }
1853:
1854: Field[] fields = new Field[1];
1855: fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17); //$NON-NLS-1$ //$NON-NLS-2$
1856: fields[0].setConnection(this .connection);
1857:
1858: return com.mysql.jdbc.ResultSetImpl.getInstance(
1859: this .currentCatalog, fields, new RowDataStatic(
1860: this .batchedGeneratedKeys), this .connection,
1861: this , false);
1862: }
1863:
1864: /*
1865: * Needed because there's no concept of super.super to get to this
1866: * implementation from ServerPreparedStatement when dealing with batched
1867: * updates.
1868: */
1869: protected java.sql.ResultSet getGeneratedKeysInternal()
1870: throws SQLException {
1871: Field[] fields = new Field[1];
1872: fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17); //$NON-NLS-1$ //$NON-NLS-2$
1873: fields[0].setConnection(this .connection);
1874:
1875: ArrayList rowSet = new ArrayList();
1876:
1877: long beginAt = getLastInsertID();
1878: int numKeys = getUpdateCount();
1879:
1880: if (this .results != null) {
1881: String serverInfo = this .results.getServerInfo();
1882:
1883: //
1884: // Only parse server info messages for 'REPLACE'
1885: // queries
1886: //
1887: if ((numKeys > 0)
1888: && (this .results.getFirstCharOfQuery() == 'R')
1889: && (serverInfo != null)
1890: && (serverInfo.length() > 0)) {
1891: numKeys = getRecordCountFromInfo(serverInfo);
1892: }
1893:
1894: if ((beginAt > 0) && (numKeys > 0)) {
1895: for (int i = 0; i < numKeys; i++) {
1896: byte[][] row = new byte[1][];
1897: row[0] = Long.toString(beginAt++).getBytes();
1898: rowSet.add(new ByteArrayRow(row));
1899: }
1900: }
1901: }
1902:
1903: return com.mysql.jdbc.ResultSetImpl.getInstance(
1904: this .currentCatalog, fields, new RowDataStatic(rowSet),
1905: this .connection, this , false);
1906: }
1907:
1908: /**
1909: * Returns the id used when profiling
1910: *
1911: * @return the id used when profiling.
1912: */
1913: protected int getId() {
1914: return this .statementId;
1915: }
1916:
1917: /**
1918: * getLastInsertID returns the value of the auto_incremented key after an
1919: * executeQuery() or excute() call.
1920: *
1921: * <p>
1922: * This gets around the un-threadsafe behavior of "select LAST_INSERT_ID()"
1923: * which is tied to the Connection that created this Statement, and
1924: * therefore could have had many INSERTS performed before one gets a chance
1925: * to call "select LAST_INSERT_ID()".
1926: * </p>
1927: *
1928: * @return the last update ID.
1929: */
1930: public long getLastInsertID() {
1931: return this .lastInsertId;
1932: }
1933:
1934: /**
1935: * getLongUpdateCount returns the current result as an update count, if the
1936: * result is a ResultSet or there are no more results, -1 is returned. It
1937: * should only be called once per result.
1938: *
1939: * <p>
1940: * This method returns longs as MySQL server versions newer than 3.22.4
1941: * return 64-bit values for update counts
1942: * </p>
1943: *
1944: * @return the current update count.
1945: */
1946: public long getLongUpdateCount() {
1947: if (this .results == null) {
1948: return -1;
1949: }
1950:
1951: if (this .results.reallyResult()) {
1952: return -1;
1953: }
1954:
1955: return this .updateCount;
1956: }
1957:
1958: /**
1959: * The maxFieldSize limit (in bytes) is the maximum amount of data returned
1960: * for any column value; it only applies to BINARY, VARBINARY,
1961: * LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR columns. If the limit is
1962: * exceeded, the excess data is silently discarded.
1963: *
1964: * @return the current max column size limit; zero means unlimited
1965: *
1966: * @exception SQLException
1967: * if a database access error occurs
1968: */
1969: public int getMaxFieldSize() throws SQLException {
1970: return this .maxFieldSize;
1971: }
1972:
1973: /**
1974: * The maxRows limit is set to limit the number of rows that any ResultSet
1975: * can contain. If the limit is exceeded, the excess rows are silently
1976: * dropped.
1977: *
1978: * @return the current maximum row limit; zero means unlimited
1979: *
1980: * @exception SQLException
1981: * if a database access error occurs
1982: */
1983: public int getMaxRows() throws SQLException {
1984: if (this .maxRows <= 0) {
1985: return 0;
1986: }
1987:
1988: return this .maxRows;
1989: }
1990:
1991: /**
1992: * getMoreResults moves to a Statement's next result. If it returns true,
1993: * this result is a ResulSet.
1994: *
1995: * @return true if the next ResultSet is valid
1996: *
1997: * @exception SQLException
1998: * if a database access error occurs
1999: */
2000: public boolean getMoreResults() throws SQLException {
2001: return getMoreResults(CLOSE_CURRENT_RESULT);
2002: }
2003:
2004: /**
2005: * @see StatementImpl#getMoreResults(int)
2006: */
2007: public boolean getMoreResults(int current) throws SQLException {
2008:
2009: if (this .results == null) {
2010: return false;
2011: }
2012:
2013: ResultSetInternalMethods nextResultSet = this .results
2014: .getNextResultSet();
2015:
2016: switch (current) {
2017: case java.sql.Statement.CLOSE_CURRENT_RESULT:
2018:
2019: if (this .results != null) {
2020: this .results.close();
2021: this .results.clearNextResult();
2022: }
2023:
2024: break;
2025:
2026: case java.sql.Statement.CLOSE_ALL_RESULTS:
2027:
2028: if (this .results != null) {
2029: this .results.close();
2030: this .results.clearNextResult();
2031: }
2032:
2033: closeAllOpenResults();
2034:
2035: break;
2036:
2037: case java.sql.Statement.KEEP_CURRENT_RESULT:
2038: if (!this .connection.getDontTrackOpenResources()) {
2039: this .openResults.add(this .results);
2040: }
2041:
2042: this .results.clearNextResult(); // nobody besides us should
2043: // ever need this value...
2044: break;
2045:
2046: default:
2047: throw SQLError.createSQLException(Messages
2048: .getString("Statement.19"), //$NON-NLS-1$
2049: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
2050: }
2051:
2052: this .results = nextResultSet;
2053:
2054: if (this .results == null) {
2055: this .updateCount = -1;
2056: this .lastInsertId = -1;
2057: } else if (this .results.reallyResult()) {
2058: this .updateCount = -1;
2059: this .lastInsertId = -1;
2060: } else {
2061: this .updateCount = this .results.getUpdateCount();
2062: this .lastInsertId = this .results.getUpdateID();
2063: }
2064:
2065: return ((this .results != null) && this .results.reallyResult()) ? true
2066: : false;
2067: }
2068:
2069: /**
2070: * The queryTimeout limit is the number of seconds the driver will wait for
2071: * a Statement to execute. If the limit is exceeded, a SQLException is
2072: * thrown.
2073: *
2074: * @return the current query timeout limit in seconds; 0 = unlimited
2075: *
2076: * @exception SQLException
2077: * if a database access error occurs
2078: */
2079: public int getQueryTimeout() throws SQLException {
2080: return this .timeoutInMillis / 1000;
2081: }
2082:
2083: /**
2084: * Parses actual record count from 'info' message
2085: *
2086: * @param serverInfo
2087: * DOCUMENT ME!
2088: *
2089: * @return DOCUMENT ME!
2090: */
2091: private int getRecordCountFromInfo(String serverInfo) {
2092: StringBuffer recordsBuf = new StringBuffer();
2093: int recordsCount = 0;
2094: int duplicatesCount = 0;
2095:
2096: char c = (char) 0;
2097:
2098: int length = serverInfo.length();
2099: int i = 0;
2100:
2101: for (; i < length; i++) {
2102: c = serverInfo.charAt(i);
2103:
2104: if (Character.isDigit(c)) {
2105: break;
2106: }
2107: }
2108:
2109: recordsBuf.append(c);
2110: i++;
2111:
2112: for (; i < length; i++) {
2113: c = serverInfo.charAt(i);
2114:
2115: if (!Character.isDigit(c)) {
2116: break;
2117: }
2118:
2119: recordsBuf.append(c);
2120: }
2121:
2122: recordsCount = Integer.parseInt(recordsBuf.toString());
2123:
2124: StringBuffer duplicatesBuf = new StringBuffer();
2125:
2126: for (; i < length; i++) {
2127: c = serverInfo.charAt(i);
2128:
2129: if (Character.isDigit(c)) {
2130: break;
2131: }
2132: }
2133:
2134: duplicatesBuf.append(c);
2135: i++;
2136:
2137: for (; i < length; i++) {
2138: c = serverInfo.charAt(i);
2139:
2140: if (!Character.isDigit(c)) {
2141: break;
2142: }
2143:
2144: duplicatesBuf.append(c);
2145: }
2146:
2147: duplicatesCount = Integer.parseInt(duplicatesBuf.toString());
2148:
2149: return recordsCount - duplicatesCount;
2150: }
2151:
2152: /**
2153: * getResultSet returns the current result as a ResultSet. It should only be
2154: * called once per result.
2155: *
2156: * @return the current result set; null if there are no more
2157: *
2158: * @exception SQLException
2159: * if a database access error occurs (why?)
2160: */
2161: public java.sql.ResultSet getResultSet() throws SQLException {
2162: return ((this .results != null) && this .results.reallyResult()) ? (java.sql.ResultSet) this .results
2163: : null;
2164: }
2165:
2166: /**
2167: * JDBC 2.0 Determine the result set concurrency.
2168: *
2169: * @return CONCUR_UPDATABLE or CONCUR_READONLY
2170: *
2171: * @throws SQLException
2172: * if an error occurs
2173: */
2174: public int getResultSetConcurrency() throws SQLException {
2175: return this .resultSetConcurrency;
2176: }
2177:
2178: /**
2179: * @see StatementImpl#getResultSetHoldability()
2180: */
2181: public int getResultSetHoldability() throws SQLException {
2182: return java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
2183: }
2184:
2185: protected ResultSetInternalMethods getResultSetInternal() {
2186: return this .results;
2187: }
2188:
2189: /**
2190: * JDBC 2.0 Determine the result set type.
2191: *
2192: * @return the ResultSet type (SCROLL_SENSITIVE or SCROLL_INSENSITIVE)
2193: *
2194: * @throws SQLException
2195: * if an error occurs.
2196: */
2197: public int getResultSetType() throws SQLException {
2198: return this .resultSetType;
2199: }
2200:
2201: /**
2202: * getUpdateCount returns the current result as an update count, if the
2203: * result is a ResultSet or there are no more results, -1 is returned. It
2204: * should only be called once per result.
2205: *
2206: * @return the current result as an update count.
2207: *
2208: * @exception SQLException
2209: * if a database access error occurs
2210: */
2211: public int getUpdateCount() throws SQLException {
2212: if (this .results == null) {
2213: return -1;
2214: }
2215:
2216: if (this .results.reallyResult()) {
2217: return -1;
2218: }
2219:
2220: int truncatedUpdateCount = 0;
2221:
2222: if (this .results.getUpdateCount() > Integer.MAX_VALUE) {
2223: truncatedUpdateCount = Integer.MAX_VALUE;
2224: } else {
2225: truncatedUpdateCount = (int) this .results.getUpdateCount();
2226: }
2227:
2228: return truncatedUpdateCount;
2229: }
2230:
2231: /**
2232: * The first warning reported by calls on this Statement is returned. A
2233: * Statement's execute methods clear its java.sql.SQLWarning chain.
2234: * Subsequent Statement warnings will be chained to this
2235: * java.sql.SQLWarning.
2236: *
2237: * <p>
2238: * The Warning chain is automatically cleared each time a statement is
2239: * (re)executed.
2240: * </p>
2241: *
2242: * <p>
2243: * <B>Note:</B> If you are processing a ResultSet then any warnings
2244: * associated with ResultSet reads will be chained on the ResultSet object.
2245: * </p>
2246: *
2247: * @return the first java.sql.SQLWarning or null
2248: *
2249: * @exception SQLException
2250: * if a database access error occurs
2251: */
2252: public java.sql.SQLWarning getWarnings() throws SQLException {
2253: checkClosed();
2254:
2255: if (this .connection != null && !this .connection.isClosed()
2256: && this .connection.versionMeetsMinimum(4, 1, 0)) {
2257: SQLWarning pendingWarningsFromServer = SQLError
2258: .convertShowWarningsToSQLWarnings(this .connection);
2259:
2260: if (this .warningChain != null) {
2261: this .warningChain
2262: .setNextWarning(pendingWarningsFromServer);
2263: } else {
2264: this .warningChain = pendingWarningsFromServer;
2265: }
2266:
2267: return this .warningChain;
2268: }
2269:
2270: return this .warningChain;
2271: }
2272:
2273: /**
2274: * Closes this statement, and frees resources.
2275: *
2276: * @param calledExplicitly
2277: * was this called from close()?
2278: *
2279: * @throws SQLException
2280: * if an error occurs
2281: */
2282: protected void realClose(boolean calledExplicitly,
2283: boolean closeOpenResults) throws SQLException {
2284: if (this .isClosed) {
2285: return;
2286: }
2287:
2288: if (this .useUsageAdvisor) {
2289: if (!calledExplicitly) {
2290: String message = Messages.getString("Statement.63") //$NON-NLS-1$
2291: + Messages.getString("Statement.64"); //$NON-NLS-1$
2292:
2293: this .eventSink.consumeEvent(new ProfilerEvent(
2294: ProfilerEvent.TYPE_WARN,
2295: "", //$NON-NLS-1$
2296: this .currentCatalog, this .connectionId, this
2297: .getId(), -1, System
2298: .currentTimeMillis(), 0,
2299: Constants.MILLIS_I18N, null,
2300: this .pointOfOrigin, message));
2301: }
2302: }
2303:
2304: if (this .results != null) {
2305: if (closeOpenResults) {
2306: closeOpenResults = !this .holdResultsOpenOverClose;
2307: }
2308:
2309: if (closeOpenResults
2310: && this .connection != null
2311: && !this .connection
2312: .getHoldResultsOpenOverStatementClose()) {
2313: try {
2314: this .results.close();
2315: } catch (Exception ex) {
2316: ;
2317: }
2318:
2319: this .closeAllOpenResults();
2320: }
2321: }
2322:
2323: if (this .connection != null) {
2324: if (this .maxRowsChanged) {
2325: this .connection.unsetMaxRows(this );
2326: }
2327:
2328: if (!this .connection.getDontTrackOpenResources()) {
2329: this .connection.unregisterStatement(this );
2330: }
2331: }
2332:
2333: this .isClosed = true;
2334:
2335: this .results = null;
2336: this .connection = null;
2337: this .warningChain = null;
2338: this .openResults = null;
2339: this .batchedGeneratedKeys = null;
2340: this .cancelTimeoutMutex = null;
2341: this .localInfileInputStream = null;
2342: this .pingTarget = null;
2343: }
2344:
2345: /**
2346: * setCursorName defines the SQL cursor name that will be used by subsequent
2347: * execute methods. This name can then be used in SQL positioned
2348: * update/delete statements to identify the current row in the ResultSet
2349: * generated by this statement. If a database doesn't support positioned
2350: * update/delete, this method is a no-op.
2351: *
2352: * <p>
2353: * <b>Note:</b> This MySQL driver does not support cursors.
2354: * </p>
2355: *
2356: * @param name
2357: * the new cursor name
2358: *
2359: * @exception SQLException
2360: * if a database access error occurs
2361: */
2362: public void setCursorName(String name) throws SQLException {
2363: // No-op
2364: }
2365:
2366: /**
2367: * If escape scanning is on (the default), the driver will do escape
2368: * substitution before sending the SQL to the database.
2369: *
2370: * @param enable
2371: * true to enable; false to disable
2372: *
2373: * @exception SQLException
2374: * if a database access error occurs
2375: */
2376: public void setEscapeProcessing(boolean enable) throws SQLException {
2377: this .doEscapeProcessing = enable;
2378: }
2379:
2380: /**
2381: * JDBC 2.0 Give a hint as to the direction in which the rows in a result
2382: * set will be processed. The hint applies only to result sets created using
2383: * this Statement object. The default value is ResultSet.FETCH_FORWARD.
2384: *
2385: * @param direction
2386: * the initial direction for processing rows
2387: *
2388: * @exception SQLException
2389: * if a database-access error occurs or direction is not one
2390: * of ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or
2391: * ResultSet.FETCH_UNKNOWN
2392: */
2393: public void setFetchDirection(int direction) throws SQLException {
2394: switch (direction) {
2395: case java.sql.ResultSet.FETCH_FORWARD:
2396: case java.sql.ResultSet.FETCH_REVERSE:
2397: case java.sql.ResultSet.FETCH_UNKNOWN:
2398: break;
2399:
2400: default:
2401: throw SQLError.createSQLException(Messages
2402: .getString("Statement.5"), //$NON-NLS-1$
2403: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
2404: }
2405: }
2406:
2407: /**
2408: * JDBC 2.0 Give the JDBC driver a hint as to the number of rows that should
2409: * be fetched from the database when more rows are needed. The number of
2410: * rows specified only affects result sets created using this statement. If
2411: * the value specified is zero, then the hint is ignored. The default value
2412: * is zero.
2413: *
2414: * @param rows
2415: * the number of rows to fetch
2416: *
2417: * @exception SQLException
2418: * if a database-access error occurs, or the condition 0
2419: * <= rows <= this.getMaxRows() is not satisfied.
2420: */
2421: public void setFetchSize(int rows) throws SQLException {
2422: if (((rows < 0) && (rows != Integer.MIN_VALUE))
2423: || ((this .maxRows != 0) && (this .maxRows != -1) && (rows > this
2424: .getMaxRows()))) {
2425: throw SQLError.createSQLException(Messages
2426: .getString("Statement.7"), //$NON-NLS-1$
2427: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$ //$NON-NLS-2$
2428: }
2429:
2430: this .fetchSize = rows;
2431: }
2432:
2433: protected void setHoldResultsOpenOverClose(
2434: boolean holdResultsOpenOverClose) {
2435: this .holdResultsOpenOverClose = holdResultsOpenOverClose;
2436: }
2437:
2438: /**
2439: * Sets the maxFieldSize
2440: *
2441: * @param max
2442: * the new max column size limit; zero means unlimited
2443: *
2444: * @exception SQLException
2445: * if size exceeds buffer size
2446: */
2447: public void setMaxFieldSize(int max) throws SQLException {
2448: if (max < 0) {
2449: throw SQLError.createSQLException(Messages
2450: .getString("Statement.11"), //$NON-NLS-1$
2451: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
2452: }
2453:
2454: int maxBuf = (this .connection != null) ? this .connection
2455: .getMaxAllowedPacket() : MysqlIO.getMaxBuf();
2456:
2457: if (max > maxBuf) {
2458: throw SQLError.createSQLException(Messages.getString(
2459: "Statement.13", //$NON-NLS-1$
2460: new Object[] { Constants.longValueOf(maxBuf) }), //$NON-NLS-1$
2461: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
2462: }
2463:
2464: this .maxFieldSize = max;
2465: }
2466:
2467: /**
2468: * Set the maximum number of rows
2469: *
2470: * @param max
2471: * the new max rows limit; zero means unlimited
2472: *
2473: * @exception SQLException
2474: * if a database access error occurs
2475: *
2476: * @see getMaxRows
2477: */
2478: public void setMaxRows(int max) throws SQLException {
2479: if ((max > MysqlDefs.MAX_ROWS) || (max < 0)) {
2480: throw SQLError
2481: .createSQLException(
2482: Messages.getString("Statement.15") + max //$NON-NLS-1$
2483: + " > " //$NON-NLS-1$ //$NON-NLS-2$
2484: + MysqlDefs.MAX_ROWS + ".", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$ //$NON-NLS-2$
2485: }
2486:
2487: if (max == 0) {
2488: max = -1;
2489: }
2490:
2491: this .maxRows = max;
2492: this .maxRowsChanged = true;
2493:
2494: if (this .maxRows == -1) {
2495: this .connection.unsetMaxRows(this );
2496: this .maxRowsChanged = false;
2497: } else {
2498: // Most people don't use setMaxRows()
2499: // so don't penalize them
2500: // with the extra query it takes
2501: // to do it efficiently unless we need
2502: // to.
2503: this .connection.maxRowsChanged(this );
2504: }
2505: }
2506:
2507: /**
2508: * Sets the queryTimeout limit
2509: *
2510: * @param seconds -
2511: * the new query timeout limit in seconds
2512: *
2513: * @exception SQLException
2514: * if a database access error occurs
2515: */
2516: public void setQueryTimeout(int seconds) throws SQLException {
2517: if (seconds < 0) {
2518: throw SQLError.createSQLException(Messages
2519: .getString("Statement.21"), //$NON-NLS-1$
2520: SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$
2521: }
2522:
2523: this .timeoutInMillis = seconds * 1000;
2524: }
2525:
2526: /**
2527: * Sets the concurrency for result sets generated by this statement
2528: *
2529: * @param concurrencyFlag
2530: * DOCUMENT ME!
2531: */
2532: void setResultSetConcurrency(int concurrencyFlag) {
2533: this .resultSetConcurrency = concurrencyFlag;
2534: }
2535:
2536: /**
2537: * Sets the result set type for result sets generated by this statement
2538: *
2539: * @param typeFlag
2540: * DOCUMENT ME!
2541: */
2542: void setResultSetType(int typeFlag) {
2543: this .resultSetType = typeFlag;
2544: }
2545:
2546: protected void getBatchedGeneratedKeys(
2547: java.sql.Statement batchedStatement) throws SQLException {
2548: if (this .retrieveGeneratedKeys) {
2549: java.sql.ResultSet rs = null;
2550:
2551: try {
2552: rs = batchedStatement.getGeneratedKeys();
2553:
2554: while (rs.next()) {
2555: this .batchedGeneratedKeys.add(new ByteArrayRow(
2556: new byte[][] { rs.getBytes(1) }));
2557: }
2558: } finally {
2559: if (rs != null) {
2560: rs.close();
2561: }
2562: }
2563: }
2564: }
2565:
2566: protected void getBatchedGeneratedKeys() throws SQLException {
2567: if (this .retrieveGeneratedKeys) {
2568: java.sql.ResultSet rs = null;
2569:
2570: try {
2571: rs = getGeneratedKeysInternal();
2572:
2573: while (rs.next()) {
2574: this .batchedGeneratedKeys.add(new ByteArrayRow(
2575: new byte[][] { rs.getBytes(1) }));
2576: }
2577: } finally {
2578: if (rs != null) {
2579: rs.close();
2580: }
2581: }
2582: }
2583: }
2584:
2585: /**
2586: * @return
2587: */
2588: private boolean useServerFetch() throws SQLException {
2589:
2590: return this .connection.isCursorFetchEnabled()
2591: && this .fetchSize > 0
2592: && this .resultSetConcurrency == ResultSet.CONCUR_READ_ONLY
2593: && this .resultSetType == ResultSet.TYPE_FORWARD_ONLY;
2594: }
2595:
2596: public synchronized boolean isClosed() throws SQLException {
2597: return this .isClosed;
2598: }
2599:
2600: private boolean isPoolable = true;
2601:
2602: public boolean isPoolable() throws SQLException {
2603: return this .isPoolable;
2604: }
2605:
2606: public void setPoolable(boolean poolable) throws SQLException {
2607: this .isPoolable = poolable;
2608: }
2609:
2610: /**
2611: * Returns true if this either implements the interface argument or is directly or indirectly a wrapper
2612: * for an object that does. Returns false otherwise. If this implements the interface then return true,
2613: * else if this is a wrapper then return the result of recursively calling <code>isWrapperFor</code> on the wrapped
2614: * object. If this does not implement the interface and is not a wrapper, return false.
2615: * This method should be implemented as a low-cost operation compared to <code>unwrap</code> so that
2616: * callers can use this method to avoid expensive <code>unwrap</code> calls that may fail. If this method
2617: * returns true then calling <code>unwrap</code> with the same argument should succeed.
2618: *
2619: * @param interfaces a Class defining an interface.
2620: * @return true if this implements the interface or directly or indirectly wraps an object that does.
2621: * @throws java.sql.SQLException if an error occurs while determining whether this is a wrapper
2622: * for an object with the given interface.
2623: * @since 1.6
2624: */
2625: public boolean isWrapperFor(Class iface) throws SQLException {
2626: checkClosed();
2627:
2628: // This works for classes that aren't actually wrapping
2629: // anything
2630: return iface.isInstance(this );
2631: }
2632:
2633: /**
2634: * Returns an object that implements the given interface to allow access to non-standard methods,
2635: * or standard methods not exposed by the proxy.
2636: * The result may be either the object found to implement the interface or a proxy for that object.
2637: * If the receiver implements the interface then that is the object. If the receiver is a wrapper
2638: * and the wrapped object implements the interface then that is the object. Otherwise the object is
2639: * the result of calling <code>unwrap</code> recursively on the wrapped object. If the receiver is not a
2640: * wrapper and does not implement the interface, then an <code>SQLException</code> is thrown.
2641: *
2642: * @param iface A Class defining an interface that the result must implement.
2643: * @return an object that implements the interface. May be a proxy for the actual implementing object.
2644: * @throws java.sql.SQLException If no object found that implements the interface
2645: * @since 1.6
2646: */
2647: public Object unwrap(Class iface) throws java.sql.SQLException {
2648: try {
2649: // This works for classes that aren't actually wrapping
2650: // anything
2651: return Util.cast(iface, this );
2652: } catch (ClassCastException cce) {
2653: throw SQLError.createSQLException("Unable to unwrap to "
2654: + iface.toString(),
2655: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
2656: }
2657: }
2658:
2659: protected int findStartOfStatement(String sql) {
2660: int statementStartPos = 0;
2661:
2662: if (StringUtils.startsWithIgnoreCaseAndWs(sql, "/*")) {
2663: statementStartPos = sql.indexOf("*/");
2664:
2665: if (statementStartPos == -1) {
2666: statementStartPos = 0;
2667: } else {
2668: statementStartPos += 2;
2669: }
2670: } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "--")
2671: || StringUtils.startsWithIgnoreCaseAndWs(sql, "#")) {
2672: statementStartPos = sql.indexOf('\n');
2673:
2674: if (statementStartPos == -1) {
2675: statementStartPos = sql.indexOf('\r');
2676:
2677: if (statementStartPos == -1) {
2678: statementStartPos = 0;
2679: }
2680: }
2681: }
2682:
2683: return statementStartPos;
2684: }
2685:
2686: private InputStream localInfileInputStream;
2687:
2688: public synchronized InputStream getLocalInfileInputStream() {
2689: return this .localInfileInputStream;
2690: }
2691:
2692: public synchronized void setLocalInfileInputStream(
2693: InputStream stream) {
2694: this .localInfileInputStream = stream;
2695: }
2696:
2697: public synchronized void setPingTarget(PingTarget pingTarget) {
2698: this.pingTarget = pingTarget;
2699: }
2700: }
|