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

View File

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

View File

@@ -10,14 +10,15 @@ using System.Linq;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan; using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
{ {
public class ShowPlanGraphUtils public class ExecutionPlanGraphUtils
{ {
public static List<ExecutionPlanGraph> CreateShowPlanGraph(string xml, string fileName) 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 return graphs.Select(g => new ExecutionPlanGraph
{ {
Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root), Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root),
@@ -31,10 +32,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
}).ToList(); }).ToList();
} }
private static ExecutionPlanNode ConvertShowPlanTreeToExecutionPlanTree(Node currentNode) public static ExecutionPlanNode ConvertShowPlanTreeToExecutionPlanTree(Node currentNode)
{ {
return new ExecutionPlanNode return new ExecutionPlanNode
{ {
ID = currentNode.ID,
Type = currentNode.Operation.Image, Type = currentNode.Operation.Image,
Cost = currentNode.Cost, Cost = currentNode.Cost,
SubTreeCost = currentNode.SubtreeCost, 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 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>(); List<ExecutionPlanGraphPropertyBase> propsList = new List<ExecutionPlanGraphPropertyBase>();
foreach (PropertyValue prop in props) foreach (PropertyValue prop in props)
@@ -96,7 +98,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
return propsList; 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 return g.Description.MissingIndices.Select(mi => new ExecutionPlanRecommendation
{ {
@@ -148,5 +150,58 @@ GO
return String.Empty; 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol; 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; using Microsoft.SqlTools.ServiceLayer.Hosting;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
{ {
/// <summary> /// <summary>
/// Main class for Migration Service functionality /// Main class for Execution Plan Service functionality
/// </summary> /// </summary>
public sealed class ExecutionPlanService : IDisposable public sealed class ExecutionPlanService : IDisposable
{ {
@@ -20,9 +23,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
private bool disposed; private bool disposed;
/// <summary> /// <summary>
/// Construct a new MigrationService instance with default parameters /// Construct a new Execution Plan Service instance with default parameters
/// </summary> /// </summary>
public ExecutionPlanService() private ExecutionPlanService()
{ {
} }
@@ -38,26 +41,23 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
/// Service host object for sending/receiving requests/events. /// Service host object for sending/receiving requests/events.
/// Internal for testing purposes. /// Internal for testing purposes.
/// </summary> /// </summary>
internal IProtocolEndpoint ServiceHost internal IProtocolEndpoint ServiceHost { get; set; }
{
get;
set;
}
/// <summary> /// <summary>
/// Initializes the ShowPlan Service instance /// Initializes the Execution Plan Service instance
/// </summary> /// </summary>
public void InitializeService(ServiceHost serviceHost) public void InitializeService(ServiceHost serviceHost)
{ {
this.ServiceHost = serviceHost; ServiceHost = serviceHost;
this.ServiceHost.SetRequestHandler(GetExecutionPlanRequest.Type, HandleGetExecutionPlan); ServiceHost.SetRequestHandler(GetExecutionPlanRequest.Type, HandleGetExecutionPlan);
ServiceHost.SetRequestHandler(ExecutionPlanComparisonRequest.Type, HandleExecutionPlanComparisonRequest);
} }
private async Task HandleGetExecutionPlan(GetExecutionPlanParams requestParams, RequestContext<GetExecutionPlanResult> requestContext) private async Task HandleGetExecutionPlan(GetExecutionPlanParams requestParams, RequestContext<GetExecutionPlanResult> requestContext)
{ {
try try
{ {
var plans = ShowPlanGraphUtils.CreateShowPlanGraph(requestParams.GraphInfo.GraphFileContent, ""); var plans = ExecutionPlanGraphUtils.CreateShowPlanGraph(requestParams.GraphInfo.GraphFileContent, "");
await requestContext.SendResult(new GetExecutionPlanResult await requestContext.SendResult(new GetExecutionPlanResult
{ {
Graphs = plans Graphs = plans
@@ -70,7 +70,46 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
} }
/// <summary> /// <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> /// </summary>
public void Dispose() public void Dispose()
{ {

View File

@@ -22,7 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
/// </summary> /// </summary>
/// <param name="root">Node to construct skeleton of</param> /// <param name="root">Node to construct skeleton of</param>
/// <returns>SkeletonNode with children representing logical descendants of the input node</returns> /// <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; Node rootNode = root;
var childCount = root.Children.Count; var childCount = root.Children.Count;
@@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
} }
return skeletonParent; return skeletonParent;
} }
else if (childCount == 1) else if (childCount == 1)
{ {
if (!ShouldIgnoreDuringComparison(rootNode)) if (!ShouldIgnoreDuringComparison(rootNode))
{ {
@@ -63,10 +63,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
public bool AreSkeletonsEquivalent(SkeletonNode root1, SkeletonNode root2, bool ignoreDatabaseName) public bool AreSkeletonsEquivalent(SkeletonNode root1, SkeletonNode root2, bool ignoreDatabaseName)
{ {
if (root1 == null && root2 == null) if (root1 == null && root2 == null)
{
return true; return true;
}
if (root1 == null || root2 == null) if (root1 == null || root2 == null)
{
return false; return false;
}
if (!root1.BaseNode.IsLogicallyEquivalentTo(root2.BaseNode, ignoreDatabaseName)) if (!root1.BaseNode.IsLogicallyEquivalentTo(root2.BaseNode, ignoreDatabaseName))
{ {
@@ -80,7 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
while (childIterator < root1.Children.Count) while (childIterator < root1.Children.Count)
{ {
var checkMatch = AreSkeletonsEquivalent(root1.Children.ElementAt(childIterator), root2.Children.ElementAt(childIterator), ignoreDatabaseName); 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 // at least one pair of children (ie inner.Child1 & outer.Child1) didn't match; stop checking rest
return false; return false;

View File

@@ -4,19 +4,21 @@
// //
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
{ {
public class SkeletonNode public class SkeletonNode
{ {
public Node BaseNode {get; set;} public Node BaseNode { get; set; }
public List<SkeletonNode> MatchingNodes { get; set; } public List<SkeletonNode> MatchingNodes { get; set; }
public bool HasMatch { get { return MatchingNodes.Count > 0; } } public bool HasMatch { get { return MatchingNodes.Count > 0; } }
public SkeletonNode ParentNode { get; set; } public SkeletonNode ParentNode { get; set; }
public IList<SkeletonNode> Children { get; set; } public IList<SkeletonNode> Children { get; set; }
public int GroupIndex public int GroupIndex
{ {
get get
{ {
return this.BaseNode.GroupIndex; 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; this.BaseNode[NodeBuilderConstants.SkeletonHasMatch] = true;
if (matchAllChildren == true) if (matchAllChildren == true)
@@ -80,5 +82,43 @@ namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
return this.BaseNode.Graph; 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 class Graph
{ {
public Node Root; public Node Root { get; set; }
public Description Description; public Description Description { get; set; }
} }
} }

View File

@@ -36,6 +36,7 @@ using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.NotebookConvert; using Microsoft.SqlTools.ServiceLayer.NotebookConvert;
using Microsoft.SqlTools.ServiceLayer.ModelManagement; using Microsoft.SqlTools.ServiceLayer.ModelManagement;
using Microsoft.SqlTools.ServiceLayer.TableDesigner; using Microsoft.SqlTools.ServiceLayer.TableDesigner;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan;
namespace Microsoft.SqlTools.ServiceLayer namespace Microsoft.SqlTools.ServiceLayer
{ {
@@ -166,8 +167,8 @@ namespace Microsoft.SqlTools.ServiceLayer
InitializeHostedServices(serviceProvider, serviceHost); InitializeHostedServices(serviceProvider, serviceHost);
serviceHost.ServiceProvider = serviceProvider; serviceHost.ServiceProvider = serviceProvider;
ExecutionPlan.ExecutionPlanService.Instance.InitializeService(serviceHost); ExecutionPlanService.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(ExecutionPlan.ExecutionPlanService.Instance); serviceProvider.RegisterSingleService(ExecutionPlanService.Instance);
serviceHost.InitializeRequestHandlers(); serviceHost.InitializeRequestHandlers();
} }

View File

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

View File

@@ -21,6 +21,7 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{ {
@@ -908,7 +909,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
var xmlString = r.GetRow(0)[0].DisplayValue; var xmlString = r.GetRow(0)[0].DisplayValue;
try try
{ {
plans = ShowPlanGraphUtils.CreateShowPlanGraph(xmlString, Path.GetFileName(ownerUri)); plans = ExecutionPlanGraphUtils.CreateShowPlanGraph(xmlString, Path.GetFileName(ownerUri));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -9,7 +9,9 @@ using System.IO;
using System.Reflection; using System.Reflection;
using NUnit.Framework; using NUnit.Framework;
using Microsoft.SqlTools.ServiceLayer.ExecutionPlan; 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 namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
{ {
@@ -21,18 +23,265 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
public void ParseXMLFileReturnsValidShowPlanGraph() public void ParseXMLFileReturnsValidShowPlanGraph()
{ {
ReadFile(".ShowPlan.TestShowPlan.xml"); 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.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], "graph should not be null");
Assert.NotNull(showPlanGraphs[0].Root, "graph should have a root"); 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] [Test]
public void ParsingNestedProperties() public void ParsingNestedProperties()
{ {
ReadFile(".ShowPlan.TestShowPlan.xml"); ReadFile(".ShowPlan.TestShowPlan.xml");
string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" }; string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" };
var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql"); var showPlanGraphs = ExecutionPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
ExecutionPlanNode rootNode = showPlanGraphs[0].Root; ExecutionPlanNode rootNode = showPlanGraphs[0].Root;
rootNode.Properties.ForEach(p => rootNode.Properties.ForEach(p =>
{ {
@@ -49,7 +298,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
//The first graph in this execution plan has 3 recommendations //The first graph in this execution plan has 3 recommendations
ReadFile(".ShowPlan.TestShowPlanRecommendations.xml"); ReadFile(".ShowPlan.TestShowPlanRecommendations.xml");
string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" }; string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" };
var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql"); var showPlanGraphs = ExecutionPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
List<ExecutionPlanRecommendation> rootNode = showPlanGraphs[0].Recommendations; List<ExecutionPlanRecommendation> rootNode = showPlanGraphs[0].Recommendations;
Assert.AreEqual(3, rootNode.Count, "3 recommendations should be returned by the showplan parser"); Assert.AreEqual(3, rootNode.Count, "3 recommendations should be returned by the showplan parser");
} }