sqlinsight-pro/tests/SQLInsight.Collectors.Tests/ShowplanAnalyzerTests.cs
Claude f1d3f5188e
Some checks are pending
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
Fase 2: plan cache analysis
Collector takes the 50 heaviest statements (CPU, last 24 h), fetches
statement plans as text (dm_exec_query_plan returns NULL for deeply
nested plans) and analyzes them in C#. Rules:
- SQLI-PLAN-001 implicit conversion preventing an index seek
- SQLI-PLAN-002 join without predicate (deliberate CROSS JOIN excluded)
- SQLI-PLAN-003 non-inlined scalar UDF in a heavy statement
- SQLI-PLAN-004 spills to tempdb, SQLI-PLAN-005 oversized memory grants
  (2016 SP2+ / 2017 CU3+ columns, new capability)
Verified against real plans in SQL Server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
2026-09-29 07:40:09 +00:00

54 lines
2.2 KiB
C#

using SQLInsight.Collectors.Performance;
namespace SQLInsight.Collectors.Tests;
public class ShowplanAnalyzerTests
{
private const string Plan = """
<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.564" Build="16.0.4295.3">
<BatchSequence><Batch><Statements><StmtSimple StatementText="SELECT ...">
<QueryPlan>
<Warnings>
<PlanAffectingConvert ConvertIssue="Cardinality Estimate" Expression="CONVERT_IMPLICIT(nvarchar(20),[x].[Code],0)" />
<PlanAffectingConvert ConvertIssue="Seek Plan" Expression="CONVERT_IMPLICIT(nvarchar(20),[x].[Code],0)=[@code]" />
</Warnings>
<RelOp PhysicalOp="Nested Loops">
<Warnings NoJoinPredicate="true">
<ColumnsWithNoStatistics><ColumnReference Table="[Orders]" Column="Status" /></ColumnsWithNoStatistics>
</Warnings>
<ComputeScalar><DefinedValues><DefinedValue><ScalarOperator>
<UserDefinedFunction FunctionName="[Sales].[dbo].[fn_Price]" />
</ScalarOperator></DefinedValue></DefinedValues></ComputeScalar>
</RelOp>
</QueryPlan>
</StmtSimple></Statements></Batch></BatchSequence>
</ShowPlanXML>
""";
[Fact]
public void Finds_the_warnings_that_matter()
{
var warnings = ShowplanAnalyzer.Analyze(Plan);
Assert.Equal(["CONVERT_IMPLICIT(nvarchar(20),[x].[Code],0)=[@code]"], warnings.SeekPreventingConversions);
Assert.True(warnings.NoJoinPredicate);
Assert.Equal(["[Sales].[dbo].[fn_Price]"], warnings.ScalarUdfs);
Assert.Equal(["[Orders].Status"], warnings.ColumnsWithNoStatistics);
Assert.False(warnings.NotAnalyzed);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("<not-closed")]
public void Missing_or_broken_plans_are_marked_not_analyzed(string? plan)
{
Assert.True(ShowplanAnalyzer.Analyze(plan).NotAnalyzed);
}
[Fact]
public void Dtd_is_rejected()
{
Assert.True(ShowplanAnalyzer.Analyze("<!DOCTYPE x [<!ENTITY e SYSTEM \"file:///etc/passwd\">]><x>&e;</x>").NotAnalyzed);
}
}