File: OleDbConnectionStringBuilder.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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;

namespace System.Data.OleDb
{
    [DefaultProperty("Provider")]
    [RefreshProperties(RefreshProperties.All)]
    [TypeConverter(typeof(OleDbConnectionStringBuilder.OleDbConnectionStringBuilderConverter))]
#if NET
    [RequiresDynamicCode(OleDbConnection.TrimWarning)]
#endif
    public sealed class OleDbConnectionStringBuilder : DbConnectionStringBuilder
    {
        private enum Keywords
        {
            FileName,
            Provider,
            DataSource,
            PersistSecurityInfo,
            OleDbServices,
        }
        private static readonly string[] s_validKeywords = new string[5]
        {
            DbConnectionStringKeywords.FileName,
            DbConnectionStringKeywords.Provider,
            DbConnectionStringKeywords.DataSource,
            DbConnectionStringKeywords.PersistSecurityInfo,
            DbConnectionStringKeywords.OleDbServices
        };
        private static readonly Dictionary<string, Keywords> s_keywords = new Dictionary<string, Keywords>(5, StringComparer.OrdinalIgnoreCase)
        {
            { DbConnectionStringKeywords.FileName, Keywords.FileName },
            { DbConnectionStringKeywords.Provider, Keywords.Provider },
            { DbConnectionStringKeywords.DataSource, Keywords.DataSource },
            { DbConnectionStringKeywords.PersistSecurityInfo, Keywords.PersistSecurityInfo },
            { DbConnectionStringKeywords.OleDbServices, Keywords.OleDbServices },
        };

        private string[]? _knownKeywords;
        private Dictionary<string, OleDbPropertyInfo>? _propertyInfo;

        private string _fileName = DbConnectionStringDefaults.FileName;

        private string _dataSource = DbConnectionStringDefaults.DataSource;
        private string _provider = DbConnectionStringDefaults.Provider;

        private int _oleDbServices = DbConnectionStringDefaults.OleDbServices;

        private bool _persistSecurityInfo = DbConnectionStringDefaults.PersistSecurityInfo;

        public OleDbConnectionStringBuilder() : this(null)
        {
            _knownKeywords = s_validKeywords;
        }

        public OleDbConnectionStringBuilder(string? connectionString) : base()
        {
            if (!ADP.IsEmpty(connectionString))
            {
                ConnectionString = connectionString;
            }
        }

        [AllowNull]
        public override object this[string keyword]
        {
            get
            {
                ADP.CheckArgumentNull(keyword, "keyword");
                object? value;
                Keywords index;
                if (s_keywords.TryGetValue(keyword, out index))
                {
                    value = GetAt(index);
                }
                else if (!base.TryGetValue(keyword, out value))
                {
                    Dictionary<string, OleDbPropertyInfo> dynamic = GetProviderInfo(Provider);
                    OleDbPropertyInfo info = dynamic[keyword];
                    value = info._defaultValue!;
                }
                return value;
            }
            set
            {
                if (null != value)
                {
                    ADP.CheckArgumentNull(keyword, "keyword");
                    Keywords index;
                    if (s_keywords.TryGetValue(keyword, out index))
                    {
                        switch (index)
                        {
                            case Keywords.DataSource:
                                DataSource = ConvertToString(value);
                                break;
                            case Keywords.FileName:
                                FileName = ConvertToString(value);
                                break;
                            case Keywords.Provider:
                                Provider = ConvertToString(value);
                                break;

                            case Keywords.OleDbServices:
                                OleDbServices = ConvertToInt32(value);
                                break;

                            case Keywords.PersistSecurityInfo:
                                PersistSecurityInfo = ConvertToBoolean(value);
                                break;
                            default:
                                Debug.Fail("unexpected keyword");
                                throw ADP.KeywordNotSupported(keyword);
                        }
                    }
                    else
                    {
                        base[keyword] = value;
                        ClearPropertyDescriptors();
                    }
                }
                else
                {
                    Remove(keyword);
                }
            }
        }

        [DisplayName(DbConnectionStringKeywords.DataSource)]
        [RefreshProperties(RefreshProperties.All)]
        // TODO: hand off to editor VS, if SQL - do database names, if Jet do file picker
        public string DataSource
        {
            get { return _dataSource; }
            set
            {
                SetValue(DbConnectionStringKeywords.DataSource, value);
                _dataSource = value;
            }
        }

        [DisplayName(DbConnectionStringKeywords.FileName)]
        [Editor("System.Windows.Forms.Design.FileNameEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
                "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
        [RefreshProperties(RefreshProperties.All)]
        // TODO: hand off to VS, they derive from FileNameEditor and set the OpenDialogFilter to *.UDL
        public string FileName
        {
            get { return _fileName; }
            set
            {
                SetValue(DbConnectionStringKeywords.FileName, value);
                _fileName = value;
            }
        }

        [DisplayName(DbConnectionStringKeywords.OleDbServices)]
        [RefreshProperties(RefreshProperties.All)]
        [TypeConverter(typeof(OleDbConnectionStringBuilder.OleDbServicesConverter))]
        public int OleDbServices
        {
            get { return _oleDbServices; }
            set
            {
                SetValue(DbConnectionStringKeywords.OleDbServices, value);
                _oleDbServices = value;
            }
        }

        [DisplayName(DbConnectionStringKeywords.PersistSecurityInfo)]
        [RefreshProperties(RefreshProperties.All)]
        public bool PersistSecurityInfo
        {
            get { return _persistSecurityInfo; }
            set
            {
                SetValue(DbConnectionStringKeywords.PersistSecurityInfo, value);
                _persistSecurityInfo = value;
            }
        }

        [DisplayName(DbConnectionStringKeywords.Provider)]
        [RefreshProperties(RefreshProperties.All)]
        [TypeConverter(typeof(OleDbConnectionStringBuilder.OleDbProviderConverter))]
        public string Provider
        {
            get { return _provider; }
            set
            {
                SetValue(DbConnectionStringKeywords.Provider, value);
                _provider = value;
                RestartProvider();
            }
        }

        public override ICollection Keys
        {
            get
            {
                string[]? knownKeywords = _knownKeywords;
                if (null == knownKeywords)
                {
                    Dictionary<string, OleDbPropertyInfo> dynamic = GetProviderInfo(Provider);
                    if (0 < dynamic.Count)
                    {
                        knownKeywords = new string[s_validKeywords.Length + dynamic.Count];
                        s_validKeywords.CopyTo(knownKeywords, 0);
                        dynamic.Keys.CopyTo(knownKeywords, s_validKeywords.Length);
                    }
                    else
                    {
                        knownKeywords = s_validKeywords;
                    }

                    int count = 0;
                    foreach (string keyword in base.Keys)
                    {
                        bool flag = true;
                        foreach (string s in knownKeywords)
                        {
                            if (StringComparer.OrdinalIgnoreCase.Equals(s, keyword))
                            {
                                flag = false;
                                break;
                            }
                        }
                        if (flag)
                        {
                            count++;
                        }
                    }
                    if (0 < count)
                    {
                        string[] tmp = new string[knownKeywords.Length + count];
                        knownKeywords.CopyTo(tmp, 0);

                        int index = knownKeywords.Length;
                        foreach (string keyword in base.Keys)
                        {
                            bool flag = true;
                            foreach (string s in knownKeywords)
                            {
                                if (StringComparer.OrdinalIgnoreCase.Equals(s, keyword))
                                {
                                    flag = false;
                                    break;
                                }
                            }
                            if (flag)
                            {
                                tmp[index++] = keyword;
                            }
                        }
                        knownKeywords = tmp;
                    }
                    _knownKeywords = knownKeywords;
                }
                return new System.Data.Common.ReadOnlyCollection<string>(knownKeywords);
            }
        }

        public override bool ContainsKey(string keyword)
        {
            ADP.CheckArgumentNull(keyword, "keyword");
            return s_keywords.ContainsKey(keyword) || base.ContainsKey(keyword);
        }

        private static bool ConvertToBoolean(object value)
        {
            return DbConnectionStringBuilderUtil.ConvertToBoolean(value);
        }
        private static int ConvertToInt32(object value)
        {
            return DbConnectionStringBuilderUtil.ConvertToInt32(value);
        }
        private static string ConvertToString(object value)
        {
            return DbConnectionStringBuilderUtil.ConvertToString(value);
        }

        public override void Clear()
        {
            base.Clear();
            for (int i = 0; i < s_validKeywords.Length; ++i)
            {
                Reset((Keywords)i);
            }
            base.ClearPropertyDescriptors();
            _knownKeywords = s_validKeywords;
        }

        private object GetAt(Keywords index)
        {
            switch (index)
            {
                case Keywords.DataSource:
                    return DataSource;
                case Keywords.FileName:
                    return FileName;
                case Keywords.OleDbServices:
                    return OleDbServices;
                case Keywords.PersistSecurityInfo:
                    return PersistSecurityInfo;
                case Keywords.Provider:
                    return Provider;
                default:
                    Debug.Fail("unexpected keyword");
                    throw ADP.KeywordNotSupported(s_validKeywords[(int)index]);
            }
        }

        public override bool Remove(string keyword)
        {
            ADP.CheckArgumentNull(keyword, "keyword");
            bool value = base.Remove(keyword);

            Keywords index;
            if (s_keywords.TryGetValue(keyword, out index))
            {
                Reset(index);
            }
            else if (value)
            {
                ClearPropertyDescriptors();
            }
            return value;
        }

        private void Reset(Keywords index)
        {
            switch (index)
            {
                case Keywords.DataSource:
                    _dataSource = DbConnectionStringDefaults.DataSource;
                    break;
                case Keywords.FileName:
                    _fileName = DbConnectionStringDefaults.FileName;
                    RestartProvider();
                    break;
                case Keywords.OleDbServices:
                    _oleDbServices = DbConnectionStringDefaults.OleDbServices;
                    break;
                case Keywords.PersistSecurityInfo:
                    _persistSecurityInfo = DbConnectionStringDefaults.PersistSecurityInfo;
                    break;
                case Keywords.Provider:
                    _provider = DbConnectionStringDefaults.Provider;
                    RestartProvider();
                    break;
                default:
                    Debug.Fail("unexpected keyword");
                    throw ADP.KeywordNotSupported(s_validKeywords[(int)index]);
            }
        }

        private new void ClearPropertyDescriptors()
        {
            base.ClearPropertyDescriptors();
            _knownKeywords = null;
        }

        private void RestartProvider()
        {
            ClearPropertyDescriptors();
            _propertyInfo = null;
        }

        private void SetValue(string keyword, bool value)
        {
            base[keyword] = value.ToString(null);
        }
        private void SetValue(string keyword, int value)
        {
            base[keyword] = value.ToString((System.IFormatProvider?)null);
        }
        private void SetValue(string keyword, string value)
        {
            ADP.CheckArgumentNull(value, keyword);
            base[keyword] = value;
        }

        public override bool TryGetValue(string keyword, [NotNullWhen(true)] out object? value)
        {
            ADP.CheckArgumentNull(keyword, "keyword");
            Keywords index;
            if (s_keywords.TryGetValue(keyword, out index))
            {
                value = GetAt(index);
                return true;
            }
            else if (!base.TryGetValue(keyword, out value))
            {
                Dictionary<string, OleDbPropertyInfo> dynamic = GetProviderInfo(Provider);
                OleDbPropertyInfo? info;
                if (dynamic.TryGetValue(keyword, out info))
                {
                    value = info._defaultValue!;
                    return true;
                }
                return false;
            }
            return true;
        }

        private Dictionary<string, OleDbPropertyInfo> GetProviderInfo(string provider)
        {
            Dictionary<string, OleDbPropertyInfo>? providerInfo = _propertyInfo;
            if (null == providerInfo)
            {
                providerInfo = new Dictionary<string, OleDbPropertyInfo>(StringComparer.OrdinalIgnoreCase);
                if (!ADP.IsEmpty(provider))
                {
                    Dictionary<string, OleDbPropertyInfo>? hash = null;
                    try
                    {
                        StringBuilder builder = new StringBuilder();
                        AppendKeyValuePair(builder, DbConnectionStringKeywords.Provider, provider);
                        OleDbConnectionString constr = new OleDbConnectionString(builder.ToString(), true);

                        // load provider without calling Initialize or CreateDataSource
                        using (OleDbConnectionInternal connection = new OleDbConnectionInternal(constr, null))
                        {
                            // get all the init property information for the provider
                            hash = connection.GetPropertyInfo(new Guid[] { OleDbPropertySetGuid.DBInitAll })!;
                            foreach (KeyValuePair<string, OleDbPropertyInfo> entry in hash)
                            {
                                Keywords index;
                                OleDbPropertyInfo info = entry.Value;
                                if (!s_keywords.TryGetValue(info._description!, out index))
                                {
                                    if ((OleDbPropertySetGuid.DBInit == info._propertySet) &&
                                            ((ODB.DBPROP_INIT_ASYNCH == info._propertyID) ||
                                             (ODB.DBPROP_INIT_HWND == info._propertyID) ||
                                             (ODB.DBPROP_INIT_PROMPT == info._propertyID)))
                                    {
                                        continue; // skip this keyword
                                    }
                                    providerInfo[info._description!] = info;
                                }
                            }

                            // what are the unique propertysets?
                            List<Guid> listPropertySets = new List<Guid>();
                            foreach (KeyValuePair<string, OleDbPropertyInfo> entry in hash)
                            {
                                OleDbPropertyInfo info = entry.Value;
                                if (!listPropertySets.Contains(info._propertySet))
                                {
                                    listPropertySets.Add(info._propertySet);
                                }
                            }
                            Guid[] arrayPropertySets = new Guid[listPropertySets.Count];
                            listPropertySets.CopyTo(arrayPropertySets, 0);

                            // get all the init property values for the provider
                            using (PropertyIDSet propidset = new PropertyIDSet(arrayPropertySets))
                            {
                                using (IDBPropertiesWrapper idbProperties = connection.IDBProperties())
                                {
                                    OleDbHResult hr;
                                    using (DBPropSet propset = new DBPropSet(idbProperties.Value, propidset, out hr))
                                    {
                                        // OleDbConnectionStringBuilder is ignoring/hiding potential errors of OLEDB provider when reading its properties information
                                        if (0 <= (int)hr)
                                        {
                                            int count = propset.PropertySetCount;
                                            for (int i = 0; i < count; ++i)
                                            {
                                                Guid propertyset;
                                                ItagDBPROP[] props = propset.GetPropertySet(i, out propertyset);

                                                // attach the default property value to the property info
                                                foreach (ItagDBPROP prop in props)
                                                {
                                                    foreach (KeyValuePair<string, OleDbPropertyInfo> entry in hash)
                                                    {
                                                        OleDbPropertyInfo info = entry.Value;
                                                        if ((info._propertyID == prop.dwPropertyID) && (info._propertySet == propertyset))
                                                        {
                                                            info._defaultValue = prop.vValue;

                                                            if (null == info._defaultValue)
                                                            {
                                                                if (typeof(string) == info._type)
                                                                {
                                                                    info._defaultValue = "";
                                                                }
                                                                else if (typeof(int) == info._type)
                                                                {
                                                                    info._defaultValue = 0;
                                                                }
                                                                else if (typeof(bool) == info._type)
                                                                {
                                                                    info._defaultValue = false;
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (System.InvalidOperationException e)
                    {
                        ADP.TraceExceptionWithoutRethrow(e);
                    }
                    catch (System.Data.OleDb.OleDbException e)
                    {
                        ADP.TraceExceptionWithoutRethrow(e);
                    }
                    catch (System.Security.SecurityException e)
                    {
                        ADP.TraceExceptionWithoutRethrow(e);
                    }
                }
                _propertyInfo = providerInfo;
            }
            return providerInfo;
        }

        [RequiresDynamicCode(OleDbConnection.TrimWarning)]
        private sealed class OleDbProviderConverter : StringConverter
        {
            private const int DBSOURCETYPE_DATASOURCE_TDP = 1;
            private const int DBSOURCETYPE_DATASOURCE_MDP = 3;

            private StandardValuesCollection? _standardValues;

            // converter classes should have public ctor
            public OleDbProviderConverter()
            {
            }

            public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
            {
                return true;
            }

            public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
            {
                return false;
            }

            public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
            {
                StandardValuesCollection? dataSourceNames = _standardValues;
                if (null == _standardValues)
                {
                    // Get the sources rowset for the SQLOLEDB enumerator
                    DataTable table = (new OleDbEnumerator()).GetElements();

                    DataColumn column2 = table.Columns["SOURCES_NAME"]!;
                    DataColumn column5 = table.Columns["SOURCES_TYPE"]!;
                    //DataColumn column4 = table.Columns["SOURCES_DESCRIPTION"];

                    System.Collections.Generic.List<string> providerNames = new System.Collections.Generic.List<string>(table.Rows.Count);
                    foreach (DataRow row in table.Rows)
                    {
                        int sourceType = (int)row[column5];
                        if (DBSOURCETYPE_DATASOURCE_TDP == sourceType || DBSOURCETYPE_DATASOURCE_MDP == sourceType)
                        {
                            string progid = (string)row[column2];
                            if (!OleDbConnectionString.IsMSDASQL(progid.ToLowerInvariant()))
                            {
                                if (0 > providerNames.IndexOf(progid))
                                {
                                    providerNames.Add(progid);
                                }
                            }
                        }
                    }

                    // Create the standard values collection that contains the sources
                    dataSourceNames = new StandardValuesCollection(providerNames);
                    _standardValues = dataSourceNames;
                }
                return dataSourceNames;
            }
        }

        [Flags]
        internal enum OleDbServiceValues : int
        {
            DisableAll = unchecked((int)0x00000000),
            ResourcePooling = unchecked((int)0x00000001),
            TransactionEnlistment = unchecked((int)0x00000002),
            ClientCursor = unchecked((int)0x00000004),
            AggregationAfterSession = unchecked((int)0x00000008),
            EnableAll = unchecked((int)0xffffffff),
            Default = ~(ClientCursor | AggregationAfterSession),
        };

        [RequiresDynamicCode(OleDbConnection.TrimWarning)]
        internal sealed class OleDbServicesConverter : TypeConverter
        {
            private StandardValuesCollection? _standardValues;

            // converter classes should have public ctor
            public OleDbServicesConverter() : base()
            {
            }

            public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
            {
                // Only know how to convert from a string
                return ((typeof(string) == sourceType) || base.CanConvertFrom(context, sourceType));
            }

            public override object? ConvertFrom(ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value)
            {
                string? svalue = (value as string);
                if (null != svalue)
                {
                    int services;
                    if (int.TryParse(svalue, out services))
                    {
                        return services;
                    }
                    else
                    {
                        if (svalue.Contains(','))
                        {
                            int convertedValue = 0;
                            string[] values = svalue.Split(OleDbConnectionInternal.s_comma);
                            foreach (string v in values)
                            {
                                convertedValue |= (int)Enum.Parse<OleDbServiceValues>(v, true);
                            }
                            return convertedValue;
                        }
                        else
                        {
                            return (int)Enum.Parse<OleDbServiceValues>(svalue, true);
                        }
                    }
                }
                return base.ConvertFrom(context, culture, value);
            }

            public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
            {
                // Only know how to convert to the NetworkLibrary enumeration
                return ((typeof(string) == destinationType) || base.CanConvertTo(context, destinationType));
            }

            public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
            {
                if ((typeof(string) == destinationType) && (null != value) && (typeof(int) == value.GetType()))
                {
                    return ((OleDbServiceValues)(int)(value)).ToString("G");
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }

            public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
            {
                return true;
            }

            public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
            {
                return false;
            }

            public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context)
            {
                StandardValuesCollection? standardValues = _standardValues;
                if (null == standardValues)
                {
                    OleDbServiceValues[] objValues = Enum.GetValues<OleDbServiceValues>();
                    Array.Sort(objValues, 0, objValues.Length);
                    standardValues = new StandardValuesCollection(objValues);
                    _standardValues = standardValues;
                }
                return standardValues;
            }

            public override bool IsValid(ITypeDescriptorContext? context, object? value)
            {
                return true;
                //return Enum.IsDefined(type, value);
            }
        }

        internal sealed class OleDbConnectionStringBuilderConverter : ExpandableObjectConverter
        {
            // converter classes should have public ctor
            public OleDbConnectionStringBuilderConverter()
            {
            }

            public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
            {
                if (typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor) == destinationType)
                {
                    return true;
                }
                return base.CanConvertTo(context, destinationType);
            }

            public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
            {
                if (destinationType == null)
                {
                    throw ADP.ArgumentNull("destinationType");
                }
                if (typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor) == destinationType)
                {
                    OleDbConnectionStringBuilder? obj = (value as OleDbConnectionStringBuilder);
                    if (null != obj)
                    {
                        return ConvertToInstanceDescriptor(obj);
                    }
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }

            private static System.ComponentModel.Design.Serialization.InstanceDescriptor ConvertToInstanceDescriptor(OleDbConnectionStringBuilder options)
            {
                Type[] ctorParams = new Type[] { typeof(string) };
                object[] ctorValues = new object[] { options.ConnectionString };
                System.Reflection.ConstructorInfo ctor = typeof(OleDbConnectionStringBuilder).GetConstructor(ctorParams)!;
                return new System.ComponentModel.Design.Serialization.InstanceDescriptor(ctor, ctorValues);
            }
        }
    }
}