mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
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
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
|
||||
@@ -4605,6 +4605,19 @@
|
||||
{1} ({2}%)</value>
|
||||
<comment>.
|
||||
Parameters: 0 - actual (string), 1 - estimated (string), 2 - percent (decimal) </comment>
|
||||
</data>
|
||||
<data name="MissingIndexFormat" xml:space="preserve">
|
||||
<value>Missing Index (Impact {0}): {1}</value>
|
||||
<comment>"Missing Index (Impact {0}): {1}" format string for showplan.
|
||||
Parameters: 0 - impact (string), 1 - queryText (string) </comment>
|
||||
</data>
|
||||
<data name="MissingIndexDetailsTitle" xml:space="preserve">
|
||||
<value>/*
|
||||
Missing Index Details from {0}
|
||||
The Query Processor estimates that implementing the following index could improve the query cost by {1}%.
|
||||
*/</value>
|
||||
<comment>title of missing index details.
|
||||
Parameters: 0 - fileName (string), 1 - impact (string) </comment>
|
||||
</data>
|
||||
<data name="TableNotInitializedException" xml:space="preserve">
|
||||
<value>Initialization is not properly done for table with id '{0}'</value>
|
||||
|
||||
@@ -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: <number_actual_rows> of\n <number_estimated_rows> (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
|
||||
|
||||
@@ -5742,6 +5742,22 @@
|
||||
<target state="new">Columns</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="MissingIndexFormat">
|
||||
<source>Missing Index (Impact {0}): {1}</source>
|
||||
<target state="new">Missing Index (Impact {0}): {1}</target>
|
||||
<note>"Missing Index (Impact {0}): {1}" format string for showplan</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="MissingIndexDetailsTitle">
|
||||
<source>/*
|
||||
Missing Index Details from {0}
|
||||
The Query Processor estimates that implementing the following index could improve the query cost by {1}%.
|
||||
*/</source>
|
||||
<target state="new">/*
|
||||
Missing Index Details from {0}
|
||||
The Query Processor estimates that implementing the following index could improve the query cost by {1}%.
|
||||
*/</target>
|
||||
<note>title of missing index details</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -21,9 +21,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
/// <summary>
|
||||
/// Underlying xml string used for generating execution plan graph
|
||||
/// Graph file that used to generate ExecutionPlanGraph
|
||||
/// </summary>
|
||||
public string XmlString { get; set; }
|
||||
public ExecutionPlanGraphFile GraphFile { get; set; }
|
||||
/// <summary>
|
||||
/// Index recommendations given by show plan to improve query performance
|
||||
/// </summary>
|
||||
public List<ExecutionPlanRecommendation> Recommendations { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionPlanNode
|
||||
@@ -113,12 +117,39 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
||||
/// <summary>
|
||||
/// Size of the rows returned by the subtree of the edge.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public double RowSize { get; set; }
|
||||
/// <summary>
|
||||
/// Edge properties to be shown in the tooltip.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public List<ExecutionPlanGraphPropertyBase> Properties { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ExecutionPlanRecommendation
|
||||
{
|
||||
/// <summary>
|
||||
/// Text displayed in the show plan graph control
|
||||
/// </summary>
|
||||
public string DisplayString { get; set; }
|
||||
/// <summary>
|
||||
/// Raw query that is recommended to the user
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
/// <summary>
|
||||
/// Query that will be opened in a new file once the user click on the recommendation
|
||||
/// </summary>
|
||||
public string QueryWithDescription { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionPlanGraphFile
|
||||
{
|
||||
/// <summary>
|
||||
/// File contents
|
||||
/// </summary>
|
||||
public string GraphFileContent { get; set; }
|
||||
/// <summary>
|
||||
/// File type for execution plan. This will be the file type of the editor when the user opens the graph file
|
||||
/// </summary>
|
||||
public string GraphFileType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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<MissingIndex> 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; }
|
||||
}
|
||||
}
|
||||
@@ -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<ShowPlanGraph> graphs = new List<ShowPlanGraph>();
|
||||
|
||||
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<MissingIndex> parsedIndexes = new List<MissingIndex>();
|
||||
|
||||
// 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 [<Name of Missing Index, sysname,>]\r\nON {0}.{1} ({2})\r\n";
|
||||
const string addIndexTemplate = "ALTER TABLE {0}.{1}\r\nADD INDEX [<Name of Missing Index, sysname,>]\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
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the raw xml document for the graph. Used to save graphs.
|
||||
/// </summary>
|
||||
public string XmlDocument { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The QueryPlanHash as recorded in the RootNode for this graph, null if not available
|
||||
/// </summary>
|
||||
|
||||
@@ -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<ExecutionPlanGraph> CreateShowPlanGraph(string xml)
|
||||
public static List<ExecutionPlanGraph> 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<ExecutionPlanRecommendation> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates query file text for the recommendations. It has the missing index query along with some lines of description.
|
||||
/// </summary>
|
||||
/// <param name="fileName">query file name that has generated the plan</param>
|
||||
/// <param name="impact">impact of the missing query on performance</param>
|
||||
/// <param name="database">database name to create the missing index in</param>
|
||||
/// <param name="query">actual query that will be used to create the missing index</param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
*/
|
||||
";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Remove=".\ShowPlan\TestExecution.xml" />
|
||||
<EmbeddedResource Include=".\ShowPlan\TestExecutionPlan.xml" />
|
||||
<Content Remove=".\ShowPlan\TestExecution.xml" />
|
||||
<EmbeddedResource Include=".\ShowPlan\TestExecutionPlan.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<Content Remove=".\ShowPlan\TestExecutionPlanRecommendations.xml" />
|
||||
<EmbeddedResource Include=".\ShowPlan\TestExecutionPlanRecommendations.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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<ExecutionPlanRecommendation> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user