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.

940 lines
35 KiB

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