using SQLInsight.Core.Data; using SQLInsight.Core.Findings; using SQLInsight.Rules.Engine; using SQLInsight.Rules.Performance; using static SQLInsight.Rules.Tests.TestInstances; namespace SQLInsight.Rules.Tests; public class PlanRuleTests { private static RuleResult Run(IRule rule, RuleContext context) => Assert.Single(new RuleEngine([rule]).Evaluate(context)); private static AnalyzedStatement Statement(PlanWarnings? warnings = null, long execs = 1000, long workerUs = 120_000_000, long? spills = null, long? grantKb = null, long? usedKb = null) => new() { QueryHash = "0x1234ABCD", DatabaseName = "Sales", StatementText = "SELECT * FROM dbo.Customers WHERE Code = @code", Executions = execs, TotalWorkerTimeUs = workerUs, TotalElapsedTimeUs = workerUs, TotalLogicalReads = execs * 1000, TotalSpills = spills, TotalGrantKb = grantKb, TotalUsedGrantKb = usedKb, Warnings = warnings ?? new PlanWarnings(), }; private static RuleContext Plans(params AnalyzedStatement[] statements) => With(new PlanAnalysisSnapshot { CapturedAt = Now, Statements = statements }); [Fact] public void Clean_plan_passes_all_plan_rules() { foreach (var rule in new IRule[] { new ImplicitConversionRule(), new NoJoinPredicateRule(), new ScalarUdfRule(), new SpillRule(), new ExcessiveGrantRule() }) { Assert.Equal(RuleOutcome.Passed, Run(rule, Plans(Statement(spills: 0, grantKb: 1000, usedKb: 900))).Outcome); } } [Fact] public void Implicit_conversion_lists_the_expression() { var statement = Statement(new PlanWarnings { SeekPreventingConversions = ["CONVERT_IMPLICIT(nvarchar(20),[Sales].[dbo].[Customers].[Code],0)=[@code]"] }); var finding = Assert.Single(Run(new ImplicitConversionRule(), Plans(statement)).Findings); Assert.Equal("statement: 0x1234ABCD", finding.Subject); Assert.Contains(finding.Evidence, e => e.Value.StartsWith("CONVERT_IMPLICIT", StringComparison.Ordinal)); Assert.Contains("AnsiString", finding.Recommendation, StringComparison.Ordinal); } [Fact] public void Missing_join_predicate_is_high() { Assert.Equal(Severity.High, Assert.Single(Run(new NoJoinPredicateRule(), Plans(Statement(new PlanWarnings { NoJoinPredicate = true }))).Findings).Severity); } [Fact] public void Deliberate_cross_join_is_not_reported() { var statement = Statement(new PlanWarnings { NoJoinPredicate = true }) with { StatementText = "SELECT * FROM dbo.Sizes s CROSS JOIN dbo.Colors c" }; Assert.Equal(RuleOutcome.Passed, Run(new NoJoinPredicateRule(), Plans(statement)).Outcome); } [Fact] public void Scalar_udf_only_matters_in_heavy_statements() { var udf = new PlanWarnings { ScalarUdfs = ["[Sales].[dbo].[fn_Price]"] }; Assert.Equal(RuleOutcome.Findings, Run(new ScalarUdfRule(), Plans(Statement(udf))).Outcome); Assert.Equal(RuleOutcome.Passed, Run(new ScalarUdfRule(), Plans(Statement(udf, workerUs: 5_000_000))).Outcome); } [Fact] public void Spills_per_execution_above_threshold() { Assert.Equal(RuleOutcome.Findings, Run(new SpillRule(), Plans(Statement(execs: 100, spills: 500_000))).Outcome); Assert.Equal(RuleOutcome.Passed, Run(new SpillRule(), Plans(Statement(execs: 100, spills: 5_000))).Outcome); } [Fact] public void Oversized_grant_used_under_ten_percent() { Assert.Equal(RuleOutcome.Findings, Run(new ExcessiveGrantRule(), Plans(Statement(execs: 10, grantKb: 10 * 500 * 1024, usedKb: 10 * 20 * 1024))).Outcome); Assert.Equal(RuleOutcome.Passed, Run(new ExcessiveGrantRule(), Plans(Statement(execs: 10, grantKb: 10 * 500 * 1024, usedKb: 10 * 300 * 1024))).Outcome); } [Fact] public void Memory_rules_must_be_checked_by_hand_without_the_columns() { Assert.Equal(RuleOutcome.ManualCheck, Run(new SpillRule(), WithVersion("14.0.1000.169", new PlanAnalysisSnapshot { CapturedAt = Now, Statements = [] })).Outcome); } [Fact] public void Findings_are_capped_and_heaviest_first() { var statements = Enumerable.Range(1, 30).Select(i => Statement(new PlanWarnings { NoJoinPredicate = true }, workerUs: i * 1_000_000) with { QueryHash = $"0x{i:X4}" }).ToArray(); var findings = Run(new NoJoinPredicateRule(), Plans(statements)).Findings; Assert.Equal(PlanRule.MaxFindingsPerRule, findings.Count); Assert.Equal("statement: 0x001E", findings[0].Subject); } }