diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionGraphComparisonResult.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionGraphComparisonResult.cs
new file mode 100644
index 00000000..57a54d62
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionGraphComparisonResult.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ public class ExecutionGraphComparisonResult
+ {
+ ///
+ /// The base ExecutionPlanNode for the ExecutionGraphComparisonResult.
+ ///
+ public ExecutionPlanNode BaseNode { get; set; }
+ ///
+ /// The children of the ExecutionGraphComparisonResult.
+ ///
+ public List Children { get; set; } = new List();
+ ///
+ /// The group index of the ExecutionGraphComparisonResult.
+ ///
+ public int GroupIndex { get; set; }
+ ///
+ /// Flag to indicate if the ExecutionGraphComparisonResult has a matching node in the compared execution plan.
+ ///
+ public bool HasMatch { get; set; }
+ ///
+ /// List of matching nodes for the ExecutionGraphComparisonResult.
+ ///
+ public List MatchingNodes { get; set; } = new List();
+ ///
+ /// The parent of the ExecutionGraphComparisonResult.
+ ///
+ public ExecutionGraphComparisonResult ParentNode { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanComparisonRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanComparisonRequest.cs
new file mode 100644
index 00000000..80ff1b29
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanComparisonRequest.cs
@@ -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
+ {
+ ///
+ /// First query execution plan for comparison.
+ ///
+ public ExecutionPlanGraphInfo FirstExecutionPlanGraphInfo { get; set; }
+
+ ///
+ /// Second query execution plan for comparison.
+ ///
+ public ExecutionPlanGraphInfo SecondExecutionPlanGraphInfo { get; set; }
+
+ ///
+ /// Flag to indicate if the database name should be ignored
+ /// during comparisons.
+ ///
+ public bool IgnoreDatabaseName { get; set; }
+ }
+
+ public class ExecutionPlanComparisonResult
+ {
+ ///
+ /// Created ExecutionGraphComparisonResult for the first execution plan
+ ///
+ public ExecutionGraphComparisonResult FirstComparisonResult { get; set; }
+
+ ///
+ /// Created ExecutionGraphComparisonResult for the second execution plan
+ ///
+ public ExecutionGraphComparisonResult SecondComparisonResult { get; set; }
+ }
+
+ public class ExecutionPlanComparisonRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("executionPlan/compareExecutionPlanGraph");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanGraph.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanGraph.cs
index e548b91f..ac27249d 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanGraph.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/ExecutionPlanGraph.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
-namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
+namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts
{
///
/// Execution plan graph object that is sent over JSON RPC
@@ -32,6 +32,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
public class ExecutionPlanNode
{
+ ///
+ /// ID for the node.
+ ///
+ public int ID { get; set; }
///
/// Type of the node. This determines the icon that is displayed for it
///
@@ -146,7 +150,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
public string QueryWithDescription { get; set; }
}
- public class ExecutionPlanGraphInfo
+ public class ExecutionPlanGraphInfo
{
///
/// File contents
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/GetExecutionPlanRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/GetExecutionPlanRequest.cs
index b483936d..c02f283a 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/GetExecutionPlanRequest.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/Contracts/GetExecutionPlanRequest.cs
@@ -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
{
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlanGraphUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanGraphUtils.cs
similarity index 69%
rename from src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlanGraphUtils.cs
rename to src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanGraphUtils.cs
index d2b183b8..b4978ad3 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlanGraphUtils.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanGraphUtils.cs
@@ -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 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 GetProperties(PropertyDescriptorCollection props)
+ public static List GetProperties(PropertyDescriptorCollection props)
{
List propsList = new List();
foreach (PropertyValue prop in props)
@@ -96,7 +98,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
return propsList;
}
- private static List ParseRecommendations(ShowPlan.ShowPlanGraph g, string fileName)
+ private static List 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();
+ 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 CreateSkeletonLookupTable(this ExecutionGraphComparisonResult node)
+ {
+ var skeletonNodeTable = new Dictionary();
+ var queue = new Queue();
+ 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;
+ }
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanService.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanService.cs
index 59684b87..3f939513 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ExecutionPlanService.cs
@@ -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
{
///
- /// Main class for Migration Service functionality
+ /// Main class for Execution Plan Service functionality
///
public sealed class ExecutionPlanService : IDisposable
{
@@ -20,9 +23,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
private bool disposed;
///
- /// Construct a new MigrationService instance with default parameters
+ /// Construct a new Execution Plan Service instance with default parameters
///
- 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.
///
- internal IProtocolEndpoint ServiceHost
- {
- get;
- set;
- }
+ internal IProtocolEndpoint ServiceHost { get; set; }
///
- /// Initializes the ShowPlan Service instance
+ /// Initializes the Execution Plan Service instance
///
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 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
}
///
- /// Disposes the ShowPlan Service
+ /// Handles requests for color matching similar nodes.
+ ///
+ internal async Task HandleExecutionPlanComparisonRequest(
+ ExecutionPlanComparisonParams requestParams,
+ RequestContext 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());
+ }
+ }
+
+ ///
+ /// Disposes the Execution Plan Service
///
public void Dispose()
{
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonManager.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonManager.cs
index 07f91b8b..9216fc95 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonManager.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonManager.cs
@@ -22,7 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
///
/// Node to construct skeleton of
/// SkeletonNode with children representing logical descendants of the input node
- 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;
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonNode.cs
index ff3169fd..7d8a7850 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonNode.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Comparison/SkeletonNode.cs
@@ -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 MatchingNodes { get; set; }
public bool HasMatch { get { return MatchingNodes.Count > 0; } }
public SkeletonNode ParentNode { get; set; }
public IList 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();
+ queue.Enqueue(this);
+
+ var skeletonNodeDTO = new ExecutionGraphComparisonResult();
+ var dtoQueue = new Queue();
+ 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;
+ }
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Graph.cs b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Graph.cs
index 6f092f81..8f26d781 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Graph.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ExecutionPlan/ShowPlan/Graph.cs
@@ -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; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
index 9e3286ca..24739657 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
@@ -36,6 +36,7 @@ using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.NotebookConvert;
using Microsoft.SqlTools.ServiceLayer.ModelManagement;
using Microsoft.SqlTools.ServiceLayer.TableDesigner;
+using Microsoft.SqlTools.ServiceLayer.ExecutionPlan;
namespace Microsoft.SqlTools.ServiceLayer
{
@@ -166,8 +167,8 @@ namespace Microsoft.SqlTools.ServiceLayer
InitializeHostedServices(serviceProvider, serviceHost);
serviceHost.ServiceProvider = serviceProvider;
- ExecutionPlan.ExecutionPlanService.Instance.InitializeService(serviceHost);
- serviceProvider.RegisterSingleService(ExecutionPlan.ExecutionPlanService.Instance);
+ ExecutionPlanService.Instance.InitializeService(serviceHost);
+ serviceProvider.RegisterSingleService(ExecutionPlanService.Instance);
serviceHost.InitializeRequestHandlers();
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ResultSetEvents.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ResultSetEvents.cs
index fa646582..27bc1de8 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ResultSetEvents.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ResultSetEvents.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
-using Microsoft.SqlTools.ServiceLayer.ExecutionPlan;
+using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests
{
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
index aa42c5b0..7a6325fa 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
@@ -21,6 +21,7 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.Utility;
+using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
@@ -908,7 +909,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
var xmlString = r.GetRow(0)[0].DisplayValue;
try
{
- plans = ShowPlanGraphUtils.CreateShowPlanGraph(xmlString, Path.GetFileName(ownerUri));
+ plans = ExecutionPlanGraphUtils.CreateShowPlanGraph(xmlString, Path.GetFileName(ownerUri));
}
catch (Exception ex)
{
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs
index c40399e9..5e539710 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs
@@ -9,7 +9,9 @@ using System.IO;
using System.Reflection;
using NUnit.Framework;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan;
-
+using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
+using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan;
+using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
{
@@ -21,18 +23,265 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
public void ParseXMLFileReturnsValidShowPlanGraph()
{
ReadFile(".ShowPlan.TestShowPlan.xml");
- var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
+ var showPlanGraphs = ExecutionPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
Assert.AreEqual(1, showPlanGraphs.Count, "exactly one show plan graph should be returned");
Assert.NotNull(showPlanGraphs[0], "graph should not be null");
Assert.NotNull(showPlanGraphs[0].Root, "graph should have a root");
}
+ [Test]
+ public void CompareShowPlan_CreateSkeleton()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ Assert.NotNull(rootNode, "graph should have a root");
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+
+ Assert.NotNull(skeletonNode, "skeletonNode should not be null");
+ }
+
+ [Test]
+ public void CompareShowPlan_CreateSkeletonUsingDefaultConfiguredNode()
+ {
+ var graph = new ShowPlanGraph()
+ {
+ Root = null,
+ Description = new Description()
+ };
+ var context = new NodeBuilderContext(graph, ShowPlanType.Unknown, null);
+ var node = new Node(default, context);
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(node);
+
+ Assert.NotNull(skeletonNode, "skeletonNode should not be null");
+ }
+
+ [Test]
+ public void CompareShowPlan_DuplicateSkeletons()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+ var skeletonNode2 = skeletonManager.CreateSkeleton(rootNode);
+
+ var skeletonCompareResult = skeletonManager.AreSkeletonsEquivalent(skeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsTrue(skeletonCompareResult);
+ }
+
+ [Test]
+ public void CompareShowPlan_TwoDefaultConfiguredSkeletons()
+ {
+ var graph = new ShowPlanGraph()
+ {
+ Root = null,
+ Description = new Description()
+ };
+ var context = new NodeBuilderContext(graph, ShowPlanType.Unknown, null);
+ var firstRoot = new Node(default, context);
+ var secondRoot = new Node(default, context);
+
+ var skeletonManager = new SkeletonManager();
+ var firstSkeletonNode = skeletonManager.CreateSkeleton(firstRoot);
+ var secondSkeletonNode = skeletonManager.CreateSkeleton(secondRoot);
+
+ var skeletonCompareResult = skeletonManager.AreSkeletonsEquivalent(firstSkeletonNode, secondSkeletonNode, ignoreDatabaseName: true);
+
+ Assert.IsTrue(skeletonCompareResult, "The two compared skeleton nodes should be equivalent");
+ }
+
+ [Test]
+ public void CompareShowPlan_ComparingSkeletonAgainstNull()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+ SkeletonNode skeletonNode2 = null;
+
+ var skeletonCompareResult = skeletonManager.AreSkeletonsEquivalent(skeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsFalse(skeletonCompareResult);
+ }
+
+ [Test]
+ public void CompareShowPlan_DifferentSkeletonChildCount()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+ var skeletonNode2 = skeletonManager.CreateSkeleton(rootNode);
+ skeletonNode2.Children.RemoveAt(skeletonNode2.Children.Count - 1);
+
+ var skeletonCompareResult = skeletonManager.AreSkeletonsEquivalent(skeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsFalse(skeletonCompareResult);
+ }
+
+ [Test]
+ public void CompareShowPlan_ColorMatchingSectionsWithEquivalentSkeletons()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+ var skeletonNode2 = skeletonManager.CreateSkeleton(rootNode);
+
+ Assert.IsFalse(skeletonNode.HasMatch);
+ Assert.IsFalse(skeletonNode2.HasMatch);
+
+ Assert.Zero(skeletonNode.MatchingNodes.Count);
+ Assert.Zero(skeletonNode2.MatchingNodes.Count);
+
+ skeletonManager.ColorMatchingSections(skeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsTrue(skeletonNode.HasMatch);
+ Assert.IsTrue(skeletonNode2.HasMatch);
+
+ Assert.AreEqual(1, skeletonNode.MatchingNodes.Count);
+ Assert.AreEqual(1, skeletonNode2.MatchingNodes.Count);
+ }
+
+ [Test]
+ public void CompareShowPlan_ColorMatchingSectionsWithNullAndNonNullSkeleton()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ SkeletonNode nullSkeletonNode = null;
+ var skeletonNode2 = skeletonManager.CreateSkeleton(rootNode);
+
+ skeletonManager.ColorMatchingSections(nullSkeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsNull(nullSkeletonNode);
+ Assert.IsFalse(skeletonNode2.HasMatch);
+ Assert.Zero(skeletonNode2.MatchingNodes.Count);
+ }
+
+ [Test]
+ public void CompareShowPlan_ColorMatchingSectionsWithTwoNullSKeletons()
+ {
+ var skeletonManager = new SkeletonManager();
+ SkeletonNode skeletonNode = null;
+ SkeletonNode skeletonNode2 = null;
+
+ skeletonManager.ColorMatchingSections(skeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsNull(skeletonNode);
+ Assert.IsNull(skeletonNode2);
+ }
+
+ [Test]
+ public void CompareShowPlan_ColorMatchingSectionsWithNonNullAndNullSkeleton()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+ SkeletonNode nullSkeletonNode = null;
+
+ skeletonManager.ColorMatchingSections(skeletonNode, nullSkeletonNode, ignoreDatabaseName: true);
+
+ Assert.IsFalse(skeletonNode.HasMatch);
+ Assert.Zero(skeletonNode.MatchingNodes.Count);
+ Assert.IsNull(nullSkeletonNode);
+ }
+
+ [Test]
+ public void CompareShowPlan_ColorMatchingSectionsWithDifferentChildCount()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var skeletonNode = skeletonManager.CreateSkeleton(rootNode);
+ var skeletonNode2 = skeletonManager.CreateSkeleton(rootNode);
+ skeletonNode2.Children.RemoveAt(skeletonNode2.Children.Count - 1);
+
+ Assert.IsFalse(skeletonNode.Children[0].HasMatch);
+ Assert.IsFalse(skeletonNode2.Children[0].HasMatch);
+
+ Assert.Zero(skeletonNode.Children[0].MatchingNodes.Count);
+ Assert.Zero(skeletonNode2.Children[0].MatchingNodes.Count);
+
+ skeletonManager.ColorMatchingSections(skeletonNode, skeletonNode2, ignoreDatabaseName: true);
+
+ Assert.IsTrue(skeletonNode.Children[0].HasMatch);
+ Assert.IsTrue(skeletonNode2.Children[0].HasMatch);
+
+ Assert.AreEqual(1, skeletonNode.Children[0].MatchingNodes.Count);
+ Assert.AreEqual(1, skeletonNode2.Children[0].MatchingNodes.Count);
+ }
+
+ [Test]
+ public void CompareShowPlan_FindNextNonIgnoreNode()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ var skeletonManager = new SkeletonManager();
+ var result = skeletonManager.FindNextNonIgnoreNode(rootNode);
+
+ Assert.NotNull(result);
+ Assert.AreEqual("InnerJoin", result.LogicalOpUnlocName);
+ }
+
+ [Test]
+ public void CompareShowPlan_FindNextNonIgnoreNodeWithChildlessRoot()
+ {
+ ReadFile(".ShowPlan.TestShowPlan.xml");
+ var graphs = ShowPlanGraph.ParseShowPlanXML(this.queryPlanFileText, ShowPlanType.Unknown);
+ var rootNode = graphs[0].Root;
+
+ for (int childIndex = 0; childIndex < rootNode.Children.Count; ++childIndex)
+ {
+ rootNode.Children.RemoveAt(0);
+ }
+
+ var skeletonManager = new SkeletonManager();
+ var result = skeletonManager.FindNextNonIgnoreNode(rootNode);
+
+ Assert.IsNull(result);
+ }
+
+ [Test]
+ public void CompareShowPlan_FindNextNonIgnoreNodeWithNullNode()
+ {
+ Node rootNode = null;
+
+ var skeletonManager = new SkeletonManager();
+ var result = skeletonManager.FindNextNonIgnoreNode(rootNode);
+
+ Assert.IsNull(result);
+ }
+
[Test]
public void ParsingNestedProperties()
{
ReadFile(".ShowPlan.TestShowPlan.xml");
string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" };
- var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
+ var showPlanGraphs = ExecutionPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
ExecutionPlanNode rootNode = showPlanGraphs[0].Root;
rootNode.Properties.ForEach(p =>
{
@@ -49,7 +298,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
//The first graph in this execution plan has 3 recommendations
ReadFile(".ShowPlan.TestShowPlanRecommendations.xml");
string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" };
- var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
+ var showPlanGraphs = ExecutionPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
List rootNode = showPlanGraphs[0].Recommendations;
Assert.AreEqual(3, rootNode.Count, "3 recommendations should be returned by the showplan parser");
}