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
992 lines
34 KiB
#if MYSQL_6_10
|
|
|
|
// Copyright ?2004, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
//
|
|
// MySQL Connector/NET is licensed under the terms of the GPLv2
|
|
// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
|
|
// MySQL Connectors. There are special exceptions to the terms and
|
|
// conditions of the GPLv2 as it is applied to this software, see the
|
|
// FLOSS License Exception
|
|
// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published
|
|
// by the Free Software Foundation; version 2 of the License.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
// for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Externals.MySql.Data.Common;
|
|
using System.Security;
|
|
using IsolationLevel = System.Data.IsolationLevel;
|
|
using Externals.MySql.Data.MySqlClient.Interceptors;
|
|
using System.Transactions;
|
|
using Externals.MySql.Data.MySqlClient.Replication;
|
|
|
|
namespace Externals.MySql.Data.MySqlClient
|
|
{
|
|
internal sealed partial class MySqlConnection : DbConnection
|
|
{
|
|
internal ConnectionState connectionState;
|
|
internal Driver driver;
|
|
internal bool hasBeenOpen;
|
|
private SchemaProvider _schemaProvider;
|
|
private ExceptionInterceptor _exceptionInterceptor;
|
|
internal CommandInterceptor commandInterceptor;
|
|
private bool _isKillQueryConnection;
|
|
private string _database;
|
|
private int _commandTimeout;
|
|
|
|
public event MySqlInfoMessageEventHandler InfoMessage;
|
|
|
|
private static readonly Cache<string, MySqlConnectionStringBuilder> ConnectionStringCache =
|
|
new Cache<string, MySqlConnectionStringBuilder>(0, 25);
|
|
|
|
public MySqlConnection()
|
|
{
|
|
//TODO: add event data to StateChange docs
|
|
Settings = new MySqlConnectionStringBuilder();
|
|
_database = String.Empty;
|
|
|
|
//#if NET_CORE
|
|
//TODO: what is thi sabout
|
|
//ConnectionString = Startup.ConnectionString;
|
|
//#endif
|
|
}
|
|
|
|
public MySqlConnection(string connectionString)
|
|
: this()
|
|
{
|
|
ConnectionString = connectionString;
|
|
}
|
|
|
|
#region Destructor
|
|
~MySqlConnection()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
#endregion
|
|
|
|
#region Interal Methods & Properties
|
|
|
|
internal PerformanceMonitor PerfMonitor { get; private set; }
|
|
|
|
internal ProcedureCache ProcedureCache { get; private set; }
|
|
|
|
internal MySqlConnectionStringBuilder Settings { get; private set; }
|
|
|
|
internal MySqlDataReader Reader
|
|
{
|
|
get
|
|
{
|
|
return driver?.reader;
|
|
}
|
|
set
|
|
{
|
|
driver.reader = value;
|
|
IsInUse = driver.reader != null;
|
|
}
|
|
}
|
|
|
|
internal void OnInfoMessage(MySqlInfoMessageEventArgs args)
|
|
{
|
|
InfoMessage?.Invoke(this, args);
|
|
}
|
|
|
|
internal bool SoftClosed
|
|
{
|
|
get
|
|
{
|
|
#if !NETSTANDARD1_3
|
|
return (State == ConnectionState.Closed) &&
|
|
driver != null && driver.currentTransaction != null;
|
|
#else
|
|
return (State == ConnectionState.Closed) &&
|
|
driver != null;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
internal bool IsInUse { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Returns the id of the server thread this connection is executing on
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public int ServerThread => driver.ThreadID;
|
|
|
|
/// <summary>
|
|
/// Gets the name of the MySQL server to which to connect.
|
|
/// </summary>
|
|
[Browsable(true)]
|
|
public override string DataSource => Settings.Server;
|
|
|
|
[Browsable(true)]
|
|
public override int ConnectionTimeout => (int)Settings.ConnectionTimeout;
|
|
|
|
[Browsable(true)]
|
|
public override string Database => _database;
|
|
|
|
/// <summary>
|
|
/// Indicates if this connection should use compression when communicating with the server.
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public bool UseCompression => Settings.UseCompression;
|
|
|
|
[Browsable(false)]
|
|
public override ConnectionState State => connectionState;
|
|
|
|
[Browsable(false)]
|
|
public override string ServerVersion => driver.Version.ToString();
|
|
|
|
[Browsable(true)]
|
|
[Category("Data")]
|
|
[Description("Information used to connect to a DataSource, such as 'Server=xxx;UserId=yyy;Password=zzz;Database=dbdb'.")]
|
|
public override string ConnectionString
|
|
{
|
|
get
|
|
{
|
|
// Always return exactly what the user set.
|
|
// Security-sensitive information may be removed.
|
|
return Settings.GetConnectionString(!hasBeenOpen || Settings.PersistSecurityInfo);
|
|
}
|
|
set
|
|
{
|
|
if (State != ConnectionState.Closed)
|
|
Throw(new MySqlException(
|
|
"Not allowed to change the 'ConnectionString' property while the connection (state=" + State + ")."));
|
|
|
|
MySqlConnectionStringBuilder newSettings;
|
|
lock (ConnectionStringCache)
|
|
{
|
|
if (value == null)
|
|
newSettings = new MySqlConnectionStringBuilder();
|
|
else
|
|
{
|
|
newSettings = ConnectionStringCache[value];
|
|
if (null == newSettings)
|
|
{
|
|
newSettings = new MySqlConnectionStringBuilder(value);
|
|
ConnectionStringCache.Add(value, newSettings);
|
|
}
|
|
}
|
|
}
|
|
|
|
Settings = newSettings;
|
|
|
|
if (!string.IsNullOrEmpty(Settings.Database))
|
|
_database = Settings.Database;
|
|
|
|
if (driver != null)
|
|
driver.Settings = newSettings;
|
|
}
|
|
}
|
|
|
|
#if !NETSTANDARD1_3
|
|
protected override DbProviderFactory DbProviderFactory => MySqlClientFactory.Instance;
|
|
#endif
|
|
/// <summary>
|
|
/// Gets a boolean value that indicates whether the password associated to the connection is expired.
|
|
/// </summary>
|
|
public bool IsPasswordExpired => driver.IsPasswordExpired;
|
|
|
|
#endregion
|
|
|
|
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
|
|
{
|
|
if (isolationLevel == IsolationLevel.Unspecified)
|
|
return BeginTransaction();
|
|
return BeginTransaction(isolationLevel);
|
|
}
|
|
|
|
protected override DbCommand CreateDbCommand()
|
|
{
|
|
return CreateCommand();
|
|
}
|
|
|
|
|
|
#region IDisposeable
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (State == ConnectionState.Open)
|
|
Close();
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Transactions
|
|
public new MySqlTransaction BeginTransaction()
|
|
{
|
|
return BeginTransaction(IsolationLevel.RepeatableRead);
|
|
}
|
|
|
|
public new MySqlTransaction BeginTransaction(IsolationLevel iso)
|
|
{
|
|
//TODO: check note in help
|
|
if (State != ConnectionState.Open)
|
|
Throw(new InvalidOperationException(Resources.ConnectionNotOpen));
|
|
|
|
// First check to see if we are in a current transaction
|
|
if (driver.HasStatus(ServerStatusFlags.InTransaction))
|
|
Throw(new InvalidOperationException(Resources.NoNestedTransactions));
|
|
|
|
MySqlTransaction t = new MySqlTransaction(this, iso);
|
|
|
|
MySqlCommand cmd = new MySqlCommand("", this);
|
|
|
|
cmd.CommandText = "SET SESSION TRANSACTION ISOLATION LEVEL ";
|
|
switch (iso)
|
|
{
|
|
case IsolationLevel.ReadCommitted:
|
|
cmd.CommandText += "READ COMMITTED";
|
|
break;
|
|
case IsolationLevel.ReadUncommitted:
|
|
cmd.CommandText += "READ UNCOMMITTED";
|
|
break;
|
|
case IsolationLevel.RepeatableRead:
|
|
cmd.CommandText += "REPEATABLE READ";
|
|
break;
|
|
case IsolationLevel.Serializable:
|
|
cmd.CommandText += "SERIALIZABLE";
|
|
break;
|
|
case IsolationLevel.Chaos:
|
|
Throw(new NotSupportedException(Resources.ChaosNotSupported));
|
|
break;
|
|
case IsolationLevel.Snapshot:
|
|
Throw(new NotSupportedException(Resources.SnapshotNotSupported));
|
|
break;
|
|
}
|
|
|
|
cmd.ExecuteNonQuery();
|
|
|
|
cmd.CommandText = "BEGIN";
|
|
cmd.CommandType = CommandType.Text;
|
|
cmd.ExecuteNonQuery();
|
|
|
|
return t;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override void ChangeDatabase(string databaseName)
|
|
{
|
|
if (databaseName == null || databaseName.Trim().Length == 0)
|
|
Throw(new ArgumentException(Resources.ParameterIsInvalid, "databaseName"));
|
|
|
|
if (State != ConnectionState.Open)
|
|
Throw(new InvalidOperationException(Resources.ConnectionNotOpen));
|
|
|
|
// This lock prevents promotable transaction rollback to run
|
|
// in parallel
|
|
lock (driver)
|
|
{
|
|
// We use default command timeout for SetDatabase
|
|
using (new CommandTimer(this, (int)Settings.DefaultCommandTimeout))
|
|
{
|
|
driver.SetDatabase(databaseName);
|
|
}
|
|
}
|
|
this._database = databaseName;
|
|
}
|
|
|
|
internal void SetState(ConnectionState newConnectionState, bool broadcast)
|
|
{
|
|
if (newConnectionState == connectionState && !broadcast)
|
|
return;
|
|
ConnectionState oldConnectionState = connectionState;
|
|
connectionState = newConnectionState;
|
|
if (broadcast)
|
|
OnStateChange(new StateChangeEventArgs(oldConnectionState, connectionState));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pings the server.
|
|
/// </summary>
|
|
/// <returns><c>true</c> if the ping was successful; otherwise, <c>false</c>.</returns>
|
|
public bool Ping()
|
|
{
|
|
if (Reader != null)
|
|
Throw(new MySqlException(Resources.DataReaderOpen));
|
|
if (driver != null && driver.Ping())
|
|
return true;
|
|
driver = null;
|
|
SetState(ConnectionState.Closed, true);
|
|
return false;
|
|
}
|
|
|
|
public override void Open()
|
|
{
|
|
if (State == ConnectionState.Open)
|
|
Throw(new InvalidOperationException(Resources.ConnectionAlreadyOpen));
|
|
|
|
// start up our interceptors
|
|
_exceptionInterceptor = new ExceptionInterceptor(this);
|
|
commandInterceptor = new CommandInterceptor(this);
|
|
|
|
SetState(ConnectionState.Connecting, true);
|
|
|
|
#if !NETSTANDARD1_3
|
|
AssertPermissions();
|
|
|
|
//TODO: SUPPORT FOR 452 AND 46X
|
|
// if we are auto enlisting in a current transaction, then we will be
|
|
// treating the connection as pooled
|
|
if (Settings.AutoEnlist && Transaction.Current != null)
|
|
{
|
|
driver = DriverTransactionManager.GetDriverInTransaction(Transaction.Current);
|
|
if (driver != null &&
|
|
(driver.IsInActiveUse ||
|
|
!driver.Settings.EquivalentTo(this.Settings)))
|
|
Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported));
|
|
}
|
|
|
|
#endif
|
|
try
|
|
{
|
|
MySqlConnectionStringBuilder currentSettings = Settings;
|
|
|
|
//TODO: SUPPORT FOR 452 AND 46X
|
|
// Load balancing
|
|
#if !NETSTANDARD1_3
|
|
if (ReplicationManager.IsReplicationGroup(Settings.Server))
|
|
{
|
|
if (driver == null)
|
|
{
|
|
ReplicationManager.GetNewConnection(Settings.Server, false, this);
|
|
}
|
|
else
|
|
currentSettings = driver.Settings;
|
|
}
|
|
#endif
|
|
|
|
if (Settings.Pooling)
|
|
{
|
|
MySqlPool pool = MySqlPoolManager.GetPool(currentSettings);
|
|
if (driver == null || !driver.IsOpen)
|
|
driver = pool.GetConnection();
|
|
ProcedureCache = pool.ProcedureCache;
|
|
|
|
}
|
|
else
|
|
{
|
|
if (driver == null || !driver.IsOpen)
|
|
driver = Driver.Create(currentSettings);
|
|
ProcedureCache = new ProcedureCache((int)Settings.ProcedureCacheSize);
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
SetState(ConnectionState.Closed, true);
|
|
throw;
|
|
}
|
|
|
|
SetState(ConnectionState.Open, false);
|
|
driver.Configure(this);
|
|
|
|
if (!(driver.SupportsPasswordExpiration && driver.IsPasswordExpired))
|
|
{
|
|
if (!string.IsNullOrEmpty(Settings.Database))
|
|
ChangeDatabase(Settings.Database);
|
|
}
|
|
|
|
// setup our schema provider
|
|
_schemaProvider = new ISSchemaProvider(this);
|
|
PerfMonitor = new PerformanceMonitor(this);
|
|
|
|
// if we are opening up inside a current transaction, then autoenlist
|
|
// TODO: control this with a connection string option
|
|
#if !NETSTANDARD1_3
|
|
if (Transaction.Current != null && Settings.AutoEnlist)
|
|
EnlistTransaction(Transaction.Current);
|
|
#endif
|
|
|
|
hasBeenOpen = true;
|
|
SetState(ConnectionState.Open, true);
|
|
}
|
|
|
|
public new MySqlCommand CreateCommand()
|
|
{
|
|
// Return a new instance of a command object.
|
|
MySqlCommand c = new MySqlCommand();
|
|
c.Connection = this;
|
|
return c;
|
|
}
|
|
|
|
internal void Abort()
|
|
{
|
|
try
|
|
{
|
|
driver.Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MySqlTrace.LogWarning(ServerThread, String.Concat("Error occurred aborting the connection. Exception was: ", ex.Message));
|
|
}
|
|
finally
|
|
{
|
|
this.IsInUse = false;
|
|
}
|
|
SetState(ConnectionState.Closed, true);
|
|
}
|
|
|
|
internal void CloseFully()
|
|
{
|
|
if (Settings.Pooling && driver.IsOpen)
|
|
{
|
|
#if !NETSTANDARD1_3
|
|
//TODO: SUPPORT FOR 452 AND 46X
|
|
//// if we are in a transaction, roll it back
|
|
if (driver.HasStatus(ServerStatusFlags.InTransaction))
|
|
{
|
|
Externals.MySql.Data.MySqlClient.MySqlTransaction t = new Externals.MySql.Data.MySqlClient.MySqlTransaction(this, IsolationLevel.Unspecified);
|
|
t.Rollback();
|
|
}
|
|
#endif
|
|
|
|
MySqlPoolManager.ReleaseConnection(driver);
|
|
}
|
|
else
|
|
driver.Close();
|
|
driver = null;
|
|
}
|
|
|
|
public override void Close()
|
|
{
|
|
if (driver != null)
|
|
driver.IsPasswordExpired = false;
|
|
|
|
if (State == ConnectionState.Closed) return;
|
|
|
|
if (Reader != null)
|
|
Reader.Close();
|
|
|
|
// if the reader was opened with CloseConnection then driver
|
|
// will be null on the second time through
|
|
if (driver != null)
|
|
{
|
|
#if !NETSTANDARD1_3
|
|
//TODO: Add support for 452 and 46X
|
|
if (driver.currentTransaction == null)
|
|
#endif
|
|
CloseFully();
|
|
#if !NETSTANDARD1_3
|
|
//TODO: Add support for 452 and 46X
|
|
else
|
|
driver.IsInActiveUse = false;
|
|
#endif
|
|
}
|
|
|
|
SetState(ConnectionState.Closed, true);
|
|
}
|
|
|
|
internal string CurrentDatabase()
|
|
{
|
|
if (!string.IsNullOrEmpty(Database))
|
|
return Database;
|
|
MySqlCommand cmd = new MySqlCommand("SELECT database()", this);
|
|
return cmd.ExecuteScalar().ToString();
|
|
}
|
|
|
|
internal void HandleTimeoutOrThreadAbort(Exception ex)
|
|
{
|
|
bool isFatal = false;
|
|
|
|
if (_isKillQueryConnection)
|
|
{
|
|
// Special connection started to cancel a query.
|
|
// Abort will prevent recursive connection spawning
|
|
Abort();
|
|
if (ex is TimeoutException)
|
|
{
|
|
Throw(new MySqlException(Resources.Timeout, true, ex));
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
// Do a fast cancel.The reason behind small values for connection
|
|
// and command timeout is that we do not want user to wait longer
|
|
// after command has already expired.
|
|
// Microsoft's SqlClient seems to be using 5 seconds timeouts
|
|
// here as well.
|
|
// Read the error packet with "interrupted" message.
|
|
CancelQuery(5);
|
|
driver.ResetTimeout(5000);
|
|
if (Reader != null)
|
|
{
|
|
Reader.Close();
|
|
Reader = null;
|
|
}
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
MySqlTrace.LogWarning(ServerThread, "Could not kill query, " +
|
|
" aborting connection. Exception was " + ex2.Message);
|
|
Abort();
|
|
isFatal = true;
|
|
}
|
|
if (ex is TimeoutException)
|
|
{
|
|
Throw(new MySqlException(Resources.Timeout, isFatal, ex));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels the query after the specified time interval.
|
|
/// </summary>
|
|
/// <param name="timeout">The length of time (in seconds) to wait for the cancelation of the command execution.</param>
|
|
public void CancelQuery(int timeout)
|
|
{
|
|
MySqlConnectionStringBuilder cb = new MySqlConnectionStringBuilder(
|
|
Settings.ConnectionString);
|
|
cb.Pooling = false;
|
|
#if !NETSTANDARD1_3
|
|
cb.AutoEnlist = false;
|
|
#endif
|
|
cb.ConnectionTimeout = (uint)timeout;
|
|
|
|
using (MySqlConnection c = new MySqlConnection(cb.ConnectionString))
|
|
{
|
|
c._isKillQueryConnection = true;
|
|
c.Open();
|
|
string commandText = "KILL QUERY " + ServerThread;
|
|
MySqlCommand cmd = new MySqlCommand(commandText, c) { CommandTimeout = timeout };
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
#region Routines for timeout support.
|
|
|
|
// Problem description:
|
|
// Sometimes, ExecuteReader is called recursively. This is the case if
|
|
// command behaviors are used and we issue "set sql_select_limit"
|
|
// before and after command. This is also the case with prepared
|
|
// statements , where we set session variables. In these situations, we
|
|
// have to prevent recursive ExecuteReader calls from overwriting
|
|
// timeouts set by the top level command.
|
|
|
|
// To solve the problem, SetCommandTimeout() and ClearCommandTimeout() are
|
|
// introduced . Query timeout here is "sticky", that is once set with
|
|
// SetCommandTimeout, it only be overwritten after ClearCommandTimeout
|
|
// (SetCommandTimeout would return false if it timeout has not been
|
|
// cleared).
|
|
|
|
// The proposed usage pattern of there routines is following:
|
|
// When timed operations starts, issue SetCommandTimeout(). When it
|
|
// finishes, issue ClearCommandTimeout(), but _only_ if call to
|
|
// SetCommandTimeout() was successful.
|
|
|
|
|
|
/// <summary>
|
|
/// Sets query timeout. If timeout has been set prior and not
|
|
/// yet cleared ClearCommandTimeout(), it has no effect.
|
|
/// </summary>
|
|
/// <param name="value">timeout in seconds</param>
|
|
/// <returns>true if </returns>
|
|
internal bool SetCommandTimeout(int value)
|
|
{
|
|
if (!hasBeenOpen)
|
|
// Connection timeout is handled by driver
|
|
return false;
|
|
|
|
if (_commandTimeout != 0)
|
|
// someone is trying to set a timeout while command is already
|
|
// running. It could be for example recursive call to ExecuteReader
|
|
// Ignore the request, as only top-level (non-recursive commands)
|
|
// can set timeouts.
|
|
return false;
|
|
|
|
if (driver == null)
|
|
return false;
|
|
|
|
_commandTimeout = value;
|
|
driver.ResetTimeout(_commandTimeout * 1000);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears query timeout, allowing next SetCommandTimeout() to succeed.
|
|
/// </summary>
|
|
internal void ClearCommandTimeout()
|
|
{
|
|
if (!hasBeenOpen)
|
|
return;
|
|
_commandTimeout = 0;
|
|
driver?.ResetTimeout(0);
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Gets a schema collection based on the provided restriction values.
|
|
/// </summary>
|
|
/// <param name="collectionName">The name of the collection.</param>
|
|
/// <param name="restrictionValues">The values to restrict.</param>
|
|
/// <returns>A schema collection object.</returns>
|
|
public MySqlSchemaCollection GetSchemaCollection(string collectionName, string[] restrictionValues)
|
|
{
|
|
if (collectionName == null)
|
|
collectionName = SchemaProvider.MetaCollection;
|
|
|
|
string[] restrictions = _schemaProvider.CleanRestrictions(restrictionValues);
|
|
MySqlSchemaCollection c = _schemaProvider.GetSchema(collectionName, restrictions);
|
|
return c;
|
|
}
|
|
|
|
#region Pool Routines
|
|
|
|
public static void ClearPool(MySqlConnection connection)
|
|
{
|
|
MySqlPoolManager.ClearPool(connection.Settings);
|
|
}
|
|
|
|
public static void ClearAllPools()
|
|
{
|
|
MySqlPoolManager.ClearAllPools();
|
|
}
|
|
|
|
#endregion
|
|
|
|
internal void Throw(Exception ex)
|
|
{
|
|
#if !NETSTANDARD1_3
|
|
if (_exceptionInterceptor == null)
|
|
throw ex;
|
|
_exceptionInterceptor.Throw(ex);
|
|
#else
|
|
throw ex;
|
|
#endif
|
|
}
|
|
|
|
public new void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#region Async
|
|
/// <summary>
|
|
/// Initiates the asynchronous execution of a transaction.
|
|
/// </summary>
|
|
/// <returns>An object representing the new transaction.</returns>
|
|
public Task<MySqlTransaction> BeginTransactionAsync()
|
|
{
|
|
return BeginTransactionAsync(IsolationLevel.RepeatableRead, CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of BeginTransaction.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>An object representing the new transaction.</returns>
|
|
public Task<MySqlTransaction> BeginTransactionAsync(CancellationToken cancellationToken)
|
|
{
|
|
return BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of BeginTransaction.
|
|
/// </summary>
|
|
/// <param name="iso">The isolation level under which the transaction should run. </param>
|
|
/// <returns>An object representing the new transaction.</returns>
|
|
public Task<MySqlTransaction> BeginTransactionAsync(IsolationLevel iso)
|
|
{
|
|
return BeginTransactionAsync(iso, CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of BeginTransaction.
|
|
/// </summary>
|
|
/// <param name="iso">The isolation level under which the transaction should run. </param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>An object representing the new transaction.</returns>
|
|
public Task<MySqlTransaction> BeginTransactionAsync(IsolationLevel iso, CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<MySqlTransaction>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
MySqlTransaction tranResult = BeginTransaction(iso);
|
|
result.SetResult(tranResult);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.SetCanceled();
|
|
}
|
|
|
|
return result.Task;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the ChangeDataBase method.
|
|
/// </summary>
|
|
/// <param name="databaseName">The name of the database to use.</param>
|
|
/// <returns></returns>
|
|
public Task ChangeDataBaseAsync(string databaseName)
|
|
{
|
|
return ChangeDataBaseAsync(databaseName, CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the ChangeDataBase method.
|
|
/// </summary>
|
|
/// <param name="databaseName">The name of the database to use.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns></returns>
|
|
public Task ChangeDataBaseAsync(string databaseName, CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<bool>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
ChangeDatabase(databaseName);
|
|
result.SetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
return result.Task;
|
|
}
|
|
|
|
///// <summary>
|
|
///// Async version of Open
|
|
///// </summary>
|
|
///// <returns></returns>
|
|
//public Task OpenAsync()
|
|
//{
|
|
// return Task.Run(() =>
|
|
// {
|
|
// Open();
|
|
// });
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the Close method.
|
|
/// </summary>
|
|
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
|
public override Task CloseAsync()
|
|
#else
|
|
public Task CloseAsync()
|
|
#endif
|
|
{
|
|
return CloseAsync(CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the Close method.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
public Task CloseAsync(CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<bool>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
Close();
|
|
result.SetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.SetCanceled();
|
|
}
|
|
return result.Task;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the ClearPool method.
|
|
/// </summary>
|
|
/// <param name="connection">The connection associated with the pool to be cleared.</param>
|
|
public Task ClearPoolAsync(MySqlConnection connection)
|
|
{
|
|
return ClearPoolAsync(connection, CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the ClearPool method.
|
|
/// </summary>
|
|
/// <param name="connection">The connection associated with the pool to be cleared.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
public Task ClearPoolAsync(MySqlConnection connection, CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<bool>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
ClearPool(connection);
|
|
result.SetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.SetCanceled();
|
|
}
|
|
return result.Task;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the ClearAllPools method.
|
|
/// </summary>
|
|
public Task ClearAllPoolsAsync()
|
|
{
|
|
return ClearAllPoolsAsync(CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the ClearAllPools method.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
public Task ClearAllPoolsAsync(CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<bool>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
ClearAllPools();
|
|
result.SetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.SetCanceled();
|
|
}
|
|
return result.Task;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the GetSchemaCollection method.
|
|
/// </summary>
|
|
/// <param name="collectionName">The name of the collection.</param>
|
|
/// <param name="restrictionValues">The values to restrict.</param>
|
|
/// <returns>A collection of schema objects.</returns>
|
|
public Task<MySqlSchemaCollection> GetSchemaCollectionAsync(string collectionName, string[] restrictionValues)
|
|
{
|
|
return GetSchemaCollectionAsync(collectionName, restrictionValues, CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous version of the GetSchemaCollection method.
|
|
/// </summary>
|
|
/// <param name="collectionName">The name of the collection.</param>
|
|
/// <param name="restrictionValues">The values to restrict.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>A collection of schema objects.</returns>
|
|
public Task<MySqlSchemaCollection> GetSchemaCollectionAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<MySqlSchemaCollection>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
var schema = GetSchemaCollection(collectionName, restrictionValues);
|
|
result.SetResult(schema);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.SetCanceled();
|
|
}
|
|
return result.Task;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents the method that will handle the <see cref="MySqlConnection.InfoMessage"/> event of a
|
|
/// <see cref="MySqlConnection"/>.
|
|
/// </summary>
|
|
internal delegate void MySqlInfoMessageEventHandler(object sender, MySqlInfoMessageEventArgs args);
|
|
|
|
/// <summary>
|
|
/// Provides data for the InfoMessage event. This class cannot be inherited.
|
|
/// </summary>
|
|
internal class MySqlInfoMessageEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets an array of <see cref="MySqlError"/> objects set with the errors found.
|
|
/// </summary>
|
|
public MySqlError[] errors { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// IDisposable wrapper around SetCommandTimeout and ClearCommandTimeout functionality.
|
|
/// </summary>
|
|
internal class CommandTimer : IDisposable
|
|
{
|
|
private bool _timeoutSet;
|
|
private MySqlConnection _connection;
|
|
|
|
public CommandTimer(MySqlConnection connection, int timeout)
|
|
{
|
|
_connection = connection;
|
|
if (connection != null)
|
|
{
|
|
_timeoutSet = connection.SetCommandTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
#region IDisposable Members
|
|
public void Dispose()
|
|
{
|
|
if (!_timeoutSet) return;
|
|
|
|
_timeoutSet = false;
|
|
_connection.ClearCommandTimeout();
|
|
_connection = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|