From 92a7248455e1ab530b570c016c25c8257a99bb40 Mon Sep 17 00:00:00 2001 From: Aasim Khan Date: Fri, 28 Jan 2022 11:35:48 -0800 Subject: [PATCH] Adding recommendations to query plan (#1373) * Adding recommendations * Adding raw graph type in execution plan graph contracts * Fixing function name and concising string formatting * Converting localized string to a function * Using better names in contract props Formatting names in a better way * Getting rid of unnecessary getter, setters and private props * Fixing localized strings, comments and imports * Fixing some contracts * Fixing csproj formatting * Fixing var names * Fixing xml comments --- .../Localization/sr.cs | 16 + .../Localization/sr.resx | 13 + .../Localization/sr.strings | 5 + .../Localization/sr.xlf | 16 + .../QueryExecution/QueryExecutionService.cs | 2 +- .../ShowPlan/Contracts/ExecutionPlanGraph.cs | 39 +- .../ShowPlan/ShowPlanGraph/Description.cs | 50 +- .../ShowPlanGraph/XmlPlanNodeBuilder.cs | 123 +- .../ShowPlan/ShowPlanGraph/showplangraph.cs | 5 + .../ShowPlan/ShowPlanGraphUtils.cs | 41 +- ...oft.SqlTools.ServiceLayer.UnitTests.csproj | 10 +- .../ShowPlan/ShowPlanTests.cs | 36 +- .../TestExecutionPlanRecommendations.xml | 1870 +++++++++++++++++ 13 files changed, 2167 insertions(+), 59 deletions(-) create mode 100644 test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/TestExecutionPlanRecommendations.xml diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 9a1930d8..3e75b055 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -8925,6 +8925,16 @@ namespace Microsoft.SqlTools.ServiceLayer return Keys.GetString(Keys.ActualOfEstimated, actual, estimated, percent); } + public static string MissingIndexFormat(string impact, string queryText) + { + return Keys.GetString(Keys.MissingIndexFormat, impact, queryText); + } + + public static string MissingIndexDetailsTitle(string fileName, string impact) + { + return Keys.GetString(Keys.MissingIndexDetailsTitle, fileName, impact); + } + public static string TableNotInitializedException(string tableId) { return Keys.GetString(Keys.TableNotInitializedException, tableId); @@ -12272,6 +12282,12 @@ namespace Microsoft.SqlTools.ServiceLayer public const string ActualOfEstimated = "ActualOfEstimated"; + public const string MissingIndexFormat = "MissingIndexFormat"; + + + public const string MissingIndexDetailsTitle = "MissingIndexDetailsTitle"; + + public const string TableNotInitializedException = "TableNotInitializedException"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 2fd413c8..b6300966 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -4605,6 +4605,19 @@ {1} ({2}%) . Parameters: 0 - actual (string), 1 - estimated (string), 2 - percent (decimal) + + + Missing Index (Impact {0}): {1} + "Missing Index (Impact {0}): {1}" format string for showplan. + Parameters: 0 - impact (string), 1 - queryText (string) + + + /* +Missing Index Details from {0} +The Query Processor estimates that implementing the following index could improve the query cost by {1}%. +*/ + title of missing index details. + Parameters: 0 - fileName (string), 1 - impact (string) Initialization is not properly done for table with id '{0}' diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 26bd5b34..2b654574 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2219,6 +2219,11 @@ SizeInTeraBytesFormat = {0} TB OperatorDisplayCost(double cost, int percentage) = {0:0.#######} ({1}%) #Would like to display actual rows and estimated rows in two lines: of\n (xx%) ActualOfEstimated(string actual, string estimated, decimal percent) = {0} of\n{1} ({2}%) +;"Missing Index (Impact {0}): {1}" format string for showplan +MissingIndexFormat(string impact, string queryText) = Missing Index (Impact {0}): {1} +;title of missing index details +MissingIndexDetailsTitle(string fileName, string impact) = /*\r\nMissing Index Details from {0}\r\nThe Query Processor estimates that implementing the following index could improve the query cost by {1}%.\r\n*/ + ############################################################################ # Table Designer diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index b59b58af..c22c139c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -5742,6 +5742,22 @@ Columns + + Missing Index (Impact {0}): {1} + Missing Index (Impact {0}): {1} + "Missing Index (Impact {0}): {1}" format string for showplan + + + /* +Missing Index Details from {0} +The Query Processor estimates that implementing the following index could improve the query cost by {1}%. +*/ + /* +Missing Index Details from {0} +The Query Processor estimates that implementing the following index could improve the query cost by {1}%. +*/ + title of missing index details + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs index a60b045b..b9b06a93 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -908,7 +908,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution var xmlString = r.GetRow(0)[0].DisplayValue; try { - plans = ShowPlanGraphUtils.CreateShowPlanGraph(xmlString); + plans = ShowPlanGraphUtils.CreateShowPlanGraph(xmlString, Path.GetFileName(ownerUri)); } catch (Exception ex) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/Contracts/ExecutionPlanGraph.cs b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/Contracts/ExecutionPlanGraph.cs index 66a38ace..9be8160b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/Contracts/ExecutionPlanGraph.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/Contracts/ExecutionPlanGraph.cs @@ -21,9 +21,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan /// public string Query { get; set; } /// - /// Underlying xml string used for generating execution plan graph + /// Graph file that used to generate ExecutionPlanGraph /// - public string XmlString { get; set; } + public ExecutionPlanGraphFile GraphFile { get; set; } + /// + /// Index recommendations given by show plan to improve query performance + /// + public List Recommendations { get; set; } } public class ExecutionPlanNode @@ -113,12 +117,39 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan /// /// Size of the rows returned by the subtree of the edge. /// - /// public double RowSize { get; set; } /// /// Edge properties to be shown in the tooltip. /// - /// public List Properties { get; set; } } + + + public class ExecutionPlanRecommendation + { + /// + /// Text displayed in the show plan graph control + /// + public string DisplayString { get; set; } + /// + /// Raw query that is recommended to the user + /// + public string Query { get; set; } + /// + /// Query that will be opened in a new file once the user click on the recommendation + /// + public string QueryWithDescription { get; set; } + } + + public class ExecutionPlanGraphFile + { + /// + /// File contents + /// + public string GraphFileContent { get; set; } + /// + /// File type for execution plan. This will be the file type of the editor when the user opens the graph file + /// + public string GraphFileType { get; set; } + } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/Description.cs b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/Description.cs index f568835c..bf2e9b8f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/Description.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/Description.cs @@ -3,6 +3,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; +using System.Collections.Generic; + namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph { public class Description @@ -14,7 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph get { return this.title; } set { - this.title = value.Trim().Replace(NewLine, " "); + this.title = value.Trim().Replace(Environment.NewLine, " "); } } @@ -24,7 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph set { string text = value.Trim(); - this.queryText = text.Replace(NewLine, " "); + this.queryText = text.Replace(Environment.NewLine, " "); } } @@ -33,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph get { return this.clusteredMode; } set { - this.clusteredMode = value.Trim().Replace(NewLine, " "); + this.clusteredMode = value.Trim().Replace(Environment.NewLine, " "); } } @@ -45,44 +48,25 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph } } - public bool HasMissingIndex - { - get { return this.hasMissingIndex; } - } - - public string MissingIndexQueryText - { - get { return this.missingIndexQueryText; } - } - - public string MissingIndexImpact - { - get { return this.missingIndexImpact; } - } - - public string MissingIndexDatabase - { - get { return this.missingIndexDatabase; } - } + public List MissingIndices { get; set; } #endregion - + #region Member variables private string title = string.Empty; private string queryText = string.Empty; private string toolTipQueryText = string.Empty; private string clusteredMode = string.Empty; - private bool isClusteredMode = false; - - private bool hasMissingIndex = false; - private string missingIndexCaption = string.Empty; // actual caption text that will be displayed on the screen - private string missingIndexQueryText = string.Empty; // create index query - private string missingIndexImpact = string.Empty; // impact - private string missingIndexDatabase = string.Empty; // database context - - private const string NewLine = "\r\n"; - + private bool isClusteredMode = false; #endregion } + + public class MissingIndex + { + public string MissingIndexCaption { get; set; } + public string MissingIndexQueryText { get; set; } + public string MissingIndexImpact { get; set; } + public string MissingIndexDatabase { get; set; } + } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/XmlPlanNodeBuilder.cs b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/XmlPlanNodeBuilder.cs index 68969e85..55f43667 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/XmlPlanNodeBuilder.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/XmlPlanNodeBuilder.cs @@ -6,7 +6,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Xml; @@ -45,9 +44,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph { plan = ReadXmlShowPlan(dataSource); } - List graphs = new List(); + int statementIndex = 0; foreach (BaseStmtInfoType statement in EnumStatements(plan)) { // Reset currentNodeId (used through Context) and create new context @@ -55,8 +54,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph NodeBuilderContext context = new NodeBuilderContext(new ShowPlanGraph(), this.showPlanType, this); // Parse the statement block XmlPlanParser.Parse(statement, null, null, context); + // Get the statement XML for the graph. + context.Graph.XmlDocument = GetSingleStatementXml(dataSource, statementIndex); + // Parse the graph description. + context.Graph.Description = ParseDescription(context.Graph); // Add graph to the list graphs.Add(context.Graph); + // Incrementing statement index + statementIndex++; } return graphs.ToArray(); @@ -79,11 +84,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph // Now make the new plan based on the existing one that contains only one statement. ShowPlanXML plan = ReadXmlShowPlan(dataSource); - plan.BatchSequence = new StmtBlockType[][] + plan.BatchSequence = new StmtBlockType[][] { new StmtBlockType[] { newStatementBlock } }; - + // Serialize the new plan. StringBuilder stringBuilder = new StringBuilder(); Serializer.Serialize(new StringWriter(stringBuilder), plan); @@ -370,6 +375,116 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph } } } + private Description ParseDescription(ShowPlanGraph graph) + { + XmlDocument stmtXmlDocument = new XmlDocument(); + stmtXmlDocument.LoadXml(graph.XmlDocument); + var nsMgr = new XmlNamespaceManager(stmtXmlDocument.NameTable); + //Manually add our showplan namespace since the document won't have it in the default NameTable + nsMgr.AddNamespace("shp", "http://schemas.microsoft.com/sqlserver/2004/07/showplan"); + + //The root node in this case is the statement node + XmlNode rootNode = stmtXmlDocument.DocumentElement; + if(rootNode == null) + { + //Couldn't find our statement node, this should never happen in a properly formed document + throw new ArgumentNullException("StatementNode"); + } + + XmlNode missingIndexes = rootNode.SelectSingleNode("descendant::shp:MissingIndexes", nsMgr); + + List parsedIndexes = new List(); + + // Not all plans will have a missing index. For those plans, just return the description. + if (missingIndexes != null) + { + + // check Memory Optimized table. + bool memoryOptimzed = false; + XmlNode scan = rootNode.SelectSingleNode("descendant::shp:IndexScan", nsMgr); + if (scan == null) + { + scan = rootNode.SelectSingleNode("descendant::shp:TableScan", nsMgr); + } + if (scan != null && scan.Attributes["Storage"] != null) + { + if (0 == string.Compare(scan.Attributes["Storage"].Value, "MemoryOptimized", StringComparison.Ordinal)) + { + memoryOptimzed = true; + } + } + + // getting all the indexgroups from the plan. A plan can have multiple missing index groups. + XmlNodeList indexGroups = missingIndexes.SelectNodes("descendant::shp:MissingIndexGroup", nsMgr); + + // missing index template + const string createIndexTemplate = "CREATE NONCLUSTERED INDEX []\r\nON {0}.{1} ({2})\r\n"; + const string addIndexTemplate = "ALTER TABLE {0}.{1}\r\nADD INDEX []\r\nNONCLUSTERED ({2})\r\n"; + const string includeTemplate = "INCLUDE ({0})"; + + // iterating over all missing index groups + foreach (XmlNode indexGroup in indexGroups) + { + // we only have one missing index per index group + XmlNode missingIndex = indexGroup.SelectSingleNode("descendant::shp:MissingIndex", nsMgr); + + string database = missingIndex.Attributes["Database"].Value; + string schemaName = missingIndex.Attributes["Schema"].Value; + string tableName = missingIndex.Attributes["Table"].Value; + string indexColumns = string.Empty; + string includeColumns = string.Empty; + + // populate index columns and include columns + XmlNodeList columnGroups = missingIndex.SelectNodes("shp:ColumnGroup", nsMgr); + foreach (XmlNode columnGroup in columnGroups) + { + foreach (XmlNode column in columnGroup.ChildNodes) + { + string columnName = column.Attributes["Name"].Value; + if (0 != string.Compare(columnGroup.Attributes["Usage"].Value, "INCLUDE", StringComparison.Ordinal)) + { + if (indexColumns == string.Empty) + indexColumns = columnName; + else + indexColumns = $"{indexColumns},{columnName}"; + } + else if (!memoryOptimzed) + { + if (includeColumns == string.Empty) + includeColumns = columnName; + else + includeColumns = $"{indexColumns},{columnName}"; + } + } + } + + // for memory optimized we just alter the existing index where as for non optimized tables we create a new one. + string queryText = string.Format((memoryOptimzed) ? addIndexTemplate : createIndexTemplate, schemaName, tableName, indexColumns); + if (!string.IsNullOrEmpty(includeColumns)) + { + queryText += string.Format(includeTemplate, includeColumns); + } + + string impact = indexGroup.Attributes["Impact"].Value; + string caption = SR.MissingIndexFormat(impact, queryText); + parsedIndexes.Add(new MissingIndex() + { + MissingIndexDatabase = database, + MissingIndexQueryText = queryText, + MissingIndexImpact = impact, + MissingIndexCaption = caption + }); + } + } + + + Description description = new Description + { + QueryText = graph.Statement, + MissingIndices = parsedIndexes, + }; + return description; + } #endregion diff --git a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/showplangraph.cs b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/showplangraph.cs index 16d73e90..94227fbf 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/showplangraph.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraph/showplangraph.cs @@ -55,6 +55,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph } } + /// + /// Contains the raw xml document for the graph. Used to save graphs. + /// + public string XmlDocument { get; set; } + /// /// The QueryPlanHash as recorded in the RootNode for this graph, null if not available /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraphUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraphUtils.cs index 3da34952..56f9092f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraphUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraphUtils.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -12,14 +13,19 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan { public class ShowPlanGraphUtils { - public static List CreateShowPlanGraph(string xml) + public static List CreateShowPlanGraph(string xml, string fileName) { ShowPlanGraph.ShowPlanGraph[] graphs = ShowPlanGraph.ShowPlanGraph.ParseShowPlanXML(xml, ShowPlanGraph.ShowPlanType.Unknown); return graphs.Select(g => new ExecutionPlanGraph { Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root), Query = g.Statement, - XmlString = xml + GraphFile = new ExecutionPlanGraphFile + { + GraphFileContent = xml, + GraphFileType = "xml" + }, + Recommendations = ParseRecommendations(g, fileName) }).ToList(); } @@ -85,5 +91,36 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan } return propsList; } + + private static List ParseRecommendations(ShowPlanGraph.ShowPlanGraph g, string fileName) + { + return g.Description.MissingIndices.Select(mi => new ExecutionPlanRecommendation + { + DisplayString = mi.MissingIndexCaption, + Query = mi.MissingIndexQueryText, + QueryWithDescription = ParseMissingIndexQueryText(fileName, mi.MissingIndexImpact, mi.MissingIndexDatabase, mi.MissingIndexQueryText) + }).ToList(); + } + + /// + /// Creates query file text for the recommendations. It has the missing index query along with some lines of description. + /// + /// query file name that has generated the plan + /// impact of the missing query on performance + /// database name to create the missing index in + /// actual query that will be used to create the missing index + /// + private static string ParseMissingIndexQueryText(string fileName, string impact, string database, string query) + { + return $@"{SR.MissingIndexDetailsTitle(fileName, impact)} + +/* +{string.Format("USE {0}", database)} +GO +{string.Format("{0}", query)} +GO +*/ +"; + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj index 4c16a2dc..10637393 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj @@ -30,7 +30,11 @@ - - + + - + + + + + \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs index 197271ea..95656b89 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/ShowPlanTests.cs @@ -4,6 +4,7 @@ using System; // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System.Collections.Generic; using System.IO; using System.Reflection; using NUnit.Framework; @@ -16,20 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan { private string queryPlanFileText; - [SetUp] - public void LoadQueryPlanBeforeEachTest() - { - Assembly assembly = Assembly.GetAssembly(typeof(ShowPlanXMLTests)); - Stream scriptStream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".ShowPlan.TestExecutionPlan.xml"); - StreamReader reader = new StreamReader(scriptStream); - queryPlanFileText = reader.ReadToEnd(); - } - [Test] public void ParseXMLFileReturnsValidShowPlanGraph() { - - var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText); + ReadFile(".ShowPlan.TestExecutionPlan.xml"); + var showPlanGraphs = ShowPlanGraphUtils.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"); @@ -38,8 +30,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan [Test] public void ParsingNestedProperties() { + ReadFile(".ShowPlan.TestExecutionPlan.xml"); string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" }; - var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText); + var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql"); ExecutionPlanNode rootNode = showPlanGraphs[0].Root; rootNode.Properties.ForEach(p => { @@ -49,5 +42,24 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan } }); } + + [Test] + public void ParseXMLFileWithRecommendations() + { + //The first graph in this execution plan has 3 recommendations + ReadFile(".ShowPlan.TestExecutionPlanRecommendations.xml"); + string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" }; + var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql"); + List rootNode = showPlanGraphs[0].Recommendations; + Assert.AreEqual(3, rootNode.Count, "3 recommendations should be returned by the showplan parser"); + } + + private void ReadFile(string fileName) + { + Assembly assembly = Assembly.GetAssembly(typeof(ShowPlanXMLTests)); + Stream scriptStream = assembly.GetManifestResourceStream(assembly.GetName().Name + fileName); + StreamReader reader = new StreamReader(scriptStream); + queryPlanFileText = reader.ReadToEnd(); + } } } \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/TestExecutionPlanRecommendations.xml b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/TestExecutionPlanRecommendations.xml new file mode 100644 index 00000000..5ec593cd --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ShowPlan/TestExecutionPlanRecommendations.xml @@ -0,0 +1,1870 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +