From 25e15aab10d72d8fc09fe5305531ad31946eb2c3 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:41:47 -0500 Subject: [PATCH] Rule 15: Restore join ancestor check alongside Merge Interval Merge Interval + TopN Sort is the strongest signal for OR expansion, but the join ancestor check is a broader catch that shouldn't have been removed. Now fires on either condition. Co-Authored-By: Claude Opus 4.6 --- Dashboard/Services/PlanAnalyzer.cs | 23 ++++++++++++++++++++--- Lite/Services/PlanAnalyzer.cs | 23 ++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Dashboard/Services/PlanAnalyzer.cs b/Dashboard/Services/PlanAnalyzer.cs index e96cf0c2..f49abb35 100644 --- a/Dashboard/Services/PlanAnalyzer.cs +++ b/Dashboard/Services/PlanAnalyzer.cs @@ -399,15 +399,16 @@ private static void AnalyzeNode(PlanNode node, PlanStatement stmt) } } - // Rule 15: Join OR clause (Concatenation + Constant Scan under Merge Interval) - // Pattern: Merge Interval → TopN Sort → Concatenation → Constant Scans + // Rule 15: Join OR clause (Concatenation + Constant Scan under join/Merge Interval) + // Best signal: Merge Interval → TopN Sort → Concatenation → Constant Scans + // Also fires under a join ancestor (broader catch) 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 && HasMergeIntervalAncestor(node)) + if (constantScanBranches >= 2 && (HasMergeIntervalAncestor(node) || HasJoinAncestor(node))) { node.Warnings.Add(new PlanWarning { @@ -626,6 +627,22 @@ private static bool HasMergeIntervalAncestor(PlanNode node) return false; } + /// + /// Checks whether a node has any join ancestor. + /// + private static bool HasJoinAncestor(PlanNode node) + { + var ancestor = node.Parent; + while (ancestor != null) + { + if (ancestor.LogicalOp != null && + ancestor.LogicalOp.Contains("Join", StringComparison.OrdinalIgnoreCase)) + return true; + ancestor = ancestor.Parent; + } + return false; + } + /// /// Finds Sort and Hash Match operators in the tree that consume memory. /// diff --git a/Lite/Services/PlanAnalyzer.cs b/Lite/Services/PlanAnalyzer.cs index e9652625..f87f5be3 100644 --- a/Lite/Services/PlanAnalyzer.cs +++ b/Lite/Services/PlanAnalyzer.cs @@ -399,15 +399,16 @@ private static void AnalyzeNode(PlanNode node, PlanStatement stmt) } } - // Rule 15: Join OR clause (Concatenation + Constant Scan under Merge Interval) - // Pattern: Merge Interval → TopN Sort → Concatenation → Constant Scans + // Rule 15: Join OR clause (Concatenation + Constant Scan under join/Merge Interval) + // Best signal: Merge Interval → TopN Sort → Concatenation → Constant Scans + // Also fires under a join ancestor (broader catch) 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 && HasMergeIntervalAncestor(node)) + if (constantScanBranches >= 2 && (HasMergeIntervalAncestor(node) || HasJoinAncestor(node))) { node.Warnings.Add(new PlanWarning { @@ -626,6 +627,22 @@ private static bool HasMergeIntervalAncestor(PlanNode node) return false; } + /// + /// Checks whether a node has any join ancestor. + /// + private static bool HasJoinAncestor(PlanNode node) + { + var ancestor = node.Parent; + while (ancestor != null) + { + if (ancestor.LogicalOp != null && + ancestor.LogicalOp.Contains("Join", StringComparison.OrdinalIgnoreCase)) + return true; + ancestor = ancestor.Parent; + } + return false; + } + /// /// Finds Sort and Hash Match operators in the tree that consume memory. ///