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..96b667cc5c 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( 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..f43eff7b62 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataSourceInformation.cs @@ -0,0 +1,100 @@ +// 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; + +#nullable enable + +namespace Microsoft.Data.SqlClient; + +internal sealed partial class SqlMetaDataFactory +{ + /// + /// Returns + /// DataSourceInformation schema collection. + /// + 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) + { + } + + 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)) + } + }; + + 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..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,7 +4,8 @@ using System.Data; using System.Data.Common; -using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; using Microsoft.Data.Common; #nullable enable @@ -13,426 +14,336 @@ namespace Microsoft.Data.SqlClient; internal sealed partial class SqlMetaDataFactory { - private static void LoadDataTypesDataTables(DataSet metaDataCollectionsDataSet) + /// + /// Returns DataTypes + /// schema collection. + /// + private sealed class DataTypesCollection : MetaDataCollectionBase { - DataTable dataTypesDataTable = CreateDataTypesDataTable(); - - 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) + #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 ,"'" ,"'" ,""), + ]; + + 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 ,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 ,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 + + internal DataTypesCollection() + : base(DbMetaDataCollectionNames.DataTypes, 0, 0) { - 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) + public async override ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null) { - 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); - } - - void AddFixedPrecisionDateTimeType(SqlDbType dateTimeDbType, bool isBestMatch, - 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; - // "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(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) + // 1. Load built-in types + result.BeginLoadData(); + foreach(TypeMetaData t in s_types) { - typeRow[DbMetaDataColumnNames.CreateFormat] = $"{metaType.TypeName}({{0}})"; - typeRow[DbMetaDataColumnNames.CreateParameters] = - isFixedLength ? "length" : "max length"; - } - else - { - typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + if (t.SupportedByCurrentVersion(context)) + { + 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); + 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); + if (tvpCollection != null) { - typeRow[MinimumVersionKey] = minimumVersion; + await tvpCollection.GetMetadata(context, result); } - dataTypesDataTable.Rows.Add(typeRow); + return result; } + } - void AddSqlVariantType() + private sealed class TypeMetaData : ISupported + { + public string TypeName{ get; init; } + public int ProviderDbType{ get; init; } + public int 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, 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, + 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 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) { - 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] = "'"; + // 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(); + } - dataTypesDataTable.Rows.Add(typeRow); + // Schema specific type properties + 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..068d689ec5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.MetaDataCollections.cs @@ -0,0 +1,78 @@ +// 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.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Data.Common; + +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() + : 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) && mdc.CollectionName[0] != '_') + { + 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) + { + Debug.Assert(context != null); + if (string.IsNullOrEmpty(collectionName) || collectionName[0] == '_') + { + throw ADP.UndefinedCollection(collectionName); + } + + MetaDataCollectionBase collection = FindMetaDataCollection(collectionName, context); + 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..a50c51abb0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs @@ -0,0 +1,161 @@ +// 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 +{ + /// + /// 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 + { + // @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) + { + 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 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.Restrictions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs new file mode 100644 index 0000000000..dc983160a9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.Restrictions.cs @@ -0,0 +1,61 @@ +// 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; +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) + { + } + + 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 && + sqlCollection.SupportedByCurrentVersion(context)) + { + 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..868dc49da4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.SqlCommand.cs @@ -0,0 +1,180 @@ +// 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; + +#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; + } + } + + + /// + /// Returns one of the SQL Server Schema collections + /// + private sealed class SqlCommandCollection : MetaDataCollectionBase + { + private readonly SupportedQuery[] Queries; + public Restriction[] RestrictionParams { get; init; } + + public SqlCommandCollection(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts, SupportedQuery[] queries, Restriction[] restrictions) + : base(collectionName, numberOfRestrictions, numberOfIdentifierParts) + { + RestrictionParams = restrictions; + 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) + { + Debug.Assert(NumberOfRestrictions >= (context.RestrictionValues?.Length ?? 0)); + + context.CancellationToken.ThrowIfCancellationRequested(); + + DataTable resultTable; + + 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 = (SqlConnection)context.Connection; + using SqlCommand command = castConnection.CreateCommand(); + + command.CommandText = GetSupportedQuery(context, true)!.Query; + 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..49e86faf67 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,136 @@ 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; +using Microsoft.Data.SqlClient.Connection; + +#nullable enable 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(), // GetSchemaCore(...) expects MetaDataCollection to be first element. + new DataSourceInformationCollection(), + new DataTypesCollection(), + new RestrictionsCollection(), + new ReservedWordsCollection(), + 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, + [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("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, + [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("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("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, + [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, + [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, + [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, + [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, + [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, + [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("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("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, + [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, + [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 + + // 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 +147,147 @@ 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] - : []; + 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)); - // 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); - } - - return requestedSchema; - } - - public void Dispose() => Dispose(true); - - private void Dispose(bool disposing) - { - if (disposing) - { - _collectionDataSet.Dispose(); - } + return schema; } - #region GetSchema Helpers: DataTable Population Method - private static bool IncludeThisColumn(DataColumn sourceColumn, ReadOnlySpan hiddenColumnNames) + internal interface ISupported { - string sourceColumnName = sourceColumn.ColumnName; - - return sourceColumnName switch - { - MinimumVersionKey or MaximumVersionKey => false, -#if NET - _ => !hiddenColumnNames.Contains(sourceColumnName), -#else - _ => hiddenColumnNames.IndexOf(sourceColumnName) == -1, -#endif - }; + string? MinimumVersion { get; } + string? MaximumVersion { get; } } - private static DataColumn[] FilterColumns(DataTable sourceTable, ReadOnlySpan hiddenColumnNames, DataColumnCollection destinationColumns) + internal sealed class MetaDataContext { - 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++; - } + 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) + { + ServerVersion = serverVersion; + RestrictionValues = restrictions; + Connection = connection; + IsAsync = isAsync; + CancellationToken = cancellationToken; } - return filteredSourceColumns; } - private DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) + internal sealed class Restriction { - DataTable destinationTable; - DataColumn[] filteredSourceColumns; - DataColumnCollection destinationColumns; - DataRow newRow; - - DataTable sourceTable = _collectionDataSet.Tables[collectionName]; + public string RestrictionName { get; init; } + public string ParameterName { get; init; } + public int RestrictionNumber { get; init; } - if (sourceTable?.TableName != collectionName) + internal Restriction(int restrictionNumber, string restrictionName, string parameterName) { - throw ADP.DataTableDoesNotExist(collectionName); + RestrictionName = restrictionName; + ParameterName = parameterName; + RestrictionNumber = restrictionNumber; } - - 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 abstract class MetaDataCollectionBase { - 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 string CollectionName { get; init; } + public int NumberOfRestrictions { get; init; } + public int NumberOfIdentifierParts { get; init; } - result = (string)restriction[parameterName]; - break; - } - } - - if (result is null) + internal MetaDataCollectionBase(string collectionName, int numberOfRestrictions, int numberOfIdentifierParts) { - throw ADP.MissingRestrictionRow(); + CollectionName = collectionName; + NumberOfRestrictions = numberOfRestrictions; + NumberOfIdentifierParts = numberOfIdentifierParts; } - return result; - } - - private async ValueTask ExecuteCommandAsync(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) - { - Debug.Assert(requestedCollectionRow is not null); + public abstract ValueTask GetMetadata(MetaDataContext context, DataTable? accumulator = null); - DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; - DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; + public virtual bool SupportedByCurrentVersion(MetaDataContext context) => true; - 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)) + protected MetaDataCollectionBase? FindMetaDataCollection(string collectionName, MetaDataContext context) { - throw ADP.TooManyRestrictions(collectionName); - } - - SqlConnection castConnection = connection as SqlConnection; - using SqlCommand command = castConnection.CreateCommand(); + bool versionFailure = false; + bool haveExactMatch = false; + bool haveMultipleInexactMatches = false; + string? exactCollectionName = null; + MetaDataCollectionBase? requestedCollection = null; - 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 + foreach (MetaDataCollectionBase metaData in s_metaDataCollection) { - // This is where we have to assign null to the value of the parameter. - restrictionParameter.Value = DBNull.Value; - } - - restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); - restrictionParameter.Direction = ParameterDirection.Input; - command.Parameters.Add(restrictionParameter); - } - - SqlDataReader reader = null; - try - { - try - { - reader = 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); - } - - if (firstResultAvailable) - { - 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 (!SupportedByCurrentVersion(context)) + { + 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) + internal static class SqlMetaDataFactoryExtensions + { + internal static bool SupportedByCurrentVersion(this SqlMetaDataFactory.ISupported item, SqlMetaDataFactory.MetaDataContext context) { - 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) + 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) { - 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); + return item.MaximumVersion == null; } - table.EndLoadData(); - table.AcceptChanges(); + 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); } - - 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} 資料行必須包含非空的字串。 - 填滿