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.

1017 lines
34 KiB

4 years ago
  1. // Copyright ?2004, 2014, 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.ComponentModel;
  25. using System.Data;
  26. using System.Data.Common;
  27. using System.Transactions;
  28. using IsolationLevel = System.Data.IsolationLevel;
  29. using System.Drawing;
  30. using System.Drawing.Design;
  31. using System.Text;
  32. using Externals.MySql.Data.Common;
  33. using System.Diagnostics;
  34. using Externals.MySql.Data.MySqlClient.Properties;
  35. using Externals.MySql.Data.MySqlClient.Replication;
  36. using System.Threading.Tasks;
  37. using System.Threading;
  38. namespace Externals.MySql.Data.MySqlClient
  39. {
  40. internal sealed partial class MySqlConnection : IDisposable
  41. {
  42. internal ConnectionState connectionState;
  43. internal Driver driver;
  44. internal bool hasBeenOpen;
  45. private SchemaProvider schemaProvider;
  46. private ProcedureCache procedureCache;
  47. private bool isInUse;
  48. private PerformanceMonitor perfMonitor;
  49. private ExceptionInterceptor exceptionInterceptor;
  50. internal CommandInterceptor commandInterceptor;
  51. private bool isKillQueryConnection;
  52. private string database;
  53. private int commandTimeout;
  54. public event MySqlInfoMessageEventHandler InfoMessage;
  55. private static Cache<string, MySqlConnectionStringBuilder> connectionStringCache =
  56. new Cache<string, MySqlConnectionStringBuilder>(0, 25);
  57. public MySqlConnection()
  58. {
  59. //TODO: add event data to StateChange docs
  60. Settings = new MySqlConnectionStringBuilder();
  61. database = String.Empty;
  62. }
  63. public MySqlConnection(string connectionString)
  64. : this()
  65. {
  66. ConnectionString = connectionString;
  67. }
  68. #region Destructor
  69. ~MySqlConnection()
  70. {
  71. Dispose(false);
  72. }
  73. #endregion
  74. #region Interal Methods & Properties
  75. internal PerformanceMonitor PerfMonitor
  76. {
  77. get { return perfMonitor; }
  78. }
  79. internal ProcedureCache ProcedureCache
  80. {
  81. get { return procedureCache; }
  82. }
  83. internal MySqlConnectionStringBuilder Settings { get; private set; }
  84. internal MySqlDataReader Reader
  85. {
  86. get
  87. {
  88. if (driver == null)
  89. return null;
  90. return driver.reader;
  91. }
  92. set
  93. {
  94. driver.reader = value;
  95. isInUse = driver.reader != null;
  96. }
  97. }
  98. internal void OnInfoMessage(MySqlInfoMessageEventArgs args)
  99. {
  100. if (InfoMessage != null)
  101. {
  102. InfoMessage(this, args);
  103. }
  104. }
  105. internal bool SoftClosed
  106. {
  107. get
  108. {
  109. return (State == ConnectionState.Closed) &&
  110. driver != null &&
  111. driver.CurrentTransaction != null;
  112. }
  113. }
  114. internal bool IsInUse
  115. {
  116. get { return isInUse; }
  117. set { isInUse = value; }
  118. }
  119. #endregion
  120. #region Properties
  121. /// <summary>
  122. /// Returns the id of the server thread this connection is executing on
  123. /// </summary>
  124. [Browsable(false)]
  125. public int ServerThread
  126. {
  127. get { return driver.ThreadID; }
  128. }
  129. /// <summary>
  130. /// Gets the name of the MySQL server to which to connect.
  131. /// </summary>
  132. [Browsable(true)]
  133. public override string DataSource
  134. {
  135. get { return Settings.Server; }
  136. }
  137. [Browsable(true)]
  138. public override int ConnectionTimeout
  139. {
  140. get { return (int)Settings.ConnectionTimeout; }
  141. }
  142. [Browsable(true)]
  143. public override string Database
  144. {
  145. get { return database; }
  146. }
  147. /// <summary>
  148. /// Indicates if this connection should use compression when communicating with the server.
  149. /// </summary>
  150. [Browsable(false)]
  151. public bool UseCompression
  152. {
  153. get { return Settings.UseCompression; }
  154. }
  155. [Browsable(false)]
  156. public override ConnectionState State
  157. {
  158. get { return connectionState; }
  159. }
  160. [Browsable(false)]
  161. public override string ServerVersion
  162. {
  163. get { return driver.Version.ToString(); }
  164. }
  165. [Editor("Externals.MySql.Data.MySqlClient.Design.ConnectionStringTypeEditor,MySqlClient.Design", typeof(UITypeEditor))]
  166. [Browsable(true)]
  167. [Category("Data")]
  168. [Description(
  169. "Information used to connect to a DataSource, such as 'Server=xxx;UserId=yyy;Password=zzz;Database=dbdb'.")]
  170. public override string ConnectionString
  171. {
  172. get
  173. {
  174. // Always return exactly what the user set.
  175. // Security-sensitive information may be removed.
  176. return Settings.GetConnectionString(!hasBeenOpen || Settings.PersistSecurityInfo);
  177. }
  178. set
  179. {
  180. if (State != ConnectionState.Closed)
  181. Throw(new MySqlException(
  182. "Not allowed to change the 'ConnectionString' property while the connection (state=" + State + ")."));
  183. MySqlConnectionStringBuilder newSettings;
  184. lock (connectionStringCache)
  185. {
  186. if (value == null)
  187. newSettings = new MySqlConnectionStringBuilder();
  188. else
  189. {
  190. newSettings = (MySqlConnectionStringBuilder)connectionStringCache[value];
  191. if (null == newSettings)
  192. {
  193. newSettings = new MySqlConnectionStringBuilder(value);
  194. connectionStringCache.Add(value, newSettings);
  195. }
  196. }
  197. }
  198. Settings = newSettings;
  199. if (Settings.Database != null && Settings.Database.Length > 0)
  200. this.database = Settings.Database;
  201. if (driver != null)
  202. driver.Settings = newSettings;
  203. }
  204. }
  205. protected override DbProviderFactory DbProviderFactory
  206. {
  207. get
  208. {
  209. return MySqlClientFactory.Instance;
  210. }
  211. }
  212. public bool IsPasswordExpired { get { return driver.IsPasswordExpired; } }
  213. #endregion
  214. partial void AssertPermissions();
  215. #region Transactions
  216. /// <summary>
  217. /// Enlists in the specified transaction.
  218. /// </summary>
  219. /// <param name="transaction">
  220. /// A reference to an existing <see cref="System.Transactions.Transaction"/> in which to enlist.
  221. /// </param>
  222. public override void EnlistTransaction(Transaction transaction)
  223. {
  224. // enlisting in the null transaction is a noop
  225. if (transaction == null)
  226. return;
  227. // guard against trying to enlist in more than one transaction
  228. if (driver.CurrentTransaction != null)
  229. {
  230. if (driver.CurrentTransaction.BaseTransaction == transaction)
  231. return;
  232. Throw(new MySqlException("Already enlisted"));
  233. }
  234. // now see if we need to swap out drivers. We would need to do this since
  235. // we have to make sure all ops for a given transaction are done on the
  236. // same physical connection.
  237. Driver existingDriver = DriverTransactionManager.GetDriverInTransaction(transaction);
  238. if (existingDriver != null)
  239. {
  240. // we can't allow more than one driver to contribute to the same connection
  241. if (existingDriver.IsInActiveUse)
  242. Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported));
  243. // there is an existing driver and it's not being currently used.
  244. // now we need to see if it is using the same connection string
  245. string text1 = existingDriver.Settings.ConnectionString;
  246. string text2 = Settings.ConnectionString;
  247. if (String.Compare(text1, text2, true) != 0)
  248. Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported));
  249. // close existing driver
  250. // set this new driver as our existing driver
  251. CloseFully();
  252. driver = existingDriver;
  253. }
  254. if (driver.CurrentTransaction == null)
  255. {
  256. MySqlPromotableTransaction t = new MySqlPromotableTransaction(this, transaction);
  257. if (!transaction.EnlistPromotableSinglePhase(t))
  258. Throw(new NotSupportedException(Resources.DistributedTxnNotSupported));
  259. driver.CurrentTransaction = t;
  260. DriverTransactionManager.SetDriverInTransaction(driver);
  261. driver.IsInActiveUse = true;
  262. }
  263. }
  264. public new MySqlTransaction BeginTransaction()
  265. {
  266. return BeginTransaction(IsolationLevel.RepeatableRead);
  267. }
  268. public new MySqlTransaction BeginTransaction(IsolationLevel iso)
  269. {
  270. //TODO: check note in help
  271. if (State != ConnectionState.Open)
  272. Throw(new InvalidOperationException(Resources.ConnectionNotOpen));
  273. // First check to see if we are in a current transaction
  274. if (driver.HasStatus(ServerStatusFlags.InTransaction))
  275. Throw(new InvalidOperationException(Resources.NoNestedTransactions));
  276. MySqlTransaction t = new MySqlTransaction(this, iso);
  277. MySqlCommand cmd = new MySqlCommand("", this);
  278. cmd.CommandText = "SET SESSION TRANSACTION ISOLATION LEVEL ";
  279. switch (iso)
  280. {
  281. case IsolationLevel.ReadCommitted:
  282. cmd.CommandText += "READ COMMITTED";
  283. break;
  284. case IsolationLevel.ReadUncommitted:
  285. cmd.CommandText += "READ UNCOMMITTED";
  286. break;
  287. case IsolationLevel.RepeatableRead:
  288. cmd.CommandText += "REPEATABLE READ";
  289. break;
  290. case IsolationLevel.Serializable:
  291. cmd.CommandText += "SERIALIZABLE";
  292. break;
  293. case IsolationLevel.Chaos:
  294. Throw(new NotSupportedException(Resources.ChaosNotSupported));
  295. break;
  296. case IsolationLevel.Snapshot:
  297. Throw(new NotSupportedException(Resources.SnapshotNotSupported));
  298. break;
  299. }
  300. cmd.ExecuteNonQuery();
  301. cmd.CommandText = "BEGIN";
  302. cmd.ExecuteNonQuery();
  303. return t;
  304. }
  305. #endregion
  306. public override void ChangeDatabase(string databaseName)
  307. {
  308. if (databaseName == null || databaseName.Trim().Length == 0)
  309. Throw(new ArgumentException(Resources.ParameterIsInvalid, "databaseName"));
  310. if (State != ConnectionState.Open)
  311. Throw(new InvalidOperationException(Resources.ConnectionNotOpen));
  312. // This lock prevents promotable transaction rollback to run
  313. // in parallel
  314. lock (driver)
  315. {
  316. if (Transaction.Current != null &&
  317. Transaction.Current.TransactionInformation.Status == TransactionStatus.Aborted)
  318. {
  319. Throw(new TransactionAbortedException());
  320. }
  321. // We use default command timeout for SetDatabase
  322. using (new CommandTimer(this, (int)Settings.DefaultCommandTimeout))
  323. {
  324. driver.SetDatabase(databaseName);
  325. }
  326. }
  327. this.database = databaseName;
  328. }
  329. internal void SetState(ConnectionState newConnectionState, bool broadcast)
  330. {
  331. if (newConnectionState == connectionState && !broadcast)
  332. return;
  333. ConnectionState oldConnectionState = connectionState;
  334. connectionState = newConnectionState;
  335. if (broadcast)
  336. OnStateChange(new StateChangeEventArgs(oldConnectionState, connectionState));
  337. }
  338. /// <summary>
  339. /// Ping
  340. /// </summary>
  341. /// <returns></returns>
  342. public bool Ping()
  343. {
  344. if (Reader != null)
  345. Throw(new MySqlException(Resources.DataReaderOpen));
  346. if (driver != null && driver.Ping())
  347. return true;
  348. driver = null;
  349. SetState(ConnectionState.Closed, true);
  350. return false;
  351. }
  352. public override void Open()
  353. {
  354. if (State == ConnectionState.Open)
  355. Throw(new InvalidOperationException(Resources.ConnectionAlreadyOpen));
  356. // start up our interceptors
  357. exceptionInterceptor = new ExceptionInterceptor(this);
  358. commandInterceptor = new CommandInterceptor(this);
  359. SetState(ConnectionState.Connecting, true);
  360. AssertPermissions();
  361. // if we are auto enlisting in a current transaction, then we will be
  362. // treating the connection as pooled
  363. if (Settings.AutoEnlist && Transaction.Current != null)
  364. {
  365. driver = DriverTransactionManager.GetDriverInTransaction(Transaction.Current);
  366. if (driver != null &&
  367. (driver.IsInActiveUse ||
  368. !driver.Settings.EquivalentTo(this.Settings)))
  369. Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported));
  370. }
  371. try
  372. {
  373. MySqlConnectionStringBuilder currentSettings = Settings;
  374. // Load balancing
  375. if (ReplicationManager.IsReplicationGroup(Settings.Server))
  376. {
  377. if (driver == null)
  378. {
  379. ReplicationManager.GetNewConnection(Settings.Server, false, this);
  380. }
  381. else
  382. currentSettings = driver.Settings;
  383. }
  384. if (Settings.Pooling)
  385. {
  386. MySqlPool pool = MySqlPoolManager.GetPool(currentSettings);
  387. if (driver == null || !driver.IsOpen)
  388. driver = pool.GetConnection();
  389. procedureCache = pool.ProcedureCache;
  390. }
  391. else
  392. {
  393. if (driver == null || !driver.IsOpen)
  394. driver = Driver.Create(currentSettings);
  395. procedureCache = new ProcedureCache((int)Settings.ProcedureCacheSize);
  396. }
  397. }
  398. catch
  399. {
  400. SetState(ConnectionState.Closed, true);
  401. throw;
  402. }
  403. // if the user is using old syntax, let them know
  404. if (driver.Settings.UseOldSyntax) MySqlTrace.LogWarning(ServerThread, "You are using old syntax that will be removed in future versions");
  405. SetState(ConnectionState.Open, false);
  406. driver.Configure(this);
  407. if (!(driver.SupportsPasswordExpiration && driver.IsPasswordExpired))
  408. {
  409. if (Settings.Database != null && Settings.Database != String.Empty)
  410. ChangeDatabase(Settings.Database);
  411. }
  412. // setup our schema provider
  413. schemaProvider = new ISSchemaProvider(this);
  414. perfMonitor = new PerformanceMonitor(this);
  415. // if we are opening up inside a current transaction, then autoenlist
  416. // TODO: control this with a connection string option
  417. if (Transaction.Current != null && Settings.AutoEnlist)
  418. EnlistTransaction(Transaction.Current);
  419. hasBeenOpen = true;
  420. SetState(ConnectionState.Open, true);
  421. }
  422. public new MySqlCommand CreateCommand()
  423. {
  424. // Return a new instance of a command object.
  425. MySqlCommand c = new MySqlCommand();
  426. c.Connection = this;
  427. return c;
  428. }
  429. /// <summary>
  430. /// Creates a new MySqlConnection object with the exact same ConnectionString value
  431. /// </summary>
  432. /// <returns>A cloned MySqlConnection object</returns>
  433. public object Clone()
  434. {
  435. MySqlConnection clone = new MySqlConnection();
  436. string connectionString = Settings.ConnectionString;
  437. if (connectionString != null)
  438. clone.ConnectionString = connectionString;
  439. return clone;
  440. }
  441. internal void Abort()
  442. {
  443. try
  444. {
  445. driver.Close();
  446. }
  447. catch (Exception ex)
  448. {
  449. MySqlTrace.LogWarning(ServerThread, String.Concat("Error occurred aborting the connection. Exception was: ", ex.Message));
  450. }
  451. finally
  452. {
  453. this.isInUse = false;
  454. }
  455. SetState(ConnectionState.Closed, true);
  456. }
  457. internal void CloseFully()
  458. {
  459. if (Settings.Pooling && driver.IsOpen)
  460. {
  461. // if we are in a transaction, roll it back
  462. if (driver.HasStatus(ServerStatusFlags.InTransaction))
  463. {
  464. MySqlTransaction t = new MySqlTransaction(this, IsolationLevel.Unspecified);
  465. t.Rollback();
  466. }
  467. MySqlPoolManager.ReleaseConnection(driver);
  468. }
  469. else
  470. driver.Close();
  471. driver = null;
  472. }
  473. public override void Close()
  474. {
  475. if (driver != null)
  476. driver.IsPasswordExpired = false;
  477. if (State == ConnectionState.Closed) return;
  478. if (Reader != null)
  479. Reader.Close();
  480. // if the reader was opened with CloseConnection then driver
  481. // will be null on the second time through
  482. if (driver != null)
  483. {
  484. if (driver.CurrentTransaction == null)
  485. CloseFully();
  486. else
  487. driver.IsInActiveUse = false;
  488. }
  489. SetState(ConnectionState.Closed, true);
  490. }
  491. internal string CurrentDatabase()
  492. {
  493. if (Database != null && Database.Length > 0)
  494. return Database;
  495. MySqlCommand cmd = new MySqlCommand("SELECT database()", this);
  496. return cmd.ExecuteScalar().ToString();
  497. }
  498. internal void HandleTimeoutOrThreadAbort(Exception ex)
  499. {
  500. bool isFatal = false;
  501. if (isKillQueryConnection)
  502. {
  503. // Special connection started to cancel a query.
  504. // Abort will prevent recursive connection spawning
  505. Abort();
  506. if (ex is TimeoutException)
  507. {
  508. Throw(new MySqlException(Resources.Timeout, true, ex));
  509. }
  510. else
  511. {
  512. return;
  513. }
  514. }
  515. try
  516. {
  517. // Do a fast cancel.The reason behind small values for connection
  518. // and command timeout is that we do not want user to wait longer
  519. // after command has already expired.
  520. // Microsoft's SqlClient seems to be using 5 seconds timeouts
  521. // here as well.
  522. // Read the error packet with "interrupted" message.
  523. CancelQuery(5);
  524. driver.ResetTimeout(5000);
  525. if (Reader != null)
  526. {
  527. Reader.Close();
  528. Reader = null;
  529. }
  530. }
  531. catch (Exception ex2)
  532. {
  533. MySqlTrace.LogWarning(ServerThread, "Could not kill query, " +
  534. " aborting connection. Exception was " + ex2.Message);
  535. Abort();
  536. isFatal = true;
  537. }
  538. if (ex is TimeoutException)
  539. {
  540. Throw(new MySqlException(Resources.Timeout, isFatal, ex));
  541. }
  542. }
  543. public void CancelQuery(int timeout)
  544. {
  545. MySqlConnectionStringBuilder cb = new MySqlConnectionStringBuilder(
  546. Settings.ConnectionString);
  547. cb.Pooling = false;
  548. cb.AutoEnlist = false;
  549. cb.ConnectionTimeout = (uint)timeout;
  550. using (MySqlConnection c = new MySqlConnection(cb.ConnectionString))
  551. {
  552. c.isKillQueryConnection = true;
  553. c.Open();
  554. string commandText = "KILL QUERY " + ServerThread;
  555. MySqlCommand cmd = new MySqlCommand(commandText, c);
  556. cmd.CommandTimeout = timeout;
  557. cmd.ExecuteNonQuery();
  558. }
  559. }
  560. #region Routines for timeout support.
  561. // Problem description:
  562. // Sometimes, ExecuteReader is called recursively. This is the case if
  563. // command behaviors are used and we issue "set sql_select_limit"
  564. // before and after command. This is also the case with prepared
  565. // statements , where we set session variables. In these situations, we
  566. // have to prevent recursive ExecuteReader calls from overwriting
  567. // timeouts set by the top level command.
  568. // To solve the problem, SetCommandTimeout() and ClearCommandTimeout() are
  569. // introduced . Query timeout here is "sticky", that is once set with
  570. // SetCommandTimeout, it only be overwritten after ClearCommandTimeout
  571. // (SetCommandTimeout would return false if it timeout has not been
  572. // cleared).
  573. // The proposed usage pattern of there routines is following:
  574. // When timed operations starts, issue SetCommandTimeout(). When it
  575. // finishes, issue ClearCommandTimeout(), but _only_ if call to
  576. // SetCommandTimeout() was successful.
  577. /// <summary>
  578. /// Sets query timeout. If timeout has been set prior and not
  579. /// yet cleared ClearCommandTimeout(), it has no effect.
  580. /// </summary>
  581. /// <param name="value">timeout in seconds</param>
  582. /// <returns>true if </returns>
  583. internal bool SetCommandTimeout(int value)
  584. {
  585. if (!hasBeenOpen)
  586. // Connection timeout is handled by driver
  587. return false;
  588. if (commandTimeout != 0)
  589. // someone is trying to set a timeout while command is already
  590. // running. It could be for example recursive call to ExecuteReader
  591. // Ignore the request, as only top-level (non-recursive commands)
  592. // can set timeouts.
  593. return false;
  594. if (driver == null)
  595. return false;
  596. commandTimeout = value;
  597. driver.ResetTimeout(commandTimeout * 1000);
  598. return true;
  599. }
  600. /// <summary>
  601. /// Clears query timeout, allowing next SetCommandTimeout() to succeed.
  602. /// </summary>
  603. internal void ClearCommandTimeout()
  604. {
  605. if (!hasBeenOpen)
  606. return;
  607. commandTimeout = 0;
  608. if (driver != null)
  609. {
  610. driver.ResetTimeout(0);
  611. }
  612. }
  613. #endregion
  614. public MySqlSchemaCollection GetSchemaCollection(string collectionName, string[] restrictionValues)
  615. {
  616. if (collectionName == null)
  617. collectionName = SchemaProvider.MetaCollection;
  618. string[] restrictions = schemaProvider.CleanRestrictions(restrictionValues);
  619. MySqlSchemaCollection c = schemaProvider.GetSchema(collectionName, restrictions);
  620. return c;
  621. }
  622. #region Pool Routines
  623. public static void ClearPool(MySqlConnection connection)
  624. {
  625. MySqlPoolManager.ClearPool(connection.Settings);
  626. }
  627. public static void ClearAllPools()
  628. {
  629. MySqlPoolManager.ClearAllPools();
  630. }
  631. #endregion
  632. internal void Throw(Exception ex)
  633. {
  634. throw ex;
  635. }
  636. public new void Dispose()
  637. {
  638. Dispose(true);
  639. GC.SuppressFinalize(this);
  640. }
  641. #region Async
  642. /// <summary>
  643. /// Async version of BeginTransaction
  644. /// </summary>
  645. /// <returns>An object representing the new transaction.</returns>
  646. public Task<MySqlTransaction> BeginTransactionAsync()
  647. {
  648. return BeginTransactionAsync(IsolationLevel.RepeatableRead, CancellationToken.None);
  649. }
  650. public Task<MySqlTransaction> BeginTransactionAsync(CancellationToken cancellationToken)
  651. {
  652. return BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
  653. }
  654. /// <summary>
  655. /// Async version of BeginTransaction
  656. /// </summary>
  657. /// <param name="iso">The isolation level under which the transaction should run. </param>
  658. /// <returns>An object representing the new transaction.</returns>
  659. public Task<MySqlTransaction> BeginTransactionAsync(IsolationLevel iso)
  660. {
  661. return BeginTransactionAsync(iso, CancellationToken.None);
  662. }
  663. public Task<MySqlTransaction> BeginTransactionAsync(IsolationLevel iso, CancellationToken cancellationToken)
  664. {
  665. var result = new TaskCompletionSource<MySqlTransaction>();
  666. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  667. {
  668. try
  669. {
  670. MySqlTransaction tranResult = BeginTransaction(iso);
  671. result.SetResult(tranResult);
  672. }
  673. catch (Exception ex)
  674. {
  675. result.SetException(ex);
  676. }
  677. }
  678. else
  679. {
  680. result.SetCanceled();
  681. }
  682. return result.Task;
  683. }
  684. public Task ChangeDataBaseAsync(string databaseName)
  685. {
  686. return ChangeDataBaseAsync(databaseName, CancellationToken.None);
  687. }
  688. /// <summary>
  689. /// Async version of ChangeDataBase
  690. /// </summary>
  691. public Task ChangeDataBaseAsync(string databaseName, CancellationToken cancellationToken)
  692. {
  693. var result = new TaskCompletionSource<bool>();
  694. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  695. {
  696. try
  697. {
  698. ChangeDatabase(databaseName);
  699. result.SetResult(true);
  700. }
  701. catch (Exception ex)
  702. {
  703. result.SetException(ex);
  704. }
  705. }
  706. return result.Task;
  707. }
  708. ///// <summary>
  709. ///// Async version of Open
  710. ///// </summary>
  711. ///// <returns></returns>
  712. //public Task OpenAsync()
  713. //{
  714. // return Task.Run(() =>
  715. // {
  716. // Open();
  717. // });
  718. //}
  719. /// <summary>
  720. /// Async version of Close
  721. /// </summary>
  722. /// <returns></returns>
  723. public Task CloseAsync()
  724. {
  725. return CloseAsync(CancellationToken.None);
  726. }
  727. public Task CloseAsync(CancellationToken cancellationToken)
  728. {
  729. var result = new TaskCompletionSource<bool>();
  730. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  731. {
  732. try
  733. {
  734. Close();
  735. result.SetResult(true);
  736. }
  737. catch (Exception ex)
  738. {
  739. result.SetException(ex);
  740. }
  741. }
  742. else
  743. {
  744. result.SetCanceled();
  745. }
  746. return result.Task;
  747. }
  748. /// <summary>
  749. /// Async version of ClearPool
  750. /// </summary>
  751. /// <param name="connection">The connection associated with the pool to be cleared.</param>
  752. /// <returns></returns>
  753. public Task ClearPoolAsync(MySqlConnection connection)
  754. {
  755. return ClearPoolAsync(connection, CancellationToken.None);
  756. }
  757. public Task ClearPoolAsync(MySqlConnection connection, CancellationToken cancellationToken)
  758. {
  759. var result = new TaskCompletionSource<bool>();
  760. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  761. {
  762. try
  763. {
  764. ClearPool(connection);
  765. result.SetResult(true);
  766. }
  767. catch (Exception ex)
  768. {
  769. result.SetException(ex);
  770. }
  771. }
  772. else
  773. {
  774. result.SetCanceled();
  775. }
  776. return result.Task;
  777. }
  778. /// <summary>
  779. /// Async version of ClearAllPools
  780. /// </summary>
  781. /// <returns></returns>
  782. public Task ClearAllPoolsAsync()
  783. {
  784. return ClearAllPoolsAsync(CancellationToken.None);
  785. }
  786. public Task ClearAllPoolsAsync(CancellationToken cancellationToken)
  787. {
  788. var result = new TaskCompletionSource<bool>();
  789. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  790. {
  791. try
  792. {
  793. ClearAllPools();
  794. result.SetResult(true);
  795. }
  796. catch (Exception ex)
  797. {
  798. result.SetException(ex);
  799. }
  800. }
  801. else
  802. {
  803. result.SetCanceled();
  804. }
  805. return result.Task;
  806. }
  807. /// <summary>
  808. /// Async version of GetSchemaCollection
  809. /// </summary>
  810. /// <param name="collectionName">Name of the collection</param>
  811. /// <param name="restrictionValues">Values to restrict</param>
  812. /// <returns>A schema collection</returns>
  813. public Task<MySqlSchemaCollection> GetSchemaCollectionAsync(string collectionName, string[] restrictionValues)
  814. {
  815. return GetSchemaCollectionAsync(collectionName, restrictionValues, CancellationToken.None);
  816. }
  817. public Task<MySqlSchemaCollection> GetSchemaCollectionAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken)
  818. {
  819. var result = new TaskCompletionSource<MySqlSchemaCollection>();
  820. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  821. {
  822. try
  823. {
  824. var schema = GetSchemaCollection(collectionName, restrictionValues);
  825. result.SetResult(schema);
  826. }
  827. catch (Exception ex)
  828. {
  829. result.SetException(ex);
  830. }
  831. }
  832. else
  833. {
  834. result.SetCanceled();
  835. }
  836. return result.Task;
  837. }
  838. #endregion
  839. }
  840. /// <summary>
  841. /// Represents the method that will handle the <see cref="MySqlConnection.InfoMessage"/> event of a
  842. /// <see cref="MySqlConnection"/>.
  843. /// </summary>
  844. internal delegate void MySqlInfoMessageEventHandler(object sender, MySqlInfoMessageEventArgs args);
  845. /// <summary>
  846. /// Provides data for the InfoMessage event. This class cannot be inherited.
  847. /// </summary>
  848. internal class MySqlInfoMessageEventArgs : EventArgs
  849. {
  850. /// <summary>
  851. ///
  852. /// </summary>
  853. public MySqlError[] errors;
  854. }
  855. /// <summary>
  856. /// IDisposable wrapper around SetCommandTimeout and ClearCommandTimeout
  857. /// functionality
  858. /// </summary>
  859. internal class CommandTimer : IDisposable
  860. {
  861. bool timeoutSet;
  862. MySqlConnection connection;
  863. public CommandTimer(MySqlConnection connection, int timeout)
  864. {
  865. this.connection = connection;
  866. if (connection != null)
  867. {
  868. timeoutSet = connection.SetCommandTimeout(timeout);
  869. }
  870. }
  871. #region IDisposable Members
  872. public void Dispose()
  873. {
  874. if (timeoutSet)
  875. {
  876. timeoutSet = false;
  877. connection.ClearCommandTimeout();
  878. connection = null;
  879. }
  880. }
  881. #endregion
  882. }
  883. }
  884. #endif