From 9b62a27d5cd0d04bba8efa7a3d661cc99baf2e52 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:32:11 -0500 Subject: [PATCH] Rules 13-15: GetRangeThroughConvert, lazy spool threshold, OR expansion detection Rule 13: Detect GetRangeThroughConvert (CONVERT/CAST on column) in addition to GetRangeWithMismatchedTypes, with distinct messages. Rule 14: Warn when cache hits (rewinds) < 5x cache misses (rebinds). Critical when hits < misses (net negative). Replaces confusing rebinds*2 >= rewinds condition. Rule 15: Use Merge Interval ancestor instead of join ancestor to detect OR expansion. Merge Interval + TopN Sort is the definitive fingerprint for this pattern. Co-Authored-By: Claude Opus 4.6 --- Dashboard/Services/PlanAnalyzer.cs | 12 ++++++------ Lite/Services/PlanAnalyzer.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Dashboard/Services/PlanAnalyzer.cs b/Dashboard/Services/PlanAnalyzer.cs index a4058a04..e96cf0c2 100644 --- a/Dashboard/Services/PlanAnalyzer.cs +++ b/Dashboard/Services/PlanAnalyzer.cs @@ -399,15 +399,15 @@ private static void AnalyzeNode(PlanNode node, PlanStatement stmt) } } - // Rule 15: Join OR clause (Concatenation + Constant Scan pattern) - // Pattern: Concatenation → Compute Scalar → Constant Scan (one per OR branch) + // Rule 15: Join OR clause (Concatenation + Constant Scan under Merge Interval) + // Pattern: Merge Interval → TopN Sort → Concatenation → Constant Scans if (node.PhysicalOp == "Concatenation") { var constantScanBranches = node.Children .Count(c => c.PhysicalOp == "Constant Scan" || c.Children.Any(gc => gc.PhysicalOp == "Constant Scan")); - if (constantScanBranches >= 2 && HasJoinAncestor(node)) + if (constantScanBranches >= 2 && HasMergeIntervalAncestor(node)) { node.Warnings.Add(new PlanWarning { @@ -612,14 +612,14 @@ private static void DetectMultiReferenceCte(PlanStatement stmt) } /// - /// Checks whether a node has a join operator as an ancestor. + /// Checks whether a node has a Merge Interval ancestor (OR expansion pattern). /// - private static bool HasJoinAncestor(PlanNode node) + private static bool HasMergeIntervalAncestor(PlanNode node) { var ancestor = node.Parent; while (ancestor != null) { - if (ancestor.LogicalOp.Contains("Join", StringComparison.OrdinalIgnoreCase)) + if (ancestor.PhysicalOp == "Merge Interval") return true; ancestor = ancestor.Parent; } diff --git a/Lite/Services/PlanAnalyzer.cs b/Lite/Services/PlanAnalyzer.cs index 5760afaa..e9652625 100644 --- a/Lite/Services/PlanAnalyzer.cs +++ b/Lite/Services/PlanAnalyzer.cs @@ -399,15 +399,15 @@ private static void AnalyzeNode(PlanNode node, PlanStatement stmt) } } - // Rule 15: Join OR clause (Concatenation + Constant Scan pattern) - // Pattern: Concatenation → Compute Scalar → Constant Scan (one per OR branch) + // Rule 15: Join OR clause (Concatenation + Constant Scan under Merge Interval) + // Pattern: Merge Interval → TopN Sort → Concatenation → Constant Scans if (node.PhysicalOp == "Concatenation") { var constantScanBranches = node.Children .Count(c => c.PhysicalOp == "Constant Scan" || c.Children.Any(gc => gc.PhysicalOp == "Constant Scan")); - if (constantScanBranches >= 2 && HasJoinAncestor(node)) + if (constantScanBranches >= 2 && HasMergeIntervalAncestor(node)) { node.Warnings.Add(new PlanWarning { @@ -612,14 +612,14 @@ private static void DetectMultiReferenceCte(PlanStatement stmt) } /// - /// Checks whether a node has a join operator as an ancestor. + /// Checks whether a node has a Merge Interval ancestor (OR expansion pattern). /// - private static bool HasJoinAncestor(PlanNode node) + private static bool HasMergeIntervalAncestor(PlanNode node) { var ancestor = node.Parent; while (ancestor != null) { - if (ancestor.LogicalOp.Contains("Join", StringComparison.OrdinalIgnoreCase)) + if (ancestor.PhysicalOp == "Merge Interval") return true; ancestor = ancestor.Parent; }