// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.ComponentModel;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using SysTx = System.Transactions;

namespace System.Data.Odbc
{
    public sealed partial class OdbcConnection : DbConnection, ICloneable
    {
        private int _connectionTimeout = ADP.DefaultConnectionTimeout;

        private OdbcInfoMessageEventHandler _infoMessageEventHandler;
        private WeakReference _weakTransaction;

        private OdbcConnectionHandle _connectionHandle;
        private ConnectionState _extraState = default(ConnectionState);    // extras, like Executing and Fetching, that we add to the State.

        public OdbcConnection(string connectionString) : this()
        {
            ConnectionString = connectionString;
        }

        private OdbcConnection(OdbcConnection connection) : this()
        { // Clone
            CopyFrom(connection);
            _connectionTimeout = connection._connectionTimeout;
        }

        internal OdbcConnectionHandle ConnectionHandle
        {
            get
            {
                return _connectionHandle;
            }
            set
            {
                Debug.Assert(null == _connectionHandle, "reopening a connection?");
                _connectionHandle = value;
            }
        }

        public override string ConnectionString
        {
            get
            {
                return ConnectionString_Get();
            }
            set
            {
                ConnectionString_Set(value);
            }
        }

        [
        DefaultValue(ADP.DefaultConnectionTimeout),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ]
        public new int ConnectionTimeout
        {
            get
            {
                return _connectionTimeout;
            }
            set
            {
                if (value < 0)
                    throw ODBC.NegativeArgument();
                if (IsOpen)
                    throw ODBC.CantSetPropertyOnOpenConnection();
                _connectionTimeout = value;
            }
        }

        [
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ]
        public override string Database
        {
            get
            {
                if (IsOpen && !ProviderInfo.NoCurrentCatalog)
                {
                    //Note: CURRENT_CATALOG may not be supported by the current driver.  In which
                    //case we ignore any error (without throwing), and just return string.empty.
                    //As we really don't want people to have to have try/catch around simple properties
                    return GetConnectAttrString(ODBC32.SQL_ATTR.CURRENT_CATALOG);
                }
                //Database is not available before open, and its not worth parsing the
                //connection string over.
                return string.Empty;
            }
        }

        [
        Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ]
        public override string DataSource
        {
            get
            {
                if (IsOpen)
                {
                    // note: This will return an empty string if the driver keyword was used to connect
                    // see ODBC3.0 Programmers Reference, SQLGetInfo
                    //
                    return GetInfoStringUnhandled(ODBC32.SQL_INFO.SERVER_NAME, true);
                }
                return string.Empty;
            }
        }

        [
        Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ]
        public override string ServerVersion
        {
            get
            {
                return InnerConnection.ServerVersion;
            }
        }

        [
        Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ]
        public override ConnectionState State
        {
            get
            {
                return InnerConnection.State;
            }
        }

        internal OdbcConnectionPoolGroupProviderInfo ProviderInfo
        {
            get
            {
                Debug.Assert(null != this.PoolGroup, "PoolGroup must never be null when accessing ProviderInfo");
                return (OdbcConnectionPoolGroupProviderInfo)this.PoolGroup.ProviderInfo;
            }
        }

        internal ConnectionState InternalState
        {
            get
            {
                return (this.State | _extraState);
            }
        }

        internal bool IsOpen
        {
            get
            {
                return (InnerConnection is OdbcConnectionOpen);
            }
        }

        internal OdbcTransaction LocalTransaction
        {
            get
            {
                OdbcTransaction result = null;
                if (null != _weakTransaction)
                {
                    result = ((OdbcTransaction)_weakTransaction.Target);
                }
                return result;
            }

            set
            {
                _weakTransaction = null;

                if (null != value)
                {
                    _weakTransaction = new WeakReference((OdbcTransaction)value);
                }
            }
        }

        [
        Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ]
        public string Driver
        {
            get
            {
                if (IsOpen)
                {
                    if (ProviderInfo.DriverName == null)
                    {
                        ProviderInfo.DriverName = GetInfoStringUnhandled(ODBC32.SQL_INFO.DRIVER_NAME);
                    }
                    return ProviderInfo.DriverName;
                }
                return ADP.StrEmpty;
            }
        }

        internal bool IsV3Driver
        {
            get
            {
                if (ProviderInfo.DriverVersion == null)
                {
                    ProviderInfo.DriverVersion = GetInfoStringUnhandled(ODBC32.SQL_INFO.DRIVER_ODBC_VER);
                    // protected against null and index out of range. Number cannot be bigger than 99
                    if (ProviderInfo.DriverVersion != null && ProviderInfo.DriverVersion.Length >= 2)
                    {
                        try
                        {   // mdac 89269: driver may return malformatted string
                            ProviderInfo.IsV3Driver = (int.Parse(ProviderInfo.DriverVersion.Substring(0, 2), CultureInfo.InvariantCulture) >= 3);
                        }
                        catch (System.FormatException e)
                        {
                            ProviderInfo.IsV3Driver = false;
                            ADP.TraceExceptionWithoutRethrow(e);
                        }
                    }
                    else
                    {
                        ProviderInfo.DriverVersion = "";
                    }
                }
                return ProviderInfo.IsV3Driver;
            }
        }

        public event OdbcInfoMessageEventHandler InfoMessage
        {
            add
            {
                _infoMessageEventHandler += value;
            }
            remove
            {
                _infoMessageEventHandler -= value;
            }
        }

        internal char EscapeChar(string method)
        {
            CheckState(method);
            if (!ProviderInfo.HasEscapeChar)
            {
                string escapeCharString;
                escapeCharString = GetInfoStringUnhandled(ODBC32.SQL_INFO.SEARCH_PATTERN_ESCAPE);
                Debug.Assert((escapeCharString.Length <= 1), "Can't handle multichar quotes");
                ProviderInfo.EscapeChar = (escapeCharString.Length == 1) ? escapeCharString[0] : QuoteChar(method)[0];
            }
            return ProviderInfo.EscapeChar;
        }

        internal string QuoteChar(string method)
        {
            CheckState(method);
            if (!ProviderInfo.HasQuoteChar)
            {
                string quoteCharString;
                quoteCharString = GetInfoStringUnhandled(ODBC32.SQL_INFO.IDENTIFIER_QUOTE_CHAR);
                Debug.Assert((quoteCharString.Length <= 1), "Can't handle multichar quotes");
                ProviderInfo.QuoteChar = (1 == quoteCharString.Length) ? quoteCharString : "\0";
            }
            return ProviderInfo.QuoteChar;
        }

        public new OdbcTransaction BeginTransaction()
        {
            return BeginTransaction(IsolationLevel.Unspecified);
        }

        public new OdbcTransaction BeginTransaction(IsolationLevel isolevel)
        {
            return (OdbcTransaction)InnerConnection.BeginTransaction(isolevel);
        }

        private void RollbackDeadTransaction()
        {
            WeakReference weak = _weakTransaction;
            if ((null != weak) && !weak.IsAlive)
            {
                _weakTransaction = null;
                ConnectionHandle.CompleteTransaction(ODBC32.SQL_ROLLBACK);
            }
        }

        public override void ChangeDatabase(string value)
        {
            InnerConnection.ChangeDatabase(value);
        }

        internal void CheckState(string method)
        {
            ConnectionState state = InternalState;
            if (ConnectionState.Open != state)
            {
                throw ADP.OpenConnectionRequired(method, state); // MDAC 68323
            }
        }

        object ICloneable.Clone()
        {
            OdbcConnection clone = new OdbcConnection(this);
            return clone;
        }

        internal bool ConnectionIsAlive(Exception innerException)
        {
            if (IsOpen)
            {
                if (!ProviderInfo.NoConnectionDead)
                {
                    int isDead = GetConnectAttr(ODBC32.SQL_ATTR.CONNECTION_DEAD, ODBC32.HANDLER.IGNORE);
                    if (ODBC32.SQL_CD_TRUE == isDead)
                    {
                        Close();
                        throw ADP.ConnectionIsDisabled(innerException);
                    }
                }
                // else connection is still alive or attribute not supported
                return true;
            }
            return false;
        }

        public new OdbcCommand CreateCommand()
        {
            return new OdbcCommand(string.Empty, this);
        }

        internal OdbcStatementHandle CreateStatementHandle()
        {
            return new OdbcStatementHandle(ConnectionHandle);
        }

        public override void Close()
        {
            InnerConnection.CloseConnection(this, ConnectionFactory);

            OdbcConnectionHandle connectionHandle = _connectionHandle;

            if (null != connectionHandle)
            {
                _connectionHandle = null;

                // If there is a pending transaction, automatically rollback.
                WeakReference weak = _weakTransaction;
                if (null != weak)
                {
                    _weakTransaction = null;
                    IDisposable transaction = weak.Target as OdbcTransaction;
                    if ((null != transaction) && weak.IsAlive)
                    {
                        transaction.Dispose();
                    }
                    // else transaction will be rolled back when handle is disposed
                }
                connectionHandle.Dispose();
            }
        }

        private void DisposeMe(bool disposing)
        { // MDAC 65459
        }

        internal string GetConnectAttrString(ODBC32.SQL_ATTR attribute)
        {
            string value = "";
            int cbActual = 0;
            byte[] buffer = new byte[100];
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                ODBC32.RetCode retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out cbActual);
                if (buffer.Length + 2 <= cbActual)
                {
                    // 2 bytes for unicode null-termination character
                    // retry with cbActual because original buffer was too small
                    buffer = new byte[cbActual + 2];
                    retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out cbActual);
                }
                if ((ODBC32.RetCode.SUCCESS == retcode) || (ODBC32.RetCode.SUCCESS_WITH_INFO == retcode))
                {
                    value = (BitConverter.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode).GetString(buffer, 0, Math.Min(cbActual, buffer.Length));
                }
                else if (retcode == ODBC32.RetCode.ERROR)
                {
                    string sqlstate = GetDiagSqlState();
                    if (("HYC00" == sqlstate) || ("HY092" == sqlstate) || ("IM001" == sqlstate))
                    {
                        FlagUnsupportedConnectAttr(attribute);
                    }
                    // not throwing errors if not supported or other failure
                }
            }
            return value;
        }

        internal int GetConnectAttr(ODBC32.SQL_ATTR attribute, ODBC32.HANDLER handler)
        {
            int retval = -1;
            int cbActual = 0;
            byte[] buffer = new byte[4];
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                ODBC32.RetCode retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out cbActual);

                if ((ODBC32.RetCode.SUCCESS == retcode) || (ODBC32.RetCode.SUCCESS_WITH_INFO == retcode))
                {
                    retval = BitConverter.ToInt32(buffer, 0);
                }
                else
                {
                    if (retcode == ODBC32.RetCode.ERROR)
                    {
                        string sqlstate = GetDiagSqlState();
                        if (("HYC00" == sqlstate) || ("HY092" == sqlstate) || ("IM001" == sqlstate))
                        {
                            FlagUnsupportedConnectAttr(attribute);
                        }
                    }
                    if (handler == ODBC32.HANDLER.THROW)
                    {
                        this.HandleError(connectionHandle, retcode);
                    }
                }
            }
            return retval;
        }

        private string GetDiagSqlState()
        {
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            string sqlstate;
            connectionHandle.GetDiagnosticField(out sqlstate);
            return sqlstate;
        }

        internal ODBC32.RetCode GetInfoInt16Unhandled(ODBC32.SQL_INFO info, out short resultValue)
        {
            byte[] buffer = new byte[2];
            ODBC32.RetCode retcode = ConnectionHandle.GetInfo1(info, buffer);
            resultValue = BitConverter.ToInt16(buffer, 0);
            return retcode;
        }

        internal ODBC32.RetCode GetInfoInt32Unhandled(ODBC32.SQL_INFO info, out int resultValue)
        {
            byte[] buffer = new byte[4];
            ODBC32.RetCode retcode = ConnectionHandle.GetInfo1(info, buffer);
            resultValue = BitConverter.ToInt32(buffer, 0);
            return retcode;
        }

        private int GetInfoInt32Unhandled(ODBC32.SQL_INFO infotype)
        {
            byte[] buffer = new byte[4];
            ConnectionHandle.GetInfo1(infotype, buffer);
            return BitConverter.ToInt32(buffer, 0);
        }

        internal string GetInfoStringUnhandled(ODBC32.SQL_INFO info)
        {
            return GetInfoStringUnhandled(info, false);
        }

        private string GetInfoStringUnhandled(ODBC32.SQL_INFO info, bool handleError)
        {
            //SQLGetInfo
            string value = null;
            short cbActual = 0;
            byte[] buffer = new byte[100];
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                ODBC32.RetCode retcode = connectionHandle.GetInfo2(info, buffer, out cbActual);
                if (buffer.Length < cbActual - 2)
                {
                    // 2 bytes for unicode null-termination character
                    // retry with cbActual because original buffer was too small
                    buffer = new byte[cbActual + 2];
                    retcode = connectionHandle.GetInfo2(info, buffer, out cbActual);
                }
                if (retcode == ODBC32.RetCode.SUCCESS || retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)
                {
                    value = (BitConverter.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode).GetString(buffer, 0, Math.Min(cbActual, buffer.Length));
                }
                else if (handleError)
                {
                    this.HandleError(ConnectionHandle, retcode);
                }
            }
            else if (handleError)
            {
                value = "";
            }
            return value;
        }

        // non-throwing HandleError
        internal Exception HandleErrorNoThrow(OdbcHandle hrHandle, ODBC32.RetCode retcode)
        {
            Debug.Assert(retcode != ODBC32.RetCode.INVALID_HANDLE, "retcode must never be ODBC32.RetCode.INVALID_HANDLE");

            switch (retcode)
            {
                case ODBC32.RetCode.SUCCESS:
                    break;
                case ODBC32.RetCode.SUCCESS_WITH_INFO:
                    {
                        //Optimize to only create the event objects and obtain error info if
                        //the user is really interested in retriveing the events...
                        if (_infoMessageEventHandler != null)
                        {
                            OdbcErrorCollection errors = ODBC32.GetDiagErrors(null, hrHandle, retcode);
                            errors.SetSource(this.Driver);
                            OnInfoMessage(new OdbcInfoMessageEventArgs(errors));
                        }
                        break;
                    }
                default:
                    OdbcException e = OdbcException.CreateException(ODBC32.GetDiagErrors(null, hrHandle, retcode), retcode);
                    if (e != null)
                    {
                        e.Errors.SetSource(this.Driver);
                    }
                    ConnectionIsAlive(e);        // this will close and throw if the connection is dead
                    return (Exception)e;
            }
            return null;
        }

        internal void HandleError(OdbcHandle hrHandle, ODBC32.RetCode retcode)
        {
            Exception e = HandleErrorNoThrow(hrHandle, retcode);
            switch (retcode)
            {
                case ODBC32.RetCode.SUCCESS:
                case ODBC32.RetCode.SUCCESS_WITH_INFO:
                    Debug.Assert(null == e, "success exception");
                    break;
                default:
                    Debug.Assert(null != e, "failure without exception");
                    throw e;
            }
        }

        public override void Open()
        {
            try
            {
                InnerConnection.OpenConnection(this, ConnectionFactory);
            }
            catch (DllNotFoundException e) when (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                throw new DllNotFoundException(SR.Odbc_UnixOdbcNotFound + Environment.NewLine + e.Message);
            }

            // SQLBUDT #276132 - need to manually enlist in some cases, because
            // native ODBC doesn't know about SysTx transactions.
            if (ADP.NeedManualEnlistment())
            {
                EnlistTransaction(SysTx.Transaction.Current);
            }
        }

        private void OnInfoMessage(OdbcInfoMessageEventArgs args)
        {
            if (null != _infoMessageEventHandler)
            {
                try
                {
                    _infoMessageEventHandler(this, args);
                }
                catch (Exception e)
                {
                    // 
                    if (!ADP.IsCatchableOrSecurityExceptionType(e))
                    {
                        throw;
                    }
                    ADP.TraceExceptionWithoutRethrow(e);
                }
            }
        }

        public static void ReleaseObjectPool()
        {
            OdbcEnvironment.ReleaseObjectPool();
        }

        internal OdbcTransaction SetStateExecuting(string method, OdbcTransaction transaction)
        { // MDAC 69003
            if (null != _weakTransaction)
            { // transaction may exist
                OdbcTransaction weak = (_weakTransaction.Target as OdbcTransaction);
                if (transaction != weak)
                { // transaction doesn't exist
                    if (null == transaction)
                    { // transaction exists
                        throw ADP.TransactionRequired(method);
                    }
                    if (this != transaction.Connection)
                    {
                        // transaction can't have come from this connection
                        throw ADP.TransactionConnectionMismatch();
                    }
                    // if transaction is zombied, we don't know the original connection
                    transaction = null; // MDAC 69264
                }
            }
            else if (null != transaction)
            { // no transaction started
                if (null != transaction.Connection)
                {
                    // transaction can't have come from this connection
                    throw ADP.TransactionConnectionMismatch();
                }
                // if transaction is zombied, we don't know the original connection
                transaction = null; // MDAC 69264
            }
            ConnectionState state = InternalState;
            if (ConnectionState.Open != state)
            {
                NotifyWeakReference(OdbcReferenceCollection.Recover); // recover for a potentially finalized reader

                state = InternalState;
                if (ConnectionState.Open != state)
                {
                    if (0 != (ConnectionState.Fetching & state))
                    {
                        throw ADP.OpenReaderExists();
                    }
                    throw ADP.OpenConnectionRequired(method, state);
                }
            }
            return transaction;
        }

        // This adds a type to the list of types that are supported by the driver
        // (don't need to know that for all the types)
        //

        internal void SetSupportedType(ODBC32.SQL_TYPE sqltype)
        {
            ODBC32.SQL_CVT sqlcvt;

            switch (sqltype)
            {
                case ODBC32.SQL_TYPE.NUMERIC:
                    {
                        sqlcvt = ODBC32.SQL_CVT.NUMERIC;
                        break;
                    }
                case ODBC32.SQL_TYPE.WCHAR:
                    {
                        sqlcvt = ODBC32.SQL_CVT.WCHAR;
                        break;
                    }
                case ODBC32.SQL_TYPE.WVARCHAR:
                    {
                        sqlcvt = ODBC32.SQL_CVT.WVARCHAR;
                        break;
                    }
                case ODBC32.SQL_TYPE.WLONGVARCHAR:
                    {
                        sqlcvt = ODBC32.SQL_CVT.WLONGVARCHAR;
                        break;
                    }
                default:
                    // other types are irrelevant at this time
                    return;
            }
            ProviderInfo.TestedSQLTypes |= (int)sqlcvt;
            ProviderInfo.SupportedSQLTypes |= (int)sqlcvt;
        }

        internal void FlagRestrictedSqlBindType(ODBC32.SQL_TYPE sqltype)
        {
            ODBC32.SQL_CVT sqlcvt;

            switch (sqltype)
            {
                case ODBC32.SQL_TYPE.NUMERIC:
                    {
                        sqlcvt = ODBC32.SQL_CVT.NUMERIC;
                        break;
                    }
                case ODBC32.SQL_TYPE.DECIMAL:
                    {
                        sqlcvt = ODBC32.SQL_CVT.DECIMAL;
                        break;
                    }
                default:
                    // other types are irrelevant at this time
                    return;
            }
            ProviderInfo.RestrictedSQLBindTypes |= (int)sqlcvt;
        }

        internal void FlagUnsupportedConnectAttr(ODBC32.SQL_ATTR Attribute)
        {
            switch (Attribute)
            {
                case ODBC32.SQL_ATTR.CURRENT_CATALOG:
                    ProviderInfo.NoCurrentCatalog = true;
                    break;
                case ODBC32.SQL_ATTR.CONNECTION_DEAD:
                    ProviderInfo.NoConnectionDead = true;
                    break;
                default:
                    Debug.Fail("Can't flag unknown Attribute");
                    break;
            }
        }

        internal void FlagUnsupportedStmtAttr(ODBC32.SQL_ATTR Attribute)
        {
            switch (Attribute)
            {
                case ODBC32.SQL_ATTR.QUERY_TIMEOUT:
                    ProviderInfo.NoQueryTimeout = true;
                    break;
                case (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE:
                    ProviderInfo.NoSqlSoptSSNoBrowseTable = true;
                    break;
                case (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS:
                    ProviderInfo.NoSqlSoptSSHiddenColumns = true;
                    break;
                default:
                    Debug.Fail("Can't flag unknown Attribute");
                    break;
            }
        }

        internal void FlagUnsupportedColAttr(ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId)
        {
            if (IsV3Driver)
            {
                switch (v3FieldId)
                {
                    case (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.COLUMN_KEY:
                        // SSS_WARNINGS_OFF
                        ProviderInfo.NoSqlCASSColumnKey = true;
                        break;
                    // SSS_WARNINGS_ON
                    default:
                        Debug.Fail("Can't flag unknown Attribute");
                        break;
                }
            }
            else
            {
                switch (v2FieldId)
                {
                    default:
                        Debug.Fail("Can't flag unknown Attribute");
                        break;
                }
            }
        }

        internal bool SQLGetFunctions(ODBC32.SQL_API odbcFunction)
        {
            //SQLGetFunctions
            ODBC32.RetCode retcode;
            short fExists;
            Debug.Assert((short)odbcFunction != 0, "SQL_API_ALL_FUNCTIONS is not supported");
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                retcode = connectionHandle.GetFunctions(odbcFunction, out fExists);
            }
            else
            {
                Debug.Fail("GetFunctions called and ConnectionHandle is null (connection is disposed?)");
                throw ODBC.ConnectionClosed();
            }

            if (retcode != ODBC32.RetCode.SUCCESS)
                this.HandleError(connectionHandle, retcode);

            if (fExists == 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        internal bool TestTypeSupport(ODBC32.SQL_TYPE sqltype)
        {
            ODBC32.SQL_CONVERT sqlconvert;
            ODBC32.SQL_CVT sqlcvt;

            // we need to convert the sqltype to sqlconvert and sqlcvt first
            //
            switch (sqltype)
            {
                case ODBC32.SQL_TYPE.NUMERIC:
                    {
                        sqlconvert = ODBC32.SQL_CONVERT.NUMERIC;
                        sqlcvt = ODBC32.SQL_CVT.NUMERIC;
                        break;
                    }
                case ODBC32.SQL_TYPE.WCHAR:
                    {
                        sqlconvert = ODBC32.SQL_CONVERT.CHAR;
                        sqlcvt = ODBC32.SQL_CVT.WCHAR;
                        break;
                    }
                case ODBC32.SQL_TYPE.WVARCHAR:
                    {
                        sqlconvert = ODBC32.SQL_CONVERT.VARCHAR;
                        sqlcvt = ODBC32.SQL_CVT.WVARCHAR;
                        break;
                    }
                case ODBC32.SQL_TYPE.WLONGVARCHAR:
                    {
                        sqlconvert = ODBC32.SQL_CONVERT.LONGVARCHAR;
                        sqlcvt = ODBC32.SQL_CVT.WLONGVARCHAR;
                        break;
                    }
                default:
                    Debug.Fail("Testing that sqltype is currently not supported");
                    return false;
            }
            // now we can check if we have already tested that type
            // if not we need to do so
            if (0 == (ProviderInfo.TestedSQLTypes & (int)sqlcvt))
            {
                int flags;

                flags = GetInfoInt32Unhandled((ODBC32.SQL_INFO)sqlconvert);
                flags = flags & (int)sqlcvt;

                ProviderInfo.TestedSQLTypes |= (int)sqlcvt;
                ProviderInfo.SupportedSQLTypes |= flags;
            }

            // now check if the type is supported and return the result
            //
            return (0 != (ProviderInfo.SupportedSQLTypes & (int)sqlcvt));
        }

        internal bool TestRestrictedSqlBindType(ODBC32.SQL_TYPE sqltype)
        {
            ODBC32.SQL_CVT sqlcvt;
            switch (sqltype)
            {
                case ODBC32.SQL_TYPE.NUMERIC:
                    {
                        sqlcvt = ODBC32.SQL_CVT.NUMERIC;
                        break;
                    }
                case ODBC32.SQL_TYPE.DECIMAL:
                    {
                        sqlcvt = ODBC32.SQL_CVT.DECIMAL;
                        break;
                    }
                default:
                    Debug.Fail("Testing that sqltype is currently not supported");
                    return false;
            }
            return (0 != (ProviderInfo.RestrictedSQLBindTypes & (int)sqlcvt));
        }

        // suppress this message - we cannot use SafeHandle here. Also, see notes in the code (VSTFDEVDIV# 560355)
        [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
        protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
        {
            DbTransaction transaction = InnerConnection.BeginTransaction(isolationLevel);

            // VSTFDEVDIV# 560355 - InnerConnection doesn't maintain a ref on the outer connection (this) and 
            //   subsequently leaves open the possibility that the outer connection could be GC'ed before the DbTransaction
            //   is fully hooked up (leaving a DbTransaction with a null connection property). Ensure that this is reachable
            //   until the completion of BeginTransaction with KeepAlive
            GC.KeepAlive(this);

            return transaction;
        }

        internal OdbcTransaction Open_BeginTransaction(IsolationLevel isolevel)
        {
            CheckState(ADP.BeginTransaction); // MDAC 68323

            RollbackDeadTransaction();

            if ((null != _weakTransaction) && _weakTransaction.IsAlive)
            { // regression from Dispose/Finalize work
                throw ADP.ParallelTransactionsNotSupported(this);
            }

            //Use the default for unspecified.
            switch (isolevel)
            {
                case IsolationLevel.Unspecified:
                case IsolationLevel.ReadUncommitted:
                case IsolationLevel.ReadCommitted:
                case IsolationLevel.RepeatableRead:
                case IsolationLevel.Serializable:
                case IsolationLevel.Snapshot:
                    break;
                case IsolationLevel.Chaos:
                    throw ODBC.NotSupportedIsolationLevel(isolevel);
                default:
                    throw ADP.InvalidIsolationLevel(isolevel);
            };

            //Start the transaction
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            ODBC32.RetCode retcode = connectionHandle.BeginTransaction(ref isolevel);
            if (retcode == ODBC32.RetCode.ERROR)
            {
                HandleError(connectionHandle, retcode);
            }
            OdbcTransaction transaction = new OdbcTransaction(this, isolevel, connectionHandle);
            _weakTransaction = new WeakReference(transaction); // MDAC 69188
            return transaction;
        }

        internal void Open_ChangeDatabase(string value)
        {
            CheckState(ADP.ChangeDatabase);

            // Database name must not be null, empty or whitspace
            if ((null == value) || (0 == value.Trim().Length))
            { // MDAC 62679
                throw ADP.EmptyDatabaseName();
            }
            if (1024 < value.Length * 2 + 2)
            {
                throw ADP.DatabaseNameTooLong();
            }
            RollbackDeadTransaction();

            //Set the database
            OdbcConnectionHandle connectionHandle = ConnectionHandle;
            ODBC32.RetCode retcode = connectionHandle.SetConnectionAttribute3(ODBC32.SQL_ATTR.CURRENT_CATALOG, value, checked((int)value.Length * 2));

            if (retcode != ODBC32.RetCode.SUCCESS)
            {
                HandleError(connectionHandle, retcode);
            }
        }

        internal string Open_GetServerVersion()
        {
            //SQLGetInfo - SQL_DBMS_VER
            return GetInfoStringUnhandled(ODBC32.SQL_INFO.DBMS_VER, true);
        }
    }
}


