Adds new graph comparison request handler to the Execution Plan Service. (#1438)

* Adds new graph comparison request handler to the Execution Plan Service.

* Code review changes

* Renames execution plan compare params, result, and request classes.
This commit is contained in:
Lewis Sanchez
2022-03-22 14:52:42 -07:00
committed by GitHub
parent d5e2a58db4
commit 8215d88dcf
13 changed files with 524 additions and 39 deletions

View File

@@ -0,0 +1,43 @@
//
// 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;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts
{
/// <summary>
/// An ExecutionGraphComparisonResult is composed of an execution plan node, but has additional properties
/// to keep track of matching ExecutionGraphComparisonResult nodes for execution plan nodes present in the
/// the graph being compared against. This class also features a group index that can assist
/// with coloring similar sections of execution plans in the UI.
/// </summary>
public class ExecutionGraphComparisonResult
{
/// <summary>
/// The base ExecutionPlanNode for the ExecutionGraphComparisonResult.
/// </summary>
public ExecutionPlanNode BaseNode { get; set; }
/// <summary>
/// The children of the ExecutionGraphComparisonResult.
/// </summary>
public List<ExecutionGraphComparisonResult> Children { get; set; } = new List<ExecutionGraphComparisonResult>();
/// <summary>
/// The group index of the ExecutionGraphComparisonResult.
/// </summary>
public int GroupIndex { get; set; }
/// <summary>
/// Flag to indicate if the ExecutionGraphComparisonResult has a matching node in the compared execution plan.
/// </summary>
public bool HasMatch { get; set; }
/// <summary>
/// List of matching nodes for the ExecutionGraphComparisonResult.
/// </summary>
public List<ExecutionGraphComparisonResult> MatchingNodes { get; set; } = new List<ExecutionGraphComparisonResult>();
/// <summary>
/// The parent of the ExecutionGraphComparisonResult.
/// </summary>
public ExecutionGraphComparisonResult ParentNode { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts
{
public class ExecutionPlanComparisonParams
{
/// <summary>
/// First query execution plan for comparison.
/// </summary>
public ExecutionPlanGraphInfo FirstExecutionPlanGraphInfo { get; set; }
/// <summary>
/// Second query execution plan for comparison.
/// </summary>
public ExecutionPlanGraphInfo SecondExecutionPlanGraphInfo { get; set; }
/// <summary>
/// Flag to indicate if the database name should be ignored
/// during comparisons.
/// </summary>
public bool IgnoreDatabaseName { get; set; }
}
public class ExecutionPlanComparisonResult
{
/// <summary>
/// Created ExecutionGraphComparisonResult for the first execution plan
/// </summary>
public ExecutionGraphComparisonResult FirstComparisonResult { get; set; }
/// <summary>
/// Created ExecutionGraphComparisonResult for the second execution plan
/// </summary>
public ExecutionGraphComparisonResult SecondComparisonResult { get; set; }
}
public class ExecutionPlanComparisonRequest
{
public static readonly
RequestType<ExecutionPlanComparisonParams, ExecutionPlanComparisonResult> Type =
RequestType<ExecutionPlanComparisonParams, ExecutionPlanComparisonResult>.Create("executionPlan/compareExecutionPlanGraph");
}
}

View File

@@ -5,7 +5,7 @@
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts
{
/// <summary>
/// Execution plan graph object that is sent over JSON RPC
@@ -32,6 +32,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
public class ExecutionPlanNode
{
/// <summary>
/// ID for the node.
/// </summary>
public int ID { get; set; }
/// <summary>
/// Type of the node. This determines the icon that is displayed for it
/// </summary>
@@ -146,7 +150,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
public string QueryWithDescription { get; set; }
}
public class ExecutionPlanGraphInfo
public class ExecutionPlanGraphInfo
{
/// <summary>
/// File contents

View File

@@ -5,6 +5,7 @@
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
{

View File

@@ -10,14 +10,15 @@ using System.Linq;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan;
using Microsoft.SqlTools.Utility;
using System.Diagnostics;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
{
public class ShowPlanGraphUtils
public class ExecutionPlanGraphUtils
{
public static List<ExecutionPlanGraph> CreateShowPlanGraph(string xml, string fileName)
{
ShowPlan.ShowPlanGraph[] graphs = ShowPlan.ShowPlanGraph.ParseShowPlanXML(xml, ShowPlan.ShowPlanType.Unknown);
ShowPlanGraph[] graphs = ShowPlanGraph.ParseShowPlanXML(xml, ShowPlan.ShowPlanType.Unknown);
return graphs.Select(g => new ExecutionPlanGraph
{
Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root),
@@ -31,10 +32,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
}).ToList();
}
private static ExecutionPlanNode ConvertShowPlanTreeToExecutionPlanTree(Node currentNode)
public static ExecutionPlanNode ConvertShowPlanTreeToExecutionPlanTree(Node currentNode)
{
return new ExecutionPlanNode
{
ID = currentNode.ID,
Type = currentNode.Operation.Image,
Cost = currentNode.Cost,
SubTreeCost = currentNode.SubtreeCost,
@@ -49,7 +51,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
};
}
private static ExecutionPlanEdges ConvertShowPlanEdgeToExecutionPlanEdge(Edge edge)
public static ExecutionPlanEdges ConvertShowPlanEdgeToExecutionPlanEdge(Edge edge)
{
return new ExecutionPlanEdges
{
@@ -59,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
};
}
private static List<ExecutionPlanGraphPropertyBase> GetProperties(PropertyDescriptorCollection props)
public static List<ExecutionPlanGraphPropertyBase> GetProperties(PropertyDescriptorCollection props)
{
List<ExecutionPlanGraphPropertyBase> propsList = new List<ExecutionPlanGraphPropertyBase>();
foreach (PropertyValue prop in props)
@@ -96,7 +98,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
return propsList;
}
private static List<ExecutionPlanRecommendation> ParseRecommendations(ShowPlan.ShowPlanGraph g, string fileName)
private static List<ExecutionPlanRecommendation> ParseRecommendations(ShowPlanGraph g, string fileName)
{
return g.Description.MissingIndices.Select(mi => new ExecutionPlanRecommendation
{
@@ -148,5 +150,58 @@ GO
return String.Empty;
}
}
public static void CopyMatchingNodesIntoSkeletonDTO(ExecutionGraphComparisonResult destRoot, ExecutionGraphComparisonResult srcRoot)
{
var srcGraphLookupTable = srcRoot.CreateSkeletonLookupTable();
var queue = new Queue<ExecutionGraphComparisonResult>();
queue.Enqueue(destRoot);
while (queue.Count != 0)
{
var curNode = queue.Dequeue();
for (int index = 0; index < curNode.MatchingNodes.Count; ++index)
{
var matchingId = curNode.MatchingNodes[index].BaseNode.ID;
var matchingNode = srcGraphLookupTable[matchingId];
curNode.MatchingNodes[index] = matchingNode;
}
foreach (var child in curNode.Children)
{
queue.Enqueue(child);
}
}
}
}
public static class ExecutionGraphComparisonResultExtensions
{
public static Dictionary<int, ExecutionGraphComparisonResult> CreateSkeletonLookupTable(this ExecutionGraphComparisonResult node)
{
var skeletonNodeTable = new Dictionary<int, ExecutionGraphComparisonResult>();
var queue = new Queue<ExecutionGraphComparisonResult>();
queue.Enqueue(node);
while (queue.Count != 0)
{
var curNode = queue.Dequeue();
if (!skeletonNodeTable.ContainsKey(curNode.BaseNode.ID))
{
skeletonNodeTable[curNode.BaseNode.ID] = curNode;
}
foreach (var child in curNode.Children)
{
queue.Enqueue(child);
}
}
return skeletonNodeTable;
}
}
}

View File

@@ -6,12 +6,15 @@
using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison;
using Microsoft.SqlTools.ServiceLayer.Hosting;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
{
/// <summary>
/// Main class for Migration Service functionality
/// Main class for Execution Plan Service functionality
/// </summary>
public sealed class ExecutionPlanService : IDisposable
{
@@ -20,9 +23,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
private bool disposed;
/// <summary>
/// Construct a new MigrationService instance with default parameters
/// Construct a new Execution Plan Service instance with default parameters
/// </summary>
public ExecutionPlanService()
private ExecutionPlanService()
{
}
@@ -38,26 +41,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
/// Service host object for sending/receiving requests/events.
/// Internal for testing purposes.
/// </summary>
internal IProtocolEndpoint ServiceHost
{
get;
set;
}
internal IProtocolEndpoint ServiceHost { get; set; }
/// <summary>
/// Initializes the ShowPlan Service instance
/// Initializes the Execution Plan Service instance
/// </summary>
public void InitializeService(ServiceHost serviceHost)
{
this.ServiceHost = serviceHost;
this.ServiceHost.SetRequestHandler(GetExecutionPlanRequest.Type, HandleGetExecutionPlan);
ServiceHost = serviceHost;
ServiceHost.SetRequestHandler(GetExecutionPlanRequest.Type, HandleGetExecutionPlan);
ServiceHost.SetRequestHandler(ExecutionPlanComparisonRequest.Type, HandleExecutionPlanComparisonRequest);
}
private async Task HandleGetExecutionPlan(GetExecutionPlanParams requestParams, RequestContext<GetExecutionPlanResult> requestContext)
{
try
{
var plans = ShowPlanGraphUtils.CreateShowPlanGraph(requestParams.GraphInfo.GraphFileContent, "");
var plans = ExecutionPlanGraphUtils.CreateShowPlanGraph(requestParams.GraphInfo.GraphFileContent, "");
await requestContext.SendResult(new GetExecutionPlanResult
{
Graphs = plans
@@ -70,7 +70,46 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
}
/// <summary>
/// Disposes the ShowPlan Service
/// Handles requests for color matching similar nodes.
/// </summary>
internal async Task HandleExecutionPlanComparisonRequest(
ExecutionPlanComparisonParams requestParams,
RequestContext<ExecutionPlanComparisonResult> requestContext)
{
try
{
var firstGraphSet = ShowPlanGraph.ParseShowPlanXML(requestParams.FirstExecutionPlanGraphInfo.GraphFileContent, ShowPlanType.Unknown);
var firstRootNode = firstGraphSet?[0]?.Root;
var secondGraphSet = ShowPlanGraph.ParseShowPlanXML(requestParams.SecondExecutionPlanGraphInfo.GraphFileContent, ShowPlanType.Unknown);
var secondRootNode = secondGraphSet?[0]?.Root;
var manager = new SkeletonManager();
var firstSkeletonNode = manager.CreateSkeleton(firstRootNode);
var secondSkeletonNode = manager.CreateSkeleton(secondRootNode);
manager.ColorMatchingSections(firstSkeletonNode, secondSkeletonNode, requestParams.IgnoreDatabaseName);
var firstGraphComparisonResultDTO = firstSkeletonNode.ConvertToDTO();
var secondGraphComparisonResultDTO = secondSkeletonNode.ConvertToDTO();
ExecutionPlanGraphUtils.CopyMatchingNodesIntoSkeletonDTO(firstGraphComparisonResultDTO, secondGraphComparisonResultDTO);
ExecutionPlanGraphUtils.CopyMatchingNodesIntoSkeletonDTO(secondGraphComparisonResultDTO, firstGraphComparisonResultDTO);
var result = new ExecutionPlanComparisonResult()
{
FirstComparisonResult = firstGraphComparisonResultDTO,
SecondComparisonResult = secondGraphComparisonResultDTO
};
await requestContext.SendResult(result);
}
catch (Exception e)
{
await requestContext.SendError(e.ToString());
}
}
/// <summary>
/// Disposes the Execution Plan Service
/// </summary>
public void Dispose()
{

View File

@@ -22,7 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
/// </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)
public SkeletonNode CreateSkeleton(Node root)
{
Node rootNode = root;
var childCount = root.Children.Count;
@@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
}
return skeletonParent;
}
else if (childCount == 1)
else if (childCount == 1)
{
if (!ShouldIgnoreDuringComparison(rootNode))
{
@@ -63,10 +63,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
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))
{
@@ -80,7 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
while (childIterator < root1.Children.Count)
{
var checkMatch = AreSkeletonsEquivalent(root1.Children.ElementAt(childIterator), root2.Children.ElementAt(childIterator), ignoreDatabaseName);
if (!checkMatch)
if (!checkMatch)
{
// at least one pair of children (ie inner.Child1 & outer.Child1) didn't match; stop checking rest
return false;

View File

@@ -4,19 +4,21 @@
//
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
{
public class SkeletonNode
{
public Node BaseNode {get; set;}
public Node BaseNode { get; set; }
public List<SkeletonNode> MatchingNodes { get; set; }
public bool HasMatch { get { return MatchingNodes.Count > 0; } }
public SkeletonNode ParentNode { get; set; }
public IList<SkeletonNode> Children { get; set; }
public int GroupIndex
{
public int GroupIndex
{
get
{
return this.BaseNode.GroupIndex;
@@ -54,7 +56,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
}
}
public void AddMatchingSkeletonNode(SkeletonNode match, bool ignoreDatabaseName, bool matchAllChildren=true)
public void AddMatchingSkeletonNode(SkeletonNode match, bool ignoreDatabaseName, bool matchAllChildren = true)
{
this.BaseNode[NodeBuilderConstants.SkeletonHasMatch] = true;
if (matchAllChildren == true)
@@ -80,5 +82,43 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
return this.BaseNode.Graph;
}
public ExecutionGraphComparisonResult ConvertToDTO()
{
var queue = new Queue<SkeletonNode>();
queue.Enqueue(this);
var skeletonNodeDTO = new ExecutionGraphComparisonResult();
var dtoQueue = new Queue<ExecutionGraphComparisonResult>();
dtoQueue.Enqueue(skeletonNodeDTO);
while (queue.Count != 0)
{
var curNode = queue.Dequeue();
var curNodeDTO = dtoQueue.Dequeue();
curNodeDTO.BaseNode = ExecutionPlanGraphUtils.ConvertShowPlanTreeToExecutionPlanTree(curNode.BaseNode);
curNodeDTO.GroupIndex = curNode.GroupIndex;
curNodeDTO.HasMatch = curNode.HasMatch;
curNodeDTO.MatchingNodes = curNode.MatchingNodes.Select(matchingNode =>
{
var skeletonNodeDTO = new Contracts.ExecutionGraphComparisonResult();
skeletonNodeDTO.BaseNode = ExecutionPlanGraphUtils.ConvertShowPlanTreeToExecutionPlanTree(matchingNode.BaseNode);
return skeletonNodeDTO;
}).ToList();
foreach (var child in curNode.Children)
{
queue.Enqueue(child);
var childDTO = new Contracts.ExecutionGraphComparisonResult();
childDTO.ParentNode = curNodeDTO;
curNodeDTO.Children.Add(childDTO);
dtoQueue.Enqueue(childDTO);
}
}
return skeletonNodeDTO;
}
}
}

View File

@@ -7,8 +7,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
{
public class Graph
{
public Node Root;
public Node Root { get; set; }
public Description Description;
public Description Description { get; set; }
}
}
}