diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 73c0cb3c9c..f8fc03f915 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -209,9 +209,6 @@ Microsoft\Data\ProviderBase\DbConnectionInternal.cs - - Microsoft\Data\ProviderBase\DbMetaDataFactory.cs - Microsoft\Data\ProviderBase\DbReferenceCollection.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index f8041d0d61..e0974d0d44 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -282,9 +282,6 @@ Microsoft\Data\ProviderBase\DbConnectionInternal.cs - - Microsoft\Data\ProviderBase\DbMetaDataFactory.cs - Microsoft\Data\ProviderBase\DbReferenceCollection.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index dcd9f4bac9..08a6dcc2b8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1129,9 +1129,6 @@ internal static IndexOutOfRangeException InvalidBufferSizeOrIndex(int numBytes, internal static Exception InvalidDataLength(long length) => IndexOutOfRange(StringsHelper.GetString(Strings.SQL_InvalidDataLength, length.ToString(CultureInfo.InvariantCulture))); - internal static bool CompareInsensitiveInvariant(string strvalue, string strconst) - => 0 == CultureInfo.InvariantCulture.CompareInfo.Compare(strvalue, strconst, CompareOptions.IgnoreCase); - internal static void SetCurrentTransaction(Transaction transaction) => Transaction.Current = transaction; internal static Exception NonSeqByteAccess(long badIndex, long currIndex, string method) @@ -1142,9 +1139,6 @@ internal static Exception NonSeqByteAccess(long badIndex, long currIndex, string internal static Exception NegativeParameter(string parameterName) => InvalidOperation(StringsHelper.GetString(Strings.ADP_NegativeParameter, parameterName)); - internal static Exception InvalidXmlMissingColumn(string collectionName, string columnName) - => Argument(StringsHelper.GetString(Strings.MDF_InvalidXmlMissingColumn, collectionName, columnName)); - internal static InvalidOperationException AsyncOperationPending() => InvalidOperation(StringsHelper.GetString(Strings.ADP_PendingAsyncOperation)); #endregion @@ -1231,7 +1225,7 @@ internal static Exception InvalidCommandTimeout(int value, [CallerMemberName] st => Argument(StringsHelper.GetString(Strings.ADP_InvalidCommandTimeout, value.ToString(CultureInfo.InvariantCulture)), property); #endregion -#region DbMetaDataFactory +#region SqlMetaDataFactory internal static Exception DataTableDoesNotExist(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_DataTableDoesNotExist, collectionName)); @@ -1268,8 +1262,6 @@ internal static InvalidOperationException OpenConnectionRequired(string method, internal static Exception OpenReaderExists(Exception e, bool marsOn) => InvalidOperation(StringsHelper.GetString(Strings.ADP_OpenReaderExists, marsOn ? ADP.Command : ADP.Connection), e); - internal static Exception InvalidXml() => Argument(StringsHelper.GetString(Strings.MDF_InvalidXml)); - internal static Exception InvalidXmlInvalidValue(string collectionName, string columnName) => Argument(StringsHelper.GetString(Strings.MDF_InvalidXmlInvalidValue, collectionName, columnName)); @@ -1287,8 +1279,6 @@ internal static Exception UndefinedCollection(string collectionName) internal static Exception AmbiguousCollectionName(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_AmbiguousCollectionName, collectionName)); - internal static Exception MissingRestrictionColumn() => Argument(StringsHelper.GetString(Strings.MDF_MissingRestrictionColumn)); - internal static Exception MissingRestrictionRow() => Argument(StringsHelper.GetString(Strings.MDF_MissingRestrictionRow)); internal static Exception UndefinedPopulationMechanism(string populationMechanism) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index dda18faa70..a7debdb0bd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -860,7 +860,7 @@ protected internal virtual DataTable GetSchema( { Debug.Assert(outerConnection is not null, "outerConnection may not be null."); - DbMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); + SqlMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); Debug.Assert(metaDataFactory is not null, "metaDataFactory may not be null."); return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs deleted file mode 100644 index 4eea91478c..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ /dev/null @@ -1,755 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient; -using System; -using System.Data; -using System.Data.Common; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Xml; - -namespace Microsoft.Data.ProviderBase -{ - internal class DbMetaDataFactory - { - - private DataSet _metaDataCollectionsDataSet; - private string _normalizedServerVersion; - private string _serverVersionString; - // well known column names - private const string CollectionNameKey = "CollectionName"; - private const string PopulationMechanismKey = "PopulationMechanism"; - private const string PopulationStringKey = "PopulationString"; - private const string MaximumVersionKey = "MaximumVersion"; - private const string MinimumVersionKey = "MinimumVersion"; - private const string RestrictionDefaultKey = "RestrictionDefault"; - private const string RestrictionNumberKey = "RestrictionNumber"; - private const string NumberOfRestrictionsKey = "NumberOfRestrictions"; - private const string RestrictionNameKey = "RestrictionName"; - private const string ParameterNameKey = "ParameterName"; - - // population mechanisms - private const string DataTableKey = "DataTable"; - private const string SqlCommandKey = "SQLCommand"; - private const string PrepareCollectionKey = "PrepareCollection"; - - public DbMetaDataFactory(Stream xmlStream, string serverVersion, string normalizedServerVersion) - { - ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); - ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); - ADP.CheckArgumentNull(normalizedServerVersion, nameof(normalizedServerVersion)); - - _serverVersionString = serverVersion; - _normalizedServerVersion = normalizedServerVersion; - - LoadDataSetFromXml(xmlStream); - } - - protected DataSet CollectionDataSet => _metaDataCollectionsDataSet; - - protected string ServerVersion => _serverVersionString; - - protected string ServerVersionNormalized => _normalizedServerVersion; - - protected DataTable CloneAndFilterCollection(string collectionName, string[] hiddenColumnNames) - { - DataTable destinationTable; - DataColumn[] filteredSourceColumns; - DataColumnCollection destinationColumns; - DataRow newRow; - - DataTable sourceTable = _metaDataCollectionsDataSet.Tables[collectionName]; - - if ((sourceTable == null) || (collectionName != sourceTable.TableName)) - { - throw ADP.DataTableDoesNotExist(collectionName); - } - - destinationTable = new DataTable(collectionName) - { - Locale = CultureInfo.InvariantCulture - }; - destinationColumns = destinationTable.Columns; - - filteredSourceColumns = FilterColumns(sourceTable, hiddenColumnNames, destinationColumns); - - foreach (DataRow row in sourceTable.Rows) - { - if (SupportedByCurrentVersion(row)) - { - newRow = destinationTable.NewRow(); - for (int i = 0; i < destinationColumns.Count; i++) - { - newRow[destinationColumns[i]] = row[filteredSourceColumns[i], DataRowVersion.Current]; - } - destinationTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } - } - - return destinationTable; - } - - public void Dispose() => Dispose(true); - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _normalizedServerVersion = null; - _serverVersionString = null; - _metaDataCollectionsDataSet.Dispose(); - } - } - - private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) - { - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; - DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; - - DataTable resultTable = null; - - Debug.Assert(requestedCollectionRow != null); - string sqlCommand = requestedCollectionRow[populationStringColumn, DataRowVersion.Current] as string; - int numberOfRestrictions = (int)requestedCollectionRow[numberOfRestrictionsColumn, DataRowVersion.Current]; - string collectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - - if ((restrictions != null) && (restrictions.Length > numberOfRestrictions)) - { - throw ADP.TooManyRestrictions(collectionName); - } - - DbCommand command = connection.CreateCommand(); - SqlConnection castConnection = connection as SqlConnection; - - command.CommandText = sqlCommand; - command.CommandTimeout = Math.Max(command.CommandTimeout, 180); - command.Transaction = castConnection?.GetOpenTdsConnection()?.CurrentTransaction?.Parent; - - for (int i = 0; i < numberOfRestrictions; i++) - { - - DbParameter restrictionParameter = command.CreateParameter(); - - if ((restrictions != null) && (restrictions.Length > i) && (restrictions[i] != null)) - { - restrictionParameter.Value = restrictions[i]; - } - else - { - // This is where we have to assign null to the value of the parameter. - restrictionParameter.Value = DBNull.Value; - } - - restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); - restrictionParameter.Direction = ParameterDirection.Input; - command.Parameters.Add(restrictionParameter); - } - - DbDataReader reader = null; - try - { - try - { - reader = command.ExecuteReader(); - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - throw ADP.QueryFailed(collectionName, e); - } - - // Build a DataTable from the reader - resultTable = new DataTable(collectionName) - { - Locale = CultureInfo.InvariantCulture - }; - - DataTable schemaTable = reader.GetSchemaTable(); - foreach (DataRow row in schemaTable.Rows) - { - resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); - } - object[] values = new object[resultTable.Columns.Count]; - while (reader.Read()) - { - reader.GetValues(values); - resultTable.Rows.Add(values); - } - } - finally - { - reader?.Dispose(); - } - return resultTable; - } - - private DataColumn[] FilterColumns(DataTable sourceTable, string[] hiddenColumnNames, DataColumnCollection destinationColumns) - { - int columnCount = 0; - foreach (DataColumn sourceColumn in sourceTable.Columns) - { - if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) - { - columnCount++; - } - } - - if (columnCount == 0) - { - throw ADP.NoColumns(); - } - - int currentColumn = 0; - DataColumn[] filteredSourceColumns = new DataColumn[columnCount]; - - foreach (DataColumn sourceColumn in sourceTable.Columns) - { - if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) - { - DataColumn newDestinationColumn = new(sourceColumn.ColumnName, sourceColumn.DataType); - destinationColumns.Add(newDestinationColumn); - filteredSourceColumns[currentColumn] = sourceColumn; - currentColumn++; - } - } - return filteredSourceColumns; - } - - internal DataRow FindMetaDataCollectionRow(string collectionName) - { - bool versionFailure; - bool haveExactMatch; - bool haveMultipleInexactMatches; - string candidateCollectionName; - - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - if (metaDataCollectionsTable == null) - { - throw ADP.InvalidXml(); - } - - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - - if (collectionNameColumn == null || (typeof(string) != collectionNameColumn.DataType)) - { - throw ADP.InvalidXmlMissingColumn(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); - } - - DataRow requestedCollectionRow = null; - string exactCollectionName = null; - - // find the requested collection - versionFailure = false; - haveExactMatch = false; - haveMultipleInexactMatches = false; - - foreach (DataRow row in metaDataCollectionsTable.Rows) - { - - candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; - if (string.IsNullOrEmpty(candidateCollectionName)) - { - throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); - } - - if (ADP.CompareInsensitiveInvariant(candidateCollectionName, collectionName)) - { - if (!SupportedByCurrentVersion(row)) - { - versionFailure = true; - } - else - { - if (collectionName == candidateCollectionName) - { - if (haveExactMatch) - { - throw ADP.CollectionNameIsNotUnique(collectionName); - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - haveExactMatch = true; - } - else if (!haveExactMatch) - { - // have an inexact match - ok only if it is the only one - if (exactCollectionName != null) - { - // can't fail here becasue we may still find an exact match - haveMultipleInexactMatches = true; - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - } - } - } - } - - if (requestedCollectionRow == null) - { - if (!versionFailure) - { - throw ADP.UndefinedCollection(collectionName); - } - else - { - throw ADP.UnsupportedVersion(collectionName); - } - } - - if (!haveExactMatch && haveMultipleInexactMatches) - { - throw ADP.AmbiguousCollectionName(collectionName); - } - - return requestedCollectionRow; - - } - - private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) - { - Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersion)); - Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersionNormalized)); - - dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = _serverVersionString; - dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = _normalizedServerVersion; - } - - - private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) - { - DataColumn collectionName = null; - DataColumn parameterName = null; - DataColumn restrictionName = null; - DataColumn restrictionNumber = null; - - string result = null; - - DataTable restrictionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; - if (restrictionsTable != null) - { - DataColumnCollection restrictionColumns = restrictionsTable.Columns; - if (restrictionColumns != null) - { - collectionName = restrictionColumns[DbMetaDataFactory.CollectionNameKey]; - parameterName = restrictionColumns[ParameterNameKey]; - restrictionName = restrictionColumns[RestrictionNameKey]; - restrictionNumber = restrictionColumns[RestrictionNumberKey]; - } - } - - if ((parameterName == null) || (collectionName == null) || (restrictionName == null) || (restrictionNumber == null)) - { - throw ADP.MissingRestrictionColumn(); - } - - foreach (DataRow restriction in restrictionsTable.Rows) - { - - if (((string)restriction[collectionName] == neededCollectionName) && - ((int)restriction[restrictionNumber] == neededRestrictionNumber) && - (SupportedByCurrentVersion(restriction))) - { - - result = (string)restriction[parameterName]; - break; - } - } - - if (result == null) - { - throw ADP.MissingRestrictionRow(); - } - - return result; - } - - public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) - { - Debug.Assert(_metaDataCollectionsDataSet != null); - - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - - string[] hiddenColumns; - - DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName); - string exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - - if (!ADP.IsEmptyArray(restrictions)) - { - - for (int i = 0; i < restrictions.Length; i++) - { - if ((restrictions[i] != null) && (restrictions[i].Length > 4096)) - { - // use a non-specific error because no new beta 2 error messages are allowed - // TODO: will add a more descriptive error in RTM - throw ADP.NotSupported(); - } - } - } - - string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string; - - DataTable requestedSchema; - switch (populationMechanism) - { - - case DataTableKey: - if (exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections) - { - hiddenColumns = new string[2]; - hiddenColumns[0] = PopulationMechanismKey; - hiddenColumns[1] = PopulationStringKey; - } - else - { - hiddenColumns = null; - } - // none of the datatable collections support restrictions - if (!ADP.IsEmptyArray(restrictions)) - { - throw ADP.TooManyRestrictions(exactCollectionName); - } - - - requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); - break; - - case SqlCommandKey: - requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection); - break; - - case PrepareCollectionKey: - requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection); - break; - - default: - throw ADP.UndefinedPopulationMechanism(populationMechanism); - } - - return requestedSchema; - } - - private bool IncludeThisColumn(DataColumn sourceColumn, string[] hiddenColumnNames) - { - - bool result = true; - string sourceColumnName = sourceColumn.ColumnName; - - switch (sourceColumnName) - { - - case MinimumVersionKey: - case MaximumVersionKey: - result = false; - break; - - default: - if (hiddenColumnNames == null) - { - break; - } - for (int i = 0; i < hiddenColumnNames.Length; i++) - { - if (hiddenColumnNames[i] == sourceColumnName) - { - result = false; - break; - } - } - break; - } - - return result; - } - - private void LoadDataSetFromXml(Stream XmlStream) - { - DataSet metaDataCollectionsDataSet = new DataSet("NewDataSet") - { - Locale = CultureInfo.InvariantCulture - }; - XmlReaderSettings settings = new() - { - XmlResolver = null, - IgnoreComments = true, - IgnoreWhitespace = true - }; - using XmlReader reader = XmlReader.Create(XmlStream, settings); - - // Build up the schema DataSet manually, then load data from XmlStream. The schema of the DataSet is defined at: - // * https://learn.microsoft.com/en-us/sql/connect/ado-net/common-schema-collections - // * https://learn.microsoft.com/en-us/sql/connect/ado-net/sql-server-schema-collections - // Building the schema manually is necessary because DataSet.ReadXml uses XML serialization. This is slow, and it - // increases the binary size of AOT assemblies by approximately 4MB. - - bool readContainer = reader.Read(); - - // Skip past the XML declaration and the outer container element. This XML document is hardcoded; - // these checks will need to be adjusted if its structure changes. - Debug.Assert(readContainer); - Debug.Assert(reader.NodeType == XmlNodeType.XmlDeclaration); - - readContainer = reader.Read(); - Debug.Assert(readContainer); - Debug.Assert(reader.NodeType == XmlNodeType.Element); - Debug.Assert(reader.Name == "MetaData"); - Debug.Assert(reader.Depth == 0); - - // Iterate over each "table element" of the outer container element. - // LoadDataTable will read the child elements of each table element. - while (reader.Read() - && reader.Depth == 1) - { - DataTable dataTable = null; - Action rowFixup = null; - - Debug.Assert(reader.NodeType == XmlNodeType.Element); - - switch (reader.Name) - { - case "MetaDataCollectionsTable": - dataTable = CreateMetaDataCollectionsDataTable(); - break; - case "RestrictionsTable": - dataTable = CreateRestrictionsDataTable(); - break; - case "DataSourceInformationTable": - dataTable = CreateDataSourceInformationDataTable(); - rowFixup = FixUpDataSourceInformationRow; - break; - case "DataTypesTable": - dataTable = CreateDataTypesDataTable(); - break; - case "ReservedWordsTable": - dataTable = CreateReservedWordsDataTable(); - break; - default: - Debug.Fail($"Unexpected table element name: {reader.Name}"); - break; - } - - if (dataTable != null) - { - LoadDataTable(reader, dataTable, rowFixup); - - metaDataCollectionsDataSet.Tables.Add(dataTable); - } - } - - _metaDataCollectionsDataSet = metaDataCollectionsDataSet; - } - - private static void LoadDataTable(XmlReader reader, DataTable table, Action rowFixup) - { - int parentDepth = reader.Depth; - - table.BeginLoadData(); - - // One outer loop per element, each loop reading every property of the row - while (reader.Read() - && reader.Depth == parentDepth + 1) - { - Debug.Assert(reader.NodeType == XmlNodeType.Element); - Debug.Assert(reader.Name == table.TableName); - - int childDepth = reader.Depth; - DataRow row = table.NewRow(); - - // Read every child property. Hardcoded structure - start with the element name, advance to the text, then to the EndElement - while (reader.Read() - && reader.Depth == childDepth + 1) - { - DataColumn column; - bool successfulRead; - - Debug.Assert(reader.NodeType == XmlNodeType.Element); - - column = table.Columns[reader.Name]; - Debug.Assert(column != null); - - successfulRead = reader.Read(); - Debug.Assert(successfulRead); - Debug.Assert(reader.NodeType == XmlNodeType.Text); - - row[column] = reader.Value; - - successfulRead = reader.Read(); - Debug.Assert(successfulRead); - Debug.Assert(reader.NodeType == XmlNodeType.EndElement); - } - - rowFixup?.Invoke(row); - - table.Rows.Add(row); - - Debug.Assert(reader.NodeType == XmlNodeType.EndElement); - } - - table.EndLoadData(); - table.AcceptChanges(); - } - - private static DataTable CreateMetaDataCollectionsDataTable() - => new DataTable(DbMetaDataCollectionNames.MetaDataCollections) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), - new DataColumn(DbMetaDataColumnNames.NumberOfRestrictions, typeof(int)), - new DataColumn(DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int)), - new DataColumn(PopulationMechanismKey, typeof(string)), - new DataColumn(PopulationStringKey, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - - private static DataTable CreateRestrictionsDataTable() - => new DataTable(DbMetaDataCollectionNames.Restrictions) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), - new DataColumn(RestrictionNameKey, typeof(string)), - new DataColumn(ParameterNameKey, typeof(string)), - new DataColumn(RestrictionDefaultKey, typeof(string)), - new DataColumn(RestrictionNumberKey, typeof(int)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - - private static DataTable CreateDataSourceInformationDataTable() - => new DataTable(DbMetaDataCollectionNames.DataSourceInformation) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataSourceProductName, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string)), - new DataColumn(DbMetaDataColumnNames.GroupByBehavior, typeof(GroupByBehavior)), - new DataColumn(DbMetaDataColumnNames.IdentifierPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.IdentifierCase, typeof(IdentifierCase)), - new DataColumn(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string)), - new DataColumn(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int)), - new DataColumn(DbMetaDataColumnNames.ParameterNamePattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(IdentifierCase)), - new DataColumn(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.StringLiteralPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.SupportedJoinOperators, typeof(SupportedJoinOperators)) - } - }; - - private static DataTable CreateDataTypesDataTable() - => new DataTable(DbMetaDataCollectionNames.DataTypes) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.TypeName, typeof(string)), - new DataColumn(DbMetaDataColumnNames.ProviderDbType, typeof(int)), - new DataColumn(DbMetaDataColumnNames.ColumnSize, typeof(long)), - new DataColumn(DbMetaDataColumnNames.CreateFormat, typeof(string)), - new DataColumn(DbMetaDataColumnNames.CreateParameters, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataType, typeof(string)), - new DataColumn(DbMetaDataColumnNames.IsAutoIncrementable, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsBestMatch, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsCaseSensitive, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsFixedLength, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsFixedPrecisionScale, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsLong, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsNullable, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsSearchable, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsSearchableWithLike, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsUnsigned, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.MaximumScale, typeof(short)), - new DataColumn(DbMetaDataColumnNames.MinimumScale, typeof(short)), - new DataColumn(DbMetaDataColumnNames.IsConcurrencyType, typeof(bool)), - new DataColumn(MaximumVersionKey, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(DbMetaDataColumnNames.IsLiteralSupported, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.LiteralPrefix, typeof(string)), - new DataColumn(DbMetaDataColumnNames.LiteralSuffix, typeof(string)) - } - }; - - private static DataTable CreateReservedWordsDataTable() - => new DataTable(DbMetaDataCollectionNames.ReservedWords) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - - protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) - { - throw ADP.NotSupported(); - } - - private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) - { - bool result = true; - DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; - DataColumn versionColumn; - object version; - - // check the minimum version first - versionColumn = tableColumns[MinimumVersionKey]; - if (versionColumn != null) - { - version = requestedCollectionRow[versionColumn]; - if (version != null) - { - if (version != DBNull.Value) - { - if (0 > string.Compare(_normalizedServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) - { - result = false; - } - } - } - } - - // if the minimum version was ok what about the maximum version - if (result) - { - versionColumn = tableColumns[MaximumVersionKey]; - if (versionColumn != null) - { - version = requestedCollectionRow[versionColumn]; - if (version != null) - { - if (version != DBNull.Value) - { - if (0 < string.Compare(_normalizedServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) - { - result = false; - } - } - } - } - } - return result; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 226a5749b4..3a7b01945a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -40,7 +40,6 @@ sealed internal class DbConnectionPoolGroup private int _state; // see PoolGroupState* below private DbConnectionPoolGroupProviderInfo _providerInfo; - private DbMetaDataFactory _metaDataFactory; private static int s_objectTypeCount; // EventSource counter @@ -95,18 +94,7 @@ internal DbConnectionPoolGroupProviderInfo ProviderInfo internal DbConnectionPoolGroupOptions PoolGroupOptions => _poolGroupOptions; - internal DbMetaDataFactory MetaDataFactory - { - get - { - return _metaDataFactory; - } - - set - { - _metaDataFactory = value; - } - } + internal SqlMetaDataFactory MetaDataFactory { get; set; } internal int Clear() { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 6f697d37e8..22eb0a3d06 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -265,28 +265,15 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup( return connectionPoolGroup; } - internal DbMetaDataFactory GetMetaDataFactory( + internal SqlMetaDataFactory GetMetaDataFactory( DbConnectionPoolGroup poolGroup, DbConnectionInternal internalConnection) { Debug.Assert(poolGroup is not null, "connectionPoolGroup may not be null."); - // Get the matadatafactory from the pool entry. If it does not already have one + // Get the metadata factory from the pool entry. If it does not already have one // create one and save it on the pool entry - DbMetaDataFactory metaDataFactory = poolGroup.MetaDataFactory; - - // CONSIDER: serializing this so we don't construct multiple metadata factories - // if two threads happen to hit this at the same time. One will be GC'd - if (metaDataFactory is null) - { - metaDataFactory = CreateMetaDataFactory(internalConnection, out bool allowCache); - if (allowCache) - { - poolGroup.MetaDataFactory = metaDataFactory; - } - } - - return metaDataFactory; + return poolGroup.MetaDataFactory ??= CreateMetaDataFactory(internalConnection); } internal void QueuePoolForRelease(IDbConnectionPool pool, bool clearing) @@ -756,19 +743,14 @@ private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(Sql return poolingOptions; } - private static DbMetaDataFactory CreateMetaDataFactory( - DbConnectionInternal internalConnection, - out bool cacheMetaDataFactory) + private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection) { Debug.Assert(internalConnection is not null, "internalConnection may not be null."); Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml"); Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null."); - cacheMetaDataFactory = true; - return new SqlMetaDataFactory(xmlStream, - internalConnection.ServerVersion, - internalConnection.ServerVersion); + return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion); } private Task CreateReplaceConnectionContinuation( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs index da39c1f375..ca3dec0658 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -6,29 +6,452 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Diagnostics; +using System.Globalization; using System.IO; using System.Text; +using System.Xml; using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; namespace Microsoft.Data.SqlClient { - internal sealed class SqlMetaDataFactory : DbMetaDataFactory + internal sealed class SqlMetaDataFactory : IDisposable { - + // Well-known column names + private const string CollectionNameKey = "CollectionName"; + private const string PopulationMechanismKey = "PopulationMechanism"; + private const string PopulationStringKey = "PopulationString"; + private const string MaximumVersionKey = "MaximumVersion"; + private const string MinimumVersionKey = "MinimumVersion"; + private const string RestrictionDefaultKey = "RestrictionDefault"; + private const string RestrictionNumberKey = "RestrictionNumber"; + private const string NumberOfRestrictionsKey = "NumberOfRestrictions"; + private const string RestrictionNameKey = "RestrictionName"; + private const string ParameterNameKey = "ParameterName"; + + // Well-known server versions private const string ServerVersionNormalized90 = "09.00.0000"; private const string ServerVersionNormalized10 = "10.00.0000"; - private readonly HashSet _assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; - public SqlMetaDataFactory(Stream XMLStream, - string serverVersion, - string serverVersionNormalized) : - base(XMLStream, serverVersion, serverVersionNormalized) - { } + private static readonly HashSet s_assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; + + private readonly DataSet _collectionDataSet; + private readonly string _serverVersion; + + public SqlMetaDataFactory(Stream xmlStream, string serverVersion) + { + ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); + ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); + + _serverVersion = serverVersion; + + _collectionDataSet = LoadDataSetFromXml(xmlStream); + } + + private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) + { + DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; + DataColumn versionColumn; + object version; + + // check the minimum version first + versionColumn = tableColumns[MinimumVersionKey]; + if (versionColumn is not null) + { + version = requestedCollectionRow[versionColumn]; + + if (version is string minVersion + && string.Compare(_serverVersion, minVersion, StringComparison.OrdinalIgnoreCase) < 0) + { + return false; + } + } + + // if the minimum version was ok what about the maximum version + versionColumn = tableColumns[MaximumVersionKey]; + if (versionColumn is not null) + { + version = requestedCollectionRow[versionColumn]; + + if (version is string maxVersion + && string.Compare(_serverVersion, maxVersion, StringComparison.OrdinalIgnoreCase) > 0) + { + return false; + } + } + + return true; + } + + private DataRow FindMetaDataCollectionRow(string collectionName) + { + bool versionFailure = false; + bool haveExactMatch = false; + bool haveMultipleInexactMatches = false; + + DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + Debug.Assert(metaDataCollectionsTable is not null); + + DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; + Debug.Assert(collectionNameColumn is not null && collectionNameColumn.DataType == typeof(string)); + + DataRow requestedCollectionRow = null; + string exactCollectionName = null; + + // find the requested collection + foreach (DataRow row in metaDataCollectionsTable.Rows) + { + string candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; + + if (string.IsNullOrEmpty(candidateCollectionName)) + { + throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); + } + + if (string.Equals(candidateCollectionName, collectionName, StringComparison.InvariantCultureIgnoreCase)) + { + if (!SupportedByCurrentVersion(row)) + { + versionFailure = true; + } + else if (collectionName == candidateCollectionName) + { + if (haveExactMatch) + { + throw ADP.CollectionNameIsNotUnique(collectionName); + } + requestedCollectionRow = row; + exactCollectionName = candidateCollectionName; + haveExactMatch = true; + } + else if (!haveExactMatch) + { + // have an inexact match - ok only if it is the only one + if (exactCollectionName is not null) + { + // can't fail here because we may still find an exact match + haveMultipleInexactMatches = true; + } + requestedCollectionRow = row; + exactCollectionName = candidateCollectionName; + } + } + } + + if (requestedCollectionRow is null) + { + if (!versionFailure) + { + throw ADP.UndefinedCollection(collectionName); + } + else + { + throw ADP.UnsupportedVersion(collectionName); + } + } + + if (!haveExactMatch && haveMultipleInexactMatches) + { + throw ADP.AmbiguousCollectionName(collectionName); + } + + return requestedCollectionRow; + + } + + public DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) + { + const string DataTableKey = "DataTable"; + const string SqlCommandKey = "SQLCommand"; + const string PrepareCollectionKey = "PrepareCollection"; + + Debug.Assert(_collectionDataSet is not null); + + DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; + DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; + DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName); + string exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; + + if (!ADP.IsEmptyArray(restrictions)) + { + + for (int i = 0; i < restrictions.Length; i++) + { + if ((restrictions[i] is not null) && (restrictions[i].Length > 4096)) + { + // use a non-specific error because no new beta 2 error messages are allowed + // TODO: will add a more descriptive error in RTM + throw ADP.NotSupported(); + } + } + } + + string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string; + + DataTable requestedSchema; + switch (populationMechanism) + { + case DataTableKey: + ReadOnlySpan hiddenColumns = exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections + ? [PopulationMechanismKey, PopulationStringKey] + : []; + + // none of the datatable collections support restrictions + if (!ADP.IsEmptyArray(restrictions)) + { + throw ADP.TooManyRestrictions(exactCollectionName); + } + + requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); + break; + + case SqlCommandKey: + requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection); + break; + + case PrepareCollectionKey: + requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection); + break; + + default: + throw ADP.UndefinedPopulationMechanism(populationMechanism); + } + + return requestedSchema; + } + + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) + { + if (disposing) + { + _collectionDataSet.Dispose(); + } + } + + #region GetSchema Helpers: DataTable Population Method + private static bool IncludeThisColumn(DataColumn sourceColumn, ReadOnlySpan hiddenColumnNames) + { + string sourceColumnName = sourceColumn.ColumnName; + + return sourceColumnName switch + { + MinimumVersionKey or MaximumVersionKey => false, +#if NET + _ => !hiddenColumnNames.Contains(sourceColumnName), +#else + _ => hiddenColumnNames.IndexOf(sourceColumnName) == -1, +#endif + }; + } + + private static DataColumn[] FilterColumns(DataTable sourceTable, ReadOnlySpan hiddenColumnNames, DataColumnCollection destinationColumns) + { + int columnCount = 0; + foreach (DataColumn sourceColumn in sourceTable.Columns) + { + if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) + { + columnCount++; + } + } + + if (columnCount == 0) + { + throw ADP.NoColumns(); + } + + int currentColumn = 0; + DataColumn[] filteredSourceColumns = new DataColumn[columnCount]; + + foreach (DataColumn sourceColumn in sourceTable.Columns) + { + if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) + { + DataColumn newDestinationColumn = new(sourceColumn.ColumnName, sourceColumn.DataType); + destinationColumns.Add(newDestinationColumn); + filteredSourceColumns[currentColumn] = sourceColumn; + currentColumn++; + } + } + return filteredSourceColumns; + } + + private DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) + { + DataTable destinationTable; + DataColumn[] filteredSourceColumns; + DataColumnCollection destinationColumns; + DataRow newRow; + + DataTable sourceTable = _collectionDataSet.Tables[collectionName]; + + if (sourceTable?.TableName != collectionName) + { + throw ADP.DataTableDoesNotExist(collectionName); + } + + destinationTable = new DataTable(collectionName) + { + Locale = CultureInfo.InvariantCulture + }; + destinationColumns = destinationTable.Columns; + + filteredSourceColumns = FilterColumns(sourceTable, hiddenColumnNames, destinationColumns); + + foreach (DataRow row in sourceTable.Rows) + { + if (SupportedByCurrentVersion(row)) + { + newRow = destinationTable.NewRow(); + for (int i = 0; i < destinationColumns.Count; i++) + { + newRow[destinationColumns[i]] = row[filteredSourceColumns[i], DataRowVersion.Current]; + } + destinationTable.Rows.Add(newRow); + newRow.AcceptChanges(); + } + } + + return destinationTable; + } + #endregion + + #region GetSchema Helpers: ExecuteCommand Population Method + private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) + { + DataTable restrictionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; + + Debug.Assert(restrictionsTable is not null); + + DataColumnCollection restrictionColumns = restrictionsTable.Columns; + DataColumn collectionName = restrictionColumns[CollectionNameKey]; + DataColumn parameterName = restrictionColumns[ParameterNameKey]; + DataColumn restrictionName = restrictionColumns[RestrictionNameKey]; + DataColumn restrictionNumber = restrictionColumns[RestrictionNumberKey]; + + Debug.Assert(parameterName is not null); + Debug.Assert(collectionName is not null); + Debug.Assert(restrictionName is not null); + Debug.Assert(restrictionNumber is not null); + + string result = null; + + foreach (DataRow restriction in restrictionsTable.Rows) + { + + if (((string)restriction[collectionName] == neededCollectionName) && + ((int)restriction[restrictionNumber] == neededRestrictionNumber) && + (SupportedByCurrentVersion(restriction))) + { + + result = (string)restriction[parameterName]; + break; + } + } + + if (result is null) + { + throw ADP.MissingRestrictionRow(); + } + + return result; + } + + private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) + { + Debug.Assert(requestedCollectionRow is not null); + + DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; + DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; + DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; + + DataTable resultTable = null; + string sqlCommand = requestedCollectionRow[populationStringColumn, DataRowVersion.Current] as string; + int numberOfRestrictions = (int)requestedCollectionRow[numberOfRestrictionsColumn, DataRowVersion.Current]; + string collectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; + + if ((restrictions is not null) && (restrictions.Length > numberOfRestrictions)) + { + throw ADP.TooManyRestrictions(collectionName); + } + + SqlConnection castConnection = connection as SqlConnection; + using SqlCommand command = castConnection.CreateCommand(); + + command.CommandText = sqlCommand; + command.CommandTimeout = Math.Max(command.CommandTimeout, 180); + command.Transaction = castConnection?.GetOpenTdsConnection()?.CurrentTransaction?.Parent; + + for (int i = 0; i < numberOfRestrictions; i++) + { + SqlParameter restrictionParameter = command.CreateParameter(); + + if ((restrictions is not null) && (i < restrictions.Length) && (restrictions[i] is not null)) + { + restrictionParameter.Value = restrictions[i]; + } + else + { + // This is where we have to assign null to the value of the parameter. + restrictionParameter.Value = DBNull.Value; + } + + restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); + restrictionParameter.Direction = ParameterDirection.Input; + command.Parameters.Add(restrictionParameter); + } + + SqlDataReader reader = null; + try + { + try + { + reader = command.ExecuteReader(); + } + catch (Exception e) + { + if (!ADP.IsCatchableExceptionType(e)) + { + throw; + } + throw ADP.QueryFailed(collectionName, e); + } + + // Build a DataTable from the reader + resultTable = new DataTable(collectionName) + { + Locale = CultureInfo.InvariantCulture + }; + + System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); + foreach (DbColumn col in colSchema) + { + resultTable.Columns.Add(col.ColumnName, col.DataType); + } + object[] values = new object[resultTable.Columns.Count]; + while (reader.Read()) + { + reader.GetValues(values); + resultTable.Rows.Add(values); + } + } + finally + { + reader?.Dispose(); + } + return resultTable; + } + #endregion - private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion) + #region GetSchema Helpers: PrepareCollection Population Method + private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { - const string sqlCommand = + const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; + const string ListUdtSqlCommand = "select " + "assemblies.name, " + "types.assembly_class, " + @@ -43,18 +466,30 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con "max_length " + "from sys.assemblies as assemblies join sys.assembly_types as types " + "on assemblies.assembly_id = types.assembly_id "; + const int AssemblyNameSqlIndex = 0; + const int AssemblyClassSqlIndex = 1; + const int VersionMajorSqlIndex = 2; + const int VersionMinorSqlIndex = 3; + const int VersionBuildSqlIndex = 4; + const int VersionRevisionSqlIndex = 5; + const int CultureInfoSqlIndex = 6; + const int PublicKeySqlIndex = 7; + const int IsNullableSqlIndex = 8; + const int IsFixedLengthSqlIndex = 9; + const int ColumnSizeSqlIndex = 10; // pre 9.0/2005 servers do not have UDTs - if (0 > string.Compare(ServerVersion, ServerVersionNormalized90, StringComparison.OrdinalIgnoreCase)) + if (0 > string.Compare(serverVersion, ServerVersionNormalized90, StringComparison.OrdinalIgnoreCase)) { return; } - SqlCommand engineEditionCommand = connection.CreateCommand(); - engineEditionCommand.CommandText = "SELECT SERVERPROPERTY('EngineEdition');"; - var engineEdition = (int)engineEditionCommand.ExecuteScalar(); + using SqlCommand command = connection.CreateCommand(); + + command.CommandText = GetEngineEditionSqlCommand; + int engineEdition = (int)command.ExecuteScalar(); - if (_assemblyPropertyUnsupportedEngines.Contains(engineEdition)) + if (s_assemblyPropertyUnsupportedEngines.Contains(engineEdition)) { // Azure SQL Edge (9) throws an exception when querying sys.assemblies // Azure Synapse Analytics (6) and Azure Synapse serverless SQL pool (11) @@ -62,10 +497,7 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con return; } - // Execute the SELECT statement - SqlCommand command = connection.CreateCommand(); - command.CommandText = sqlCommand; - DataColumn providerDbtype = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; + DataColumn providerDbType = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; DataColumn columnSize = dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize]; DataColumn isFixedLength = dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedLength]; DataColumn isSearchable = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable]; @@ -73,206 +505,161 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con DataColumn typeName = dataTypesTable.Columns[DbMetaDataColumnNames.TypeName]; DataColumn isNullable = dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable]; - if ((providerDbtype == null) || - (columnSize == null) || - (isFixedLength == null) || - (isSearchable == null) || - (isLiteralSupported == null) || - (typeName == null) || - (isNullable == null)) - { - throw ADP.InvalidXml(); - } + Debug.Assert(providerDbType is not null, "providerDbType column not found"); + Debug.Assert(columnSize is not null, "columnSize column not found"); + Debug.Assert(isFixedLength is not null, "isFixedLength column not found"); + Debug.Assert(isSearchable is not null, "isSearchable column not found"); + Debug.Assert(isLiteralSupported is not null, "isLiteralSupported column not found"); + Debug.Assert(typeName is not null, "typeName column not found"); + Debug.Assert(isNullable is not null, "isNullable column not found"); - const int columnSizeIndex = 10; - const int isFixedLengthIndex = 9; - const int isNullableIndex = 8; - const int assemblyNameIndex = 0; - const int assemblyClassIndex = 1; - const int versionMajorIndex = 2; - const int versionMinorIndex = 3; - const int versionBuildIndex = 4; - const int versionRevisionIndex = 5; - const int cultureInfoIndex = 6; - const int publicKeyIndex = 7; + // Execute the SELECT statement + command.CommandText = ListUdtSqlCommand; + using SqlDataReader reader = command.ExecuteReader(); + object[] values = new object[11]; - using (IDataReader reader = command.ExecuteReader()) + while (reader.Read()) { + reader.GetValues(values); + DataRow newRow = dataTypesTable.NewRow(); - object[] values = new object[11]; - while (reader.Read()) - { + newRow[providerDbType] = SqlDbType.Udt; + newRow[columnSize] = values[ColumnSizeSqlIndex]; - reader.GetValues(values); - DataRow newRow = dataTypesTable.NewRow(); + if (values[IsFixedLengthSqlIndex] != DBNull.Value) + { + newRow[isFixedLength] = values[IsFixedLengthSqlIndex]; + } - newRow[providerDbtype] = SqlDbType.Udt; + newRow[isSearchable] = true; + newRow[isLiteralSupported] = false; + if (values[IsNullableSqlIndex] != DBNull.Value) + { + newRow[isNullable] = values[IsNullableSqlIndex]; + } - if (values[columnSizeIndex] != DBNull.Value) - { - newRow[columnSize] = values[columnSizeIndex]; - } + if ((values[AssemblyClassSqlIndex] != DBNull.Value) && + (values[VersionMajorSqlIndex] != DBNull.Value) && + (values[VersionMinorSqlIndex] != DBNull.Value) && + (values[VersionBuildSqlIndex] != DBNull.Value) && + (values[VersionRevisionSqlIndex] != DBNull.Value)) + { - if (values[isFixedLengthIndex] != DBNull.Value) - { - newRow[isFixedLength] = values[isFixedLengthIndex]; - } + StringBuilder nameString = new(); + nameString.Append($"{values[AssemblyClassSqlIndex]}, {values[AssemblyNameSqlIndex]}" + + $", Version={values[VersionMajorSqlIndex]}.{values[VersionMinorSqlIndex]}.{values[VersionBuildSqlIndex]}.{values[VersionRevisionSqlIndex]}"); - newRow[isSearchable] = true; - newRow[isLiteralSupported] = false; - if (values[isNullableIndex] != DBNull.Value) + if (values[CultureInfoSqlIndex] != DBNull.Value) { - newRow[isNullable] = values[isNullableIndex]; + nameString.Append($", Culture={values[CultureInfoSqlIndex]}"); } - if ((values[assemblyNameIndex] != DBNull.Value) && - (values[assemblyClassIndex] != DBNull.Value) && - (values[versionMajorIndex] != DBNull.Value) && - (values[versionMinorIndex] != DBNull.Value) && - (values[versionBuildIndex] != DBNull.Value) && - (values[versionRevisionIndex] != DBNull.Value)) + if (values[PublicKeySqlIndex] != DBNull.Value) { - StringBuilder nameString = new(); - nameString.Append(values[assemblyClassIndex].ToString()); - nameString.Append(", "); - nameString.Append(values[assemblyNameIndex].ToString()); - nameString.Append(", Version="); - - nameString.Append(values[versionMajorIndex].ToString()); - nameString.Append("."); - nameString.Append(values[versionMinorIndex].ToString()); - nameString.Append("."); - nameString.Append(values[versionBuildIndex].ToString()); - nameString.Append("."); - nameString.Append(values[versionRevisionIndex].ToString()); - - if (values[cultureInfoIndex] != DBNull.Value) - { - nameString.Append(", Culture="); - nameString.Append(values[cultureInfoIndex].ToString()); - } + nameString.Append(", PublicKeyToken="); - if (values[publicKeyIndex] != DBNull.Value) + byte[] byteArrayValue = (byte[])values[PublicKeySqlIndex]; +#if NET9_0_OR_GREATER + nameString.Append(Convert.ToHexStringLower(byteArrayValue)); +#elif NET8_0 + nameString.Append(Convert.ToHexString(byteArrayValue).ToLowerInvariant()); +#else + foreach (byte b in byteArrayValue) { - - nameString.Append(", PublicKeyToken="); - - StringBuilder resultString = new(); - byte[] byteArrayValue = (byte[])values[publicKeyIndex]; - foreach (byte b in byteArrayValue) - { - resultString.Append(string.Format("{0,-2:x2}", b)); - } - nameString.Append(resultString.ToString()); + nameString.Append(string.Format("{0,-2:x2}", b)); } +#endif + } - newRow[typeName] = nameString.ToString(); - dataTypesTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } // if assembly name - - }//end while - } // end using + newRow[typeName] = nameString.ToString(); + dataTypesTable.Rows.Add(newRow); + newRow.AcceptChanges(); + } // if assembly name + }//end while } - private void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion) + private static void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { - - const string sqlCommand = + const string ListTvpsSqlCommand = "select " + "name, " + "is_nullable, " + "max_length " + "from sys.types " + "where is_table_type = 1"; + const int TypeNameSqlIndex = 0; + const int IsNullableSqlIndex = 1; + const int ColumnSizeSqlIndex = 2; // TODO: update this check once the server upgrades major version number!!! // pre 9.0/2005 servers do not have Table types - if (0 > string.Compare(ServerVersion, ServerVersionNormalized10, StringComparison.OrdinalIgnoreCase)) + if (0 > string.Compare(serverVersion, ServerVersionNormalized10, StringComparison.OrdinalIgnoreCase)) { return; } // Execute the SELECT statement - SqlCommand command = connection.CreateCommand(); - command.CommandText = sqlCommand; - DataColumn providerDbtype = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; + using SqlCommand command = connection.CreateCommand(); + command.CommandText = ListTvpsSqlCommand; + + DataColumn providerDbType = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; DataColumn columnSize = dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize]; DataColumn isSearchable = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable]; DataColumn isLiteralSupported = dataTypesTable.Columns[DbMetaDataColumnNames.IsLiteralSupported]; DataColumn typeName = dataTypesTable.Columns[DbMetaDataColumnNames.TypeName]; DataColumn isNullable = dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable]; - if ((providerDbtype == null) || - (columnSize == null) || - (isSearchable == null) || - (isLiteralSupported == null) || - (typeName == null) || - (isNullable == null)) - { - throw ADP.InvalidXml(); - } + Debug.Assert(providerDbType is not null, "providerDbType column not found"); + Debug.Assert(columnSize is not null, "columnSize column not found"); + Debug.Assert(isSearchable is not null, "isSearchable column not found"); + Debug.Assert(isLiteralSupported is not null, "isLiteralSupported column not found"); + Debug.Assert(typeName is not null, "typeName column not found"); + Debug.Assert(isNullable is not null, "isNullable column not found"); - const int columnSizeIndex = 2; - const int isNullableIndex = 1; - const int typeNameIndex = 0; + using SqlDataReader reader = command.ExecuteReader(); - using (IDataReader reader = command.ExecuteReader()) + object[] values = new object[11]; + while (reader.Read()) { - object[] values = new object[11]; - while (reader.Read()) - { + reader.GetValues(values); + DataRow newRow = dataTypesTable.NewRow(); - reader.GetValues(values); - DataRow newRow = dataTypesTable.NewRow(); - - newRow[providerDbtype] = SqlDbType.Structured; + newRow[providerDbType] = SqlDbType.Structured; + newRow[columnSize] = values[ColumnSizeSqlIndex]; + newRow[isSearchable] = false; + newRow[isLiteralSupported] = false; - if (values[columnSizeIndex] != DBNull.Value) - { - newRow[columnSize] = values[columnSizeIndex]; - } + if (values[IsNullableSqlIndex] != DBNull.Value) + { + newRow[isNullable] = values[IsNullableSqlIndex]; + } - newRow[isSearchable] = false; - newRow[isLiteralSupported] = false; - if (values[isNullableIndex] != DBNull.Value) - { - newRow[isNullable] = values[isNullableIndex]; - } + newRow[typeName] = values[TypeNameSqlIndex]; - if (values[typeNameIndex] != DBNull.Value) - { - newRow[typeName] = values[typeNameIndex]; - dataTypesTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } // if type name - }//end while - } // end using + dataTypesTable.Rows.Add(newRow); + newRow.AcceptChanges(); + }//end while } private DataTable GetDataTypesTable(SqlConnection 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); - } + Debug.Assert(_collectionDataSet.Tables.Contains(DbMetaDataCollectionNames.DataTypes), "DataTypes collection not found in the DataSet"); // copy the table filtering out any rows that don't apply to tho current version of the provider - dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null); + DataTable dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, []); - addUDTsToDataTypesTable(dataTypesTable, connection, ServerVersionNormalized); - AddTVPsToDataTypesTable(dataTypesTable, connection, ServerVersionNormalized); + AddUDTsToDataTypesTable(dataTypesTable, connection, _serverVersion); + AddTVPsToDataTypesTable(dataTypesTable, connection, _serverVersion); dataTypesTable.AcceptChanges(); return dataTypesTable; } - protected override DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) + private DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) { SqlConnection sqlConnection = (SqlConnection)connection; DataTable resultTable = null; @@ -292,10 +679,243 @@ protected override DataTable PrepareCollection(string collectionName, string[] r } return resultTable; + } + #endregion + + #region Create MetaDataCollections DataSet from XML + private DataSet LoadDataSetFromXml(Stream XmlStream) + { + DataSet metaDataCollectionsDataSet = new("NewDataSet") + { + Locale = CultureInfo.InvariantCulture + }; + XmlReaderSettings settings = new() + { + XmlResolver = null, + IgnoreComments = true, + IgnoreWhitespace = true + }; + using XmlReader reader = XmlReader.Create(XmlStream, settings); + + // Build up the schema DataSet manually, then load data from XmlStream. The schema of the DataSet is defined at: + // * https://learn.microsoft.com/en-us/sql/connect/ado-net/common-schema-collections + // * https://learn.microsoft.com/en-us/sql/connect/ado-net/sql-server-schema-collections + // Building the schema manually is necessary because DataSet.ReadXml uses XML serialization. This is slow, and it + // increases the binary size of AOT assemblies by approximately 4MB. + + bool readContainer = reader.Read(); + + // Skip past the XML declaration and the outer container element. This XML document is hardcoded; + // these checks will need to be adjusted if its structure changes. + Debug.Assert(readContainer); + Debug.Assert(reader.NodeType == XmlNodeType.XmlDeclaration); + + readContainer = reader.Read(); + Debug.Assert(readContainer); + Debug.Assert(reader.NodeType == XmlNodeType.Element); + Debug.Assert(reader.Name == "MetaData"); + Debug.Assert(reader.Depth == 0); + + // Iterate over each "table element" of the outer container element. + // LoadDataTable will read the child elements of each table element. + while (reader.Read() + && reader.Depth == 1) + { + DataTable dataTable = null; + Action rowFixup = null; + + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + switch (reader.Name) + { + case "MetaDataCollectionsTable": + dataTable = CreateMetaDataCollectionsDataTable(); + break; + case "RestrictionsTable": + dataTable = CreateRestrictionsDataTable(); + break; + case "DataSourceInformationTable": + dataTable = CreateDataSourceInformationDataTable(); + rowFixup = FixUpDataSourceInformationRow; + break; + case "DataTypesTable": + dataTable = CreateDataTypesDataTable(); + break; + case "ReservedWordsTable": + dataTable = CreateReservedWordsDataTable(); + break; + default: + Debug.Fail($"Unexpected table element name: {reader.Name}"); + break; + } + + if (dataTable is not null) + { + LoadDataTable(reader, dataTable, rowFixup); + + metaDataCollectionsDataSet.Tables.Add(dataTable); + } + } + + return metaDataCollectionsDataSet; + } + + private static void LoadDataTable(XmlReader reader, DataTable table, Action rowFixup) + { + int parentDepth = reader.Depth; + + table.BeginLoadData(); + // One outer loop per element, each loop reading every property of the row + while (reader.Read() + && reader.Depth == parentDepth + 1) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + Debug.Assert(reader.Name == table.TableName); + + int childDepth = reader.Depth; + DataRow row = table.NewRow(); + + // Read every child property. Hardcoded structure - start with the element name, advance to the text, then to the EndElement + while (reader.Read() + && reader.Depth == childDepth + 1) + { + DataColumn column; + bool successfulRead; + + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + column = table.Columns[reader.Name]; + Debug.Assert(column is not null); + + successfulRead = reader.Read(); + Debug.Assert(successfulRead); + Debug.Assert(reader.NodeType == XmlNodeType.Text); + + row[column] = reader.Value; + + successfulRead = reader.Read(); + Debug.Assert(successfulRead); + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + } + + rowFixup?.Invoke(row); + + table.Rows.Add(row); + + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + } + + table.EndLoadData(); + table.AcceptChanges(); + } + + private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) + { + Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersion)); + Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersionNormalized)); + + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = _serverVersion; + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = _serverVersion; } + private static DataTable CreateMetaDataCollectionsDataTable() + => new(DbMetaDataCollectionNames.MetaDataCollections) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.NumberOfRestrictions, typeof(int)), + new DataColumn(DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int)), + new DataColumn(PopulationMechanismKey, typeof(string)), + new DataColumn(PopulationStringKey, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + + private static DataTable CreateRestrictionsDataTable() + => new(DbMetaDataCollectionNames.Restrictions) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(RestrictionNameKey, typeof(string)), + new DataColumn(ParameterNameKey, typeof(string)), + new DataColumn(RestrictionDefaultKey, typeof(string)), + new DataColumn(RestrictionNumberKey, typeof(int)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + + private static DataTable CreateDataSourceInformationDataTable() + => new(DbMetaDataCollectionNames.DataSourceInformation) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string)), + new DataColumn(DbMetaDataColumnNames.GroupByBehavior, typeof(GroupByBehavior)), + new DataColumn(DbMetaDataColumnNames.IdentifierPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IdentifierCase, typeof(IdentifierCase)), + new DataColumn(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int)), + new DataColumn(DbMetaDataColumnNames.ParameterNamePattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(IdentifierCase)), + new DataColumn(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.StringLiteralPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.SupportedJoinOperators, typeof(SupportedJoinOperators)) + } + }; + private static DataTable CreateDataTypesDataTable() + => new(DbMetaDataCollectionNames.DataTypes) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.TypeName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ProviderDbType, typeof(int)), + new DataColumn(DbMetaDataColumnNames.ColumnSize, typeof(long)), + new DataColumn(DbMetaDataColumnNames.CreateFormat, typeof(string)), + new DataColumn(DbMetaDataColumnNames.CreateParameters, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataType, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IsAutoIncrementable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsBestMatch, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsCaseSensitive, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsFixedLength, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsFixedPrecisionScale, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsLong, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsNullable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsSearchable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsSearchableWithLike, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsUnsigned, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.MaximumScale, typeof(short)), + new DataColumn(DbMetaDataColumnNames.MinimumScale, typeof(short)), + new DataColumn(DbMetaDataColumnNames.IsConcurrencyType, typeof(bool)), + new DataColumn(MaximumVersionKey, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IsLiteralSupported, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.LiteralPrefix, typeof(string)), + new DataColumn(DbMetaDataColumnNames.LiteralSuffix, typeof(string)) + } + }; + private static DataTable CreateReservedWordsDataTable() + => new(DbMetaDataCollectionNames.ReservedWords) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + #endregion } }