Files
sqltoolsservice/src/Microsoft.SqlTools.ServiceLayer/ShowPlan/ShowPlanGraphUtils.cs
Aasim Khan 92a7248455 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
2022-01-28 11:35:48 -08:00

127 lines
5.1 KiB
C#

//
// Copyright (c) Microsoft. All rights reserved.
// 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;
using Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph;
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
{
public class ShowPlanGraphUtils
{
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,
GraphFile = new ExecutionPlanGraphFile
{
GraphFileContent = xml,
GraphFileType = "xml"
},
Recommendations = ParseRecommendations(g, fileName)
}).ToList();
}
private static ExecutionPlanNode ConvertShowPlanTreeToExecutionPlanTree(Node currentNode)
{
return new ExecutionPlanNode
{
Type = currentNode.Operation.Image,
Cost = currentNode.Cost,
SubTreeCost = currentNode.SubtreeCost,
Description = currentNode.Description,
Subtext = currentNode.GetDisplayLinesOfText(),
RelativeCost = currentNode.RelativeCost,
Properties = GetProperties(currentNode.Properties),
Children = currentNode.Children.Select(x => ConvertShowPlanTreeToExecutionPlanTree(x)).ToList(),
Edges = currentNode.Edges.Select(x => ConvertShowPlanEdgeToExecutionPlanEdge(x)).ToList(),
Name = currentNode.DisplayName,
ElapsedTimeInMs = currentNode.ElapsedTimeInMs
};
}
private static ExecutionPlanEdges ConvertShowPlanEdgeToExecutionPlanEdge(Edge edge)
{
return new ExecutionPlanEdges
{
RowCount = edge.RowCount,
RowSize = edge.RowSize,
Properties = GetProperties(edge.Properties)
};
}
private static List<ExecutionPlanGraphPropertyBase> GetProperties(PropertyDescriptorCollection props)
{
List<ExecutionPlanGraphPropertyBase> propsList = new List<ExecutionPlanGraphPropertyBase>();
foreach (PropertyValue prop in props)
{
var complexProperty = prop.Value as ExpandableObjectWrapper;
if (complexProperty == null)
{
var propertyValue = prop.DisplayValue;
propsList.Add(new ExecutionPlanGraphProperty()
{
Name = prop.DisplayName,
Value = propertyValue,
ShowInTooltip = prop.IsBrowsable,
DisplayOrder = prop.DisplayOrder,
IsLongString = prop.IsLongString,
});
}
else
{
var propertyValue = GetProperties(complexProperty.Properties);
propsList.Add(new NestedExecutionPlanGraphProperty()
{
Name = prop.DisplayName,
Value = propertyValue,
ShowInTooltip = prop.IsBrowsable,
DisplayOrder = prop.DisplayOrder,
IsLongString = prop.IsLongString,
});
}
}
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
*/
";
}
}
}