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.

992 lines
34 KiB

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