File: OleDbConnectionInternal.cs
Web Access
Project: src\src\runtime\src\libraries\System.Data.OleDb\src\System.Data.OleDb.csproj (System.Data.OleDb)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using SysTx = System.Transactions;

namespace System.Data.OleDb
{
#if NET
    [RequiresDynamicCode(OleDbConnection.TrimWarning)]
#endif
    internal sealed class OleDbConnectionInternal : DbConnectionInternal, IDisposable
    {
        private static volatile OleDbServicesWrapper? idataInitialize;
        private static readonly object dataInitializeLock = new object();

        internal readonly OleDbConnectionString ConnectionString; // parsed connection string attributes

        // A SafeHandle is used instead of a RCW because we need to fake the CLR into not marshalling

        // OLE DB Services is marked apartment thread, but it actually supports/requires free-threading.
        // However the CLR doesn't know this and attempts to marshal the interfaces back to their original context.
        // But the OLE DB doesn't marshal very well if at all.  Our workaround is based on the fact
        // OLE DB is free-threaded and allows the workaround.

        // Creating DataSource/Session would requiring marshalling DataLins to its original context
        // and has a severe performance impact (when working with transactions), hence our workaround to not Marshal.

        // Creating a Command would requiring marshalling Session to its original context and
        // actually doesn't work correctly, without our workaround you must execute the command in
        // the same context of the connection open.  This doesn't work for pooled objects that contain
        // an open OleDbConnection.

        // We don't do extra work at this time to allow the DataReader to be used in a different context
        // from which the command was executed in. IRowset.GetNextRows will throw InvalidCastException

        // In V1.0, we worked around the performance impact of creating a DataSource/Session using
        // WrapIUnknownWithComObject which creates a new RCW without searching for existing RCW
        // effectively faking out the CLR into thinking the call is in the correct context.
        // We also would use Marshal.ReleaseComObject to force the release of the 'temporary' RCW.

        // In V1.1, we worked around the CreateCommand issue with the same WrapIUnknownWithComObject trick.

        // In V2.0, the performance of using WrapIUnknownWithComObject & ReleaseComObject severly degraded.
        // Using a SafeHandle (for lifetime control) and a delegate to call the appropriate COM method
        // offered much better performance.

        // the "Data Source object".
        private readonly DataSourceWrapper? _datasrcwrp;

        // the "Session object".
        private readonly SessionWrapper? _sessionwrp;

        private WeakReference? weakTransaction;

        // When set to true the current connection is enlisted in a transaction that must be
        // un-enlisted during Deactivate.
        private bool _unEnlistDuringDeactivate;

        internal OleDbConnectionInternal(OleDbConnectionString constr, OleDbConnection? connection) : base()
        {
            Debug.Assert((null != constr) && !constr.IsEmpty, "empty connectionstring");
            ConnectionString = constr;

            if (constr.PossiblePrompt && !System.Environment.UserInteractive)
            {
                throw ODB.PossiblePromptNotUserInteractive();
            }

            try
            {
                // this is the native DataLinks object which pools the native datasource/session
                OleDbServicesWrapper wrapper = OleDbConnectionInternal.GetObjectPool();
                _datasrcwrp = new DataSourceWrapper();

                // DataLinks wrapper will call IDataInitialize::GetDataSource to create the DataSource
                // uses constr.ActualConnectionString, no InfoMessageEvent checking
                wrapper.GetDataSource(constr, ref _datasrcwrp);
                Debug.Assert(!_datasrcwrp.IsInvalid, "bad DataSource");

                // initialization is delayed because of OleDbConnectionStringBuilder only wants
                // pre-Initialize IDBPropertyInfo & IDBProperties on the data source
                if (null != connection)
                {
                    _sessionwrp = new SessionWrapper();

                    // From the DataSource object, will call IDBInitialize.Initialize & IDBCreateSession.CreateSession
                    // We always need both called so we use a single call for a single DangerousAddRef/DangerousRelease pair.
                    OleDbHResult hr = _datasrcwrp.InitializeAndCreateSession(constr, ref _sessionwrp);

                    // process the HResult here instead of from the SafeHandle because the possibility
                    // of an InfoMessageEvent.
                    if ((0 <= hr) && !_sessionwrp.IsInvalid)
                    { // process infonessage events
                        OleDbConnection.ProcessResults(hr, connection);
                    }
                    else
                    {
                        Exception? e = OleDbConnection.ProcessResults(hr, null);
                        Debug.Assert(null != e, "CreateSessionError");
                        throw e;
                    }
                    Debug.Assert(!_sessionwrp.IsInvalid, "bad Session");
                }
            }
            catch
            {
                if (null != _sessionwrp)
                {
                    _sessionwrp.Dispose();
                    _sessionwrp = null;
                }
                if (null != _datasrcwrp)
                {
                    _datasrcwrp.Dispose();
                    _datasrcwrp = null;
                }
                throw;
            }
        }

        internal OleDbConnection? Connection
        {
            get
            {
                return (OleDbConnection?)Owner;
            }
        }

        internal bool HasSession
        {
            get
            {
                return (null != _sessionwrp);
            }
        }

        internal OleDbTransaction? LocalTransaction
        {
            get
            {
                OleDbTransaction? result = null;
                if (null != weakTransaction)
                {
                    result = ((OleDbTransaction?)weakTransaction.Target);
                }
                return result;
            }
            set
            {
                weakTransaction = null;

                if (null != value)
                {
                    weakTransaction = new WeakReference((OleDbTransaction)value);
                }
            }
        }

        private string Provider
        {
            get { return ConnectionString.Provider; }
        }

        public override string ServerVersion
        {
            // consider making a method, not a property
            get
            {
                object value = GetDataSourceValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_DBMSVER)!;
                return Convert.ToString(value, CultureInfo.InvariantCulture)!;
            }
        }

        internal static readonly char[] s_comma = new char[] { ',' };

        // grouping the native OLE DB casts togther by required interfaces and optional interfaces, connection then session
        // want these to be methods, not properties otherwise they appear in VS7 managed debugger which attempts to evaluate them

        // required interface, safe cast
        internal IDBPropertiesWrapper IDBProperties()
        {
            Debug.Assert(null != _datasrcwrp, "IDBProperties: null datasource");
            return _datasrcwrp.IDBProperties();
        }

        // required interface, safe cast
        internal IOpenRowsetWrapper IOpenRowset()
        {
            Debug.Assert(null != _datasrcwrp, "IOpenRowset: null datasource");
            Debug.Assert(null != _sessionwrp, "IOpenRowset: null session");
            return _sessionwrp.IOpenRowset();
        }

        // optional interface, unsafe cast
        private IDBInfoWrapper IDBInfo()
        {
            Debug.Assert(null != _datasrcwrp, "IDBInfo: null datasource");
            return _datasrcwrp.IDBInfo();
        }

        // optional interface, unsafe cast
        internal IDBSchemaRowsetWrapper IDBSchemaRowset()
        {
            Debug.Assert(null != _datasrcwrp, "IDBSchemaRowset: null datasource");
            Debug.Assert(null != _sessionwrp, "IDBSchemaRowset: null session");
            return _sessionwrp.IDBSchemaRowset();
        }

        // optional interface, unsafe cast
        internal ITransactionJoinWrapper ITransactionJoin()
        {
            Debug.Assert(null != _datasrcwrp, "ITransactionJoin: null datasource");
            Debug.Assert(null != _sessionwrp, "ITransactionJoin: null session");
            return _sessionwrp.ITransactionJoin();
        }

        // optional interface, unsafe cast
        internal UnsafeNativeMethods.ICommandText? ICommandText()
        {
            Debug.Assert(null != _datasrcwrp, "IDBCreateCommand: null datasource");
            Debug.Assert(null != _sessionwrp, "IDBCreateCommand: null session");

            object? icommandText = null;
            OleDbHResult hr = _sessionwrp.CreateCommand(ref icommandText);

            Debug.Assert((0 <= hr) || (null == icommandText), "CreateICommandText: error with ICommandText");
            if (hr < 0)
            {
                if (OleDbHResult.E_NOINTERFACE != hr)
                {
                    ProcessResults(hr);
                }
                else
                {
                    SafeNativeMethods.Wrapper.ClearErrorInfo();
                }
            }
            return (UnsafeNativeMethods.ICommandText?)icommandText;
        }

        protected override void Activate(SysTx.Transaction? transaction)
        {
            throw ADP.NotSupported();
        }

        public override DbTransaction BeginTransaction(IsolationLevel isolationLevel)
        {
            OleDbConnection outerConnection = Connection!;
            if (null != LocalTransaction)
            {
                throw ADP.ParallelTransactionsNotSupported(outerConnection);
            }

            object? unknown = null;
            OleDbTransaction transaction;
            try
            {
                transaction = new OleDbTransaction(outerConnection, null, isolationLevel);
                Debug.Assert(null != _datasrcwrp, "ITransactionLocal: null datasource");
                Debug.Assert(null != _sessionwrp, "ITransactionLocal: null session");
                unknown = _sessionwrp.ComWrapper();
                UnsafeNativeMethods.ITransactionLocal? value = (unknown as UnsafeNativeMethods.ITransactionLocal);
                if (null == value)
                {
                    throw ODB.TransactionsNotSupported(Provider, null);
                }
                transaction.BeginInternal(value);
            }
            finally
            {
                if (null != unknown)
                {
                    Marshal.ReleaseComObject(unknown);
                }
            }
            LocalTransaction = transaction;
            return transaction;
        }

        protected override DbReferenceCollection CreateReferenceCollection()
        {
            return new OleDbReferenceCollection();
        }

        protected override void Deactivate()
        { // used by both managed and native pooling
            NotifyWeakReference(OleDbReferenceCollection.Closing);

            if (_unEnlistDuringDeactivate)
            {
                // Un-enlist transaction as OLEDB connection pool is unaware of managed transactions.
                EnlistTransactionInternal(null);
            }
            OleDbTransaction? transaction = LocalTransaction;
            if (null != transaction)
            {
                LocalTransaction = null;
                // required to rollback any transactions on this connection
                // before releasing the back to the oledb connection pool
                transaction.Dispose();
            }
        }

        public override void Dispose()
        {
            Debug.Assert(null == LocalTransaction, "why was Deactivate not called first");
            _sessionwrp?.Dispose();
            _datasrcwrp?.Dispose();
            base.Dispose();
        }

        public override void EnlistTransaction(SysTx.Transaction? transaction)
        {
            if (null != LocalTransaction)
            {
                throw ADP.LocalTransactionPresent();
            }
            EnlistTransactionInternal(transaction);
        }

        internal void EnlistTransactionInternal(SysTx.Transaction? transaction)
        {
            SysTx.IDtcTransaction? oleTxTransaction = ADP.GetOletxTransaction(transaction);

            using (ITransactionJoinWrapper transactionJoin = ITransactionJoin())
            {
                if (null == transactionJoin.Value)
                {
                    throw ODB.TransactionsNotSupported(Provider, null);
                }
                transactionJoin.Value.JoinTransaction(oleTxTransaction, (int)IsolationLevel.Unspecified, 0, IntPtr.Zero);
                _unEnlistDuringDeactivate = (null != transaction);
            }
            EnlistedTransaction = transaction;
        }

        internal object? GetDataSourceValue(Guid propertySet, int propertyID)
        {
            object? value = GetDataSourcePropertyValue(propertySet, propertyID);
            if ((value is OleDbPropertyStatus) || Convert.IsDBNull(value))
            {
                value = null;
            }
            return value;
        }

        internal object? GetDataSourcePropertyValue(Guid propertySet, int propertyID)
        {
            OleDbHResult hr;
            ItagDBPROP[] dbprops;
            using (IDBPropertiesWrapper idbProperties = IDBProperties())
            {
                using (PropertyIDSet propidset = new PropertyIDSet(propertySet, propertyID))
                {
                    using (DBPropSet propset = new DBPropSet(idbProperties.Value, propidset, out hr))
                    {
                        if (hr < 0)
                        {
                            // OLEDB Data Reader masks provider specific errors by raising "Internal Data Provider error 30."
                            // DBPropSet c-tor will register the exception and it will be raised at GetPropertySet call in case of failure
                            SafeNativeMethods.Wrapper.ClearErrorInfo();
                        }
                        dbprops = propset.GetPropertySet(0, out propertySet);
                    }
                }
            }
            if (OleDbPropertyStatus.Ok == dbprops[0].dwStatus)
            {
                return dbprops[0].vValue;
            }
            return dbprops[0].dwStatus;
        }

        internal unsafe DataTable? BuildInfoLiterals()
        {
            using (IDBInfoWrapper wrapper = IDBInfo())
            {
                UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value;
                // https://github.com/dotnet/runtime/issues/44288: check may not be necessary (and thus method may return non-nullable)
                if (null == dbInfo)
                {
                    return null;
                }

                DataTable table = new DataTable("DbInfoLiterals");
                table.Locale = CultureInfo.InvariantCulture;
                DataColumn literalName = new DataColumn("LiteralName", typeof(string));
                DataColumn literalValue = new DataColumn("LiteralValue", typeof(string));
                DataColumn invalidChars = new DataColumn("InvalidChars", typeof(string));
                DataColumn invalidStart = new DataColumn("InvalidStartingChars", typeof(string));
                DataColumn literal = new DataColumn("Literal", typeof(int));
                DataColumn maxlen = new DataColumn("Maxlen", typeof(int));

                table.Columns.Add(literalName);
                table.Columns.Add(literalValue);
                table.Columns.Add(invalidChars);
                table.Columns.Add(invalidStart);
                table.Columns.Add(literal);
                table.Columns.Add(maxlen);

                OleDbHResult hr;
                int literalCount = 0;
                IntPtr literalInfo = IntPtr.Zero;
                using (DualCoTaskMem handle = new DualCoTaskMem(dbInfo, null, out literalCount, out literalInfo, out hr))
                {
                    // All literals were either invalid or unsupported. The provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to FALSE. The consumer frees this memory when it no longer needs the information.
                    if (OleDbHResult.DB_E_ERRORSOCCURRED != hr)
                    {
                        byte* offset = (byte*)literalInfo;
                        tagDBLITERALINFO tag = new tagDBLITERALINFO();
                        for (int i = 0; i < literalCount; ++i, offset += ODB.SizeOf_tagDBLITERALINFO)
                        {
                            if (offset is null)
                            {
                                throw new NullReferenceException();
                            }
                            tag = Marshal.PtrToStructure<tagDBLITERALINFO>((IntPtr)offset)!;

                            DataRow row = table.NewRow();
                            row[literalName] = ((OleDbLiteral)tag.it).ToString();
                            row[literalValue] = tag.pwszLiteralValue;
                            row[invalidChars] = tag.pwszInvalidChars;
                            row[invalidStart] = tag.pwszInvalidStartingChars;
                            row[literal] = tag.it;
                            row[maxlen] = tag.cchMaxLen;

                            table.Rows.Add(row);
                            row.AcceptChanges();
                        }
                        if (hr < 0)
                        { // ignore infomsg
                            ProcessResults(hr);
                        }
                    }
                    else
                    {
                        SafeNativeMethods.Wrapper.ClearErrorInfo();
                    }
                }
                return table;
            }
        }

        internal DataTable? BuildInfoKeywords()
        {
            DataTable? table = new DataTable(ODB.DbInfoKeywords);
            table.Locale = CultureInfo.InvariantCulture;
            DataColumn keyword = new DataColumn(ODB.Keyword, typeof(string));
            table.Columns.Add(keyword);

            if (!AddInfoKeywordsToTable(table, keyword))
            {
                table = null;
            }

            return table;
        }

        internal bool AddInfoKeywordsToTable(DataTable table, DataColumn keyword)
        {
            using (IDBInfoWrapper wrapper = IDBInfo())
            {
                UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value;
                if (null == dbInfo)
                {
                    return false;
                }

                OleDbHResult hr;
                string keywords;
                hr = dbInfo.GetKeywords(out keywords);

                if (hr < 0)
                { // ignore infomsg
                    ProcessResults(hr);
                }

                if (null != keywords)
                {
                    string[] values = keywords.Split(s_comma);
                    for (int i = 0; i < values.Length; ++i)
                    {
                        DataRow row = table.NewRow();
                        row[keyword] = values[i];

                        table.Rows.Add(row);
                        row.AcceptChanges();
                    }
                }
                return true;
            }
        }

        internal DataTable BuildSchemaGuids()
        {
            DataTable table = new DataTable(ODB.SchemaGuids);
            table.Locale = CultureInfo.InvariantCulture;

            DataColumn schemaGuid = new DataColumn(ODB.Schema, typeof(Guid));
            DataColumn restrictionSupport = new DataColumn(ODB.RestrictionSupport, typeof(int));

            table.Columns.Add(schemaGuid);
            table.Columns.Add(restrictionSupport);

            SchemaSupport[]? supportedSchemas = GetSchemaRowsetInformation();

            if (null != supportedSchemas)
            {
                object[] values = new object[2];
                table.BeginLoadData();
                for (int i = 0; i < supportedSchemas.Length; ++i)
                {
                    values[0] = supportedSchemas[i]._schemaRowset;
                    values[1] = supportedSchemas[i]._restrictions;
                    table.LoadDataRow(values, LoadOption.OverwriteChanges);
                }
                table.EndLoadData();
            }
            return table;
        }

        internal string? GetLiteralInfo(int literal)
        {
            using (IDBInfoWrapper wrapper = IDBInfo())
            {
                UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value;
                if (null == dbInfo)
                {
                    return null;
                }
                string? literalValue = null;
                IntPtr literalInfo = IntPtr.Zero;
                int literalCount = 0;
                OleDbHResult hr;

                using (DualCoTaskMem handle = new DualCoTaskMem(dbInfo, new int[1] { literal }, out literalCount, out literalInfo, out hr))
                {
                    // All literals were either invalid or unsupported. The provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to FALSE. The consumer frees this memory when it no longer needs the information.
                    if (OleDbHResult.DB_E_ERRORSOCCURRED != hr)
                    {
                        if ((1 == literalCount) && Marshal.ReadInt32(literalInfo, ODB.OffsetOf_tagDBLITERALINFO_it) == literal)
                        {
                            literalValue = Marshal.PtrToStringUni(Marshal.ReadIntPtr(literalInfo, 0));
                        }
                        if (hr < 0)
                        { // ignore infomsg
                            ProcessResults(hr);
                        }
                    }
                    else
                    {
                        SafeNativeMethods.Wrapper.ClearErrorInfo();
                    }
                }
                return literalValue;
            }
        }

        internal SchemaSupport[]? GetSchemaRowsetInformation()
        {
            OleDbConnectionString constr = ConnectionString;
            SchemaSupport[]? supportedSchemas = constr.SchemaSupport;
            if (null != supportedSchemas)
            {
                return supportedSchemas;
            }
            using (IDBSchemaRowsetWrapper wrapper = IDBSchemaRowset())
            {
                UnsafeNativeMethods.IDBSchemaRowset? dbSchemaRowset = wrapper.Value;
                if (null == dbSchemaRowset)
                {
                    return null; // IDBSchemaRowset not supported
                }

                OleDbHResult hr;
                int schemaCount = 0;
                IntPtr schemaGuids = IntPtr.Zero;
                IntPtr schemaRestrictions = IntPtr.Zero;

                using (DualCoTaskMem safehandle = new DualCoTaskMem(dbSchemaRowset, out schemaCount, out schemaGuids, out schemaRestrictions, out hr))
                {
                    dbSchemaRowset = null;
                    if (hr < 0)
                    { // ignore infomsg
                        ProcessResults(hr);
                    }

                    supportedSchemas = new SchemaSupport[schemaCount];
                    if (IntPtr.Zero != schemaGuids)
                    {
                        for (int i = 0, offset = 0; i < supportedSchemas.Length; ++i, offset += ODB.SizeOf_Guid)
                        {
                            IntPtr ptr = ADP.IntPtrOffset(schemaGuids, i * ODB.SizeOf_Guid);
                            supportedSchemas[i]._schemaRowset = Marshal.PtrToStructure<Guid>(ptr)!;
                        }
                    }
                    if (IntPtr.Zero != schemaRestrictions)
                    {
                        for (int i = 0; i < supportedSchemas.Length; ++i)
                        {
                            supportedSchemas[i]._restrictions = Marshal.ReadInt32(schemaRestrictions, i * 4);
                        }
                    }
                }
                constr.SchemaSupport = supportedSchemas;
                return supportedSchemas;
            }
        }

        internal DataTable? GetSchemaRowset(Guid schema, object?[]? restrictions)
        {
            if (null == restrictions)
            {
                restrictions = Array.Empty<object>();
            }
            DataTable? dataTable = null;
            using (IDBSchemaRowsetWrapper wrapper = IDBSchemaRowset())
            {
                UnsafeNativeMethods.IDBSchemaRowset dbSchemaRowset = wrapper.Value;
                if (null == dbSchemaRowset)
                {
                    throw ODB.SchemaRowsetsNotSupported(Provider);
                }

                UnsafeNativeMethods.IRowset? rowset = null;
                OleDbHResult hr;
                hr = dbSchemaRowset.GetRowset(IntPtr.Zero, in schema, restrictions.Length, restrictions, in ODB.IID_IRowset, 0, IntPtr.Zero, out rowset);

                if (hr < 0)
                { // ignore infomsg
                    ProcessResults(hr);
                }

                if (null != rowset)
                {
                    using (OleDbDataReader dataReader = new OleDbDataReader(Connection, null, 0, CommandBehavior.Default))
                    {
                        dataReader.InitializeIRowset(rowset, ChapterHandle.DB_NULL_HCHAPTER, IntPtr.Zero);
                        dataReader.BuildMetaInfo();
                        dataReader.HasRowsRead();

                        dataTable = new DataTable();
                        dataTable.Locale = CultureInfo.InvariantCulture;
                        dataTable.TableName = OleDbSchemaGuid.GetTextFromValue(schema);
                        OleDbDataAdapter.FillDataTable(dataReader, dataTable);
                    }
                }
                return dataTable;
            }
        }

        // returns true if there is an active data reader on the specified command
        internal bool HasLiveReader(OleDbCommand cmd)
        {
            OleDbDataReader? reader = null;

            if (null != ReferenceCollection)
            {
                reader = ReferenceCollection.FindItem<OleDbDataReader>(OleDbReferenceCollection.DataReaderTag, (dataReader) => cmd == dataReader.Command);
            }

            return (reader != null);
        }

        private void ProcessResults(OleDbHResult hr)
        {
            OleDbConnection? connection = Connection; // get value from weakref only once
            Exception? e = OleDbConnection.ProcessResults(hr, connection);
            if (null != e)
            { throw e; }
        }

        internal bool SupportSchemaRowset(Guid schema)
        {
            SchemaSupport[]? schemaSupport = GetSchemaRowsetInformation();
            if (null != schemaSupport)
            {
                for (int i = 0; i < schemaSupport.Length; ++i)
                {
                    if (schema == schemaSupport[i]._schemaRowset)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private static object CreateInstanceDataLinks()
        {
            Type datalink = Type.GetTypeFromCLSID(ODB.CLSID_DataLinks, true)!;
            return Activator.CreateInstance(datalink, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, null, null, CultureInfo.InvariantCulture, null)!;
        }

        // @devnote: should be multithread safe access to OleDbConnection.idataInitialize,
        // though last one wins for setting variable.  It may be different objects, but
        // OLE DB will ensure I'll work with just the single pool
        private static OleDbServicesWrapper GetObjectPool()
        {
            OleDbServicesWrapper? wrapper = OleDbConnectionInternal.idataInitialize;
            if (null == wrapper)
            {
                lock (dataInitializeLock)
                {
                    wrapper = OleDbConnectionInternal.idataInitialize;
                    if (null == wrapper)
                    {
                        VersionCheck();

                        object datalinks;
                        try
                        {
                            datalinks = CreateInstanceDataLinks();
                        }
                        catch (Exception e)
                        {
                            // UNDONE - should not be catching all exceptions!!!
                            if (!ADP.IsCatchableExceptionType(e))
                            {
                                throw;
                            }

                            throw ODB.MDACNotAvailable(e);
                        }
                        if (null == datalinks)
                        {
                            throw ODB.MDACNotAvailable(null);
                        }
                        wrapper = new OleDbServicesWrapper(datalinks);
                        OleDbConnectionInternal.idataInitialize = wrapper;
                    }
                }
            }
            Debug.Assert(null != wrapper, "GetObjectPool: null dataInitialize");
            return wrapper;
        }

        private static void VersionCheck()
        {
            // $REVIEW: do we still need this?
            // if ApartmentUnknown, then CoInitialize may not have been called yet
            if (ApartmentState.Unknown == Thread.CurrentThread.GetApartmentState())
            {
                SetMTAApartmentState();
            }

            ADP.CheckVersionMDAC(false);
        }

        [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        private static void SetMTAApartmentState()
        {
            // we are defaulting to a multithread apartment state
            Thread.CurrentThread.SetApartmentState(ApartmentState.MTA);
        }

        // @devnote: should be multithread safe
        public static void ReleaseObjectPool()
        {
            OleDbConnectionInternal.idataInitialize = null;
        }

        internal OleDbTransaction? ValidateTransaction(OleDbTransaction? transaction, string method)
        {
            if (null != this.weakTransaction)
            {
                OleDbTransaction? head = (OleDbTransaction?)this.weakTransaction.Target;
                if ((null != head) && this.weakTransaction.IsAlive)
                {
                    head = OleDbTransaction.TransactionUpdate(head);

                    // either we are wrong or finalize was called and object still alive
                    Debug.Assert(null != head, "unexcpted Transaction state");
                }
                // else transaction has finalized on user

                if (null != head)
                {
                    if (null == transaction)
                    {
                        // valid transaction exists and cmd doesn't have it
                        throw ADP.TransactionRequired(method);
                    }
                    else
                    {
                        OleDbTransaction tail = OleDbTransaction.TransactionLast(head);
                        if (tail != transaction)
                        {
                            if (tail.Connection != transaction.Connection)
                            {
                                throw ADP.TransactionConnectionMismatch();
                            }
                            // else cmd has incorrect transaction
                            throw ADP.TransactionCompleted();
                        }
                        // else cmd has correct transaction
                        return transaction;
                    }
                }
                else
                { // cleanup for Finalized transaction
                    this.weakTransaction = null;
                }
            }
            else if ((null != transaction) && (null != transaction.Connection))
            {
                throw ADP.TransactionConnectionMismatch();
            }
            // else no transaction and cmd is correct

            // if transactionObject is from this connection but zombied
            // and no transactions currently exists - then ignore the bogus object
            return null;
        }

        internal Dictionary<string, OleDbPropertyInfo>? GetPropertyInfo(Guid[] propertySets)
        {
            Dictionary<string, OleDbPropertyInfo>? properties = null;

            if (null == propertySets)
            {
                propertySets = Array.Empty<Guid>();
            }
            using (PropertyIDSet propidset = new PropertyIDSet(propertySets))
            {
                using (IDBPropertiesWrapper idbProperties = IDBProperties())
                {
                    using (PropertyInfoSet infoset = new PropertyInfoSet(idbProperties.Value, propidset))
                    {
                        properties = infoset.GetValues();
                    }
                }
            }
            return properties;
        }
    }
}