You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1000 lines
33 KiB

  1. // Copyright ?2004, 2018 Oracle and/or its affiliates. All rights reserved.
  2. //
  3. // MySQL Connector/NET is licensed under the terms of the GPLv2
  4. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  5. // MySQL Connectors. There are special exceptions to the terms and
  6. // conditions of the GPLv2 as it is applied to this software, see the
  7. // FLOSS License Exception
  8. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  9. //
  10. // This program is free software; you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published
  12. // by the Free Software Foundation; version 2 of the License.
  13. //
  14. // This program is distributed in the hope that it will be useful, but
  15. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  17. // for more details.
  18. //
  19. // You should have received a copy of the GNU General Public License along
  20. // with this program; if not, write to the Free Software Foundation, Inc.,
  21. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  22. #if NET40 || NET461
  23. using System;
  24. using System.IO;
  25. using System.Collections;
  26. using System.Text;
  27. #if !RT
  28. using System.Data;
  29. using System.Data.Common;
  30. #endif
  31. using Externals.MySql.Data.Common;
  32. using System.ComponentModel;
  33. using System.Threading;
  34. using System.Diagnostics;
  35. using System.Globalization;
  36. using System.Collections.Generic;
  37. using Externals.MySql.Data.MySqlClient.Properties;
  38. using Externals.MySql.Data.MySqlClient.Replication;
  39. namespace Externals.MySql.Data.MySqlClient
  40. {
  41. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ClassSummary/*'/>
  42. internal sealed partial class MySqlCommand : ICloneable, IDisposable
  43. {
  44. MySqlConnection connection;
  45. MySqlTransaction curTransaction;
  46. string cmdText;
  47. CommandType cmdType;
  48. long updatedRowCount;
  49. MySqlParameterCollection parameters;
  50. private IAsyncResult asyncResult;
  51. internal Int64 lastInsertedId;
  52. private PreparableStatement statement;
  53. private int commandTimeout;
  54. private bool canceled;
  55. private bool resetSqlSelect;
  56. List<MySqlCommand> batch;
  57. private string batchableCommandText;
  58. CommandTimer commandTimer;
  59. private bool useDefaultTimeout;
  60. private bool shouldCache;
  61. private int cacheAge;
  62. private bool internallyCreated;
  63. private static List<string> keywords = null;
  64. private bool disposed = false;
  65. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ctor1/*'/>
  66. public MySqlCommand()
  67. {
  68. cmdType = CommandType.Text;
  69. parameters = new MySqlParameterCollection(this);
  70. cmdText = String.Empty;
  71. useDefaultTimeout = true;
  72. Constructor();
  73. }
  74. partial void Constructor();
  75. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ctor2/*'/>
  76. public MySqlCommand(string cmdText)
  77. : this()
  78. {
  79. CommandText = cmdText;
  80. }
  81. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ctor3/*'/>
  82. public MySqlCommand(string cmdText, MySqlConnection connection)
  83. : this(cmdText)
  84. {
  85. Connection = connection;
  86. }
  87. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ctor4/*'/>
  88. public MySqlCommand(string cmdText, MySqlConnection connection,
  89. MySqlTransaction transaction)
  90. :
  91. this(cmdText, connection)
  92. {
  93. curTransaction = transaction;
  94. }
  95. #region Destructor
  96. ~MySqlCommand()
  97. {
  98. Dispose(false);
  99. }
  100. #endregion
  101. #region Properties
  102. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/LastInseredId/*'/>
  103. [Browsable(false)]
  104. public Int64 LastInsertedId
  105. {
  106. get { return lastInsertedId; }
  107. }
  108. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/CommandText/*'/>
  109. [Category("Data")]
  110. [Description("Command text to execute")]
  111. [Editor("Externals.MySql.Data.Common.Design.SqlCommandTextEditor,MySqlClient.Design", typeof(System.Drawing.Design.UITypeEditor))]
  112. public override string CommandText
  113. {
  114. get { return cmdText; }
  115. set
  116. {
  117. cmdText = value ?? string.Empty;
  118. statement = null;
  119. batchableCommandText = null;
  120. if (cmdText != null && cmdText.EndsWith("DEFAULT VALUES", StringComparison.OrdinalIgnoreCase))
  121. {
  122. cmdText = cmdText.Substring(0, cmdText.Length - 14);
  123. cmdText = cmdText + "() VALUES ()";
  124. }
  125. }
  126. }
  127. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/CommandTimeout/*'/>
  128. [Category("Misc")]
  129. [Description("Time to wait for command to execute")]
  130. [DefaultValue(30)]
  131. public override int CommandTimeout
  132. {
  133. get { return useDefaultTimeout ? 30 : commandTimeout; }
  134. set
  135. {
  136. if (commandTimeout < 0)
  137. Throw(new ArgumentException("Command timeout must not be negative"));
  138. // Timeout in milliseconds should not exceed maximum for 32 bit
  139. // signed integer (~24 days), because underlying driver (and streams)
  140. // use milliseconds expressed ints for timeout values.
  141. // Hence, truncate the value.
  142. int timeout = Math.Min(value, Int32.MaxValue / 1000);
  143. if (timeout != value)
  144. {
  145. MySqlTrace.LogWarning(connection.ServerThread,
  146. "Command timeout value too large ("
  147. + value + " seconds). Changed to max. possible value ("
  148. + timeout + " seconds)");
  149. }
  150. commandTimeout = timeout;
  151. useDefaultTimeout = false;
  152. }
  153. }
  154. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/CommandType/*'/>
  155. [Category("Data")]
  156. public override CommandType CommandType
  157. {
  158. get { return cmdType; }
  159. set { cmdType = value; }
  160. }
  161. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/IsPrepared/*'/>
  162. [Browsable(false)]
  163. public bool IsPrepared
  164. {
  165. get { return statement != null && statement.IsPrepared; }
  166. }
  167. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/Connection/*'/>
  168. [Category("Behavior")]
  169. [Description("Connection used by the command")]
  170. public new MySqlConnection Connection
  171. {
  172. get { return connection; }
  173. set
  174. {
  175. /*
  176. * The connection is associated with the transaction
  177. * so set the transaction object to return a null reference if the connection
  178. * is reset.
  179. */
  180. if (connection != value)
  181. Transaction = null;
  182. connection = value;
  183. // if the user has not already set the command timeout, then
  184. // take the default from the connection
  185. if (connection != null)
  186. {
  187. if (useDefaultTimeout)
  188. {
  189. commandTimeout = (int)connection.Settings.DefaultCommandTimeout;
  190. useDefaultTimeout = false;
  191. }
  192. EnableCaching = connection.Settings.TableCaching;
  193. CacheAge = connection.Settings.DefaultTableCacheAge;
  194. }
  195. }
  196. }
  197. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/Parameters/*'/>
  198. [Category("Data")]
  199. [Description("The parameters collection")]
  200. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  201. public new MySqlParameterCollection Parameters
  202. {
  203. get { return parameters; }
  204. }
  205. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/Transaction/*'/>
  206. [Browsable(false)]
  207. public new MySqlTransaction Transaction
  208. {
  209. get { return curTransaction; }
  210. set { curTransaction = value; }
  211. }
  212. public bool EnableCaching
  213. {
  214. get { return shouldCache; }
  215. set { shouldCache = value; }
  216. }
  217. public int CacheAge
  218. {
  219. get { return cacheAge; }
  220. set { cacheAge = value; }
  221. }
  222. internal List<MySqlCommand> Batch
  223. {
  224. get { return batch; }
  225. }
  226. internal bool Canceled
  227. {
  228. get { return canceled; }
  229. }
  230. internal string BatchableCommandText
  231. {
  232. get { return batchableCommandText; }
  233. }
  234. internal bool InternallyCreated
  235. {
  236. get { return internallyCreated; }
  237. set { internallyCreated = value; }
  238. }
  239. #endregion
  240. #region Methods
  241. /// <summary>
  242. /// Attempts to cancel the execution of a currently active command
  243. /// </summary>
  244. /// <remarks>
  245. /// Cancelling a currently active query only works with MySQL versions 5.0.0 and higher.
  246. /// </remarks>
  247. public override void Cancel()
  248. {
  249. connection.CancelQuery(connection.ConnectionTimeout);
  250. canceled = true;
  251. }
  252. /// <summary>
  253. /// Creates a new instance of a <see cref="MySqlParameter"/> object.
  254. /// </summary>
  255. /// <remarks>
  256. /// This method is a strongly-typed version of <see cref="IDbCommand.CreateParameter"/>.
  257. /// </remarks>
  258. /// <returns>A <see cref="MySqlParameter"/> object.</returns>
  259. ///
  260. public new MySqlParameter CreateParameter()
  261. {
  262. return (MySqlParameter)CreateDbParameter();
  263. }
  264. /// <summary>
  265. /// Check the connection to make sure
  266. /// - it is open
  267. /// - it is not currently being used by a reader
  268. /// - and we have the right version of MySQL for the requested command type
  269. /// </summary>
  270. private void CheckState()
  271. {
  272. // There must be a valid and open connection.
  273. if (connection == null)
  274. Throw(new InvalidOperationException("Connection must be valid and open."));
  275. if (connection.State != ConnectionState.Open && !connection.SoftClosed)
  276. Throw(new InvalidOperationException("Connection must be valid and open."));
  277. // Data readers have to be closed first
  278. if (connection.IsInUse && !this.internallyCreated)
  279. Throw(new MySqlException("There is already an open DataReader associated with this Connection which must be closed first."));
  280. }
  281. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ExecuteNonQuery/*'/>
  282. public override int ExecuteNonQuery()
  283. {
  284. int records = -1;
  285. // give our interceptors a shot at it first
  286. if (connection != null &&
  287. connection.commandInterceptor != null &&
  288. connection.commandInterceptor.ExecuteNonQuery(CommandText, ref records))
  289. return records;
  290. // ok, none of our interceptors handled this so we default
  291. using (MySqlDataReader reader = ExecuteReader())
  292. {
  293. reader.Close();
  294. return reader.RecordsAffected;
  295. }
  296. }
  297. internal void ClearCommandTimer()
  298. {
  299. if (commandTimer != null)
  300. {
  301. commandTimer.Dispose();
  302. commandTimer = null;
  303. }
  304. }
  305. internal void Close(MySqlDataReader reader)
  306. {
  307. if (statement != null)
  308. statement.Close(reader);
  309. ResetSqlSelectLimit();
  310. if (statement != null && connection != null && connection.driver != null)
  311. connection.driver.CloseQuery(connection, statement.StatementId);
  312. ClearCommandTimer();
  313. }
  314. /// <summary>
  315. /// Reset reader to null, to avoid "There is already an open data reader"
  316. /// on the next ExecuteReader(). Used in error handling scenarios.
  317. /// </summary>
  318. private void ResetReader()
  319. {
  320. if (connection != null && connection.Reader != null)
  321. {
  322. connection.Reader.Close();
  323. connection.Reader = null;
  324. }
  325. }
  326. /// <summary>
  327. /// Reset SQL_SELECT_LIMIT that could have been modified by CommandBehavior.
  328. /// </summary>
  329. internal void ResetSqlSelectLimit()
  330. {
  331. // if we are supposed to reset the sql select limit, do that here
  332. if (resetSqlSelect)
  333. {
  334. resetSqlSelect = false;
  335. MySqlCommand command = new MySqlCommand("SET SQL_SELECT_LIMIT=DEFAULT", connection);
  336. command.internallyCreated = true;
  337. command.ExecuteNonQuery();
  338. }
  339. }
  340. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ExecuteReader/*'/>
  341. public new MySqlDataReader ExecuteReader()
  342. {
  343. return ExecuteReader(CommandBehavior.Default);
  344. }
  345. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ExecuteReader1/*'/>
  346. public new MySqlDataReader ExecuteReader(CommandBehavior behavior)
  347. {
  348. // give our interceptors a shot at it first
  349. MySqlDataReader interceptedReader = null;
  350. if (connection != null &&
  351. connection.commandInterceptor != null &&
  352. connection.commandInterceptor.ExecuteReader(CommandText, behavior, ref interceptedReader))
  353. return interceptedReader;
  354. // interceptors didn't handle this so we fall through
  355. bool success = false;
  356. CheckState();
  357. Driver driver = connection.driver;
  358. cmdText = cmdText.Trim();
  359. if (String.IsNullOrEmpty(cmdText))
  360. Throw(new InvalidOperationException(Resources.CommandTextNotInitialized));
  361. string sql = cmdText.Trim(';');
  362. // Load balancing getting a new connection
  363. if (connection.hasBeenOpen && !driver.HasStatus(ServerStatusFlags.InTransaction))
  364. {
  365. ReplicationManager.GetNewConnection(connection.Settings.Server, !IsReadOnlyCommand(sql), connection);
  366. }
  367. lock (driver)
  368. {
  369. // We have to recheck that there is no reader, after we got the lock
  370. if (connection.Reader != null)
  371. {
  372. Throw(new MySqlException(Resources.DataReaderOpen));
  373. }
  374. System.Transactions.Transaction curTrans = System.Transactions.Transaction.Current;
  375. if (curTrans != null)
  376. {
  377. bool inRollback = false;
  378. if (driver.CurrentTransaction != null)
  379. inRollback = driver.CurrentTransaction.InRollback;
  380. if (!inRollback)
  381. {
  382. System.Transactions.TransactionStatus status = System.Transactions.TransactionStatus.InDoubt;
  383. try
  384. {
  385. // in some cases (during state transitions) this throws
  386. // an exception. Ignore exceptions, we're only interested
  387. // whether transaction was aborted or not.
  388. status = curTrans.TransactionInformation.Status;
  389. }
  390. catch (System.Transactions.TransactionException)
  391. {
  392. }
  393. if (status == System.Transactions.TransactionStatus.Aborted)
  394. Throw(new System.Transactions.TransactionAbortedException());
  395. }
  396. }
  397. commandTimer = new CommandTimer(connection, CommandTimeout);
  398. lastInsertedId = -1;
  399. if (CommandType == CommandType.TableDirect)
  400. sql = "SELECT * FROM " + sql;
  401. else if (CommandType == CommandType.Text)
  402. {
  403. // validates single word statetment (maybe is a stored procedure call)
  404. if (sql.IndexOf(" ") == -1)
  405. {
  406. if (AddCallStatement(sql))
  407. sql = "call " + sql;
  408. }
  409. }
  410. // if we are on a replicated connection, we are only allow readonly statements
  411. if (connection.Settings.Replication && !InternallyCreated)
  412. EnsureCommandIsReadOnly(sql);
  413. if (statement == null || !statement.IsPrepared)
  414. {
  415. if (CommandType == CommandType.StoredProcedure)
  416. statement = new StoredProcedure(this, sql);
  417. else
  418. statement = new PreparableStatement(this, sql);
  419. }
  420. // stored procs are the only statement type that need do anything during resolve
  421. statement.Resolve(false);
  422. // Now that we have completed our resolve step, we can handle our
  423. // command behaviors
  424. HandleCommandBehaviors(behavior);
  425. updatedRowCount = -1;
  426. try
  427. {
  428. MySqlDataReader reader = new MySqlDataReader(this, statement, behavior);
  429. connection.Reader = reader;
  430. canceled = false;
  431. // execute the statement
  432. statement.Execute();
  433. // wait for data to return
  434. reader.NextResult();
  435. success = true;
  436. return reader;
  437. }
  438. catch (TimeoutException tex)
  439. {
  440. connection.HandleTimeoutOrThreadAbort(tex);
  441. throw; //unreached
  442. }
  443. catch (ThreadAbortException taex)
  444. {
  445. connection.HandleTimeoutOrThreadAbort(taex);
  446. throw;
  447. }
  448. catch (IOException ioex)
  449. {
  450. connection.Abort(); // Closes connection without returning it to the pool
  451. throw new MySqlException(Resources.FatalErrorDuringExecute, ioex);
  452. }
  453. catch (MySqlException ex)
  454. {
  455. if (ex.InnerException is TimeoutException)
  456. throw; // already handled
  457. try
  458. {
  459. ResetReader();
  460. ResetSqlSelectLimit();
  461. }
  462. catch (Exception)
  463. {
  464. // Reset SqlLimit did not work, connection is hosed.
  465. Connection.Abort();
  466. throw new MySqlException(ex.Message, true, ex);
  467. }
  468. // if we caught an exception because of a cancel, then just return null
  469. if (ex.IsQueryAborted)
  470. return null;
  471. if (ex.IsFatal)
  472. Connection.Close();
  473. if (ex.Number == 0)
  474. throw new MySqlException(Resources.FatalErrorDuringExecute, ex);
  475. throw;
  476. }
  477. finally
  478. {
  479. if (connection != null)
  480. {
  481. if (connection.Reader == null)
  482. {
  483. // Something went seriously wrong, and reader would not
  484. // be able to clear timeout on closing.
  485. // So we clear timeout here.
  486. ClearCommandTimer();
  487. }
  488. if (!success)
  489. {
  490. // ExecuteReader failed.Close Reader and set to null to
  491. // prevent subsequent errors with DataReaderOpen
  492. ResetReader();
  493. }
  494. }
  495. }
  496. }
  497. }
  498. private void EnsureCommandIsReadOnly(string sql)
  499. {
  500. sql = StringUtility.ToLowerInvariant(sql);
  501. if (!sql.StartsWith("select") && !sql.StartsWith("show"))
  502. Throw(new MySqlException(Resources.ReplicatedConnectionsAllowOnlyReadonlyStatements));
  503. if (sql.EndsWith("for update") || sql.EndsWith("lock in share mode"))
  504. Throw(new MySqlException(Resources.ReplicatedConnectionsAllowOnlyReadonlyStatements));
  505. }
  506. private bool IsReadOnlyCommand(string sql)
  507. {
  508. sql = sql.ToLower();
  509. return (sql.StartsWith("select") || sql.StartsWith("show"))
  510. && !(sql.EndsWith("for update") || sql.EndsWith("lock in share mode"));
  511. }
  512. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/ExecuteScalar/*'/>
  513. public override object ExecuteScalar()
  514. {
  515. lastInsertedId = -1;
  516. object val = null;
  517. // give our interceptors a shot at it first
  518. if (connection != null &&
  519. connection.commandInterceptor.ExecuteScalar(CommandText, ref val))
  520. return val;
  521. using (MySqlDataReader reader = ExecuteReader())
  522. {
  523. if (reader.Read())
  524. val = reader.GetValue(0);
  525. }
  526. return val;
  527. }
  528. private void HandleCommandBehaviors(CommandBehavior behavior)
  529. {
  530. if ((behavior & CommandBehavior.SchemaOnly) != 0)
  531. {
  532. new MySqlCommand("SET SQL_SELECT_LIMIT=0", connection).ExecuteNonQuery();
  533. resetSqlSelect = true;
  534. }
  535. else if ((behavior & CommandBehavior.SingleRow) != 0)
  536. {
  537. new MySqlCommand("SET SQL_SELECT_LIMIT=1", connection).ExecuteNonQuery();
  538. resetSqlSelect = true;
  539. }
  540. }
  541. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/Prepare2/*'/>
  542. private void Prepare(int cursorPageSize)
  543. {
  544. using (new CommandTimer(Connection, CommandTimeout))
  545. {
  546. // if the length of the command text is zero, then just return
  547. string psSQL = CommandText;
  548. if (psSQL == null ||
  549. psSQL.Trim().Length == 0)
  550. return;
  551. if (CommandType == CommandType.StoredProcedure)
  552. statement = new StoredProcedure(this, CommandText);
  553. else
  554. statement = new PreparableStatement(this, CommandText);
  555. statement.Resolve(true);
  556. statement.Prepare();
  557. }
  558. }
  559. /// <include file='Externals/MySql.Data-6.9.11/docs/mysqlcommand.xml' path='docs/Prepare/*'/>
  560. public override void Prepare()
  561. {
  562. if (connection == null)
  563. Throw(new InvalidOperationException("The connection property has not been set."));
  564. if (connection.State != ConnectionState.Open)
  565. Throw(new InvalidOperationException("The connection is not open."));
  566. if (connection.Settings.IgnorePrepare)
  567. return;
  568. Prepare(0);
  569. }
  570. #endregion
  571. #region Async Methods
  572. internal delegate object AsyncDelegate(int type, CommandBehavior behavior);
  573. internal AsyncDelegate caller = null;
  574. internal Exception thrownException;
  575. internal object AsyncExecuteWrapper(int type, CommandBehavior behavior)
  576. {
  577. thrownException = null;
  578. try
  579. {
  580. if (type == 1)
  581. return ExecuteReader(behavior);
  582. return ExecuteNonQuery();
  583. }
  584. catch (Exception ex)
  585. {
  586. thrownException = ex;
  587. }
  588. return null;
  589. }
  590. /// <summary>
  591. /// Initiates the asynchronous execution of the SQL statement or stored procedure
  592. /// that is described by this <see cref="MySqlCommand"/>, and retrieves one or more
  593. /// result sets from the server.
  594. /// </summary>
  595. /// <returns>An <see cref="IAsyncResult"/> that can be used to poll, wait for results,
  596. /// or both; this value is also needed when invoking EndExecuteReader,
  597. /// which returns a <see cref="MySqlDataReader"/> instance that can be used to retrieve
  598. /// the returned rows. </returns>
  599. public IAsyncResult BeginExecuteReader()
  600. {
  601. return BeginExecuteReader(CommandBehavior.Default);
  602. }
  603. /// <summary>
  604. /// Initiates the asynchronous execution of the SQL statement or stored procedure
  605. /// that is described by this <see cref="MySqlCommand"/> using one of the
  606. /// <b>CommandBehavior</b> values.
  607. /// </summary>
  608. /// <param name="behavior">One of the <see cref="CommandBehavior"/> values, indicating
  609. /// options for statement execution and data retrieval.</param>
  610. /// <returns>An <see cref="IAsyncResult"/> that can be used to poll, wait for results,
  611. /// or both; this value is also needed when invoking EndExecuteReader,
  612. /// which returns a <see cref="MySqlDataReader"/> instance that can be used to retrieve
  613. /// the returned rows. </returns>
  614. public IAsyncResult BeginExecuteReader(CommandBehavior behavior)
  615. {
  616. if (caller != null)
  617. Throw(new MySqlException(Resources.UnableToStartSecondAsyncOp));
  618. caller = new AsyncDelegate(AsyncExecuteWrapper);
  619. asyncResult = caller.BeginInvoke(1, behavior, null, null);
  620. return asyncResult;
  621. }
  622. /// <summary>
  623. /// Finishes asynchronous execution of a SQL statement, returning the requested
  624. /// <see cref="MySqlDataReader"/>.
  625. /// </summary>
  626. /// <param name="result">The <see cref="IAsyncResult"/> returned by the call to
  627. /// <see cref="BeginExecuteReader()"/>.</param>
  628. /// <returns>A <b>MySqlDataReader</b> object that can be used to retrieve the requested rows. </returns>
  629. public MySqlDataReader EndExecuteReader(IAsyncResult result)
  630. {
  631. result.AsyncWaitHandle.WaitOne();
  632. AsyncDelegate c = caller;
  633. caller = null;
  634. if (thrownException != null)
  635. throw thrownException;
  636. return (MySqlDataReader)c.EndInvoke(result);
  637. }
  638. /// <summary>
  639. /// Initiates the asynchronous execution of the SQL statement or stored procedure
  640. /// that is described by this <see cref="MySqlCommand"/>.
  641. /// </summary>
  642. /// <param name="callback">
  643. /// An <see cref="AsyncCallback"/> delegate that is invoked when the command's
  644. /// execution has completed. Pass a null reference (<b>Nothing</b> in Visual Basic)
  645. /// to indicate that no callback is required.</param>
  646. /// <param name="stateObject">A user-defined state object that is passed to the
  647. /// callback procedure. Retrieve this object from within the callback procedure
  648. /// using the <see cref="IAsyncResult.AsyncState"/> property.</param>
  649. /// <returns>An <see cref="IAsyncResult"/> that can be used to poll or wait for results,
  650. /// or both; this value is also needed when invoking <see cref="EndExecuteNonQuery"/>,
  651. /// which returns the number of affected rows. </returns>
  652. public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObject)
  653. {
  654. if (caller != null)
  655. Throw(new MySqlException(Resources.UnableToStartSecondAsyncOp));
  656. caller = new AsyncDelegate(AsyncExecuteWrapper);
  657. asyncResult = caller.BeginInvoke(2, CommandBehavior.Default,
  658. callback, stateObject);
  659. return asyncResult;
  660. }
  661. /// <summary>
  662. /// Initiates the asynchronous execution of the SQL statement or stored procedure
  663. /// that is described by this <see cref="MySqlCommand"/>.
  664. /// </summary>
  665. /// <returns>An <see cref="IAsyncResult"/> that can be used to poll or wait for results,
  666. /// or both; this value is also needed when invoking <see cref="EndExecuteNonQuery"/>,
  667. /// which returns the number of affected rows. </returns>
  668. public IAsyncResult BeginExecuteNonQuery()
  669. {
  670. if (caller != null)
  671. Throw(new MySqlException(Resources.UnableToStartSecondAsyncOp));
  672. caller = new AsyncDelegate(AsyncExecuteWrapper);
  673. asyncResult = caller.BeginInvoke(2, CommandBehavior.Default, null, null);
  674. return asyncResult;
  675. }
  676. /// <summary>
  677. /// Finishes asynchronous execution of a SQL statement.
  678. /// </summary>
  679. /// <param name="asyncResult">The <see cref="IAsyncResult"/> returned by the call
  680. /// to <see cref="BeginExecuteNonQuery()"/>.</param>
  681. /// <returns></returns>
  682. public int EndExecuteNonQuery(IAsyncResult asyncResult)
  683. {
  684. asyncResult.AsyncWaitHandle.WaitOne();
  685. AsyncDelegate c = caller;
  686. caller = null;
  687. if (thrownException != null)
  688. throw thrownException;
  689. return (int)c.EndInvoke(asyncResult);
  690. }
  691. #endregion
  692. #region Private Methods
  693. /* private ArrayList PrepareSqlBuffers(string sql)
  694. {
  695. ArrayList buffers = new ArrayList();
  696. MySqlStreamWriter writer = new MySqlStreamWriter(new MemoryStream(), connection.Encoding);
  697. writer.Version = connection.driver.Version;
  698. // if we are executing as a stored procedure, then we need to add the call
  699. // keyword.
  700. if (CommandType == CommandType.StoredProcedure)
  701. {
  702. if (storedProcedure == null)
  703. storedProcedure = new StoredProcedure(this);
  704. sql = storedProcedure.Prepare( CommandText );
  705. }
  706. // tokenize the SQL
  707. sql = sql.TrimStart(';').TrimEnd(';');
  708. ArrayList tokens = TokenizeSql( sql );
  709. foreach (string token in tokens)
  710. {
  711. if (token.Trim().Length == 0) continue;
  712. if (token == ";" && ! connection.driver.SupportsBatch)
  713. {
  714. MemoryStream ms = (MemoryStream)writer.Stream;
  715. if (ms.Length > 0)
  716. buffers.Add( ms );
  717. writer = new MySqlStreamWriter(new MemoryStream(), connection.Encoding);
  718. writer.Version = connection.driver.Version;
  719. continue;
  720. }
  721. else if (token[0] == parameters.ParameterMarker)
  722. {
  723. if (SerializeParameter(writer, token)) continue;
  724. }
  725. // our fall through case is to write the token to the byte stream
  726. writer.WriteStringNoNull(token);
  727. }
  728. // capture any buffer that is left over
  729. MemoryStream mStream = (MemoryStream)writer.Stream;
  730. if (mStream.Length > 0)
  731. buffers.Add( mStream );
  732. return buffers;
  733. }*/
  734. internal long EstimatedSize()
  735. {
  736. long size = CommandText.Length;
  737. foreach (MySqlParameter parameter in Parameters)
  738. size += parameter.EstimatedSize();
  739. return size;
  740. }
  741. /// <summary>
  742. /// Verifies if a query is valid even if it has not spaces or is a stored procedure call
  743. /// </summary>
  744. /// <param name="query">Query to validate</param>
  745. /// <returns>If it is necessary to add call statement</returns>
  746. private bool AddCallStatement(string query)
  747. {
  748. if (string.IsNullOrEmpty(query)) return false;
  749. string keyword = query.ToUpper();
  750. int indexChar = keyword.IndexOfAny(new char[] { '(', '"', '@', '\'', '`' });
  751. if (indexChar > 0)
  752. keyword = keyword.Substring(0, indexChar);
  753. if (keywords == null) keywords = new List<string>(Resources.Keywords);
  754. return !keywords.Contains(keyword);
  755. }
  756. #endregion
  757. #region ICloneable
  758. /// <summary>
  759. /// Creates a clone of this MySqlCommand object. CommandText, Connection, and Transaction properties
  760. /// are included as well as the entire parameter list.
  761. /// </summary>
  762. /// <returns>The cloned MySqlCommand object</returns>
  763. public MySqlCommand Clone()
  764. {
  765. MySqlCommand clone = new MySqlCommand(cmdText, connection, curTransaction);
  766. clone.CommandType = CommandType;
  767. clone.commandTimeout = commandTimeout;
  768. clone.useDefaultTimeout = useDefaultTimeout;
  769. clone.batchableCommandText = batchableCommandText;
  770. clone.EnableCaching = EnableCaching;
  771. clone.CacheAge = CacheAge;
  772. PartialClone(clone);
  773. foreach (MySqlParameter p in parameters)
  774. {
  775. clone.Parameters.Add(p.Clone());
  776. }
  777. return clone;
  778. }
  779. partial void PartialClone(MySqlCommand clone);
  780. object ICloneable.Clone()
  781. {
  782. return this.Clone();
  783. }
  784. #endregion
  785. #region Batching support
  786. internal void AddToBatch(MySqlCommand command)
  787. {
  788. if (batch == null)
  789. batch = new List<MySqlCommand>();
  790. batch.Add(command);
  791. }
  792. internal string GetCommandTextForBatching()
  793. {
  794. if (batchableCommandText == null)
  795. {
  796. // if the command starts with insert and is "simple" enough, then
  797. // we can use the multi-value form of insert
  798. if (String.Compare(CommandText.Substring(0, 6), "INSERT", StringComparison.OrdinalIgnoreCase) == 0)
  799. {
  800. MySqlCommand cmd = new MySqlCommand("SELECT @@sql_mode", Connection);
  801. string sql_mode = StringUtility.ToUpperInvariant(cmd.ExecuteScalar().ToString());
  802. MySqlTokenizer tokenizer = new MySqlTokenizer(CommandText);
  803. tokenizer.AnsiQuotes = sql_mode.IndexOf("ANSI_QUOTES") != -1;
  804. tokenizer.BackslashEscapes = sql_mode.IndexOf("NO_BACKSLASH_ESCAPES") == -1;
  805. string token = StringUtility.ToLowerInvariant(tokenizer.NextToken());
  806. while (token != null)
  807. {
  808. if (StringUtility.ToUpperInvariant(token) == "VALUES" &&
  809. !tokenizer.Quoted)
  810. {
  811. token = tokenizer.NextToken();
  812. Debug.Assert(token == "(");
  813. // find matching right paren, and ensure that parens
  814. // are balanced.
  815. int openParenCount = 1;
  816. while (token != null)
  817. {
  818. batchableCommandText += token;
  819. token = tokenizer.NextToken();
  820. if (token == "(")
  821. openParenCount++;
  822. else if (token == ")")
  823. openParenCount--;
  824. if (openParenCount == 0)
  825. break;
  826. }
  827. if (token != null)
  828. batchableCommandText += token;
  829. token = tokenizer.NextToken();
  830. if (token != null && (token == "," ||
  831. StringUtility.ToUpperInvariant(token) == "ON"))
  832. {
  833. batchableCommandText = null;
  834. break;
  835. }
  836. }
  837. token = tokenizer.NextToken();
  838. }
  839. }
  840. // Otherwise use the command verbatim
  841. else batchableCommandText = CommandText;
  842. }
  843. return batchableCommandText;
  844. }
  845. #endregion
  846. // This method is used to throw all exceptions from this class.
  847. private void Throw(Exception ex)
  848. {
  849. if (connection != null)
  850. connection.Throw(ex);
  851. throw ex;
  852. }
  853. public new void Dispose()
  854. {
  855. Dispose(true);
  856. GC.SuppressFinalize(this);
  857. }
  858. protected override void Dispose(bool disposing)
  859. {
  860. if (disposed)
  861. return;
  862. if (disposing)
  863. if (statement != null && statement.IsPrepared)
  864. statement.CloseStatement();
  865. base.Dispose(disposing);
  866. disposed = true;
  867. }
  868. }
  869. }
  870. #endif