mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -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);
|
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)
|
public static string TableNotInitializedException(string tableId)
|
||||||
{
|
{
|
||||||
return Keys.GetString(Keys.TableNotInitializedException, tableId);
|
return Keys.GetString(Keys.TableNotInitializedException, tableId);
|
||||||
@@ -12272,6 +12282,12 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
public const string ActualOfEstimated = "ActualOfEstimated";
|
public const string ActualOfEstimated = "ActualOfEstimated";
|
||||||
|
|
||||||
|
|
||||||
|
public const string MissingIndexFormat = "MissingIndexFormat";
|
||||||
|
|
||||||
|
|
||||||
|
public const string MissingIndexDetailsTitle = "MissingIndexDetailsTitle";
|
||||||
|
|
||||||
|
|
||||||
public const string TableNotInitializedException = "TableNotInitializedException";
|
public const string TableNotInitializedException = "TableNotInitializedException";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4605,6 +4605,19 @@
|
|||||||
{1} ({2}%)</value>
|
{1} ({2}%)</value>
|
||||||
<comment>.
|
<comment>.
|
||||||
Parameters: 0 - actual (string), 1 - estimated (string), 2 - percent (decimal) </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>
|
||||||
<data name="TableNotInitializedException" xml:space="preserve">
|
<data name="TableNotInitializedException" xml:space="preserve">
|
||||||
<value>Initialization is not properly done for table with id '{0}'</value>
|
<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}%)
|
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%)
|
#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}%)
|
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
|
# Table Designer
|
||||||
|
|||||||
@@ -5742,6 +5742,22 @@
|
|||||||
<target state="new">Columns</target>
|
<target state="new">Columns</target>
|
||||||
<note></note>
|
<note></note>
|
||||||
</trans-unit>
|
</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>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
@@ -908,7 +908,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);
|
plans = ShowPlanGraphUtils.CreateShowPlanGraph(xmlString, Path.GetFileName(ownerUri));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,9 +21,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Query { get; set; }
|
public string Query { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Underlying xml string used for generating execution plan graph
|
/// Graph file that used to generate ExecutionPlanGraph
|
||||||
/// </summary>
|
/// </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
|
public class ExecutionPlanNode
|
||||||
@@ -113,12 +117,39 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Size of the rows returned by the subtree of the edge.
|
/// Size of the rows returned by the subtree of the edge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value></value>
|
|
||||||
public double RowSize { get; set; }
|
public double RowSize { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Edge properties to be shown in the tooltip.
|
/// Edge properties to be shown in the tooltip.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value></value>
|
|
||||||
public List<ExecutionPlanGraphPropertyBase> Properties { get; set; }
|
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.
|
// 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
|
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
||||||
{
|
{
|
||||||
public class Description
|
public class Description
|
||||||
@@ -14,7 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
get { return this.title; }
|
get { return this.title; }
|
||||||
set
|
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
|
set
|
||||||
{
|
{
|
||||||
string text = value.Trim();
|
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; }
|
get { return this.clusteredMode; }
|
||||||
set
|
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
|
public List<MissingIndex> MissingIndices { get; set; }
|
||||||
{
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Member variables
|
#region Member variables
|
||||||
|
|
||||||
private string title = string.Empty;
|
private string title = string.Empty;
|
||||||
private string queryText = string.Empty;
|
private string queryText = string.Empty;
|
||||||
private string toolTipQueryText = string.Empty;
|
private string toolTipQueryText = string.Empty;
|
||||||
private string clusteredMode = string.Empty;
|
private string clusteredMode = string.Empty;
|
||||||
private bool isClusteredMode = false;
|
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";
|
|
||||||
|
|
||||||
#endregion
|
#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;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -45,9 +44,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
{
|
{
|
||||||
plan = ReadXmlShowPlan(dataSource);
|
plan = ReadXmlShowPlan(dataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ShowPlanGraph> graphs = new List<ShowPlanGraph>();
|
List<ShowPlanGraph> graphs = new List<ShowPlanGraph>();
|
||||||
|
|
||||||
|
int statementIndex = 0;
|
||||||
foreach (BaseStmtInfoType statement in EnumStatements(plan))
|
foreach (BaseStmtInfoType statement in EnumStatements(plan))
|
||||||
{
|
{
|
||||||
// Reset currentNodeId (used through Context) and create new context
|
// 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);
|
NodeBuilderContext context = new NodeBuilderContext(new ShowPlanGraph(), this.showPlanType, this);
|
||||||
// Parse the statement block
|
// Parse the statement block
|
||||||
XmlPlanParser.Parse(statement, null, null, context);
|
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
|
// Add graph to the list
|
||||||
graphs.Add(context.Graph);
|
graphs.Add(context.Graph);
|
||||||
|
// Incrementing statement index
|
||||||
|
statementIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return graphs.ToArray();
|
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.
|
// Now make the new plan based on the existing one that contains only one statement.
|
||||||
ShowPlanXML plan = ReadXmlShowPlan(dataSource);
|
ShowPlanXML plan = ReadXmlShowPlan(dataSource);
|
||||||
plan.BatchSequence = new StmtBlockType[][]
|
plan.BatchSequence = new StmtBlockType[][]
|
||||||
{
|
{
|
||||||
new StmtBlockType[] { newStatementBlock }
|
new StmtBlockType[] { newStatementBlock }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serialize the new plan.
|
// Serialize the new plan.
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
Serializer.Serialize(new StringWriter(stringBuilder), plan);
|
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
|
#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>
|
/// <summary>
|
||||||
/// The QueryPlanHash as recorded in the RootNode for this graph, null if not available
|
/// The QueryPlanHash as recorded in the RootNode for this graph, null if not available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -12,14 +13,19 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
|||||||
{
|
{
|
||||||
public class ShowPlanGraphUtils
|
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);
|
ShowPlanGraph.ShowPlanGraph[] graphs = ShowPlanGraph.ShowPlanGraph.ParseShowPlanXML(xml, ShowPlanGraph.ShowPlanType.Unknown);
|
||||||
return graphs.Select(g => new ExecutionPlanGraph
|
return graphs.Select(g => new ExecutionPlanGraph
|
||||||
{
|
{
|
||||||
Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root),
|
Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root),
|
||||||
Query = g.Statement,
|
Query = g.Statement,
|
||||||
XmlString = xml
|
GraphFile = new ExecutionPlanGraphFile
|
||||||
|
{
|
||||||
|
GraphFileContent = xml,
|
||||||
|
GraphFileType = "xml"
|
||||||
|
},
|
||||||
|
Recommendations = ParseRecommendations(g, fileName)
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,5 +91,36 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
|||||||
}
|
}
|
||||||
return propsList;
|
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}" />
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Remove=".\ShowPlan\TestExecution.xml" />
|
<Content Remove=".\ShowPlan\TestExecution.xml" />
|
||||||
<EmbeddedResource Include=".\ShowPlan\TestExecutionPlan.xml" />
|
<EmbeddedResource Include=".\ShowPlan\TestExecutionPlan.xml" />
|
||||||
</ItemGroup>
|
</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.
|
// 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.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -16,20 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
|
|||||||
{
|
{
|
||||||
private string queryPlanFileText;
|
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]
|
[Test]
|
||||||
public void ParseXMLFileReturnsValidShowPlanGraph()
|
public void ParseXMLFileReturnsValidShowPlanGraph()
|
||||||
{
|
{
|
||||||
|
ReadFile(".ShowPlan.TestExecutionPlan.xml");
|
||||||
var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText);
|
var showPlanGraphs = ShowPlanGraphUtils.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");
|
||||||
@@ -38,8 +30,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
|
|||||||
[Test]
|
[Test]
|
||||||
public void ParsingNestedProperties()
|
public void ParsingNestedProperties()
|
||||||
{
|
{
|
||||||
|
ReadFile(".ShowPlan.TestExecutionPlan.xml");
|
||||||
string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" };
|
string[] commonNestedPropertiesNames = { "MemoryGrantInfo", "OptimizerHardwareDependentProperties" };
|
||||||
var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText);
|
var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(queryPlanFileText, "testFile.sql");
|
||||||
ExecutionPlanNode rootNode = showPlanGraphs[0].Root;
|
ExecutionPlanNode rootNode = showPlanGraphs[0].Root;
|
||||||
rootNode.Properties.ForEach(p =>
|
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