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. ///