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
1017 lines
34 KiB
// Copyright ?2004, 2014, 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
|
|
|
|
#if MYSQL_6_9
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Transactions;
|
|
using IsolationLevel = System.Data.IsolationLevel;
|
|
using System.Drawing;
|
|
using System.Drawing.Design;
|
|
using System.Text;
|
|
using Externals.MySql.Data.Common;
|
|
using System.Diagnostics;
|
|
using Externals.MySql.Data.MySqlClient.Properties;
|
|
using Externals.MySql.Data.MySqlClient.Replication;
|
|
using System.Threading.Tasks;
|
|
using System.Threading;
|
|
|
|
namespace Externals.MySql.Data.MySqlClient
|
|
{
|
|
|
|
internal sealed partial class MySqlConnection : IDisposable
|
|
{
|
|
internal ConnectionState connectionState;
|
|
internal Driver driver;
|
|
internal bool hasBeenOpen;
|
|
private SchemaProvider schemaProvider;
|
|
private ProcedureCache procedureCache;
|
|
private bool isInUse;
|
|
private PerformanceMonitor perfMonitor;
|
|
private ExceptionInterceptor exceptionInterceptor;
|
|
internal CommandInterceptor commandInterceptor;
|
|
private bool isKillQueryConnection;
|
|
private string database;
|
|
private int commandTimeout;
|
|
|
|
public event MySqlInfoMessageEventHandler InfoMessage;
|
|
|
|
private static 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;
|
|
}
|
|
|
|
public MySqlConnection(string connectionString)
|
|
: this()
|
|
{
|
|
ConnectionString = connectionString;
|
|
}
|
|
|
|
#region Destructor
|
|
~MySqlConnection()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
#endregion
|
|
|
|
#region Interal Methods & Properties
|
|
|
|
internal PerformanceMonitor PerfMonitor
|
|
{
|
|
get { return perfMonitor; }
|
|
}
|
|
|
|
internal ProcedureCache ProcedureCache
|
|
{
|
|
get { return procedureCache; }
|
|
}
|
|
|
|
internal MySqlConnectionStringBuilder Settings { get; private set; }
|
|
|
|
internal MySqlDataReader Reader
|
|
{
|
|
get
|
|
{
|
|
if (driver == null)
|
|
return null;
|
|
return driver.reader;
|
|
}
|
|
set
|
|
{
|
|
driver.reader = value;
|
|
isInUse = driver.reader != null;
|
|
}
|
|
}
|
|
|
|
internal void OnInfoMessage(MySqlInfoMessageEventArgs args)
|
|
{
|
|
if (InfoMessage != null)
|
|
{
|
|
InfoMessage(this, args);
|
|
}
|
|
}
|
|
|
|
internal bool SoftClosed
|
|
{
|
|
get
|
|
{
|
|
return (State == ConnectionState.Closed) &&
|
|
driver != null &&
|
|
driver.CurrentTransaction != null;
|
|
}
|
|
}
|
|
|
|
internal bool IsInUse
|
|
{
|
|
get { return isInUse; }
|
|
set { isInUse = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Returns the id of the server thread this connection is executing on
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public int ServerThread
|
|
{
|
|
get { return driver.ThreadID; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the MySQL server to which to connect.
|
|
/// </summary>
|
|
[Browsable(true)]
|
|
public override string DataSource
|
|
{
|
|
get { return Settings.Server; }
|
|
}
|
|
|
|
[Browsable(true)]
|
|
public override int ConnectionTimeout
|
|
{
|
|
get { return (int)Settings.ConnectionTimeout; }
|
|
}
|
|
|
|
[Browsable(true)]
|
|
public override string Database
|
|
{
|
|
get { return database; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates if this connection should use compression when communicating with the server.
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public bool UseCompression
|
|
{
|
|
get { return Settings.UseCompression; }
|
|
}
|
|
|
|
[Browsable(false)]
|
|
public override ConnectionState State
|
|
{
|
|
get { return connectionState; }
|
|
}
|
|
|
|
[Browsable(false)]
|
|
public override string ServerVersion
|
|
{
|
|
get { return driver.Version.ToString(); }
|
|
}
|
|
|
|
[Editor("Externals.MySql.Data.MySqlClient.Design.ConnectionStringTypeEditor,MySqlClient.Design", typeof(UITypeEditor))]
|
|
[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 = (MySqlConnectionStringBuilder)connectionStringCache[value];
|
|
if (null == newSettings)
|
|
{
|
|
newSettings = new MySqlConnectionStringBuilder(value);
|
|
connectionStringCache.Add(value, newSettings);
|
|
}
|
|
}
|
|
}
|
|
|
|
Settings = newSettings;
|
|
|
|
if (Settings.Database != null && Settings.Database.Length > 0)
|
|
this.database = Settings.Database;
|
|
|
|
if (driver != null)
|
|
driver.Settings = newSettings;
|
|
}
|
|
}
|
|
|
|
protected override DbProviderFactory DbProviderFactory
|
|
{
|
|
get
|
|
{
|
|
return MySqlClientFactory.Instance;
|
|
}
|
|
}
|
|
|
|
public bool IsPasswordExpired { get { return driver.IsPasswordExpired; } }
|
|
|
|
#endregion
|
|
|
|
partial void AssertPermissions();
|
|
|
|
#region Transactions
|
|
|
|
/// <summary>
|
|
/// Enlists in the specified transaction.
|
|
/// </summary>
|
|
/// <param name="transaction">
|
|
/// A reference to an existing <see cref="System.Transactions.Transaction"/> in which to enlist.
|
|
/// </param>
|
|
public override void EnlistTransaction(Transaction transaction)
|
|
{
|
|
// enlisting in the null transaction is a noop
|
|
if (transaction == null)
|
|
return;
|
|
|
|
// guard against trying to enlist in more than one transaction
|
|
if (driver.CurrentTransaction != null)
|
|
{
|
|
if (driver.CurrentTransaction.BaseTransaction == transaction)
|
|
return;
|
|
|
|
Throw(new MySqlException("Already enlisted"));
|
|
}
|
|
|
|
// now see if we need to swap out drivers. We would need to do this since
|
|
// we have to make sure all ops for a given transaction are done on the
|
|
// same physical connection.
|
|
Driver existingDriver = DriverTransactionManager.GetDriverInTransaction(transaction);
|
|
if (existingDriver != null)
|
|
{
|
|
// we can't allow more than one driver to contribute to the same connection
|
|
if (existingDriver.IsInActiveUse)
|
|
Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported));
|
|
|
|
// there is an existing driver and it's not being currently used.
|
|
// now we need to see if it is using the same connection string
|
|
string text1 = existingDriver.Settings.ConnectionString;
|
|
string text2 = Settings.ConnectionString;
|
|
if (String.Compare(text1, text2, true) != 0)
|
|
Throw(new NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported));
|
|
|
|
// close existing driver
|
|
// set this new driver as our existing driver
|
|
CloseFully();
|
|
driver = existingDriver;
|
|
}
|
|
|
|
if (driver.CurrentTransaction == null)
|
|
{
|
|
MySqlPromotableTransaction t = new MySqlPromotableTransaction(this, transaction);
|
|
if (!transaction.EnlistPromotableSinglePhase(t))
|
|
Throw(new NotSupportedException(Resources.DistributedTxnNotSupported));
|
|
|
|
driver.CurrentTransaction = t;
|
|
DriverTransactionManager.SetDriverInTransaction(driver);
|
|
driver.IsInActiveUse = true;
|
|
}
|
|
}
|
|
|
|
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.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)
|
|
{
|
|
if (Transaction.Current != null &&
|
|
Transaction.Current.TransactionInformation.Status == TransactionStatus.Aborted)
|
|
{
|
|
Throw(new TransactionAbortedException());
|
|
}
|
|
|
|
// 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>
|
|
/// Ping
|
|
/// </summary>
|
|
/// <returns></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);
|
|
|
|
AssertPermissions();
|
|
|
|
// 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));
|
|
}
|
|
|
|
try
|
|
{
|
|
MySqlConnectionStringBuilder currentSettings = Settings;
|
|
|
|
// Load balancing
|
|
if (ReplicationManager.IsReplicationGroup(Settings.Server))
|
|
{
|
|
if (driver == null)
|
|
{
|
|
ReplicationManager.GetNewConnection(Settings.Server, false, this);
|
|
}
|
|
else
|
|
currentSettings = driver.Settings;
|
|
}
|
|
|
|
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
|
|
{
|
|
SetState(ConnectionState.Closed, true);
|
|
throw;
|
|
}
|
|
|
|
// if the user is using old syntax, let them know
|
|
if (driver.Settings.UseOldSyntax) MySqlTrace.LogWarning(ServerThread, "You are using old syntax that will be removed in future versions");
|
|
|
|
SetState(ConnectionState.Open, false);
|
|
driver.Configure(this);
|
|
|
|
if (!(driver.SupportsPasswordExpiration && driver.IsPasswordExpired))
|
|
{
|
|
if (Settings.Database != null && Settings.Database != String.Empty)
|
|
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 (Transaction.Current != null && Settings.AutoEnlist)
|
|
EnlistTransaction(Transaction.Current);
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new MySqlConnection object with the exact same ConnectionString value
|
|
/// </summary>
|
|
/// <returns>A cloned MySqlConnection object</returns>
|
|
public object Clone()
|
|
{
|
|
MySqlConnection clone = new MySqlConnection();
|
|
string connectionString = Settings.ConnectionString;
|
|
if (connectionString != null)
|
|
clone.ConnectionString = connectionString;
|
|
return clone;
|
|
}
|
|
|
|
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 we are in a transaction, roll it back
|
|
if (driver.HasStatus(ServerStatusFlags.InTransaction))
|
|
{
|
|
MySqlTransaction t = new MySqlTransaction(this, IsolationLevel.Unspecified);
|
|
t.Rollback();
|
|
}
|
|
|
|
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 (driver.CurrentTransaction == null)
|
|
CloseFully();
|
|
else
|
|
driver.IsInActiveUse = false;
|
|
}
|
|
|
|
SetState(ConnectionState.Closed, true);
|
|
}
|
|
|
|
internal string CurrentDatabase()
|
|
{
|
|
if (Database != null && Database.Length > 0)
|
|
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));
|
|
}
|
|
}
|
|
|
|
public void CancelQuery(int timeout)
|
|
{
|
|
MySqlConnectionStringBuilder cb = new MySqlConnectionStringBuilder(
|
|
Settings.ConnectionString);
|
|
cb.Pooling = false;
|
|
cb.AutoEnlist = false;
|
|
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);
|
|
cmd.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;
|
|
if (driver != null)
|
|
{
|
|
driver.ResetTimeout(0);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
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)
|
|
{
|
|
throw ex;
|
|
}
|
|
|
|
public new void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#region Async
|
|
/// <summary>
|
|
/// Async version of BeginTransaction
|
|
/// </summary>
|
|
/// <returns>An object representing the new transaction.</returns>
|
|
public Task<MySqlTransaction> BeginTransactionAsync()
|
|
{
|
|
return BeginTransactionAsync(IsolationLevel.RepeatableRead, CancellationToken.None);
|
|
}
|
|
|
|
public Task<MySqlTransaction> BeginTransactionAsync(CancellationToken cancellationToken)
|
|
{
|
|
return BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Async 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public Task ChangeDataBaseAsync(string databaseName)
|
|
{
|
|
return ChangeDataBaseAsync(databaseName, CancellationToken.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Async version of ChangeDataBase
|
|
/// </summary>
|
|
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>
|
|
/// Async version of Close
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Task CloseAsync()
|
|
{
|
|
return CloseAsync(CancellationToken.None);
|
|
}
|
|
|
|
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>
|
|
/// Async version of ClearPool
|
|
/// </summary>
|
|
/// <param name="connection">The connection associated with the pool to be cleared.</param>
|
|
/// <returns></returns>
|
|
public Task ClearPoolAsync(MySqlConnection connection)
|
|
{
|
|
return ClearPoolAsync(connection, CancellationToken.None);
|
|
}
|
|
|
|
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>
|
|
/// Async version of ClearAllPools
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Task ClearAllPoolsAsync()
|
|
{
|
|
return ClearAllPoolsAsync(CancellationToken.None);
|
|
}
|
|
|
|
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>
|
|
/// Async version of GetSchemaCollection
|
|
/// </summary>
|
|
/// <param name="collectionName">Name of the collection</param>
|
|
/// <param name="restrictionValues">Values to restrict</param>
|
|
/// <returns>A schema collection</returns>
|
|
public Task<MySqlSchemaCollection> GetSchemaCollectionAsync(string collectionName, string[] restrictionValues)
|
|
{
|
|
return GetSchemaCollectionAsync(collectionName, restrictionValues, CancellationToken.None);
|
|
}
|
|
|
|
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>
|
|
///
|
|
/// </summary>
|
|
public MySqlError[] errors;
|
|
}
|
|
|
|
/// <summary>
|
|
/// IDisposable wrapper around SetCommandTimeout and ClearCommandTimeout
|
|
/// functionality
|
|
/// </summary>
|
|
internal class CommandTimer : IDisposable
|
|
{
|
|
bool timeoutSet;
|
|
MySqlConnection connection;
|
|
|
|
public CommandTimer(MySqlConnection connection, int timeout)
|
|
{
|
|
this.connection = connection;
|
|
if (connection != null)
|
|
{
|
|
timeoutSet = connection.SetCommandTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
#region IDisposable Members
|
|
public void Dispose()
|
|
{
|
|
if (timeoutSet)
|
|
{
|
|
timeoutSet = false;
|
|
connection.ClearCommandTimeout();
|
|
connection = null;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|