// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System.Collections.Generic; using System.Linq; namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph.Comparison { /// /// Handles operations for creating and comparing skeletons of showplan trees /// A skeleton is the tree with some nodes filtered out to reduce complexity /// public class SkeletonManager { public SkeletonManager() { } /// /// Constructs a skeleton tree representing logical structure of the showplan /// Primarily represents joins and data access operators /// /// Node to construct skeleton of /// SkeletonNode with children representing logical descendants of the input node public SkeletonNode CreateSkeleton(Node root) { Node rootNode = root; var childCount = root.Children.Count; if (childCount > 1) { SkeletonNode skeletonParent = new SkeletonNode(root); foreach (Node child in root.Children) { SkeletonNode skeletonChild = CreateSkeleton(child); skeletonParent.AddChild(skeletonChild); } return skeletonParent; } else if (childCount == 1) { if (!ShouldIgnoreDuringComparison(rootNode)) { // get children recursively then return this node to add it to the skeleton SkeletonNode skeletonParent = new SkeletonNode(root); SkeletonNode child = CreateSkeleton(root.Children.ElementAt(0)); skeletonParent.AddChild(child); return skeletonParent; } // if ignoring root, just go on to the next node return CreateSkeleton(root.Children.First()); } // no children; base case SkeletonNode skeletonNode = new SkeletonNode(root); return skeletonNode; } /// /// Checks root and all children for equivalent tree structure and logical equivalence at the node level /// /// /// /// public bool AreSkeletonsEquivalent(SkeletonNode root1, SkeletonNode root2, bool ignoreDatabaseName) { if (root1 == null && root2 == null) return true; if (root1 == null || root2 == null) return false; if (!root1.BaseNode.IsLogicallyEquivalentTo(root2.BaseNode, ignoreDatabaseName)) { return false; } if (root1.Children.Count != root2.Children.Count) { return false; } var childIterator = 0; while (childIterator < root1.Children.Count) { var checkMatch = AreSkeletonsEquivalent(root1.Children.ElementAt(childIterator), root2.Children.ElementAt(childIterator), ignoreDatabaseName); if (!checkMatch) { // at least one pair of children (ie inner.Child1 & outer.Child1) didn't match; stop checking rest return false; } childIterator++; } return true; } /// /// Finds the largest matching subtrees in two skeletons and colors those subtrees a unique color /// /// /// public void ColorMatchingSections(SkeletonNode skeleton1, SkeletonNode skeleton2, bool ignoreDatabaseName) { // starting node for the outer loop iteration SkeletonNode outerNode = skeleton1; Queue outerQueue = new Queue(); int groupIndexCounter = 1; // Iterates over all nodes in skeleton1 while (outerNode != null) { bool matchFound = false; SkeletonNode innerNode = skeleton2; Queue innerQueue = new Queue(); // to find all the match sleleton2 node for skeleton1, iterate over each node of skeleton2 until all innerNode have been tested, there might be multiple match while (innerNode != null) { if (this.AreSkeletonsEquivalent(outerNode, innerNode, ignoreDatabaseName)) { matchFound = true; int matchColor = groupIndexCounter++; if (innerNode.ParentNode != null && innerNode.ParentNode.HasMatch) { int parentColor = innerNode.ParentNode.GroupIndex; int innerColor = innerNode.GroupIndex; // innerNode is the root of a matching subtree, so use its color for the new match instead of the random new color if (parentColor != innerColor) { matchColor = innerColor; } } else if (innerNode.HasMatch) { matchColor = innerNode.GroupIndex; } else if (outerNode.HasMatch) { // outerNode already finds a matching innerNode, but we keep looking for more matching innerNode matchColor = outerNode.GroupIndex; } outerNode.AddMatchingSkeletonNode(innerNode, ignoreDatabaseName); innerNode.AddMatchingSkeletonNode(outerNode, ignoreDatabaseName); outerNode.ChangeSkeletonGroupIndex(matchColor); innerNode.ChangeSkeletonGroupIndex(matchColor); } // even if we found a matching innerNode, we keep looking since there might be other innerNode that matches same outerNode foreach (SkeletonNode child in innerNode.Children) { innerQueue.Enqueue(child); } innerNode = innerQueue.Any() ? innerQueue.Dequeue() : null; } // no match at all, so add this node's children to queue of nodes to check // effectively does a bfs - doesn't check children if a match has been found (and the entire subtree colored) if (!matchFound) { foreach (SkeletonNode child in outerNode.Children) { outerQueue.Enqueue(child); } } outerNode = outerQueue.Any() ? outerQueue.Dequeue() : null; } } public Node FindNextNonIgnoreNode(Node node) { Node curNode = node; while (curNode != null && ShouldIgnoreDuringComparison(curNode)) { if (curNode.Children.Count > 0) { curNode = curNode.Children.ElementAt(0); } else { // should ignore, but this is a leaf node, so there is no matching node curNode = null; } } return curNode; } #region Private Methods /// /// Determines if the node should be ignored when building a skeleton of the showplan /// /// /// private bool ShouldIgnoreDuringComparison(Node node) { return IgnoreWhenBuildingSkeleton.Contains(node.Operation.Name) || (node[NodeBuilderConstants.LogicalOp] == null); } #endregion /// /// List of Node names which can safely be ignored when building the skeleton /// We can ignore these because not finding a matching between them shouldn't imapct of the shaping of matching nodes /// However, if in the future we see a use case to benefit from matching one of them, for ex I removed Filter because we need it to be skeleton node /// so we user can find issue for, and jump to the Filter node pair when doing scenario based issue detection /// private List IgnoreWhenBuildingSkeleton = new List { SR.Keys.Assert, SR.Keys.BatchHashTableBuild, SR.Keys.Bitmap, SR.Keys.Collapse, SR.Keys.RepartitionStreams, SR.Keys.ComputeScalar, SR.Keys.MergeInterval, SR.Keys.Parallelism, SR.Keys.Print, SR.Keys.RowCountSpool, SR.Keys.LogicalOpLazySpool, SR.Keys.TableSpool, SR.Keys.Segment, SR.Keys.SequenceProject, SR.Keys.Split, SR.Keys.Spool, SR.Keys.Window, SR.Keys.Sort, SR.Keys.Top, SR.Keys.LogicalOpTopNSort }; } }