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
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
54 lines
2.2 KiB
C#
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);
|
|
}
|
|
}
|