#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