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 QueryStoreRuleTests { private static RuleResult Run(IRule rule, RuleContext context) => Assert.Single(new RuleEngine([rule]).Evaluate(context)); private static QueryPlanStats Plan(long query, long plan, bool recent, long execs, double avgMs, double maxMs = 0, double stdevMs = 0) => new() { QueryId = query, PlanId = plan, IsRecent = recent, Executions = execs, TotalDurationUs = execs * avgMs * 1000, TotalCpuUs = execs * avgMs * 800, TotalLogicalReads = execs * 1000, MaxStdevDurationUs = stdevMs * 1000, MaxDurationUs = (maxMs == 0 ? avgMs : maxMs) * 1000, MinDurationUs = avgMs * 500, QueryText = "SELECT * FROM dbo.Orders WHERE CustomerId = @p0", ObjectName = "dbo.GetOrders", }; private static RuleContext Store(params QueryPlanStats[] plans) => With(new QueryStoreSnapshot { CapturedAt = Now, Databases = [new QueryStoreDatabase { Name = "Sales", Plans = plans }] }); [Fact] public void New_slow_plan_is_a_regression_with_force_script() { var result = Run(new PlanRegressionRule(), Store( Plan(7, 1, recent: false, execs: 5000, avgMs: 20), Plan(7, 1, recent: true, execs: 200, avgMs: 20), Plan(7, 2, recent: true, execs: 3000, avgMs: 400))); var finding = Assert.Single(result.Findings); Assert.Equal(Severity.High, finding.Severity); // 3000 × 380 ms = 1140 s extra Assert.Equal("query: Sales/7", finding.Subject); Assert.Contains("@query_id = 7, @plan_id = 1", finding.FixScript, StringComparison.Ordinal); Assert.StartsWith("USE [Sales];", finding.FixScript, StringComparison.Ordinal); } [Fact] public void Slowdown_too_small_to_matter_is_ignored() { // 3× slower, but only 20 executions × 40 ms = 0.8 s extra per day. var result = Run(new PlanRegressionRule(), Store( Plan(7, 1, recent: false, execs: 100, avgMs: 20), Plan(7, 2, recent: true, execs: 20, avgMs: 60))); Assert.Equal(RuleOutcome.Passed, result.Outcome); } [Fact] public void Same_plan_getting_slower_is_not_a_plan_regression() { var result = Run(new PlanRegressionRule(), Store( Plan(7, 1, recent: false, execs: 5000, avgMs: 20), Plan(7, 1, recent: true, execs: 5000, avgMs: 200))); Assert.Equal(RuleOutcome.Passed, result.Outcome); } [Fact] public void Failed_forced_plan_is_high_with_unforce_script() { var context = With(new QueryStoreSnapshot { CapturedAt = Now, Databases = [new QueryStoreDatabase { Name = "Sales", Plans = [], FailedForcedPlans = [new FailedForcedPlan(7, 3, 12, "NO_INDEX", "SELECT 1")] }], }); var finding = Assert.Single(Run(new FailedForcedPlanRule(), context).Findings); Assert.Equal(Severity.High, finding.Severity); Assert.Contains("Genopret det index", finding.Recommendation, StringComparison.Ordinal); Assert.Contains("sp_query_store_unforce_plan @query_id = 7, @plan_id = 3", finding.FixScript, StringComparison.Ordinal); } [Fact] public void Plans_with_very_different_durations_indicate_parameter_sensitivity() { var finding = Assert.Single(Run(new ParameterSensitivityRule(), Store( Plan(9, 1, recent: true, execs: 900, avgMs: 5), Plan(9, 2, recent: true, execs: 100, avgMs: 2000))).Findings); Assert.Contains("2 planer", finding.Explanation, StringComparison.Ordinal); Assert.Contains("sp_query_store_set_hints", finding.FixScript, StringComparison.Ordinal); // 2022 test instance } [Fact] public void Volatile_single_plan_indicates_parameter_sensitivity() { var result = Run(new ParameterSensitivityRule(), Store(Plan(9, 1, recent: true, execs: 5000, avgMs: 50, maxMs: 8000, stdevMs: 300))); Assert.Equal(RuleOutcome.Findings, result.Outcome); } [Fact] public void Stable_query_is_not_parameter_sensitive() { var result = Run(new ParameterSensitivityRule(), Store(Plan(9, 1, recent: true, execs: 5000, avgMs: 50, maxMs: 120, stdevMs: 10))); Assert.Equal(RuleOutcome.Passed, result.Outcome); } [Fact] public void No_hints_script_before_2022() { var context = WithVersion("15.0.4490.9", new QueryStoreSnapshot { CapturedAt = Now, Databases = [new QueryStoreDatabase { Name = "Sales", Plans = [Plan(9, 1, true, 900, 5), Plan(9, 2, true, 100, 2000)] }], }); Assert.Null(Assert.Single(Run(new ParameterSensitivityRule(), context).Findings).FixScript); } [Fact] public void Query_store_rules_are_not_applicable_before_2016() { var result = Run(new PlanRegressionRule(), WithVersion("12.0.6024.0")); Assert.Equal(RuleOutcome.NotApplicable, result.Outcome); } }