Files
sqltoolsservice/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/Comparison/SkeletonManager.cs
Aasim Khan e8c50caea1 Porting showplan code from ssms (#1280)
* Porting showplan code from ssms

* Moving showplans bits to a subfolder

* code cleanup

* Remvoing unnecssary conditional visibility
2021-11-04 14:54:52 -07:00

211 lines
9.2 KiB
C#

//
// 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
{
/// <summary>
/// Handles operations for creating and comparing skeletons of showplan trees
/// A skeleton is the tree with some nodes filtered out to reduce complexity
/// </summary>
public class SkeletonManager
{
public SkeletonManager() { }
/// <summary>
/// Constructs a skeleton tree representing logical structure of the showplan
/// Primarily represents joins and data access operators
/// </summary>
/// <param name="root">Node to construct skeleton of</param>
/// <returns>SkeletonNode with children representing logical descendants of the input node</returns>
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;
}
/// <summary>
/// Checks root and all children for equivalent tree structure and logical equivalence at the node level
/// </summary>
/// <param name="root1"></param>
/// <param name="root2"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Finds the largest matching subtrees in two skeletons and colors those subtrees a unique color
/// </summary>
/// <param name="skeleton1"></param>
/// <param name="skeleton2"></param>
public void ColorMatchingSections(SkeletonNode skeleton1, SkeletonNode skeleton2, bool ignoreDatabaseName)
{
// starting node for the outer loop iteration
SkeletonNode outerNode = skeleton1;
Queue<SkeletonNode> outerQueue = new Queue<SkeletonNode>();
int groupIndexCounter = 1;
// Iterates over all nodes in skeleton1
while (outerNode != null)
{
bool matchFound = false;
SkeletonNode innerNode = skeleton2;
Queue<SkeletonNode> innerQueue = new Queue<SkeletonNode>();
// 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
/// <summary>
/// Determines if the node should be ignored when building a skeleton of the showplan
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private bool ShouldIgnoreDuringComparison(Node node)
{
return IgnoreWhenBuildingSkeleton.Contains(node.Operation.Name) || (node[NodeBuilderConstants.LogicalOp] == null);
}
#endregion
/// <summary>
/// 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
/// </summary>
private List<string> IgnoreWhenBuildingSkeleton = new List<string> { 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 };
}
}