File: System\Data\Odbc\OdbcConnection.cs
Web Access
Project: src\src\libraries\System.Data.Odbc\src\System.Data.Odbc.csproj (System.Data.Odbc)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
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;
 
        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;
            }
        }
 
        [AllowNull]
        [Editor("Microsoft.VSDesigner.Data.Odbc.Design.OdbcConnectionStringEditor, Microsoft.VSDesigner, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
                "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
        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;
            }
        }
 
        protected override DbProviderFactory DbProviderFactory
        {
            get
            {
                return OdbcFactory.Instance;
            }
        }
 
        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;
            }
        }
 
        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)
                {
                    return ProviderInfo.DriverName ??= GetInfoStringUnhandled(ODBC32.SQL_INFO.DRIVER_NAME)!;
                }
 
                return string.Empty;
            }
        }
 
        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();
            }
        }
 
        internal string GetConnectAttrString(ODBC32.SQL_ATTR attribute)
        {
            string value = "";
            int cbActual;
            byte[] buffer = new byte[100];
            OdbcConnectionHandle? connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                ODBC32.SQLRETURN 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.SQLRETURN.SUCCESS == retcode) || (ODBC32.SQLRETURN.SUCCESS_WITH_INFO == retcode))
                {
                    value = (BitConverter.IsLittleEndian ? Encoding.Unicode : Encoding.BigEndianUnicode).GetString(buffer, 0, Math.Min(cbActual, buffer.Length));
                }
                else if (retcode == ODBC32.SQLRETURN.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;
            byte[] buffer = new byte[4];
            OdbcConnectionHandle? connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                ODBC32.SQLRETURN retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out _);
 
                if ((ODBC32.SQLRETURN.SUCCESS == retcode) || (ODBC32.SQLRETURN.SUCCESS_WITH_INFO == retcode))
                {
                    retval = BitConverter.ToInt32(buffer, 0);
                }
                else
                {
                    if (retcode == ODBC32.SQLRETURN.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.SQLRETURN GetInfoInt16Unhandled(ODBC32.SQL_INFO info, out short resultValue)
        {
            byte[] buffer = new byte[2];
            ODBC32.SQLRETURN retcode = ConnectionHandle!.GetInfo1(info, buffer);
            resultValue = BitConverter.ToInt16(buffer, 0);
            return retcode;
        }
 
        internal ODBC32.SQLRETURN GetInfoInt32Unhandled(ODBC32.SQL_INFO info, out int resultValue)
        {
            byte[] buffer = new byte[4];
            ODBC32.SQLRETURN 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;
            byte[] buffer = new byte[100];
            OdbcConnectionHandle? connectionHandle = ConnectionHandle;
            if (null != connectionHandle)
            {
                ODBC32.SQLRETURN 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.SQLRETURN.SUCCESS || retcode == ODBC32.SQLRETURN.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.SQLRETURN retcode)
        {
            Debug.Assert(retcode != ODBC32.SQLRETURN.INVALID_HANDLE, "retcode must never be ODBC32.RetCode.INVALID_HANDLE");
 
            switch (retcode)
            {
                case ODBC32.SQLRETURN.SUCCESS:
                    break;
                case ODBC32.SQLRETURN.SUCCESS_WITH_INFO:
                    {
                        //Optimize to only create the event objects and obtain error info if
                        //the user is really interested in retrieveing 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);
                    e?.Errors.SetSource(this.Driver);
                    ConnectionIsAlive(e);        // this will close and throw if the connection is dead
                    return e;
            }
            return null;
        }
 
        internal void HandleError(OdbcHandle hrHandle, ODBC32.SQLRETURN retcode)
        {
            Exception? e = HandleErrorNoThrow(hrHandle, retcode);
            switch (retcode)
            {
                case ODBC32.SQLRETURN.SUCCESS:
                case ODBC32.SQLRETURN.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.SQLRETURN 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.SQLRETURN.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 &= (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)
        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.SQLRETURN retcode = connectionHandle.BeginTransaction(ref isolevel);
            if (retcode == ODBC32.SQLRETURN.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 (string.IsNullOrWhiteSpace(value))
            { // MDAC 62679
                throw ADP.EmptyDatabaseName();
            }
            if (1024 < value.Length * 2 + 2)
            {
                throw ADP.DatabaseNameTooLong();
            }
            RollbackDeadTransaction();
 
            //Set the database
            OdbcConnectionHandle connectionHandle = ConnectionHandle!;
            ODBC32.SQLRETURN retcode = connectionHandle.SetConnectionAttribute3(ODBC32.SQL_ATTR.CURRENT_CATALOG, value, checked((int)value.Length * 2));
 
            if (retcode != ODBC32.SQLRETURN.SUCCESS)
            {
                HandleError(connectionHandle, retcode);
            }
        }
 
        internal string? Open_GetServerVersion()
        {
            //SQLGetInfo - SQL_DBMS_VER
            return GetInfoStringUnhandled(ODBC32.SQL_INFO.DBMS_VER, true);
        }
    }
}