From 4b438b779d65714cab1dd4c29a57a734d1e3b7ad Mon Sep 17 00:00:00 2001 From: martincostello Date: Fri, 9 Jan 2026 09:28:13 +0000 Subject: [PATCH] [SqlClient] Recognise more keywords Recognise more SQL keywords to improve query summaries. Resolves #3666. --- .../CHANGELOG.md | 3 + .../CHANGELOG.md | 3 + src/Shared/SqlProcessor.cs | 80 +++++++++++++- .../SqlProcessorAdditionalTestCases.json | 103 ++++++++++++++++-- 4 files changed, 176 insertions(+), 13 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index ce5810d7ca..a427571b00 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -13,6 +13,9 @@ remove from query summaries. ([#3663](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3663)) +* Improve SQL parsing to generate query summaries for more T-SQL keywords. + ([#3671](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3671)) + ## 1.14.0-beta.2 Released 2025-Nov-14 diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md index b1652d5ee5..6fcddc0c8f 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md @@ -18,6 +18,9 @@ remove from query summaries. ([#3663](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3663)) +* Improve SQL parsing to generate query summaries for more T-SQL keywords. + ([#3671](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3671)) + ## 1.14.0-beta.1 Released 2025-Nov-13 diff --git a/src/Shared/SqlProcessor.cs b/src/Shared/SqlProcessor.cs index 2b2311ba80..184d97b83f 100644 --- a/src/Shared/SqlProcessor.cs +++ b/src/Shared/SqlProcessor.cs @@ -61,6 +61,16 @@ internal static class SqlProcessor SqlKeywordInfo.AlterKeyword, SqlKeywordInfo.DropKeyword, SqlKeywordInfo.ExecKeyword, + SqlKeywordInfo.ExecuteKeyword, + SqlKeywordInfo.GrantKeyword, + SqlKeywordInfo.DenyKeyword, + SqlKeywordInfo.TruncateKeyword, + SqlKeywordInfo.RevokeKeyword, + SqlKeywordInfo.BulkKeyword, + SqlKeywordInfo.DisableKeyword, + SqlKeywordInfo.EnableKeyword, + SqlKeywordInfo.BackupKeyword, + SqlKeywordInfo.RestoreKeyword, ]; // This is a special case used when handling sub-queries in parentheses. @@ -77,17 +87,25 @@ internal static class SqlProcessor private enum SqlKeyword { Unknown, + Backup, + Bulk, Alter, Clustered, + Connect, Create, Database, Delete, + Deny, + Disable, Distinct, Drop, + Enable, Exec, + Execute, Exists, From, Function, + Grant, If, Index, Insert, @@ -98,12 +116,16 @@ private enum SqlKeyword Not, On, Procedure, + Restore, + Revoke, Role, Schema, Select, Sequence, + Statistics, Table, Trigger, + Truncate, Unique, Union, Update, @@ -935,29 +957,41 @@ static SqlKeywordInfo() // Phase 1: Create all static instances. // We will compare the SQL we are parsing in lowercase, so we store these in lowercase also. AlterKeyword = new("alter", SqlKeyword.Alter, Unknown); + BackupKeyword = new("backup", SqlKeyword.Backup, Unknown); + BulkKeyword = new("bulk", SqlKeyword.Bulk, Unknown); + ConnectKeyword = new("connect", SqlKeyword.Connect, Unknown); CreateKeyword = new("create", SqlKeyword.Create, Unknown); - DatabaseKeyword = new("database", SqlKeyword.Database, DdlKeywords); + DatabaseKeyword = new("database", SqlKeyword.Database, [.. DdlKeywords, SqlKeyword.Backup, SqlKeyword.Restore]); DeleteKeyword = new("delete", SqlKeyword.Delete, Unknown); + DenyKeyword = new("deny", SqlKeyword.Deny, Unknown); + DisableKeyword = new("disable", SqlKeyword.Disable, Unknown); DropKeyword = new("drop", SqlKeyword.Drop, Unknown); + EnableKeyword = new("enable", SqlKeyword.Enable, Unknown); ExecKeyword = new("exec", SqlKeyword.Exec, Unknown); + ExecuteKeyword = new("execute", SqlKeyword.Execute, Unknown); ExistsKeyword = new("exists", SqlKeyword.Exists); FromKeyword = new("from", SqlKeyword.From); FunctionKeyword = new("function", SqlKeyword.Function, DdlKeywords); + GrantKeyword = new("grant", SqlKeyword.Grant, Unknown); IfKeyword = new("if", SqlKeyword.If); IndexKeyword = new("index", SqlKeyword.Index, [.. DdlKeywords, SqlKeyword.Unique, SqlKeyword.Clustered, SqlKeyword.NonClustered]); - InsertKeyword = new("insert", SqlKeyword.Insert, Unknown); + InsertKeyword = new("insert", SqlKeyword.Insert, [SqlKeyword.Unknown, SqlKeyword.Bulk]); IntoKeyword = new("into", SqlKeyword.Into); JoinKeyword = new("join", SqlKeyword.Join); LoginKeyword = new("login", SqlKeyword.Login, DdlKeywords); NotKeyword = new("not", SqlKeyword.Not); OnKeyword = new("on", SqlKeyword.On); ProcedureKeyword = new("procedure", SqlKeyword.Procedure, DdlKeywords); + RestoreKeyword = new("restore", SqlKeyword.Restore, Unknown); + RevokeKeyword = new("revoke", SqlKeyword.Revoke, Unknown); RoleKeyword = new("role", SqlKeyword.Role, DdlKeywords); SchemaKeyword = new("schema", SqlKeyword.Schema, DdlKeywords); SelectKeyword = new("select", SqlKeyword.Select, [SqlKeyword.Select, SqlKeyword.Unknown]); SequenceKeyword = new("sequence", SqlKeyword.Sequence, DdlKeywords); - TableKeyword = new("table", SqlKeyword.Table, DdlKeywords); - TriggerKeyword = new("trigger", SqlKeyword.Trigger, DdlKeywords); + StatisticsKeyword = new("statistics", SqlKeyword.Statistics, [SqlKeyword.Update]); + TableKeyword = new("table", SqlKeyword.Table, [.. DdlKeywords, SqlKeyword.Truncate]); + TriggerKeyword = new("trigger", SqlKeyword.Trigger, [.. DdlKeywords, SqlKeyword.Enable, SqlKeyword.Disable]); + TruncateKeyword = new("truncate", SqlKeyword.Truncate, Unknown); UnionKeyword = new("union", SqlKeyword.Union); UnknownKeyword = new(string.Empty, SqlKeyword.Unknown); UpdateKeyword = new("update", SqlKeyword.Update, Unknown); @@ -985,11 +1019,17 @@ static SqlKeywordInfo() // Phase 3: Wire follow relationships AlterKeyword.FollowedByKeywords = DdlSubKeywords; + BackupKeyword.FollowedByKeywords = [DatabaseKeyword]; + BulkKeyword.FollowedByKeywords = [InsertKeyword]; CreateKeyword.FollowedByKeywords = DdlSubKeywords; DatabaseKeyword.FollowedByKeywords = [IfKeyword]; + DenyKeyword.FollowedByKeywords = [ConnectKeyword]; + DisableKeyword.FollowedByKeywords = [TriggerKeyword]; DropKeyword.FollowedByKeywords = DdlSubKeywords; + EnableKeyword.FollowedByKeywords = [TriggerKeyword]; FromKeyword.FollowedByKeywords = [JoinKeyword, UnionKeyword]; FunctionKeyword.FollowedByKeywords = [IfKeyword]; + GrantKeyword.FollowedByKeywords = [ConnectKeyword]; IfKeyword.FollowedByKeywords = [NotKeyword, ExistsKeyword]; IndexKeyword.FollowedByKeywords = [OnKeyword, IfKeyword]; InsertKeyword.FollowedByKeywords = [IntoKeyword]; @@ -998,13 +1038,17 @@ static SqlKeywordInfo() NotKeyword.FollowedByKeywords = [ExistsKeyword]; OnKeyword.FollowedByKeywords = [JoinKeyword]; ProcedureKeyword.FollowedByKeywords = [IfKeyword]; + RestoreKeyword.FollowedByKeywords = [DatabaseKeyword]; + RevokeKeyword.FollowedByKeywords = [ConnectKeyword]; RoleKeyword.FollowedByKeywords = [IfKeyword]; SchemaKeyword.FollowedByKeywords = [IfKeyword, UnionKeyword]; SelectKeyword.FollowedByKeywords = [FromKeyword]; SequenceKeyword.FollowedByKeywords = [IfKeyword]; TableKeyword.FollowedByKeywords = [IfKeyword]; TriggerKeyword.FollowedByKeywords = [IfKeyword]; + TruncateKeyword.FollowedByKeywords = [TableKeyword]; UnionKeyword.FollowedByKeywords = [SelectKeyword]; + UpdateKeyword.FollowedByKeywords = [StatisticsKeyword]; UserKeyword.FollowedByKeywords = [IfKeyword]; ViewKeyword.FollowedByKeywords = [IfKeyword]; } @@ -1022,22 +1066,38 @@ private SqlKeywordInfo( public static SqlKeywordInfo AlterKeyword { get; } + public static SqlKeywordInfo BackupKeyword { get; } + + public static SqlKeywordInfo BulkKeyword { get; } + + public static SqlKeywordInfo ConnectKeyword { get; } + public static SqlKeywordInfo CreateKeyword { get; } public static SqlKeywordInfo DatabaseKeyword { get; } public static SqlKeywordInfo DeleteKeyword { get; } + public static SqlKeywordInfo DenyKeyword { get; } + + public static SqlKeywordInfo DisableKeyword { get; } + public static SqlKeywordInfo DropKeyword { get; } + public static SqlKeywordInfo EnableKeyword { get; } + public static SqlKeywordInfo ExecKeyword { get; } + public static SqlKeywordInfo ExecuteKeyword { get; } + public static SqlKeywordInfo ExistsKeyword { get; } public static SqlKeywordInfo FromKeyword { get; } public static SqlKeywordInfo FunctionKeyword { get; } + public static SqlKeywordInfo GrantKeyword { get; } + public static SqlKeywordInfo IfKeyword { get; } public static SqlKeywordInfo IndexKeyword { get; } @@ -1056,6 +1116,10 @@ private SqlKeywordInfo( public static SqlKeywordInfo ProcedureKeyword { get; } + public static SqlKeywordInfo RestoreKeyword { get; } + + public static SqlKeywordInfo RevokeKeyword { get; } + public static SqlKeywordInfo RoleKeyword { get; } public static SqlKeywordInfo SchemaKeyword { get; } @@ -1064,10 +1128,14 @@ private SqlKeywordInfo( public static SqlKeywordInfo SequenceKeyword { get; } + public static SqlKeywordInfo StatisticsKeyword { get; } + public static SqlKeywordInfo TableKeyword { get; } public static SqlKeywordInfo TriggerKeyword { get; } + public static SqlKeywordInfo TruncateKeyword { get; } + public static SqlKeywordInfo UnionKeyword { get; } public static SqlKeywordInfo UnknownKeyword { get; } @@ -1095,6 +1163,9 @@ private SqlKeywordInfo( SqlKeyword.Into => state.FirstSummaryKeyword is SqlKeyword.Insert, SqlKeyword.Join => state.FirstSummaryKeyword is SqlKeyword.Select or SqlKeyword.Join, SqlKeyword.Login or SqlKeyword.User => false, + SqlKeyword.Statistics => state.FirstSummaryKeyword is SqlKeyword.Update, + SqlKeyword.Trigger => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop or SqlKeyword.Disable or SqlKeyword.Enable, + SqlKeyword.Truncate => state.FirstSummaryKeyword is SqlKeyword.Table, SqlKeyword.Database or SqlKeyword.Function or SqlKeyword.Index or @@ -1103,7 +1174,6 @@ SqlKeyword.Role or SqlKeyword.Schema or SqlKeyword.Sequence or SqlKeyword.Table or - SqlKeyword.Trigger or SqlKeyword.View => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop, _ => false, }; diff --git a/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json b/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json index 7f86b78950..1d11617ff0 100644 --- a/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json +++ b/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json @@ -1210,8 +1210,7 @@ } }, { - "name": "revoke", - "skip": "https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/3666", + "name": "revoke_select", "input": { "db.system.name": "microsoft.sql_server", "query": "REVOKE SELECT ON SCHEMA :: Sales TO Vendors;" @@ -1220,12 +1219,24 @@ "db.query.text": [ "REVOKE SELECT ON SCHEMA :: Sales TO Vendors;" ], - "db.query.summary": "REVOKE SELECT" + "db.query.summary": "REVOKE" + } + }, + { + "name": "grant_select", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "GRANT SELECT ON SCHEMA :: Sales TO Vendors;" + }, + "expected": { + "db.query.text": [ + "GRANT SELECT ON SCHEMA :: Sales TO Vendors;" + ], + "db.query.summary": "GRANT" } }, { "name": "grant", - "skip": "https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/3666", "input": { "db.system.name": "microsoft.sql_server", "query": "GRANT EXECUTE ON TestProc TO TesterRole WITH GRANT OPTION;" @@ -1234,12 +1245,11 @@ "db.query.text": [ "GRANT EXECUTE ON TestProc TO TesterRole WITH GRANT OPTION;" ], - "db.query.summary": "GRANT EXECUTE" + "db.query.summary": "GRANT" } }, { "name": "deny", - "skip": "https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/3666", "input": { "db.system.name": "microsoft.sql_server", "query": "DENY CONNECT SQL TO Annika CASCADE;" @@ -1248,12 +1258,11 @@ "db.query.text": [ "DENY CONNECT SQL TO Annika CASCADE;" ], - "db.query.summary": "DENY CONNECT" + "db.query.summary": "DENY" } }, { "name": "truncate_table", - "skip": "https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/3666", "input": { "db.system.name": "microsoft.sql_server", "query": "TRUNCATE TABLE HumanResources.JobCandidate;" @@ -1264,5 +1273,83 @@ ], "db.query.summary": "TRUNCATE TABLE" } + }, + { + "name": "update_statistics", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "UPDATE STATISTICS Sales.SalesOrderDetail (AK_SalesOrderDetail_rowguid);" + }, + "expected": { + "db.query.text": [ + "UPDATE STATISTICS Sales.SalesOrderDetail (AK_SalesOrderDetail_rowguid);" + ], + "db.query.summary": "UPDATE STATISTICS Sales.SalesOrderDetail" + } + }, + { + "name": "backup_database", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "BACKUP DATABASE Sales TO URL = 'https://mystorageaccount.blob.core.windows.net/myfirstcontainer/Sales.bak' WITH STATS = 5;" + }, + "expected": { + "db.query.text": [ + "BACKUP DATABASE Sales TO URL = ? WITH STATS = ?;" + ], + "db.query.summary": "BACKUP DATABASE" + } + }, + { + "name": "restore_database", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "RESTORE DATABASE AdventureWorks2022 FROM AdventureWorks2022Backups;" + }, + "expected": { + "db.query.text": [ + "RESTORE DATABASE AdventureWorks2022 FROM AdventureWorks2022Backups;" + ], + "db.query.summary": "RESTORE DATABASE" + } + }, + { + "name": "bulk_insert", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "BULK INSERT my_data FROM 'C:\\data\\data.csv';" + }, + "expected": { + "db.query.text": [ + "BULK INSERT my_data FROM ?;" + ], + "db.query.summary": "BULK INSERT" + } + }, + { + "name": "disable_trigger", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "DISABLE TRIGGER Person.uAddress ON Person.Address;" + }, + "expected": { + "db.query.text": [ + "DISABLE TRIGGER Person.uAddress ON Person.Address;" + ], + "db.query.summary": "DISABLE TRIGGER Person.uAddress" + } + }, + { + "name": "enable_trigger", + "input": { + "db.system.name": "microsoft.sql_server", + "query": "ENABLE TRIGGER Person.uAddress ON Person.Address;" + }, + "expected": { + "db.query.text": [ + "ENABLE TRIGGER Person.uAddress ON Person.Address;" + ], + "db.query.summary": "ENABLE TRIGGER Person.uAddress" + } } ]