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
16 changes: 10 additions & 6 deletions Dashboard/Services/PlanAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@

// Rule 27: OPTIMIZE FOR UNKNOWN in statement text
if (!string.IsNullOrEmpty(stmt.StatementText) &&
Regex.IsMatch(stmt.StatementText, @"OPTIMIZE\s+FOR\s+UNKNOWN", RegexOptions.IgnoreCase))
OptimizeForUnknownRegExp().IsMatch(stmt.StatementText))
{
stmt.PlanWarnings.Add(new PlanWarning
{
Expand Down Expand Up @@ -496,7 +496,7 @@
"Leading wildcard LIKE prevents an index seek — SQL Server must scan every row. If substring search performance is critical, consider a full-text index or a trigram-based approach.",
"CASE expression in predicate" =>
"CASE expression in a predicate prevents an index seek. Rewrite using separate WHERE clauses combined with OR, or split into multiple queries.",
_ when nonSargableReason.StartsWith("Function call") =>

Check warning on line 499 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorDashboard.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorDashboard.Models.PlanNode, PerformanceMonitorDashboard.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorDashboard.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorDashboard.Models.PlanNode, PerformanceMonitorDashboard.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorDashboard.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorDashboard.Models.PlanNode, PerformanceMonitorDashboard.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorDashboard.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorDashboard.Models.PlanNode, PerformanceMonitorDashboard.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorDashboard.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorDashboard.Models.PlanNode, PerformanceMonitorDashboard.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorDashboard.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorDashboard.Models.PlanNode, PerformanceMonitorDashboard.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)
$"{nonSargableReason} prevents an index seek. Remove the function from the column side — apply it to the parameter instead, or create a computed column with the expression and index that.",
_ =>
$"{nonSargableReason} prevents an index seek, forcing a scan."
Expand Down Expand Up @@ -785,7 +785,7 @@
{
// Check statement text for NOT IN
if (string.IsNullOrEmpty(stmt.StatementText) ||
!Regex.IsMatch(stmt.StatementText, @"\bNOT\s+IN\b", RegexOptions.IgnoreCase))
!NotInRegExp().IsMatch(stmt.StatementText))
return false;

// Walk up the tree checking ancestors and their children
Expand Down Expand Up @@ -1236,14 +1236,18 @@
return value.Length <= maxLength ? value : value[..maxLength] + "...";
}

[GeneratedRegex(@"\b(CONVERT_IMPLICIT|CONVERT|CAST|isnull|coalesce|datepart|datediff|dateadd|year|month|day|upper|lower|ltrim|rtrim|trim|substring|left|right|charindex|replace|len|datalength|abs|floor|ceiling|round|reverse|stuff|format)\s*\(", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
[GeneratedRegex(@"\b(CONVERT_IMPLICIT|CONVERT|CAST|isnull|coalesce|datepart|datediff|dateadd|year|month|day|upper|lower|ltrim|rtrim|trim|substring|left|right|charindex|replace|len|datalength|abs|floor|ceiling|round|reverse|stuff|format)\s*\(", RegexOptions.IgnoreCase)]
private static partial Regex FunctionInPredicateRegExp();
[GeneratedRegex(@"\blike\b[^'""]*?N?'%", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
[GeneratedRegex(@"\blike\b[^'""]*?N?'%", RegexOptions.IgnoreCase)]
private static partial Regex LeadingWildcardLikeRegExp();
[GeneratedRegex(@"\bCASE\s+(WHEN\b|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
[GeneratedRegex(@"\bCASE\s+(WHEN\b|$)", RegexOptions.IgnoreCase)]
private static partial Regex CaseInPredicateRegExp();
[GeneratedRegex(@"(?:\bWITH\s+|\,\s*)(\w+)\s+AS\s*\(", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
[GeneratedRegex(@"(?:\bWITH\s+|\,\s*)(\w+)\s+AS\s*\(", RegexOptions.IgnoreCase)]
private static partial Regex CteDefinitionRegExp();
[GeneratedRegex(@"\b(isnull|coalesce)\s*\(", RegexOptions.IgnoreCase)]
private static partial Regex IsNullCoalesceRegExp();
[GeneratedRegex(@"OPTIMIZE\s+FOR\s+UNKNOWN", RegexOptions.IgnoreCase)]
private static partial Regex OptimizeForUnknownRegExp();
[GeneratedRegex(@"\bNOT\s+IN\b", RegexOptions.IgnoreCase)]
private static partial Regex NotInRegExp();
}
2 changes: 1 addition & 1 deletion Installer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1799,7 +1799,7 @@ Write file
return reportPath;
}

[GeneratedRegex(@"^\s*GO\s*(?:--[^\r\n]*)?\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled)]
[GeneratedRegex(@"^\s*GO\s*(?:--[^\r\n]*)?\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline)]
private static partial Regex GoBatchRegExp();
}
}
2 changes: 1 addition & 1 deletion InstallerGui/Services/InstallationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,7 +1428,7 @@ INSERT INTO PerformanceMonitor.config.installation_history
}
}

[GeneratedRegex(@"^\d{2}[a-z]?_.*\.sql$", RegexOptions.Compiled)]
[GeneratedRegex(@"^\d{2}[a-z]?_.*\.sql$")]
private static partial Regex SqlFileRegExp();
}
}
32 changes: 19 additions & 13 deletions Lite/Services/PlanAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,12 @@
{
private static readonly Regex FunctionInPredicateRegex = FunctionInPredicateRegExp();

private static readonly Regex LeadingWildcardLikeRegex = new(
@"\blike\b[^'""]*?N?'%",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex LeadingWildcardLikeRegex = LeadingWildcardLikeRegExp();

private static readonly Regex CaseInPredicateRegex = new(
@"\bCASE\s+(WHEN\b|$)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex CaseInPredicateRegex = CaseInPredicateRegExp();

// Matches CTE definitions: WITH name AS ( or , name AS (
private static readonly Regex CteDefinitionRegex = new(
@"(?:\bWITH\s+|\,\s*)(\w+)\s+AS\s*\(",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex CteDefinitionRegex = CteDefinitionRegExp();

public static void Analyze(ParsedPlan plan)
{
Expand Down Expand Up @@ -184,7 +178,7 @@

// Rule 27: OPTIMIZE FOR UNKNOWN in statement text
if (!string.IsNullOrEmpty(stmt.StatementText) &&
Regex.IsMatch(stmt.StatementText, @"OPTIMIZE\s+FOR\s+UNKNOWN", RegexOptions.IgnoreCase))
OptimizeForUnknownRegExp().IsMatch(stmt.StatementText))
{
stmt.PlanWarnings.Add(new PlanWarning
{
Expand Down Expand Up @@ -502,7 +496,7 @@
"Leading wildcard LIKE prevents an index seek — SQL Server must scan every row. If substring search performance is critical, consider a full-text index or a trigram-based approach.",
"CASE expression in predicate" =>
"CASE expression in a predicate prevents an index seek. Rewrite using separate WHERE clauses combined with OR, or split into multiple queries.",
_ when nonSargableReason.StartsWith("Function call") =>

Check warning on line 499 in Lite/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorLite.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorLite.Models.PlanNode, PerformanceMonitorLite.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Lite/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorLite.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorLite.Models.PlanNode, PerformanceMonitorLite.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Lite/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorLite.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorLite.Models.PlanNode, PerformanceMonitorLite.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Lite/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorLite.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorLite.Models.PlanNode, PerformanceMonitorLite.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Lite/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorLite.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorLite.Models.PlanNode, PerformanceMonitorLite.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)

Check warning on line 499 in Lite/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

The behavior of 'string.StartsWith(string)' could vary based on the current user's locale settings. Replace this call in 'PerformanceMonitorLite.Services.PlanAnalyzer.AnalyzeNode(PerformanceMonitorLite.Models.PlanNode, PerformanceMonitorLite.Models.PlanStatement)' with a call to 'string.StartsWith(string, System.StringComparison)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310)
$"{nonSargableReason} prevents an index seek. Remove the function from the column side — apply it to the parameter instead, or create a computed column with the expression and index that.",
_ =>
$"{nonSargableReason} prevents an index seek, forcing a scan."
Expand Down Expand Up @@ -791,7 +785,7 @@
{
// Check statement text for NOT IN
if (string.IsNullOrEmpty(stmt.StatementText) ||
!Regex.IsMatch(stmt.StatementText, @"\bNOT\s+IN\b", RegexOptions.IgnoreCase))
!NotInRegExp().IsMatch(stmt.StatementText))
return false;

// Walk up the tree checking ancestors and their children
Expand Down Expand Up @@ -888,7 +882,7 @@
return "Implicit conversion (CONVERT_IMPLICIT)";

// ISNULL / COALESCE wrapping column
if (Regex.IsMatch(predicate, @"\b(isnull|coalesce)\s*\(", RegexOptions.IgnoreCase))
if (IsNullCoalesceRegExp().IsMatch(predicate))
return "ISNULL/COALESCE wrapping column";

// Common function calls on columns
Expand Down Expand Up @@ -1242,6 +1236,18 @@
return value.Length <= maxLength ? value : value[..maxLength] + "...";
}

[GeneratedRegex(@"\b(CONVERT_IMPLICIT|CONVERT|CAST|isnull|coalesce|datepart|datediff|dateadd|year|month|day|upper|lower|ltrim|rtrim|trim|substring|left|right|charindex|replace|len|datalength|abs|floor|ceiling|round|reverse|stuff|format)\s*\(", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
[GeneratedRegex(@"\b(CONVERT_IMPLICIT|CONVERT|CAST|isnull|coalesce|datepart|datediff|dateadd|year|month|day|upper|lower|ltrim|rtrim|trim|substring|left|right|charindex|replace|len|datalength|abs|floor|ceiling|round|reverse|stuff|format)\s*\(", RegexOptions.IgnoreCase)]
private static partial Regex FunctionInPredicateRegExp();
[GeneratedRegex(@"\blike\b[^'""]*?N?'%", RegexOptions.IgnoreCase)]
private static partial Regex LeadingWildcardLikeRegExp();
[GeneratedRegex(@"\bCASE\s+(WHEN\b|$)", RegexOptions.IgnoreCase)]
private static partial Regex CaseInPredicateRegExp();
[GeneratedRegex(@"(?:\bWITH\s+|\,\s*)(\w+)\s+AS\s*\(", RegexOptions.IgnoreCase)]
private static partial Regex CteDefinitionRegExp();
[GeneratedRegex(@"\b(isnull|coalesce)\s*\(", RegexOptions.IgnoreCase)]
private static partial Regex IsNullCoalesceRegExp();
[GeneratedRegex(@"OPTIMIZE\s+FOR\s+UNKNOWN", RegexOptions.IgnoreCase)]
private static partial Regex OptimizeForUnknownRegExp();
[GeneratedRegex(@"\bNOT\s+IN\b", RegexOptions.IgnoreCase)]
private static partial Regex NotInRegExp();
}