File: OleDbMetaDataFactory.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.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;

namespace System.Data.OleDb
{
    [RequiresDynamicCode(OleDbConnection.TrimWarning)]
    internal sealed class OleDbMetaDataFactory : DbMetaDataFactory
    { // V1.2.3300

        private readonly struct SchemaRowsetName
        {
            internal SchemaRowsetName(string schemaName, Guid schemaRowset)
            {
                _schemaName = schemaName;
                _schemaRowset = schemaRowset;
            }
            internal readonly string _schemaName;
            internal readonly Guid _schemaRowset;
        }

        private const string _collectionName = "CollectionName";
        private const string _populationMechanism = "PopulationMechanism";
        private const string _prepareCollection = "PrepareCollection";

        private readonly SchemaRowsetName[] _schemaMapping;

        internal OleDbMetaDataFactory(Stream XMLStream,
                                    string serverVersion,
                                    string serverVersionNormalized,
                                    SchemaSupport[]? schemaSupport) :
            base(XMLStream, serverVersion, serverVersionNormalized)
        {
            // set up the colletion mane schema rowset guid mapping
            _schemaMapping = new SchemaRowsetName[] {
                 new SchemaRowsetName(DbMetaDataCollectionNames.DataTypes, OleDbSchemaGuid.Provider_Types),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Catalogs, OleDbSchemaGuid.Catalogs),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Collations, OleDbSchemaGuid.Collations),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Columns, OleDbSchemaGuid.Columns),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Indexes, OleDbSchemaGuid.Indexes),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Procedures, OleDbSchemaGuid.Procedures),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.ProcedureColumns, OleDbSchemaGuid.Procedure_Columns),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.ProcedureParameters, OleDbSchemaGuid.Procedure_Parameters),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Tables, OleDbSchemaGuid.Tables),
                 new SchemaRowsetName(OleDbMetaDataCollectionNames.Views, OleDbSchemaGuid.Views)};

            // verify the existence of the table in the data set
            DataTable? metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections];
            if (metaDataCollectionsTable == null)
            {
                throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.MetaDataCollections);
            }

            // copy the table filtering out any rows that don't apply to the current version of the provider
            metaDataCollectionsTable = CloneAndFilterCollection(DbMetaDataCollectionNames.MetaDataCollections, null);

            // verify the existence of the table in the data set
            DataTable? restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions];
            if (restrictionsTable != null)
            {
                // copy the table filtering out any rows that don't apply to the current version of the provider
                restrictionsTable = CloneAndFilterCollection(DbMetaDataCollectionNames.Restrictions, null);
            }

            // need to filter out any of the collections where
            // 1) it is populated using prepare collection
            // 2) it is in the collection to schema rowset mapping above
            // 3) the provider does not support the necessary schema rowset

            DataColumn? populationMechanism = metaDataCollectionsTable.Columns[_populationMechanism];
            if ((null == populationMechanism) || (typeof(string) != populationMechanism.DataType))
            {
                throw ADP.InvalidXmlMissingColumn(DbMetaDataCollectionNames.MetaDataCollections, _populationMechanism);
            }
            DataColumn? collectionName = metaDataCollectionsTable.Columns[_collectionName];
            if ((null == collectionName) || (typeof(string) != collectionName.DataType))
            {
                throw ADP.InvalidXmlMissingColumn(DbMetaDataCollectionNames.MetaDataCollections, _collectionName);
            }
            DataColumn? restrictionCollectionName = null;
            if (restrictionsTable != null)
            {
                restrictionCollectionName = restrictionsTable.Columns[_collectionName];
                if ((null == restrictionCollectionName) || (typeof(string) != restrictionCollectionName.DataType))
                {
                    throw ADP.InvalidXmlMissingColumn(DbMetaDataCollectionNames.Restrictions, _collectionName);
                }
            }

            foreach (DataRow collection in metaDataCollectionsTable.Rows)
            {
                string? populationMechanismValue = collection[populationMechanism] as string;
                if (ADP.IsEmpty(populationMechanismValue))
                {
                    throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, _populationMechanism);
                }
                string? collectionNameValue = collection[collectionName] as string;
                if (ADP.IsEmpty(collectionNameValue))
                {
                    throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, _collectionName);
                }

                if (populationMechanismValue == _prepareCollection)
                {
                    // is the collection in the mapping
                    int mapping = -1;
                    for (int i = 0; i < _schemaMapping.Length; i++)
                    {
                        if (_schemaMapping[i]._schemaName == collectionNameValue)
                        {
                            mapping = i;
                            break;
                        }
                    }
                    // no go on to the next collection
                    if (mapping == -1)
                    {
                        continue;
                    }

                    // does the provider support the necessary schema rowset
                    bool isSchemaRowsetSupported = false;
                    if (schemaSupport != null)
                    {
                        for (int i = 0; i < schemaSupport.Length; i++)
                        {
                            if (_schemaMapping[mapping]._schemaRowset == schemaSupport[i]._schemaRowset)
                            {
                                isSchemaRowsetSupported = true;
                                break;
                            }
                        }
                    }
                    // if not delete the row from the table
                    if (!isSchemaRowsetSupported)
                    {
                        // but first delete any related restrictions
                        if (restrictionsTable != null)
                        {
                            foreach (DataRow restriction in restrictionsTable.Rows)
                            {
                                string? restrictionCollectionNameValue = restriction[restrictionCollectionName!] as string;
                                if (ADP.IsEmpty(restrictionCollectionNameValue))
                                {
                                    throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.Restrictions, _collectionName);
                                }
                                if (collectionNameValue == restrictionCollectionNameValue)
                                {
                                    restriction.Delete();
                                }
                            }
                            restrictionsTable.AcceptChanges();
                        }
                        collection.Delete();
                    }

                }
            }

            // replace the original table with the updated one
            metaDataCollectionsTable.AcceptChanges();
            CollectionDataSet.Tables.Remove(CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]!);
            CollectionDataSet.Tables.Add(metaDataCollectionsTable);

            if (restrictionsTable != null)
            {
                CollectionDataSet.Tables.Remove(CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]!);
                CollectionDataSet.Tables.Add(restrictionsTable);
            }

        }

        private static string BuildRegularExpression(string invalidChars, string invalidStartingChars)
        {
            StringBuilder regularExpression = new StringBuilder("[^");
            ADP.EscapeSpecialCharacters(invalidStartingChars, regularExpression);
            regularExpression.Append("][^");
            ADP.EscapeSpecialCharacters(invalidChars, regularExpression);
            regularExpression.Append("]*");

            return regularExpression.ToString();
        }

        private DataTable GetDataSourceInformationTable(OleDbConnection connection, OleDbConnectionInternal internalConnection)
        {
            // verify that the data source information table is in the data set
            DataTable? dataSourceInformationTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.DataSourceInformation];
            if (dataSourceInformationTable == null)
            {
                throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.DataSourceInformation);
            }

            // copy the table filtering out any rows that don't apply to tho current version of the prrovider
            dataSourceInformationTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataSourceInformation, null);

            // after filtering there better be just one row
            if (dataSourceInformationTable.Rows.Count != 1)
            {
                throw ADP.IncorrectNumberOfDataSourceInformationRows();
            }
            DataRow dataSourceInformation = dataSourceInformationTable.Rows[0];

            // update the identifier separator
            string? catalogSeparatorPattern = internalConnection.GetLiteralInfo(ODB.DBLITERAL_CATALOG_SEPARATOR);
            string? schemaSeparatorPattern = internalConnection.GetLiteralInfo(ODB.DBLITERAL_SCHEMA_SEPARATOR);

            if (catalogSeparatorPattern != null)
            {
                StringBuilder compositeSeparatorPattern = new StringBuilder();
                StringBuilder patternEscaped = new StringBuilder();
                ADP.EscapeSpecialCharacters(catalogSeparatorPattern, patternEscaped);
                compositeSeparatorPattern.Append(patternEscaped);
                if ((schemaSeparatorPattern != null) && (schemaSeparatorPattern != catalogSeparatorPattern))
                {
                    compositeSeparatorPattern.Append('|');
                    patternEscaped.Length = 0;
                    ADP.EscapeSpecialCharacters(schemaSeparatorPattern, patternEscaped);
                    compositeSeparatorPattern.Append(patternEscaped);
                }
                dataSourceInformation[DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern] = compositeSeparatorPattern.ToString();
            }
            else if (schemaSeparatorPattern != null)
            {
                StringBuilder patternEscaped = new StringBuilder();
                ADP.EscapeSpecialCharacters(schemaSeparatorPattern, patternEscaped);
                dataSourceInformation[DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern] = patternEscaped.ToString();
                ;
            }

            // update the DataSourceProductName
            object? property;
            property = connection.GetDataSourcePropertyValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_DBMSNAME);
            if (property != null)
            {
                dataSourceInformation[DbMetaDataColumnNames.DataSourceProductName] = (string)property;
            }

            // update the server version strings
            dataSourceInformation[DbMetaDataColumnNames.DataSourceProductVersion] = ServerVersion;
            dataSourceInformation[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersionNormalized;

            // values that are the same for all OLE DB Providers.
            dataSourceInformation[DbMetaDataColumnNames.ParameterMarkerFormat] = "?";
            dataSourceInformation[DbMetaDataColumnNames.ParameterMarkerPattern] = "\\?";
            dataSourceInformation[DbMetaDataColumnNames.ParameterNameMaxLength] = 0;

            property = connection.GetDataSourcePropertyValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_GROUPBY);
            GroupByBehavior groupByBehavior = GroupByBehavior.Unknown;
            if (property != null)
            {
                switch ((int)property)
                {
                    case ODB.DBPROPVAL_GB_CONTAINS_SELECT:
                        groupByBehavior = GroupByBehavior.MustContainAll;
                        break;

                    case ODB.DBPROPVAL_GB_EQUALS_SELECT:
                        groupByBehavior = GroupByBehavior.ExactMatch;
                        break;

                    case ODB.DBPROPVAL_GB_NO_RELATION:
                        groupByBehavior = GroupByBehavior.Unrelated;
                        break;

                    case ODB.DBPROPVAL_GB_NOT_SUPPORTED:
                        groupByBehavior = GroupByBehavior.NotSupported;
                        break;
                }
            }
            dataSourceInformation[DbMetaDataColumnNames.GroupByBehavior] = groupByBehavior;

            SetIdentifierCase(DbMetaDataColumnNames.IdentifierCase, ODB.DBPROP_IDENTIFIERCASE, dataSourceInformation, connection);
            SetIdentifierCase(DbMetaDataColumnNames.QuotedIdentifierCase, ODB.DBPROP_QUOTEDIDENTIFIERCASE, dataSourceInformation, connection);

            property = connection.GetDataSourcePropertyValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_ORDERBYCOLUNSINSELECT);
            if (property != null)
            {
                dataSourceInformation[DbMetaDataColumnNames.OrderByColumnsInSelect] = (bool)property;
            }

            DataTable? infoLiterals = internalConnection.BuildInfoLiterals();
            if (infoLiterals != null)
            {
                DataRow[] tableNameRow = infoLiterals.Select("Literal = " + ODB.DBLITERAL_TABLE_NAME.ToString(CultureInfo.InvariantCulture));
                if (tableNameRow != null)
                {
                    object invalidCharsObject = tableNameRow[0]["InvalidChars"];
                    if (invalidCharsObject.GetType() == typeof(string))
                    {
                        string invalidChars = (string)invalidCharsObject;
                        object invalidStartingCharsObject = tableNameRow[0]["InvalidStartingChars"];
                        string invalidStartingChars;
                        if (invalidStartingCharsObject.GetType() == typeof(string))
                        {
                            invalidStartingChars = (string)invalidStartingCharsObject;
                        }
                        else
                        {
                            invalidStartingChars = invalidChars;
                        }
                        dataSourceInformation[DbMetaDataColumnNames.IdentifierPattern] =
                                                    BuildRegularExpression(invalidChars, invalidStartingChars);
                    }
                }
            }

            // build the QuotedIdentifierPattern using the quote prefix and suffix from the provider and
            // assuming that the quote suffix is escaped via repetion (i.e " becomes "")
            string quotePrefix;
            string quoteSuffix;
            connection.GetLiteralQuotes(ADP.GetSchema, out quotePrefix, out quoteSuffix);

            if (quotePrefix != null)
            {
                // if the quote suffix is null assume that it is the same as the prefix (See OLEDB spec
                // IDBInfo::GetLiteralInfo DBLITERAL_QUOTE_SUFFIX.)
                quoteSuffix ??= quotePrefix;

                // only know how to build the parttern if the suffix is 1 character
                // in all other cases just leave the field null
                if (quoteSuffix.Length == 1)
                {
                    StringBuilder scratchStringBuilder = new StringBuilder();
                    ADP.EscapeSpecialCharacters(quoteSuffix, scratchStringBuilder);
                    string escapedQuoteSuffixString = scratchStringBuilder.ToString();
                    scratchStringBuilder.Length = 0;

                    ADP.EscapeSpecialCharacters(quotePrefix, scratchStringBuilder);
                    scratchStringBuilder.Append("(([^");
                    scratchStringBuilder.Append(escapedQuoteSuffixString);
                    scratchStringBuilder.Append("]|");
                    scratchStringBuilder.Append(escapedQuoteSuffixString);
                    scratchStringBuilder.Append(escapedQuoteSuffixString);
                    scratchStringBuilder.Append(")*)");
                    scratchStringBuilder.Append(escapedQuoteSuffixString);
                    dataSourceInformation[DbMetaDataColumnNames.QuotedIdentifierPattern] = scratchStringBuilder.ToString();
                }
            }

            dataSourceInformationTable.AcceptChanges();

            return dataSourceInformationTable;
        }

        private DataTable GetDataTypesTable(OleDbConnection connection)
        {
            // verify the existence of the table in the data set
            DataTable? dataTypesTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.DataTypes];
            if (dataTypesTable == null)
            {
                throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.DataTypes);
            }

            // copy the table filtering out any rows that don't apply to tho current version of the prrovider
            dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null);

            DataTable providerTypesTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Provider_Types, null)!;

            DataColumn?[] targetColumns = new DataColumn?[] {
                dataTypesTable.Columns[DbMetaDataColumnNames.TypeName],
                dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize],
                dataTypesTable.Columns[DbMetaDataColumnNames.CreateParameters],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsAutoIncrementable],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsCaseSensitive],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedLength],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedPrecisionScale],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsLong],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable],
                dataTypesTable.Columns[DbMetaDataColumnNames.IsUnsigned],
                dataTypesTable.Columns[DbMetaDataColumnNames.MaximumScale],
                dataTypesTable.Columns[DbMetaDataColumnNames.MinimumScale],
                dataTypesTable.Columns[DbMetaDataColumnNames.LiteralPrefix],
                dataTypesTable.Columns[DbMetaDataColumnNames.LiteralSuffix],
                dataTypesTable.Columns[OleDbMetaDataColumnNames.NativeDataType]};

            DataColumn?[] sourceColumns = new DataColumn?[] {
                providerTypesTable.Columns["TYPE_NAME"],
                providerTypesTable.Columns["COLUMN_SIZE"],
                providerTypesTable.Columns["CREATE_PARAMS"],
                providerTypesTable.Columns["AUTO_UNIQUE_VALUE"],
                providerTypesTable.Columns["CASE_SENSITIVE"],
                providerTypesTable.Columns["IS_FIXEDLENGTH"],
                providerTypesTable.Columns["FIXED_PREC_SCALE"],
                providerTypesTable.Columns["IS_LONG"],
                providerTypesTable.Columns["IS_NULLABLE"],
                providerTypesTable.Columns["UNSIGNED_ATTRIBUTE"],
                providerTypesTable.Columns["MAXIMUM_SCALE"],
                providerTypesTable.Columns["MINIMUM_SCALE"],
                providerTypesTable.Columns["LITERAL_PREFIX"],
                providerTypesTable.Columns["LITERAL_SUFFIX"],
                providerTypesTable.Columns["DATA_TYPE"]};

            Debug.Assert(sourceColumns.Length == targetColumns.Length);

            DataColumn isSearchable = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable]!;
            DataColumn isSearchableWithLike = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchableWithLike]!;
            DataColumn providerDbType = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]!;
            DataColumn clrType = dataTypesTable.Columns[DbMetaDataColumnNames.DataType]!;
            DataColumn isLong = dataTypesTable.Columns[DbMetaDataColumnNames.IsLong]!;
            DataColumn isFixed = dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedLength]!;
            DataColumn sourceOleDbType = providerTypesTable.Columns["DATA_TYPE"]!;

            DataColumn searchable = providerTypesTable.Columns["SEARCHABLE"]!;

            foreach (DataRow sourceRow in providerTypesTable.Rows)
            {
                DataRow newRow = dataTypesTable.NewRow();
                for (int i = 0; i < sourceColumns.Length; i++)
                {
                    if ((sourceColumns[i] is DataColumn sourceColumn) && (targetColumns[i] is DataColumn targetColumn))
                    {
                        newRow[targetColumn] = sourceRow[sourceColumn];
                    }
                }

                short nativeDataType = (short)Convert.ChangeType(sourceRow[sourceOleDbType], typeof(short), CultureInfo.InvariantCulture);
                NativeDBType nativeType = NativeDBType.FromDBType(nativeDataType, (bool)newRow[isLong], (bool)newRow[isFixed]);

                newRow[clrType] = nativeType.dataType!.FullName;
                newRow[providerDbType] = nativeType.enumOleDbType;

                // searchable has to be special cased because it is not an eaxct mapping
                if ((isSearchable != null) && (isSearchableWithLike != null) && (searchable != null))
                {
                    newRow[isSearchable] = DBNull.Value;
                    newRow[isSearchableWithLike] = DBNull.Value;
                    if (DBNull.Value != sourceRow[searchable])
                    {
                        long searchableValue = (long)(sourceRow[searchable]);
                        switch (searchableValue)
                        {
                            case ODB.DB_UNSEARCHABLE:
                                newRow[isSearchable] = false;
                                newRow[isSearchableWithLike] = false;
                                break;

                            case ODB.DB_LIKE_ONLY:
                                newRow[isSearchable] = false;
                                newRow[isSearchableWithLike] = true;
                                break;

                            case ODB.DB_ALL_EXCEPT_LIKE:
                                newRow[isSearchable] = true;
                                newRow[isSearchableWithLike] = false;
                                break;

                            case ODB.DB_SEARCHABLE:
                                newRow[isSearchable] = true;
                                newRow[isSearchableWithLike] = true;
                                break;
                        }
                    }
                }

                dataTypesTable.Rows.Add(newRow);
            }

            dataTypesTable.AcceptChanges();

            return dataTypesTable;

        }

        private DataTable GetReservedWordsTable(OleDbConnectionInternal internalConnection)
        {
            // verify the existence of the table in the data set
            DataTable? reservedWordsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.ReservedWords];
            if (null == reservedWordsTable)
            {
                throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.ReservedWords);
            }

            // copy the table filtering out any rows that don't apply to tho current version of the prrovider
            reservedWordsTable = CloneAndFilterCollection(DbMetaDataCollectionNames.ReservedWords, null);

            DataColumn? reservedWordColumn = reservedWordsTable.Columns[DbMetaDataColumnNames.ReservedWord];
            if (null == reservedWordColumn)
            {
                throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.ReservedWords);
            }

            if (!internalConnection.AddInfoKeywordsToTable(reservedWordsTable, reservedWordColumn))
            {
                throw ODB.IDBInfoNotSupported();
            }

            return reservedWordsTable;
        }

        protected override DataTable PrepareCollection(string collectionName, string?[]? restrictions, DbConnection connection)
        {
            OleDbConnection oleDbConnection = (OleDbConnection)connection;
            OleDbConnectionInternal oleDbInternalConnection = (OleDbConnectionInternal)(oleDbConnection.InnerConnection);
            DataTable? resultTable = null;
            if (collectionName == DbMetaDataCollectionNames.DataSourceInformation)
            {
                if (!ADP.IsEmptyArray(restrictions))
                {
                    throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataSourceInformation);
                }
                resultTable = GetDataSourceInformationTable(oleDbConnection, oleDbInternalConnection);
            }
            else if (collectionName == DbMetaDataCollectionNames.DataTypes)
            {
                if (!ADP.IsEmptyArray(restrictions))
                {
                    throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes);
                }
                resultTable = GetDataTypesTable(oleDbConnection);
            }
            else if (collectionName == DbMetaDataCollectionNames.ReservedWords)
            {
                if (!ADP.IsEmptyArray(restrictions))
                {
                    throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.ReservedWords);
                }
                resultTable = GetReservedWordsTable(oleDbInternalConnection);
            }
            else
            {
                for (int i = 0; i < _schemaMapping.Length; i++)
                {
                    if (_schemaMapping[i]._schemaName == collectionName)
                    {
                        // need to special case the oledb schema rowset restrictions on columns that are not
                        // string tpyes
                        object?[]? mungedRestrictions = restrictions;
                        ;
                        if (restrictions != null)
                        {
                            //verify that there are not too many restrictions
                            DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]!;
                            int numberOfSupportedRestictions = -1;
                            // prepare colletion is called with the exact collection name so
                            // we can do an exact string comparison here
                            foreach (DataRow row in metaDataCollectionsTable.Rows)
                            {
                                string candidateCollectionName = ((string)row[DbMetaDataColumnNames.CollectionName, DataRowVersion.Current]);

                                if (collectionName == candidateCollectionName)
                                {
                                    numberOfSupportedRestictions = (int)row[DbMetaDataColumnNames.NumberOfRestrictions];
                                    if (numberOfSupportedRestictions < restrictions.Length)
                                    {
                                        throw ADP.TooManyRestrictions(collectionName);
                                    }
                                    break;
                                }
                            }

                            Debug.Assert(numberOfSupportedRestictions != -1, "PrepareCollection was called for an collection that is not supported.");

                            // the 4th restrictionon the indexes schema rowset(type) is an I2 - enum
                            const int indexRestrictionTypeSlot = 3;

                            if ((collectionName == OleDbMetaDataCollectionNames.Indexes) &&
                                (restrictions.Length >= indexRestrictionTypeSlot + 1) &&
                                (restrictions[indexRestrictionTypeSlot] != null))
                            {
                                mungedRestrictions = new object[restrictions.Length];
                                for (int j = 0; j < restrictions.Length; j++)
                                {
                                    mungedRestrictions[j] = restrictions[j];
                                }

                                ushort indexTypeValue;

                                if ((restrictions[indexRestrictionTypeSlot] == "DBPROPVAL_IT_BTREE") ||
                                    (restrictions[indexRestrictionTypeSlot] == "1"))
                                {
                                    indexTypeValue = 1;
                                }
                                else if ((restrictions[indexRestrictionTypeSlot] == "DBPROPVAL_IT_HASH") ||
                                    (restrictions[indexRestrictionTypeSlot] == "2"))
                                {
                                    indexTypeValue = 2;
                                }
                                else if ((restrictions[indexRestrictionTypeSlot] == "DBPROPVAL_IT_CONTENT") ||
                                    (restrictions[indexRestrictionTypeSlot] == "3"))
                                {
                                    indexTypeValue = 3;
                                }
                                else if ((restrictions[indexRestrictionTypeSlot] == "DBPROPVAL_IT_OTHER") ||
                                    (restrictions[indexRestrictionTypeSlot] == "4"))
                                {
                                    indexTypeValue = 4;
                                }
                                else
                                {
                                    throw ADP.InvalidRestrictionValue(collectionName, "TYPE", restrictions[indexRestrictionTypeSlot]!);
                                }

                                mungedRestrictions[indexRestrictionTypeSlot] = indexTypeValue;

                            }

                            // the 4th restrictionon the procedures schema rowset(type) is an I2 - enum
                            const int procedureRestrictionTypeSlot = 3;

                            if ((collectionName == OleDbMetaDataCollectionNames.Procedures) &&
                                (restrictions.Length >= procedureRestrictionTypeSlot + 1) &&
                                (restrictions[procedureRestrictionTypeSlot] != null))
                            {
                                mungedRestrictions = new object[restrictions.Length];
                                for (int j = 0; j < restrictions.Length; j++)
                                {
                                    mungedRestrictions[j] = restrictions[j];
                                }

                                short procedureTypeValue;

                                if ((restrictions[procedureRestrictionTypeSlot] == "DB_PT_UNKNOWN") ||
                                    (restrictions[procedureRestrictionTypeSlot] == "1"))
                                {
                                    procedureTypeValue = 1;
                                }
                                else if ((restrictions[procedureRestrictionTypeSlot] == "DB_PT_PROCEDURE") ||
                                    (restrictions[procedureRestrictionTypeSlot] == "2"))
                                {
                                    procedureTypeValue = 2;
                                }
                                else if ((restrictions[procedureRestrictionTypeSlot] == "DB_PT_FUNCTION") ||
                                    (restrictions[procedureRestrictionTypeSlot] == "3"))
                                {
                                    procedureTypeValue = 3;
                                }
                                else
                                {
                                    throw ADP.InvalidRestrictionValue(collectionName, "PROCEDURE_TYPE", restrictions[procedureRestrictionTypeSlot]!);
                                }

                                mungedRestrictions[procedureRestrictionTypeSlot] = procedureTypeValue;

                            }
                        }

                        resultTable = oleDbConnection.GetOleDbSchemaTable((System.Guid)_schemaMapping[i]._schemaRowset, mungedRestrictions);
                        break;
                    }
                }
            }

            if (resultTable == null)
            {
                throw ADP.UnableToBuildCollection(collectionName);
            }

            return resultTable;
        }

        private static void SetIdentifierCase(string columnName, int propertyID, DataRow row, OleDbConnection connection)
        {
            object? property = connection.GetDataSourcePropertyValue(OleDbPropertySetGuid.DataSourceInfo, propertyID);
            IdentifierCase identifierCase = IdentifierCase.Unknown;
            if (property != null)
            {
                int propertyValue = (int)property;
                switch (propertyValue)
                {
                    case ODB.DBPROPVAL_IC_UPPER:
                    case ODB.DBPROPVAL_IC_LOWER:
                    case ODB.DBPROPVAL_IC_MIXED:
                        identifierCase = IdentifierCase.Insensitive;
                        break;

                    case ODB.DBPROPVAL_IC_SENSITIVE:
                        identifierCase = IdentifierCase.Sensitive;
                        break;
                }
            }
            row[columnName] = identifierCase;

        }

    }
}