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.

980 lines
31 KiB

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