Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
cases for escaped identifiers. Optimize performance of parsing logic.
([#3627](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3627))

* Sanitize the object name for SQL query text using the LOGIN or USER keywords and
remove from query summaries.
([#3663](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3663))

## 1.14.0-beta.2

Released 2025-Nov-14
Expand Down
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
cases for escaped identifiers. Optimize performance of parsing logic.
([#3627](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3627))

* Sanitize the object name for SQL query text using the LOGIN or USER keywords and
remove from query summaries.
([#3663](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3663))

## 1.14.0-beta.1

Released 2025-Nov-13
Expand Down
45 changes: 39 additions & 6 deletions src/Shared/SqlProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private enum SqlKeyword
Insert,
Into,
Join,
Login,
NonClustered,
Not,
On,
Expand Down Expand Up @@ -422,6 +423,7 @@ private static void ParseNextToken(
}

state.CaptureNextNonKeywordTokenAsIdentifier = SqlKeywordInfo.CaptureNextTokenInSummary(in state, potentialKeywordInfo.SqlKeyword);
state.SanitizeNextNonKeywordToken = SqlKeywordInfo.SanitizeNextToken(in state, potentialKeywordInfo.SqlKeyword);
state.InFromClause = potentialKeywordInfo.SqlKeyword == SqlKeyword.From || (state.PreviousParsedKeyword?.SqlKeyword == SqlKeyword.From && state.CaptureNextNonKeywordTokenAsIdentifier);
state.PreviousParsedKeyword = potentialKeywordInfo;
state.ParsePosition += keywordLength;
Expand Down Expand Up @@ -484,8 +486,15 @@ private static void ParseNextToken(
}
}

sql.Slice(start, length).CopyTo(buffer.Slice(state.SanitizedPosition));
state.SanitizedPosition += length;
if (state.SanitizeNextNonKeywordToken)
{
buffer[state.SanitizedPosition++] = SanitizationPlaceholder;
}
else
{
sql.Slice(start, length).CopyTo(buffer.Slice(state.SanitizedPosition));
state.SanitizedPosition += length;
}

// Optionally copy to summary buffer.
if (state.CaptureNextNonKeywordTokenAsIdentifier)
Expand All @@ -500,6 +509,7 @@ private static void ParseNextToken(

state.ParsePosition = i;
state.CaptureNextNonKeywordTokenAsIdentifier = false;
state.SanitizeNextNonKeywordToken = false;
state.PreviousTokenStartPosition = start;
state.PreviousTokenEndPosition = i;
}
Expand Down Expand Up @@ -881,6 +891,8 @@ private ref struct ParseState

public bool CaptureNextNonKeywordTokenAsIdentifier; // 1 byte

public bool SanitizeNextNonKeywordToken; // 1 byte

/// <summary>
/// Used to track if we are in an escaped identifier (e.g., "[table]").
/// </summary>
Expand Down Expand Up @@ -926,6 +938,7 @@ static SqlKeywordInfo()
InsertKeyword = new("insert", SqlKeyword.Insert, Unknown);
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);
Expand All @@ -952,6 +965,7 @@ static SqlKeywordInfo()
ProcedureKeyword,
TriggerKeyword,
DatabaseKeyword,
LoginKeyword,
UserKeyword,
RoleKeyword,
SequenceKeyword,
Expand All @@ -970,6 +984,7 @@ static SqlKeywordInfo()
IndexKeyword.FollowedByKeywords = [OnKeyword, IfKeyword];
InsertKeyword.FollowedByKeywords = [IntoKeyword];
JoinKeyword.FollowedByKeywords = [OnKeyword];
LoginKeyword.FollowedByKeywords = [IfKeyword];
NotKeyword.FollowedByKeywords = [ExistsKeyword];
OnKeyword.FollowedByKeywords = [JoinKeyword];
ProcedureKeyword.FollowedByKeywords = [IfKeyword];
Expand Down Expand Up @@ -1023,6 +1038,8 @@ private SqlKeywordInfo(

public static SqlKeywordInfo JoinKeyword { get; }

public static SqlKeywordInfo LoginKeyword { get; }

public static SqlKeywordInfo NotKeyword { get; }

public static SqlKeywordInfo OnKeyword { get; }
Expand Down Expand Up @@ -1063,13 +1080,29 @@ private SqlKeywordInfo(
public static bool CaptureNextTokenInSummary(in ParseState state, SqlKeyword currentKeyword) => currentKeyword switch
{
SqlKeyword.Exec => true,
SqlKeyword.Exists => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop && state.PreviousSummaryKeyword is not (SqlKeyword.Login or SqlKeyword.User),
Comment thread
martincostello marked this conversation as resolved.
SqlKeyword.From => state.PreviousSummaryKeyword is SqlKeyword.Select or SqlKeyword.Distinct,
SqlKeyword.Into => state.FirstSummaryKeyword is SqlKeyword.Insert,
SqlKeyword.Join => state.FirstSummaryKeyword is SqlKeyword.Select or SqlKeyword.Join,
SqlKeyword.Database or SqlKeyword.Schema or SqlKeyword.Table or SqlKeyword.Index or SqlKeyword.View
or SqlKeyword.Procedure or SqlKeyword.Trigger or SqlKeyword.Function or SqlKeyword.User
or SqlKeyword.Role or SqlKeyword.Sequence => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop,
SqlKeyword.Exists => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop,
SqlKeyword.Login or SqlKeyword.User => false,
SqlKeyword.Database or
SqlKeyword.Function or
SqlKeyword.Index or
SqlKeyword.Procedure or
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,
};

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool SanitizeNextToken(in ParseState state, SqlKeyword currentKeyword) => currentKeyword switch
{
SqlKeyword.Login or SqlKeyword.User => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop,
SqlKeyword.Exists => state.PreviousSummaryKeyword is SqlKeyword.Login or SqlKeyword.User,
_ => false,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,9 @@
},
"expected": {
"db.query.text": [
"CREATE USER johndoe WITH PASSWORD = ?;"
"CREATE USER ? WITH PASSWORD = ?;"
],
"db.query.summary": "CREATE USER johndoe"
"db.query.summary": "CREATE USER"
}
},
{
Expand All @@ -493,9 +493,9 @@
},
"expected": {
"db.query.text": [
"ALTER USER johndoe WITH PASSWORD = ?;"
"ALTER USER ? WITH PASSWORD = ?;"
],
"db.query.summary": "ALTER USER johndoe"
"db.query.summary": "ALTER USER"
}
},
{
Expand All @@ -506,9 +506,48 @@
},
"expected": {
"db.query.text": [
"DROP USER IF EXISTS johndoe;"
"DROP USER IF EXISTS ?;"
],
"db.query.summary": "DROP USER johndoe"
"db.query.summary": "DROP USER"
}
},
{
"name": "create_login",
"input": {
"db.system.name": "other_sql",
"query": "CREATE LOGIN johndoe WITH PASSWORD = 'password';"
},
"expected": {
"db.query.text": [
"CREATE LOGIN ? WITH PASSWORD = ?;"
],
"db.query.summary": "CREATE LOGIN"
}
},
{
"name": "alter_login",
"input": {
"db.system.name": "other_sql",
"query": "ALTER LOGIN johndoe WITH PASSWORD = 'newpassword';"
},
"expected": {
"db.query.text": [
"ALTER LOGIN ? WITH PASSWORD = ?;"
],
"db.query.summary": "ALTER LOGIN"
}
},
{
"name": "drop_login",
"input": {
"db.system.name": "other_sql",
"query": "DROP LOGIN IF EXISTS johndoe;"
},
"expected": {
"db.query.text": [
"DROP LOGIN IF EXISTS ?;"
],
"db.query.summary": "DROP LOGIN"
}
},
{
Expand Down
Loading