From 961958f9c81e23008e420b8747e349d5d0e81b69 Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Mon, 15 Jun 2026 11:29:36 +0300 Subject: [PATCH 1/8] Serving database schema metadata from internal representation, instead of Microsoft.Data.SqlClient.SqlMetaData.xml --- .../src/Microsoft.Data.SqlClient.csproj | 8 +- .../src/Microsoft/Data/Common/AdapterUtil.cs | 16 +- .../Data/SqlClient/SqlConnectionFactory.cs | 13 +- ...qlMetaDataFactory.DataSourceInformation.cs | 121 ++ .../SqlClient/SqlMetaDataFactory.DataTypes.cs | 589 +++------ .../SqlMetaDataFactory.MetaDataCollections.cs | 67 + .../SqlMetaDataFactory.ReservedWords.cs | 49 + .../SqlMetaDataFactory.Restrictions.cs | 59 + .../SqlMetaDataFactory.SqlCommand.cs | 138 ++ .../Data/SqlClient/SqlMetaDataFactory.cs | 1160 +++++------------ .../src/Resources/Strings.Designer.cs | 47 +- .../src/Resources/Strings.cs.resx | 71 +- .../src/Resources/Strings.de.resx | 71 +- .../src/Resources/Strings.es.resx | 71 +- .../src/Resources/Strings.fr.resx | 71 +- .../src/Resources/Strings.it.resx | 71 +- .../src/Resources/Strings.ja.resx | 71 +- .../src/Resources/Strings.ko.resx | 71 +- .../src/Resources/Strings.pl.resx | 71 +- .../src/Resources/Strings.pt-BR.resx | 71 +- .../src/Resources/Strings.resx | 71 +- .../src/Resources/Strings.ru.resx | 71 +- .../src/Resources/Strings.tr.resx | 71 +- .../src/Resources/Strings.zh-Hans.resx | 71 +- .../src/Resources/Strings.zh-Hant.resx | 71 +- 25 files changed, 1367 insertions(+), 1894 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index e9b3ad2741..4d5eff5ac7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -1,4 +1,4 @@ - + Microsoft.Data.SqlClient @@ -230,12 +230,6 @@ - - - Resources\Microsoft.Data.SqlCLient.SqlMetaData.xml - Microsoft.Data.SqlClient.SqlMetaData.xml - - Microsoft.Data.SqlClient.Resources.Strings.resources 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 c0e170b3f8..fc15663d29 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1245,6 +1245,7 @@ internal static Exception InvalidCommandTimeout(int value, [CallerMemberName] st #endregion #region SqlMetaDataFactory + // TODO: Rephrase error message internal static Exception DataTableDoesNotExist(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_DataTableDoesNotExist, collectionName)); @@ -1268,8 +1269,6 @@ internal static ArgumentOutOfRangeException InvalidUpdateRowSource(UpdateRowSour internal static Exception QueryFailed(string collectionName, Exception e) => InvalidOperation(StringsHelper.GetString(Strings.MDF_QueryFailed, collectionName), e); - internal static Exception NoColumns() => Argument(StringsHelper.GetString(Strings.MDF_NoColumns)); - internal static InvalidOperationException ConnectionRequired(string method) => InvalidOperation(StringsHelper.GetString(Strings.ADP_ConnectionRequired, method)); @@ -1281,14 +1280,9 @@ 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 InvalidXmlInvalidValue(string collectionName, string columnName) - => Argument(StringsHelper.GetString(Strings.MDF_InvalidXmlInvalidValue, collectionName, columnName)); - internal static Exception CollectionNameIsNotUnique(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_CollectionNameISNotUnique, collectionName)); - internal static Exception UnableToBuildCollection(string collectionName) - => Argument(StringsHelper.GetString(Strings.MDF_UnableToBuildCollection, collectionName)); internal static Exception UndefinedCollection(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_UndefinedCollection, collectionName)); @@ -1298,14 +1292,6 @@ internal static Exception UndefinedCollection(string collectionName) internal static Exception AmbiguousCollectionName(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_AmbiguousCollectionName, collectionName)); - internal static Exception MissingRestrictionRow() => Argument(StringsHelper.GetString(Strings.MDF_MissingRestrictionRow)); - - internal static Exception UndefinedPopulationMechanism(string populationMechanism) -#if NETFRAMEWORK - => Argument(StringsHelper.GetString(Strings.MDF_UndefinedPopulationMechanism, populationMechanism)); -#else - => throw new NotImplementedException(); -#endif #endregion #region DbConnectionPool and related 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 4cdee8bdc2..db175f7f58 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -7,12 +7,10 @@ using System.Data; using System.Data.Common; using System.Diagnostics; -using System.IO; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; -using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.ConnectionPool; @@ -749,10 +747,7 @@ private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal int { 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."); - - return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion); + return new SqlMetaDataFactory(internalConnection.ServerVersion); } private Task CreateReplaceConnectionContinuation( @@ -950,7 +945,7 @@ private void TryGetConnectionCompletedContinuation(Task ta } } - #if NET +#if NET private void Unload(object sender, EventArgs e) { try @@ -973,9 +968,9 @@ private void SubscribeToAssemblyLoadContextUnload() AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlConnectionFactoryAssemblyLoadContext_Unloading; } - #endif +#endif - #endregion +#endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs new file mode 100644 index 0000000000..f7d67f134f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs @@ -0,0 +1,121 @@ +// 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 System.Data; +using System.Data.Common; +using System.Threading.Tasks; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class SqlMetaDataFactory + { + private sealed class DataSourceInformationCollection : MetaDataCollectionBase + { + private readonly string _compositeIdentifierSeparatorPattern; + private readonly string _dataSourceProductName; + private readonly GroupByBehavior _groupByBehavior; + private readonly string _identifierPattern; + private readonly IdentifierCase _identifierCase; + private readonly bool _orderByColumnsInSelect; + private readonly string _parameterMarkerFormat; + private readonly string _parameterMarkerPattern; + private readonly int _parameterNameMaxLength; + private readonly string _parameterNamePattern; + private readonly string _quotedIdentifierPattern; + private readonly IdentifierCase _quotedIdentifierCase; + private readonly string _statementSeparatorPattern; + private readonly string _stringLiteralPattern; + private readonly SupportedJoinOperators _supportedJoinOperators; + + internal DataSourceInformationCollection(string CompositeIdentifierSeparatorPattern, + string DataSourceProductName, + GroupByBehavior GroupByBehavior, + string IdentifierPattern, + IdentifierCase IdentifierCase, + bool OrderByColumnsInSelect, + string ParameterMarkerFormat, + string ParameterMarkerPattern, + int ParameterNameMaxLength, + string ParameterNamePattern, + string QuotedIdentifierPattern, + IdentifierCase QuotedIdentifierCase, + string StatementSeparatorPattern, + string StringLiteralPattern, + SupportedJoinOperators SupportedJoinOperators) + : base(DbMetaDataCollectionNames.DataSourceInformation, 0, 0) + { + this._compositeIdentifierSeparatorPattern = CompositeIdentifierSeparatorPattern; + this._dataSourceProductName = DataSourceProductName; + this._groupByBehavior = GroupByBehavior; + this._identifierPattern = IdentifierPattern; + this._identifierCase = IdentifierCase; + this._orderByColumnsInSelect = OrderByColumnsInSelect; + this._parameterMarkerFormat = ParameterMarkerFormat; + this._parameterMarkerPattern = ParameterMarkerPattern; + this._parameterNameMaxLength = ParameterNameMaxLength; + this._parameterNamePattern = ParameterNamePattern; + this._quotedIdentifierPattern = QuotedIdentifierPattern; + this._quotedIdentifierCase = QuotedIdentifierCase; + this._statementSeparatorPattern = StatementSeparatorPattern; + this._stringLiteralPattern = StringLiteralPattern; + this._supportedJoinOperators = SupportedJoinOperators; + } + + public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + { + if (!ADP.IsEmptyArray(context.RestrictionValues)) + { + throw ADP.TooManyRestrictions(CollectionName); + } + + DataTable table = accumulator ?? 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)) + } + }; + + DataRow row = table.NewRow(); + table.Rows.Add([ + _compositeIdentifierSeparatorPattern, + _dataSourceProductName, + context.ServerVersion, + context.ServerVersion, + _groupByBehavior, + _identifierPattern, + _identifierCase, + _orderByColumnsInSelect, + _parameterMarkerFormat, + _parameterMarkerPattern, + _parameterNameMaxLength, + _parameterNamePattern, + _quotedIdentifierPattern, + _quotedIdentifierCase, + _statementSeparatorPattern, + _stringLiteralPattern, + _supportedJoinOperators + ]); + return new ValueTask(table); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs index eba61969c3..a59e0c0e41 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs @@ -2,9 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Data; using System.Data.Common; -using System.Diagnostics; +using System.Threading.Tasks; using Microsoft.Data.Common; #nullable enable @@ -13,426 +14,230 @@ namespace Microsoft.Data.SqlClient; internal sealed partial class SqlMetaDataFactory { - private static void LoadDataTypesDataTables(DataSet metaDataCollectionsDataSet) + private sealed class DataTypesCollection : MetaDataCollectionBase { - DataTable dataTypesDataTable = CreateDataTypesDataTable(); + private readonly TypeMetaData[] _types; - dataTypesDataTable.BeginLoadData(); - - // SQL Server data types are grouped into the rough categories below: - // 1. Fixed-scale and fixed-precision numeric types: tinyint, smallint, int, bigint, real, money, smallmoney, bit. - // 2. Variable-precision numeric types: float and decimal / numeric. - // 3. Fixed-precision date/time types: datetime, smalldatetime, date. - // 4. Variable-precision date/time types: time, datetime2, datetimeoffset. - // 5. Long types: xml, json, text, ntext, image. - // 6. Fixed-length string and binary types: char, nchar, binary. - // 7. Variable-length string and binary types: varchar, nvarchar, varbinary. - // 8. Miscellaneous fixed-length types: uniqueidentifier, sql_variant, timestamp - - AddFixedNumericType(SqlDbType.TinyInt, isBestMatch: true); - AddFixedNumericType(SqlDbType.SmallInt, isBestMatch: true); - AddFixedNumericType(SqlDbType.Int, isBestMatch: true); - AddFixedNumericType(SqlDbType.BigInt, isBestMatch: true); - AddFixedNumericType(SqlDbType.Real, isBestMatch: true); - // Money and smallmoney are not the best ways to represent System.Decimal values. SQL Server - // provides a variable-precision "numeric" type for that purpose. - AddFixedNumericType(SqlDbType.Money, isBestMatch: false); - AddFixedNumericType(SqlDbType.SmallMoney, isBestMatch: false); - AddFixedNumericType(SqlDbType.Bit, isBestMatch: false); - - AddVariablePrecisionNumericType(SqlDbType.Float, columnSize: 53); - AddVariablePrecisionNumericType(SqlDbType.Decimal, columnSize: 38); - AddVariablePrecisionNumericType(SqlDbType.Decimal, columnSize: 38, - aliasType: "numeric"); - - AddFixedPrecisionDateTimeType(SqlDbType.DateTime, isBestMatch: true); - AddFixedPrecisionDateTimeType(SqlDbType.SmallDateTime, isBestMatch: true); - AddFixedPrecisionDateTimeType(SqlDbType.Date, isBestMatch: false, - minimumVersion: "10.00.000.0"); - - AddVariablePrecisionDateTimeType(SqlDbType.Time, columnSize: 5, isBestMatch: false, - minimumVersion: "10.00.000.0"); - AddVariablePrecisionDateTimeType(SqlDbType.DateTime2, columnSize: 8, - minimumVersion: "10.00.000.0"); - AddVariablePrecisionDateTimeType(SqlDbType.DateTimeOffset, columnSize: 10, - minimumVersion: "10.00.000.0"); - - AddLongStringOrBinaryType(SqlDbType.Xml); - AddLongStringOrBinaryType(SqlDbTypeExtensions.Json, literalPrefix: "'", literalSuffix: "'", - minimumVersion: "17.00.000.0"); - AddLongStringOrBinaryType(SqlDbType.Text, literalPrefix: "'", literalSuffix: "'"); - AddLongStringOrBinaryType(SqlDbType.NText, literalPrefix: "N'", literalSuffix: "'"); - AddLongStringOrBinaryType(SqlDbType.Image, literalPrefix: "0x"); - - AddFixedLengthStringOrBinaryType(SqlDbType.Char, literalPrefix: "'", literalSuffix: "'"); - AddFixedLengthStringOrBinaryType(SqlDbType.NChar, literalPrefix: "N'", literalSuffix: "'"); - AddFixedLengthStringOrBinaryType(SqlDbType.Binary, literalPrefix: "0x"); - - AddVariableLengthStringOrBinaryType(SqlDbType.VarChar, literalPrefix: "'", literalSuffix: "'"); - AddVariableLengthStringOrBinaryType(SqlDbType.NVarChar, literalPrefix: "N'", literalSuffix: "'"); - AddVariableLengthStringOrBinaryType(SqlDbType.VarBinary, literalPrefix: "0x"); - - AddUniqueIdentifierType(); - AddSqlVariantType(); - AddRowVersionType(); - - dataTypesDataTable.EndLoadData(); - dataTypesDataTable.AcceptChanges(); - - metaDataCollectionsDataSet.Tables.Add(dataTypesDataTable); - - void AddFixedNumericType(SqlDbType integerDbType, bool isBestMatch) - { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(integerDbType, isMultiValued: false); - DataRow typeRow = dataTypesDataTable.NewRow(); - - typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; - - // A fixed-scale integer always has precision equal to the column size and a scale of 0xFF. - // If the precision is marked as unknown, then report the column size directly. - Debug.Assert(metaType.Scale == TdsEnums.UNKNOWN_PRECISION_SCALE); - typeRow[DbMetaDataColumnNames.ColumnSize] = - metaType.Precision == TdsEnums.UNKNOWN_PRECISION_SCALE - ? metaType.FixedLength - : metaType.Precision; - - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - // Of all fixed-scale integer types, only "tinyint", "smallint", "int" and "bigint" are auto-incrementable. - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = - integerDbType is SqlDbType.TinyInt or SqlDbType.SmallInt or SqlDbType.Int or SqlDbType.BigInt; - typeRow[DbMetaDataColumnNames.IsBestMatch] = isBestMatch; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; - typeRow[DbMetaDataColumnNames.IsFixedLength] = true; - // "real" is an ISO synonym of "float(24)". This means that it's a fixed-scale alias of a dynamic-scale type. - // "bit" is also not considered fixed precision/scale, since SQL Server packs multiple bit columns into the same byte. - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = integerDbType is not SqlDbType.Real and not SqlDbType.Bit; - typeRow[DbMetaDataColumnNames.IsLong] = false; - typeRow[DbMetaDataColumnNames.IsNullable] = true; - typeRow[DbMetaDataColumnNames.IsSearchable] = true; - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; - // Only "tinyint" is unsigned. "bit" does not have the concept of signed/unsigned. - if (integerDbType is not SqlDbType.Bit) - { - typeRow[DbMetaDataColumnNames.IsUnsigned] = integerDbType is SqlDbType.TinyInt; - } - - dataTypesDataTable.Rows.Add(typeRow); - } - - void AddVariablePrecisionNumericType(SqlDbType numericDbType, int columnSize, - string? aliasType = null) + internal DataTypesCollection(TypeMetaData[] types) + : base(DbMetaDataCollectionNames.DataTypes, 0, 0) { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(numericDbType, isMultiValued: false); - string typeName = aliasType ?? metaType.TypeName; - bool variableScale = numericDbType is SqlDbType.Decimal; - DataRow typeRow = dataTypesDataTable.NewRow(); - - typeRow[DbMetaDataColumnNames.TypeName] = typeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - typeRow[DbMetaDataColumnNames.ColumnSize] = columnSize; - - // Both "float" and "decimal" have variable precision, but "decimal" also has variable scale. - if (variableScale) - { - typeRow[DbMetaDataColumnNames.CreateFormat] = $"{typeName}({{0}}, {{1}})"; - typeRow[DbMetaDataColumnNames.CreateParameters] = "precision,scale"; - typeRow[DbMetaDataColumnNames.MinimumScale] = 0; - // The data type is a fixed number of bytes, which can be distributed between the precision - // and the scale. Therefore, the maximum scale is equal to the column size. - typeRow[DbMetaDataColumnNames.MaximumScale] = columnSize; - } - else - { - typeRow[DbMetaDataColumnNames.CreateFormat] = $"{typeName}({{0}})"; - typeRow[DbMetaDataColumnNames.CreateParameters] = "number of bits used to store the mantissa"; - } - - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - // Only the "decimal" type is auto-incrementable. - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = numericDbType is SqlDbType.Decimal; - typeRow[DbMetaDataColumnNames.IsBestMatch] = true; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; - typeRow[DbMetaDataColumnNames.IsFixedLength] = true; - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; - typeRow[DbMetaDataColumnNames.IsLong] = false; - typeRow[DbMetaDataColumnNames.IsNullable] = true; - typeRow[DbMetaDataColumnNames.IsSearchable] = true; - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; - typeRow[DbMetaDataColumnNames.IsUnsigned] = false; - - dataTypesDataTable.Rows.Add(typeRow); + _types = types; } - void AddFixedPrecisionDateTimeType(SqlDbType dateTimeDbType, bool isBestMatch, - string? minimumVersion = null) + public async override ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null) { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(dateTimeDbType, isMultiValued: false); - DataRow typeRow = dataTypesDataTable.NewRow(); - - typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - // "date" reports an unknown precision and an unknown scale, even though its precision and scale are fixed. - // To that end, we report its column length directly. - typeRow[DbMetaDataColumnNames.ColumnSize] = - metaType.Precision == TdsEnums.UNKNOWN_PRECISION_SCALE - ? metaType.FixedLength - : metaType.Precision; - typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; - typeRow[DbMetaDataColumnNames.IsBestMatch] = isBestMatch; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; - typeRow[DbMetaDataColumnNames.IsFixedLength] = true; - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = dateTimeDbType is SqlDbType.Date; - typeRow[DbMetaDataColumnNames.IsLong] = false; - typeRow[DbMetaDataColumnNames.IsNullable] = true; - typeRow[DbMetaDataColumnNames.IsSearchable] = true; - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = true; - typeRow[DbMetaDataColumnNames.LiteralPrefix] = @"{ts '"; - typeRow[DbMetaDataColumnNames.LiteralSuffix] = @"'}"; - - if (minimumVersion is not null) + if (ADP.IsEmptyArray(context.RestrictionValues) == false) { - typeRow[MinimumVersionKey] = minimumVersion; + throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes); } - dataTypesDataTable.Rows.Add(typeRow); - } - - void AddVariablePrecisionDateTimeType(SqlDbType dateTimeDbType, int columnSize, - bool isBestMatch = true, - string? minimumVersion = null) - { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(dateTimeDbType, isMultiValued: false); - DataRow typeRow = dataTypesDataTable.NewRow(); - - typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - typeRow[DbMetaDataColumnNames.ColumnSize] = columnSize; - typeRow[DbMetaDataColumnNames.CreateFormat] = $"{metaType.TypeName}({{0}})"; - // The documentation describes these data types as having variable precision, but GetSchema reports that they - // have variable scale. - typeRow[DbMetaDataColumnNames.CreateParameters] = "scale"; - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; - typeRow[DbMetaDataColumnNames.IsBestMatch] = isBestMatch; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; - typeRow[DbMetaDataColumnNames.IsFixedLength] = false; - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; - typeRow[DbMetaDataColumnNames.IsLong] = false; - typeRow[DbMetaDataColumnNames.IsNullable] = true; - typeRow[DbMetaDataColumnNames.IsSearchable] = true; - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = true; - typeRow[DbMetaDataColumnNames.MinimumScale] = 0; - typeRow[DbMetaDataColumnNames.MaximumScale] = metaType.Scale; - typeRow[DbMetaDataColumnNames.LiteralPrefix] = @"{ts '"; - typeRow[DbMetaDataColumnNames.LiteralSuffix] = @"'}"; - - if (minimumVersion is not null) + DataTable result = accumulator ?? new(DbMetaDataCollectionNames.DataTypes) { - typeRow[MinimumVersionKey] = minimumVersion; - } - - dataTypesDataTable.Rows.Add(typeRow); - } - - void AddLongStringOrBinaryType(SqlDbType longDbType, - string? literalPrefix = null, string? literalSuffix = null, - string? minimumVersion = null) => - AddStringOrBinaryType(longDbType, - // The column size is measured in elements, not bytes. For ntext, each element is a 2 byte Unicode character. - columnSize: longDbType is SqlDbType.NText ? int.MaxValue / ADP.CharSize : int.MaxValue, - isLong: true, isFixedLength: false, - isSearchable: false, - literalPrefix: literalPrefix, literalSuffix: literalSuffix, - minimumVersion: minimumVersion); - - void AddFixedLengthStringOrBinaryType(SqlDbType fixedLengthDbType, - string? literalPrefix = null, string? literalSuffix = null) => - AddStringOrBinaryType(fixedLengthDbType, - // See the comment on AddLongStringOrBinary regarding column sizes. To add: the "binary" type can be up to 8000 bytes. - columnSize: fixedLengthDbType switch + Columns = { - SqlDbType.Binary => 8000, - SqlDbType.NChar => int.MaxValue / ADP.CharSize, - _ => int.MaxValue - }, - isLong: false, isFixedLength: true, - isSearchable: true, - literalPrefix: literalPrefix, literalSuffix: literalSuffix); - - void AddVariableLengthStringOrBinaryType(SqlDbType variableLengthDbType, - string? literalPrefix = null, string? literalSuffix = null) => - AddStringOrBinaryType(variableLengthDbType, - // See the comment on AddLongStringOrBinary regarding column sizes. Unlike the "binary" type, varbinary is reported to - // have a maximum column size of (2^32-1) / 2 elements, just as nvarchar does. - columnSize: variableLengthDbType is SqlDbType.NVarChar or SqlDbType.VarBinary - ? int.MaxValue / ADP.CharSize - : int.MaxValue, - isLong: false, isFixedLength: false, - isSearchable: true, - literalPrefix: literalPrefix, literalSuffix: literalSuffix); - - void AddRowVersionType() => - AddStringOrBinaryType(SqlDbType.Timestamp, - columnSize: TdsEnums.TEXT_TIME_STAMP_LEN, isLong: false, isFixedLength: true, - isSearchable: true, - literalPrefix: "0x"); - - void AddStringOrBinaryType(SqlDbType sqlDbType, int columnSize, bool isLong, - bool isFixedLength, bool isSearchable, - string? literalPrefix = null, string? literalSuffix = null, - string? minimumVersion = null) - { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(sqlDbType, isMultiValued: false); - DataRow typeRow = dataTypesDataTable.NewRow(); - bool hasLengthSpecifier = sqlDbType is SqlDbType.Char or SqlDbType.NChar or SqlDbType.Binary - or SqlDbType.VarChar or SqlDbType.NVarChar or SqlDbType.VarBinary; + 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)) + } + }; - typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - typeRow[DbMetaDataColumnNames.ColumnSize] = columnSize; - // Several string or binary data types do not include the length in their type declaration. - // Of the data types which do, fixed-length types use the length parameter to decide the full length, - // while the rest use it to decide the maximum length. - if (hasLengthSpecifier) - { - typeRow[DbMetaDataColumnNames.CreateFormat] = $"{metaType.TypeName}({{0}})"; - typeRow[DbMetaDataColumnNames.CreateParameters] = - isFixedLength ? "length" : "max length"; - } - else + // 1. Load built-in types + result.BeginLoadData(); + foreach(TypeMetaData t in _types) { - typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + if ((t.MinimumVersion == null || string.Compare(context.ServerVersion, t.MinimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && + (t.MaximumVersion == null || string.Compare(context.ServerVersion, t.MaximumVersion, StringComparison.OrdinalIgnoreCase) <= 0)) + { + DataRow row = result.NewRow(); + row[DbMetaDataColumnNames.TypeName] = t.TypeName; + row[DbMetaDataColumnNames.ProviderDbType] = t.ProviderDbType; + if (t.ColumnSize > -1) + { + row[DbMetaDataColumnNames.ColumnSize] = t.ColumnSize; + } + + row[DbMetaDataColumnNames.CreateFormat] = t.CreateFormat; + row[DbMetaDataColumnNames.CreateParameters] = t.CreateParameters; + row[DbMetaDataColumnNames.DataType] = t.DataType; + row[DbMetaDataColumnNames.IsAutoIncrementable] = t.IsAutoIncrementable; + row[DbMetaDataColumnNames.IsBestMatch] = t.IsBestMatch; + row[DbMetaDataColumnNames.IsCaseSensitive] = t.IsCaseSensitive; + row[DbMetaDataColumnNames.IsFixedLength] = t.IsFixedLength; + row[DbMetaDataColumnNames.IsFixedPrecisionScale] = t.IsFixedPrecisionScale; + row[DbMetaDataColumnNames.IsLong] = t.IsLong; + row[DbMetaDataColumnNames.IsNullable] = t.IsNullable; + row[DbMetaDataColumnNames.IsSearchable] = t.IsSearchable; + row[DbMetaDataColumnNames.IsSearchableWithLike] = t.IsSearchableWithLike; + if (t.IsUnsigned != null) + { + row[DbMetaDataColumnNames.IsUnsigned] = t.IsUnsigned; + } + + if (t.MaximumScale > -1) + { + row[DbMetaDataColumnNames.MaximumScale] = t.MaximumScale; + } + + if (t.MinimumScale > -1) + { + row[DbMetaDataColumnNames.MinimumScale] = t.MinimumScale; + } + + row[DbMetaDataColumnNames.IsConcurrencyType] = t.IsConcurrencyType; + if (t.IsLiteralSupported != null) + { + row[DbMetaDataColumnNames.IsLiteralSupported] = t.IsLiteralSupported; + } + + row[DbMetaDataColumnNames.LiteralPrefix] = t.LiteralPrefix; + row[DbMetaDataColumnNames.LiteralSuffix] = t.LiteralSuffix; + result.Rows.Add(row); + } } - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; - // The DataType for XML is string, which is not the best match for an XML type. - // Similarly, although timestamp/rowversion is represented as a byte array, it's not best - // represented as such. - typeRow[DbMetaDataColumnNames.IsBestMatch] = sqlDbType is not SqlDbType.Xml and not SqlDbType.Timestamp and not SqlDbTypeExtensions.Json; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = sqlDbType is SqlDbType.Timestamp; - typeRow[DbMetaDataColumnNames.IsFixedLength] = isFixedLength; - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; - typeRow[DbMetaDataColumnNames.IsLong] = isLong; - typeRow[DbMetaDataColumnNames.IsNullable] = sqlDbType is not SqlDbType.Timestamp; - typeRow[DbMetaDataColumnNames.IsSearchable] = isSearchable; - // String types are searchable with LIKE; binary types are not. SQL Server considers XML and JSON a binary type for this purpose. - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = - metaType.IsCharType && sqlDbType is not SqlDbType.Xml and not SqlDbTypeExtensions.Json; + result.EndLoadData(); + result.AcceptChanges(); - if (literalPrefix is null && literalSuffix is null) - { - // If no literal prefix or suffix is specified, then literal support is not available. - typeRow[DbMetaDataColumnNames.IsLiteralSupported] = false; - } - else + // 2. Add UDTs from the server if supported + MetaDataCollectionBase udtCollection = FindMetaDataCollection("UDTs", context.ServerVersion); + if (udtCollection != null) { - if (literalPrefix is not null) + const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; + using SqlCommand command = ((SqlConnection)context.Connection).CreateCommand(); + + command.CommandText = GetEngineEditionSqlCommand; + int engineEdition = (int)(context.IsAsync ? await command.ExecuteScalarAsync(context.CancellationToken).ConfigureAwait(false) : command.ExecuteScalar()); + // Azure SQL Edge (9) throws an exception when querying sys.assemblies + // Azure Synapse Analytics (6) and Azure Synapse serverless SQL pool (11) + // do not support ASSEMBLYPROPERTY + if (!s_assemblyPropertyUnsupportedEngines.Contains(engineEdition)) { - typeRow[DbMetaDataColumnNames.LiteralPrefix] = literalPrefix; - } - if (literalSuffix is not null) - { - typeRow[DbMetaDataColumnNames.LiteralSuffix] = literalSuffix; + await udtCollection.GetMetadata(context, result); } } - if (minimumVersion is not null) + // 3. Add TVPs from the server if supported + MetaDataCollectionBase tvpCollection = FindMetaDataCollection("TVPs", context.ServerVersion); + if (tvpCollection != null) { - typeRow[MinimumVersionKey] = minimumVersion; + await tvpCollection.GetMetadata(context, result); } - dataTypesDataTable.Rows.Add(typeRow); + return result; } + } - void AddSqlVariantType() + private sealed class TypeMetaData + { + public readonly string TypeName; + public readonly int ProviderDbType; + public readonly long ColumnSize; + public readonly string CreateFormat; + public readonly string? CreateParameters; + public readonly string DataType; + public readonly bool IsAutoIncrementable; + public readonly bool IsBestMatch; + public readonly bool IsCaseSensitive; + public readonly bool IsFixedLength; + public readonly bool IsFixedPrecisionScale; + public readonly bool IsLong; + public readonly bool IsNullable; + public readonly bool IsSearchable; + public readonly bool IsSearchableWithLike; + public readonly bool? IsUnsigned; + public readonly short MaximumScale; + public readonly short MinimumScale; + public readonly bool IsConcurrencyType; + public readonly string? MaximumVersion; + public readonly string? MinimumVersion; + public readonly bool? IsLiteralSupported; + public readonly string? LiteralPrefix; + public readonly string? LiteralSuffix; + + public TypeMetaData(string typeName, int providerDbType, long columnSize, string createFormat, + string dataType, bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, + bool isFixedPrecisionScale, bool isLong, bool isNullable, bool isSearchable, bool isSearchableWithLike, + bool? isUnsigned, short maximumScale, short minimumScale, bool isConcurrencyType, + string? minimumVersion, string? maximumVersion, bool? isLiteralSupported, string? literalPrefix, string? literalSuffix, string createParameters) { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.Variant, isMultiValued: false); - DataRow typeRow = dataTypesDataTable.NewRow(); - - typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; - typeRow[DbMetaDataColumnNames.IsBestMatch] = true; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; - typeRow[DbMetaDataColumnNames.IsFixedLength] = false; - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; - typeRow[DbMetaDataColumnNames.IsLong] = false; - typeRow[DbMetaDataColumnNames.IsNullable] = true; - typeRow[DbMetaDataColumnNames.IsSearchable] = true; - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; - typeRow[DbMetaDataColumnNames.IsLiteralSupported] = false; - - dataTypesDataTable.Rows.Add(typeRow); + TypeName = typeName; + ProviderDbType = providerDbType; + ColumnSize = columnSize; + CreateFormat = createFormat; + CreateParameters = createParameters; + DataType = dataType; + IsAutoIncrementable = isAutoIncrementable; + IsBestMatch = isBestMatch; + IsCaseSensitive = isCaseSensitive; + IsFixedLength = isFixedLength; + IsFixedPrecisionScale = isFixedPrecisionScale; + IsLong = isLong; + IsNullable = isNullable; + IsSearchable = isSearchable; + IsSearchableWithLike = isSearchableWithLike; + IsUnsigned = isUnsigned; + MaximumScale = maximumScale; + MinimumScale = minimumScale; + IsConcurrencyType = isConcurrencyType; + MaximumVersion = maximumVersion; + MinimumVersion = minimumVersion; + IsLiteralSupported = isLiteralSupported; + LiteralPrefix = literalPrefix; + LiteralSuffix = literalSuffix; } - void AddUniqueIdentifierType() + public TypeMetaData(SqlDbType target, + string dataType, bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, + bool isFixedPrecisionScale, bool isLong, bool isNullable, bool isSearchable, bool isSearchableWithLike, + bool isUnsigned, short maximumScale, short minimumScale, bool isConcurrencyType, + string maximumVersion, string minimumVersion, bool isLiteralSupported, string literalPrefix, string literalSuffix) { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.UniqueIdentifier, isMultiValued: false); - DataRow typeRow = dataTypesDataTable.NewRow(); - - typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; - typeRow[DbMetaDataColumnNames.ColumnSize] = metaType.FixedLength; - typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; - typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; - typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; - typeRow[DbMetaDataColumnNames.IsBestMatch] = true; - typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; - typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; - typeRow[DbMetaDataColumnNames.IsFixedLength] = true; - typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; - typeRow[DbMetaDataColumnNames.IsLong] = false; - typeRow[DbMetaDataColumnNames.IsNullable] = true; - typeRow[DbMetaDataColumnNames.IsSearchable] = true; - typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; - typeRow[DbMetaDataColumnNames.LiteralPrefix] = "'"; - typeRow[DbMetaDataColumnNames.LiteralSuffix] = "'"; - - dataTypesDataTable.Rows.Add(typeRow); + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(target, isMultiValued: false); + + TypeName = metaType.TypeName; + ProviderDbType = (int)metaType.SqlDbType; + ColumnSize = metaType.FixedLength; + CreateFormat = metaType.TypeName; + CreateParameters = metaType.ClassType.FullName; + DataType = dataType; + IsAutoIncrementable = isAutoIncrementable; + IsBestMatch = isBestMatch; + IsCaseSensitive = isCaseSensitive; + IsFixedLength = isFixedLength; + IsFixedPrecisionScale = isFixedPrecisionScale; + IsLong = isLong; + IsNullable = isNullable; + IsSearchable = isSearchable; + IsSearchableWithLike = isSearchableWithLike; + IsUnsigned = isUnsigned; + MaximumScale = maximumScale; + MinimumScale = minimumScale; + IsConcurrencyType = isConcurrencyType; + MaximumVersion = maximumVersion; + MinimumVersion = minimumVersion; + IsLiteralSupported = isLiteralSupported; + LiteralPrefix = literalPrefix; + LiteralSuffix = literalSuffix; } } - - 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)) - } - }; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs new file mode 100644 index 0000000000..dc950fe29f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs @@ -0,0 +1,67 @@ +// 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 System.Collections; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient; + +internal sealed partial class SqlMetaDataFactory +{ + private class MetaDataCollection : MetaDataCollectionBase + { + internal MetaDataCollection() + : base(DbMetaDataCollectionNames.MetaDataCollections, 0, 0) + { + } + + public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + { + if (!ADP.IsEmptyArray(context.RestrictionValues)) + { + throw ADP.TooManyRestrictions(CollectionName); + } + + DataTable table = accumulator ?? new(CollectionName) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.NumberOfRestrictions, typeof(int)), + new DataColumn(DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int)) + } + }; + + foreach (MetaDataCollectionBase mdc in s_metaDataCollection) + { + if (mdc.SupportedByCurrentVersion(context.ServerVersion)) + { + DataRow row = table.NewRow(); + table.Rows.Add([mdc.CollectionName, mdc.NumberOfRestrictions, mdc.NumberOfIdentifierParts]); + } + } + + return new ValueTask(table); + } + + internal async ValueTask GetMetadata(string collectionName, MetaDataContext context, DataTable accumulator = null) + { + MetaDataCollectionBase collection = FindMetaDataCollection(collectionName, context.ServerVersion); + if (collection == null) + { + throw ADP.UnsupportedVersion(collectionName); + //throw ADP.UndefinedCollection(collectionName); + //throw ADP.UnableToBuildCollection(collectionName); + } + + DataTable table = await collection.GetMetadata(context); + return table; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs new file mode 100644 index 0000000000..843a9bec3e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs @@ -0,0 +1,49 @@ +// 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 System.Data; +using System.Data.Common; +using System.Threading.Tasks; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class SqlMetaDataFactory + { + private sealed class ReservedWordsCollection : MetaDataCollectionBase + { + private readonly string[] _reservedWords; + internal ReservedWordsCollection(string[] reserverdWords) + : base(DbMetaDataCollectionNames.ReservedWords, 0, 0) + { + _reservedWords = reserverdWords; + } + + public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + { + if (!ADP.IsEmptyArray(context.RestrictionValues)) + { + throw ADP.TooManyRestrictions(CollectionName); + } + + DataTable table = accumulator ?? new(CollectionName) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)) + } + }; + + foreach (string reservedWord in _reservedWords) + { + DataRow row = table.NewRow(); + row[DbMetaDataColumnNames.ReservedWord] = reservedWord; + table.Rows.Add(row); + } + + return new ValueTask(table); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs new file mode 100644 index 0000000000..63e460dbb6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs @@ -0,0 +1,59 @@ +// 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 System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class SqlMetaDataFactory + { + private sealed class RestrictionsCollection : MetaDataCollectionBase + { + internal RestrictionsCollection() : base(DbMetaDataCollectionNames.Restrictions, 0, 0) + { + } + + public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + { + if (!ADP.IsEmptyArray(context.RestrictionValues)) + { + throw ADP.TooManyRestrictions(CollectionName); + } + + DataTable table = accumulator ?? new(CollectionName) + { + 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)), + } + }; + + foreach (MetaDataCollectionBase mdc in s_metaDataCollection) + { + if (mdc is SqlCommandCollection sqlCollection && + sqlCollection.RestrictionParams != null && + mdc.SupportedByCurrentVersion(context.ServerVersion)) + { + foreach (Restriction restriction in sqlCollection.RestrictionParams) + { + DataRow row = table.NewRow(); + table.Rows.Add([mdc.CollectionName, restriction.RestrictionName, restriction.ParameterName, null, restriction.RestrictionNumber]); + } + } + } + + return new ValueTask(table); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs new file mode 100644 index 0000000000..4cd83b7454 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs @@ -0,0 +1,138 @@ +// 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 System; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class SqlMetaDataFactory + { + private sealed class SqlCommandCollection : MetaDataCollectionBase + { + private readonly string _populationString; + public readonly Restriction[] RestrictionParams; + + public SqlCommandCollection(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, + string minimumVersion, string maximumVersion, string populationString, Restriction[] restrictions) + : base(collectionName, numberOfRestrictions, numberOfIdentifierParts, minimumVersion, maximumVersion) + { + RestrictionParams = restrictions; + _populationString = populationString; + } + + public async override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + { + Debug.Assert(NumberOfRestrictions >= (context.RestrictionValues?.Length ?? 0)); + + context.CancellationToken.ThrowIfCancellationRequested(); + + DataTable resultTable = null; + + if ((context.RestrictionValues is not null) && (context.RestrictionValues.Length > NumberOfRestrictions)) + { + throw ADP.TooManyRestrictions(CollectionName); + } + + if (!ADP.IsEmptyArray(context.RestrictionValues)) + { + for (int i = 0; i < context.RestrictionValues.Length; i++) + { + if ((context.RestrictionValues[i] is not null) && (context.RestrictionValues[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(); + } + } + } + + SqlConnection castConnection = context.Connection as SqlConnection; + using SqlCommand command = castConnection.CreateCommand(); + + command.CommandText = _populationString; + 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 ((context.RestrictionValues is not null) && (i < context.RestrictionValues.Length) && (context.RestrictionValues[i] is not null)) + { + restrictionParameter.Value = context.RestrictionValues[i]; + } + else + { + // This is where we have to assign null to the value of the parameter. + restrictionParameter.Value = DBNull.Value; + } + + restrictionParameter.ParameterName = RestrictionParams[i].ParameterName; + restrictionParameter.Direction = ParameterDirection.Input; + command.Parameters.Add(restrictionParameter); + } + + SqlDataReader reader = null; + try + { + try + { + reader = context.IsAsync ? await command.ExecuteReaderAsync(context.CancellationToken) : command.ExecuteReader(); + } + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) + { + throw ADP.QueryFailed(CollectionName, e); + } + + // reader.GetColumnSchema waits synchronously for the reader to receive its type metadata. + // Instead, we invoke reader.ReadAsync outside of the while loop, which will implicitly ensure that + // the metadata is available. ReadAsync/Read will throw an exception if necessary, so we can trust + // that the list of fields is available if the call returns. + bool firstResultAvailable = context.IsAsync ? await reader.ReadAsync(context.CancellationToken) : reader.Read(); + + if (accumulator != null) + { + resultTable = accumulator; + } + else + { + // 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); + } + } + + if (firstResultAvailable) + { + object[] values = new object[resultTable.Columns.Count]; + do + { + reader.GetValues(values); + resultTable.Rows.Add(values); + } while (context.IsAsync ? await reader.ReadAsync(context.CancellationToken) : reader.Read()); + } + } + finally + { + reader?.Dispose(); + } + + return resultTable; + } + } + } +} 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 841c71d410..6a392099fb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -7,158 +7,277 @@ using System.Data; using System.Data.Common; using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { - internal sealed partial class SqlMetaDataFactory : IDisposable - { + internal sealed partial class SqlMetaDataFactory + { + #pragma warning disable format + private readonly static MetaDataCollectionBase[] s_metaDataCollection = [ + new MetaDataCollection(), + new DataSourceInformationCollection( + CompositeIdentifierSeparatorPattern: "\\.", + DataSourceProductName: "Microsoft SQL Server", + GroupByBehavior: GroupByBehavior.Unrelated, + IdentifierPattern: @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)", + IdentifierCase: IdentifierCase.Insensitive, + OrderByColumnsInSelect: false, + ParameterMarkerFormat: "{0}", + ParameterMarkerPattern: @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)", + ParameterNameMaxLength: 128, + ParameterNamePattern: @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)", + QuotedIdentifierPattern:"(([^\\[]|\\]\\])*)", + QuotedIdentifierCase: IdentifierCase.Insensitive, + StatementSeparatorPattern: ";", + StringLiteralPattern: "'(([^']|'')*)'", + SupportedJoinOperators: SupportedJoinOperators.Inner | SupportedJoinOperators.LeftOuter | SupportedJoinOperators.RightOuter | SupportedJoinOperators.FullOuter), + new DataTypesCollection([ + // Type order follows the order from SqlMetaData.xml + // IsBestMatch isFixedPrecisionScale IsSearchable MaximumScale MinimumVersion LiteralPrefix + // ProviderDbType IsCaseSensitive IsLong IsSearchableWithLike MinimumScale MaximumVersion LiteralSuffix + // TypeName ColumnSize CreateFormat DataType IsAutoIncrementable IsFixedLength IsNullable isUnsigned IsConcurrencyType IsLiteralSupported CreateParameters + new ("smallint" ,16 ,5 ,"smallint" ,"System.Int16" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("int" ,8 ,10 ,"int" ,"System.Int32" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("real" ,13 ,7 ,"real" ,"System.Single" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("float" ,6 ,53 ,"float({0})" ,"System.Double" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,"number of bits used to store the mantissa"), + new ("money" ,9 ,19 ,"money" ,"System.Decimal" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("smallmoney" ,17 ,10 ,"smallmoney" ,"System.Decimal" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("bit" ,2 ,1 ,"bit" ,"System.Boolean" ,false ,false ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("tinyint" ,20 ,3 ,"tinyint" ,"System.Byte" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,true ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("bigint" ,0 ,19 ,"bigint" ,"System.Int64" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("timestamp" ,19 ,8 ,"timestamp" ,"System.Byte[]" ,false ,false ,false ,true ,false ,false ,false ,true ,false ,null ,-1 ,-1 ,true ,null ,null ,null ,"0x" ,null ,""), + new ("binary" ,1 ,8000 ,"binary({0})" ,"System.Byte[]" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"length"), + new ("image" ,7 ,2147483647,"image" ,"System.Byte[]" ,false ,true ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,""), + new ("text" ,18 ,2147483647,"text" ,"System.String" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), + new ("ntext" ,11 ,1073741823,"ntext" ,"System.String" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,""), + new ("decimal" ,5 ,38 ,"decimal({0}, {1})" ,"System.Decimal" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), + new ("numeric" ,5 ,38 ,"numeric({0}, {1})" ,"System.Decimal" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), + new ("datetime" ,4 ,23 ,"datetime" ,"System.DateTime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), + new ("smalldatetime" ,15 ,16 ,"smalldatetime" ,"System.DateTime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), + new ("sql_variant" ,23 ,-1 ,"sql_variant" ,"System.Object" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), + new ("xml" ,25 ,2147483647,"xml" ,"System.String" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), + new ("varchar" ,22 ,2147483647,"varchar({0})" ,"System.String" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"max length"), + new ("char" ,3 ,2147483647,"char({0})" ,"System.String" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"length"), + new ("nchar" ,10 ,1073741823,"nchar({0})" ,"System.String" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"length"), + new ("nvarchar" ,12 ,1073741823,"nvarchar({0})" ,"System.String" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"max length"), + new ("varbinary" ,21 ,1073741823,"varbinary({0})" ,"System.Byte[]" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"max length"), + new ("uniqueidentifier",14 ,16 ,"uniqueidentifier" ,"System.Guid" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), + new ("date" ,31 ,3 ,"date" ,"System.DateTime" ,false ,false ,false ,true ,true ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,""), + new ("time" ,32 ,5 ,"time({0})" ,"System.TimeSpan" ,false ,false ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new ("datetime2" ,33 ,8 ,"datetime2({0})" ,"System.DateTime" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new ("datetimeoffset" ,34 ,10 ,"datetimeoffset({0})","System.DateTimeOffset",false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new ("json" ,35 ,2147483647,"json" ,"System.String" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,"17.00.000.0" ,null ,false ,"'" ,"'" ,""), + ]), + new RestrictionsCollection(), + new ReservedWordsCollection([ + // Reserved keywords used by SQL Server and Azure Synapse Analytics. + "ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AUTHORIZATION", "BACKUP", + "BEGIN", "BETWEEN", "BREAK", "BROWSE", "BULK", "BY", "CASCADE", "CASE", "CHECK", + "CHECKPOINT", "CLOSE", "CLUSTERED", "COALESCE", "COLLATE", "COLUMN", "COMMIT", "COMPUTE", "CONSTRAINT", + "CONTAINS", "CONTAINSTABLE", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", + "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DBCC", "DEALLOCATE", "DECLARE", "DEFAULT", "DELETE", + "DENY", "DESC", "DISK", "DISTINCT", "DISTRIBUTED", "DOUBLE", "DROP", "DUMP", "ELSE", + "END", "ERRLVL", "ESCAPE", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXTERNAL", + "FETCH", "FILE", "FILLFACTOR", "FOR", "FOREIGN", "FREETEXT", "FREETEXTTABLE", "FROM", "FULL", + "FUNCTION", "GOTO", "GRANT", "GROUP", "HAVING", "HOLDLOCK", "IDENTITY", "IDENTITY_INSERT", "IDENTITYCOL", + "IF", "IN", "INDEX", "INNER", "INSERT", "INTERSECT", "INTO", "IS", "JOIN", + // @TODO: Missing keyword: MERGE + "KEY", "KILL", "LEFT", "LIKE", "LINENO", "LOAD", /* "MERGE", */ "NATIONAL", "NOCHECK", + "NONCLUSTERED", "NOT", "NULL", "NULLIF", "OF", "OFF", "OFFSETS", "ON", "OPEN", + "OPENDATASOURCE", "OPENQUERY", "OPENROWSET", "OPENXML", "OPTION", "OR", "ORDER", "OUTER", "OVER", + // @TODO: Missing keyword: PIVOT + "PERCENT", /* "PIVOT", */ "PLAN", "PRECISION", "PRIMARY", "PRINT", "PROC", "PROCEDURE", "PUBLIC", + "RAISERROR", "READ", "READTEXT", "RECONFIGURE", "REFERENCES", "REPLICATION", "RESTORE", "RESTRICT", "RETURN", + // @TODO: Missing keyword: REVERT + /* "REVERT", */ "REVOKE", "RIGHT", "ROLLBACK", "ROWCOUNT", "ROWGUIDCOL", "RULE", "SAVE", "SCHEMA", + // @TODO: Missing keywords: SECURITYAUDIT, SEMANTICKEYPHRASETABLE, SEMANTICSIMILARITYDETAILSTABLE + /* "SECURITYAUDIT", */ "SELECT", /* "SEMANTICKEYPHRASETABLE", "SEMANTICSIMILARITYDETAILSTABLE", */ + // @TODO: Missing keyword: SEMANTICSIMILARITYTABLE + /* "SEMANTICSIMILARITYTABLE", */ "SESSION_USER", "SET", "SETUSER", "SHUTDOWN", + // @TODO: Missing keyword: TABLESAMPLE + "SOME", "STATISTICS", "SYSTEM_USER", "TABLE", /* "TABLESAMPLE", */ "TEXTSIZE", "THEN", "TO", "TOP", + // @TODO: Missing keywords: TRY_CONVERT, UNPIVOT + "TRAN", "TRANSACTION", "TRIGGER", "TRUNCATE", /* "TRY_CONVERT", */ "TSEQUAL", "UNION", "UNIQUE", /* "UNPIVOT", */ + "UPDATE", "UPDATETEXT", "USE", "USER", "VALUES", "VARYING", "VIEW", "WAITFOR", "WHEN", + // @TODO: Missing keyword: WITHIN GROUP + "WHERE", "WHILE", "WITH", /* "WITHIN GROUP", */ "WRITETEXT", + + // ODBC reserved keywords. + "ABSOLUTE", "ACTION", "ADA", "ALLOCATE", "ARE", "ASSERTION", "AT", "AVG", "BIT", + "BIT_LENGTH", "BOTH", "CASCADED", "CAST", "CATALOG", "CHAR", "CHAR_LENGTH", "CHARACTER", "CHARACTER_LENGTH", + "COLLATION", "CONNECT", "CONNECTION", "CONSTRAINTS", "CORRESPONDING", "COUNT", "DATE", "DAY", "DECIMAL", + "DEFERRABLE", "DEFERRED", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS", "DISCONNECT", "DOMAIN", "END-EXEC", "EXCEPTION", + "EXTRACT", "FALSE", "FIRST", "FLOAT", "FORTRAN", "FOUND", "GET", "GLOBAL", "GO", + "HOUR", "IMMEDIATE", "INCLUDE", "INDICATOR", "INITIALLY", "INPUT", "INSENSITIVE", "INT", "INTEGER", + "INTERVAL", "ISOLATION", "LANGUAGE", "LAST", "LEADING", "LEVEL", "LOCAL", "LOWER", "MATCH", + "MAX", "MIN", "MINUTE", "MODULE", "MONTH", "NAMES", "NATURAL", "NCHAR", "NEXT", + "NO", "NONE", "NUMERIC", "OCTET_LENGTH", "ONLY", "OUTPUT", "OVERLAPS", "PAD", "PASCAL", + "POSITION", "PREPARE", "PRESERVE", "PRIOR", "PRIVILEGES", "REAL", "RELATIVE", "ROWS", "SCROLL", + "SECOND", "SECTION", "SESSION", "SIZE", "SMALLINT", "SPACE", "SQL", "SQLCA", "SQLCODE", + "SQLERROR", "SQLSTATE", "SQLWARNING", "SUBSTRING", "SUM", "TEMPORARY", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", + "TIMEZONE_MINUTE", "TRAILING", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE", "UNKNOWN", "UPPER", "USAGE", + "USING", "VALUE", "VARCHAR", "WHENEVER", "WORK", "WRITE", "YEAR", "ZONE", + + // Future reserved keywords. + // @TODO: Missing keywords: ASENSITIVE, ASYMMETRIC, ATOMIC + "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ARRAY", /* "ASENSITIVE", "ASYMMETRIC", "ATOMIC", */ "BEFORE", + // @TODO: Missing keyword: CALLED, CARDINALITY + "BINARY", "BLOB", "BOOLEAN", "BREADTH", "CALL", /* "CALLED", "CARDINALITY", */ "CLASS", "CLOB", + // @TODO: Missing keywords: COLLECT, CONDITION, CORR, COVAR_POP, COVAR_SAMP, CUME_DIST + /* "COLLECT", */ "COMPLETION", /* "CONDITION", */ "CONSTRUCTOR", /* "CORR", "COVAR_POP", "COVAR_SAMP", */ "CUBE", /* "CUME_DIST", */ + // @TODO: Missing keywords: CURRENT_CATALOG, CURRENT_DEFAULT_TRANSFORM_GROUP + /* "CURRENT_CATALOG", "CURRENT_DEFAULT_TRANSFORM_GROUP", */ "CURRENT_PATH", "CURRENT_ROLE", + // @TODO: Missing keywords: CURRENT_SCHEMA, CURRENT_TRANSFORM_GROUP_FOR_TYPE + /* "CURRENT_SCHEMA", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", */ "CYCLE", "DATA", "DEC", + // @TODO: Missing keyword: ELEMENT + "DEPTH", "DEREF", "DESTROY", "DESTRUCTOR", "DETERMINISTIC", "DICTIONARY", "DYNAMIC", "EACH", /* "ELEMENT", */ + // @TODO: Missing keywords: FILTER, FULLTEXTTABLE, FUSION, HOLD + "EQUALS", "EVERY", /* "FILTER", */ "FREE", /* "FULLTEXTTABLE", "FUSION", */ "GENERAL", "GROUPING", /* "HOLD", */ + // @TODO: Missing keyword: INTERSECTION + "HOST", "IGNORE", "INITIALIZE", "INOUT", /* "INTERSECTION", */ "ITERATE", "LARGE", "LATERAL", "LESS", + // @TODO: Missing keywords: LIKE_REGEX, LN, MEMBER, METHOD + /* "LIKE_REGEX",*/ "LIMIT", /* "LN", */ "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "MAP", /* "MEMBER", "METHOD", */ + // @TODO: Missing keywords: MOD, MULTISET, NORMALIZE, OCCURRENCES_REGEX + /* "MOD", */ "MODIFIES", "MODIFY", /* "MULTISET", */ "NCLOB", "NEW", /* "NORMALIZE", */ "OBJECT", /* "OCCURRENCES_REGEX", */ + // @TODO: Missing keyword: OVERLAY, PARTITION + "OLD", "OPERATION", "ORDINALITY", "OUT", /* "OVERLAY", */ "PARAMETER", "PARAMETERS", "PARTIAL", /* "PARTITION" */ + // @TODO: Missing keywords: PERCENT_RANK, PERCENTILE_CONT, PERCENTILE_DISC, POSITION_REGEX, RANGE + "PATH", "POSTFIX", "PREFIX", "PREORDER", /* "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "POSITION_REGEX", "RANGE", */ + "READS", "RECURSIVE", "REF", "REFERENCING", + // @TODO: Missing keywords: REGR_AVGX, REGR_AVGY, REGR_COUNT, REGR_INTERCEPT, REGR_R2, REGR_SLOPE + /* "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", */ + // @TODO: Missing keywords: REGR_SXX, REGR_SXY, REGR_SYY, RELEASE + /* "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELEASE", */ "RESULT", "RETURNS", "ROLE", "ROLLUP", "ROUTINE", + // @TODO: Missing keywords: SENSITIVE, SIMILAR + "ROW", "SAVEPOINT", "SCOPE", "SEARCH", /* "SENSITIVE", */ "SEQUENCE", "SETS", /* "SIMILAR", */ "SPECIFIC", + // @TODO: Missing keywords: STDDEV_POP, STDDEV_SAMP + "SPECIFICTYPE", "SQLEXCEPTION", "START", "STATE", "STATEMENT", "STATIC", /* "STDDEV_POP", "STDDEV_SAMP", */ "STRUCTURE", + + // @TODO: Missing keywords: SUBMULTISET, SUBSTRING_REGEX, SYMMETRIC, SYSTEM, TRANSLATE_REGEX, UESCAPE + /* "SUBMULTISET", "SUBSTRING_REGEX", "SYMMETRIC", "SYSTEM", */ "TERMINATE", "THAN", /* "TRANSLATE_REGEX", */ "TREAT", /* "UESCAPE", */ + // @TODO: Missing keywords: VAR_POP, VAR_SAMP, WIDTH_BUCKET, WINDOW, WITHIN + "UNDER", "UNNEST", /* "VAR_POP", "VAR_SAMP", */ "VARIABLE", /* "WIDTH_BUCKET", */ "WITHOUT", /* , "WINDOW", "WITHIN", */ + // @TODO: Missing keywords: XMLAGG, XMLATTRIBUTES, XMLBINARY, XMLCAST, XMLCOMMENT, XMLCONCAT, XMLDOCUMENT, XMLELEMENT, XMLEXISTS + /* "XMLAGG", "XMLATTRIBUTES", "XMLBINARY", "XMLCAST", "XMLCOMMENT", "XMLCONCAT", "XMLDOCUMENT", "XMLELEMENT", "XMLEXISTS", */ + // @TODO: Missing keywords: XMLFOREST, XMLITERATE, XMLNAMESPACES, XMLPARSE, XMLPI, XMLQUERY, XMLSERIALIZE, XMLTABLE, XMLTEXT + /* "XMLFOREST", "XMLITERATE", "XMLNAMESPACES", "XMLPARSE", "XMLPI", "XMLQUERY", "XMLSERIALIZE", "XMLTABLE", "XMLTEXT", */ + // @TODO: Missing keyword: XMLVALIDATE + /* "XMLVALIDATE" */ + + // Keywords which appear in the SQL Server 2000 documentation but not in newer versions. + // Preserved for backwards compatibility purposes. + "DUMMY" + ]), + new SqlCommandCollection("Users", 1, 1, null, null, "select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null))", + [new Restriction(1, "User_Name", "@Name")]), + new SqlCommandCollection("Databases", 1, 1, null, "09.99.999.9", "select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))", + [new Restriction(1, "Name", "@Name")]), + new SqlCommandCollection("Databases", 1, 1, "10.00.000.0", null, "IF OBJECT_ID('master..sysdatabases') IS NULL EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name ELSE EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name", + [new Restriction(1, "Name", "@Name")]), + new SqlCommandCollection("Tables", 4, 3, null, null, "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE from INFORMATION_SCHEMA.TABLES where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Name or (@Name is null)) and (TABLE_TYPE = @TableType or (@TableType is null))", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Name"), + new Restriction(4, "TableType", "@TableType")]), + new SqlCommandCollection("Columns", 4, 4, null, "09.99.999.9", "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG from INFORMATION_SCHEMA.COLUMNS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Column", "@Column")]), + new SqlCommandCollection("Columns", 4, 4, "10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 0", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Column", "@Column")]), + new SqlCommandCollection("AllColumns", 4, 4, "10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 1", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Column", "@Column")]), + new SqlCommandCollection("ColumnSetColumns", 3, 3, "10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, null, 2", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table")]), + new SqlCommandCollection("StructuredTypeMembers", 4, 4, "10.00.000.0", null, "SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Type", "@Type"), + new Restriction(4, "Member", "@Member")]), + new SqlCommandCollection("Views", 3, 3, "08.00.000.0", null, "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE from INFORMATION_SCHEMA.VIEWS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table")]), + new SqlCommandCollection("ViewColumns", 4, 4, "08.00.000.0", null, "select VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where (VIEW_CATALOG = @Catalog or (@Catalog is null)) and (VIEW_SCHEMA = @Owner or (@Owner is null)) and (VIEW_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Column", "@Column")]), + new SqlCommandCollection("ProcedureParameters", 4, 1, "08.00.0000", null, "select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, CASE WHEN DATA_TYPE IS NULL THEN USER_DEFINED_TYPE_NAME WHEN DATA_TYPE = 'table type' THEN USER_DEFINED_TYPE_NAME ELSE DATA_TYPE END as DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, COLLATION_CATALOG, COLLATION_SCHEMA, COLLATION_NAME, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION from INFORMATION_SCHEMA.PARAMETERS where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (PARAMETER_NAME = @Parameter or (@Parameter is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, PARAMETER_NAME", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Name", "@Name"), + new Restriction(4, "Parameter", "@Parameter")]), + new SqlCommandCollection("Procedures", 4, 3, "08.00.0000", null, "select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_CATALOG, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED, LAST_ALTERED from INFORMATION_SCHEMA.ROUTINES where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (ROUTINE_TYPE = @Type or (@Type is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Name", "@Name"), + new Restriction(4, "Type", "@Type")]), + new SqlCommandCollection("IndexColumns", 5, 4, null, "09.99.9999", "select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, column_name = c.name, ordinal_position = convert(int, xk.keyno), KeyType = c.xtype, index_name = x.name from sysobjects o, sysindexes x, syscolumns c, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = c.id and o.id = xk.id and x.indid = xk.indid and c.colid = xk.colid and xk.keyno < = x.keycnt and permissions(o.id, c.name) <> 0 and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @ConstraintName or (@ConstraintName is null)) and (c.name = @Column or (@Column is null)) order by table_name, index_name", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "ConstraintName", "@ConstraintName"), + new Restriction(5, "Column", "@Column")]), + new SqlCommandCollection("IndexColumns", 5, 4, "10.00.0000", null, "EXEC sys.sp_indexcolumns_managed @Catalog, @Owner, @Table, @ConstraintName, @Column", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "ConstraintName", "@ConstraintName"), + new Restriction(5, "Column", "@Column")]), + new SqlCommandCollection("Indexes", 4, 3, null, "09.99.9999", "select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, index_name = x.name from sysobjects o, sysindexes x, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = xk.id and x.indid = xk.indid and xk.keyno < = x.keycnt and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @Name or (@Name is null)) order by table_name, index_name", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Name", "@Name")]), + new SqlCommandCollection("Indexes", 4, 3, "10.00.0000", null, "EXEC sys.sp_indexes_managed @Catalog, @Owner, @Table, @Name", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Name", "@Name")]), + new SqlCommandCollection("UserDefinedTypes", 2, 1, "09.00.0000", null, "select assemblies.name as assembly_name, types.assembly_class as udt_name, ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, is_fixed_length, max_length, Create_Date, Permission_set_desc from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id where (assemblies.name = @AssemblyName or (@AssemblyName is null)) and (types.assembly_class = @UDTName or (@UDTName is null))", + [new Restriction(1, "assembly_name", "@AssemblyName"), + new Restriction(2, "udt_name", "@UDTName")]), + new SqlCommandCollection("ForeignKeys", 4, 3, null, null, "select CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_TYPE, IS_DEFERRABLE, INITIALLY_DEFERRED from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where (CONSTRAINT_CATALOG = @Catalog or (@Catalog is null)) and (CONSTRAINT_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (CONSTRAINT_NAME = @Name or (@Name is null)) and CONSTRAINT_TYPE = 'FOREIGN KEY' order by CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME", + [new Restriction(1, "Catalog", "@Catalog"), + new Restriction(2, "Owner", "@Owner"), + new Restriction(3, "Table", "@Table"), + new Restriction(4, "Name", "@Name")]), + new SqlCommandCollection("TVPs", 0, 0, "10.00.0000", null, @"select name TypeName, 30 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, null IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 0 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.types where is_table_type = 1", null), + new SqlCommandCollection("UDTs", 0, 0, "09.00.0000", null, @"select types.assembly_class COLLATE database_default + ', ' + assemblies.name + ', Version=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision')) + ISNULL(', Culture=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo')),'') + ISNULL(', PublicKeyToken=' + LOWER(REPLACE(CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'PublicKey'),1),'0x','')),'') TypeName, 29 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, is_fixed_length IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 1 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id", null), + ]; + #pragma warning restore format + + // 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 static readonly HashSet s_assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; - private readonly DataSet _collectionDataSet; private readonly string _serverVersion; - public SqlMetaDataFactory(Stream xmlStream, string serverVersion) + public SqlMetaDataFactory(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) => @@ -169,741 +288,136 @@ public async Task GetSchemaAsync(DbConnection connection, string coll public async ValueTask GetSchemaCore(DbConnection connection, string collectionName, string[] restrictions, bool isAsync, CancellationToken cancellationToken) { - const string DataTableKey = "DataTable"; - const string SqlCommandKey = "SQLCommand"; - const string PrepareCollectionKey = "PrepareCollection"; - - Debug.Assert(_collectionDataSet is not null); - cancellationToken.ThrowIfCancellationRequested(); - - 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(); - } - } - } - cancellationToken.ThrowIfCancellationRequested(); - 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 = await ExecuteCommandAsync(requestedCollectionRow, restrictions, connection, isAsync, cancellationToken); - break; - - case PrepareCollectionKey: - requestedSchema = await PrepareCollectionAsync(exactCollectionName, restrictions, connection, isAsync, cancellationToken); - break; - - default: - throw ADP.UndefinedPopulationMechanism(populationMechanism); - } + MetaDataCollection metadataRoot = s_metaDataCollection[0] as MetaDataCollection; + // We expect first element of s_metaDataCollection to be an instance of MetaDataCollection + Debug.Assert(metadataRoot != null); + DataTable schema = await metadataRoot.GetMetadata(collectionName, new MetaDataContext(_serverVersion, restrictions, connection, isAsync, cancellationToken)); - return requestedSchema; + return schema; } - 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) + + internal sealed class MetaDataContext { - DataTable destinationTable; - DataColumn[] filteredSourceColumns; - DataColumnCollection destinationColumns; - DataRow newRow; - - DataTable sourceTable = _collectionDataSet.Tables[collectionName]; - - if (sourceTable?.TableName != collectionName) - { - throw ADP.DataTableDoesNotExist(collectionName); + public readonly string ServerVersion; + public readonly string[] RestrictionValues; + public readonly DbConnection Connection; + public readonly bool IsAsync = false; + public readonly CancellationToken CancellationToken; + + internal MetaDataContext(string serverVersion, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) + { + ServerVersion = serverVersion; + RestrictionValues = restrictions; + Connection = connection; + IsAsync = isAsync; + CancellationToken = cancellationToken; } - - 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) + internal sealed class Restriction { - 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))) - { + public readonly string RestrictionName; + public readonly string ParameterName; + public readonly int RestrictionNumber; - result = (string)restriction[parameterName]; - break; - } - } - - if (result is null) + internal Restriction(int restrictionNumber, string restrictionName, string parameterName) { - throw ADP.MissingRestrictionRow(); + RestrictionName = restrictionName; + ParameterName = parameterName; + RestrictionNumber = restrictionNumber; } - - return result; } - private async ValueTask ExecuteCommandAsync(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) + internal abstract class MetaDataCollectionBase { - 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); + public readonly string CollectionName; + public readonly int NumberOfRestrictions; + public readonly int NumberOfIdentifierParts; + private readonly string _minimumVersion; + private readonly string _maximumVersion; + + internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, string minimumVersion = null, string maximumVersion = null) + { + CollectionName = collectionName; + NumberOfRestrictions = numberOfRestrictions; + NumberOfIdentifierParts = numberOfIdentifierParts; + _minimumVersion = minimumVersion; + _maximumVersion = maximumVersion; } - 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; - } + public abstract ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null); - restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); - restrictionParameter.Direction = ParameterDirection.Input; - command.Parameters.Add(restrictionParameter); - } + public bool SupportedByCurrentVersion(string serverVersion) => + (_minimumVersion == null || string.Compare(serverVersion, _minimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && + (_maximumVersion == null || string.Compare(serverVersion, _maximumVersion, StringComparison.OrdinalIgnoreCase) <= 0); - SqlDataReader reader = null; - try + protected MetaDataCollectionBase FindMetaDataCollection(string collectionName, string serverVersion) { - try - { - reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken) : command.ExecuteReader(); - } - catch (Exception e) when (ADP.IsCatchableExceptionType(e)) - { - throw ADP.QueryFailed(collectionName, e); - } - - // Build a DataTable from the reader - resultTable = new DataTable(collectionName) - { - Locale = CultureInfo.InvariantCulture - }; - - // reader.GetColumnSchema waits synchronously for the reader to receive its type metadata. - // Instead, we invoke reader.ReadAsync outside of the while loop, which will implicitly ensure that - // the metadata is available. ReadAsync/Read will throw an exception if necessary, so we can trust - // that the list of fields is available if the call returns. - bool firstResultAvailable = isAsync ? await reader.ReadAsync(cancellationToken) : reader.Read(); - System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); - - foreach (DbColumn col in colSchema) - { - resultTable.Columns.Add(col.ColumnName, col.DataType); - } + bool versionFailure = false; + bool haveExactMatch = false; + bool haveMultipleInexactMatches = false; + string exactCollectionName = null; + MetaDataCollectionBase requestedCollection = null; - if (firstResultAvailable) + foreach (MetaDataCollectionBase metaData in s_metaDataCollection) { - object[] values = new object[resultTable.Columns.Count]; - do + if (string.Equals(metaData.CollectionName, collectionName, StringComparison.InvariantCultureIgnoreCase)) { - reader.GetValues(values); - resultTable.Rows.Add(values); - } while (isAsync ? await reader.ReadAsync(cancellationToken) : reader.Read()); - } - } - finally - { - reader?.Dispose(); - } - return resultTable; - } - #endregion - - #region GetSchema Helpers: PrepareCollection Population Method - private static async ValueTask AddUDTsToDataTypesTableAsync(DataTable dataTypesTable, SqlConnection connection, string serverVersion, bool isAsync, CancellationToken cancellationToken) - { - const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; - const string ListUdtSqlCommand = - "select " + - "assemblies.name, " + - "types.assembly_class, " + - "ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, " + - "ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, " + - "ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, " + - "ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, " + - "ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, " + - "ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, " + - "is_nullable, " + - "is_fixed_length, " + - "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)) - { - return; - } - - using SqlCommand command = connection.CreateCommand(); - - command.CommandText = GetEngineEditionSqlCommand; - int engineEdition = (int)(isAsync ? await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteScalar()); - - 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) - // do not support ASSEMBLYPROPERTY - return; - } - - DataColumn providerDbType = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; - DataColumn columnSize = dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize]; - DataColumn isFixedLength = dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedLength]; - DataColumn isSearchable = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable]; - DataColumn isLiteralSupported = dataTypesTable.Columns[DbMetaDataColumnNames.IsLiteralSupported]; - DataColumn typeName = dataTypesTable.Columns[DbMetaDataColumnNames.TypeName]; - DataColumn isNullable = dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable]; - - 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"); - - // Execute the SELECT statement - command.CommandText = ListUdtSqlCommand; - - using SqlDataReader reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader(); - object[] values = new object[11]; - - while (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read()) - { - reader.GetValues(values); - DataRow newRow = dataTypesTable.NewRow(); - - newRow[providerDbType] = SqlDbType.Udt; - newRow[columnSize] = values[ColumnSizeSqlIndex]; - - if (values[IsFixedLengthSqlIndex] != DBNull.Value) - { - newRow[isFixedLength] = values[IsFixedLengthSqlIndex]; - } - - newRow[isSearchable] = true; - newRow[isLiteralSupported] = false; - if (values[IsNullableSqlIndex] != DBNull.Value) - { - newRow[isNullable] = values[IsNullableSqlIndex]; + if (!metaData.SupportedByCurrentVersion(serverVersion)) + { + versionFailure = true; + } + else if (collectionName == metaData.CollectionName) + { + if (haveExactMatch) + { + throw ADP.CollectionNameIsNotUnique(collectionName); + } + requestedCollection = metaData; + exactCollectionName = metaData.CollectionName; + 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; + } + requestedCollection = metaData; + exactCollectionName = metaData.CollectionName; + } + } } - if ((values[AssemblyClassSqlIndex] != DBNull.Value) && - (values[VersionMajorSqlIndex] != DBNull.Value) && - (values[VersionMinorSqlIndex] != DBNull.Value) && - (values[VersionBuildSqlIndex] != DBNull.Value) && - (values[VersionRevisionSqlIndex] != DBNull.Value)) + if (requestedCollection is null) { - - StringBuilder nameString = new(); - nameString.Append($"{values[AssemblyClassSqlIndex]}, {values[AssemblyNameSqlIndex]}" - + $", Version={values[VersionMajorSqlIndex]}.{values[VersionMinorSqlIndex]}.{values[VersionBuildSqlIndex]}.{values[VersionRevisionSqlIndex]}"); - - if (values[CultureInfoSqlIndex] != DBNull.Value) + if (!versionFailure) { - nameString.Append($", Culture={values[CultureInfoSqlIndex]}"); + throw ADP.UndefinedCollection(collectionName); } - - if (values[PublicKeySqlIndex] != DBNull.Value) + else { - - nameString.Append(", PublicKeyToken="); - - 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(string.Format("{0,-2:x2}", b)); - } -#endif + //throw ADP.UnsupportedVersion(collectionName); } - - newRow[typeName] = nameString.ToString(); - dataTypesTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } // if assembly name - - cancellationToken.ThrowIfCancellationRequested(); - }//end while - } - - private static async ValueTask AddTVPsToDataTypesTableAsync(DataTable dataTypesTable, SqlConnection connection, string serverVersion, bool isAsync, CancellationToken cancellationToken) - { - 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)) - { - return; - } - - // Execute the SELECT statement - 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]; - - 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"); - - using SqlDataReader reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader(); - - object[] values = new object[11]; - while (isAsync ? await reader.ReadAsync(cancellationToken) : reader.Read()) - { - - reader.GetValues(values); - DataRow newRow = dataTypesTable.NewRow(); - - newRow[providerDbType] = SqlDbType.Structured; - newRow[columnSize] = values[ColumnSizeSqlIndex]; - newRow[isSearchable] = false; - newRow[isLiteralSupported] = false; - - if (values[IsNullableSqlIndex] != DBNull.Value) - { - newRow[isNullable] = values[IsNullableSqlIndex]; - } - - newRow[typeName] = values[TypeNameSqlIndex]; - - dataTypesTable.Rows.Add(newRow); - newRow.AcceptChanges(); - - cancellationToken.ThrowIfCancellationRequested(); - }//end while - } - - private async ValueTask GetDataTypesTable(SqlConnection connection, bool isAsync, CancellationToken cancellationToken) - { - // verify the existence of the table in the data set - 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 - DataTable dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, []); - - await AddUDTsToDataTypesTableAsync(dataTypesTable, connection, _serverVersion, isAsync, cancellationToken).ConfigureAwait(false); - await AddTVPsToDataTypesTableAsync(dataTypesTable, connection, _serverVersion, isAsync, cancellationToken).ConfigureAwait(false); - - dataTypesTable.AcceptChanges(); - return dataTypesTable; - } - - private async ValueTask PrepareCollectionAsync(string collectionName, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) - { - SqlConnection sqlConnection = (SqlConnection)connection; - DataTable resultTable = null; - - if (collectionName == DbMetaDataCollectionNames.DataTypes) - { - if (ADP.IsEmptyArray(restrictions) == false) - { - throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes); } - resultTable = await GetDataTypesTable(sqlConnection, isAsync, cancellationToken).ConfigureAwait(false); - } - if (resultTable == null) - { - throw ADP.UnableToBuildCollection(collectionName); - } - - return resultTable; - } - #endregion - - #region Create MetaDataCollections DataSet from XML - private DataSet LoadDataSetFromXml(Stream XmlStream) - { - DataSet metaDataCollectionsDataSet = new("NewDataSet") - { - Locale = CultureInfo.InvariantCulture - }; - - LoadDataTypesDataTables(metaDataCollectionsDataSet); - - 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) + if (!haveExactMatch && haveMultipleInexactMatches) { - case "MetaDataCollectionsTable": - dataTable = CreateMetaDataCollectionsDataTable(); - break; - case "RestrictionsTable": - dataTable = CreateRestrictionsDataTable(); - break; - case "DataSourceInformationTable": - dataTable = CreateDataSourceInformationDataTable(); - rowFixup = FixUpDataSourceInformationRow; - break; - case "ReservedWordsTable": - dataTable = CreateReservedWordsDataTable(); - break; - default: - Debug.Fail($"Unexpected table element name: {reader.Name}"); - break; + throw ADP.AmbiguousCollectionName(collectionName); } - if (dataTable is not null) - { - LoadDataTable(reader, dataTable, rowFixup); - - metaDataCollectionsDataSet.Tables.Add(dataTable); - } + return requestedCollection; } - - 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 CreateReservedWordsDataTable() - => new(DbMetaDataCollectionNames.ReservedWords) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index 041b9aa8aa..de8fe172de 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -1914,33 +1914,6 @@ internal static string MDF_DataTableDoesNotExist { } } - /// - /// Looks up a localized string similar to The metadata XML is invalid. The {1} column of the {0} collection must contain a non-empty string.. - /// - internal static string MDF_InvalidXmlInvalidValue { - get { - return ResourceManager.GetString("MDF_InvalidXmlInvalidValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A restriction exists for which there is no matching row in the restrictions collection.. - /// - internal static string MDF_MissingRestrictionRow { - get { - return ResourceManager.GetString("MDF_MissingRestrictionRow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The schema table contains no columns.. - /// - internal static string MDF_NoColumns { - get { - return ResourceManager.GetString("MDF_NoColumns", resourceCulture); - } - } - /// /// Looks up a localized string similar to Unable to build the '{0}' collection because execution of the SQL query failed. See the inner exception for details.. /// @@ -1959,15 +1932,6 @@ internal static string MDF_TooManyRestrictions { } } - /// - /// Looks up a localized string similar to Unable to build schema collection '{0}';. - /// - internal static string MDF_UnableToBuildCollection { - get { - return ResourceManager.GetString("MDF_UnableToBuildCollection", resourceCulture); - } - } - /// /// Looks up a localized string similar to The requested collection ({0}) is not defined.. /// @@ -1977,15 +1941,6 @@ internal static string MDF_UndefinedCollection { } } - /// - /// Looks up a localized string similar to The population mechanism '{0}' is not defined.. - /// - internal static string MDF_UndefinedPopulationMechanism { - get { - return ResourceManager.GetString("MDF_UndefinedPopulationMechanism", resourceCulture); - } - } - /// /// Looks up a localized string similar to The requested collection ({0}) is not supported by this version of the provider.. /// diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx index 65914fdf4c..6437cb20b1 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ Požadovaná kolekce ({0}) není definována. - - Mechanismus populace dat {0} není definován. - Požadovaná kolekce ({0}) není podporována touto verzí zprostředkovatele. - - Existuje omezení, pro které v kolekci omezení neexistuje odpovídající řádek. - - - Tabulka schématu neobsahuje žádné sloupce. - - - Nelze vytvořit kolekci schématu {0}; - Název kolekce {0} odpovídá nejméně dvěma kolekcím, pokud nejsou rozlišována malá a velká písmena, ale neodpovídá ani jedné z nich, pokud malá a velká písmena rozlišována jsou. @@ -276,9 +264,6 @@ V kódu XML metadat chybí kolekce {0}. - - Jazyk XML metadat je neplatný. Sloupec {1} kolekce {0} musí obsahovat neprázdný řetězec. - Výplň diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx index fcbac77cb7..89e9ae2945 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ Die angeforderte Sammlung ({0}) ist nicht definiert. - - Der Auffüllmechanismus '{0}' ist nicht definiert. - Die angeforderte Sammlung ({0}) wird von dieser Version des Providers nicht unterstützt. - - Es liegt eine Einschränkung vor, für die es keine übereinstimmende Zeile in der Sammlung der Einschränkungen gibt. - - - Die Schematabelle enthält keine Spalten. - - - Die Schema-Sammlung '{0}' konnte nicht erstellt werden. - Der Sammlungsname '{0}' stimmt mit mindestens zwei Sammlungen mit demselben Namen, jedoch anderer Schreibung überein, ohne aber mit diesen exakt übereinzustimmen. @@ -276,9 +264,6 @@ Die Sammlung '{0}' fehlt im Metadaten-XML-Code. - - Der Metadaten-XML-Code ist ungültig. Die Spalte {1} der Sammlung {0} muss eine nicht leere Zeichenfolge enthalten. - Füllen diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx index e62177e353..ffffa3ff8b 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ La colección solicitada ({0}) no está definida. - - El mecanismo de llenado '{0}' no está definido. - La colección solicitada ({0}) no es compatible con esta versión del proveedor. - - Existe una restricción para la que no hay fila coincidente en la colección de restricciones. - - - La tabla de esquema no contiene columnas. - - - No se puede compilar la colección de esquemas '{0}'; - El nombre de colección '{0}' coincide al menos con dos colecciones con el mismo nombre pero con diferente caso, pero no coincide exactamente con ninguna de ellas. @@ -276,9 +264,6 @@ Falta la colección '{0}' del XML de metadatos. - - XML de metadatos no válido. La columna {1} de la colección {0} debe contener una cadena que no esté vacía. - Relleno diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx index f175dbf816..42021b3167 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ La collection demandée ({0}) n'est pas définie. - - Le mécanisme de remplissage '{0}' n'est pas défini. - La collection demandée ({0}) n'est pas prise en charge par cette version du fournisseur. - - Il existe une restriction sans ligne correspondante dans la collection de restrictions. - - - La table de schémas ne comporte aucune colonne. - - - Impossible de créer la collection de schémas '{0}' ; - Le nom de collection '{0}' correspond au moins à deux collections dotées du même nom, mais de casse différente, mais cette correspondance n'est pas précise. @@ -276,9 +264,6 @@ La collection '{0}' est manquante dans le XML de métadonnées. - - Le XML de métadonnées n'est pas valide. La colonne {1} de la collection {0} doit comporter une chaîne non vide. - Remplissage diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx index 129310c5eb..e57bf95826 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ La raccolta richiesta ({0}) non è definita. - - Il meccanismo di popolamento "{0}" non è definito. - La raccolta richiesta ({0}) non è supportata da questa versione del provider. - - Esiste una restrizione per la quale non c'è nessuna riga corrispondente nella raccolta di restrizioni. - - - La tabella dello schema non contiene colonne. - - - Impossibile compilare la raccolta di schemi '{0}'; - Il nome della raccolta "{0}" corrisponde ad almeno due raccolte i cui nomi differiscono solo per l'uso delle maiuscole, ma non corrisponde a nessuno dei due esattamente. @@ -276,9 +264,6 @@ Dai metadati XML manca la raccolta "{0}". - - I metadati XML non sono validi. La colonna {1} della raccolta {0} deve contenere una stringa non vuota. - Riempimento diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx index f8b67f7cba..f0ec1cd6d1 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ 要求されたコレクション ({0}) は定義されていません。 - - ポピュレーション機構 '{0}' が定義されていません。 - 要求されたコレクション ({0}) は、プロバイダーのこのバージョンではサポートされていません。 - - 制約コレクション内の行に一致しない制約があります。 - - - スキーマ テーブルに列がありません。 - - - スキーマ コレクション '{0}' を構築できません。 - コレクション名 '{0}' と同じ名前のコレクションが少なくとも 2 つありますが、大文字と小文字が一致しておらず、このどちらとも完全には名前が一致していません。 @@ -276,9 +264,6 @@ メタデータ XML に '{0}' がありません。 - - メタデータの XML が無効です。{0} コレクションの {1} 列には、空でない文字列が設定されている必要があります。 - 塗りつぶし diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx index a3a6bc51e4..5abb7434de 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ 요청한 컬렉션({0})이 정의되지 않았습니다. - - 채우기 메커니즘 '{0}'이(가) 정의되지 않았습니다. - 요청한 컬렉션({0})은 이 버전의 공급자에서 지원되지 않습니다. - - 제한 컬렉션에 일치하는 행이 없는 제한이 있습니다. - - - 스키마 테이블에 열이 없습니다. - - - 스키마 컬렉션 '{0}'을(를) 작성할 수 없습니다. - 컬렉션 이름 '{0}'이(가) 이름은 같지만 사례는 다른 둘 이상의 컬렉션과 일치합니다. 그러나 모두 정확하게 일치하지는 않습니다. @@ -276,9 +264,6 @@ 컬렉션 '{0}'이(가) 메타데이터 XML에 없습니다. - - 메타데이터 XML이 잘못되었습니다. {0} 컬렉션의 {1} 열에는 비어 있지 않은 문자열이 있어야 합니다. - 채우기 diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx index eeb6d69665..53c6d35963 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ Żądana kolekcja ({0}) nie została zdefiniowana. - - Mechanizm populacji {0} nie został zdefiniowany. - Żądana kolekcja ({0}) nie jest obsługiwana przez tę wersję dostawcy. - - Istnieje ograniczenie, dla którego nie ma pasującego wiersza w kolekcji ograniczeń. - - - Tabela schematów nie zawiera żadnych kolumn. - - - Nie można utworzyć kolekcji schematów {0}; - Nazwa kolekcji {0} pasuje do co najmniej dwóch kolekcji o tej samej nazwie, jeśli wielkość liter nie jest uwzględniana; jeśli wielkość liter jest uwzględniana, nazwa kolekcji nie pasuje do żadnej z nich. @@ -276,9 +264,6 @@ Brak kolekcji {0} w elemencie XML metadanych. - - Nieprawidłowy element XML metadanych. Kolumna {1} kolekcji {0} musi zawierać niepusty ciąg. - Wypełnij diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx index 5873d61783..8e26a97235 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ Coleção solicitada ({0}) não definida. - - Mecanismo de preenchimento '{0}' não definido. - Esta versão do provedor não dá suporte à coleção solicitada ({0}). - - Há uma restrição sem linha correspondente na coleção de restrições. - - - A tabela de esquema não contém colunas. - - - Não é possível criar a coleção de esquemas '{0}'; - O nome de coleção '{0}' corresponde a pelo menos duas coleções com o mesmo nome, mas com diferença em maiúsculas e minúsculas, mas não corresponde a qualquer uma delas exatamente. @@ -276,9 +264,6 @@ Falta a coleção '{0}' no XML de metadados. - - O XML de metadados é inválido. A coluna {1} da coleção {0} deve conter uma cadeia de caracteres não vazia. - Preenchimento diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index ca9dcf0a93..a31ad83ee3 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -1,17 +1,17 @@ - @@ -252,21 +252,9 @@ The requested collection ({0}) is not defined. - - The population mechanism '{0}' is not defined. - The requested collection ({0}) is not supported by this version of the provider. - - A restriction exists for which there is no matching row in the restrictions collection. - - - The schema table contains no columns. - - - Unable to build schema collection '{0}'; - The collection name '{0}' matches at least two collections with the same name but with different case, but does not match any of them exactly. @@ -276,9 +264,6 @@ The collection '{0}' is missing from the metadata XML. - - The metadata XML is invalid. The {1} column of the {0} collection must contain a non-empty string. - Fill @@ -2208,4 +2193,4 @@ The type '{0}' has an invalid size. - + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx index 9d1fcbc6f1..d8363bef96 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ Затребованная коллекция ({0}) не определена. - - Механизм заполнения "{0}" не определен. - Затребованная коллекция ({0}) не поддерживается поставщиком этой версии. - - Для одного из ограничений нет соответствующей строки в коллекции ограничений. - - - Таблица схемы не содержит столбцов. - - - Не удается создать коллекцию схемы "{0}"; - Имя коллекции "{0}" без учета регистра совпадает по меньшей мере с двумя именами коллекций, но с учетом регистра не совпадает ни с одним из них. @@ -276,9 +264,6 @@ Коллекции "{0}" нет в метаданных XML. - - Метаданные XML недействительны. Столбец {1} коллекции {0} должен содержать непустую строку. - Заливка diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx index 65a0046cfb..c4fe1df7cf 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ İstenen koleksiyon ({0}) tanımlı değil. - - Topluluk mekanizması '{0}' tanımlı değil. - İstenen ({0}) koleksiyonu, sağlayıcının bu sürümü tarafından desteklenmiyor. - - Kısıtlamalar koleksiyonunda kendisiyle eşleşen bir satır olmayan bir kısıtlama var. - - - Şema tablosunda hiç sütun yok. - - - '{0}' şema koleksiyonu oluşturulamadı; - '{0}' koleksiyon adı, adı aynı olup farklı büyük/küçük harf biçimine sahip en az iki koleksiyonla eşleşiyor, ancak hiçbiriyle tam olarak eşleşmiyor. @@ -276,9 +264,6 @@ Meta veri XML’inde '{0}' koleksiyonu eksik. - - Meta veri XML’i geçersiz. {0} koleksiyonunun {1} sütunu boş olmayan bir dize içermeli. - Doldur diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx index 8f881105c9..55be69d767 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ 请求的集合({0})未定义。 - - 填充机制“{0}”未定义。 - 此版本的提供程序不支持请求的集合 ({0})。 - - 限制集合中存在没有匹配行的限制。 - - - 架构表未包含列。 - - - 无法构建架构集合“{0}”; - 集合名“{0}”至少与两个同名但是大小写不同的集合匹配,但是不与任何一个完全匹配。 @@ -276,9 +264,6 @@ 元数据 XML 中缺少集合“{0}”。 - - 元数据 XML 无效。{0} 集合的 {1} 列必须包含非空字符串。 - 填充 diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx index 2081722fb7..9c5792a769 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx @@ -1,17 +1,17 @@ - + - @@ -252,21 +252,9 @@ 未定義必要的集合 ({0})。 - - 未定義擴展機制 '{0}'。 - 提供者的版本不支援要求的集合 ({0})。 - - 有一項限制為限制集合中沒有相符的資料列。 - - - 結構描述資料表沒有包含資料行。 - - - 無法建置結構描述 '{0}'; - 集合名稱 '{0}' 至少與兩個同名但大小寫不同的名稱相符,但與其中任一個均不完全相符。 @@ -276,9 +264,6 @@ 集合 '{0}' 已從中繼資料 XML 中遺失。 - - 中繼資料 XML 無效。{0} 集合的 {1} 資料行必須包含非空的字串。 - 填滿 From 63261de43c7439e23ffa93a93f739e80061d271a Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Mon, 15 Jun 2026 15:44:55 +0300 Subject: [PATCH 2/8] small code cleanups --- .../Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs | 3 --- .../Data/SqlClient/SqlMetaDataFactory.Restrictions.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs index dc950fe29f..9d9194a622 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections; using System.Data; using System.Data.Common; -using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Microsoft.Data.Common; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs index 63e460dbb6..e4fdeffb3f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Data.Common; From 89439272c052946579eed92582e7ea2f8f837215 Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Tue, 16 Jun 2026 20:35:22 +0300 Subject: [PATCH 3/8] implementing some of the PR review suggestions --- .../Data/SqlClient/SqlConnectionFactory.cs | 6 +- ...qlMetaDataFactory.DataSourceInformation.cs | 171 +++++++-------- .../SqlClient/SqlMetaDataFactory.DataTypes.cs | 118 ++++++++-- .../SqlMetaDataFactory.MetaDataCollections.cs | 7 +- .../SqlMetaDataFactory.ReservedWords.cs | 164 +++++++++++--- .../Data/SqlClient/SqlMetaDataFactory.cs | 205 +++--------------- 6 files changed, 350 insertions(+), 321 deletions(-) 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 db175f7f58..96b667cc5c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -945,7 +945,7 @@ private void TryGetConnectionCompletedContinuation(Task ta } } -#if NET + #if NET private void Unload(object sender, EventArgs e) { try @@ -968,9 +968,9 @@ private void SubscribeToAssemblyLoadContextUnload() AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlConnectionFactoryAssemblyLoadContext_Unloading; } -#endif + #endif -#endregion + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs index f7d67f134f..6aeee92612 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs @@ -7,115 +7,90 @@ using System.Threading.Tasks; using Microsoft.Data.Common; -namespace Microsoft.Data.SqlClient +#nullable enable + +namespace Microsoft.Data.SqlClient; + +internal sealed partial class SqlMetaDataFactory { - internal sealed partial class SqlMetaDataFactory + private sealed class DataSourceInformationCollection : MetaDataCollectionBase { - private sealed class DataSourceInformationCollection : MetaDataCollectionBase + private const string CompositeIdentifierSeparatorPattern = "\\."; + private const string DataSourceProductName = "Microsoft SQL Server"; + private const GroupByBehavior GroupByBehavior = GroupByBehavior.Unrelated; + private const string IdentifierPattern = @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)"; + private const IdentifierCase IdentifierCase = IdentifierCase.Insensitive; + private const bool OrderByColumnsInSelect = false; + private const string _parameterMarkerFormat = "{0}"; + private const string ParameterMarkerPattern = @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)"; + private const int ParameterNameMaxLength = 128; + private const string ParameterNamePattern = @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)"; + private const string QuotedIdentifierPattern = "(([^\\[]|\\]\\])*)"; + private const IdentifierCase QuotedIdentifierCase = IdentifierCase.Insensitive; + private const string StatementSeparatorPattern = ";"; + private const string StringLiteralPattern = "'(([^']|'')*)'"; + private const SupportedJoinOperators SupportedJoinOperators = SupportedJoinOperators.Inner + | SupportedJoinOperators.LeftOuter + | SupportedJoinOperators.RightOuter + | SupportedJoinOperators.FullOuter; + + internal DataSourceInformationCollection() + : base(DbMetaDataCollectionNames.DataSourceInformation, 0, 0) { - private readonly string _compositeIdentifierSeparatorPattern; - private readonly string _dataSourceProductName; - private readonly GroupByBehavior _groupByBehavior; - private readonly string _identifierPattern; - private readonly IdentifierCase _identifierCase; - private readonly bool _orderByColumnsInSelect; - private readonly string _parameterMarkerFormat; - private readonly string _parameterMarkerPattern; - private readonly int _parameterNameMaxLength; - private readonly string _parameterNamePattern; - private readonly string _quotedIdentifierPattern; - private readonly IdentifierCase _quotedIdentifierCase; - private readonly string _statementSeparatorPattern; - private readonly string _stringLiteralPattern; - private readonly SupportedJoinOperators _supportedJoinOperators; + } - internal DataSourceInformationCollection(string CompositeIdentifierSeparatorPattern, - string DataSourceProductName, - GroupByBehavior GroupByBehavior, - string IdentifierPattern, - IdentifierCase IdentifierCase, - bool OrderByColumnsInSelect, - string ParameterMarkerFormat, - string ParameterMarkerPattern, - int ParameterNameMaxLength, - string ParameterNamePattern, - string QuotedIdentifierPattern, - IdentifierCase QuotedIdentifierCase, - string StatementSeparatorPattern, - string StringLiteralPattern, - SupportedJoinOperators SupportedJoinOperators) - : base(DbMetaDataCollectionNames.DataSourceInformation, 0, 0) + public override ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null) + { + if (!ADP.IsEmptyArray(context.RestrictionValues)) { - this._compositeIdentifierSeparatorPattern = CompositeIdentifierSeparatorPattern; - this._dataSourceProductName = DataSourceProductName; - this._groupByBehavior = GroupByBehavior; - this._identifierPattern = IdentifierPattern; - this._identifierCase = IdentifierCase; - this._orderByColumnsInSelect = OrderByColumnsInSelect; - this._parameterMarkerFormat = ParameterMarkerFormat; - this._parameterMarkerPattern = ParameterMarkerPattern; - this._parameterNameMaxLength = ParameterNameMaxLength; - this._parameterNamePattern = ParameterNamePattern; - this._quotedIdentifierPattern = QuotedIdentifierPattern; - this._quotedIdentifierCase = QuotedIdentifierCase; - this._statementSeparatorPattern = StatementSeparatorPattern; - this._stringLiteralPattern = StringLiteralPattern; - this._supportedJoinOperators = SupportedJoinOperators; + throw ADP.TooManyRestrictions(CollectionName); } - public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + DataTable table = accumulator ?? new(DbMetaDataCollectionNames.DataSourceInformation) { - if (!ADP.IsEmptyArray(context.RestrictionValues)) + Columns = { - throw ADP.TooManyRestrictions(CollectionName); + 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)) } + }; - DataTable table = accumulator ?? 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)) - } - }; - - DataRow row = table.NewRow(); - table.Rows.Add([ - _compositeIdentifierSeparatorPattern, - _dataSourceProductName, - context.ServerVersion, - context.ServerVersion, - _groupByBehavior, - _identifierPattern, - _identifierCase, - _orderByColumnsInSelect, - _parameterMarkerFormat, - _parameterMarkerPattern, - _parameterNameMaxLength, - _parameterNamePattern, - _quotedIdentifierPattern, - _quotedIdentifierCase, - _statementSeparatorPattern, - _stringLiteralPattern, - _supportedJoinOperators - ]); - return new ValueTask(table); - } + DataRow row = table.NewRow(); + table.Rows.Add([ + CompositeIdentifierSeparatorPattern, + DataSourceProductName, + context.ServerVersion, + context.ServerVersion, + GroupByBehavior, + IdentifierPattern, + IdentifierCase, + OrderByColumnsInSelect, + _parameterMarkerFormat, + ParameterMarkerPattern, + ParameterNameMaxLength, + ParameterNamePattern, + QuotedIdentifierPattern, + QuotedIdentifierCase, + StatementSeparatorPattern, + StringLiteralPattern, + SupportedJoinOperators + ]); + return new ValueTask(table); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs index a59e0c0e41..99c6c34911 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs @@ -5,8 +5,10 @@ using System; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.Connection; #nullable enable @@ -16,12 +18,89 @@ internal sealed partial class SqlMetaDataFactory { private sealed class DataTypesCollection : MetaDataCollectionBase { - private readonly TypeMetaData[] _types; + #pragma warning disable format + private static readonly TypeMetaData[] s_types_flat = [ + // Type order follows the order from SqlMetaData.xml + // IsBestMatch isFixedPrecisionScale IsSearchable MaximumScale MinimumVersion LiteralPrefix + // ProviderDbType IsCaseSensitive IsLong IsSearchableWithLike MinimumScale MaximumVersion LiteralSuffix + // TypeName ColumnSize CreateFormat DataType IsAutoIncrementable IsFixedLength IsNullable isUnsigned IsConcurrencyType IsLiteralSupported CreateParameters + new ("smallint" ,16 ,5 ,"smallint" ,"System.Int16" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("int" ,8 ,10 ,"int" ,"System.Int32" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("real" ,13 ,7 ,"real" ,"System.Single" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("float" ,6 ,53 ,"float({0})" ,"System.Double" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,"number of bits used to store the mantissa"), + new ("money" ,9 ,19 ,"money" ,"System.Decimal" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("smallmoney" ,17 ,10 ,"smallmoney" ,"System.Decimal" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("bit" ,2 ,1 ,"bit" ,"System.Boolean" ,false ,false ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("tinyint" ,20 ,3 ,"tinyint" ,"System.Byte" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,true ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("bigint" ,0 ,19 ,"bigint" ,"System.Int64" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new ("timestamp" ,19 ,8 ,"timestamp" ,"System.Byte[]" ,false ,false ,false ,true ,false ,false ,false ,true ,false ,null ,-1 ,-1 ,true ,null ,null ,null ,"0x" ,null ,""), + new ("binary" ,1 ,8000 ,"binary({0})" ,"System.Byte[]" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"length"), + new ("image" ,7 ,2147483647,"image" ,"System.Byte[]" ,false ,true ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,""), + new ("text" ,18 ,2147483647,"text" ,"System.String" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), + new ("ntext" ,11 ,1073741823,"ntext" ,"System.String" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,""), + new ("decimal" ,5 ,38 ,"decimal({0}, {1})" ,"System.Decimal" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), + new ("numeric" ,5 ,38 ,"numeric({0}, {1})" ,"System.Decimal" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), + new ("datetime" ,4 ,23 ,"datetime" ,"System.DateTime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), + new ("smalldatetime" ,15 ,16 ,"smalldatetime" ,"System.DateTime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), + new ("sql_variant" ,23 ,-1 ,"sql_variant" ,"System.Object" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), + new ("xml" ,25 ,2147483647,"xml" ,"System.String" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), + new ("varchar" ,22 ,2147483647,"varchar({0})" ,"System.String" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"max length"), + new ("char" ,3 ,2147483647,"char({0})" ,"System.String" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"length"), + new ("nchar" ,10 ,1073741823,"nchar({0})" ,"System.String" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"length"), + new ("nvarchar" ,12 ,1073741823,"nvarchar({0})" ,"System.String" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"max length"), + new ("varbinary" ,21 ,1073741823,"varbinary({0})" ,"System.Byte[]" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"max length"), + new ("uniqueidentifier",14 ,16 ,"uniqueidentifier" ,"System.Guid" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), + new ("date" ,31 ,3 ,"date" ,"System.DateTime" ,false ,false ,false ,true ,true ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,""), + new ("time" ,32 ,5 ,"time({0})" ,"System.TimeSpan" ,false ,false ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new ("datetime2" ,33 ,8 ,"datetime2({0})" ,"System.DateTime" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new ("datetimeoffset" ,34 ,10 ,"datetimeoffset({0})","System.DateTimeOffset",false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new ("json" ,35 ,2147483647,"json" ,"System.String" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,"17.00.000.0" ,null ,false ,"'" ,"'" ,""), + ]; - internal DataTypesCollection(TypeMetaData[] types) + private static readonly TypeMetaData[] s_types = [ + // Type order follows the order from SqlMetaData.xml + // IsBestMatch isFixedPrecisionScale IsSearchable MaximumScale MinimumVersion LiteralPrefix + // IsCaseSensitive IsLong IsSearchableWithLike MinimumScale MaximumVersion LiteralSuffix + // ColumnSize CreateFormat IsAutoIncrementable IsFixedLength IsNullable isUnsigned IsConcurrencyType IsLiteralSupported CreateParameters + new (SqlDbType.SmallInt ,5 ,"smallint" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.Int ,10 ,"int" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.Real ,7 ,"real" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.Float ,53 ,"float({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,"number of bits used to store the mantissa"), + new (SqlDbType.Money ,19 ,"money" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.SmallMoney ,10 ,"smallmoney" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.Bit ,1 ,"bit" ,false ,false ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.TinyInt ,3 ,"tinyint" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,true ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.BigInt ,19 ,"bigint" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), + new (SqlDbType.Timestamp ,8 ,"timestamp" ,false ,false ,false ,true ,false ,false ,false ,true ,false ,null ,-1 ,-1 ,true ,null ,null ,null ,"0x" ,null ,""), + new (SqlDbType.Binary ,8000 ,"binary({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"length"), + new (SqlDbType.Image ,2147483647,"image" ,false ,true ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,""), + new (SqlDbType.Text ,2147483647,"text" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), + new (SqlDbType.NText ,1073741823,"ntext" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,""), + new (SqlDbType.Decimal ,38 ,"decimal({0}, {1})" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), + new (SqlDbType.Decimal ,38 ,"numeric({0}, {1})" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale", + alias: "numeric"), + new (SqlDbType.DateTime ,23 ,"datetime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), + new (SqlDbType.SmallDateTime ,16 ,"smalldatetime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), + new (SqlDbType.Variant ,-1 ,"sql_variant" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), + new (SqlDbType.Xml ,2147483647,"xml" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), + new (SqlDbType.VarChar ,2147483647,"varchar({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"max length"), + new (SqlDbType.Char ,2147483647,"char({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"length"), + new (SqlDbType.NChar ,1073741823,"nchar({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"length"), + new (SqlDbType.NVarChar ,1073741823,"nvarchar({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"max length"), + new (SqlDbType.VarBinary ,1073741823,"varbinary({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"max length"), + new (SqlDbType.UniqueIdentifier,16 ,"uniqueidentifier" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), + new (SqlDbType.Date ,3 ,"date" ,false ,false ,false ,true ,true ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,""), + new (SqlDbType.Time ,5 ,"time({0})" ,false ,false ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new (SqlDbType.DateTime2 ,8 ,"datetime2({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new (SqlDbType.DateTimeOffset ,10 ,"datetimeoffset({0})",false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), + new (SqlDbTypeExtensions.Json ,35 ,"json" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,"17.00.000.0" ,null ,false ,"'" ,"'" ,""), + ]; + + #pragma warning restore format + + internal DataTypesCollection() : base(DbMetaDataCollectionNames.DataTypes, 0, 0) { - _types = types; } public async override ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null) @@ -64,8 +143,10 @@ public async override ValueTask GetMetadata(MetaDataContext context, // 1. Load built-in types result.BeginLoadData(); - foreach(TypeMetaData t in _types) + foreach(TypeMetaData t in s_types) { + // TODO : There is a problem, that json datatype is not returned on Azure, because it's minimumVersion="17.0.0000.0", but Azure SQL version is always 12.0.2000.8 + // || ((context.Connection as SqlConnection)?.InnerConnection as SqlConnectionInternal)?.IsAzureSqlConnection ?? false) if ((t.MinimumVersion == null || string.Compare(context.ServerVersion, t.MinimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && (t.MaximumVersion == null || string.Compare(context.ServerVersion, t.MaximumVersion, StringComparison.OrdinalIgnoreCase) <= 0)) { @@ -119,7 +200,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, result.AcceptChanges(); // 2. Add UDTs from the server if supported - MetaDataCollectionBase udtCollection = FindMetaDataCollection("UDTs", context.ServerVersion); + MetaDataCollectionBase? udtCollection = FindMetaDataCollection("_UDTs", context.ServerVersion); if (udtCollection != null) { const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; @@ -137,7 +218,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, } // 3. Add TVPs from the server if supported - MetaDataCollectionBase tvpCollection = FindMetaDataCollection("TVPs", context.ServerVersion); + MetaDataCollectionBase? tvpCollection = FindMetaDataCollection("_TVPs", context.ServerVersion); if (tvpCollection != null) { await tvpCollection.GetMetadata(context, result); @@ -206,20 +287,23 @@ public TypeMetaData(string typeName, int providerDbType, long columnSize, string LiteralSuffix = literalSuffix; } - public TypeMetaData(SqlDbType target, - string dataType, bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, + public TypeMetaData(SqlDbType dbType, long columnSize, string createFormat, + bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, bool isFixedPrecisionScale, bool isLong, bool isNullable, bool isSearchable, bool isSearchableWithLike, - bool isUnsigned, short maximumScale, short minimumScale, bool isConcurrencyType, - string maximumVersion, string minimumVersion, bool isLiteralSupported, string literalPrefix, string literalSuffix) + bool? isUnsigned, short maximumScale, short minimumScale, bool isConcurrencyType, + string? minimumVersion, string? maximumVersion, bool? isLiteralSupported, string? literalPrefix, string? literalSuffix, + string createParameters, string? alias = null) { - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(target, isMultiValued: false); - - TypeName = metaType.TypeName; + // Shared type properties + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(dbType, isMultiValued: false); + TypeName = alias ?? metaType.TypeName; ProviderDbType = (int)metaType.SqlDbType; - ColumnSize = metaType.FixedLength; - CreateFormat = metaType.TypeName; - CreateParameters = metaType.ClassType.FullName; - DataType = dataType; + DataType = metaType.ClassType.FullName!; + + // Schema specific type properties + ColumnSize = columnSize; + CreateParameters = createParameters; + CreateFormat = createFormat; IsAutoIncrementable = isAutoIncrementable; IsBestMatch = isBestMatch; IsCaseSensitive = isCaseSensitive; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs index 9d9194a622..9a888b886a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs @@ -37,7 +37,7 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa foreach (MetaDataCollectionBase mdc in s_metaDataCollection) { - if (mdc.SupportedByCurrentVersion(context.ServerVersion)) + if (mdc.SupportedByCurrentVersion(context.ServerVersion) && mdc.CollectionName[0] != '_') { DataRow row = table.NewRow(); table.Rows.Add([mdc.CollectionName, mdc.NumberOfRestrictions, mdc.NumberOfIdentifierParts]); @@ -49,6 +49,11 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa internal async ValueTask GetMetadata(string collectionName, MetaDataContext context, DataTable accumulator = null) { + if (string.IsNullOrEmpty(collectionName) || collectionName[0] == '_') + { + throw ADP.UndefinedCollection(collectionName); + } + MetaDataCollectionBase collection = FindMetaDataCollection(collectionName, context.ServerVersion); if (collection == null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs index 843a9bec3e..a50c51abb0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs @@ -7,43 +7,155 @@ using System.Threading.Tasks; using Microsoft.Data.Common; -namespace Microsoft.Data.SqlClient +namespace Microsoft.Data.SqlClient; + +internal sealed partial class SqlMetaDataFactory { - internal sealed partial class SqlMetaDataFactory + /// + /// Returns server reserved words + /// + /// + /// These reserved words are defined by the server, and vary depending upon the version + /// and edition. + /// + /// + private sealed class ReservedWordsCollection : MetaDataCollectionBase { - private sealed class ReservedWordsCollection : MetaDataCollectionBase + // @TODO: These have been ported from the existing XML resource file, but they don't perfectly + // align with the referenced link. These need to be reviewed, and if it's correct to add + // the new keywords then they need to indicate which version of SQL Server introduced them. + // @TODO: Azure Synapse Analytics also has an extra reserved keyword. This isn't included at + // the moment, but if we choose to do so then we need a way to identify such. Doing so may + // be non-trivial, depending upon whether we query SERVERPROPERTY('EngineEdition') or use a + // similar approach to ADP.IsAzureSynapseOnDemandEndpoint (i.e. check the data source string.) + private static readonly string[] s_reservedWords = [ + // Reserved keywords used by SQL Server and Azure Synapse Analytics. + "ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AUTHORIZATION", "BACKUP", + "BEGIN", "BETWEEN", "BREAK", "BROWSE", "BULK", "BY", "CASCADE", "CASE", "CHECK", + "CHECKPOINT", "CLOSE", "CLUSTERED", "COALESCE", "COLLATE", "COLUMN", "COMMIT", "COMPUTE", "CONSTRAINT", + "CONTAINS", "CONTAINSTABLE", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", + "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DBCC", "DEALLOCATE", "DECLARE", "DEFAULT", "DELETE", + "DENY", "DESC", "DISK", "DISTINCT", "DISTRIBUTED", "DOUBLE", "DROP", "DUMP", "ELSE", + "END", "ERRLVL", "ESCAPE", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXTERNAL", + "FETCH", "FILE", "FILLFACTOR", "FOR", "FOREIGN", "FREETEXT", "FREETEXTTABLE", "FROM", "FULL", + "FUNCTION", "GOTO", "GRANT", "GROUP", "HAVING", "HOLDLOCK", "IDENTITY", "IDENTITY_INSERT", "IDENTITYCOL", + "IF", "IN", "INDEX", "INNER", "INSERT", "INTERSECT", "INTO", "IS", "JOIN", + // @TODO: Missing keyword: MERGE + "KEY", "KILL", "LEFT", "LIKE", "LINENO", "LOAD", /* "MERGE", */ "NATIONAL", "NOCHECK", + "NONCLUSTERED", "NOT", "NULL", "NULLIF", "OF", "OFF", "OFFSETS", "ON", "OPEN", + "OPENDATASOURCE", "OPENQUERY", "OPENROWSET", "OPENXML", "OPTION", "OR", "ORDER", "OUTER", "OVER", + // @TODO: Missing keyword: PIVOT + "PERCENT", /* "PIVOT", */ "PLAN", "PRECISION", "PRIMARY", "PRINT", "PROC", "PROCEDURE", "PUBLIC", + "RAISERROR", "READ", "READTEXT", "RECONFIGURE", "REFERENCES", "REPLICATION", "RESTORE", "RESTRICT", "RETURN", + // @TODO: Missing keyword: REVERT + /* "REVERT", */ "REVOKE", "RIGHT", "ROLLBACK", "ROWCOUNT", "ROWGUIDCOL", "RULE", "SAVE", "SCHEMA", + // @TODO: Missing keywords: SECURITYAUDIT, SEMANTICKEYPHRASETABLE, SEMANTICSIMILARITYDETAILSTABLE + /* "SECURITYAUDIT", */ "SELECT", /* "SEMANTICKEYPHRASETABLE", "SEMANTICSIMILARITYDETAILSTABLE", */ + // @TODO: Missing keyword: SEMANTICSIMILARITYTABLE + /* "SEMANTICSIMILARITYTABLE", */ "SESSION_USER", "SET", "SETUSER", "SHUTDOWN", + // @TODO: Missing keyword: TABLESAMPLE + "SOME", "STATISTICS", "SYSTEM_USER", "TABLE", /* "TABLESAMPLE", */ "TEXTSIZE", "THEN", "TO", "TOP", + // @TODO: Missing keywords: TRY_CONVERT, UNPIVOT + "TRAN", "TRANSACTION", "TRIGGER", "TRUNCATE", /* "TRY_CONVERT", */ "TSEQUAL", "UNION", "UNIQUE", /* "UNPIVOT", */ + "UPDATE", "UPDATETEXT", "USE", "USER", "VALUES", "VARYING", "VIEW", "WAITFOR", "WHEN", + // @TODO: Missing keyword: WITHIN GROUP + "WHERE", "WHILE", "WITH", /* "WITHIN GROUP", */ "WRITETEXT", + + // ODBC reserved keywords. + "ABSOLUTE", "ACTION", "ADA", "ALLOCATE", "ARE", "ASSERTION", "AT", "AVG", "BIT", + "BIT_LENGTH", "BOTH", "CASCADED", "CAST", "CATALOG", "CHAR", "CHAR_LENGTH", "CHARACTER", "CHARACTER_LENGTH", + "COLLATION", "CONNECT", "CONNECTION", "CONSTRAINTS", "CORRESPONDING", "COUNT", "DATE", "DAY", "DECIMAL", + "DEFERRABLE", "DEFERRED", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS", "DISCONNECT", "DOMAIN", "END-EXEC", "EXCEPTION", + "EXTRACT", "FALSE", "FIRST", "FLOAT", "FORTRAN", "FOUND", "GET", "GLOBAL", "GO", + "HOUR", "IMMEDIATE", "INCLUDE", "INDICATOR", "INITIALLY", "INPUT", "INSENSITIVE", "INT", "INTEGER", + "INTERVAL", "ISOLATION", "LANGUAGE", "LAST", "LEADING", "LEVEL", "LOCAL", "LOWER", "MATCH", + "MAX", "MIN", "MINUTE", "MODULE", "MONTH", "NAMES", "NATURAL", "NCHAR", "NEXT", + "NO", "NONE", "NUMERIC", "OCTET_LENGTH", "ONLY", "OUTPUT", "OVERLAPS", "PAD", "PASCAL", + "POSITION", "PREPARE", "PRESERVE", "PRIOR", "PRIVILEGES", "REAL", "RELATIVE", "ROWS", "SCROLL", + "SECOND", "SECTION", "SESSION", "SIZE", "SMALLINT", "SPACE", "SQL", "SQLCA", "SQLCODE", + "SQLERROR", "SQLSTATE", "SQLWARNING", "SUBSTRING", "SUM", "TEMPORARY", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", + "TIMEZONE_MINUTE", "TRAILING", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE", "UNKNOWN", "UPPER", "USAGE", + "USING", "VALUE", "VARCHAR", "WHENEVER", "WORK", "WRITE", "YEAR", "ZONE", + + // Future reserved keywords. + // @TODO: Missing keywords: ASENSITIVE, ASYMMETRIC, ATOMIC + "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ARRAY", /* "ASENSITIVE", "ASYMMETRIC", "ATOMIC", */ "BEFORE", + // @TODO: Missing keyword: CALLED, CARDINALITY + "BINARY", "BLOB", "BOOLEAN", "BREADTH", "CALL", /* "CALLED", "CARDINALITY", */ "CLASS", "CLOB", + // @TODO: Missing keywords: COLLECT, CONDITION, CORR, COVAR_POP, COVAR_SAMP, CUME_DIST + /* "COLLECT", */ "COMPLETION", /* "CONDITION", */ "CONSTRUCTOR", /* "CORR", "COVAR_POP", "COVAR_SAMP", */ "CUBE", /* "CUME_DIST", */ + // @TODO: Missing keywords: CURRENT_CATALOG, CURRENT_DEFAULT_TRANSFORM_GROUP + /* "CURRENT_CATALOG", "CURRENT_DEFAULT_TRANSFORM_GROUP", */ "CURRENT_PATH", "CURRENT_ROLE", + // @TODO: Missing keywords: CURRENT_SCHEMA, CURRENT_TRANSFORM_GROUP_FOR_TYPE + /* "CURRENT_SCHEMA", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", */ "CYCLE", "DATA", "DEC", + // @TODO: Missing keyword: ELEMENT + "DEPTH", "DEREF", "DESTROY", "DESTRUCTOR", "DETERMINISTIC", "DICTIONARY", "DYNAMIC", "EACH", /* "ELEMENT", */ + // @TODO: Missing keywords: FILTER, FULLTEXTTABLE, FUSION, HOLD + "EQUALS", "EVERY", /* "FILTER", */ "FREE", /* "FULLTEXTTABLE", "FUSION", */ "GENERAL", "GROUPING", /* "HOLD", */ + // @TODO: Missing keyword: INTERSECTION + "HOST", "IGNORE", "INITIALIZE", "INOUT", /* "INTERSECTION", */ "ITERATE", "LARGE", "LATERAL", "LESS", + // @TODO: Missing keywords: LIKE_REGEX, LN, MEMBER, METHOD + /* "LIKE_REGEX",*/ "LIMIT", /* "LN", */ "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "MAP", /* "MEMBER", "METHOD", */ + // @TODO: Missing keywords: MOD, MULTISET, NORMALIZE, OCCURRENCES_REGEX + /* "MOD", */ "MODIFIES", "MODIFY", /* "MULTISET", */ "NCLOB", "NEW", /* "NORMALIZE", */ "OBJECT", /* "OCCURRENCES_REGEX", */ + // @TODO: Missing keyword: OVERLAY, PARTITION + "OLD", "OPERATION", "ORDINALITY", "OUT", /* "OVERLAY", */ "PARAMETER", "PARAMETERS", "PARTIAL", /* "PARTITION" */ + // @TODO: Missing keywords: PERCENT_RANK, PERCENTILE_CONT, PERCENTILE_DISC, POSITION_REGEX, RANGE + "PATH", "POSTFIX", "PREFIX", "PREORDER", /* "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "POSITION_REGEX", "RANGE", */ + "READS", "RECURSIVE", "REF", "REFERENCING", + // @TODO: Missing keywords: REGR_AVGX, REGR_AVGY, REGR_COUNT, REGR_INTERCEPT, REGR_R2, REGR_SLOPE + /* "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", */ + // @TODO: Missing keywords: REGR_SXX, REGR_SXY, REGR_SYY, RELEASE + /* "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELEASE", */ "RESULT", "RETURNS", "ROLE", "ROLLUP", "ROUTINE", + // @TODO: Missing keywords: SENSITIVE, SIMILAR + "ROW", "SAVEPOINT", "SCOPE", "SEARCH", /* "SENSITIVE", */ "SEQUENCE", "SETS", /* "SIMILAR", */ "SPECIFIC", + // @TODO: Missing keywords: STDDEV_POP, STDDEV_SAMP + "SPECIFICTYPE", "SQLEXCEPTION", "START", "STATE", "STATEMENT", "STATIC", /* "STDDEV_POP", "STDDEV_SAMP", */ "STRUCTURE", + + // @TODO: Missing keywords: SUBMULTISET, SUBSTRING_REGEX, SYMMETRIC, SYSTEM, TRANSLATE_REGEX, UESCAPE + /* "SUBMULTISET", "SUBSTRING_REGEX", "SYMMETRIC", "SYSTEM", */ "TERMINATE", "THAN", /* "TRANSLATE_REGEX", */ "TREAT", /* "UESCAPE", */ + // @TODO: Missing keywords: VAR_POP, VAR_SAMP, WIDTH_BUCKET, WINDOW, WITHIN + "UNDER", "UNNEST", /* "VAR_POP", "VAR_SAMP", */ "VARIABLE", /* "WIDTH_BUCKET", */ "WITHOUT", /* , "WINDOW", "WITHIN", */ + // @TODO: Missing keywords: XMLAGG, XMLATTRIBUTES, XMLBINARY, XMLCAST, XMLCOMMENT, XMLCONCAT, XMLDOCUMENT, XMLELEMENT, XMLEXISTS + /* "XMLAGG", "XMLATTRIBUTES", "XMLBINARY", "XMLCAST", "XMLCOMMENT", "XMLCONCAT", "XMLDOCUMENT", "XMLELEMENT", "XMLEXISTS", */ + // @TODO: Missing keywords: XMLFOREST, XMLITERATE, XMLNAMESPACES, XMLPARSE, XMLPI, XMLQUERY, XMLSERIALIZE, XMLTABLE, XMLTEXT + /* "XMLFOREST", "XMLITERATE", "XMLNAMESPACES", "XMLPARSE", "XMLPI", "XMLQUERY", "XMLSERIALIZE", "XMLTABLE", "XMLTEXT", */ + // @TODO: Missing keyword: XMLVALIDATE + /* "XMLVALIDATE" */ + + // Keywords which appear in the SQL Server 2000 documentation but not in newer versions. + // Preserved for backwards compatibility purposes. + "DUMMY" + ]; + + internal ReservedWordsCollection() + : base(DbMetaDataCollectionNames.ReservedWords, 0, 0) + { + } + + public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) { - private readonly string[] _reservedWords; - internal ReservedWordsCollection(string[] reserverdWords) - : base(DbMetaDataCollectionNames.ReservedWords, 0, 0) + if (!ADP.IsEmptyArray(context.RestrictionValues)) { - _reservedWords = reserverdWords; + throw ADP.TooManyRestrictions(CollectionName); } - public override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + DataTable table = accumulator ?? new(CollectionName) { - if (!ADP.IsEmptyArray(context.RestrictionValues)) - { - throw ADP.TooManyRestrictions(CollectionName); - } - - DataTable table = accumulator ?? new(CollectionName) + Columns = { - Columns = - { - new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)) - } - }; - - foreach (string reservedWord in _reservedWords) - { - DataRow row = table.NewRow(); - row[DbMetaDataColumnNames.ReservedWord] = reservedWord; - table.Rows.Add(row); + new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)) } + }; - return new ValueTask(table); + foreach (string reservedWord in s_reservedWords) + { + DataRow row = table.NewRow(); + row[DbMetaDataColumnNames.ReservedWord] = reservedWord; + table.Rows.Add(row); } + + return new ValueTask(table); } } } 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 6a392099fb..ddb6d223a1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -11,166 +11,19 @@ using System.Threading.Tasks; using Microsoft.Data.Common; +#nullable enable + namespace Microsoft.Data.SqlClient { internal sealed partial class SqlMetaDataFactory { #pragma warning disable format private readonly static MetaDataCollectionBase[] s_metaDataCollection = [ - new MetaDataCollection(), - new DataSourceInformationCollection( - CompositeIdentifierSeparatorPattern: "\\.", - DataSourceProductName: "Microsoft SQL Server", - GroupByBehavior: GroupByBehavior.Unrelated, - IdentifierPattern: @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)", - IdentifierCase: IdentifierCase.Insensitive, - OrderByColumnsInSelect: false, - ParameterMarkerFormat: "{0}", - ParameterMarkerPattern: @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)", - ParameterNameMaxLength: 128, - ParameterNamePattern: @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)", - QuotedIdentifierPattern:"(([^\\[]|\\]\\])*)", - QuotedIdentifierCase: IdentifierCase.Insensitive, - StatementSeparatorPattern: ";", - StringLiteralPattern: "'(([^']|'')*)'", - SupportedJoinOperators: SupportedJoinOperators.Inner | SupportedJoinOperators.LeftOuter | SupportedJoinOperators.RightOuter | SupportedJoinOperators.FullOuter), - new DataTypesCollection([ - // Type order follows the order from SqlMetaData.xml - // IsBestMatch isFixedPrecisionScale IsSearchable MaximumScale MinimumVersion LiteralPrefix - // ProviderDbType IsCaseSensitive IsLong IsSearchableWithLike MinimumScale MaximumVersion LiteralSuffix - // TypeName ColumnSize CreateFormat DataType IsAutoIncrementable IsFixedLength IsNullable isUnsigned IsConcurrencyType IsLiteralSupported CreateParameters - new ("smallint" ,16 ,5 ,"smallint" ,"System.Int16" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("int" ,8 ,10 ,"int" ,"System.Int32" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("real" ,13 ,7 ,"real" ,"System.Single" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("float" ,6 ,53 ,"float({0})" ,"System.Double" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,"number of bits used to store the mantissa"), - new ("money" ,9 ,19 ,"money" ,"System.Decimal" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("smallmoney" ,17 ,10 ,"smallmoney" ,"System.Decimal" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("bit" ,2 ,1 ,"bit" ,"System.Boolean" ,false ,false ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("tinyint" ,20 ,3 ,"tinyint" ,"System.Byte" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,true ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("bigint" ,0 ,19 ,"bigint" ,"System.Int64" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new ("timestamp" ,19 ,8 ,"timestamp" ,"System.Byte[]" ,false ,false ,false ,true ,false ,false ,false ,true ,false ,null ,-1 ,-1 ,true ,null ,null ,null ,"0x" ,null ,""), - new ("binary" ,1 ,8000 ,"binary({0})" ,"System.Byte[]" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"length"), - new ("image" ,7 ,2147483647,"image" ,"System.Byte[]" ,false ,true ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,""), - new ("text" ,18 ,2147483647,"text" ,"System.String" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), - new ("ntext" ,11 ,1073741823,"ntext" ,"System.String" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,""), - new ("decimal" ,5 ,38 ,"decimal({0}, {1})" ,"System.Decimal" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), - new ("numeric" ,5 ,38 ,"numeric({0}, {1})" ,"System.Decimal" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), - new ("datetime" ,4 ,23 ,"datetime" ,"System.DateTime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), - new ("smalldatetime" ,15 ,16 ,"smalldatetime" ,"System.DateTime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), - new ("sql_variant" ,23 ,-1 ,"sql_variant" ,"System.Object" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), - new ("xml" ,25 ,2147483647,"xml" ,"System.String" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), - new ("varchar" ,22 ,2147483647,"varchar({0})" ,"System.String" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"max length"), - new ("char" ,3 ,2147483647,"char({0})" ,"System.String" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"length"), - new ("nchar" ,10 ,1073741823,"nchar({0})" ,"System.String" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"length"), - new ("nvarchar" ,12 ,1073741823,"nvarchar({0})" ,"System.String" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"max length"), - new ("varbinary" ,21 ,1073741823,"varbinary({0})" ,"System.Byte[]" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"max length"), - new ("uniqueidentifier",14 ,16 ,"uniqueidentifier" ,"System.Guid" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), - new ("date" ,31 ,3 ,"date" ,"System.DateTime" ,false ,false ,false ,true ,true ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,""), - new ("time" ,32 ,5 ,"time({0})" ,"System.TimeSpan" ,false ,false ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), - new ("datetime2" ,33 ,8 ,"datetime2({0})" ,"System.DateTime" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), - new ("datetimeoffset" ,34 ,10 ,"datetimeoffset({0})","System.DateTimeOffset",false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), - new ("json" ,35 ,2147483647,"json" ,"System.String" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,"17.00.000.0" ,null ,false ,"'" ,"'" ,""), - ]), + new MetaDataCollection(), // GetSchemaCore(...) expects MetaDataCollection to be first element. + new DataSourceInformationCollection(), + new DataTypesCollection(), new RestrictionsCollection(), - new ReservedWordsCollection([ - // Reserved keywords used by SQL Server and Azure Synapse Analytics. - "ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AUTHORIZATION", "BACKUP", - "BEGIN", "BETWEEN", "BREAK", "BROWSE", "BULK", "BY", "CASCADE", "CASE", "CHECK", - "CHECKPOINT", "CLOSE", "CLUSTERED", "COALESCE", "COLLATE", "COLUMN", "COMMIT", "COMPUTE", "CONSTRAINT", - "CONTAINS", "CONTAINSTABLE", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", - "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DBCC", "DEALLOCATE", "DECLARE", "DEFAULT", "DELETE", - "DENY", "DESC", "DISK", "DISTINCT", "DISTRIBUTED", "DOUBLE", "DROP", "DUMP", "ELSE", - "END", "ERRLVL", "ESCAPE", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXTERNAL", - "FETCH", "FILE", "FILLFACTOR", "FOR", "FOREIGN", "FREETEXT", "FREETEXTTABLE", "FROM", "FULL", - "FUNCTION", "GOTO", "GRANT", "GROUP", "HAVING", "HOLDLOCK", "IDENTITY", "IDENTITY_INSERT", "IDENTITYCOL", - "IF", "IN", "INDEX", "INNER", "INSERT", "INTERSECT", "INTO", "IS", "JOIN", - // @TODO: Missing keyword: MERGE - "KEY", "KILL", "LEFT", "LIKE", "LINENO", "LOAD", /* "MERGE", */ "NATIONAL", "NOCHECK", - "NONCLUSTERED", "NOT", "NULL", "NULLIF", "OF", "OFF", "OFFSETS", "ON", "OPEN", - "OPENDATASOURCE", "OPENQUERY", "OPENROWSET", "OPENXML", "OPTION", "OR", "ORDER", "OUTER", "OVER", - // @TODO: Missing keyword: PIVOT - "PERCENT", /* "PIVOT", */ "PLAN", "PRECISION", "PRIMARY", "PRINT", "PROC", "PROCEDURE", "PUBLIC", - "RAISERROR", "READ", "READTEXT", "RECONFIGURE", "REFERENCES", "REPLICATION", "RESTORE", "RESTRICT", "RETURN", - // @TODO: Missing keyword: REVERT - /* "REVERT", */ "REVOKE", "RIGHT", "ROLLBACK", "ROWCOUNT", "ROWGUIDCOL", "RULE", "SAVE", "SCHEMA", - // @TODO: Missing keywords: SECURITYAUDIT, SEMANTICKEYPHRASETABLE, SEMANTICSIMILARITYDETAILSTABLE - /* "SECURITYAUDIT", */ "SELECT", /* "SEMANTICKEYPHRASETABLE", "SEMANTICSIMILARITYDETAILSTABLE", */ - // @TODO: Missing keyword: SEMANTICSIMILARITYTABLE - /* "SEMANTICSIMILARITYTABLE", */ "SESSION_USER", "SET", "SETUSER", "SHUTDOWN", - // @TODO: Missing keyword: TABLESAMPLE - "SOME", "STATISTICS", "SYSTEM_USER", "TABLE", /* "TABLESAMPLE", */ "TEXTSIZE", "THEN", "TO", "TOP", - // @TODO: Missing keywords: TRY_CONVERT, UNPIVOT - "TRAN", "TRANSACTION", "TRIGGER", "TRUNCATE", /* "TRY_CONVERT", */ "TSEQUAL", "UNION", "UNIQUE", /* "UNPIVOT", */ - "UPDATE", "UPDATETEXT", "USE", "USER", "VALUES", "VARYING", "VIEW", "WAITFOR", "WHEN", - // @TODO: Missing keyword: WITHIN GROUP - "WHERE", "WHILE", "WITH", /* "WITHIN GROUP", */ "WRITETEXT", - - // ODBC reserved keywords. - "ABSOLUTE", "ACTION", "ADA", "ALLOCATE", "ARE", "ASSERTION", "AT", "AVG", "BIT", - "BIT_LENGTH", "BOTH", "CASCADED", "CAST", "CATALOG", "CHAR", "CHAR_LENGTH", "CHARACTER", "CHARACTER_LENGTH", - "COLLATION", "CONNECT", "CONNECTION", "CONSTRAINTS", "CORRESPONDING", "COUNT", "DATE", "DAY", "DECIMAL", - "DEFERRABLE", "DEFERRED", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS", "DISCONNECT", "DOMAIN", "END-EXEC", "EXCEPTION", - "EXTRACT", "FALSE", "FIRST", "FLOAT", "FORTRAN", "FOUND", "GET", "GLOBAL", "GO", - "HOUR", "IMMEDIATE", "INCLUDE", "INDICATOR", "INITIALLY", "INPUT", "INSENSITIVE", "INT", "INTEGER", - "INTERVAL", "ISOLATION", "LANGUAGE", "LAST", "LEADING", "LEVEL", "LOCAL", "LOWER", "MATCH", - "MAX", "MIN", "MINUTE", "MODULE", "MONTH", "NAMES", "NATURAL", "NCHAR", "NEXT", - "NO", "NONE", "NUMERIC", "OCTET_LENGTH", "ONLY", "OUTPUT", "OVERLAPS", "PAD", "PASCAL", - "POSITION", "PREPARE", "PRESERVE", "PRIOR", "PRIVILEGES", "REAL", "RELATIVE", "ROWS", "SCROLL", - "SECOND", "SECTION", "SESSION", "SIZE", "SMALLINT", "SPACE", "SQL", "SQLCA", "SQLCODE", - "SQLERROR", "SQLSTATE", "SQLWARNING", "SUBSTRING", "SUM", "TEMPORARY", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", - "TIMEZONE_MINUTE", "TRAILING", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE", "UNKNOWN", "UPPER", "USAGE", - "USING", "VALUE", "VARCHAR", "WHENEVER", "WORK", "WRITE", "YEAR", "ZONE", - - // Future reserved keywords. - // @TODO: Missing keywords: ASENSITIVE, ASYMMETRIC, ATOMIC - "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ARRAY", /* "ASENSITIVE", "ASYMMETRIC", "ATOMIC", */ "BEFORE", - // @TODO: Missing keyword: CALLED, CARDINALITY - "BINARY", "BLOB", "BOOLEAN", "BREADTH", "CALL", /* "CALLED", "CARDINALITY", */ "CLASS", "CLOB", - // @TODO: Missing keywords: COLLECT, CONDITION, CORR, COVAR_POP, COVAR_SAMP, CUME_DIST - /* "COLLECT", */ "COMPLETION", /* "CONDITION", */ "CONSTRUCTOR", /* "CORR", "COVAR_POP", "COVAR_SAMP", */ "CUBE", /* "CUME_DIST", */ - // @TODO: Missing keywords: CURRENT_CATALOG, CURRENT_DEFAULT_TRANSFORM_GROUP - /* "CURRENT_CATALOG", "CURRENT_DEFAULT_TRANSFORM_GROUP", */ "CURRENT_PATH", "CURRENT_ROLE", - // @TODO: Missing keywords: CURRENT_SCHEMA, CURRENT_TRANSFORM_GROUP_FOR_TYPE - /* "CURRENT_SCHEMA", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", */ "CYCLE", "DATA", "DEC", - // @TODO: Missing keyword: ELEMENT - "DEPTH", "DEREF", "DESTROY", "DESTRUCTOR", "DETERMINISTIC", "DICTIONARY", "DYNAMIC", "EACH", /* "ELEMENT", */ - // @TODO: Missing keywords: FILTER, FULLTEXTTABLE, FUSION, HOLD - "EQUALS", "EVERY", /* "FILTER", */ "FREE", /* "FULLTEXTTABLE", "FUSION", */ "GENERAL", "GROUPING", /* "HOLD", */ - // @TODO: Missing keyword: INTERSECTION - "HOST", "IGNORE", "INITIALIZE", "INOUT", /* "INTERSECTION", */ "ITERATE", "LARGE", "LATERAL", "LESS", - // @TODO: Missing keywords: LIKE_REGEX, LN, MEMBER, METHOD - /* "LIKE_REGEX",*/ "LIMIT", /* "LN", */ "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "MAP", /* "MEMBER", "METHOD", */ - // @TODO: Missing keywords: MOD, MULTISET, NORMALIZE, OCCURRENCES_REGEX - /* "MOD", */ "MODIFIES", "MODIFY", /* "MULTISET", */ "NCLOB", "NEW", /* "NORMALIZE", */ "OBJECT", /* "OCCURRENCES_REGEX", */ - // @TODO: Missing keyword: OVERLAY, PARTITION - "OLD", "OPERATION", "ORDINALITY", "OUT", /* "OVERLAY", */ "PARAMETER", "PARAMETERS", "PARTIAL", /* "PARTITION" */ - // @TODO: Missing keywords: PERCENT_RANK, PERCENTILE_CONT, PERCENTILE_DISC, POSITION_REGEX, RANGE - "PATH", "POSTFIX", "PREFIX", "PREORDER", /* "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "POSITION_REGEX", "RANGE", */ - "READS", "RECURSIVE", "REF", "REFERENCING", - // @TODO: Missing keywords: REGR_AVGX, REGR_AVGY, REGR_COUNT, REGR_INTERCEPT, REGR_R2, REGR_SLOPE - /* "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", */ - // @TODO: Missing keywords: REGR_SXX, REGR_SXY, REGR_SYY, RELEASE - /* "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELEASE", */ "RESULT", "RETURNS", "ROLE", "ROLLUP", "ROUTINE", - // @TODO: Missing keywords: SENSITIVE, SIMILAR - "ROW", "SAVEPOINT", "SCOPE", "SEARCH", /* "SENSITIVE", */ "SEQUENCE", "SETS", /* "SIMILAR", */ "SPECIFIC", - // @TODO: Missing keywords: STDDEV_POP, STDDEV_SAMP - "SPECIFICTYPE", "SQLEXCEPTION", "START", "STATE", "STATEMENT", "STATIC", /* "STDDEV_POP", "STDDEV_SAMP", */ "STRUCTURE", - - // @TODO: Missing keywords: SUBMULTISET, SUBSTRING_REGEX, SYMMETRIC, SYSTEM, TRANSLATE_REGEX, UESCAPE - /* "SUBMULTISET", "SUBSTRING_REGEX", "SYMMETRIC", "SYSTEM", */ "TERMINATE", "THAN", /* "TRANSLATE_REGEX", */ "TREAT", /* "UESCAPE", */ - // @TODO: Missing keywords: VAR_POP, VAR_SAMP, WIDTH_BUCKET, WINDOW, WITHIN - "UNDER", "UNNEST", /* "VAR_POP", "VAR_SAMP", */ "VARIABLE", /* "WIDTH_BUCKET", */ "WITHOUT", /* , "WINDOW", "WITHIN", */ - // @TODO: Missing keywords: XMLAGG, XMLATTRIBUTES, XMLBINARY, XMLCAST, XMLCOMMENT, XMLCONCAT, XMLDOCUMENT, XMLELEMENT, XMLEXISTS - /* "XMLAGG", "XMLATTRIBUTES", "XMLBINARY", "XMLCAST", "XMLCOMMENT", "XMLCONCAT", "XMLDOCUMENT", "XMLELEMENT", "XMLEXISTS", */ - // @TODO: Missing keywords: XMLFOREST, XMLITERATE, XMLNAMESPACES, XMLPARSE, XMLPI, XMLQUERY, XMLSERIALIZE, XMLTABLE, XMLTEXT - /* "XMLFOREST", "XMLITERATE", "XMLNAMESPACES", "XMLPARSE", "XMLPI", "XMLQUERY", "XMLSERIALIZE", "XMLTABLE", "XMLTEXT", */ - // @TODO: Missing keyword: XMLVALIDATE - /* "XMLVALIDATE" */ - - // Keywords which appear in the SQL Server 2000 documentation but not in newer versions. - // Preserved for backwards compatibility purposes. - "DUMMY" - ]), + new ReservedWordsCollection(), new SqlCommandCollection("Users", 1, 1, null, null, "select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null))", [new Restriction(1, "User_Name", "@Name")]), new SqlCommandCollection("Databases", 1, 1, null, "09.99.999.9", "select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))", @@ -255,8 +108,8 @@ [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "Name", "@Name")]), - new SqlCommandCollection("TVPs", 0, 0, "10.00.0000", null, @"select name TypeName, 30 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, null IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 0 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.types where is_table_type = 1", null), - new SqlCommandCollection("UDTs", 0, 0, "09.00.0000", null, @"select types.assembly_class COLLATE database_default + ', ' + assemblies.name + ', Version=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision')) + ISNULL(', Culture=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo')),'') + ISNULL(', PublicKeyToken=' + LOWER(REPLACE(CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'PublicKey'),1),'0x','')),'') TypeName, 29 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, is_fixed_length IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 1 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id", null), + new SqlCommandCollection("_TVPs", 0, 0, "10.00.0000", null, @"select name TypeName, 30 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, null IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 0 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.types where is_table_type = 1", null), + new SqlCommandCollection("_UDTs", 0, 0, "09.00.0000", null, @"select types.assembly_class COLLATE database_default + ', ' + assemblies.name + ', Version=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision')) + ISNULL(', Culture=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo')),'') + ISNULL(', PublicKeyToken=' + LOWER(REPLACE(CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'PublicKey'),1),'0x','')),'') TypeName, 29 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, is_fixed_length IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 1 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id", null), ]; #pragma warning restore format @@ -290,10 +143,10 @@ public async ValueTask GetSchemaCore(DbConnection connection, string { cancellationToken.ThrowIfCancellationRequested(); - MetaDataCollection metadataRoot = s_metaDataCollection[0] as MetaDataCollection; + MetaDataCollection? metadataRoot = s_metaDataCollection[0] as MetaDataCollection; // We expect first element of s_metaDataCollection to be an instance of MetaDataCollection Debug.Assert(metadataRoot != null); - DataTable schema = await metadataRoot.GetMetadata(collectionName, new MetaDataContext(_serverVersion, restrictions, connection, isAsync, cancellationToken)); + DataTable schema = await metadataRoot!.GetMetadata(collectionName, new MetaDataContext(_serverVersion, restrictions, connection, isAsync, cancellationToken)); return schema; } @@ -303,14 +156,14 @@ public async ValueTask GetSchemaCore(DbConnection connection, string - + internal sealed class MetaDataContext { - public readonly string ServerVersion; - public readonly string[] RestrictionValues; - public readonly DbConnection Connection; - public readonly bool IsAsync = false; - public readonly CancellationToken CancellationToken; + public string ServerVersion { get; init; } + public string[] RestrictionValues { get; init; } + public DbConnection Connection { get; init; } + public bool IsAsync { get; init; } + public CancellationToken CancellationToken { get; init; } internal MetaDataContext(string serverVersion, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) { @@ -324,9 +177,9 @@ internal MetaDataContext(string serverVersion, string[] restrictions, DbConnecti internal sealed class Restriction { - public readonly string RestrictionName; - public readonly string ParameterName; - public readonly int RestrictionNumber; + public string RestrictionName { get; init; } + public string ParameterName { get; init; } + public int RestrictionNumber { get; init; } internal Restriction(int restrictionNumber, string restrictionName, string parameterName) { @@ -338,13 +191,13 @@ internal Restriction(int restrictionNumber, string restrictionName, string param internal abstract class MetaDataCollectionBase { - public readonly string CollectionName; - public readonly int NumberOfRestrictions; - public readonly int NumberOfIdentifierParts; - private readonly string _minimumVersion; - private readonly string _maximumVersion; + private readonly string? _minimumVersion; + private readonly string? _maximumVersion; + public string CollectionName { get; init; } + public int NumberOfRestrictions { get; init; } + public int NumberOfIdentifierParts { get; init; } - internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, string minimumVersion = null, string maximumVersion = null) + internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, string? minimumVersion = null, string? maximumVersion = null) { CollectionName = collectionName; NumberOfRestrictions = numberOfRestrictions; @@ -353,19 +206,19 @@ internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, _maximumVersion = maximumVersion; } - public abstract ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null); + public abstract ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null); public bool SupportedByCurrentVersion(string serverVersion) => (_minimumVersion == null || string.Compare(serverVersion, _minimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && (_maximumVersion == null || string.Compare(serverVersion, _maximumVersion, StringComparison.OrdinalIgnoreCase) <= 0); - protected MetaDataCollectionBase FindMetaDataCollection(string collectionName, string serverVersion) + protected MetaDataCollectionBase? FindMetaDataCollection(string collectionName, string serverVersion) { bool versionFailure = false; bool haveExactMatch = false; bool haveMultipleInexactMatches = false; - string exactCollectionName = null; - MetaDataCollectionBase requestedCollection = null; + string? exactCollectionName = null; + MetaDataCollectionBase? requestedCollection = null; foreach (MetaDataCollectionBase metaData in s_metaDataCollection) { From ffeb6fbedb0a22967bb02efea0ac409c96d314dd Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Wed, 17 Jun 2026 09:54:07 +0300 Subject: [PATCH 4/8] Ensuring that all query-based collections would have the same restrictions for all query versions --- .../SqlClient/SqlMetaDataFactory.DataTypes.cs | 61 +++++----- .../SqlMetaDataFactory.MetaDataCollections.cs | 8 +- .../SqlMetaDataFactory.Restrictions.cs | 2 +- .../SqlMetaDataFactory.SqlCommand.cs | 64 +++++++--- .../Data/SqlClient/SqlMetaDataFactory.cs | 109 ++++++++++-------- 5 files changed, 144 insertions(+), 100 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs index 99c6c34911..7d3ada4b77 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs @@ -133,8 +133,6 @@ public async override ValueTask GetMetadata(MetaDataContext context, 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)) @@ -145,10 +143,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, result.BeginLoadData(); foreach(TypeMetaData t in s_types) { - // TODO : There is a problem, that json datatype is not returned on Azure, because it's minimumVersion="17.0.0000.0", but Azure SQL version is always 12.0.2000.8 - // || ((context.Connection as SqlConnection)?.InnerConnection as SqlConnectionInternal)?.IsAzureSqlConnection ?? false) - if ((t.MinimumVersion == null || string.Compare(context.ServerVersion, t.MinimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && - (t.MaximumVersion == null || string.Compare(context.ServerVersion, t.MaximumVersion, StringComparison.OrdinalIgnoreCase) <= 0)) + if (t.SupportedByCurrentVersion(context)) { DataRow row = result.NewRow(); row[DbMetaDataColumnNames.TypeName] = t.TypeName; @@ -200,7 +195,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, result.AcceptChanges(); // 2. Add UDTs from the server if supported - MetaDataCollectionBase? udtCollection = FindMetaDataCollection("_UDTs", context.ServerVersion); + MetaDataCollectionBase? udtCollection = FindMetaDataCollection("_UDTs", context); if (udtCollection != null) { const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; @@ -218,7 +213,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, } // 3. Add TVPs from the server if supported - MetaDataCollectionBase? tvpCollection = FindMetaDataCollection("_TVPs", context.ServerVersion); + MetaDataCollectionBase? tvpCollection = FindMetaDataCollection("_TVPs", context); if (tvpCollection != null) { await tvpCollection.GetMetadata(context, result); @@ -228,32 +223,32 @@ public async override ValueTask GetMetadata(MetaDataContext context, } } - private sealed class TypeMetaData + private sealed class TypeMetaData : ISupported { - public readonly string TypeName; - public readonly int ProviderDbType; - public readonly long ColumnSize; - public readonly string CreateFormat; - public readonly string? CreateParameters; - public readonly string DataType; - public readonly bool IsAutoIncrementable; - public readonly bool IsBestMatch; - public readonly bool IsCaseSensitive; - public readonly bool IsFixedLength; - public readonly bool IsFixedPrecisionScale; - public readonly bool IsLong; - public readonly bool IsNullable; - public readonly bool IsSearchable; - public readonly bool IsSearchableWithLike; - public readonly bool? IsUnsigned; - public readonly short MaximumScale; - public readonly short MinimumScale; - public readonly bool IsConcurrencyType; - public readonly string? MaximumVersion; - public readonly string? MinimumVersion; - public readonly bool? IsLiteralSupported; - public readonly string? LiteralPrefix; - public readonly string? LiteralSuffix; + public string TypeName{ get; init; } + public int ProviderDbType{ get; init; } + public long ColumnSize{ get; init; } + public string CreateFormat{ get; init; } + public string? CreateParameters{ get; init; } + public string DataType{ get; init; } + public bool IsAutoIncrementable{ get; init; } + public bool IsBestMatch{ get; init; } + public bool IsCaseSensitive{ get; init; } + public bool IsFixedLength{ get; init; } + public bool IsFixedPrecisionScale{ get; init; } + public bool IsLong{ get; init; } + public bool IsNullable{ get; init; } + public bool IsSearchable{ get; init; } + public bool IsSearchableWithLike{ get; init; } + public bool? IsUnsigned{ get; init; } + public short MaximumScale{ get; init; } + public short MinimumScale{ get; init; } + public bool IsConcurrencyType{ get; init; } + public string? MaximumVersion{ get; init; } + public string? MinimumVersion{ get; init; } + public bool? IsLiteralSupported{ get; init; } + public string? LiteralPrefix{ get; init; } + public string? LiteralSuffix{ get; init; } public TypeMetaData(string typeName, int providerDbType, long columnSize, string createFormat, string dataType, bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs index 9a888b886a..5701900e7c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs @@ -4,6 +4,7 @@ using System.Data; using System.Data.Common; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Data.Common; @@ -37,7 +38,7 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa foreach (MetaDataCollectionBase mdc in s_metaDataCollection) { - if (mdc.SupportedByCurrentVersion(context.ServerVersion) && mdc.CollectionName[0] != '_') + if (mdc.SupportedByCurrentVersion(context) && mdc.CollectionName[0] != '_') { DataRow row = table.NewRow(); table.Rows.Add([mdc.CollectionName, mdc.NumberOfRestrictions, mdc.NumberOfIdentifierParts]); @@ -47,14 +48,15 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa return new ValueTask(table); } - internal async ValueTask GetMetadata(string collectionName, MetaDataContext context, DataTable accumulator = null) + internal async ValueTask GetMetadata(string collectionName, MetaDataContext context) { + Debug.Assert(context != null); if (string.IsNullOrEmpty(collectionName) || collectionName[0] == '_') { throw ADP.UndefinedCollection(collectionName); } - MetaDataCollectionBase collection = FindMetaDataCollection(collectionName, context.ServerVersion); + MetaDataCollectionBase collection = FindMetaDataCollection(collectionName, context); if (collection == null) { throw ADP.UnsupportedVersion(collectionName); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs index e4fdeffb3f..d971ef1b5d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs @@ -40,7 +40,7 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa { if (mdc is SqlCommandCollection sqlCollection && sqlCollection.RestrictionParams != null && - mdc.SupportedByCurrentVersion(context.ServerVersion)) + sqlCollection.SupportedByCurrentVersion(context)) { foreach (Restriction restriction in sqlCollection.RestrictionParams) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs index 4cd83b7454..dcb95f21a0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs @@ -10,30 +10,68 @@ using System.Threading.Tasks; using Microsoft.Data.Common; +#nullable enable + namespace Microsoft.Data.SqlClient { internal sealed partial class SqlMetaDataFactory { + private sealed class SupportedQuery : ISupported + { + public string? MinimumVersion { get; init; } + public string? MaximumVersion { get; init; } + public string Query { get; init; } + + public SupportedQuery(string? minimumVersion, string? maximumVersion, string query) + { + MinimumVersion = minimumVersion; + MaximumVersion = maximumVersion; + Query = query; + } + } + private sealed class SqlCommandCollection : MetaDataCollectionBase { - private readonly string _populationString; - public readonly Restriction[] RestrictionParams; + private readonly SupportedQuery[] Queries; + public Restriction[] RestrictionParams { get; init; } - public SqlCommandCollection(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, - string minimumVersion, string maximumVersion, string populationString, Restriction[] restrictions) - : base(collectionName, numberOfRestrictions, numberOfIdentifierParts, minimumVersion, maximumVersion) + public SqlCommandCollection(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, SupportedQuery[] queries, Restriction[] restrictions) + : base(collectionName, numberOfRestrictions, numberOfIdentifierParts) { RestrictionParams = restrictions; - _populationString = populationString; + Queries = queries; + } + + public override bool SupportedByCurrentVersion(MetaDataContext context) => GetSupportedQuery(context, false) != null; + + private SupportedQuery? GetSupportedQuery(MetaDataContext context, bool throwIfNotFound) + { + SupportedQuery? validQuery = null; + foreach (SupportedQuery query in Queries) + { + if (query.SupportedByCurrentVersion(context)) + { + Debug.Assert(validQuery == null, $"Two queries matches current version {context.ServerVersion} in collection {CollectionName}"); + validQuery = query; + } + } + + // If there are no queries matching current server version, then whole collection is undefined for this version + if (validQuery == null && throwIfNotFound) + { + throw ADP.UndefinedCollection(CollectionName); + } + + return validQuery; } - public async override ValueTask GetMetadata(MetaDataContext context, DataTable accumulator = null) + public async override ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null) { Debug.Assert(NumberOfRestrictions >= (context.RestrictionValues?.Length ?? 0)); context.CancellationToken.ThrowIfCancellationRequested(); - DataTable resultTable = null; + DataTable resultTable; if ((context.RestrictionValues is not null) && (context.RestrictionValues.Length > NumberOfRestrictions)) { @@ -42,7 +80,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, if (!ADP.IsEmptyArray(context.RestrictionValues)) { - for (int i = 0; i < context.RestrictionValues.Length; i++) + for (int i = 0; i < context.RestrictionValues!.Length; i++) { if ((context.RestrictionValues[i] is not null) && (context.RestrictionValues[i].Length > 4096)) { @@ -53,10 +91,10 @@ public async override ValueTask GetMetadata(MetaDataContext context, } } - SqlConnection castConnection = context.Connection as SqlConnection; + SqlConnection castConnection = (SqlConnection)context.Connection; using SqlCommand command = castConnection.CreateCommand(); - command.CommandText = _populationString; + command.CommandText = GetSupportedQuery(context, true)!.Query; command.CommandTimeout = Math.Max(command.CommandTimeout, 180); command.Transaction = castConnection?.GetOpenTdsConnection()?.CurrentTransaction?.Parent; @@ -79,7 +117,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, command.Parameters.Add(restrictionParameter); } - SqlDataReader reader = null; + SqlDataReader? reader = null; try { try @@ -112,7 +150,7 @@ public async override ValueTask GetMetadata(MetaDataContext context, System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); foreach (DbColumn col in colSchema) { - resultTable.Columns.Add(col.ColumnName, col.DataType); + resultTable.Columns.Add(col.ColumnName, col.DataType!); } } 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 ddb6d223a1..8a69a9c333 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.Connection; #nullable enable @@ -24,92 +25,97 @@ internal sealed partial class SqlMetaDataFactory new DataTypesCollection(), new RestrictionsCollection(), new ReservedWordsCollection(), - new SqlCommandCollection("Users", 1, 1, null, null, "select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null))", + new SqlCommandCollection("Users", 1, 1, + [new SupportedQuery(null, null, "select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null))")], [new Restriction(1, "User_Name", "@Name")]), - new SqlCommandCollection("Databases", 1, 1, null, "09.99.999.9", "select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))", + new SqlCommandCollection("Databases", 1, 1, + [new SupportedQuery(null, "09.99.999.9", "select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))"), + new SupportedQuery("10.00.000.0", null, "IF OBJECT_ID('master..sysdatabases') IS NULL EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name ELSE EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name")], [new Restriction(1, "Name", "@Name")]), - new SqlCommandCollection("Databases", 1, 1, "10.00.000.0", null, "IF OBJECT_ID('master..sysdatabases') IS NULL EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name ELSE EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name", - [new Restriction(1, "Name", "@Name")]), - new SqlCommandCollection("Tables", 4, 3, null, null, "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE from INFORMATION_SCHEMA.TABLES where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Name or (@Name is null)) and (TABLE_TYPE = @TableType or (@TableType is null))", + new SqlCommandCollection("Tables", 4, 3, + [new SupportedQuery(null, null, "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE from INFORMATION_SCHEMA.TABLES where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Name or (@Name is null)) and (TABLE_TYPE = @TableType or (@TableType is null))")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Name"), new Restriction(4, "TableType", "@TableType")]), - new SqlCommandCollection("Columns", 4, 4, null, "09.99.999.9", "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG from INFORMATION_SCHEMA.COLUMNS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME", + new SqlCommandCollection("Columns", 4, 4, + [new SupportedQuery(null, "09.99.999.9", "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG from INFORMATION_SCHEMA.COLUMNS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME"), + new SupportedQuery("10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 0")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "Column", "@Column")]), - new SqlCommandCollection("Columns", 4, 4, "10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 0", + new SqlCommandCollection("AllColumns", 4, 4, + [new SupportedQuery("10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 1")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "Column", "@Column")]), - new SqlCommandCollection("AllColumns", 4, 4, "10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 1", - [new Restriction(1, "Catalog", "@Catalog"), - new Restriction(2, "Owner", "@Owner"), - new Restriction(3, "Table", "@Table"), - new Restriction(4, "Column", "@Column")]), - new SqlCommandCollection("ColumnSetColumns", 3, 3, "10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, null, 2", + new SqlCommandCollection("ColumnSetColumns", 3, 3, + [new SupportedQuery("10.00.000.0", null, "EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, null, 2")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table")]), - new SqlCommandCollection("StructuredTypeMembers", 4, 4, "10.00.000.0", null, "SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name", + new SqlCommandCollection("StructuredTypeMembers", 4, 4, + [new SupportedQuery("10.00.000.0", null, "SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Type", "@Type"), new Restriction(4, "Member", "@Member")]), - new SqlCommandCollection("Views", 3, 3, "08.00.000.0", null, "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE from INFORMATION_SCHEMA.VIEWS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME", + new SqlCommandCollection("Views", 3, 3, + [new SupportedQuery("08.00.000.0", null, "select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE from INFORMATION_SCHEMA.VIEWS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table")]), - new SqlCommandCollection("ViewColumns", 4, 4, "08.00.000.0", null, "select VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where (VIEW_CATALOG = @Catalog or (@Catalog is null)) and (VIEW_SCHEMA = @Owner or (@Owner is null)) and (VIEW_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME", + new SqlCommandCollection("ViewColumns", 4, 4, + [new SupportedQuery("08.00.000.0", null, "select VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where (VIEW_CATALOG = @Catalog or (@Catalog is null)) and (VIEW_SCHEMA = @Owner or (@Owner is null)) and (VIEW_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "Column", "@Column")]), - new SqlCommandCollection("ProcedureParameters", 4, 1, "08.00.0000", null, "select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, CASE WHEN DATA_TYPE IS NULL THEN USER_DEFINED_TYPE_NAME WHEN DATA_TYPE = 'table type' THEN USER_DEFINED_TYPE_NAME ELSE DATA_TYPE END as DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, COLLATION_CATALOG, COLLATION_SCHEMA, COLLATION_NAME, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION from INFORMATION_SCHEMA.PARAMETERS where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (PARAMETER_NAME = @Parameter or (@Parameter is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, PARAMETER_NAME", + new SqlCommandCollection("ProcedureParameters", 4, 1, + [new SupportedQuery("08.00.0000", null, "select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, CASE WHEN DATA_TYPE IS NULL THEN USER_DEFINED_TYPE_NAME WHEN DATA_TYPE = 'table type' THEN USER_DEFINED_TYPE_NAME ELSE DATA_TYPE END as DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, COLLATION_CATALOG, COLLATION_SCHEMA, COLLATION_NAME, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION from INFORMATION_SCHEMA.PARAMETERS where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (PARAMETER_NAME = @Parameter or (@Parameter is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, PARAMETER_NAME")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Name", "@Name"), new Restriction(4, "Parameter", "@Parameter")]), - new SqlCommandCollection("Procedures", 4, 3, "08.00.0000", null, "select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_CATALOG, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED, LAST_ALTERED from INFORMATION_SCHEMA.ROUTINES where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (ROUTINE_TYPE = @Type or (@Type is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME", + new SqlCommandCollection("Procedures", 4, 3, + [new SupportedQuery("08.00.0000", null, "select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_CATALOG, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED, LAST_ALTERED from INFORMATION_SCHEMA.ROUTINES where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (ROUTINE_TYPE = @Type or (@Type is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Name", "@Name"), new Restriction(4, "Type", "@Type")]), - new SqlCommandCollection("IndexColumns", 5, 4, null, "09.99.9999", "select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, column_name = c.name, ordinal_position = convert(int, xk.keyno), KeyType = c.xtype, index_name = x.name from sysobjects o, sysindexes x, syscolumns c, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = c.id and o.id = xk.id and x.indid = xk.indid and c.colid = xk.colid and xk.keyno < = x.keycnt and permissions(o.id, c.name) <> 0 and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @ConstraintName or (@ConstraintName is null)) and (c.name = @Column or (@Column is null)) order by table_name, index_name", + new SqlCommandCollection("IndexColumns", 5, 4, + [new SupportedQuery(null, "09.99.9999", "select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, column_name = c.name, ordinal_position = convert(int, xk.keyno), KeyType = c.xtype, index_name = x.name from sysobjects o, sysindexes x, syscolumns c, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = c.id and o.id = xk.id and x.indid = xk.indid and c.colid = xk.colid and xk.keyno < = x.keycnt and permissions(o.id, c.name) <> 0 and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @ConstraintName or (@ConstraintName is null)) and (c.name = @Column or (@Column is null)) order by table_name, index_name"), + new SupportedQuery("10.00.0000", null, "EXEC sys.sp_indexcolumns_managed @Catalog, @Owner, @Table, @ConstraintName, @Column")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "ConstraintName", "@ConstraintName"), new Restriction(5, "Column", "@Column")]), - new SqlCommandCollection("IndexColumns", 5, 4, "10.00.0000", null, "EXEC sys.sp_indexcolumns_managed @Catalog, @Owner, @Table, @ConstraintName, @Column", - [new Restriction(1, "Catalog", "@Catalog"), - new Restriction(2, "Owner", "@Owner"), - new Restriction(3, "Table", "@Table"), - new Restriction(4, "ConstraintName", "@ConstraintName"), - new Restriction(5, "Column", "@Column")]), - new SqlCommandCollection("Indexes", 4, 3, null, "09.99.9999", "select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, index_name = x.name from sysobjects o, sysindexes x, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = xk.id and x.indid = xk.indid and xk.keyno < = x.keycnt and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @Name or (@Name is null)) order by table_name, index_name", + new SqlCommandCollection("Indexes", 4, 3, + [new SupportedQuery(null, "09.99.9999", "select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, index_name = x.name from sysobjects o, sysindexes x, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = xk.id and x.indid = xk.indid and xk.keyno < = x.keycnt and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @Name or (@Name is null)) order by table_name, index_name"), + new SupportedQuery("10.00.0000", null, "EXEC sys.sp_indexes_managed @Catalog, @Owner, @Table, @Name")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "Name", "@Name")]), - new SqlCommandCollection("Indexes", 4, 3, "10.00.0000", null, "EXEC sys.sp_indexes_managed @Catalog, @Owner, @Table, @Name", - [new Restriction(1, "Catalog", "@Catalog"), - new Restriction(2, "Owner", "@Owner"), - new Restriction(3, "Table", "@Table"), - new Restriction(4, "Name", "@Name")]), - new SqlCommandCollection("UserDefinedTypes", 2, 1, "09.00.0000", null, "select assemblies.name as assembly_name, types.assembly_class as udt_name, ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, is_fixed_length, max_length, Create_Date, Permission_set_desc from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id where (assemblies.name = @AssemblyName or (@AssemblyName is null)) and (types.assembly_class = @UDTName or (@UDTName is null))", + new SqlCommandCollection("UserDefinedTypes", 2, 1, + [new SupportedQuery("09.00.0000", null, "select assemblies.name as assembly_name, types.assembly_class as udt_name, ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, is_fixed_length, max_length, Create_Date, Permission_set_desc from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id where (assemblies.name = @AssemblyName or (@AssemblyName is null)) and (types.assembly_class = @UDTName or (@UDTName is null))")], [new Restriction(1, "assembly_name", "@AssemblyName"), new Restriction(2, "udt_name", "@UDTName")]), - new SqlCommandCollection("ForeignKeys", 4, 3, null, null, "select CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_TYPE, IS_DEFERRABLE, INITIALLY_DEFERRED from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where (CONSTRAINT_CATALOG = @Catalog or (@Catalog is null)) and (CONSTRAINT_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (CONSTRAINT_NAME = @Name or (@Name is null)) and CONSTRAINT_TYPE = 'FOREIGN KEY' order by CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME", + new SqlCommandCollection("ForeignKeys", 4, 3, + [new SupportedQuery(null, null, "select CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_TYPE, IS_DEFERRABLE, INITIALLY_DEFERRED from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where (CONSTRAINT_CATALOG = @Catalog or (@Catalog is null)) and (CONSTRAINT_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (CONSTRAINT_NAME = @Name or (@Name is null)) and CONSTRAINT_TYPE = 'FOREIGN KEY' order by CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME")], [new Restriction(1, "Catalog", "@Catalog"), new Restriction(2, "Owner", "@Owner"), new Restriction(3, "Table", "@Table"), new Restriction(4, "Name", "@Name")]), - new SqlCommandCollection("_TVPs", 0, 0, "10.00.0000", null, @"select name TypeName, 30 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, null IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 0 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.types where is_table_type = 1", null), - new SqlCommandCollection("_UDTs", 0, 0, "09.00.0000", null, @"select types.assembly_class COLLATE database_default + ', ' + assemblies.name + ', Version=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision')) + ISNULL(', Culture=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo')),'') + ISNULL(', PublicKeyToken=' + LOWER(REPLACE(CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'PublicKey'),1),'0x','')),'') TypeName, 29 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, is_fixed_length IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 1 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id", null), + new SqlCommandCollection("_TVPs", 0, 0, + [new SupportedQuery("10.00.0000", null, "select name TypeName, 30 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, null IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 0 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.types where is_table_type = 1")], + []), + new SqlCommandCollection("_UDTs", 0, 0, + [new SupportedQuery("09.00.0000", null, "select types.assembly_class COLLATE database_default + ', ' + assemblies.name + ', Version=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision')) + ISNULL(', Culture=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo')),'') + ISNULL(', PublicKeyToken=' + LOWER(REPLACE(CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'PublicKey'),1),'0x','')),'') TypeName, 29 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, is_fixed_length IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 1 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id")], + []), ]; #pragma warning restore format @@ -151,11 +157,11 @@ public async ValueTask GetSchemaCore(DbConnection connection, string return schema; } - - - - - + internal interface ISupported + { + string? MinimumVersion { get; } + string? MaximumVersion { get; } + } internal sealed class MetaDataContext { @@ -191,28 +197,22 @@ internal Restriction(int restrictionNumber, string restrictionName, string param internal abstract class MetaDataCollectionBase { - private readonly string? _minimumVersion; - private readonly string? _maximumVersion; public string CollectionName { get; init; } public int NumberOfRestrictions { get; init; } public int NumberOfIdentifierParts { get; init; } - internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, string? minimumVersion = null, string? maximumVersion = null) + internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts) { CollectionName = collectionName; NumberOfRestrictions = numberOfRestrictions; NumberOfIdentifierParts = numberOfIdentifierParts; - _minimumVersion = minimumVersion; - _maximumVersion = maximumVersion; } public abstract ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null); - public bool SupportedByCurrentVersion(string serverVersion) => - (_minimumVersion == null || string.Compare(serverVersion, _minimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && - (_maximumVersion == null || string.Compare(serverVersion, _maximumVersion, StringComparison.OrdinalIgnoreCase) <= 0); + public virtual bool SupportedByCurrentVersion(MetaDataContext context) => true; - protected MetaDataCollectionBase? FindMetaDataCollection(string collectionName, string serverVersion) + protected MetaDataCollectionBase? FindMetaDataCollection(string collectionName, MetaDataContext context) { bool versionFailure = false; bool haveExactMatch = false; @@ -224,7 +224,7 @@ public bool SupportedByCurrentVersion(string serverVersion) => { if (string.Equals(metaData.CollectionName, collectionName, StringComparison.InvariantCultureIgnoreCase)) { - if (!metaData.SupportedByCurrentVersion(serverVersion)) + if (!SupportedByCurrentVersion(context)) { versionFailure = true; } @@ -273,4 +273,13 @@ public bool SupportedByCurrentVersion(string serverVersion) => } } } + + internal static class SqlMetaDataFactoryExtensions + { + internal static bool SupportedByCurrentVersion(this SqlMetaDataFactory.ISupported item, SqlMetaDataFactory.MetaDataContext context) + { + return (item.MinimumVersion == null || string.Compare(context.ServerVersion, item.MinimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && + (item.MaximumVersion == null || string.Compare(context.ServerVersion, item.MaximumVersion, StringComparison.OrdinalIgnoreCase) <= 0); + } + } } From 4d47d0528094cfe7c40b09a573886ae87c8fa8a9 Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Wed, 17 Jun 2026 09:55:57 +0300 Subject: [PATCH 5/8] Fixing problem with Azure SQL, where it would incorrectly determine if certain collection, or type is supported, because Azure SQL always returns version 12.0.XXXX. --- .../src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs | 8 ++++++++ 1 file changed, 8 insertions(+) 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 8a69a9c333..f928267c62 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -278,6 +278,14 @@ internal static class SqlMetaDataFactoryExtensions { internal static bool SupportedByCurrentVersion(this SqlMetaDataFactory.ISupported item, SqlMetaDataFactory.MetaDataContext context) { + bool isAzure = ADP.IsAzureSqlServerEndpoint(context.Connection.DataSource); + // Azure SQL always returns v12.00.XXXX (TDS returns 12.00.9114, SERVERPROPERTY('ProductVersion') returns 12.0.2000.8, SERVERPROPERTY('ResourceVersion') returns 17.00.9114), + // but in fact it has latest stable version. For Azure SQL only item where MaximumVersion=null should be valid. + if (isAzure) + { + return item.MaximumVersion == null; + } + return (item.MinimumVersion == null || string.Compare(context.ServerVersion, item.MinimumVersion, StringComparison.OrdinalIgnoreCase) >= 0) && (item.MaximumVersion == null || string.Compare(context.ServerVersion, item.MaximumVersion, StringComparison.OrdinalIgnoreCase) <= 0); } From f38991567b8cba6c43c2718fb8c57a8a68dbf24a Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Wed, 17 Jun 2026 10:25:56 +0300 Subject: [PATCH 6/8] Adding refereces to ADO.NET documentation. --- .../SqlClient/SqlMetaDataFactory.DataSourceInformation.cs | 4 ++++ .../Data/SqlClient/SqlMetaDataFactory.DataTypes.cs | 7 ++++--- .../SqlClient/SqlMetaDataFactory.MetaDataCollections.cs | 7 +++++++ .../Data/SqlClient/SqlMetaDataFactory.Restrictions.cs | 4 ++++ .../Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs | 4 ++++ .../src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs | 2 +- 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs index 6aeee92612..60f85b14b1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs @@ -13,6 +13,10 @@ namespace Microsoft.Data.SqlClient; internal sealed partial class SqlMetaDataFactory { + /// + /// Returns + /// DataSourceInformation schema collection. + /// private sealed class DataSourceInformationCollection : MetaDataCollectionBase { private const string CompositeIdentifierSeparatorPattern = "\\."; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs index 7d3ada4b77..7a76eb7198 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs @@ -2,13 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Data; using System.Data.Common; -using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Data.Common; -using Microsoft.Data.SqlClient.Connection; #nullable enable @@ -16,6 +13,10 @@ namespace Microsoft.Data.SqlClient; internal sealed partial class SqlMetaDataFactory { + /// + /// Returns DataTypes + /// schema collection. + /// private sealed class DataTypesCollection : MetaDataCollectionBase { #pragma warning disable format diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs index 5701900e7c..068d689ec5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs @@ -12,6 +12,13 @@ namespace Microsoft.Data.SqlClient; internal sealed partial class SqlMetaDataFactory { + /// + /// Returns a list of all schema collections supported by the current data provider + /// + /// + /// Returns both common schema collections + /// and SQL Server schema collections + /// private class MetaDataCollection : MetaDataCollectionBase { internal MetaDataCollection() diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs index d971ef1b5d..dc983160a9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs @@ -6,11 +6,15 @@ using System.Data.Common; using System.Threading.Tasks; using Microsoft.Data.Common; +using static System.Net.WebRequestMethods; namespace Microsoft.Data.SqlClient { internal sealed partial class SqlMetaDataFactory { + /// + /// Returns Restrictions schema collection. + /// private sealed class RestrictionsCollection : MetaDataCollectionBase { internal RestrictionsCollection() : base(DbMetaDataCollectionNames.Restrictions, 0, 0) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs index dcb95f21a0..868dc49da4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs @@ -30,6 +30,10 @@ public SupportedQuery(string? minimumVersion, string? maximumVersion, string que } } + + /// + /// Returns one of the SQL Server Schema collections + /// private sealed class SqlCommandCollection : MetaDataCollectionBase { private readonly SupportedQuery[] Queries; 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 f928267c62..49e86faf67 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -117,7 +117,7 @@ [new SupportedQuery("10.00.0000", null, "select name TypeName, 30 ProviderDbType [new SupportedQuery("09.00.0000", null, "select types.assembly_class COLLATE database_default + ', ' + assemblies.name + ', Version=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild')) + '.' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision')) + ISNULL(', Culture=' + CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo')),'') + ISNULL(', PublicKeyToken=' + LOWER(REPLACE(CONVERT(VARCHAR(200),ASSEMBLYPROPERTY(assemblies.name, 'PublicKey'),1),'0x','')),'') TypeName, 29 ProviderDbType, max_length ColumnSize, null CreateFormat, null CreateParameters, null DataType, null IsAutoincrementable, null IsBestMatch, null IsCaseSensitive, is_fixed_length IsFixedLength, null IsFixedPrecisionScale, null IsLong, is_nullable IsNullable, 1 IsSearchable, null IsSearchableWithLike, null IsUnsigned, null MaximumScale, null MinimumScale, null IsConcurrencyType, 0 IsLiteralSupported, null LiteralPrefix, null LiteralSuffix, null NativeDataType from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id")], []), ]; - #pragma warning restore format + #pragma warning restore format // Well-known column names From beab53dd15258ae735a046c2fc9a33d7031836fd Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Wed, 17 Jun 2026 22:24:58 +0300 Subject: [PATCH 7/8] Remove unused object creation --- .../Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs index 60f85b14b1..f43eff7b62 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs @@ -74,7 +74,6 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa } }; - DataRow row = table.NewRow(); table.Rows.Add([ CompositeIdentifierSeparatorPattern, DataSourceProductName, @@ -94,6 +93,7 @@ public override ValueTask GetMetadata(MetaDataContext context, DataTa StringLiteralPattern, SupportedJoinOperators ]); + return new ValueTask(table); } } From 60c58c091b3aec255415176edcfa03392927ceae Mon Sep 17 00:00:00 2001 From: Mihails Golubevs Date: Fri, 19 Jun 2026 13:33:43 +0300 Subject: [PATCH 8/8] Deriving most of the TypeMetaData values from M.D.S.MetaType --- .../SqlClient/SqlMetaDataFactory.DataTypes.cs | 108 +++++++++++------- 1 file changed, 67 insertions(+), 41 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs index 7a76eb7198..86736eac3f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs @@ -4,6 +4,7 @@ using System.Data; using System.Data.Common; +using System.Text; using System.Threading.Tasks; using Microsoft.Data.Common; @@ -60,42 +61,43 @@ private sealed class DataTypesCollection : MetaDataCollectionBase private static readonly TypeMetaData[] s_types = [ // Type order follows the order from SqlMetaData.xml - // IsBestMatch isFixedPrecisionScale IsSearchable MaximumScale MinimumVersion LiteralPrefix - // IsCaseSensitive IsLong IsSearchableWithLike MinimumScale MaximumVersion LiteralSuffix - // ColumnSize CreateFormat IsAutoIncrementable IsFixedLength IsNullable isUnsigned IsConcurrencyType IsLiteralSupported CreateParameters - new (SqlDbType.SmallInt ,5 ,"smallint" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.Int ,10 ,"int" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.Real ,7 ,"real" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.Float ,53 ,"float({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,"number of bits used to store the mantissa"), - new (SqlDbType.Money ,19 ,"money" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.SmallMoney ,10 ,"smallmoney" ,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.Bit ,1 ,"bit" ,false ,false ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.TinyInt ,3 ,"tinyint" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,true ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.BigInt ,19 ,"bigint" ,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,""), - new (SqlDbType.Timestamp ,8 ,"timestamp" ,false ,false ,false ,true ,false ,false ,false ,true ,false ,null ,-1 ,-1 ,true ,null ,null ,null ,"0x" ,null ,""), - new (SqlDbType.Binary ,8000 ,"binary({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"length"), - new (SqlDbType.Image ,2147483647,"image" ,false ,true ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,""), - new (SqlDbType.Text ,2147483647,"text" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), - new (SqlDbType.NText ,1073741823,"ntext" ,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,""), - new (SqlDbType.Decimal ,38 ,"decimal({0}, {1})" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale"), - new (SqlDbType.Decimal ,38 ,"numeric({0}, {1})" ,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,"precision,scale", + // IsBestMatch isFixedPrecisionScale IsSearchable MaximumScale MinimumVersion LiteralPrefix + // IsCaseSensitive IsLong IsSearchableWithLike MinimumScale MaximumVersion LiteralSuffix + // ColumnSize (CreateFormat)* IsAutoIncrementable IsFixedLength IsNullable isUnsigned IsConcurrencyType IsLiteralSupported CreateParameters + new (SqlDbType.SmallInt ,null /*,"smallint" */,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.Int ,null /*,"int" */,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.Real ,null /*,"real" */,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.Float ,53 /*,"float({0})" */,false ,true ,false ,true ,false ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,["number of bits used to store the mantissa"]), + new (SqlDbType.Money ,null /*,"money" */,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.SmallMoney ,null /*,"smallmoney" */,false ,false ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.Bit ,null /*,"bit" */,false ,false ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.TinyInt ,null /*,"tinyint" */,true ,true ,false ,true ,true ,false ,true ,true ,false ,true ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.BigInt ,null /*,"bigint" */,true ,true ,false ,true ,true ,false ,true ,true ,false ,false ,-1 ,-1 ,false ,null ,null ,null ,null ,null ,null), + new (SqlDbType.Timestamp ,TdsEnums.TEXT_TIME_STAMP_LEN/*,"timestamp" */,false ,false ,false ,true ,false ,false ,false ,true ,false ,null ,-1 ,-1 ,true ,null ,null ,null ,"0x" ,null ,null), + new (SqlDbType.Binary ,8000 /*,"binary({0})" */,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,["length"]), + new (SqlDbType.Image ,int.MaxValue /*,"image" */,false ,true ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,null), + new (SqlDbType.Text ,int.MaxValue /*,"text" */,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,null), + new (SqlDbType.NText ,int.MaxValue / ADP.CharSize /*,"ntext" */,false ,true ,false ,false ,false ,true ,true ,false ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,null), + new (SqlDbType.Decimal ,38 /*,"decimal({0}, {1})" */,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,["precision","scale"]), + new (SqlDbType.Decimal ,38 /*,"numeric({0}, {1})" */,true ,true ,false ,true ,false ,false ,true ,true ,false ,false ,38 ,0 ,false ,null ,null ,null ,null ,null ,["precision","scale"], alias: "numeric"), - new (SqlDbType.DateTime ,23 ,"datetime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), - new (SqlDbType.SmallDateTime ,16 ,"smalldatetime" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,""), - new (SqlDbType.Variant ,-1 ,"sql_variant" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), - new (SqlDbType.Xml ,2147483647,"xml" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,""), - new (SqlDbType.VarChar ,2147483647,"varchar({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"max length"), - new (SqlDbType.Char ,2147483647,"char({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,"length"), - new (SqlDbType.NChar ,1073741823,"nchar({0})" ,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"length"), - new (SqlDbType.NVarChar ,1073741823,"nvarchar({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,"max length"), - new (SqlDbType.VarBinary ,1073741823,"varbinary({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,"max length"), - new (SqlDbType.UniqueIdentifier,16 ,"uniqueidentifier" ,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,""), - new (SqlDbType.Date ,3 ,"date" ,false ,false ,false ,true ,true ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,""), - new (SqlDbType.Time ,5 ,"time({0})" ,false ,false ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), - new (SqlDbType.DateTime2 ,8 ,"datetime2({0})" ,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), - new (SqlDbType.DateTimeOffset ,10 ,"datetimeoffset({0})",false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,"scale"), - new (SqlDbTypeExtensions.Json ,35 ,"json" ,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,"17.00.000.0" ,null ,false ,"'" ,"'" ,""), + new (SqlDbType.DateTime ,null /*,"datetime" */,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,null), + new (SqlDbType.SmallDateTime ,null /*,"smalldatetime" */,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"{ts '","'}" ,null), + new (SqlDbType.Variant ,-1 /*,"sql_variant" */,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,null), + new (SqlDbType.Xml ,int.MaxValue /*,"xml" */,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,null ,null ,false ,null ,null ,null), + new (SqlDbType.VarChar ,int.MaxValue /*,"varchar({0})" */,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,["max length"]), + new (SqlDbType.Char ,int.MaxValue /*,"char({0})" */,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,["length"]), + new (SqlDbType.NChar ,int.MaxValue / ADP.CharSize /*,"nchar({0})" */,false ,true ,false ,true ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,["length"]), + new (SqlDbType.NVarChar ,int.MaxValue / ADP.CharSize /*,"nvarchar({0})" */,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,null ,null ,null ,"N'" ,"'" ,["max length"]), + new (SqlDbType.VarBinary ,int.MaxValue / ADP.CharSize /*,"varbinary({0})" */,false ,true ,false ,false ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"0x" ,null ,["max length"]), + new (SqlDbType.UniqueIdentifier,null /*,"uniqueidentifier" */,false ,true ,false ,true ,false ,false ,true ,true ,false ,null ,-1 ,-1 ,false ,null ,null ,null ,"'" ,"'" ,null), + new (SqlDbType.Date ,null /*,"date" */,false ,false ,false ,true ,true ,false ,true ,true ,true ,null ,-1 ,-1 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,null), + new (SqlDbType.Time ,5 /*,"time({0})" */,false ,false ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,["scale"]), + new (SqlDbType.DateTime2 ,8 /*,"datetime2({0})" */,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,["scale"]), + new (SqlDbType.DateTimeOffset ,10 /*,"datetimeoffset({0})"*/,false ,true ,false ,false ,false ,false ,true ,true ,true ,null ,7 ,0 ,false ,"10.00.000.0" ,null ,null ,"{ts '","'}" ,["scale"]), + new (SqlDbTypeExtensions.Json ,int.MaxValue /*,"json" */,false ,false ,false ,false ,false ,true ,true ,false ,false ,null ,-1 ,-1 ,false ,"17.00.000.0" ,null ,false ,"'" ,"'" ,null), ]; + // * - CreateFormat value, that supposed to be produced #pragma warning restore format @@ -228,7 +230,7 @@ private sealed class TypeMetaData : ISupported { public string TypeName{ get; init; } public int ProviderDbType{ get; init; } - public long ColumnSize{ get; init; } + public int ColumnSize{ get; init; } public string CreateFormat{ get; init; } public string? CreateParameters{ get; init; } public string DataType{ get; init; } @@ -251,7 +253,7 @@ private sealed class TypeMetaData : ISupported public string? LiteralPrefix{ get; init; } public string? LiteralSuffix{ get; init; } - public TypeMetaData(string typeName, int providerDbType, long columnSize, string createFormat, + public TypeMetaData(string typeName, int providerDbType, int columnSize, string createFormat, string dataType, bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, bool isFixedPrecisionScale, bool isLong, bool isNullable, bool isSearchable, bool isSearchableWithLike, bool? isUnsigned, short maximumScale, short minimumScale, bool isConcurrencyType, @@ -283,23 +285,47 @@ public TypeMetaData(string typeName, int providerDbType, long columnSize, string LiteralSuffix = literalSuffix; } - public TypeMetaData(SqlDbType dbType, long columnSize, string createFormat, + public TypeMetaData(SqlDbType dbType, int? columnSize, bool isAutoIncrementable, bool isBestMatch, bool isCaseSensitive, bool isFixedLength, bool isFixedPrecisionScale, bool isLong, bool isNullable, bool isSearchable, bool isSearchableWithLike, bool? isUnsigned, short maximumScale, short minimumScale, bool isConcurrencyType, string? minimumVersion, string? maximumVersion, bool? isLiteralSupported, string? literalPrefix, string? literalSuffix, - string createParameters, string? alias = null) + string[]? createParameters, string? alias = null) { // Shared type properties MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(dbType, isMultiValued: false); TypeName = alias ?? metaType.TypeName; ProviderDbType = (int)metaType.SqlDbType; DataType = metaType.ClassType.FullName!; + ColumnSize = columnSize ?? (metaType.Precision == TdsEnums.UNKNOWN_PRECISION_SCALE + ? metaType.FixedLength + : metaType.Precision); + if (ADP.IsEmptyArray(createParameters)) + { + CreateFormat = metaType.TypeName; + CreateParameters = null; + } + else + { + StringBuilder sbFormat = new StringBuilder(alias ?? metaType.TypeName); + StringBuilder sbParams = new StringBuilder(); + sbFormat.Append('('); + for (int i = 0; i < createParameters!.Length; i++) + { + if (i > 0) + { + sbFormat.Append(','); + sbParams.Append(','); + } + sbFormat.AppendFormat("{{{0}}}", i); + sbParams.Append(createParameters[i]); + } + sbFormat.Append(')'); + CreateFormat = sbFormat.ToString(); + CreateParameters = sbParams.ToString(); + } // Schema specific type properties - ColumnSize = columnSize; - CreateParameters = createParameters; - CreateFormat = createFormat; IsAutoIncrementable = isAutoIncrementable; IsBestMatch = isBestMatch; IsCaseSensitive = isCaseSensitive;