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..73b85f5d45 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.ReservedWords.cs @@ -0,0 +1,177 @@ +// 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; + +#nullable enable + +namespace Microsoft.Data.SqlClient; + +internal sealed partial class SqlMetaDataFactory +{ + /// + /// Adds reserved words to the indicated metadata DataSet. + /// + /// The metadata DataSet to contain the reserved words. + /// + /// These reserved words are defined by the server, and vary depending upon the version + /// and edition. + /// + /// + private static void LoadReservedWordsDataTables(DataSet metaDataCollectionsDataSet) + { + DataTable reservedWordsDataTable = CreateReservedWordsDataTable(); + + reservedWordsDataTable.BeginLoadData(); + + // @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.) + + // Add reserved keywords used by SQL Server and Azure Synapse Analytics. + AddReservedWords(minVersion: null, maxVersion: null, + "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"); + + // Add ODBC reserved keywords. Some of these overlap with the previous category, and are not included. + AddReservedWords(minVersion: null, maxVersion: null, + "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"); + + // Add future reserved keywords. + AddReservedWords(minVersion: null, maxVersion: null, + // @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. + AddReservedWords(minVersion: null, maxVersion: null, "DUMMY"); + + reservedWordsDataTable.EndLoadData(); + reservedWordsDataTable.AcceptChanges(); + + metaDataCollectionsDataSet.Tables.Add(reservedWordsDataTable); + + void AddReservedWords(string? minVersion, string? maxVersion, params ReadOnlySpan reservedWords) + { + foreach (string reservedWord in reservedWords) + { + DataRow wordRow = reservedWordsDataTable.NewRow(); + + wordRow[DbMetaDataColumnNames.ReservedWord] = reservedWord; + + if (minVersion is not null) + { + wordRow[MinimumVersionKey] = minVersion; + } + + if (maxVersion is not null) + { + wordRow[MaximumVersionKey] = maxVersion; + } + + reservedWordsDataTable.Rows.Add(wordRow); + } + } + } + + private static DataTable CreateReservedWordsDataTable() + => new(DbMetaDataCollectionNames.ReservedWords) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; +} 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 1d9ceebafd..b5c4627f22 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -715,6 +715,7 @@ private DataSet LoadDataSetFromXml(Stream XmlStream) }; LoadDataTypesDataTables(metaDataCollectionsDataSet); + LoadReservedWordsDataTables(metaDataCollectionsDataSet); XmlReaderSettings settings = new() { @@ -765,9 +766,6 @@ private DataSet LoadDataSetFromXml(Stream XmlStream) dataTable = CreateDataSourceInformationDataTable(); rowFixup = FixUpDataSourceInformationRow; break; - case "ReservedWordsTable": - dataTable = CreateReservedWordsDataTable(); - break; default: Debug.Fail($"Unexpected table element name: {reader.Name}"); break; @@ -897,17 +895,6 @@ private static DataTable CreateDataSourceInformationDataTable() 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/Microsoft.Data.SqlClient.SqlMetaData.xml b/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml index 494e9a9106..dc9fae7d43 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml +++ b/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml @@ -580,1185 +580,4 @@ 15 - - - ADD - - - EXCEPT - - - PERCENT - - - ALL - - - EXEC - - - PLAN - - - ALTER - - - EXECUTE - - - PRECISION - - - AND - - - EXISTS - - - PRIMARY - - - ANY - - - EXIT - - - PRINT - - - AS - - - FETCH - - - PROC - - - ASC - - - FILE - - - PROCEDURE - - - AUTHORIZATION - - - FILLFACTOR - - - PUBLIC - - - BACKUP - - - FOR - - - RAISERROR - - - BEGIN - - - FOREIGN - - - READ - - - BETWEEN - - - FREETEXT - - - READTEXT - - - BREAK - - - FREETEXTTABLE - - - RECONFIGURE - - - BROWSE - - - FROM - - - REFERENCES - - - BULK - - - FULL - - - REPLICATION - - - BY - - - FUNCTION - - - RESTORE - - - CASCADE - - - GOTO - - - RESTRICT - - - CASE - - - GRANT - - - RETURN - - - CHECK - - - GROUP - - - REVOKE - - - CHECKPOINT - - - HAVING - - - RIGHT - - - CLOSE - - - HOLDLOCK - - - ROLLBACK - - - CLUSTERED - - - IDENTITY - - - ROWCOUNT - - - COALESCE - - - IDENTITY_INSERT - - - ROWGUIDCOL - - - COLLATE - - - IDENTITYCOL - - - RULE - - - COLUMN - - - IF - - - SAVE - - - COMMIT - - - IN - - - SCHEMA - - - COMPUTE - - - INDEX - - - SELECT - - - CONSTRAINT - - - INNER - - - SESSION_USER - - - CONTAINS - - - INSERT - - - SET - - - CONTAINSTABLE - - - INTERSECT - - - SETUSER - - - CONTINUE - - - INTO - - - SHUTDOWN - - - CONVERT - - - IS - - - SOME - - - CREATE - - - JOIN - - - STATISTICS - - - CROSS - - - KEY - - - SYSTEM_USER - - - CURRENT - - - KILL - - - TABLE - - - CURRENT_DATE - - - LEFT - - - TEXTSIZE - - - CURRENT_TIME - - - LIKE - - - THEN - - - CURRENT_TIMESTAMP - - - LINENO - - - TO - - - CURRENT_USER - - - LOAD - - - TOP - - - CURSOR - - - NATIONAL - - - TRAN - - - DATABASE - - - NOCHECK - - - TRANSACTION - - - DBCC - - - NONCLUSTERED - - - TRIGGER - - - DEALLOCATE - - - NOT - - - TRUNCATE - - - DECLARE - - - NULL - - - TSEQUAL - - - DEFAULT - - - NULLIF - - - UNION - - - DELETE - - - OF - - - UNIQUE - - - DENY - - - OFF - - - UPDATE - - - DESC - - - OFFSETS - - - UPDATETEXT - - - DISK - - - ON - - - USE - - - DISTINCT - - - OPEN - - - USER - - - DISTRIBUTED - - - OPENDATASOURCE - - - VALUES - - - DOUBLE - - - OPENQUERY - - - VARYING - - - DROP - - - OPENROWSET - - - VIEW - - - DUMMY - - - OPENXML - - - WAITFOR - - - DUMP - - - OPTION - - - WHEN - - - ELSE - - - OR - - - WHERE - - - END - - - ORDER - - - WHILE - - - ERRLVL - - - OUTER - - - WITH - - - ESCAPE - - - OVER - - - WRITETEXT - - - ABSOLUTE - - - FOUND - - - PRESERVE - - - ACTION - - - FREE - - - PRIOR - - - ADMIN - - - GENERAL - - - PRIVILEGES - - - AFTER - - - GET - - - READS - - - AGGREGATE - - - GLOBAL - - - REAL - - - ALIAS - - - GO - - - RECURSIVE - - - ALLOCATE - - - GROUPING - - - REF - - - ARE - - - HOST - - - REFERENCING - - - ARRAY - - - HOUR - - - RELATIVE - - - ASSERTION - - - IGNORE - - - RESULT - - - AT - - - IMMEDIATE - - - RETURNS - - - BEFORE - - - INDICATOR - - - ROLE - - - BINARY - - - INITIALIZE - - - ROLLUP - - - BIT - - - INITIALLY - - - ROUTINE - - - BLOB - - - INOUT - - - ROW - - - BOOLEAN - - - INPUT - - - ROWS - - - BOTH - - - INT - - - SAVEPOINT - - - BREADTH - - - INTEGER - - - SCROLL - - - CALL - - - INTERVAL - - - SCOPE - - - CASCADED - - - ISOLATION - - - SEARCH - - - CAST - - - ITERATE - - - SECOND - - - CATALOG - - - LANGUAGE - - - SECTION - - - CHAR - - - LARGE - - - SEQUENCE - - - CHARACTER - - - LAST - - - SESSION - - - CLASS - - - LATERAL - - - SETS - - - CLOB - - - LEADING - - - SIZE - - - COLLATION - - - LESS - - - SMALLINT - - - COMPLETION - - - LEVEL - - - SPACE - - - CONNECT - - - LIMIT - - - SPECIFIC - - - CONNECTION - - - LOCAL - - - SPECIFICTYPE - - - CONSTRAINTS - - - LOCALTIME - - - SQL - - - CONSTRUCTOR - - - LOCALTIMESTAMP - - - SQLEXCEPTION - - - CORRESPONDING - - - LOCATOR - - - SQLSTATE - - - CUBE - - - MAP - - - SQLWARNING - - - CURRENT_PATH - - - MATCH - - - START - - - CURRENT_ROLE - - - MINUTE - - - STATE - - - CYCLE - - - MODIFIES - - - STATEMENT - - - DATA - - - MODIFY - - - STATIC - - - DATE - - - MODULE - - - STRUCTURE - - - DAY - - - MONTH - - - TEMPORARY - - - DEC - - - NAMES - - - TERMINATE - - - DECIMAL - - - NATURAL - - - THAN - - - DEFERRABLE - - - NCHAR - - - TIME - - - DEFERRED - - - NCLOB - - - TIMESTAMP - - - DEPTH - - - NEW - - - TIMEZONE_HOUR - - - DEREF - - - NEXT - - - TIMEZONE_MINUTE - - - DESCRIBE - - - NO - - - TRAILING - - - DESCRIPTOR - - - NONE - - - TRANSLATION - - - DESTROY - - - NUMERIC - - - TREAT - - - DESTRUCTOR - - - OBJECT - - - TRUE - - - DETERMINISTIC - - - OLD - - - UNDER - - - DICTIONARY - - - ONLY - - - UNKNOWN - - - DIAGNOSTICS - - - OPERATION - - - UNNEST - - - DISCONNECT - - - ORDINALITY - - - USAGE - - - DOMAIN - - - OUT - - - USING - - - DYNAMIC - - - OUTPUT - - - VALUE - - - EACH - - - PAD - - - VARCHAR - - - END-EXEC - - - PARAMETER - - - VARIABLE - - - EQUALS - - - PARAMETERS - - - WHENEVER - - - EVERY - - - PARTIAL - - - WITHOUT - - - EXCEPTION - - - PATH - - - WORK - - - EXTERNAL - - - POSTFIX - - - WRITE - - - FALSE - - - PREFIX - - - YEAR - - - FIRST - - - PREORDER - - - ZONE - - - FLOAT - - - PREPARE - - - ADA - - - AVG - - - BIT_LENGTH - - - CHAR_LENGTH - - - CHARACTER_LENGTH - - - COUNT - - - EXTRACT - - - FORTRAN - - - INCLUDE - - - INSENSITIVE - - - LOWER - - - MAX - - - MIN - - - OCTET_LENGTH - - - OVERLAPS - - - PASCAL - - - POSITION - - - SQLCA - - - SQLCODE - - - SQLERROR - - - SUBSTRING - - - SUM - - - TRANSLATE - - - TRIM - - - UPPER - - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs index 028447251c..44f51ac161 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs @@ -55,6 +55,15 @@ public static async Task GetIndexesFromSchema() await VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" }); } + [ConditionalFact(nameof(CanRunSchemaTests))] + public static async Task GetReservedWordsFromSchema() + { + (DataTable syncReservedWordTable, DataTable asyncReservedWordsTable) = await VerifySchemaTable(DbMetaDataCollectionNames.ReservedWords, new string[] { "ReservedWord" }); + + VerifyReservedWordsTable(syncReservedWordTable); + VerifyReservedWordsTable(asyncReservedWordsTable); + } + [ConditionalFact(nameof(CanRunSchemaTests))] public static async Task GetIndexColumnsFromSchema() { @@ -198,5 +207,36 @@ private static void VerifyDataTypesTable(DataTable dataTypesTable) // SQL Azure reports a version of 12.x but supports JSON, so SqlClient doesn't include it in the list of types. Assert.Equal(DataTestUtility.IsJsonSupported && DataTestUtility.IsNotAzureServer(), actualTypes.Contains("json")); } + + private static void VerifyReservedWordsTable(DataTable reservedWordsTable) + { + // This set contains four example words from each of the categories of reserved words + string[] sampleReservedWords = [ + // SQL Server reserved words + "SELECT", "FROM", "WHERE", "NATIONAL", + // ODBC reserved words + "GO", "COUNT", "SQLCODE", "SMALLINT", + // Future reserved keywords + "AGGREGATE", "ALIAS", "DATA", "LOCALTIME", + // Older keyword + "DUMMY" + ]; + HashSet actualReservedWords = []; + + // Assert that every reserved word is unique. + foreach (DataRow row in reservedWordsTable.Rows) + { + string reservedWord = row[DbMetaDataColumnNames.ReservedWord] as string; + + Assert.False(string.IsNullOrEmpty(reservedWord)); + // Older versions of SqlClient included a keyword of "NATIONAL " (note the trailing space.) + // Verify that this is no longer possible. + Assert.Equal(reservedWord.Trim(), reservedWord); + Assert.True(actualReservedWords.Add(reservedWord)); + } + + Assert.All(sampleReservedWords, reservedWord => Assert.Contains(reservedWord, actualReservedWords)); + Assert.Equal(393, actualReservedWords.Count); + } } }