Skip to content

Commit dcec793

Browse files
Mask comments and literals in rule 38 and the parameters panel (#579)
Review of #582 found rule 38 still matched MAXDOP 2 against the raw text, so a MAXDOP 2 mentioned in a comment suppressed the Standard Edition DOP warning. The app's parameters panel had the same raw OPTIMIZE FOR UNKNOWN check for its annotation. Both now use MaskCommentsAndLiterals, which is internal so the app can call it. Add direct tests for the helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
1 parent cc18844 commit dcec793

4 files changed

Lines changed: 45 additions & 5 deletions

File tree

‎src/PlanViewer.App/Controls/PlanViewerControl.Parameters.cs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@ private void ShowParameters(PlanStatement statement)
131131
// Annotations
132132
if (allCompiledNull && parameters.Count > 0)
133133
{
134+
// #579: the phrase inside a string literal or a comment is not a hint.
134135
var hasOptimizeForUnknown = statement.StatementText
135136
.Contains("OPTIMIZE", StringComparison.OrdinalIgnoreCase)
136-
&& Regex.IsMatch(statement.StatementText, @"OPTIMIZE\s+FOR\s+UNKNOWN", RegexOptions.IgnoreCase);
137+
&& Regex.IsMatch(PlanAnalyzer.MaskCommentsAndLiterals(statement.StatementText),
138+
@"OPTIMIZE\s+FOR\s+UNKNOWN", RegexOptions.IgnoreCase);
137139

138140
if (hasOptimizeForUnknown)
139141
{

‎src/PlanViewer.Core/Services/PlanAnalyzer.Helpers.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,15 +281,15 @@ private static string FormatNodeRef(PlanNode node)
281281
return $"{node.PhysicalOp} (Node {node.NodeId})";
282282
}
283283

284-
/* #579: the rules that look for a hint or a keyword in the query text (MAXDOP 1, RECOMPILE,
285-
OPTIMIZE FOR UNKNOWN, NOT IN, a cursor declaration, a row goal's cause) matched the raw
284+
/* #579: the rules that look for a hint or a keyword in the query text (MAXDOP 1, MAXDOP 2,
285+
RECOMPILE, OPTIMIZE FOR UNKNOWN, NOT IN, a cursor declaration, a row goal's cause) matched the raw
286286
StatementText, so the words inside a string literal or a comment counted as code. This
287287
blanks string-literal contents and whole comments with spaces, keeping every other character
288288
where it was, so a match in the result is a match in the code. The scan follows
289289
ParameterSubstitution's: '...' with doubled-quote escapes, -- to the end of the line, and
290290
block comments, which nest in T-SQL. Delimited identifiers ("..." and [...]) are stepped
291291
over unchanged, so a quote or a dash inside one does not start a string or a comment. */
292-
private static string MaskCommentsAndLiterals(string? text)
292+
internal static string MaskCommentsAndLiterals(string? text)
293293
{
294294
if (string.IsNullOrEmpty(text))
295295
return "";

‎src/PlanViewer.Core/Services/PlanAnalyzer.Statement.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ private static void Rule38_StandardEditionDop(PlanStatement stmt, AnalyzerConfig
408408
// Suppress when the user explicitly set MAXDOP 2 as a query hint — the DOP
409409
// cap is intentional, not the Standard Edition batch-mode limitation.
410410
var hasMaxdop2Hint = !string.IsNullOrEmpty(stmt.StatementText)
411-
&& Regex.IsMatch(stmt.StatementText, @"MAXDOP\s+2\b", RegexOptions.IgnoreCase);
411+
&& Regex.IsMatch(MaskCommentsAndLiterals(stmt.StatementText), @"MAXDOP\s+2\b", RegexOptions.IgnoreCase); // #579
412412

413413
if (!hasMaxdop2Hint)
414414
{

‎tests/PlanViewer.Core.Tests/AnalyzerRuleGateTests.cs‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,21 @@ public void Rule06_Rule3ExplainsTheUdf_OneFindingNotTwo()
111111

112112
// ---- #579: text inside string literals and comments is not code -------------------------
113113

114+
[Theory]
115+
// String contents and whole comments become spaces; code, quotes and [identifiers] stay put.
116+
[InlineData("SELECT [it's] /* x */ 'y' -- z\nFROM t", "SELECT [it's] ' ' \nFROM t")]
117+
// Block comments nest in T-SQL: the first */ closes only the inner one.
118+
[InlineData("a /* 1 /* 2 */ 3 */ b", "a b")]
119+
// An unclosed string runs to the end, as SQL Server would read it.
120+
[InlineData("SELECT 'abc", "SELECT ' ")]
121+
public void MaskCommentsAndLiterals_BlanksOnlyLiteralsAndComments(string text, string expected)
122+
{
123+
var masked = PlanAnalyzer.MaskCommentsAndLiterals(text);
124+
125+
Assert.Equal(expected, masked);
126+
Assert.Equal(text.Length, masked.Length);
127+
}
128+
114129
[Theory]
115130
[InlineData("SELECT 'OPTIMIZE FOR UNKNOWN'")]
116131
[InlineData("SELECT N'it''s OPTIMIZE FOR UNKNOWN'")]
@@ -158,6 +173,29 @@ public void Rule03_Maxdop1InACommentIsNotAQueryHint(string text, bool expectWarn
158173
Assert.Equal(expectWarning, Has(stmt, "Serial Plan"));
159174
}
160175

176+
[Theory]
177+
[InlineData("SELECT a FROM dbo.t -- OPTION (MAXDOP 2)", true)]
178+
[InlineData("SELECT a FROM dbo.t OPTION (MAXDOP 2)", false)]
179+
public void Rule38_Maxdop2InACommentDoesNotExplainTheDopCap(string text, bool expectWarning)
180+
{
181+
// A MAXDOP 2 hint makes DOP 2 intentional, so rule 38 stays quiet. A MAXDOP 2 that is
182+
// only mentioned in a comment is not a hint. No server metadata: the edition is unknown.
183+
var stmt = new PlanStatement
184+
{
185+
DegreeOfParallelism = 2,
186+
StatementText = text,
187+
RootNode = new PlanNode
188+
{
189+
PhysicalOp = "Hash Match",
190+
LogicalOp = "Aggregate",
191+
ActualExecutionMode = "Batch"
192+
}
193+
};
194+
Analyze(stmt);
195+
196+
Assert.Equal(expectWarning, Has(stmt, "Standard Edition DOP Limitation"));
197+
}
198+
161199
[Theory]
162200
[InlineData("SELECT a FROM dbo.t WHERE b = @b /* OPTION (RECOMPILE) */", true)]
163201
[InlineData("SELECT a FROM dbo.t WHERE b = @b OPTION (RECOMPILE)", false)]

0 commit comments

Comments
 (0)