mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-22 01:25:44 -05:00
Renames ShowPlan directories along with corresponding namespaces (#1435)
* Renames ShowPlan directories along with corresponding namespaces * Renames ShowPlanGraphUtils to ExecutionPlanGraphUtils * Revert "Renames ShowPlanGraphUtils to ExecutionPlanGraphUtils" This reverts commit 5dc2696ae906598447eed7360a3f342218432b83. * Reverts show plan tests name change. * Renames show plan test XML files. * Renames ported directory to ShowPlan and updates namespace accordingly
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Execution plan graph object that is sent over JSON RPC
|
||||
/// </summary>
|
||||
public class ExecutionPlanGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// Root of the execution plan tree
|
||||
/// </summary>
|
||||
public ExecutionPlanNode Root { get; set; }
|
||||
/// <summary>
|
||||
/// Underlying query for the execution plan graph
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
/// <summary>
|
||||
/// Graph file that used to generate ExecutionPlanGraph
|
||||
/// </summary>
|
||||
public ExecutionPlanGraphInfo GraphFile { get; set; }
|
||||
/// <summary>
|
||||
/// Index recommendations given by show plan to improve query performance
|
||||
/// </summary>
|
||||
public List<ExecutionPlanRecommendation> Recommendations { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionPlanNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the node. This determines the icon that is displayed for it
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
/// <summary>
|
||||
/// Cost associated with the node
|
||||
/// </summary>
|
||||
public double Cost { get; set; }
|
||||
/// <summary>
|
||||
/// Cost of the node subtree
|
||||
/// </summary>
|
||||
public double SubTreeCost { get; set; }
|
||||
/// <summary>
|
||||
/// Relative cost of the node compared to its siblings.
|
||||
/// </summary>
|
||||
public double RelativeCost { get; set; }
|
||||
/// <summary>
|
||||
/// Time take by the node operation in milliseconds
|
||||
/// </summary>
|
||||
public long? ElapsedTimeInMs { get; set; }
|
||||
/// <summary>
|
||||
/// Node properties to be shown in the tooltip
|
||||
/// </summary>
|
||||
public List<ExecutionPlanGraphPropertyBase> Properties { get; set; }
|
||||
/// <summary>
|
||||
/// Display name for the node
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Description associated with the node.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// Subtext displayed under the node name
|
||||
/// </summary>
|
||||
public string[] Subtext { get; set; }
|
||||
public List<ExecutionPlanNode> Children { get; set; }
|
||||
public List<ExecutionPlanEdges> Edges { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionPlanGraphPropertyBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the property
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Flag to show/hide props in tooltip
|
||||
/// </summary>
|
||||
public bool ShowInTooltip { get; set; }
|
||||
/// <summary>
|
||||
/// Display order of property
|
||||
/// </summary>
|
||||
public int DisplayOrder { get; set; }
|
||||
/// <summary>
|
||||
/// Flag to show property at the bottom of tooltip. Generally done for for properties with longer value.
|
||||
/// </summary>
|
||||
public bool PositionAtBottom { get; set; }
|
||||
/// <summary>
|
||||
/// Value to be displayed in UI like tooltips and properties View
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string DisplayValue { get; set; }
|
||||
}
|
||||
|
||||
public class NestedExecutionPlanGraphProperty : ExecutionPlanGraphPropertyBase
|
||||
{
|
||||
/// <summary>
|
||||
/// In case of nested properties, the value field is a list of properties.
|
||||
/// </summary>
|
||||
public List<ExecutionPlanGraphPropertyBase> Value { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionPlanGraphProperty : ExecutionPlanGraphPropertyBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Formatted value for the property
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionPlanEdges
|
||||
{
|
||||
/// <summary>
|
||||
/// Count of the rows returned by the subtree of the edge.
|
||||
/// </summary>
|
||||
public double RowCount { get; set; }
|
||||
/// <summary>
|
||||
/// Size of the rows returned by the subtree of the edge.
|
||||
/// </summary>
|
||||
public double RowSize { get; set; }
|
||||
/// <summary>
|
||||
/// Edge properties to be shown in the tooltip.
|
||||
/// </summary>
|
||||
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 ExecutionPlanGraphInfo
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// 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;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
|
||||
{
|
||||
public class GetExecutionPlanParams
|
||||
{
|
||||
public ExecutionPlanGraphInfo GraphInfo { get; set; }
|
||||
}
|
||||
|
||||
public class GetExecutionPlanResult
|
||||
{
|
||||
public List<ExecutionPlanGraph> Graphs { get; set; }
|
||||
}
|
||||
|
||||
public class GetExecutionPlanRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<GetExecutionPlanParams, GetExecutionPlanResult> Type =
|
||||
RequestType<GetExecutionPlanParams, GetExecutionPlanResult>.Create("queryexecutionplan/getexecutionplan");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Migration Service functionality
|
||||
/// </summary>
|
||||
public sealed class ExecutionPlanService : IDisposable
|
||||
{
|
||||
private static readonly Lazy<ExecutionPlanService> instance = new Lazy<ExecutionPlanService>(() => new ExecutionPlanService());
|
||||
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new MigrationService instance with default parameters
|
||||
/// </summary>
|
||||
public ExecutionPlanService()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton instance object
|
||||
/// </summary>
|
||||
public static ExecutionPlanService Instance
|
||||
{
|
||||
get { return instance.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service host object for sending/receiving requests/events.
|
||||
/// Internal for testing purposes.
|
||||
/// </summary>
|
||||
internal IProtocolEndpoint ServiceHost
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the ShowPlan Service instance
|
||||
/// </summary>
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
{
|
||||
this.ServiceHost = serviceHost;
|
||||
this.ServiceHost.SetRequestHandler(GetExecutionPlanRequest.Type, HandleGetExecutionPlan);
|
||||
}
|
||||
|
||||
private async Task HandleGetExecutionPlan(GetExecutionPlanParams requestParams, RequestContext<GetExecutionPlanResult> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plans = ShowPlanGraphUtils.CreateShowPlanGraph(requestParams.GraphInfo.GraphFileContent, "");
|
||||
await requestContext.SendResult(new GetExecutionPlanResult
|
||||
{
|
||||
Graphs = plans
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await requestContext.SendError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the ShowPlan Service
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds hierarchy of Graph objects from SQL 2000 Actual ShowPlan Record Set
|
||||
/// </summary>
|
||||
internal class ActualPlanDataReaderNodeBuilder : DataReaderNodeBuilder
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Constructs ActualPlanDataReaderNodeBuilder
|
||||
/// </summary>
|
||||
public ActualPlanDataReaderNodeBuilder() : base()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
protected override ShowPlanType ShowPlanType
|
||||
{
|
||||
get { return ShowPlanType.Actual; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of Node Id in the recordset
|
||||
/// </summary>
|
||||
protected override int NodeIdIndex
|
||||
{
|
||||
get { return 4; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of Parent Id in the recordset
|
||||
/// </summary>
|
||||
protected override int ParentIndex
|
||||
{
|
||||
get { return 5; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets property names that correspond to values returned
|
||||
/// in each ShowPlan row.
|
||||
/// </summary>
|
||||
/// <returns>Array of property names</returns>
|
||||
protected override string[] GetPropertyNames()
|
||||
{
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private members
|
||||
|
||||
private static string[] propertyNames = new string[]
|
||||
{
|
||||
NodeBuilderConstants.ActualRows, // Rows
|
||||
NodeBuilderConstants.ActualExecutions, // Executes
|
||||
NodeBuilderConstants.StatementText, // StmtText
|
||||
null, // StmtId
|
||||
NodeBuilderConstants.NodeId,
|
||||
null, // Parent
|
||||
NodeBuilderConstants.PhysicalOp,
|
||||
NodeBuilderConstants.LogicalOp,
|
||||
NodeBuilderConstants.Argument,
|
||||
NodeBuilderConstants.DefinedValues,
|
||||
NodeBuilderConstants.EstimateRows,
|
||||
NodeBuilderConstants.EstimateIO,
|
||||
NodeBuilderConstants.EstimateCPU,
|
||||
NodeBuilderConstants.AvgRowSize,
|
||||
NodeBuilderConstants.TotalSubtreeCost,
|
||||
NodeBuilderConstants.OutputList,
|
||||
NodeBuilderConstants.Warnings,
|
||||
NodeBuilderConstants.StatementType, // Type
|
||||
NodeBuilderConstants.Parallel,
|
||||
null, // EstimateExecutions
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
//
|
||||
// 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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
#region FloatTypeConverter
|
||||
|
||||
/// <summary>
|
||||
/// FloatTypeConverter is used to get a desired float / double representation
|
||||
/// in the property sheet and the tool tip.
|
||||
/// The currently used scientific format isn't very readable
|
||||
///
|
||||
/// To use this converter, add the following attribute on top of the property:
|
||||
/// [TypeConverter(typeof(FloatTypeConverter))]
|
||||
/// </summary>
|
||||
|
||||
public class FloatTypeConverter : TypeConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the object value to another type.
|
||||
/// In this case the method only supports conversion to string.
|
||||
/// </summary>
|
||||
/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
|
||||
/// <param name="culture">A CultureInfo object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed.</param>
|
||||
/// <param name="value">The Object to convert.</param>
|
||||
/// <param name="destinationType">The Type to convert the value parameter to.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(value);
|
||||
if (converter.CanConvertTo(typeof(double)))
|
||||
{
|
||||
double doubleValue = (double)converter.ConvertTo(value, typeof(double));
|
||||
return doubleValue.ToString("0.#######", CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DataSizeTypeConverter
|
||||
/// <summary>
|
||||
/// DataSizeTypeConverter is used to represent data size in bytes,
|
||||
/// kilobytes, megabytes, etc., depending on the actual number.
|
||||
///
|
||||
/// To use this converter, add the following attribute on top of the property:
|
||||
/// [TypeConverter(typeof(DataSizeTypeConverter))]
|
||||
/// </summary>
|
||||
|
||||
public class DataSizeTypeConverter : TypeConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the object value to another type.
|
||||
/// In this case the method only supports conversion to string.
|
||||
/// </summary>
|
||||
/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
|
||||
/// <param name="culture">A CultureInfo object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed.</param>
|
||||
/// <param name="value">The Object to convert.</param>
|
||||
/// <param name="destinationType">The Type to convert the value parameter to.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
return this.ConvertTo(context, culture, value, destinationType, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object value to another type.
|
||||
/// In this case the method only supports conversion to string.
|
||||
/// </summary>
|
||||
/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
|
||||
/// <param name="culture">A CultureInfo object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed.</param>
|
||||
/// <param name="value">The Object to convert.</param>
|
||||
/// <param name="destinationType">The Type to convert the value parameter to.</param>
|
||||
/// <param name="formatIndex">The index in size formats to start with.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
protected object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType, int formatIndex)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(value);
|
||||
if (converter.CanConvertTo(typeof(double)))
|
||||
{
|
||||
double dataSize = (double)converter.ConvertTo(value, typeof(double));
|
||||
Debug.Assert(dataSize >= 0, "Data size must not be a negative number.");
|
||||
|
||||
// This cycle finds an optimal range for the size where no more than
|
||||
// 4 digits are displayed. Furthermore the result is rounded to a neareast
|
||||
// integer. So it will display sizes up to 9999 bytes in bytes then switch to
|
||||
// to 10K. Then it will go up to 9999 KB and switch to 10M.
|
||||
// Please note that 10000 bytes = 9.76 KB and will be rounded to 10 KB.
|
||||
while (formatIndex < sizeFormats.Length - 1)
|
||||
{
|
||||
if ((long)Math.Round(dataSize) < 10000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
dataSize /= 1024;
|
||||
formatIndex++;
|
||||
}
|
||||
|
||||
return String.Format(culture, sizeFormats[formatIndex], (long)Math.Round(dataSize));
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
static string[] sizeFormats = new string[]
|
||||
{
|
||||
SR.SizeInBytesFormat,
|
||||
SR.SizeInKiloBytesFormat,
|
||||
SR.SizeInMegaBytesFormat,
|
||||
SR.SizeInGigaBytesFormat,
|
||||
SR.SizeInTeraBytesFormat
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region KBSizeTypeConverter
|
||||
|
||||
/// <summary>
|
||||
/// KBSizeTypeConverter is used to represent data size in
|
||||
/// kilobytes, megabytes, etc., depending on the actual number.
|
||||
/// Assumes input is in kilobytes
|
||||
///
|
||||
/// To use this converter, add the following attribute on top of the property:
|
||||
/// [TypeConverter(typeof(KBSizeTypeConverter))]
|
||||
/// </summary>
|
||||
public sealed class KBSizeTypeConverter : DataSizeTypeConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the object value to another type.
|
||||
/// In this case the method only supports conversion to string.
|
||||
/// </summary>
|
||||
/// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
|
||||
/// <param name="culture">A CultureInfo object. If a null reference (Nothing in Visual Basic) is passed, the current culture is assumed.</param>
|
||||
/// <param name="value">The Object to convert.</param>
|
||||
/// <param name="destinationType">The Type to convert the value parameter to.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
// formatIndex of 1 indicates value of input should be in KB
|
||||
return base.ConvertTo(context, culture, value, destinationType, formatIndex: 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DisplayNameAndDescription
|
||||
|
||||
/// <summary>
|
||||
/// Describes property DisplayName and description keywords.
|
||||
/// </summary>
|
||||
public class DisplayNameDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Public default constructor
|
||||
/// </summary>
|
||||
/// <param name="displayName">Property DisplayName key.</param>
|
||||
public DisplayNameDescriptionAttribute(string displayName)
|
||||
{
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public default constructor
|
||||
/// </summary>
|
||||
/// <param name="displayName">Property DisplayName key.</param>
|
||||
/// <param name="description">Property Description key.</param>
|
||||
public DisplayNameDescriptionAttribute(string displayName, string description)
|
||||
{
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Display name
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get { return this.displayName; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets Description
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get { return this.description; }
|
||||
}
|
||||
|
||||
private string displayName;
|
||||
private string description;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DisplayOrder
|
||||
|
||||
/// <summary>
|
||||
/// Represent order attribute. Tool tip window uses this attribute to sort properties accordingly
|
||||
/// </summary>
|
||||
public sealed class DisplayOrderAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Public default constructor
|
||||
/// </summary>
|
||||
/// <param name="displayOrder"></param>
|
||||
public DisplayOrderAttribute(int displayOrder)
|
||||
{
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display order
|
||||
/// </summary>
|
||||
public int DisplayOrder
|
||||
{
|
||||
get { return this.displayOrder; }
|
||||
}
|
||||
|
||||
private int displayOrder;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ShowInToolTip
|
||||
|
||||
/// <summary>
|
||||
/// Represent order attribute. Tool tip window uses this attribute to sort properties accordingly
|
||||
/// </summary>
|
||||
|
||||
public sealed class ShowInToolTipAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Public default constructor.
|
||||
/// </summary>
|
||||
public ShowInToolTipAttribute()
|
||||
{
|
||||
this.value = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public constructor.
|
||||
/// </summary>
|
||||
/// <param name="value">Specifies whether the corresponding property should be visible in tool tips.
|
||||
/// The default value is true.</param>
|
||||
public ShowInToolTipAttribute(bool value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a property should be shown in ToolTip; otherwise false.
|
||||
/// </summary>
|
||||
public bool Value
|
||||
{
|
||||
get { return this.value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a property is a long string and should take an entire row in a tool tip; otherwise false.
|
||||
/// </summary>
|
||||
public bool LongString
|
||||
{
|
||||
get { return this.longString; }
|
||||
set { this.longString = value; }
|
||||
}
|
||||
|
||||
private bool value = true;
|
||||
private bool longString = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan.Comparison
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles operations for creating and comparing skeletons of showplan trees
|
||||
/// A skeleton is the tree with some nodes filtered out to reduce complexity
|
||||
/// </summary>
|
||||
public class SkeletonManager
|
||||
{
|
||||
public SkeletonManager() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a skeleton tree representing logical structure of the showplan
|
||||
/// Primarily represents joins and data access operators
|
||||
/// </summary>
|
||||
/// <param name="root">Node to construct skeleton of</param>
|
||||
/// <returns>SkeletonNode with children representing logical descendants of the input node</returns>
|
||||
public SkeletonNode CreateSkeleton(Node root)
|
||||
{
|
||||
Node rootNode = root;
|
||||
var childCount = root.Children.Count;
|
||||
if (childCount > 1)
|
||||
{
|
||||
SkeletonNode skeletonParent = new SkeletonNode(root);
|
||||
foreach (Node child in root.Children)
|
||||
{
|
||||
SkeletonNode skeletonChild = CreateSkeleton(child);
|
||||
skeletonParent.AddChild(skeletonChild);
|
||||
}
|
||||
return skeletonParent;
|
||||
}
|
||||
else if (childCount == 1)
|
||||
{
|
||||
if (!ShouldIgnoreDuringComparison(rootNode))
|
||||
{
|
||||
// get children recursively then return this node to add it to the skeleton
|
||||
SkeletonNode skeletonParent = new SkeletonNode(root);
|
||||
SkeletonNode child = CreateSkeleton(root.Children.ElementAt(0));
|
||||
skeletonParent.AddChild(child);
|
||||
return skeletonParent;
|
||||
}
|
||||
// if ignoring root, just go on to the next node
|
||||
return CreateSkeleton(root.Children.First());
|
||||
}
|
||||
// no children; base case
|
||||
SkeletonNode skeletonNode = new SkeletonNode(root);
|
||||
return skeletonNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks root and all children for equivalent tree structure and logical equivalence at the node level
|
||||
/// </summary>
|
||||
/// <param name="root1"></param>
|
||||
/// <param name="root2"></param>
|
||||
/// <returns></returns>
|
||||
public bool AreSkeletonsEquivalent(SkeletonNode root1, SkeletonNode root2, bool ignoreDatabaseName)
|
||||
{
|
||||
if (root1 == null && root2 == null)
|
||||
return true;
|
||||
|
||||
if (root1 == null || root2 == null)
|
||||
return false;
|
||||
|
||||
if (!root1.BaseNode.IsLogicallyEquivalentTo(root2.BaseNode, ignoreDatabaseName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (root1.Children.Count != root2.Children.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var childIterator = 0;
|
||||
while (childIterator < root1.Children.Count)
|
||||
{
|
||||
var checkMatch = AreSkeletonsEquivalent(root1.Children.ElementAt(childIterator), root2.Children.ElementAt(childIterator), ignoreDatabaseName);
|
||||
if (!checkMatch)
|
||||
{
|
||||
// at least one pair of children (ie inner.Child1 & outer.Child1) didn't match; stop checking rest
|
||||
return false;
|
||||
}
|
||||
childIterator++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the largest matching subtrees in two skeletons and colors those subtrees a unique color
|
||||
/// </summary>
|
||||
/// <param name="skeleton1"></param>
|
||||
/// <param name="skeleton2"></param>
|
||||
public void ColorMatchingSections(SkeletonNode skeleton1, SkeletonNode skeleton2, bool ignoreDatabaseName)
|
||||
{
|
||||
// starting node for the outer loop iteration
|
||||
SkeletonNode outerNode = skeleton1;
|
||||
Queue<SkeletonNode> outerQueue = new Queue<SkeletonNode>();
|
||||
|
||||
int groupIndexCounter = 1;
|
||||
|
||||
// Iterates over all nodes in skeleton1
|
||||
while (outerNode != null)
|
||||
{
|
||||
bool matchFound = false;
|
||||
SkeletonNode innerNode = skeleton2;
|
||||
Queue<SkeletonNode> innerQueue = new Queue<SkeletonNode>();
|
||||
// to find all the match sleleton2 node for skeleton1, iterate over each node of skeleton2 until all innerNode have been tested, there might be multiple match
|
||||
while (innerNode != null)
|
||||
{
|
||||
if (this.AreSkeletonsEquivalent(outerNode, innerNode, ignoreDatabaseName))
|
||||
{
|
||||
matchFound = true;
|
||||
int matchColor = groupIndexCounter++;
|
||||
if (innerNode.ParentNode != null && innerNode.ParentNode.HasMatch)
|
||||
{
|
||||
int parentColor = innerNode.ParentNode.GroupIndex;
|
||||
int innerColor = innerNode.GroupIndex;
|
||||
// innerNode is the root of a matching subtree, so use its color for the new match instead of the random new color
|
||||
if (parentColor != innerColor)
|
||||
{
|
||||
matchColor = innerColor;
|
||||
}
|
||||
}
|
||||
else if (innerNode.HasMatch)
|
||||
{
|
||||
matchColor = innerNode.GroupIndex;
|
||||
}
|
||||
else if (outerNode.HasMatch)
|
||||
{
|
||||
// outerNode already finds a matching innerNode, but we keep looking for more matching innerNode
|
||||
matchColor = outerNode.GroupIndex;
|
||||
}
|
||||
|
||||
outerNode.AddMatchingSkeletonNode(innerNode, ignoreDatabaseName);
|
||||
innerNode.AddMatchingSkeletonNode(outerNode, ignoreDatabaseName);
|
||||
outerNode.ChangeSkeletonGroupIndex(matchColor);
|
||||
innerNode.ChangeSkeletonGroupIndex(matchColor);
|
||||
}
|
||||
|
||||
// even if we found a matching innerNode, we keep looking since there might be other innerNode that matches same outerNode
|
||||
foreach (SkeletonNode child in innerNode.Children)
|
||||
{
|
||||
innerQueue.Enqueue(child);
|
||||
}
|
||||
innerNode = innerQueue.Any() ? innerQueue.Dequeue() : null;
|
||||
|
||||
}
|
||||
|
||||
// no match at all, so add this node's children to queue of nodes to check
|
||||
// effectively does a bfs - doesn't check children if a match has been found (and the entire subtree colored)
|
||||
if (!matchFound)
|
||||
{
|
||||
foreach (SkeletonNode child in outerNode.Children)
|
||||
{
|
||||
outerQueue.Enqueue(child);
|
||||
}
|
||||
}
|
||||
outerNode = outerQueue.Any() ? outerQueue.Dequeue() : null;
|
||||
}
|
||||
}
|
||||
|
||||
public Node FindNextNonIgnoreNode(Node node)
|
||||
{
|
||||
Node curNode = node;
|
||||
while (curNode != null && ShouldIgnoreDuringComparison(curNode))
|
||||
{
|
||||
if (curNode.Children.Count > 0)
|
||||
{
|
||||
curNode = curNode.Children.ElementAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// should ignore, but this is a leaf node, so there is no matching node
|
||||
curNode = null;
|
||||
}
|
||||
}
|
||||
return curNode;
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the node should be ignored when building a skeleton of the showplan
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
private bool ShouldIgnoreDuringComparison(Node node)
|
||||
{
|
||||
return IgnoreWhenBuildingSkeleton.Contains(node.Operation.Name) || (node[NodeBuilderConstants.LogicalOp] == null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// List of Node names which can safely be ignored when building the skeleton
|
||||
/// We can ignore these because not finding a matching between them shouldn't imapct of the shaping of matching nodes
|
||||
/// However, if in the future we see a use case to benefit from matching one of them, for ex I removed Filter because we need it to be skeleton node
|
||||
/// so we user can find issue for, and jump to the Filter node pair when doing scenario based issue detection
|
||||
/// </summary>
|
||||
private List<string> IgnoreWhenBuildingSkeleton = new List<string> { SR.Keys.Assert, SR.Keys.BatchHashTableBuild, SR.Keys.Bitmap, SR.Keys.Collapse, SR.Keys.RepartitionStreams,
|
||||
SR.Keys.ComputeScalar, SR.Keys.MergeInterval, SR.Keys.Parallelism, SR.Keys.Print, SR.Keys.RowCountSpool, SR.Keys.LogicalOpLazySpool,
|
||||
SR.Keys.TableSpool, SR.Keys.Segment, SR.Keys.SequenceProject, SR.Keys.Split, SR.Keys.Spool, SR.Keys.Window,
|
||||
SR.Keys.Sort, SR.Keys.Top, SR.Keys.LogicalOpTopNSort };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// 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.ShowPlan.Comparison
|
||||
{
|
||||
public class SkeletonNode
|
||||
{
|
||||
public Node BaseNode {get; set;}
|
||||
public List<SkeletonNode> MatchingNodes { get; set; }
|
||||
public bool HasMatch { get { return MatchingNodes.Count > 0; } }
|
||||
public SkeletonNode ParentNode { get; set; }
|
||||
public IList<SkeletonNode> Children { get; set; }
|
||||
|
||||
public int GroupIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.BaseNode.GroupIndex;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.BaseNode.GroupIndex = value;
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletonNode(Node baseNode)
|
||||
{
|
||||
baseNode[NodeBuilderConstants.SkeletonNode] = this;
|
||||
this.BaseNode = baseNode;
|
||||
this.Children = new List<SkeletonNode>();
|
||||
this.MatchingNodes = new List<SkeletonNode>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds node to children collection and sets this node as parent of the child
|
||||
/// </summary>
|
||||
/// <param name="child"></param>
|
||||
public void AddChild(SkeletonNode child)
|
||||
{
|
||||
child.ParentNode = this;
|
||||
this.Children.Add(child);
|
||||
}
|
||||
|
||||
public void ChangeSkeletonGroupIndex(int groupIndex)
|
||||
{
|
||||
this.GroupIndex = groupIndex;
|
||||
foreach (SkeletonNode child in this.Children)
|
||||
{
|
||||
child.ChangeSkeletonGroupIndex(groupIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMatchingSkeletonNode(SkeletonNode match, bool ignoreDatabaseName, bool matchAllChildren=true)
|
||||
{
|
||||
this.BaseNode[NodeBuilderConstants.SkeletonHasMatch] = true;
|
||||
if (matchAllChildren == true)
|
||||
{
|
||||
SkeletonManager manager = new SkeletonManager();
|
||||
foreach (SkeletonNode baseChild in this.Children)
|
||||
{
|
||||
foreach (SkeletonNode matchChild in match.Children)
|
||||
{
|
||||
// make sure this is the right child to match
|
||||
if (baseChild.BaseNode.IsLogicallyEquivalentTo(matchChild.BaseNode, ignoreDatabaseName))
|
||||
{
|
||||
baseChild.AddMatchingSkeletonNode(matchChild, ignoreDatabaseName, matchAllChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.MatchingNodes.Add(match);
|
||||
}
|
||||
|
||||
public Graph GetGraph()
|
||||
{
|
||||
return this.BaseNode.Graph;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// 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.ShowPlan
|
||||
{
|
||||
internal sealed class ConditionParser : XmlPlanHierarchyParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates FunctionType blocks and removes all items from UDF property.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public override IEnumerable<FunctionTypeItem> ExtractFunctions(object parsedItem)
|
||||
{
|
||||
StmtCondTypeCondition condition = parsedItem as StmtCondTypeCondition;
|
||||
if (condition != null && condition.UDF != null)
|
||||
{
|
||||
foreach (FunctionType function in condition.UDF)
|
||||
{
|
||||
yield return new FunctionTypeItem(function, FunctionTypeItem.ItemType.Udf);
|
||||
}
|
||||
|
||||
condition.UDF = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private ConditionParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static ConditionParser conditionParser = null;
|
||||
public static new ConditionParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (conditionParser == null)
|
||||
{
|
||||
conditionParser = new ConditionParser();
|
||||
}
|
||||
return conditionParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public static string Parenthesis(string text)
|
||||
{
|
||||
return string.Format("({0})", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses StmtCursorType ShowPlan XML nodes
|
||||
/// </summary>
|
||||
internal class CursorOperationParser : XmlPlanParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new node and adds it to the graph.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentItem">Parent item.</param>
|
||||
/// <param name="parentNode">Parent node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
/// <returns>The node that corresponds to the item being parsed.</returns>
|
||||
public override Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
return NewNode(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines Operation that corresponds to the object being parsed.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Operation that corresponds to the node.</returns>
|
||||
protected override Operation GetNodeOperation(Node node)
|
||||
{
|
||||
object cursorOperationName = node["OperationType"];
|
||||
|
||||
Operation cursorOperation = cursorOperationName != null
|
||||
? OperationTable.GetPhysicalOperation(cursorOperationName.ToString())
|
||||
: Operation.Unknown;
|
||||
|
||||
|
||||
return cursorOperation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines node subtree cost from existing node properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Node subtree cost.</returns>
|
||||
protected override double GetNodeSubtreeCost(Node node)
|
||||
{
|
||||
// This node doesn't have subtree cost, so it
|
||||
// will be determined based on child nodes.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private CursorOperationParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static CursorOperationParser cursorOperationParser = null;
|
||||
public static CursorOperationParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cursorOperationParser == null)
|
||||
{
|
||||
cursorOperationParser = new CursorOperationParser();
|
||||
}
|
||||
return cursorOperationParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses StmtCursorType ShowPlan XML nodes
|
||||
/// </summary>
|
||||
internal class CursorStatementParser : StatementParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines Operation that corresponds to the object being parsed.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Operation that corresponds to the node.</returns>
|
||||
protected override Operation GetNodeOperation(Node node)
|
||||
{
|
||||
object cursorType = node["CursorActualType"];
|
||||
|
||||
if (cursorType == null)
|
||||
{
|
||||
cursorType = node["StatementType"];
|
||||
}
|
||||
|
||||
Operation cursor = cursorType != null
|
||||
? OperationTable.GetCursorType(cursorType.ToString())
|
||||
: Operation.Unknown;
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private CursorStatementParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static CursorStatementParser cursorStatementParser = null;
|
||||
public static new CursorStatementParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cursorStatementParser == null)
|
||||
{
|
||||
cursorStatementParser = new CursorStatementParser();
|
||||
}
|
||||
return cursorStatementParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// 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.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for building hierarchy of Graph objects from ShowPlan Record Set
|
||||
/// </summary>
|
||||
internal abstract class DataReaderNodeBuilder: INodeBuilder
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Initializes base class members.
|
||||
/// </summary>
|
||||
/// <param name="showPlanType">Show Plan type.</param>
|
||||
public DataReaderNodeBuilder()
|
||||
{}
|
||||
|
||||
#endregion
|
||||
|
||||
#region INodeBuilder
|
||||
|
||||
/// <summary>
|
||||
/// Builds one or more Graphs that
|
||||
/// represnet data from the data source.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data Source.</param>
|
||||
/// <returns>An array of AnalysisServices Graph objects.</returns>
|
||||
public ShowPlanGraph[] Execute(object dataSource)
|
||||
{
|
||||
IDataReader reader = dataSource as IDataReader;
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
Debug.Assert(false, "Unexpected ShowPlan source = " + dataSource.GetType().ToString());
|
||||
throw new ArgumentException(SR.Keys.UnknownShowPlanSource);
|
||||
}
|
||||
|
||||
List<ShowPlanGraph> graphs = new List<ShowPlanGraph>();
|
||||
Dictionary<int, Node> currentGraphNodes = null;
|
||||
NodeBuilderContext context = null;
|
||||
|
||||
object[] values = null;
|
||||
string[] names = GetPropertyNames();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
ReadValues(reader, ref values);
|
||||
|
||||
int nodeID = (int)values[NodeIdIndex];
|
||||
int parentNodeID = (int)values[ParentIndex];
|
||||
|
||||
Node parentNode = null;
|
||||
|
||||
if (parentNodeID == 0)
|
||||
{
|
||||
// Starting a new graph
|
||||
// First add an old graph to the list
|
||||
if (context != null)
|
||||
{
|
||||
graphs.Add(context.Graph);
|
||||
}
|
||||
|
||||
// Create new Context and new Nodes hashtable
|
||||
context = new NodeBuilderContext(new ShowPlanGraph(), ShowPlanType, this);
|
||||
currentGraphNodes = new Dictionary<int, Node>();
|
||||
}
|
||||
else
|
||||
{
|
||||
parentNode = currentGraphNodes[parentNodeID];
|
||||
}
|
||||
|
||||
// Create new node.
|
||||
Debug.Assert(context != null);
|
||||
Node node = CreateNode(nodeID, context);
|
||||
|
||||
ParseProperties(node, names, values);
|
||||
SetNodeSpecialProperties(node);
|
||||
|
||||
if (parentNode != null)
|
||||
{
|
||||
parentNode.Children.Add(node);
|
||||
}
|
||||
|
||||
// Add node to the hashtable
|
||||
// In some rare cases the graph may already
|
||||
// contain the node with the same ID.
|
||||
// This happens, for example, in a case of
|
||||
// Table Spool node. In this case it is safe
|
||||
// to not add the node to the currentGraphNodes collection
|
||||
// because it isn't going to have any children (guaranteed)
|
||||
if (!currentGraphNodes.ContainsKey(nodeID))
|
||||
{
|
||||
currentGraphNodes.Add(nodeID, node);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last parsed graph to the list of graphs.
|
||||
if (context != null)
|
||||
{
|
||||
graphs.Add(context.Graph);
|
||||
}
|
||||
|
||||
return graphs.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Gets property names that correspond to values returned
|
||||
/// in each ShowPlan row.
|
||||
/// </summary>
|
||||
/// <returns>Array of property names</returns>
|
||||
protected abstract string[] GetPropertyNames();
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of Node Id in the recordset
|
||||
/// </summary>
|
||||
protected abstract int NodeIdIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of Parent Id in the recordset
|
||||
/// </summary>
|
||||
protected abstract int ParentIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ShowPlanType of hte resordset
|
||||
/// </summary>
|
||||
protected abstract ShowPlanType ShowPlanType{ get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sequentially reads all columns from IDataReader
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="values"></param>
|
||||
private void ReadValues(IDataReader reader, ref object[] values)
|
||||
{
|
||||
if (values == null || reader.FieldCount != values.Length)
|
||||
{
|
||||
values = new object[reader.FieldCount];
|
||||
}
|
||||
|
||||
// We specifically need to read values sequentially
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = reader.GetValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads properties from the data row.
|
||||
/// </summary>
|
||||
/// <param name="node">Node which is populated with properties</param>
|
||||
/// <param name="names">Property names.</param>
|
||||
/// <param name="values">Property values.</param>
|
||||
private void ParseProperties(Node node, string[] names, object[] values)
|
||||
{
|
||||
int count = Math.Min(names.Length, values.Length);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (names[i] != null && !((values[i] is DBNull) || values[i] == null))
|
||||
{
|
||||
node[names[i]] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets special properties on the node.
|
||||
/// </summary>
|
||||
/// <param name="node">Node.</param>
|
||||
private static void SetNodeSpecialProperties(Node node)
|
||||
{
|
||||
// SubtreeCost is a special property that should be set separately
|
||||
node.SubtreeCost = GetNodeSubtreeCost(node);
|
||||
|
||||
Operation resultOp;
|
||||
|
||||
string nodeType = (string)node["StatementType"];
|
||||
if (string.Compare(nodeType, "PLAN_ROW", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
// This is a statement
|
||||
resultOp = OperationTable.GetStatement(nodeType);
|
||||
|
||||
node["LogicalOp"] = resultOp.DisplayName;
|
||||
node["PhysicalOp"] = resultOp.DisplayName;
|
||||
|
||||
// For statements, Argument is the same as the Statement text (if any defined)
|
||||
node["Argument"] = node["StatementText"];
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is an operation node
|
||||
|
||||
// Remove StatementText property
|
||||
PropertyDescriptor statementTextProperty = node.Properties["StatementText"];
|
||||
if (statementTextProperty != null)
|
||||
{
|
||||
node.Properties.Remove(statementTextProperty);
|
||||
}
|
||||
|
||||
// Special consideration for Argument property:
|
||||
// Try to parse Object name from it
|
||||
string argument = node["Argument"] as string;
|
||||
if (argument != null)
|
||||
{
|
||||
Match match = argumentObjectExpression.Match(argument);
|
||||
if (match != Match.Empty)
|
||||
{
|
||||
node["Object"] = match.Groups["Object"].Value;
|
||||
}
|
||||
}
|
||||
|
||||
string physicalOpType = node["PhysicalOp"] as string;
|
||||
string logicalOpType = node["LogicalOp"] as string;
|
||||
|
||||
if (physicalOpType == null || logicalOpType == null)
|
||||
{
|
||||
throw new FormatException(SR.Keys.UnknownShowPlanSource);
|
||||
}
|
||||
|
||||
// Remove spaces and other special characters from physical and logical names
|
||||
physicalOpType = operatorReplaceExpression.Replace(physicalOpType, "");
|
||||
logicalOpType = operatorReplaceExpression.Replace(logicalOpType, "");
|
||||
|
||||
Operation physicalOp = OperationTable.GetPhysicalOperation(physicalOpType);
|
||||
Operation logicalOp = OperationTable.GetLogicalOperation(logicalOpType);
|
||||
|
||||
resultOp = logicalOp != null && logicalOp.Image != null && logicalOp.Description != null
|
||||
? logicalOp : physicalOp;
|
||||
|
||||
node["LogicalOp"] = logicalOp.DisplayName;
|
||||
node["PhysicalOp"] = physicalOp.DisplayName;
|
||||
|
||||
// EstimateExecutions = EstimateRebinds + EstimateRewinds + 1
|
||||
if (node["EstimateRebinds"] != null && node["EstimateRewinds"] != null)
|
||||
{
|
||||
double estimateRebinds = (double) node["EstimateRebinds"];
|
||||
double estimateRewinds = (double) node["EstimateRewinds"];
|
||||
node["EstimateExecutions"] = estimateRebinds + estimateRewinds + 1;
|
||||
}
|
||||
|
||||
// EstimateRowsAllExecs = EstimateRows * EstimateExecutions
|
||||
double estimateRows = node["EstimateRows"] == null ? 0.0 : Convert.ToDouble(node["EstimateRows"]);
|
||||
double estimateExecutions = node["EstimateExecutions"] == null ? 0.0 : Convert.ToDouble(node["EstimateExecutions"]);
|
||||
double actualExecutions = node["ActualExecutions"] == null ? 0.0 : Convert.ToDouble(node["ActualExecutions"]);
|
||||
node["EstimateRowsAllExecs"] = estimateRows * estimateExecutions;
|
||||
}
|
||||
|
||||
Debug.Assert(resultOp.Image != null);
|
||||
node.Operation = resultOp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 'Factory' method for creating Nodes, allows for subclasses to override
|
||||
/// </summary>
|
||||
protected virtual Node CreateNode(int nodeId, NodeBuilderContext context)
|
||||
{
|
||||
return new Node(nodeId, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines node subtree cost from existing node properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Node subtree cost.</returns>
|
||||
private static double GetNodeSubtreeCost(Node node)
|
||||
{
|
||||
object value = node["TotalSubtreeCost"];
|
||||
return value != null ? Convert.ToDouble(value, CultureInfo.CurrentCulture) : 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private members
|
||||
|
||||
private static Regex operatorReplaceExpression = new Regex(@"[ \-]", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
private static Regex argumentObjectExpression = new Regex(@"OBJECT:\((?<Object>[^\)]*)\)", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public class Description
|
||||
{
|
||||
#region Properties
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return this.title; }
|
||||
set
|
||||
{
|
||||
this.title = value.Trim().Replace(Environment.NewLine, " ");
|
||||
}
|
||||
}
|
||||
|
||||
public string QueryText
|
||||
{
|
||||
get { return this.queryText; }
|
||||
set
|
||||
{
|
||||
string text = value.Trim();
|
||||
this.queryText = text.Replace(Environment.NewLine, " ");
|
||||
}
|
||||
}
|
||||
|
||||
public string ClusteredMode
|
||||
{
|
||||
get { return this.clusteredMode; }
|
||||
set
|
||||
{
|
||||
this.clusteredMode = value.Trim().Replace(Environment.NewLine, " ");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsClusteredMode
|
||||
{
|
||||
set
|
||||
{
|
||||
this.isClusteredMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class MissingIndex
|
||||
{
|
||||
public string MissingIndexCaption { get; set; }
|
||||
public string MissingIndexQueryText { get; set; }
|
||||
public string MissingIndexImpact { get; set; }
|
||||
public string MissingIndexDatabase { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// 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.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public class Edge
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
public Node FromNode;
|
||||
public Node ToNode;
|
||||
|
||||
public Edge(Node fromNode, Node toNode)
|
||||
{
|
||||
Initialize(toNode as Node);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties and methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets Edge properties.
|
||||
/// </summary>
|
||||
public PropertyDescriptorCollection Properties
|
||||
{
|
||||
get { return this.properties; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets node property value.
|
||||
/// </summary>
|
||||
public object this[string propertyName]
|
||||
{
|
||||
get
|
||||
{
|
||||
PropertyValue property = this.properties[propertyName] as PropertyValue;
|
||||
return property != null ? property.Value : null;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
PropertyValue property = this.properties[propertyName] as PropertyValue;
|
||||
if (property != null)
|
||||
{
|
||||
// Overwrite existing property value
|
||||
property.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new property
|
||||
this.properties.Add(PropertyFactory.CreateProperty(propertyName, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double RowSize
|
||||
{
|
||||
get
|
||||
{
|
||||
object propertyValue = this["AvgRowSize"];
|
||||
return propertyValue != null ? Convert.ToDouble(propertyValue, CultureInfo.CurrentCulture) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public double RowCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if(this["ActualRowsRead"] == null && this["ActualRows"] == null)
|
||||
{
|
||||
// If Actual Row count and ActualRowsRead are not set, default to estimated row count
|
||||
return EstimatedRowCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// at least one of ActualRowsRead and ActualRows is set
|
||||
double actualRowsReadValue = 0;
|
||||
double actualRowsValue = 0;
|
||||
if (this["ActualRowsRead"] != null)
|
||||
{
|
||||
actualRowsReadValue = Convert.ToDouble(this["ActualRowsRead"].ToString(), CultureInfo.CurrentCulture);
|
||||
}
|
||||
if (this["ActualRows"] != null)
|
||||
{
|
||||
actualRowsValue = Convert.ToDouble(this["ActualRows"].ToString(), CultureInfo.CurrentCulture);
|
||||
}
|
||||
return actualRowsReadValue > actualRowsValue ? actualRowsReadValue : actualRowsValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double EstimatedRowCount
|
||||
{
|
||||
get
|
||||
{
|
||||
object propertyValue = this["EstimateRows"];
|
||||
if (propertyValue == null)
|
||||
{
|
||||
propertyValue = this["StatementEstRows"];
|
||||
}
|
||||
|
||||
return propertyValue != null ? Convert.ToDouble(propertyValue, CultureInfo.CurrentCulture) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public double EstimatedDataSize
|
||||
{
|
||||
get
|
||||
{
|
||||
object propertyValue = this["EstimatedDataSize"];
|
||||
return propertyValue != null ? Convert.ToDouble(propertyValue, CultureInfo.CurrentCulture) : 0;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
this["EstimatedDataSize"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation details
|
||||
|
||||
/// <summary>
|
||||
/// Copy some of edge properties from the node connected through this edge.
|
||||
/// </summary>
|
||||
/// <param name="node">The node connected on the right side of the edge.</param>
|
||||
private void Initialize(Node node)
|
||||
{
|
||||
this.properties = new PropertyDescriptorCollection(new PropertyDescriptor[] {});
|
||||
|
||||
string[] propertyNames = new string[]
|
||||
{
|
||||
"ActualRows",
|
||||
"ActualRowsRead",
|
||||
"AvgRowSize",
|
||||
"EstimateRows",
|
||||
"EstimateRowsAllExecs",
|
||||
"StatementEstRows"
|
||||
};
|
||||
|
||||
// Copy properties
|
||||
foreach (string propertyName in propertyNames)
|
||||
{
|
||||
object value = node[propertyName];
|
||||
if (value != null)
|
||||
{
|
||||
this[propertyName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.EstimatedDataSize = this.RowSize * this.EstimatedRowCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
private PropertyDescriptorCollection properties;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds hierarchy of Graph objects from SQL 2000 Estimated ShowPlan Record Set
|
||||
/// </summary>
|
||||
internal sealed class EstimatedPlanDataReaderNodeBuilder : DataReaderNodeBuilder
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Constructs EstimatedPlanDataReaderNodeBuilder
|
||||
/// </summary>
|
||||
public EstimatedPlanDataReaderNodeBuilder() : base()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
protected override ShowPlanType ShowPlanType
|
||||
{
|
||||
get { return ShowPlanType.Estimated; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of Node Id in the recordset
|
||||
/// </summary>
|
||||
protected override int NodeIdIndex
|
||||
{
|
||||
get { return 2; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of Parent Id in the recordset
|
||||
/// </summary>
|
||||
protected override int ParentIndex
|
||||
{
|
||||
get { return 3; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets property names that correspond to values returned
|
||||
/// in each ShowPlan row.
|
||||
/// </summary>
|
||||
/// <returns>Array of property names</returns>
|
||||
protected override string[] GetPropertyNames()
|
||||
{
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private members
|
||||
|
||||
private static string[] propertyNames = new string[]
|
||||
{
|
||||
NodeBuilderConstants.StatementText, // StmtText
|
||||
null, // StmtId
|
||||
NodeBuilderConstants.NodeId,
|
||||
null, // Parent
|
||||
NodeBuilderConstants.PhysicalOp,
|
||||
NodeBuilderConstants.LogicalOp,
|
||||
NodeBuilderConstants.Argument,
|
||||
NodeBuilderConstants.DefinedValues,
|
||||
NodeBuilderConstants.EstimateRows,
|
||||
NodeBuilderConstants.EstimateIO,
|
||||
NodeBuilderConstants.EstimateCPU,
|
||||
NodeBuilderConstants.AvgRowSize,
|
||||
NodeBuilderConstants.TotalSubtreeCost,
|
||||
NodeBuilderConstants.OutputList,
|
||||
NodeBuilderConstants.Warnings,
|
||||
NodeBuilderConstants.StatementType, // Type
|
||||
NodeBuilderConstants.Parallel,
|
||||
NodeBuilderConstants.EstimateExecutions
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
|
||||
public class ExpandableArrayWrapper : ExpandableObjectWrapper
|
||||
{
|
||||
public ExpandableArrayWrapper(ICollection collection) : base()
|
||||
{
|
||||
PopulateProperties(collection);
|
||||
}
|
||||
|
||||
#region Implementation details
|
||||
|
||||
private void PopulateProperties(ICollection collection)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
int index = 0;
|
||||
|
||||
foreach (object item in collection)
|
||||
{
|
||||
if (ObjectWrapperTypeConverter.Default.CanConvertFrom(item.GetType()))
|
||||
{
|
||||
object convertedItem = ObjectWrapperTypeConverter.Default.ConvertFrom(item);
|
||||
if (convertedItem != null)
|
||||
{
|
||||
this[GetPropertyName(++index)] = convertedItem;
|
||||
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
stringBuilder.Append(CultureInfo.CurrentCulture.TextInfo.ListSeparator);
|
||||
stringBuilder.Append(" ");
|
||||
}
|
||||
|
||||
stringBuilder.Append(convertedItem.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.DisplayName = stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public static string GetPropertyName(int index)
|
||||
{
|
||||
return String.Format(CultureInfo.CurrentCulture, "[{0}]", index);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public class ExpandableObjectWrapper : ObjectParser, ICustomTypeDescriptor
|
||||
{
|
||||
public ExpandableObjectWrapper()
|
||||
: this(null, null, String.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public ExpandableObjectWrapper(object item)
|
||||
: this(item, null)
|
||||
{
|
||||
}
|
||||
|
||||
public ExpandableObjectWrapper(object item, string defaultPropertyName)
|
||||
: this(item, defaultPropertyName, GetDefaultDisplayName(item))
|
||||
{
|
||||
}
|
||||
|
||||
public ExpandableObjectWrapper(object item, string defaultPropertyName, string displayName)
|
||||
{
|
||||
this.properties = new PropertyDescriptorCollection(new PropertyDescriptor[]{});
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
ParseProperties(item, this.properties, null);
|
||||
}
|
||||
|
||||
if (defaultPropertyName != null)
|
||||
{
|
||||
defaultProperty = this.properties[defaultPropertyName];
|
||||
}
|
||||
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets node property value.
|
||||
/// </summary>
|
||||
public object this[string propertyName]
|
||||
{
|
||||
get
|
||||
{
|
||||
PropertyValue property = this.properties[propertyName] as PropertyValue;
|
||||
return property != null ? property.Value : null;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
PropertyValue property = this.properties[propertyName] as PropertyValue;
|
||||
if (property != null)
|
||||
{
|
||||
// Overwrite existing property value
|
||||
property.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new property
|
||||
this.properties.Add(new PropertyValue(propertyName, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public string DisplayName
|
||||
{
|
||||
get { return this.displayName; }
|
||||
set { this.displayName = value; }
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public PropertyDescriptorCollection Properties
|
||||
{
|
||||
get { return this.properties; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of item.ToString if it isn't the item class name.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to stringize.</param>
|
||||
/// <returns>Default item display name.</returns>
|
||||
public static string GetDefaultDisplayName(object item)
|
||||
{
|
||||
string itemString = item.ToString();
|
||||
return itemString != item.GetType().ToString() ? itemString : String.Empty;
|
||||
}
|
||||
|
||||
#region ICustomTypeDescriptor
|
||||
|
||||
AttributeCollection ICustomTypeDescriptor.GetAttributes()
|
||||
{
|
||||
return TypeDescriptor.GetAttributes(GetType());
|
||||
}
|
||||
|
||||
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
|
||||
{
|
||||
return TypeDescriptor.GetDefaultEvent(GetType());
|
||||
}
|
||||
|
||||
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
|
||||
{
|
||||
return defaultProperty;
|
||||
}
|
||||
|
||||
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
|
||||
{
|
||||
return TypeDescriptor.GetEditor(GetType(), editorBaseType);
|
||||
}
|
||||
|
||||
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
|
||||
{
|
||||
return TypeDescriptor.GetEvents(GetType());
|
||||
}
|
||||
|
||||
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
|
||||
{
|
||||
return TypeDescriptor.GetEvents(GetType(), attributes);
|
||||
}
|
||||
|
||||
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
|
||||
{
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
|
||||
{
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
string ICustomTypeDescriptor.GetComponentName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeConverter ICustomTypeDescriptor.GetConverter()
|
||||
{
|
||||
return TypeDescriptor.GetConverter(GetType());
|
||||
}
|
||||
|
||||
string ICustomTypeDescriptor.GetClassName()
|
||||
{
|
||||
return GetType().Name;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private PropertyDescriptorCollection properties;
|
||||
private PropertyDescriptor defaultProperty;
|
||||
private string displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses ShowPlan XML objects derived from RelOpBaseType type
|
||||
/// </summary>
|
||||
internal sealed class FilterTypeParser : RelOpBaseTypeParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private FilterTypeParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates node special properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
public override void ParseProperties(object parsedItem, PropertyDescriptorCollection targetPropertyBag, NodeBuilderContext context)
|
||||
{
|
||||
base.ParseProperties(parsedItem, targetPropertyBag, context);
|
||||
|
||||
FilterType item = parsedItem as FilterType;
|
||||
Debug.Assert(item != null, "FilterType object expected");
|
||||
|
||||
if (item.StartupExpression)
|
||||
{
|
||||
// If the filter has Predicate property, it has to be renamed to
|
||||
// Startup Expression Predicate
|
||||
PropertyValue property = targetPropertyBag["Predicate"] as PropertyValue;
|
||||
if (property != null)
|
||||
{
|
||||
property.SetDisplayNameAndDescription(SR.StartupExpressionPredicate, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static FilterTypeParser filterTypeParser = null;
|
||||
public static new FilterTypeParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (filterTypeParser == null)
|
||||
{
|
||||
filterTypeParser = new FilterTypeParser();
|
||||
}
|
||||
return filterTypeParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
internal sealed class FunctionTypeParser : XmlPlanParser
|
||||
{
|
||||
/// <summary>
|
||||
/// This function doesn't do anything. It simply returns the parent node
|
||||
/// passed it.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentItem">Parent item.</param>
|
||||
/// <param name="parentNode">Parent node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
/// <returns>The node that corresponds to the item being parsed.</returns>
|
||||
public override Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
Node currentNode = NewNode(context);
|
||||
|
||||
bool isStoredProcedure = false;
|
||||
|
||||
if (parentItem != null)
|
||||
{
|
||||
PropertyDescriptor storedProcProperty = TypeDescriptor.GetProperties(parentItem)["StoredProc"];
|
||||
|
||||
// If parent item has "StoredProc" property and it references the current item
|
||||
// then this item is a Stored Procedure. Otherwise it is an UDF.
|
||||
if (storedProcProperty != null && storedProcProperty.GetValue(parentItem) == item)
|
||||
{
|
||||
isStoredProcedure = true;
|
||||
}
|
||||
}
|
||||
|
||||
currentNode.Operation = isStoredProcedure ? OperationTable.GetStoredProc() : OperationTable.GetUdf();
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines Operation that corresponds to the object being parsed.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Operation that corresponds to the node.</returns>
|
||||
protected override Operation GetNodeOperation(Node node)
|
||||
{
|
||||
|
||||
// Node operation is defined above based on parent item.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines node subtree cost from existing node properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Node subtree cost.</returns>
|
||||
protected override double GetNodeSubtreeCost(Node node)
|
||||
{
|
||||
// This node doesn't have subtree cost, so it
|
||||
// will be determined based on child nodes.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private FunctionTypeParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static FunctionTypeParser functionTypeParser = null;
|
||||
public static FunctionTypeParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (functionTypeParser == null)
|
||||
{
|
||||
functionTypeParser = new FunctionTypeParser();
|
||||
}
|
||||
return functionTypeParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public class Graph
|
||||
{
|
||||
public Node Root;
|
||||
|
||||
public Description Description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface represents an abstract builder that gets
|
||||
/// data from the data source and represents it as
|
||||
/// an array of AnalysisServices Graph objects.
|
||||
/// </summary>
|
||||
public interface INodeBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds one or more Graphs that
|
||||
/// represnet data from the data source.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data Source.</param>
|
||||
/// <returns>An array of AnalysisServices Graph objects.</returns>
|
||||
ShowPlanGraph[] Execute(object dataSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface represents ability to split an data source containing multiple
|
||||
/// batches / statement into statements and return an XML containing a single statement.
|
||||
/// This is used for XML ShowPlan saving.
|
||||
/// </summary>
|
||||
public interface IXmlBatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds one or more Graphs that
|
||||
/// represnet data from the data source.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data Source.</param>
|
||||
/// <returns>An array of AnalysisServices Graph objects.</returns>
|
||||
string GetSingleStatementXml(object dataSource, int statementIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns statements block type object
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data source</param>
|
||||
/// <param name="statementIndex">Statement index in the data source</param>
|
||||
/// <returns>Statement block type object</returns>
|
||||
StmtBlockType GetSingleStatementObject(object dataSource, int statementIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses ShowPlan XML objects derived from RelOpBaseType type
|
||||
/// </summary>
|
||||
internal sealed class IndexOpTypeParser : RelOpBaseTypeParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private IndexOpTypeParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static IndexOpTypeParser indexOpTypeParser = null;
|
||||
public static new IndexOpTypeParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (indexOpTypeParser == null)
|
||||
{
|
||||
indexOpTypeParser = new IndexOpTypeParser();
|
||||
}
|
||||
return indexOpTypeParser;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the ObjectType that the Index operation references.
|
||||
/// </summary>
|
||||
/// <param name="indexScanType">The current Index operation node being parsed</param>
|
||||
private ObjectType GetObjectTypeFromProperties(object parsedItem)
|
||||
{
|
||||
ObjectType objectType = null;
|
||||
|
||||
// The index operators operate on an object, get that object.
|
||||
PropertyDescriptor objectProperty = TypeDescriptor.GetProperties(parsedItem)["Object"];
|
||||
Debug.Assert(objectProperty != null, "Object expected");
|
||||
if (objectProperty != null)
|
||||
{
|
||||
object objectItem = objectProperty.GetValue(parsedItem);
|
||||
if (objectItem != null)
|
||||
{
|
||||
ObjectType[] objectTypeArray = objectItem as ObjectType[];
|
||||
Debug.Assert(objectTypeArray != null && objectTypeArray.Length == 1, "ObjectTypeArray is null or more than one object found for IndexScan");
|
||||
|
||||
// Only handle the index operations operate on one object
|
||||
if (objectTypeArray != null && objectTypeArray.Length == 1)
|
||||
{
|
||||
objectType = objectTypeArray[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objectType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the indexKind attribute, if it exists, from the ObjectType as a property in the targetPropertyBag.
|
||||
/// </summary>
|
||||
/// <param name="objectType">The objectType for the indexScan.</param>
|
||||
/// <param name="targetPropertyBag">The target the property bag where we will put the PhysicalOperationKind element.</param>
|
||||
private void AddIndexKindAsPhysicalOperatorKind(ObjectType objectType, PropertyDescriptorCollection targetPropertyBag)
|
||||
{
|
||||
if (objectType.IndexKindSpecified)
|
||||
{
|
||||
if (0 < objectType.IndexKind.ToString().Length)
|
||||
{
|
||||
PropertyDescriptor wrapperProperty = PropertyFactory.CreateProperty("PhysicalOperationKind", objectType.IndexKind.ToString());
|
||||
if (wrapperProperty != null)
|
||||
{
|
||||
targetPropertyBag.Add(wrapperProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates node special properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed. The node should be IndexScanType or CreateIndexType</param>
|
||||
public override void ParseProperties(object parsedItem, PropertyDescriptorCollection targetPropertyBag, NodeBuilderContext context)
|
||||
{
|
||||
Debug.Assert((parsedItem is IndexScanType) || (parsedItem is CreateIndexType), "IndexScanType or CreateIndexType object expected");
|
||||
|
||||
// Parse the item as usual with RelOpBaseTypeParser first
|
||||
base.ParseProperties(parsedItem, targetPropertyBag, context);
|
||||
|
||||
// Now look for the object and get the indexKind
|
||||
if ((parsedItem is IndexScanType) || (parsedItem is CreateIndexType))
|
||||
{
|
||||
ObjectType objectType = this.GetObjectTypeFromProperties(parsedItem);
|
||||
if (objectType != null)
|
||||
{
|
||||
this.AddIndexKindAsPhysicalOperatorKind(objectType, targetPropertyBag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses ShowPlan XML objects derived from RelOpBaseType type
|
||||
/// </summary>
|
||||
internal sealed class MergeTypeParser : RelOpBaseTypeParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private MergeTypeParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates node special properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
public override void ParseProperties(object parsedItem, PropertyDescriptorCollection targetPropertyBag, NodeBuilderContext context)
|
||||
{
|
||||
base.ParseProperties(parsedItem, targetPropertyBag, context);
|
||||
|
||||
MergeType item = parsedItem as MergeType;
|
||||
Debug.Assert(item != null, "MergeType object expected");
|
||||
|
||||
// Make a new property which combines "InnerSideJoinColumns" and "OuterSideJoinColumns"
|
||||
object mergeColumnsWrapper = ObjectWrapperTypeConverter.Convert(new MergeColumns(item));
|
||||
PropertyDescriptor wrapperProperty = PropertyFactory.CreateProperty("WhereJoinColumns", mergeColumnsWrapper);
|
||||
if (wrapperProperty != null)
|
||||
{
|
||||
targetPropertyBag.Add(wrapperProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a property should be skipped from the target property bag
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ShouldSkipProperty(PropertyDescriptor property)
|
||||
{
|
||||
if (property.Name == "InnerSideJoinColumns" || property.Name == "OuterSideJoinColumns")
|
||||
{
|
||||
// These two properties are handled in a special way
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.ShouldSkipProperty(property);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static MergeTypeParser mergeTypeParser = null;
|
||||
public static new MergeTypeParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mergeTypeParser == null)
|
||||
{
|
||||
mergeTypeParser = new MergeTypeParser();
|
||||
}
|
||||
return mergeTypeParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This type is used for 2 purposes:
|
||||
/// 1) It creates additional level in the property hierarchy. Instead of including
|
||||
/// InnerSideJoinColumns and OuterSideJoinColumnsField properties in the Node, we
|
||||
/// create additional property which has these two properties as nested properties.
|
||||
/// 2) It allows to convert this to string the same way we convert other custom types
|
||||
/// See static Convert(MergeColumns) method in ObjectWrapperTypeConverter.cs
|
||||
/// </summary>
|
||||
public sealed class MergeColumns
|
||||
{
|
||||
public MergeColumns(MergeType mergeType)
|
||||
{
|
||||
this.innerSideJoinColumnsField = mergeType.InnerSideJoinColumns;
|
||||
this.outerSideJoinColumnsField = mergeType.OuterSideJoinColumns;
|
||||
}
|
||||
|
||||
public ColumnReferenceType[] InnerSideJoinColumns
|
||||
{
|
||||
get { return this.innerSideJoinColumnsField; }
|
||||
}
|
||||
|
||||
public ColumnReferenceType[] OuterSideJoinColumns
|
||||
{
|
||||
get { return this.outerSideJoinColumnsField; }
|
||||
}
|
||||
|
||||
private ColumnReferenceType[] innerSideJoinColumnsField;
|
||||
private ColumnReferenceType[] outerSideJoinColumnsField;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
//
|
||||
// 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Status of operator
|
||||
/// Based on the query profile DMV view
|
||||
/// Pending: when FirstRowTime > 0, Running: FirstRowTime > 0 && CloseTime ==0, Finish: CloseTime > 0
|
||||
/// </summary>
|
||||
public enum STATUS
|
||||
{
|
||||
PENDING,
|
||||
RUNNING,
|
||||
FINISH
|
||||
}
|
||||
|
||||
public class Node
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
public Node(int id, NodeBuilderContext context)
|
||||
{
|
||||
this.ID = id;
|
||||
this.properties = new PropertyDescriptorCollection(new PropertyDescriptor[0]);
|
||||
this.children = new List<Node>();
|
||||
this.childrenEdges = new List<Edge>();
|
||||
this.LogicalOpUnlocName = null;
|
||||
this.PhysicalOpUnlocName = null;
|
||||
this.root = context.Graph.Root;
|
||||
if (this.root == null)
|
||||
{
|
||||
this.root = this;
|
||||
}
|
||||
this.Graph = context.Graph;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public methods and properties
|
||||
|
||||
public int ID
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int GroupIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Node display name
|
||||
/// </summary>
|
||||
public virtual string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Operation == Operation.Unknown)
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
// The display name can consist of two lines
|
||||
// The first line is the Physical name and the physical kind in parenthesis
|
||||
// The second line should contains either Object value or LogicalOp name.
|
||||
// The second line should not show the same content as the first line.
|
||||
|
||||
string firstLine = this["PhysicalOp"] as string;
|
||||
if (firstLine == null)
|
||||
{
|
||||
if (this.Operation == null)
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
firstLine = this.Operation.DisplayName;
|
||||
}
|
||||
|
||||
// Check if the PhysicalOp is specialized to a specific kind
|
||||
string firstLineAppend = this["PhysicalOperationKind"] as string;
|
||||
if (firstLineAppend != null)
|
||||
{
|
||||
firstLine = String.Format(CultureInfo.CurrentCulture, "{0} {1}", firstLine, Constants.Parenthesis(firstLineAppend));
|
||||
}
|
||||
|
||||
|
||||
string secondLine;
|
||||
|
||||
object objectValue = this["Object"];
|
||||
if (objectValue != null)
|
||||
{
|
||||
secondLine = GetObjectNameForDisplay(objectValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
secondLine = this["LogicalOp"] as string;
|
||||
if (secondLine != null)
|
||||
{
|
||||
if (secondLine != firstLine)
|
||||
{
|
||||
// Enclose logical name in parenthesis.
|
||||
secondLine = Constants.Parenthesis(secondLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't show the second line if its value is the same as on the first line.
|
||||
secondLine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return secondLine == null || secondLine.Length == 0
|
||||
? firstLine
|
||||
: String.Format(CultureInfo.CurrentCulture, "{0}\n{1}", firstLine, secondLine);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Node description
|
||||
/// </summary>
|
||||
[DisplayOrder(2), DisplayNameDescription(SR.Keys.OperationDescriptionShort, SR.Keys.OperationDescription)]
|
||||
public string Description
|
||||
{
|
||||
get { return this.Operation.Description; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value that indicates Node parallelism.
|
||||
/// </summary>
|
||||
public bool IsParallel
|
||||
{
|
||||
get
|
||||
{
|
||||
object value = this["Parallel"];
|
||||
return value != null ? (bool)value : false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value that indicates whether the Node has warnings.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool HasWarnings
|
||||
{
|
||||
get
|
||||
{
|
||||
return this["Warnings"] != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value that indicates whether the Node has critical warnings.
|
||||
/// </summary>
|
||||
private bool HasCriticalWarnings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this["Warnings"] != null)
|
||||
{
|
||||
ExpandableObjectWrapper wrapper = this["Warnings"] as ExpandableObjectWrapper;
|
||||
if (wrapper["NoJoinPredicate"] != null)
|
||||
{
|
||||
return (bool)wrapper["NoJoinPredicate"];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this showplan_xml has PDW cost.
|
||||
/// </summary>
|
||||
private bool HasPDWCost
|
||||
{
|
||||
get
|
||||
{
|
||||
return this["PDWAccumulativeCost"] != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cost associated with the Node.
|
||||
/// </summary>
|
||||
[ShowInToolTip, DisplayOrder(8), DisplayNameDescription(SR.Keys.EstimatedOperatorCost, SR.Keys.EstimatedOperatorCostDescription)]
|
||||
public string DisplayCost
|
||||
{
|
||||
get
|
||||
{
|
||||
double cost = this.RelativeCost * 100;
|
||||
if (this.HasPDWCost && cost <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return SR.OperatorDisplayCost(this.Cost, (int)Math.Round(cost));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cost associated with the current Node.
|
||||
/// </summary>
|
||||
public double Cost
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.costCalculated)
|
||||
{
|
||||
this.cost = this.SubtreeCost;
|
||||
foreach (Node childNode in this.Children)
|
||||
{
|
||||
this.cost -= childNode.SubtreeCost;
|
||||
}
|
||||
|
||||
// In some cases cost may become a small negative
|
||||
// number due to rounding. Make it 0 in that case.
|
||||
this.cost = Math.Max(this.cost, 0.0);
|
||||
this.costCalculated = true;
|
||||
}
|
||||
|
||||
return this.cost;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative cost associated with the current Node.
|
||||
/// </summary>
|
||||
public double RelativeCost
|
||||
{
|
||||
get
|
||||
{
|
||||
double overallCost = Root.SubtreeCost;
|
||||
return overallCost > 0 ? Cost / overallCost : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cost associated with the Node subtree.
|
||||
/// </summary>
|
||||
[ShowInToolTip, DisplayOrder(9), DisplayNameDescription(SR.Keys.EstimatedSubtreeCost, SR.Keys.EstimatedSubtreeCostDescription)]
|
||||
public double SubtreeCost
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.subtreeCost == 0)
|
||||
{
|
||||
foreach (Node childNode in this.Children)
|
||||
{
|
||||
this.subtreeCost += childNode.SubtreeCost;
|
||||
}
|
||||
}
|
||||
|
||||
return this.subtreeCost;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.subtreeCost = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Max Children X Position.
|
||||
/// </summary>
|
||||
public int MaxChildrenXPosition;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operation information (localized name, description, image, etc)
|
||||
/// </summary>
|
||||
public Operation Operation
|
||||
{
|
||||
get { return this.operation; }
|
||||
set { this.operation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets node properties.
|
||||
/// </summary>
|
||||
public PropertyDescriptorCollection Properties
|
||||
{
|
||||
get { return this.properties; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets node property value.
|
||||
/// </summary>
|
||||
public object this[string propertyName]
|
||||
{
|
||||
get
|
||||
{
|
||||
PropertyValue property = this.properties[propertyName] as PropertyValue;
|
||||
return property != null ? property.Value : null;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
PropertyValue property = this.properties[propertyName] as PropertyValue;
|
||||
if (property != null)
|
||||
{
|
||||
// Overwrite existing property value
|
||||
property.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new property
|
||||
this.properties.Add(PropertyFactory.CreateProperty(propertyName, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsComputeScalarType()
|
||||
{
|
||||
return this[NodeBuilderConstants.PhysicalOp] != null
|
||||
&& ((string)this[NodeBuilderConstants.PhysicalOp]).StartsWith(SR.Keys.ComputeScalar, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsSeekOrScanType()
|
||||
{
|
||||
return this[NodeBuilderConstants.PhysicalOp] != null && SeekOrScanPhysicalOpList.Contains(this.PhysicalOpUnlocName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of node children.
|
||||
/// </summary>
|
||||
public List<Node> Children
|
||||
{
|
||||
get { return this.children; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of node children.
|
||||
/// </summary>
|
||||
public List<Edge> Edges
|
||||
{
|
||||
get { return this.childrenEdges; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets current node parent.
|
||||
/// </summary>
|
||||
public Node Parent
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public Node Root
|
||||
{
|
||||
get { return this.root; }
|
||||
}
|
||||
|
||||
public Graph Graph
|
||||
{
|
||||
get => this.graph;
|
||||
set
|
||||
{
|
||||
this.graph = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies if this node is finished executing
|
||||
/// </summary>
|
||||
/// <returns>True if finished</returns>
|
||||
public bool IsFinished()
|
||||
{
|
||||
var statusObject = this[NodeBuilderConstants.Status] as STATUS?;
|
||||
|
||||
return statusObject != null && (STATUS)statusObject == STATUS.FINISH;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies if this node is executing
|
||||
/// </summary>
|
||||
/// <returns>True if running</returns>
|
||||
public bool IsRunning()
|
||||
{
|
||||
var statusObject = this[NodeBuilderConstants.Status] as STATUS?;
|
||||
|
||||
return statusObject != null && (STATUS)statusObject == STATUS.RUNNING;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the properties of two nodes are logically similar enough to be considered
|
||||
/// the same for skeleton comparison purposes
|
||||
/// Does not check children
|
||||
/// </summary>
|
||||
/// <param name="nodeToCompare"></param>
|
||||
/// <param name="ignoreDatabaseName"></param>
|
||||
/// <returns></returns>
|
||||
///
|
||||
public bool IsLogicallyEquivalentTo(Node nodeToCompare, bool ignoreDatabaseName)
|
||||
{
|
||||
// same exact node
|
||||
if (this == nodeToCompare)
|
||||
return true;
|
||||
|
||||
// seek and scan types are equivalent so ignore them when comparing logical op
|
||||
if (this[NodeBuilderConstants.LogicalOp] != nodeToCompare[NodeBuilderConstants.LogicalOp] &&
|
||||
(!this.IsSeekOrScanType() || !nodeToCompare.IsSeekOrScanType()))
|
||||
return false;
|
||||
|
||||
// one has object but other does not
|
||||
if (this[objectProperty] != null && nodeToCompare[objectProperty] == null || nodeToCompare[objectProperty] != null && this[objectProperty] == null)
|
||||
return false;
|
||||
|
||||
// both have object
|
||||
if (this[objectProperty] != null && nodeToCompare[objectProperty] != null)
|
||||
{
|
||||
ExpandableObjectWrapper objectProp1 = (ExpandableObjectWrapper)this[objectProperty];
|
||||
ExpandableObjectWrapper objectProp2 = (ExpandableObjectWrapper)nodeToCompare[objectProperty];
|
||||
// object property doesn't match
|
||||
// by default, we ignore DB name
|
||||
// for ex: "[master].[sys].[sysobjvalues].[clst] [e]" and "[master_copy].[sys].[sysobjvalues].[clst] [e]" would be same
|
||||
if (ignoreDatabaseName)
|
||||
{
|
||||
if (!CompareObjectPropertyValue((PropertyValue)(objectProp1.Properties[SR.ObjectServer]), (PropertyValue)(objectProp2.Properties[SR.ObjectServer])))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!CompareObjectPropertyValue((PropertyValue)(objectProp1.Properties[SR.ObjectSchema]), (PropertyValue)(objectProp2.Properties[SR.ObjectSchema])))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!CompareObjectPropertyValue((PropertyValue)(objectProp1.Properties[SR.ObjectTable]), (PropertyValue)(objectProp2.Properties[SR.ObjectTable])))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!CompareObjectPropertyValue((PropertyValue)(objectProp1.Properties[SR.ObjectAlias]), (PropertyValue)(objectProp2.Properties[SR.ObjectAlias])))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for CloneAccessScope if it is specified
|
||||
PropertyValue specified1 = (PropertyValue)(objectProp1.Properties["CloneAccessScopeSpecified"]);
|
||||
PropertyValue specified2 = (PropertyValue)(objectProp2.Properties["CloneAccessScopeSpecified"]);
|
||||
if (specified1 == null && specified2 != null || specified1 != null && specified2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (specified1 != null && specified2 != null)
|
||||
{
|
||||
if ((bool)(specified1.Value) != (bool)(specified2.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((bool)(specified1.Value) == true)
|
||||
{
|
||||
PropertyValue p1 = (PropertyValue)(objectProp1.Properties["CloneAccessScope"]);
|
||||
PropertyValue p2 = (PropertyValue)(objectProp2.Properties["CloneAccessScope"]);
|
||||
if (p1 == null && p2 != null || p1 != null && p2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (p1 != null && p2 != null)
|
||||
{
|
||||
if ((CloneAccessScopeType)(p1.Value) != (CloneAccessScopeType)(p2.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (objectProp1.DisplayName != objectProp2.DisplayName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// same logical op, no other criteria
|
||||
return true;
|
||||
}
|
||||
|
||||
public long? ElapsedTimeInMs
|
||||
{
|
||||
get
|
||||
{
|
||||
long? time = null;
|
||||
var actualStatsWrapper = this["ActualTimeStatistics"] as ExpandableObjectWrapper;
|
||||
if (actualStatsWrapper != null)
|
||||
{
|
||||
var counters = actualStatsWrapper["ActualElapsedms"] as RunTimeCounters;
|
||||
if (counters != null)
|
||||
{
|
||||
var elapsedTime = counters.MaxCounter;
|
||||
long ticks = (long)elapsedTime * TimeSpan.TicksPerMillisecond;
|
||||
time = new DateTime(ticks).Millisecond;
|
||||
}
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ENU name for Logical Operator
|
||||
/// </summary>
|
||||
public string LogicalOpUnlocName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ENU name for Physical Operator
|
||||
/// </summary>
|
||||
public string PhysicalOpUnlocName { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation details
|
||||
|
||||
/// <summary>
|
||||
/// Gets short object name for display.
|
||||
/// Since database and schema is not important and displaying table first is much useful,
|
||||
/// we are displaying object name in [Table].[Index] [Alias] format.
|
||||
/// </summary>
|
||||
/// <param name="objectProperty">Object property in the property bag</param>
|
||||
private string GetObjectNameForDisplay(object objectProperty)
|
||||
{
|
||||
string objectNameForDisplay = string.Empty;
|
||||
|
||||
Debug.Assert(objectProperty != null);
|
||||
if (objectProperty != null)
|
||||
{
|
||||
objectNameForDisplay = objectProperty.ToString();
|
||||
|
||||
ExpandableObjectWrapper objectWrapper = objectProperty as ExpandableObjectWrapper;
|
||||
Debug.Assert(objectWrapper != null);
|
||||
if (objectWrapper != null)
|
||||
{
|
||||
objectNameForDisplay = ObjectWrapperTypeConverter.MergeString(".", objectWrapper["Table"], objectWrapper["Index"]);
|
||||
objectNameForDisplay = ObjectWrapperTypeConverter.MergeString(" ", objectNameForDisplay, objectWrapper["Alias"]);
|
||||
}
|
||||
}
|
||||
|
||||
return objectNameForDisplay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// used to compare multiple string type PropertyValue in Object properties,
|
||||
/// for ex: Server, Database, Schema, Table, Index, etc...
|
||||
/// </summary>
|
||||
/// <returns>True if two PropertyValue are equal</returns>
|
||||
private bool CompareObjectPropertyValue(PropertyValue p1, PropertyValue p2)
|
||||
{
|
||||
if (p1 == null && p2 != null || p1 != null && p2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (p1 != null && p2 != null)
|
||||
{
|
||||
string s1 = p1.Value as string;
|
||||
string s2 = p2.Value as string;
|
||||
if (string.Compare(s1, s2, StringComparison.Ordinal) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets lines of text displayed under the icon.
|
||||
/// </summary>
|
||||
/// <returns>Array of strings.</returns>
|
||||
public string[] GetDisplayLinesOfText()
|
||||
{
|
||||
string newDisplayNameLines = this.DisplayName;
|
||||
|
||||
// cost
|
||||
double cost = this.RelativeCost * 100;
|
||||
|
||||
if (!this.HasPDWCost || cost > 0)
|
||||
{
|
||||
string costText = SR.CostFormat((int)Math.Round(cost));
|
||||
newDisplayNameLines += '\n' + costText;
|
||||
}
|
||||
|
||||
|
||||
// elapsed time in miliseconds
|
||||
string elapsedTime = GetElapsedTimeDisplayString();
|
||||
if (!String.IsNullOrEmpty(elapsedTime))
|
||||
{
|
||||
newDisplayNameLines += '\n' + elapsedTime;
|
||||
}
|
||||
|
||||
// actual/estimated rows
|
||||
string rowStatistics = GetRowStatisticsDisplayString();
|
||||
if (!String.IsNullOrEmpty(rowStatistics))
|
||||
{
|
||||
newDisplayNameLines += '\n' + rowStatistics;
|
||||
}
|
||||
|
||||
return newDisplayNameLines.Split('\n');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide a string for the actual elapsed time if it is available
|
||||
/// </summary>
|
||||
/// <returns>formatted string of execution time</returns>
|
||||
public string GetElapsedTimeDisplayString()
|
||||
{
|
||||
string formattedTime = null;
|
||||
|
||||
var actualStatsWrapper = this["ActualTimeStatistics"] as ExpandableObjectWrapper;
|
||||
if (actualStatsWrapper != null)
|
||||
{
|
||||
var counters = actualStatsWrapper["ActualElapsedms"] as RunTimeCounters;
|
||||
if (counters != null)
|
||||
{
|
||||
var elapsedTime = counters.MaxCounter;
|
||||
long ticks = (long)elapsedTime * TimeSpan.TicksPerMillisecond;
|
||||
var time = new DateTime(ticks);
|
||||
if (ticks < 1000L * TimeSpan.TicksPerMillisecond * 60) // 60 seconds
|
||||
{
|
||||
formattedTime = time.ToString("s.fff") + "s";
|
||||
}
|
||||
else
|
||||
{
|
||||
// calculate the hours
|
||||
long hours = ticks / (1000L * TimeSpan.TicksPerMillisecond * 60 * 60); //1 hour
|
||||
formattedTime = hours.ToString() + time.ToString(":mm:ss");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide a string for the actual rows vs estimated rows if they are both available in the actual execution plan
|
||||
/// </summary>
|
||||
/// <returns>formatted string of actual rows vs estimated rows; or null if estimateRows or actualRows is null</returns>
|
||||
private string GetRowStatisticsDisplayString()
|
||||
{
|
||||
var actualRowsCounters = this[NodeBuilderConstants.ActualRows] as RunTimeCounters;
|
||||
ulong? actualRows = actualRowsCounters != null ? actualRowsCounters.TotalCounters : (ulong?)null;
|
||||
var estimateRows = this[NodeBuilderConstants.EstimateRows] as double?;
|
||||
var estimateExecutions = this[NodeBuilderConstants.EstimateExecutions] as double?;
|
||||
|
||||
if (estimateRows != null)
|
||||
{
|
||||
if (estimateExecutions != null)
|
||||
{
|
||||
estimateRows = estimateRows * estimateExecutions;
|
||||
}
|
||||
// we display estimate rows as integer so need round function
|
||||
estimateRows = Math.Round(estimateRows.Value);
|
||||
}
|
||||
|
||||
return GetRowStatisticsDisplayString(actualRows, estimateRows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inner function to provide a string for the actual rows vs estimated rows if they are both available in the actual execution plan
|
||||
/// </summary>
|
||||
/// <param name="actualRows">actual rows</param>
|
||||
/// <param name="estimateRows">estimated rows</param>
|
||||
/// <returns>formatted string of actual rows vs estimated rows; or null if any of the arguments is null</returns>
|
||||
private string GetRowStatisticsDisplayString(ulong? actualRows, double? estimateRows)
|
||||
{
|
||||
if (!actualRows.HasValue || !estimateRows.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// estimateRows should always to be positive, I just change it to 1 just in case since we need to calculate the percentage
|
||||
estimateRows = estimateRows > 0 ? estimateRows : 1;
|
||||
|
||||
// get the difference in percentage
|
||||
var actualString = actualRows.Value.ToString();
|
||||
var estimateString = estimateRows.Value.ToString();
|
||||
int percent = 100;
|
||||
if (estimateRows > 0)
|
||||
{
|
||||
percent = (int)(100 * ((double)actualRows / estimateRows));
|
||||
}
|
||||
|
||||
actualString = actualString.PadLeft(estimateString.Length);
|
||||
estimateString = estimateString.PadLeft(actualString.Length);
|
||||
|
||||
return SR.ActualOfEstimated(actualString, estimateString, percent);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
private double cost;
|
||||
private bool costCalculated;
|
||||
private double subtreeCost;
|
||||
private Operation operation;
|
||||
private PropertyDescriptorCollection properties;
|
||||
private List<Node> children;
|
||||
private readonly string objectProperty = NodeBuilderConstants.Object;
|
||||
private readonly string predicateProperty = NodeBuilderConstants.LogicalOp;
|
||||
private Node parent;
|
||||
private Graph graph;
|
||||
private Edge parentEdge;
|
||||
private List<Edge> childrenEdges;
|
||||
private string nodeType;
|
||||
|
||||
private Node root;
|
||||
|
||||
/// <summary>
|
||||
/// List of Seek or Scan type operators that can be considered match
|
||||
/// </summary>
|
||||
private List<string> SeekOrScanPhysicalOpList = new List<string> { "IndexSeek", "TableScan", "IndexScan", "ColumnstoreIndexScan" };
|
||||
|
||||
#endregion
|
||||
|
||||
public void AddChild(Node child)
|
||||
{
|
||||
Edge edge = new Edge(this, child);
|
||||
this.childrenEdges.Add(edge);
|
||||
child.parentEdge = edge;
|
||||
this.children.Add(child);
|
||||
child.parent = this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public class NodeBuilderContext
|
||||
{
|
||||
public NodeBuilderContext(ShowPlanGraph graph, ShowPlanType type, object context)
|
||||
{
|
||||
this.graph = graph;
|
||||
this.showPlanType = type;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets currently processing Graph
|
||||
/// </summary>
|
||||
public ShowPlanGraph Graph
|
||||
{
|
||||
get { return this.graph; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets current ShowPlan type.
|
||||
/// </summary>
|
||||
public ShowPlanType ShowPlanType
|
||||
{
|
||||
get { return this.showPlanType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Misc context object.
|
||||
/// </summary>
|
||||
public object Context
|
||||
{
|
||||
get { return this.context; }
|
||||
}
|
||||
|
||||
private ShowPlanGraph graph;
|
||||
private ShowPlanType showPlanType;
|
||||
private object context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// 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.Data;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that creates concrete INodeBuilder instances.
|
||||
/// </summary>
|
||||
public static class NodeBuilderFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a concrete node builder based on dataSource type
|
||||
/// </summary>
|
||||
/// <param name="dataSource">data</param>
|
||||
/// <returns></returns>
|
||||
public static INodeBuilder Create(object dataSource, ShowPlanType type)
|
||||
{
|
||||
if (dataSource is String || dataSource is byte[] || dataSource is ShowPlanXML)
|
||||
{
|
||||
// REVIEW: add the code that looks inside the XML
|
||||
// and validates the root node and namespace
|
||||
// REVIEW: consider using XmlTextReader
|
||||
return new XmlPlanNodeBuilder(type);
|
||||
}
|
||||
else if (dataSource is IDataReader)
|
||||
{
|
||||
// REVIEW: for now the assumption is that this is
|
||||
// a Shiloh Row set, either actual or estimated
|
||||
if (type == ShowPlanType.Actual)
|
||||
{
|
||||
return new ActualPlanDataReaderNodeBuilder();
|
||||
}
|
||||
else if (type == ShowPlanType.Estimated)
|
||||
{
|
||||
return new EstimatedPlanDataReaderNodeBuilder();
|
||||
}
|
||||
// else if (type == ShowPlanType.Live)
|
||||
// {
|
||||
// return new LivePlanDataReaderNodeBuilder();
|
||||
// }
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Unexpected ShowPlan type");
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(false, "Unexpected ShowPlan source = " + dataSource.ToString());
|
||||
throw new ArgumentException(SR.Keys.UnknownShowPlanSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// 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.ComponentModel;
|
||||
using System.Collections;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all object / Node parsers
|
||||
/// Used for parsing properties and hierarchy.
|
||||
/// </summary>
|
||||
public abstract class ObjectParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses item properties.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">Item which properties are being parsed.</param>
|
||||
/// <param name="targetPropertyBag">Target property bag to populate with property wrappers.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
public virtual void ParseProperties(object parsedItem, PropertyDescriptorCollection targetPropertyBag, NodeBuilderContext context)
|
||||
{
|
||||
PropertyDescriptorCollection allProperties = TypeDescriptor.GetProperties(parsedItem);
|
||||
|
||||
|
||||
|
||||
foreach (PropertyDescriptor property in allProperties)
|
||||
{
|
||||
if (property.Attributes.Contains(XmlIgnoreAttribute))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// a special "...Specified" property (such as StatementIdSpecified)
|
||||
PropertyDescriptor specifiedProperty = allProperties[property.Name + "Specified"];
|
||||
if (specifiedProperty != null && specifiedProperty.GetValue(parsedItem).Equals(false))
|
||||
{
|
||||
// The "...Specified" property value is false.
|
||||
// We should skip this property
|
||||
continue;
|
||||
}
|
||||
|
||||
object value = property.GetValue(parsedItem);
|
||||
if (value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Type.GetTypeCode(property.PropertyType) == TypeCode.Object && ShouldSkipProperty(property))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// In case of xml Choice group, the property name can be general like "Item" or "Items".
|
||||
// Ideally, it should contain/refer to only one of the possible values in xml choice group,
|
||||
// but due to limitations on engine side the xml choice group can contain more than one
|
||||
// value and it is not possible to change the choice group to a sequence group in XSD
|
||||
// because engine is not able to generate values in a particular order for the case of
|
||||
// warnings type. Hence, we need to iterate through all the values in items and create
|
||||
// separate property for each value and add it to target property bag.
|
||||
if (property.Name == "Items" || property.Name == "Item")
|
||||
{
|
||||
ICollection collection = value as ICollection;
|
||||
if (collection != null)
|
||||
{
|
||||
foreach (object obj in collection)
|
||||
{
|
||||
ObjectParser.AddProperty(targetPropertyBag, property, obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//We can get single object in choice like SeekPredicateNew in Spool as Item
|
||||
ObjectParser.AddProperty(targetPropertyBag, property, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectParser.AddProperty(targetPropertyBag, property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddProperty(PropertyDescriptorCollection targetPropertyBag, PropertyDescriptor property, object value)
|
||||
{
|
||||
PropertyDescriptor wrapperProperty = PropertyFactory.CreateProperty(property, value);
|
||||
if (wrapperProperty != null)
|
||||
{
|
||||
targetPropertyBag.Add(wrapperProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the current property is used to reference a child item.
|
||||
/// Hierarchy properties are skipped when property wrappers are being created.
|
||||
/// </summary>
|
||||
/// <param name="property">Property subject to test.</param>
|
||||
/// <returns>True if the property is a hierarchy property;
|
||||
/// false if this is a regular property that should appear in the property grid.
|
||||
/// </returns>
|
||||
protected virtual bool ShouldSkipProperty(PropertyDescriptor property)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly Attribute XmlIgnoreAttribute = new System.Xml.Serialization.XmlIgnoreAttribute();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// 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.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
public sealed class Operation
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructs Operation.
|
||||
/// </summary>
|
||||
/// <param name="name">Operator name</param>
|
||||
/// <param name="displayNameKey">Display name resource ID</param>
|
||||
/// <param name="descriptionKey">Description resource ID</param>
|
||||
/// <param name="imageName">Image name</param>
|
||||
public Operation(string name, string displayNameKey, string descriptionKey, string imageName)
|
||||
{
|
||||
this.name = name;
|
||||
this.displayNameKey = displayNameKey;
|
||||
this.descriptionKey = descriptionKey;
|
||||
this.imageName = imageName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="name">Operator name</param>
|
||||
/// <param name="displayNameKey">Display name resource ID</param>
|
||||
/// <returns></returns>
|
||||
public Operation(string name, string displayNameKey): this(name, displayNameKey, null, null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets operator name.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets localized display name.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.displayName == null && this.displayNameKey != null)
|
||||
{
|
||||
this.displayName = SR.Keys.GetString(this.displayNameKey);
|
||||
Debug.Assert(this.displayName != null);
|
||||
}
|
||||
|
||||
return this.displayName != null ? this.displayName : this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets localized description.
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.description == null && this.descriptionKey != null)
|
||||
{
|
||||
this.description = SR.Keys.GetString(this.descriptionKey);
|
||||
Debug.Assert(this.description != null);
|
||||
}
|
||||
|
||||
return this.description;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets image.
|
||||
/// </summary>
|
||||
public string Image
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.image == null && this.imageName != null)
|
||||
{
|
||||
this.image = this.imageName;
|
||||
}
|
||||
return this.image;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates one-off operation with only display name.
|
||||
/// </summary>
|
||||
/// <param name="operationDisplayName">Operation display name.</param>
|
||||
public static Operation CreateUnknown(string operationDisplayName, string iconName)
|
||||
{
|
||||
return new Operation(operationDisplayName, null, null, iconName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unknown operation
|
||||
/// </summary>
|
||||
public static Operation Unknown
|
||||
{
|
||||
get { return Operation.unknown; }
|
||||
}
|
||||
|
||||
private string name;
|
||||
private string displayNameKey;
|
||||
private string descriptionKey;
|
||||
private string imageName;
|
||||
private string helpKeyword;
|
||||
private Type displayNodeType;
|
||||
|
||||
private string image;
|
||||
private string displayName;
|
||||
private string description;
|
||||
|
||||
private static readonly Operation unknown = new Operation(
|
||||
String.Empty,
|
||||
SR.Keys.Unknown,
|
||||
SR.Keys.UnknownDescription,
|
||||
"Result_32x.ico");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
//
|
||||
// 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.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that holds information about a physical or logical operator, or a statement.
|
||||
/// </summary>
|
||||
internal static class OperationTable
|
||||
{
|
||||
#region Public members
|
||||
|
||||
public static Operation GetStatement(string statementTypeName)
|
||||
{
|
||||
Operation operation;
|
||||
|
||||
if (!Statements.TryGetValue(statementTypeName, out operation))
|
||||
{
|
||||
operation = Operation.CreateUnknown(statementTypeName, "languageConstructCatchAll");
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
public static Operation GetCursorType(string cursorTypeName)
|
||||
{
|
||||
Operation operation;
|
||||
|
||||
if (!CursorTypes.TryGetValue(cursorTypeName, out operation))
|
||||
{
|
||||
cursorTypeName = GetNameFromXmlEnumAttribute(cursorTypeName, typeof(CursorType));
|
||||
operation = Operation.CreateUnknown(cursorTypeName, "cursorCatchAll");
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
public static Operation GetPhysicalOperation(string operationType)
|
||||
{
|
||||
Operation operation;
|
||||
|
||||
if (!PhysicalOperations.TryGetValue(operationType, out operation))
|
||||
{
|
||||
operationType = GetNameFromXmlEnumAttribute(operationType, typeof(PhysicalOpType));
|
||||
operation = Operation.CreateUnknown(operationType, "iteratorCatchAll");
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
public static Operation GetLogicalOperation(string operationType)
|
||||
{
|
||||
Operation operation;
|
||||
|
||||
if (!LogicalOperations.TryGetValue(operationType, out operation))
|
||||
{
|
||||
operationType = GetNameFromXmlEnumAttribute(operationType, typeof(LogicalOpType));
|
||||
// Should not use Operation.CreateUnknown here, because it would
|
||||
// use some default description and icons. Instead we should fall back to description
|
||||
// and Icon from the corresponding physical operation.
|
||||
operation = new Operation(null, operationType);
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
public static Operation GetUdf()
|
||||
{
|
||||
return new Operation(null, SR.Keys.Udf, null, "languageConstructCatchAll");
|
||||
}
|
||||
|
||||
public static Operation GetStoredProc()
|
||||
{
|
||||
return new Operation(null, SR.Keys.StoredProc, null, "languageConstructCatchAll");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation details
|
||||
|
||||
static OperationTable()
|
||||
{
|
||||
Operation[] physicalOperationList = new Operation[]
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// XML ShowPlan Operators (see showplanxml.cs for the list)
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new Operation("AdaptiveJoin", SR.Keys.AdaptiveJoin, SR.Keys.AdaptiveJoinDescription, "adaptiveJoin"),
|
||||
new Operation("Assert", SR.Keys.Assert, SR.Keys.AssertDescription, "assert"),
|
||||
new Operation("Bitmap", SR.Keys.Bitmap, SR.Keys.BitmapDescription, "bitmap"),
|
||||
new Operation("ClusteredIndexDelete", SR.Keys.ClusteredIndexDelete, SR.Keys.ClusteredIndexDeleteDescription, "clusteredIndexDelete"),
|
||||
new Operation("ClusteredIndexInsert", SR.Keys.ClusteredIndexInsert, SR.Keys.ClusteredIndexInsertDescription, "clusteredIndexInsert"),
|
||||
new Operation("ClusteredIndexScan", SR.Keys.ClusteredIndexScan, SR.Keys.ClusteredIndexScanDescription, "clusteredIndexScan"),
|
||||
new Operation("ClusteredIndexSeek", SR.Keys.ClusteredIndexSeek, SR.Keys.ClusteredIndexSeekDescription, "clusteredIndexSeek"),
|
||||
new Operation("ClusteredIndexUpdate", SR.Keys.ClusteredIndexUpdate, SR.Keys.ClusteredIndexUpdateDescription, "clusteredIndexUpdate"),
|
||||
new Operation("ClusteredIndexMerge", SR.Keys.ClusteredIndexMerge, SR.Keys.ClusteredIndexMergeDescription, "clusteredIndexMerge"),
|
||||
new Operation("ClusteredUpdate", SR.Keys.ClusteredUpdate, SR.Keys.ClusteredUpdateDescription, "clusteredUpdate"),
|
||||
new Operation("Collapse", SR.Keys.Collapse, SR.Keys.CollapseDescription, "collapse"),
|
||||
new Operation("ComputeScalar", SR.Keys.ComputeScalar, SR.Keys.ComputeScalarDescription, "computeScalar"),
|
||||
new Operation("Concatenation", SR.Keys.Concatenation, SR.Keys.ConcatenationDescription, "concatenation"),
|
||||
new Operation("ConstantScan", SR.Keys.ConstantScan, SR.Keys.ConstantScanDescription, "constantScan"),
|
||||
new Operation("DeletedScan", SR.Keys.DeletedScan, SR.Keys.DeletedScanDescription, "deletedScan"),
|
||||
new Operation("Filter", SR.Keys.Filter, SR.Keys.FilterDescription, "filter"),
|
||||
new Operation("HashMatch", SR.Keys.HashMatch, SR.Keys.HashMatchDescription, "hashMatch"),
|
||||
new Operation("IndexDelete", SR.Keys.IndexDelete, SR.Keys.IndexDeleteDescription, "indexDelete"),
|
||||
new Operation("IndexInsert", SR.Keys.IndexInsert, SR.Keys.IndexInsertDescription, "indexInsert"),
|
||||
new Operation("IndexScan", SR.Keys.IndexScan, SR.Keys.IndexScanDescription, "indexScan"),
|
||||
new Operation("ColumnstoreIndexDelete", SR.Keys.ColumnstoreIndexDelete, SR.Keys.ColumnstoreIndexDeleteDescription, "columnstoreIndexDelete"),
|
||||
new Operation("ColumnstoreIndexInsert", SR.Keys.ColumnstoreIndexInsert, SR.Keys.ColumnstoreIndexInsertDescription, "columnstoreIndexInsert"),
|
||||
new Operation("ColumnstoreIndexMerge", SR.Keys.ColumnstoreIndexMerge, SR.Keys.ColumnstoreIndexMergeDescription, "columnstoreIndexMerge"),
|
||||
new Operation("ColumnstoreIndexScan", SR.Keys.ColumnstoreIndexScan, SR.Keys.ColumnstoreIndexScanDescription, "columnstoreIndexScan"),
|
||||
new Operation("ColumnstoreIndexUpdate", SR.Keys.ColumnstoreIndexUpdate, SR.Keys.ColumnstoreIndexUpdateDescription, "columnstoreIndexUpdate"),
|
||||
new Operation("IndexSeek", SR.Keys.IndexSeek, SR.Keys.IndexSeekDescription, "indexSeek"),
|
||||
new Operation("IndexSpool", SR.Keys.IndexSpool, SR.Keys.IndexSpoolDescription, "indexSpool"),
|
||||
new Operation("IndexUpdate", SR.Keys.IndexUpdate, SR.Keys.IndexUpdateDescription, "indexUpdate"),
|
||||
new Operation("InsertedScan", SR.Keys.InsertedScan, SR.Keys.InsertedScanDescription, "insertedScan"),
|
||||
new Operation("LogRowScan", SR.Keys.LogRowScan, SR.Keys.LogRowScanDescription, "logRowScan"),
|
||||
new Operation("MergeInterval", SR.Keys.MergeInterval, SR.Keys.MergeIntervalDescription, "mergeInterval"),
|
||||
new Operation("MergeJoin", SR.Keys.MergeJoin, SR.Keys.MergeJoinDescription, "mergeJoin"),
|
||||
new Operation("NestedLoops", SR.Keys.NestedLoops, SR.Keys.NestedLoopsDescription, "nestedLoops"),
|
||||
new Operation("Parallelism", SR.Keys.Parallelism, SR.Keys.ParallelismDescription, "parallelism"),
|
||||
new Operation("ParameterTableScan", SR.Keys.ParameterTableScan, SR.Keys.ParameterTableScanDescription, "parameterTableScan"),
|
||||
new Operation("Print", SR.Keys.Print, SR.Keys.PrintDescription, "print"),
|
||||
new Operation("Put", SR.Keys.Put, SR.Keys.PutDescription, "Put_32x.ico"),
|
||||
new Operation("Rank", SR.Keys.Rank, SR.Keys.RankDescription, "rank"),
|
||||
// using the temporary icon as of now. Once the new icon is available, it will be updated.
|
||||
new Operation("ForeignKeyReferencesCheck", SR.Keys.ForeignKeyReferencesCheck, SR.Keys.ForeignKeyReferencesCheckDescription, "foreignKeyReferencesCheck"),
|
||||
new Operation("RemoteDelete", SR.Keys.RemoteDelete, SR.Keys.RemoteDeleteDescription, "remoteDelete"),
|
||||
new Operation("RemoteIndexScan", SR.Keys.RemoteIndexScan, SR.Keys.RemoteIndexScanDescription, "remoteIndexScan"),
|
||||
new Operation("RemoteIndexSeek", SR.Keys.RemoteIndexSeek, SR.Keys.RemoteIndexSeekDescription, "remoteIndexSeek"),
|
||||
new Operation("RemoteInsert", SR.Keys.RemoteInsert, SR.Keys.RemoteInsertDescription, "remoteInsert"),
|
||||
new Operation("RemoteQuery", SR.Keys.RemoteQuery, SR.Keys.RemoteQueryDescription, "remoteQuery"),
|
||||
new Operation("RemoteScan", SR.Keys.RemoteScan, SR.Keys.RemoteScanDescription, "remoteScan"),
|
||||
new Operation("RemoteUpdate", SR.Keys.RemoteUpdate, SR.Keys.RemoteUpdateDescription, "remoteUpdate"),
|
||||
new Operation("RIDLookup", SR.Keys.RIDLookup, SR.Keys.RIDLookupDescription, "ridLookup"),
|
||||
new Operation("RowCountSpool", SR.Keys.RowCountSpool, SR.Keys.RowCountSpoolDescription, "rowCountSpool"),
|
||||
new Operation("Segment", SR.Keys.Segment, SR.Keys.SegmentDescription, "segment"),
|
||||
new Operation("Sequence", SR.Keys.Sequence, SR.Keys.SequenceDescription, "sequence"),
|
||||
new Operation("SequenceProject", SR.Keys.SequenceProject, SR.Keys.SequenceProjectDescription, "sequenceProject"),
|
||||
new Operation("Sort", SR.Keys.Sort, SR.Keys.SortDescription, "sort"),
|
||||
new Operation("Split", SR.Keys.Split, SR.Keys.SplitDescription, "split"),
|
||||
new Operation("StreamAggregate", SR.Keys.StreamAggregate, SR.Keys.StreamAggregateDescription, "streamAggregate"),
|
||||
new Operation("Switch", SR.Keys.Switch, SR.Keys.SwitchDescription, "switchStatement"),
|
||||
new Operation("Tablevaluedfunction", SR.Keys.TableValueFunction, SR.Keys.TableValueFunctionDescription, "tableValuedFunction"),
|
||||
new Operation("TableDelete", SR.Keys.TableDelete, SR.Keys.TableDeleteDescription, "tableDelete"),
|
||||
new Operation("TableInsert", SR.Keys.TableInsert, SR.Keys.TableInsertDescription, "tableInsert"),
|
||||
new Operation("TableScan", SR.Keys.TableScan, SR.Keys.TableScanDescription, "tableScan"),
|
||||
new Operation("TableSpool", SR.Keys.TableSpool, SR.Keys.TableSpoolDescription, "tableSpool"),
|
||||
new Operation("TableUpdate", SR.Keys.TableUpdate, SR.Keys.TableUpdateDescription, "tableUpdate"),
|
||||
new Operation("TableMerge", SR.Keys.TableMerge, SR.Keys.TableMergeDescription, "tableMerge"),
|
||||
new Operation("TFP", SR.Keys.TFP, SR.Keys.TFPDescription, "tfp"),
|
||||
new Operation("Top", SR.Keys.Top, SR.Keys.TopDescription, "top"),
|
||||
new Operation("UDX", SR.Keys.UDX, SR.Keys.UDXDescription, "udx"),
|
||||
new Operation("BatchHashTableBuild", SR.Keys.BatchHashTableBuild, SR.Keys.BatchHashTableBuildDescription, "batchHashTableBuild"),
|
||||
new Operation("WindowSpool", SR.Keys.Window, SR.Keys.WindowDescription, "windowSpool"),
|
||||
new Operation("WindowAggregate", SR.Keys.WindowAggregate, SR.Keys.WindowAggregateDescription, "windowAggregate"),
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// XML ShowPlan Cursor Operators (see showplanxml.cs for the list)
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
new Operation("FetchQuery", SR.Keys.FetchQuery, SR.Keys.FetchQueryDescription, "fetchQuery"),
|
||||
new Operation("PopulateQuery", SR.Keys.PopulationQuery, SR.Keys.PopulationQueryDescription, "populateQuery"),
|
||||
new Operation("RefreshQuery", SR.Keys.RefreshQuery, SR.Keys.RefreshQueryDescription, "refreshQuery"),
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Shiloh Operators (see star\sqlquery\src\plan.cpp for the list)
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new Operation("Result", SR.Keys.Result, SR.Keys.ResultDescription, "result"),
|
||||
new Operation("Aggregate", SR.Keys.Aggregate, SR.Keys.AggregateDescription, "aggregate"),
|
||||
new Operation("Assign", SR.Keys.Assign, SR.Keys.AssignDescription, "assign"),
|
||||
new Operation("ArithmeticExpression", SR.Keys.ArithmeticExpression, SR.Keys.ArithmeticExpressionDescription, "arithmeticExpression"),
|
||||
new Operation("BookmarkLookup", SR.Keys.BookmarkLookup, SR.Keys.BookmarkLookupDescription, "bookmarkLookup"),
|
||||
new Operation("Convert", SR.Keys.Convert, SR.Keys.ConvertDescription, "convert"),
|
||||
new Operation("Declare", SR.Keys.Declare, SR.Keys.DeclareDescription, "declare"),
|
||||
new Operation("Delete", SR.Keys.Delete, SR.Keys.DeleteDescription, "deleteOperator"),
|
||||
new Operation("Dynamic", SR.Keys.Dynamic, SR.Keys.DynamicDescription, "dynamic"),
|
||||
new Operation("HashMatchRoot", SR.Keys.HashMatchRoot, SR.Keys.HashMatchRootDescription, "hashMatchRoot"),
|
||||
new Operation("HashMatchTeam", SR.Keys.HashMatchTeam, SR.Keys.HashMatchTeamDescription, "hashMatchTeam"),
|
||||
new Operation("If", SR.Keys.If, SR.Keys.IfDescription, "ifOperator"),
|
||||
new Operation("Insert", SR.Keys.Insert, SR.Keys.InsertDescription, "insert"),
|
||||
new Operation("Intrinsic", SR.Keys.Intrinsic, SR.Keys.IntrinsicDescription, "intrinsic"),
|
||||
new Operation("Keyset", SR.Keys.Keyset, SR.Keys.KeysetDescription, "keyset"),
|
||||
new Operation("Locate", SR.Keys.Locate, SR.Keys.LocateDescription, "locate"),
|
||||
new Operation("PopulationQuery", SR.Keys.PopulationQuery, SR.Keys.PopulationQueryDescription, "populationQuery"),
|
||||
new Operation("SetFunction", SR.Keys.SetFunction, SR.Keys.SetFunctionDescription, "setFunction"),
|
||||
new Operation("Snapshot", SR.Keys.Snapshot, SR.Keys.SnapshotDescription, "snapshot"),
|
||||
new Operation("Spool", SR.Keys.Spool, SR.Keys.SpoolDescription, "spool"),
|
||||
new Operation("TSQL", SR.Keys.SQL, SR.Keys.SQLDescription, "tsql"),
|
||||
new Operation("Update", SR.Keys.Update, SR.Keys.UpdateDescription, "update"),
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Fake Operators - Used to special case existing operators and expose them using different name / icons (see sqlbu#434739)
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new Operation("KeyLookup", SR.Keys.KeyLookup, SR.Keys.KeyLookupDescription, "keyLookup"),
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// PDW Operators (See PDW comment tags in showplanxml.xsd)
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new Operation("Apply", SR.Keys.Apply, SR.Keys.ApplyDescription, "apply"),
|
||||
new Operation("Broadcast", SR.Keys.Broadcast, SR.Keys.BroadcastDescription, "broadcast"),
|
||||
new Operation("ComputeToControlNode", SR.Keys.ComputeToControlNode, SR.Keys.ComputeToControlNodeDescription, "computeToControlNode"),
|
||||
new Operation("ConstTableGet", SR.Keys.ConstTableGet, SR.Keys.ConstTableGetDescription, "constTableGet"),
|
||||
new Operation("ControlToComputeNodes", SR.Keys.ControlToComputeNodes, SR.Keys.ControlToComputeNodesDescription, "controlToComputeNodes"),
|
||||
new Operation("ExternalBroadcast", SR.Keys.ExternalBroadcast, SR.Keys.ExternalBroadcastDescription, "externalBroadcast"),
|
||||
new Operation("ExternalExport", SR.Keys.ExternalExport, SR.Keys.ExternalExportDescription, "externalExport"),
|
||||
new Operation("ExternalLocalStreaming", SR.Keys.ExternalLocalStreaming, SR.Keys.ExternalLocalStreamingDescription, "externalLocalStreaming"),
|
||||
new Operation("ExternalRoundRobin", SR.Keys.ExternalRoundRobin, SR.Keys.ExternalRoundRobinDescription, "externalRoundRobin"),
|
||||
new Operation("ExternalShuffle", SR.Keys.ExternalShuffle, SR.Keys.ExternalShuffleDescription, "externalShuffle"),
|
||||
new Operation("Get", SR.Keys.Get, SR.Keys.GetDescription, "get"),
|
||||
new Operation("GbApply", SR.Keys.GbApply, SR.Keys.GbApplyDescription, "groupByApply"),
|
||||
new Operation("GbAgg", SR.Keys.GbAgg, SR.Keys.GbAggDescription, "groupByAggregate"),
|
||||
new Operation("Join", SR.Keys.Join, SR.Keys.JoinDescription, "join"),
|
||||
new Operation("LocalCube", SR.Keys.LocalCube, SR.Keys.LocalCubeDescription, "localCube"),
|
||||
new Operation("Project", SR.Keys.Project, SR.Keys.ProjectDescription, "project"),
|
||||
new Operation("Shuffle", SR.Keys.Shuffle, SR.Keys.ShuffleDescription, "shuffle"),
|
||||
new Operation("SingleSourceRoundRobin", SR.Keys.SingleSourceRoundRobin, SR.Keys.SingleSourceRoundRobinDescription, "singleSourceRoundRobin"),
|
||||
new Operation("SingleSourceShuffle", SR.Keys.SingleSourceShuffle, SR.Keys.SingleSourceShuffleDescription, "singleSourceShuffle"),
|
||||
new Operation("Trim", SR.Keys.Trim, SR.Keys.TrimDescription, "trim"),
|
||||
new Operation("Union", SR.Keys.Union, SR.Keys.UnionDescription, "union"),
|
||||
new Operation("UnionAll", SR.Keys.UnionAll, SR.Keys.UnionAllDescription, "unionAll"),
|
||||
};
|
||||
|
||||
PhysicalOperations = DictionaryFromList(physicalOperationList);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Logical Operations
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Operation[] logicalOperationList = new Operation[]
|
||||
{
|
||||
new Operation("Aggregate", SR.Keys.LogicalOpAggregate),
|
||||
new Operation("AntiDiff", SR.Keys.LogicalOpAntiDiff),
|
||||
new Operation("Assert", SR.Keys.LogicalOpAssert),
|
||||
new Operation("BitmapCreate", SR.Keys.LogicalOpBitmapCreate),
|
||||
new Operation("ClusteredIndexScan", SR.Keys.LogicalOpClusteredIndexScan),
|
||||
new Operation("ClusteredIndexSeek", SR.Keys.LogicalOpClusteredIndexSeek),
|
||||
new Operation("ClusteredUpdate", SR.Keys.LogicalOpClusteredUpdate),
|
||||
new Operation("Collapse", SR.Keys.LogicalOpCollapse),
|
||||
new Operation("ComputeScalar", SR.Keys.LogicalOpComputeScalar),
|
||||
new Operation("Concatenation", SR.Keys.LogicalOpConcatenation),
|
||||
new Operation("ConstantScan", SR.Keys.LogicalOpConstantScan),
|
||||
new Operation("CrossJoin", SR.Keys.LogicalOpCrossJoin),
|
||||
new Operation("Delete", SR.Keys.LogicalOpDelete),
|
||||
new Operation("DeletedScan", SR.Keys.LogicalOpDeletedScan),
|
||||
new Operation("DistinctSort", SR.Keys.LogicalOpDistinctSort),
|
||||
new Operation("Distinct", SR.Keys.LogicalOpDistinct),
|
||||
new Operation("DistributeStreams", SR.Keys.LogicalOpDistributeStreams, SR.Keys.DistributeStreamsDescription, "Parallelism_distribute.ico"),
|
||||
new Operation("EagerSpool", SR.Keys.LogicalOpEagerSpool),
|
||||
new Operation("Filter", SR.Keys.LogicalOpFilter),
|
||||
new Operation("FlowDistinct", SR.Keys.LogicalOpFlowDistinct),
|
||||
new Operation("FullOuterJoin", SR.Keys.LogicalOpFullOuterJoin),
|
||||
new Operation("GatherStreams", SR.Keys.LogicalOpGatherStreams, SR.Keys.GatherStreamsDescription, "parallelism"),
|
||||
new Operation("IndexScan", SR.Keys.LogicalOpIndexScan),
|
||||
new Operation("IndexSeek", SR.Keys.LogicalOpIndexSeek),
|
||||
new Operation("InnerApply", SR.Keys.LogicalOpInnerApply),
|
||||
new Operation("InnerJoin", SR.Keys.LogicalOpInnerJoin),
|
||||
new Operation("Insert", SR.Keys.LogicalOpInsert),
|
||||
new Operation("InsertedScan", SR.Keys.LogicalOpInsertedScan),
|
||||
new Operation("IntersectAll", SR.Keys.LogicalOpIntersectAll),
|
||||
new Operation("Intersect", SR.Keys.LogicalOpIntersect),
|
||||
new Operation("KeyLookup", SR.Keys.LogicalKeyLookup),
|
||||
new Operation("LazySpool", SR.Keys.LogicalOpLazySpool),
|
||||
new Operation("LeftAntiSemiApply", SR.Keys.LogicalOpLeftAntiSemiApply),
|
||||
new Operation("LeftAntiSemiJoin", SR.Keys.LogicalOpLeftAntiSemiJoin),
|
||||
new Operation("LeftDiffAll", SR.Keys.LogicalOpLeftDiffAll),
|
||||
new Operation("LeftDiff", SR.Keys.LogicalOpLeftDiff),
|
||||
new Operation("LeftOuterApply", SR.Keys.LogicalOpLeftOuterApply),
|
||||
new Operation("LeftOuterJoin", SR.Keys.LogicalOpLeftOuterJoin),
|
||||
new Operation("LeftSemiApply", SR.Keys.LogicalOpLeftSemiApply),
|
||||
new Operation("LeftSemiJoin", SR.Keys.LogicalOpLeftSemiJoin),
|
||||
new Operation("LogRowScan", SR.Keys.LogicalOpLogRowScan),
|
||||
new Operation("MergeInterval", SR.Keys.LogicalOpMergeInterval),
|
||||
new Operation("ParameterTableScan", SR.Keys.LogicalOpParameterTableScan),
|
||||
new Operation("PartialAggregate", SR.Keys.LogicalOpPartialAggregate),
|
||||
new Operation("Print", SR.Keys.LogicalOpPrint),
|
||||
new Operation("Put", SR.Keys.LogicalOpPut),
|
||||
new Operation("Rank", SR.Keys.LogicalOpRank),
|
||||
new Operation("ForeignKeyReferencesCheck", SR.Keys.LogicalOpForeignKeyReferencesCheck),
|
||||
new Operation("RemoteDelete", SR.Keys.LogicalOpRemoteDelete),
|
||||
new Operation("RemoteIndexScan", SR.Keys.LogicalOpRemoteIndexScan),
|
||||
new Operation("RemoteIndexSeek", SR.Keys.LogicalOpRemoteIndexSeek),
|
||||
new Operation("RemoteInsert", SR.Keys.LogicalOpRemoteInsert),
|
||||
new Operation("RemoteQuery", SR.Keys.LogicalOpRemoteQuery),
|
||||
new Operation("RemoteScan", SR.Keys.LogicalOpRemoteScan),
|
||||
new Operation("RemoteUpdate", SR.Keys.LogicalOpRemoteUpdate),
|
||||
new Operation("RepartitionStreams", SR.Keys.LogicalOpRepartitionStreams, SR.Keys.RepartitionStreamsDescription, "Parallelism_repartition.ico"),
|
||||
new Operation("RIDLookup", SR.Keys.LogicalOpRIDLookup),
|
||||
new Operation("RightAntiSemiJoin", SR.Keys.LogicalOpRightAntiSemiJoin),
|
||||
new Operation("RightDiffAll", SR.Keys.LogicalOpRightDiffAll),
|
||||
new Operation("RightDiff", SR.Keys.LogicalOpRightDiff),
|
||||
new Operation("RightOuterJoin", SR.Keys.LogicalOpRightOuterJoin),
|
||||
new Operation("RightSemiJoin", SR.Keys.LogicalOpRightSemiJoin),
|
||||
new Operation("Segment", SR.Keys.LogicalOpSegment),
|
||||
new Operation("Sequence", SR.Keys.LogicalOpSequence),
|
||||
new Operation("Sort", SR.Keys.LogicalOpSort),
|
||||
new Operation("Split", SR.Keys.LogicalOpSplit),
|
||||
new Operation("Switch", SR.Keys.LogicalOpSwitch),
|
||||
new Operation("Tablevaluedfunction", SR.Keys.LogicalOpTableValuedFunction),
|
||||
new Operation("TableScan", SR.Keys.LogicalOpTableScan),
|
||||
new Operation("Top", SR.Keys.LogicalOpTop),
|
||||
new Operation("TopNSort", SR.Keys.LogicalOpTopNSort),
|
||||
new Operation("UDX", SR.Keys.LogicalOpUDX),
|
||||
new Operation("Union", SR.Keys.LogicalOpUnion),
|
||||
new Operation("Update", SR.Keys.LogicalOpUpdate),
|
||||
new Operation("Merge", SR.Keys.LogicalOpMerge),
|
||||
new Operation("MergeStats", SR.Keys.LogicalOpMergeStats),
|
||||
new Operation("LocalStats", SR.Keys.LogicalOpLocalStats),
|
||||
new Operation("BatchHashTableBuild", SR.Keys.LogicalOpBatchHashTableBuild),
|
||||
new Operation("WindowSpool", SR.Keys.LogicalOpWindow),
|
||||
};
|
||||
|
||||
LogicalOperations = DictionaryFromList(logicalOperationList);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Statements
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// TODO: may need to put a few more statements in here
|
||||
Operation[] statementList = new Operation[]
|
||||
{
|
||||
new Operation("SELECT", null, null, "result"),
|
||||
new Operation("COND", null, null, "ifOperator")
|
||||
};
|
||||
|
||||
Statements = DictionaryFromList(statementList);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Cursor types
|
||||
/// Name / Type SR Display Name Key SR Description Key Image
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Operation[] cursorTypeList = new Operation[]
|
||||
{
|
||||
new Operation("Dynamic", SR.Keys.Dynamic, SR.Keys.DynamicDescription, "dynamic"),
|
||||
new Operation("FastForward", SR.Keys.FastForward, SR.Keys.FastForwardDescription, "cursorCatchAll"),
|
||||
new Operation("Keyset", SR.Keys.Keyset, SR.Keys.KeysetDescription, "keyset"),
|
||||
new Operation("SnapShot", SR.Keys.Snapshot, SR.Keys.SnapshotDescription, "snapshot")
|
||||
};
|
||||
|
||||
CursorTypes = DictionaryFromList(cursorTypeList);
|
||||
}
|
||||
|
||||
private static Dictionary<string, Operation> DictionaryFromList(Operation[] list)
|
||||
{
|
||||
Dictionary<string, Operation> dictionary = new Dictionary<string, Operation>(list.Length);
|
||||
foreach (Operation item in list)
|
||||
{
|
||||
dictionary.Add(item.Name, item);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private static string GetNameFromXmlEnumAttribute(string enumMemberName, Type enumType)
|
||||
{
|
||||
Debug.Assert(enumType.IsEnum);
|
||||
|
||||
foreach (MemberInfo member in enumType.GetMembers())
|
||||
{
|
||||
if (member.Name == enumMemberName)
|
||||
{
|
||||
object[] attributes = member.GetCustomAttributes(typeof(System.Xml.Serialization.XmlEnumAttribute), true);
|
||||
foreach (System.Xml.Serialization.XmlEnumAttribute attribute in attributes)
|
||||
{
|
||||
return attribute.Name;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing has been found, just return enumMemberName.
|
||||
return enumMemberName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private members
|
||||
|
||||
private static readonly Dictionary<string, Operation> PhysicalOperations;
|
||||
private static readonly Dictionary<string, Operation> LogicalOperations;
|
||||
private static readonly Dictionary<string, Operation> Statements;
|
||||
private static readonly Dictionary<string, Operation> CursorTypes;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,742 @@
|
||||
//
|
||||
// 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.ComponentModel;
|
||||
using System.Xml.Serialization;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// PropertyFactory creates properties based on template properties (this class public properties)
|
||||
///
|
||||
/// IMPORTANT: Property names should match those in ShowPlanXML classes
|
||||
///
|
||||
/// Note: to hide a property from PropertyGrid, it should be defined
|
||||
/// here with [Browsable(false)] attribute.
|
||||
///
|
||||
/// </summary>
|
||||
internal class PropertyFactory
|
||||
{
|
||||
#region Property templates
|
||||
|
||||
[ShowInToolTip, DisplayOrder(0), DisplayNameDescription(SR.Keys.PhysicalOperation, SR.Keys.PhysicalOperationDesc)]
|
||||
public string PhysicalOp { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(1), DisplayNameDescription(SR.Keys.LogicalOperation, SR.Keys.LogicalOperationDesc)]
|
||||
public string LogicalOp { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.EstimatedExecMode, SR.Keys.EstimatedExecModeDesc)]
|
||||
public string EstimatedExecutionMode { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.ActualExecMode, SR.Keys.ActualExecModeDesc)]
|
||||
public string ActualExecutionMode { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(3), DisplayNameDescription(SR.Keys.Storage, SR.Keys.StorageDesc)]
|
||||
public string Storage { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(102), DisplayNameDescription(SR.Keys.EstimatedDataSize, SR.Keys.EstimatedDataSizeDescription)]
|
||||
[TypeConverter(typeof(DataSizeTypeConverter))]
|
||||
public double EstimatedDataSize { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(4), DisplayNameDescription(SR.Keys.NumberOfRows, SR.Keys.NumberOfRowsDescription)]
|
||||
public double ActualRows { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(4), DisplayNameDescription(SR.Keys.ActualRowsRead, SR.Keys.ActualRowsReadDescription)]
|
||||
public double ActualRowsRead { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(5), DisplayNameDescription(SR.Keys.NumberOfBatches, SR.Keys.NumberOfBatchesDescription)]
|
||||
public double ActualBatches { get { return 0; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(6), DisplayNameDescription(SR.Keys.Statement, SR.Keys.StatementDesc)]
|
||||
public string StatementText { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(6), DisplayNameDescription(SR.Keys.Predicate, SR.Keys.PredicateDescription)]
|
||||
public string Predicate { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(101), DisplayNameDescription(SR.Keys.EstimatedRowSize, SR.Keys.EstimatedRowSizeDescription)]
|
||||
[TypeConverter(typeof(DataSizeTypeConverter))]
|
||||
public int AvgRowSize { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(7), DisplayNameDescription(SR.Keys.CachedPlanSize, SR.Keys.CachedPlanSizeDescription)]
|
||||
[TypeConverter(typeof(KBSizeTypeConverter))]
|
||||
public int CachedPlanSize { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(7), DisplayNameDescription(SR.Keys.UsePlan)]
|
||||
public bool UsePlan { get { return false; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(7), DisplayNameDescription(SR.Keys.ContainsInlineScalarTsqlUdfs)]
|
||||
|
||||
public bool ContainsInlineScalarTsqlUdfs { get { return false; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(8), DisplayNameDescription(SR.Keys.EstimatedIoCost, SR.Keys.EstimatedIoCostDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimateIO { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(8), DisplayNameDescription(SR.Keys.DegreeOfParallelism, SR.Keys.DegreeOfParallelismDescription)]
|
||||
public int DegreeOfParallelism { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(8), DisplayNameDescription(SR.Keys.EffectiveDegreeOfParallelism, SR.Keys.EffectiveDegreeOfParallelismDescription)]
|
||||
public int EffectiveDegreeOfParallelism { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(9), DisplayNameDescription(SR.Keys.EstimatedCpuCost, SR.Keys.EstimatedCpuCostDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimateCPU { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(9), DisplayNameDescription(SR.Keys.MemoryGrant, SR.Keys.MemoryGrantDescription)]
|
||||
[TypeConverter(typeof(KBSizeTypeConverter))]
|
||||
public ulong MemoryGrant { get { return 0; } }
|
||||
|
||||
[DisplayOrder(10), DisplayNameDescription(SR.Keys.ParameterList, SR.Keys.ParameterListDescription)]
|
||||
public object ParameterList { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(10), DisplayNameDescription(SR.Keys.NumberOfExecutions, SR.Keys.NumberOfExecutionsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double ActualExecutions { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(10), DisplayNameDescription(SR.Keys.EstimatedNumberOfExecutions, SR.Keys.EstimatedNumberOfExecutionsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
|
||||
public double EstimateExecutions { get { return 0; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(12), DisplayNameDescription(SR.Keys.ObjectShort, SR.Keys.ObjectDescription)]
|
||||
public object Object { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.IndexKind, SR.Keys.IndexKindDescription)]
|
||||
public string IndexKind { get { return null; } }
|
||||
|
||||
[DisplayOrder(12), DisplayNameDescription(SR.Keys.OperationArgumentShort, SR.Keys.OperationArgumentDescription)]
|
||||
public string Argument { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(111), DisplayNameDescription(SR.Keys.ActualRebinds, SR.Keys.ActualRebindsDescription)]
|
||||
public object ActualRebinds { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(112), DisplayNameDescription(SR.Keys.ActualRewinds, SR.Keys.ActualRewindsDescription)]
|
||||
|
||||
public object ActualRewinds { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLocallyAggregatedRows, SR.Keys.ActualLocallyAggregatedRowsDescription)]
|
||||
public object ActualLocallyAggregatedRows { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualElapsedms, SR.Keys.ActualElapsedmsDescription)]
|
||||
public object ActualElapsedms { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualCPUms, SR.Keys.ActualCPUmsDescription)]
|
||||
public object ActualCPUms { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualScans, SR.Keys.ActualScansDescription)]
|
||||
public object ActualScans { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLogicalReads, SR.Keys.ActualLogicalReadsDescription)]
|
||||
public object ActualLogicalReads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualPhysicalReads, SR.Keys.ActualPhysicalReadsDescription)]
|
||||
public object ActualPhysicalReads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualPageServerReads, SR.Keys.ActualPageServerReadsDescription)]
|
||||
public object ActualPageServerReads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualReadAheads, SR.Keys.ActualReadAheadsDescription)]
|
||||
public object ActualReadAheads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualPageServerReadAheads, SR.Keys.ActualPageServerReadAheadsDescription)]
|
||||
public object ActualPageServerReadAheads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLobLogicalReads, SR.Keys.ActualLobLogicalReadsDescription)]
|
||||
public object ActualLobLogicalReads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLobPhysicalReads, SR.Keys.ActualLobPhysicalReadsDescription)]
|
||||
public object ActualLobPhysicalReads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLobPageServerReads, SR.Keys.ActualLobPageServerReadsDescription)]
|
||||
public object ActualLobPageServerReads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLobReadAheads, SR.Keys.ActualLobReadAheadsDescription)]
|
||||
public object ActualLobReadAheads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualLobPageServerReadAheads, SR.Keys.ActualLobPageServerReadAheadsDescription)]
|
||||
public object ActualLobPageServerReadAheads { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualIOStatistics, SR.Keys.ActualIOStatisticsDescription)]
|
||||
public object ActualIOStatistics { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualTimeStatistics, SR.Keys.ActualTimeStatisticsDescription)]
|
||||
public object ActualTimeStatistics { get { return null; } }
|
||||
|
||||
[DisplayOrder(221), DisplayNameDescription(SR.Keys.ActualMemoryGrantStats, SR.Keys.ActualMemoryGrantStats)]
|
||||
public object ActualMemoryGrantStats { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.HpcRowCount, SR.Keys.HpcRowCountDescription)]
|
||||
public object HpcRowCount { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.HpcKernelElapsedUs, SR.Keys.HpcKernelElapsedUsDescription)]
|
||||
public object HpcKernelElapsedUs { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.HpcHostToDeviceBytes, SR.Keys.HpcHostToDeviceBytesDescription)]
|
||||
public object HpcHostToDeviceBytes { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(221), DisplayNameDescription(SR.Keys.HpcDeviceToHostBytes, SR.Keys.HpcDeviceToHostBytesDescription)]
|
||||
public object HpcDeviceToHostBytes { get { return null; } }
|
||||
|
||||
[DisplayOrder(221), DisplayNameDescription(SR.Keys.InputMemoryGrant, SR.Keys.InputMemoryGrant)]
|
||||
public object InputMemoryGrant { get { return null; } }
|
||||
|
||||
[DisplayOrder(221), DisplayNameDescription(SR.Keys.OutputMemoryGrant, SR.Keys.OutputMemoryGrant)]
|
||||
public object OutputMemoryGrant { get { return null; } }
|
||||
|
||||
[DisplayOrder(221), DisplayNameDescription(SR.Keys.UsedMemoryGrant, SR.Keys.UsedMemoryGrant)]
|
||||
public object UsedMemoryGrant { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.IsGraphDBTransitiveClosure, SR.Keys.IsGraphDBTransitiveClosureDescription)]
|
||||
public bool IsGraphDBTransitiveClosure { get { return false; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.IsInterleavedExecuted, SR.Keys.IsInterleavedExecutedDescription)]
|
||||
public bool IsInterleavedExecuted { get { return false; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.IsAdaptive, SR.Keys.IsAdaptiveDescription)]
|
||||
public bool IsAdaptive { get { return false; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.AdaptiveThresholdRows, SR.Keys.AdaptiveThresholdRowsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double AdaptiveThresholdRows { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.EstimatedJoinType, SR.Keys.EstimatedJoinTypeDescription)]
|
||||
public string EstimatedJoinType { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(2), DisplayNameDescription(SR.Keys.ActualJoinType, SR.Keys.ActualJoinTypeDescription)]
|
||||
public string ActualJoinType { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(100), DisplayNameDescription(SR.Keys.EstimatedNumberOfRowsPerExecution, SR.Keys.EstimatedNumberOfRowsPerExecutionDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimateRows { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(100), DisplayNameDescription(SR.Keys.EstimatedNumberOfRowsPerExecution, SR.Keys.EstimatedNumberOfRowsPerExecutionDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double StatementEstRows { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(100), DisplayNameDescription(SR.Keys.EstimatedNumberOfRowsForAllExecutions, SR.Keys.EstimatedNumberOfRowsForAllExecutionsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimateRowsAllExecs { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(100), DisplayNameDescription(SR.Keys.EstimatedNumberOfRowsForAllExecutions, SR.Keys.EstimatedNumberOfRowsForAllExecutionsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double StatementEstRowsAllExecs { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(100), DisplayNameDescription(SR.Keys.EstimatedRowsRead, SR.Keys.EstimatedRowsReadDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimatedRowsRead { get { return 0; } }
|
||||
|
||||
[DisplayOrder(101), DisplayNameDescription(SR.Keys.EstimatedRebinds, SR.Keys.EstimatedRebindsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimateRebinds { get { return 0; } }
|
||||
|
||||
[DisplayOrder(102), DisplayNameDescription(SR.Keys.EstimatedRewinds, SR.Keys.EstimatedRewindsDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double EstimateRewinds { get { return 0; } }
|
||||
|
||||
[DisplayOrder(200), DisplayNameDescription(SR.Keys.DefinedValues, SR.Keys.DefinedValuesDescription)]
|
||||
public string DefinedValues { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(201), DisplayNameDescription(SR.Keys.OutputList, SR.Keys.OutputListDescription)]
|
||||
public object OutputList { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(202), DisplayNameDescription(SR.Keys.Warnings, SR.Keys.WarningsDescription)]
|
||||
public object Warnings { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.Parallel, SR.Keys.ParallelDescription)]
|
||||
public bool Parallel { get { return false; } }
|
||||
|
||||
[DisplayOrder(204), DisplayNameDescription(SR.Keys.SetOptions, SR.Keys.SetOptionsDescription)]
|
||||
public object StatementSetOptions { get { return null; } }
|
||||
|
||||
[DisplayOrder(205), DisplayNameDescription(SR.Keys.OptimizationLevel, SR.Keys.OptimizationLevelDescription)]
|
||||
public string StatementOptmLevel { get { return null; } }
|
||||
|
||||
[DisplayOrder(206), DisplayNameDescription(SR.Keys.StatementOptmEarlyAbortReason)]
|
||||
public string StatementOptmEarlyAbortReason { get { return null; } }
|
||||
|
||||
[DisplayOrder(211), DisplayNameDescription(SR.Keys.MemoryFractions, SR.Keys.MemoryFractionsDescription)]
|
||||
public object MemoryFractions { get { return null; } }
|
||||
|
||||
[DisplayOrder(211), DisplayNameDescription(SR.Keys.MemoryFractionsInput, SR.Keys.MemoryFractionsInputDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double Input { get { return 0; } }
|
||||
|
||||
[DisplayOrder(212), DisplayNameDescription(SR.Keys.MemoryFractionsOutput, SR.Keys.MemoryFractionsOutputDescription)]
|
||||
[TypeConverter(typeof(FloatTypeConverter))]
|
||||
public double Output { get { return 0; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.RemoteDestination, SR.Keys.RemoteDestinationDescription)]
|
||||
public string RemoteDestination { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.RemoteObject, SR.Keys.RemoteObjectDescription)]
|
||||
public string RemoteObject { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.RemoteSource, SR.Keys.RemoteSourceDescription)]
|
||||
public string RemoteSource { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.RemoteQuery, SR.Keys.RemoteQueryDescription)]
|
||||
public string RemoteQuery { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.UsedUdxColumns, SR.Keys.UsedUdxColumnsDescription)]
|
||||
public object UsedUDXColumns { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(204), DisplayNameDescription(SR.Keys.UdxName, SR.Keys.UdxNameDescription)]
|
||||
public string UDXName { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.InnerSideJoinColumns, SR.Keys.InnerSideJoinColumnsDescription)]
|
||||
public object InnerSideJoinColumns { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(204), DisplayNameDescription(SR.Keys.OuterSideJoinColumns, SR.Keys.OuterSideJoinColumnsDescription)]
|
||||
public object OuterSideJoinColumns { get { return null; } }
|
||||
|
||||
[DisplayOrder(205), DisplayNameDescription(SR.Keys.Residual, SR.Keys.ResidualDescription)]
|
||||
public string Residual { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(206), DisplayNameDescription(SR.Keys.PassThru, SR.Keys.PassThruDescription)]
|
||||
public string PassThru { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(207), DisplayNameDescription(SR.Keys.ManyToMany, SR.Keys.ManyToManyDescription)]
|
||||
public bool ManyToMany { get { return false; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.PartitionColumns, SR.Keys.PartitionColumnsDescription)]
|
||||
public object PartitionColumns { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(204), DisplayNameDescription(SR.Keys.OrderBy, SR.Keys.OrderByDescription)]
|
||||
public object OrderBy { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(205), DisplayNameDescription(SR.Keys.HashKeys, SR.Keys.HashKeysDescription)]
|
||||
public object HashKeys { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(206), DisplayNameDescription(SR.Keys.ProbeColumn, SR.Keys.ProbeColumnDescription)]
|
||||
public object ProbeColumn { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(207), DisplayNameDescription(SR.Keys.PartitioningType, SR.Keys.PartitioningTypeDescription)]
|
||||
public string PartitioningType { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.GroupBy, SR.Keys.GroupByDescription)]
|
||||
public object GroupBy { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.GroupingSets, SR.Keys.GroupingSetsDescription)]
|
||||
public object GroupingSets { get { return null; } }
|
||||
|
||||
[DisplayOrder(200), DisplayNameDescription(SR.Keys.RollupInfo, SR.Keys.RollupInfoDescription)]
|
||||
public object RollupInfo { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.HighestLevel, SR.Keys.HighestLevelDescription)]
|
||||
public object HighestLevel { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.RollupLevel, SR.Keys.RollupLevelDescription)]
|
||||
[Browsable(true), ImmutableObject(true)]
|
||||
public object RollupLevel { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.Level, SR.Keys.LevelDescription)]
|
||||
public object Level { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.SegmentColumn, SR.Keys.SegmentColumnDescription)]
|
||||
public object SegmentColumn { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.HashKeysBuild, SR.Keys.HashKeysBuildDescription)]
|
||||
public object HashKeysBuild { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.HashKeysProbe, SR.Keys.HashKeysProbeDescription)]
|
||||
public object HashKeysProbe { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.BuildResidual, SR.Keys.BuildResidualDescription)]
|
||||
public string BuildResidual { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.ProbeResidual, SR.Keys.ProbeResidualDescription)]
|
||||
public string ProbeResidual { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.SetPredicate, SR.Keys.SetPredicateDescription)]
|
||||
public string SetPredicate { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.RankColumns, SR.Keys.RankColumnsDescription)]
|
||||
public object RankColumns { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.ActionColumn, SR.Keys.ActionColumnDescription)]
|
||||
public object ActionColumn { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.OriginalActionColumn, SR.Keys.OriginalActionColumnDescription)]
|
||||
public object OriginalActionColumn { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.Rows, SR.Keys.RowsDescription)]
|
||||
public int Rows { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(150), DisplayNameDescription(SR.Keys.Partitioned, SR.Keys.PartitionedDescription)]
|
||||
public object Partitioned { get { return null; } }
|
||||
|
||||
[DisplayOrder(156), DisplayNameDescription(SR.Keys.PartitionsAccessed)]
|
||||
public object PartitionsAccessed { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(152), DisplayNameDescription(SR.Keys.PartitionCount)]
|
||||
public object PartitionCount { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.TieColumns, SR.Keys.TieColumnsDescription)]
|
||||
public object TieColumns { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.IsPercent, SR.Keys.IsPercentDescription)]
|
||||
public bool IsPercent { get { return false; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.WithTies, SR.Keys.WithTiesDescription)]
|
||||
public bool WithTies { get { return false; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.TopExpression, SR.Keys.TopExpressionDescription)]
|
||||
public string TopExpression { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.Distinct, SR.Keys.DistinctDescription)]
|
||||
public bool Distinct { get { return false; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(205), DisplayNameDescription(SR.Keys.OuterReferences, SR.Keys.OuterReferencesDescription)]
|
||||
public object OuterReferences { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.PartitionId, SR.Keys.PartitionIdDescription)]
|
||||
public object PartitionId { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.Ordered, SR.Keys.OrderedDescription)]
|
||||
public bool Ordered { get { return false; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.ScanDirection, SR.Keys.ScanDirectionDescription)]
|
||||
public object ScanDirection { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.SeekPredicate, SR.Keys.SeekPredicateDescription)]
|
||||
public object SeekPredicate { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.SeekPredicate, SR.Keys.SeekPredicateDescription)]
|
||||
public object SeekPredicateNew { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(203), DisplayNameDescription(SR.Keys.SeekPredicate, SR.Keys.SeekPredicateDescription)]
|
||||
public object SeekPredicatePart { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(205), DisplayNameDescription(SR.Keys.SeekPredicates, SR.Keys.SeekPredicatesDescription)]
|
||||
public string SeekPredicates { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.ForcedIndex, SR.Keys.ForcedIndexDescription)]
|
||||
public bool ForcedIndex { get { return false; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(5), DisplayNameDescription(SR.Keys.Values, SR.Keys.ValuesDescription)]
|
||||
public object Values { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.ColumnsWithNoStatistics, SR.Keys.ColumnsWithNoStatisticsDescription)]
|
||||
public object ColumnsWithNoStatistics { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.NoJoinPredicate, SR.Keys.NoJoinPredicateDescription)]
|
||||
public bool NoJoinPredicate { get { return false; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.SpillToTempDb, SR.Keys.SpillToTempDbDescription)]
|
||||
public object SpillToTempDb { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.StartupExpression, SR.Keys.StartupExpressionDescription)]
|
||||
public bool StartupExpression { get { return false; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.Query)]
|
||||
public string Query { get { return null; } }
|
||||
|
||||
[DisplayOrder(203), DisplayNameDescription(SR.Keys.Stack)]
|
||||
public bool Stack { get { return false; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(203), DisplayNameDescription(SR.Keys.RowCount)]
|
||||
public bool RowCount { get { return false; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.Optimized)]
|
||||
public bool Optimized { get { return false; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.WithPrefetch)]
|
||||
public bool WithPrefetch { get { return false; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.Prefix)]
|
||||
public object Prefix { get { return null; } }
|
||||
|
||||
[DisplayOrder(7), DisplayNameDescription(SR.Keys.StartRange, SR.Keys.StartRangeDescription)]
|
||||
public object StartRange { get { return null; } }
|
||||
|
||||
[DisplayOrder(8), DisplayNameDescription(SR.Keys.EndRange, SR.Keys.EndRangeDescription)]
|
||||
public object EndRange { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.RangeColumns)]
|
||||
public object RangeColumns { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.RangeExpressions)]
|
||||
public object RangeExpressions { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ScanType)]
|
||||
public object ScanType { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ColumnReference)]
|
||||
public object ColumnReference { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectServer, SR.Keys.ObjectServerDescription)]
|
||||
public string Server { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectDatabase, SR.Keys.ObjectDatabaseDescription)]
|
||||
public string Database { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectIndex, SR.Keys.ObjectIndexDescription)]
|
||||
public string Index { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectSchema, SR.Keys.ObjectSchemaDescription)]
|
||||
public string Schema { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectTable, SR.Keys.ObjectTableDescription)]
|
||||
public string Table { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectAlias, SR.Keys.ObjectAliasDescription)]
|
||||
public string Alias { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectColumn, SR.Keys.ObjectColumnDescription)]
|
||||
public string Column { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ObjectComputedColumn, SR.Keys.ObjectComputedColumnDescription)]
|
||||
public bool ComputedColumn { get { return false; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ParameterDataType)]
|
||||
public string ParameterDataType { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ParameterCompiledValue)]
|
||||
public string ParameterCompiledValue { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ParameterRuntimeValue)]
|
||||
public string ParameterRuntimeValue { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.CursorPlan)]
|
||||
public object CursorPlan { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.CursorOperation)]
|
||||
public object Operation { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.CursorName)]
|
||||
public string CursorName { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.CursorActualType)]
|
||||
public object CursorActualType { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.CursorRequestedType)]
|
||||
public object CursorRequestedType { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.CursorConcurrency)]
|
||||
public object CursorConcurrency { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.ForwardOnly)]
|
||||
public bool ForwardOnly { get { return false; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.QueryPlan)]
|
||||
public object QueryPlan { get { return null; } }
|
||||
|
||||
[DisplayOrder(6), DisplayNameDescription(SR.Keys.OperationType)]
|
||||
public object OperationType { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(300), DisplayNameDescription(SR.Keys.NodeId)]
|
||||
public int NodeId { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(301), DisplayNameDescription(SR.Keys.PrimaryNodeId)]
|
||||
public int PrimaryNodeId { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(302), DisplayNameDescription(SR.Keys.ForeignKeyReferencesCount)]
|
||||
public int ForeignKeyReferencesCount { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(303), DisplayNameDescription(SR.Keys.NoMatchingIndexCount)]
|
||||
public int NoMatchingIndexCount { get { return 0; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(304), DisplayNameDescription(SR.Keys.PartialMatchingIndexCount)]
|
||||
public int PartialMatchingIndexCount { get { return 0; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(6), DisplayNameDescription(SR.Keys.WhereJoinColumns)]
|
||||
public object WhereJoinColumns { get { return null; } }
|
||||
|
||||
[ShowInToolTip(LongString = true), DisplayOrder(6), DisplayNameDescription(SR.Keys.ProcName)]
|
||||
public string ProcName { get { return null; } }
|
||||
|
||||
[DisplayOrder(400), DisplayNameDescription(SR.Keys.InternalInfo)]
|
||||
public object InternalInfo { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(220), DisplayNameDescription(SR.Keys.RemoteDataAccess, SR.Keys.RemoteDataAccessDescription)]
|
||||
public bool RemoteDataAccess { get { return false; } }
|
||||
|
||||
[DisplayOrder(220), DisplayNameDescription(SR.Keys.CloneAccessScope, SR.Keys.CloneAccessScopeDescription)]
|
||||
public string CloneAccessScope { get { return null; } }
|
||||
|
||||
[ShowInToolTip, DisplayOrder(220), DisplayNameDescription(SR.Keys.Remoting, SR.Keys.RemotingDescription)]
|
||||
public bool Remoting { get { return false; } }
|
||||
|
||||
[DisplayOrder(201), DisplayNameDescription(SR.Keys.Activation)]
|
||||
public object Activation { get { return null; } }
|
||||
|
||||
[DisplayOrder(201), DisplayNameDescription(SR.Keys.BrickRouting)]
|
||||
public object BrickRouting { get { return null; } }
|
||||
|
||||
[DisplayOrder(201), DisplayNameDescription(SR.Keys.FragmentIdColumn)]
|
||||
public object FragmentIdColumn { get { return null; } }
|
||||
public string CardinalityEstimationModelVersion { get { return null; } }
|
||||
public string CompileCPU { get { return null; } }
|
||||
public string CompileMemory { get { return null; } }
|
||||
public string CompileTime { get { return null; } }
|
||||
public string NonParallelPlanReason { get { return null; } }
|
||||
public string QueryHash { get { return null; } }
|
||||
public string QueryPlanHash { get { return null; } }
|
||||
public bool RetrievedFromCache { get { return false; } }
|
||||
public bool SecurityPolicyApplied { get { return false; } }
|
||||
public bool NoExpandHint { get { return false; } }
|
||||
public double TableCardinality { get { return 0; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Non-browsable properties
|
||||
|
||||
// The following properties should be hidden from UI
|
||||
|
||||
|
||||
[Browsable(false)]
|
||||
public string PhysicalOperationKind { get { return null; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public double EstimatedTotalSubtreeCost { get { return 0; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public double StatementSubTreeCost { get { return 0; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public double TotalSubtreeCost { get { return 0; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public int Parent { get { return 0; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public int StatementId { get { return 0; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public int StatementCompId { get { return 0; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public object RunTimeInformation { get { return null; } }
|
||||
|
||||
[Browsable(false)]
|
||||
public object StatementType { get { return null; } }
|
||||
|
||||
/// <summary>
|
||||
/// Run time partition summary should not show up as one node. Details such as PartitionsAccessed is displayed in individually.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public object RunTimePartitionSummary { get { return null; } }
|
||||
[Browsable(false)]
|
||||
public object SkeletonNode { get { return null; } }
|
||||
[Browsable(false)]
|
||||
public object SkeletonHasMatch { get { return null; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region CreateProperty
|
||||
public static PropertyDescriptor CreateProperty(PropertyDescriptor property, object value)
|
||||
{
|
||||
Type type = null;
|
||||
|
||||
// In case of xml Choice group, the property name can be general like "Item" or "Items".
|
||||
// The real names are specified by XmlElementAttributes. We need to save the type of
|
||||
// value to extract its original name from its XmlElementAttribute.
|
||||
if (property.Name == "Items" || property.Name == "Item")
|
||||
{
|
||||
type = value.GetType();
|
||||
}
|
||||
|
||||
// Convert value if ObjectWrapperTypeConverter supports it
|
||||
if (ObjectWrapperTypeConverter.Default.CanConvertFrom(property.PropertyType))
|
||||
{
|
||||
value = ObjectWrapperTypeConverter.Default.ConvertFrom(value);
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PropertyDescriptor templateProperty = Properties[property.Name];
|
||||
|
||||
if (templateProperty != null)
|
||||
{
|
||||
return new PropertyValue(templateProperty, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable attributeCollection = property.Attributes;
|
||||
string propertyName = property.Name;
|
||||
|
||||
// In case of xml Choice group, the property name can be general like "Item" or "Items".
|
||||
// The real names are specified by XmlElementAttributes. property.Attributes does not
|
||||
// return all the attributes. Hence we need extract custom attributes through
|
||||
// PropertyInfo class.
|
||||
if (type != null)
|
||||
{
|
||||
attributeCollection = PropertyFactory.GetAttributeCollectionForChoiceElement(property);
|
||||
}
|
||||
|
||||
foreach (object attrib in attributeCollection)
|
||||
{
|
||||
XmlElementAttribute attribute = attrib as XmlElementAttribute;
|
||||
|
||||
if (attribute != null && !string.IsNullOrEmpty(attribute.ElementName))
|
||||
{
|
||||
if ((type == null) || (type.Equals(attribute.Type)))
|
||||
{
|
||||
propertyName = attribute.ElementName;
|
||||
|
||||
templateProperty = Properties[propertyName];
|
||||
if (templateProperty != null)
|
||||
{
|
||||
return new PropertyValue(templateProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: review this debug code
|
||||
return new PropertyValue(propertyName, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable GetAttributeCollectionForChoiceElement(PropertyDescriptor property)
|
||||
{
|
||||
Type type = property.ComponentType;
|
||||
PropertyInfo pInfo = type.GetProperty("Items");
|
||||
|
||||
if (pInfo == null)
|
||||
{
|
||||
//Try using item.
|
||||
pInfo = type.GetProperty("Item");
|
||||
}
|
||||
|
||||
if (pInfo != null)
|
||||
{
|
||||
return pInfo.GetCustomAttributes(true);
|
||||
}
|
||||
|
||||
return property.Attributes;
|
||||
}
|
||||
public static PropertyDescriptor CreateProperty(string propertyName, object value)
|
||||
{
|
||||
PropertyDescriptor templateProperty = Properties[propertyName];
|
||||
|
||||
if (templateProperty != null)
|
||||
{
|
||||
return new PropertyValue(templateProperty, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: review this debug code
|
||||
return new PropertyValue(propertyName, value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation details
|
||||
|
||||
private PropertyFactory() { }
|
||||
|
||||
private static PropertyDescriptorCollection Properties = TypeDescriptor.GetProperties(typeof(PropertyFactory));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
//
|
||||
// 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;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
internal sealed class PropertyValue : PropertyDescriptor
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public PropertyValue(string name, object value)
|
||||
: base(name, null)
|
||||
{
|
||||
this.propertyValue = value;
|
||||
}
|
||||
|
||||
public PropertyValue(PropertyDescriptor baseProperty, object value)
|
||||
: this(baseProperty.Name, value)
|
||||
{
|
||||
this.baseProperty = baseProperty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public methods and properties
|
||||
|
||||
public object Value
|
||||
{
|
||||
get { return this.propertyValue; }
|
||||
set { this.propertyValue = value; }
|
||||
}
|
||||
|
||||
public string DisplayValue
|
||||
{
|
||||
get => this.Converter.ConvertToString(null, null, this.Value);
|
||||
}
|
||||
|
||||
public int DisplayOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDisplayAttributesIfNecessary();
|
||||
return this.displayOrder;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowInTooltip
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.showInTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLongString
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDisplayAttributesIfNecessary();
|
||||
return this.isLongString;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDisplayNameAndDescription(string newDisplayName, string newDescription)
|
||||
{
|
||||
this.displayName = newDisplayName;
|
||||
this.description = newDescription;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PropertyDesciptor overrides
|
||||
|
||||
public override AttributeCollection Attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.baseProperty != null ? baseProperty.Attributes : base.Attributes;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanResetValue(object component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsReadOnly
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override Type ComponentType
|
||||
{
|
||||
get { return this.GetType(); }
|
||||
}
|
||||
|
||||
public override Type PropertyType
|
||||
{
|
||||
get { return this.propertyValue != null ? this.propertyValue.GetType() : typeof(string); }
|
||||
}
|
||||
|
||||
public override object GetValue(object component)
|
||||
{
|
||||
return this.propertyValue;
|
||||
}
|
||||
|
||||
public override void ResetValue(object component)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetValue(object component, object value)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ShouldSerializeValue(object component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDisplayAttributesIfNecessary();
|
||||
|
||||
if (this.displayName != null || this.displayNameKey != null)
|
||||
{
|
||||
if (this.displayName == null)
|
||||
{
|
||||
this.displayName = SR.Keys.GetString(this.displayNameKey);
|
||||
}
|
||||
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
return base.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDisplayAttributesIfNecessary();
|
||||
|
||||
if (this.description != null || this.descriptionKey != null)
|
||||
{
|
||||
if (this.description == null)
|
||||
{
|
||||
this.description = SR.Keys.GetString(this.descriptionKey);
|
||||
}
|
||||
|
||||
return this.description;
|
||||
}
|
||||
|
||||
return base.Description;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void InitializeDisplayAttributesIfNecessary()
|
||||
{
|
||||
if (this.initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
DisplayNameDescriptionAttribute displayNameDescriptionAttribute =
|
||||
Attributes[typeof(DisplayNameDescriptionAttribute)] as DisplayNameDescriptionAttribute;
|
||||
if (displayNameDescriptionAttribute != null)
|
||||
{
|
||||
this.displayNameKey = displayNameDescriptionAttribute.DisplayName;
|
||||
this.descriptionKey = displayNameDescriptionAttribute.Description;
|
||||
if (this.descriptionKey == null)
|
||||
{
|
||||
this.descriptionKey = this.displayNameKey;
|
||||
}
|
||||
}
|
||||
|
||||
DisplayOrderAttribute displayOrderAttribute =
|
||||
Attributes[typeof(DisplayOrderAttribute)] as DisplayOrderAttribute;
|
||||
if (displayOrderAttribute != null)
|
||||
{
|
||||
this.displayOrder = displayOrderAttribute.DisplayOrder;
|
||||
}
|
||||
|
||||
ShowInToolTipAttribute showInToolTipAttribute =
|
||||
Attributes[typeof(ShowInToolTipAttribute)] as ShowInToolTipAttribute;
|
||||
if (showInToolTipAttribute != null)
|
||||
{
|
||||
this.isLongString = showInToolTipAttribute.LongString;
|
||||
this.showInTooltip = showInToolTipAttribute.Value;
|
||||
} else
|
||||
{
|
||||
this.showInTooltip = false;
|
||||
}
|
||||
}
|
||||
|
||||
#region Private members
|
||||
|
||||
private object propertyValue;
|
||||
private string displayName;
|
||||
private string displayNameKey;
|
||||
private string description;
|
||||
private string descriptionKey;
|
||||
private int displayOrder = Int32.MaxValue;
|
||||
private PropertyDescriptor baseProperty;
|
||||
private bool isLongString;
|
||||
private bool initialized;
|
||||
private bool showInTooltip;
|
||||
|
||||
#endregion
|
||||
|
||||
#region OrderComparer
|
||||
|
||||
internal sealed class OrderComparer : IComparer
|
||||
{
|
||||
int IComparer.Compare(object x, object y)
|
||||
{
|
||||
return Compare(x as PropertyValue, y as PropertyValue);
|
||||
}
|
||||
|
||||
private static int Compare(PropertyValue x, PropertyValue y)
|
||||
{
|
||||
if (x.IsLongString != y.IsLongString)
|
||||
{
|
||||
return x.IsLongString ? 1 : -1;
|
||||
}
|
||||
|
||||
int orderOfX = x != null ? x.DisplayOrder : Int32.MaxValue - 1;
|
||||
int orderOfY = y != null ? y.DisplayOrder : Int32.MaxValue - 1;
|
||||
|
||||
return orderOfX - orderOfY;
|
||||
}
|
||||
|
||||
public static readonly OrderComparer Default = new OrderComparer();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Collections;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses ShowPlan XML objects derived from RelOpBaseType type
|
||||
/// </summary>
|
||||
internal class RelOpBaseTypeParser : XmlPlanParser
|
||||
{
|
||||
/// <summary>
|
||||
/// This function doesn't do anything. It simply returns the parent node
|
||||
/// passed it.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentItem">Parent item.</param>
|
||||
/// <param name="parentNode">Parent node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
/// <returns>The node that corresponds to the item being parsed.</returns>
|
||||
public override Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates children items of the item being parsed.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public override IEnumerable GetChildren(object parsedItem)
|
||||
{
|
||||
PropertyDescriptor relOpProperty = TypeDescriptor.GetProperties(parsedItem)["RelOp"];
|
||||
if (relOpProperty != null)
|
||||
{
|
||||
object value = relOpProperty.GetValue(parsedItem);
|
||||
if (value != null)
|
||||
{
|
||||
if (value is IEnumerable)
|
||||
{
|
||||
foreach (object item in (IEnumerable)value)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
protected RelOpBaseTypeParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static RelOpBaseTypeParser relOpBaseTypeParser = null;
|
||||
public static RelOpBaseTypeParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (relOpBaseTypeParser == null)
|
||||
{
|
||||
relOpBaseTypeParser = new RelOpBaseTypeParser();
|
||||
}
|
||||
return relOpBaseTypeParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,733 @@
|
||||
//
|
||||
// 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.Text;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
internal sealed class RelOpTypeParser : XmlPlanParser
|
||||
{
|
||||
#region Constants
|
||||
private const string OPERATION_INDEX_DELETE = "IndexDelete";
|
||||
private const string OPERATION_CLUSTERED_INDEX_DELETE = "ClusteredIndexDelete";
|
||||
private const string OPERATION_COLUMNSTORE_INDEX_DELETE = "ColumnstoreIndexDelete";
|
||||
|
||||
private const string OPERATION_INDEX_INSERT = "IndexInsert";
|
||||
private const string OPERATION_CLUSTERED_INDEX_INSERT = "ClusteredIndexInsert";
|
||||
private const string OPERATION_COLUMNSTORE_INDEX_INSERT = "ColumnstoreIndexInsert";
|
||||
|
||||
private const string OPERATION_INDEX_MERGE = "IndexMerge";
|
||||
private const string OPERATION_CLUSTERED_INDEX_MERGE = "ClusteredIndexMerge";
|
||||
private const string OPERATION_COLUMNSTORE_INDEX_MERGE = "ColumnstoreIndexMerge";
|
||||
|
||||
private const string OPERATION_INDEX_SCAN = "IndexScan";
|
||||
private const string OPERATION_CLUSTERED_INDEX_SCAN = "ClusteredIndexScan";
|
||||
private const string OPERATION_COLUMNSTORE_INDEX_SCAN = "ColumnstoreIndexScan";
|
||||
|
||||
private const string OPERATION_INDEX_UPDATE = "IndexUpdate";
|
||||
private const string OPERATION_CLUSTERED_INDEX_UPDATE = "ClusteredIndexUpdate";
|
||||
private const string OPERATION_COLUMNSTORE_INDEX_UPDATE = "ColumnstoreIndexUpdate";
|
||||
|
||||
private const string OBJECT_NODE = "Object";
|
||||
private const string STORAGE_PROPERTY = "Storage";
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates new node and adds it to the graph.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentItem">Parent item.</param>
|
||||
/// <param name="parentNode">Parent node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
/// <returns>The node that corresponds to the item being parsed.</returns>
|
||||
public override Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
return NewNode(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates children items of the item being parsed.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public override IEnumerable GetChildren(object parsedItem)
|
||||
{
|
||||
RelOpType item = parsedItem as RelOpType;
|
||||
if (item.Item != null)
|
||||
{
|
||||
yield return item.Item;
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines Operation that corresponds to the object being parsed.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Operation that corresponds to the node.</returns>
|
||||
protected override Operation GetNodeOperation(Node node)
|
||||
{
|
||||
object physicalOpType = node["PhysicalOp"];
|
||||
object logicalOpType = node["LogicalOp"];
|
||||
|
||||
if (physicalOpType == null || logicalOpType == null)
|
||||
{
|
||||
throw new FormatException(SR.Keys.UnknownShowPlanSource);
|
||||
}
|
||||
|
||||
string physicalOpTypeName = physicalOpType.ToString();
|
||||
string logicalOpTypeName = logicalOpType.ToString();
|
||||
|
||||
// SQLBU# 434739: Custom description and icons for KeyLookup operation:
|
||||
//
|
||||
// SQL Server 2005 doesnt expose 'KeyLookup' operations as thier own type,
|
||||
// instead they indicate Bookmark operations as a 'ClusteredIndexSeek' op
|
||||
// that is having Lookup=true. Users have to select the actual node to tell
|
||||
// if a ClusteredIndexSeek is an actual bookmark operation or not.
|
||||
//
|
||||
// Our request for having engine expose the Bookmark operation as its own type,
|
||||
// instead of exposing it as a 'ClusteredIndexSeek' cannot be addressed by
|
||||
// engine in SP2 timeframe (reasons include compatibility on published showplanxml.xsd
|
||||
// schema as well as amount of changes in components that consume the xml showplan)
|
||||
//
|
||||
// For SP2 timeframe the solution is to do an aesthetic only change:
|
||||
// SSMS interprets the xml showplan and provides custom icons and descriptions
|
||||
// for a new operation: 'KeyLookup', that is getting documented in BOL.
|
||||
const string operationClusteredIndexSeek = "ClusteredIndexSeek";
|
||||
const string operationKeyLookup = "KeyLookup";
|
||||
|
||||
object lookup = node["Lookup"];
|
||||
if ((lookup != null) && (lookup is System.Boolean))
|
||||
{
|
||||
if (Convert.ToBoolean(lookup) == true)
|
||||
{
|
||||
if (0 == string.Compare(physicalOpTypeName, operationClusteredIndexSeek, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
physicalOpTypeName = operationKeyLookup;
|
||||
}
|
||||
if (0 == string.Compare(logicalOpTypeName, operationClusteredIndexSeek, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logicalOpTypeName = operationKeyLookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For index scans, Storage property should be read from this node.
|
||||
* Otherwise, for DML operations, Storage property should be read from this node's child "Object" element.
|
||||
*/
|
||||
if (0 == string.Compare(physicalOpTypeName, OPERATION_INDEX_SCAN, StringComparison.OrdinalIgnoreCase) ||
|
||||
0 == string.Compare(physicalOpTypeName, OPERATION_CLUSTERED_INDEX_SCAN, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
object storage = node[STORAGE_PROPERTY];
|
||||
if ((storage != null) && (storage.Equals(StorageType.ColumnStore)))
|
||||
{
|
||||
physicalOpTypeName = OPERATION_COLUMNSTORE_INDEX_SCAN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpandableObjectWrapper objectWrapper = (ExpandableObjectWrapper)node[OBJECT_NODE];
|
||||
if (objectWrapper != null)
|
||||
{
|
||||
PropertyValue storagePropertyValue = (PropertyValue)objectWrapper.Properties[STORAGE_PROPERTY];
|
||||
|
||||
/*
|
||||
* If object's storage is of type Storage.Columnstore,
|
||||
* PhysicalOperations should be updated to their columnstore counterparts.
|
||||
*/
|
||||
if (storagePropertyValue != null && ((storagePropertyValue).Value.Equals(StorageType.ColumnStore)))
|
||||
{
|
||||
if (0 == string.Compare(physicalOpTypeName, OPERATION_INDEX_DELETE, StringComparison.OrdinalIgnoreCase) ||
|
||||
0 == string.Compare(physicalOpTypeName, OPERATION_CLUSTERED_INDEX_DELETE, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
physicalOpTypeName = OPERATION_COLUMNSTORE_INDEX_DELETE;
|
||||
}
|
||||
else if (0 == string.Compare(physicalOpTypeName, OPERATION_INDEX_INSERT, StringComparison.OrdinalIgnoreCase) ||
|
||||
0 == string.Compare(physicalOpTypeName, OPERATION_CLUSTERED_INDEX_INSERT, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
physicalOpTypeName = OPERATION_COLUMNSTORE_INDEX_INSERT;
|
||||
}
|
||||
else if (0 == string.Compare(physicalOpTypeName, OPERATION_INDEX_MERGE, StringComparison.OrdinalIgnoreCase) ||
|
||||
0 == string.Compare(physicalOpTypeName, OPERATION_CLUSTERED_INDEX_MERGE, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
physicalOpTypeName = OPERATION_COLUMNSTORE_INDEX_MERGE;
|
||||
}
|
||||
else if (0 == string.Compare(physicalOpTypeName, OPERATION_INDEX_UPDATE, StringComparison.OrdinalIgnoreCase) ||
|
||||
0 == string.Compare(physicalOpTypeName, OPERATION_CLUSTERED_INDEX_UPDATE, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
physicalOpTypeName = OPERATION_COLUMNSTORE_INDEX_UPDATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Operation physicalOp = OperationTable.GetPhysicalOperation(physicalOpTypeName);
|
||||
Operation logicalOp = OperationTable.GetLogicalOperation(logicalOpTypeName);
|
||||
|
||||
Operation resultOp = logicalOp != null && logicalOp.Image != null && logicalOp.Description != null
|
||||
? logicalOp : physicalOp;
|
||||
|
||||
node.LogicalOpUnlocName = logicalOpTypeName;
|
||||
node.PhysicalOpUnlocName = physicalOpTypeName;
|
||||
node["PhysicalOp"] = physicalOp.DisplayName;
|
||||
node["LogicalOp"] = logicalOp.DisplayName;
|
||||
|
||||
Debug.Assert(logicalOp.DisplayName != null);
|
||||
Debug.Assert(physicalOp.DisplayName != null);
|
||||
Debug.Assert(resultOp.Description != null);
|
||||
Debug.Assert(resultOp.Image != null);
|
||||
|
||||
return resultOp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines node subtree cost from existing node properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Node subtree cost.</returns>
|
||||
protected override double GetNodeSubtreeCost(Node node)
|
||||
{
|
||||
object value = node["PDWAccumulativeCost"] ?? node["EstimatedTotalSubtreeCost"];
|
||||
return value != null ? Convert.ToDouble(value, CultureInfo.CurrentCulture) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates node special properties such as Operator, Cost, SubtreeCost.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
public override void ParseProperties(object parsedItem, PropertyDescriptorCollection targetPropertyBag, NodeBuilderContext context)
|
||||
{
|
||||
base.ParseProperties(parsedItem, targetPropertyBag, context);
|
||||
|
||||
RelOpType item = parsedItem as RelOpType;
|
||||
Debug.Assert(item != null);
|
||||
|
||||
if (item.RunTimeInformation != null && item.RunTimeInformation.Length > 0)
|
||||
{
|
||||
RunTimeCounters actualRowCountCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualRowsReadCountCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualBatchCountCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualRebindsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualRewindsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualExecutionsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLocallyAggregatedRowsCountCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualElapsedTimeCounter = new RunTimeCounters { DisplayTotalCounters = false };
|
||||
RunTimeCounters actualElapsedCPUTimeCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualScansCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLogicalReadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualPhysicalReadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualPageServerReadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualReadAheadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualPageServerReadAheadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLobLogicalReadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLobPhysicalReadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLobPageServerReadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLobReadAheadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualLobPageServerReadAheadsCounter = new RunTimeCounters();
|
||||
RunTimeCounters actualInputMemoryGrantCounter = new MemGrantRunTimeCounters();
|
||||
RunTimeCounters actualOutputMemoryGrantCounter = new MemGrantRunTimeCounters();
|
||||
RunTimeCounters actualUsedMemoryGrantCounter = new RunTimeCounters();
|
||||
|
||||
RunTimeCounters hpcKernelElapsedUsCounter = new RunTimeCounters();
|
||||
RunTimeCounters hpcRowCountCounter = new RunTimeCounters();
|
||||
RunTimeCounters hpcHostToDeviceBytesCounter = new RunTimeCounters();
|
||||
RunTimeCounters hpcDeviceToHostBytesCounter = new RunTimeCounters();
|
||||
|
||||
ExpandableObjectWrapper actualTimeStatsObjWrapper = new ExpandableObjectWrapper();
|
||||
ExpandableObjectWrapper actualIOStatsObjWrapper = new ExpandableObjectWrapper();
|
||||
ExpandableObjectWrapper actualMemoryGrantStatsObjWrapper = new ExpandableObjectWrapper();
|
||||
|
||||
String actualExecutionModeValue = String.Empty;
|
||||
String actualJoinTypeValue = String.Empty;
|
||||
bool actualIsInterleavedExecuted = false;
|
||||
|
||||
foreach (RunTimeInformationTypeRunTimeCountersPerThread counter in item.RunTimeInformation)
|
||||
{
|
||||
if (counter.BrickIdSpecified)
|
||||
{
|
||||
actualRowCountCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualRows);
|
||||
actualRebindsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualRebinds);
|
||||
actualRewindsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualRewinds);
|
||||
actualExecutionsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualExecutions);
|
||||
actualLocallyAggregatedRowsCountCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLocallyAggregatedRows);
|
||||
|
||||
if (counter.ActualElapsedmsSpecified)
|
||||
{
|
||||
actualElapsedTimeCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualElapsedms);
|
||||
}
|
||||
|
||||
if (counter.ActualCPUmsSpecified)
|
||||
{
|
||||
actualElapsedCPUTimeCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualCPUms);
|
||||
}
|
||||
|
||||
if (counter.ActualScansSpecified)
|
||||
{
|
||||
actualScansCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualScans);
|
||||
}
|
||||
|
||||
if (counter.ActualLogicalReadsSpecified)
|
||||
{
|
||||
actualLogicalReadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLogicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualPhysicalReadsSpecified)
|
||||
{
|
||||
actualPhysicalReadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualPhysicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualPageServerReadsSpecified)
|
||||
{
|
||||
actualPageServerReadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualPageServerReads);
|
||||
}
|
||||
|
||||
if (counter.ActualReadAheadsSpecified)
|
||||
{
|
||||
actualReadAheadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualPageServerReadAheadsSpecified)
|
||||
{
|
||||
actualPageServerReadAheadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualPageServerReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobLogicalReadsSpecified)
|
||||
{
|
||||
actualLobLogicalReadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLobLogicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobPhysicalReadsSpecified)
|
||||
{
|
||||
actualLobPhysicalReadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLobPhysicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobPageServerReadsSpecified)
|
||||
{
|
||||
actualLobPageServerReadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLobPageServerReads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobReadAheadsSpecified)
|
||||
{
|
||||
actualLobReadAheadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLobReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobPageServerReadAheadsSpecified)
|
||||
{
|
||||
actualLobPageServerReadAheadsCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualLobPageServerReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualRowsReadSpecified)
|
||||
{
|
||||
actualRowsReadCountCounter.AddCounter(counter.Thread, counter.BrickId, counter.ActualRowsRead);
|
||||
}
|
||||
|
||||
if (counter.BatchesSpecified)
|
||||
{
|
||||
actualBatchCountCounter.AddCounter(counter.Thread, counter.BrickId, counter.Batches);
|
||||
}
|
||||
|
||||
if (counter.HpcRowCountSpecified)
|
||||
{
|
||||
hpcRowCountCounter.AddCounter(counter.Thread, counter.BrickId, counter.HpcRowCount);
|
||||
}
|
||||
|
||||
if (counter.HpcKernelElapsedUsSpecified)
|
||||
{
|
||||
hpcKernelElapsedUsCounter.AddCounter(counter.Thread, counter.BrickId, counter.HpcKernelElapsedUs);
|
||||
}
|
||||
|
||||
if (counter.HpcHostToDeviceBytesSpecified)
|
||||
{
|
||||
hpcHostToDeviceBytesCounter.AddCounter(counter.Thread, counter.BrickId, counter.HpcHostToDeviceBytes);
|
||||
}
|
||||
|
||||
if (counter.HpcDeviceToHostBytesSpecified)
|
||||
{
|
||||
hpcDeviceToHostBytesCounter.AddCounter(counter.Thread, counter.BrickId, counter.HpcDeviceToHostBytes);
|
||||
}
|
||||
|
||||
if (counter.InputMemoryGrantSpecified)
|
||||
{
|
||||
actualInputMemoryGrantCounter.AddCounter(counter.Thread, counter.BrickId, counter.InputMemoryGrant);
|
||||
}
|
||||
|
||||
if (counter.OutputMemoryGrantSpecified)
|
||||
{
|
||||
actualOutputMemoryGrantCounter.AddCounter(counter.Thread, counter.BrickId, counter.OutputMemoryGrant);
|
||||
}
|
||||
|
||||
if (counter.UsedMemoryGrantSpecified)
|
||||
{
|
||||
actualUsedMemoryGrantCounter.AddCounter(counter.Thread, counter.BrickId, counter.UsedMemoryGrant);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actualRowCountCounter.AddCounter(counter.Thread, counter.ActualRows);
|
||||
actualRebindsCounter.AddCounter(counter.Thread, counter.ActualRebinds);
|
||||
actualRewindsCounter.AddCounter(counter.Thread, counter.ActualRewinds);
|
||||
actualExecutionsCounter.AddCounter(counter.Thread, counter.ActualExecutions);
|
||||
actualLocallyAggregatedRowsCountCounter.AddCounter(counter.Thread, counter.ActualLocallyAggregatedRows);
|
||||
|
||||
if (counter.ActualElapsedmsSpecified)
|
||||
{
|
||||
actualElapsedTimeCounter.AddCounter(counter.Thread, counter.ActualElapsedms);
|
||||
}
|
||||
|
||||
if (counter.ActualCPUmsSpecified)
|
||||
{
|
||||
actualElapsedCPUTimeCounter.AddCounter(counter.Thread, counter.ActualCPUms);
|
||||
}
|
||||
|
||||
if (counter.ActualScansSpecified)
|
||||
{
|
||||
actualScansCounter.AddCounter(counter.Thread, counter.ActualScans);
|
||||
}
|
||||
|
||||
if (counter.ActualLogicalReadsSpecified)
|
||||
{
|
||||
actualLogicalReadsCounter.AddCounter(counter.Thread, counter.ActualLogicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualPhysicalReadsSpecified)
|
||||
{
|
||||
actualPhysicalReadsCounter.AddCounter(counter.Thread, counter.ActualPhysicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualPageServerReadsSpecified)
|
||||
{
|
||||
actualPageServerReadsCounter.AddCounter(counter.Thread, counter.ActualPageServerReads);
|
||||
}
|
||||
|
||||
if (counter.ActualReadAheadsSpecified)
|
||||
{
|
||||
actualReadAheadsCounter.AddCounter(counter.Thread, counter.ActualReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualPageServerReadAheadsSpecified)
|
||||
{
|
||||
actualPageServerReadAheadsCounter.AddCounter(counter.Thread, counter.ActualPageServerReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobLogicalReadsSpecified)
|
||||
{
|
||||
actualLobLogicalReadsCounter.AddCounter(counter.Thread, counter.ActualLobLogicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobPhysicalReadsSpecified)
|
||||
{
|
||||
actualLobPhysicalReadsCounter.AddCounter(counter.Thread, counter.ActualLobPhysicalReads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobPageServerReadsSpecified)
|
||||
{
|
||||
actualLobPageServerReadsCounter.AddCounter(counter.Thread, counter.ActualLobPageServerReads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobReadAheadsSpecified)
|
||||
{
|
||||
actualLobReadAheadsCounter.AddCounter(counter.Thread, counter.ActualLobReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualLobPageServerReadAheadsSpecified)
|
||||
{
|
||||
actualLobPageServerReadAheadsCounter.AddCounter(counter.Thread, counter.ActualLobPageServerReadAheads);
|
||||
}
|
||||
|
||||
if (counter.ActualRowsReadSpecified)
|
||||
{
|
||||
actualRowsReadCountCounter.AddCounter(counter.Thread, counter.ActualRowsRead);
|
||||
}
|
||||
|
||||
if (counter.BatchesSpecified)
|
||||
{
|
||||
actualBatchCountCounter.AddCounter(counter.Thread, counter.Batches);
|
||||
}
|
||||
|
||||
if (counter.HpcRowCountSpecified)
|
||||
{
|
||||
hpcRowCountCounter.AddCounter(counter.Thread, counter.HpcRowCount);
|
||||
}
|
||||
|
||||
if (counter.HpcKernelElapsedUsSpecified)
|
||||
{
|
||||
hpcKernelElapsedUsCounter.AddCounter(counter.Thread, counter.HpcKernelElapsedUs);
|
||||
}
|
||||
|
||||
if (counter.HpcHostToDeviceBytesSpecified)
|
||||
{
|
||||
hpcHostToDeviceBytesCounter.AddCounter(counter.Thread, counter.HpcHostToDeviceBytes);
|
||||
}
|
||||
|
||||
if (counter.HpcDeviceToHostBytesSpecified)
|
||||
{
|
||||
hpcDeviceToHostBytesCounter.AddCounter(counter.Thread, counter.HpcDeviceToHostBytes);
|
||||
}
|
||||
|
||||
if (counter.InputMemoryGrantSpecified)
|
||||
{
|
||||
actualInputMemoryGrantCounter.AddCounter(counter.Thread, counter.InputMemoryGrant);
|
||||
}
|
||||
|
||||
if (counter.OutputMemoryGrantSpecified)
|
||||
{
|
||||
actualOutputMemoryGrantCounter.AddCounter(counter.Thread, counter.OutputMemoryGrant);
|
||||
}
|
||||
|
||||
if (counter.UsedMemoryGrantSpecified)
|
||||
{
|
||||
actualUsedMemoryGrantCounter.AddCounter(counter.Thread, counter.UsedMemoryGrant);
|
||||
}
|
||||
}
|
||||
|
||||
if (counter.ActualExecutions > 0)
|
||||
{
|
||||
actualExecutionModeValue = Enum.GetName(typeof(ExecutionModeType), counter.ActualExecutionMode);
|
||||
}
|
||||
|
||||
if (counter.ActualJoinTypeSpecified)
|
||||
{
|
||||
actualJoinTypeValue = Enum.GetName(typeof(PhysicalOpType), counter.ActualJoinType);
|
||||
}
|
||||
|
||||
if (counter.IsInterleavedExecuted)
|
||||
{
|
||||
actualIsInterleavedExecuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (actualIsInterleavedExecuted)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("IsInterleavedExecuted", actualIsInterleavedExecuted));
|
||||
}
|
||||
|
||||
// Create localizable properties and add them to the property bag
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualRows", actualRowCountCounter));
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualBatches", actualBatchCountCounter));
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualRebinds", actualRebindsCounter));
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualRewinds", actualRewindsCounter));
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualExecutions", actualExecutionsCounter));
|
||||
|
||||
if (actualRowsReadCountCounter.TotalCounters > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualRowsRead", actualRowsReadCountCounter));
|
||||
}
|
||||
|
||||
if (actualLocallyAggregatedRowsCountCounter.TotalCounters > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualLocallyAggregatedRows", actualLocallyAggregatedRowsCountCounter));
|
||||
}
|
||||
|
||||
if (hpcRowCountCounter.TotalCounters > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("HpcRowCount", hpcRowCountCounter));
|
||||
}
|
||||
|
||||
if (hpcKernelElapsedUsCounter.TotalCounters > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("HpcKernelElapsedUs", hpcKernelElapsedUsCounter));
|
||||
}
|
||||
|
||||
if (hpcHostToDeviceBytesCounter.TotalCounters > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("HpcHostToDeviceBytes", hpcHostToDeviceBytesCounter));
|
||||
}
|
||||
|
||||
if (hpcDeviceToHostBytesCounter.TotalCounters > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("HpcDeviceToHostBytes", hpcDeviceToHostBytesCounter));
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(actualExecutionModeValue))
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualExecutionMode", actualExecutionModeValue));
|
||||
}
|
||||
|
||||
|
||||
if (!String.IsNullOrEmpty(actualJoinTypeValue))
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualJoinType", actualJoinTypeValue));
|
||||
}
|
||||
|
||||
// Populate the "Actual Time Statistics" property if applicable
|
||||
// Nested properties include "Actual Elapsed Time" and "Actual Elapsed CPU Time"
|
||||
if (actualElapsedTimeCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualTimeStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualElapsedms", actualElapsedTimeCounter));
|
||||
}
|
||||
|
||||
if (actualElapsedCPUTimeCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualTimeStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualCPUms", actualElapsedCPUTimeCounter));
|
||||
}
|
||||
|
||||
if (actualTimeStatsObjWrapper.Properties.Count > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualTimeStatistics", actualTimeStatsObjWrapper));
|
||||
}
|
||||
|
||||
// Populate the "Actual IO Statistics" property if applicable
|
||||
// Nested properties include "Scan" and "Read" properties.
|
||||
if (actualScansCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualScans", actualScansCounter));
|
||||
}
|
||||
|
||||
if (actualLogicalReadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualLogicalReads", actualLogicalReadsCounter));
|
||||
}
|
||||
|
||||
if (actualPhysicalReadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualPhysicalReads", actualPhysicalReadsCounter));
|
||||
}
|
||||
|
||||
if (actualPageServerReadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualPageServerReads", actualPageServerReadsCounter));
|
||||
}
|
||||
|
||||
if (actualReadAheadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualReadAheads", actualReadAheadsCounter));
|
||||
}
|
||||
|
||||
if (actualPageServerReadAheadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualPageServerReadAheads", actualPageServerReadAheadsCounter));
|
||||
}
|
||||
|
||||
if (actualLobLogicalReadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualLobLogicalReads", actualLobLogicalReadsCounter));
|
||||
}
|
||||
|
||||
if (actualLobPhysicalReadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualLobPhysicalReads", actualLobPhysicalReadsCounter));
|
||||
}
|
||||
|
||||
if (actualLobPageServerReadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualLobPageServerReads", actualLobPageServerReadsCounter));
|
||||
}
|
||||
|
||||
if (actualLobReadAheadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualLobReadAheads", actualLobReadAheadsCounter));
|
||||
}
|
||||
|
||||
if (actualLobPageServerReadAheadsCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualIOStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("ActualLobPageServerReadAheads", actualLobPageServerReadAheadsCounter));
|
||||
}
|
||||
|
||||
if (actualIOStatsObjWrapper.Properties.Count > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualIOStatistics", actualIOStatsObjWrapper));
|
||||
}
|
||||
|
||||
// Populate ActualMemoryGrantStats
|
||||
if (actualInputMemoryGrantCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualMemoryGrantStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("InputMemoryGrant", actualInputMemoryGrantCounter));
|
||||
}
|
||||
|
||||
if (actualOutputMemoryGrantCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualMemoryGrantStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("OutputMemoryGrant", actualOutputMemoryGrantCounter));
|
||||
}
|
||||
|
||||
if (actualUsedMemoryGrantCounter.NumOfCounters > 0)
|
||||
{
|
||||
actualMemoryGrantStatsObjWrapper.Properties.Add(PropertyFactory.CreateProperty("UsedMemoryGrant", actualUsedMemoryGrantCounter));
|
||||
}
|
||||
|
||||
if (actualMemoryGrantStatsObjWrapper.Properties.Count > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("ActualMemoryGrantStats", actualMemoryGrantStatsObjWrapper));
|
||||
}
|
||||
}
|
||||
|
||||
// Decompose RunTimePartitionSummary and add them individually.
|
||||
// Otherwise, the properties will show up as nested in the property window.
|
||||
if (item.RunTimePartitionSummary != null && item.RunTimePartitionSummary.PartitionsAccessed != null)
|
||||
{
|
||||
RunTimePartitionSummaryTypePartitionsAccessed partitions = item.RunTimePartitionSummary.PartitionsAccessed;
|
||||
|
||||
// Create localizable properties and add them to the property bag
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("PartitionCount", partitions.PartitionCount));
|
||||
|
||||
if (partitions.PartitionRange != null && partitions.PartitionRange.Length > 0)
|
||||
{
|
||||
targetPropertyBag.Add(PropertyFactory.CreateProperty("PartitionsAccessed", GetPartitionRangeString(partitions.PartitionRange)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to format partition range string.
|
||||
/// </summary>
|
||||
/// <param name="ranges">Partition ranges</param>
|
||||
/// <returns>property string</returns>
|
||||
private static string GetPartitionRangeString(RunTimePartitionSummaryTypePartitionsAccessedPartitionRange[] ranges)
|
||||
{
|
||||
Debug.Assert(ranges != null);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
string separator = CultureInfo.CurrentCulture.TextInfo.ListSeparator;
|
||||
for (int i = 0; i < ranges.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
stringBuilder.Append(separator);
|
||||
}
|
||||
|
||||
RunTimePartitionSummaryTypePartitionsAccessedPartitionRange range = ranges[i];
|
||||
if (range.Start == range.End)
|
||||
{
|
||||
// The range is a single number
|
||||
stringBuilder.Append(range.Start);
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0}..{1}", range.Start, range.End);
|
||||
}
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
private RelOpTypeParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static RelOpTypeParser relOpTypeParser = null;
|
||||
public static RelOpTypeParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (relOpTypeParser == null)
|
||||
{
|
||||
relOpTypeParser = new RelOpTypeParser();
|
||||
}
|
||||
return relOpTypeParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
//
|
||||
// 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.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// RunTimeCounters class stores RunTimeCountersPerThread information
|
||||
/// </summary>
|
||||
[TypeConverterAttribute(typeof(ExpandableObjectConverter))]
|
||||
internal class RunTimeCounters : ICustomTypeDescriptor
|
||||
{
|
||||
#region Inner classes
|
||||
|
||||
protected struct Counter
|
||||
{
|
||||
public int Thread;
|
||||
public int BrickId;
|
||||
public bool BrickIdSpecified;
|
||||
public ulong Value;
|
||||
|
||||
public Counter(int thread, ulong value)
|
||||
{
|
||||
Thread = thread;
|
||||
BrickIdSpecified = false;
|
||||
BrickId = 0;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Counter(int thread, int brickId, ulong value)
|
||||
{
|
||||
Thread = thread;
|
||||
BrickIdSpecified = true;
|
||||
BrickId = brickId;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
ulong totalCounters;
|
||||
ulong maxCounter;
|
||||
protected List<Counter> counters = new List<Counter>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public RunTimeCounters()
|
||||
{
|
||||
maxCounter = 0;
|
||||
DisplayTotalCounters = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public methods and properties
|
||||
|
||||
public void AddCounter(int thread, ulong counterValue)
|
||||
{
|
||||
this.counters.Add(new Counter(thread, counterValue));
|
||||
this.totalCounters += counterValue;
|
||||
if (counterValue > maxCounter)
|
||||
{
|
||||
maxCounter = counterValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCounter(int thread, int brickId, ulong counterValue)
|
||||
{
|
||||
this.counters.Add(new Counter(thread, brickId, counterValue));
|
||||
this.totalCounters += counterValue;
|
||||
if (counterValue > maxCounter)
|
||||
{
|
||||
maxCounter = counterValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sum of values passed to AddCounter
|
||||
/// </summary>
|
||||
public ulong TotalCounters
|
||||
{
|
||||
get { return this.totalCounters; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// max value passed to AddCounter
|
||||
/// </summary>
|
||||
public ulong MaxCounter
|
||||
{
|
||||
get { return this.maxCounter; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// if true, display TotalCounters as string representation, otherwise display MaxCounter
|
||||
/// </summary>
|
||||
public bool DisplayTotalCounters
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of Counter objects added to counters list
|
||||
/// Does not represent the calculated total count.
|
||||
/// </summary>
|
||||
public int NumOfCounters
|
||||
{
|
||||
get { return this.counters.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string representation of RunTimeCounters
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
// display max counter value as the string representation of this class for specific properties, for ex ActualElapsedms
|
||||
// for other properties, display total counter value
|
||||
if (DisplayTotalCounters)
|
||||
{
|
||||
return TotalCounters.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MaxCounter.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICustomTypeDescriptor
|
||||
|
||||
AttributeCollection ICustomTypeDescriptor.GetAttributes()
|
||||
{
|
||||
return TypeDescriptor.GetAttributes(GetType());
|
||||
}
|
||||
|
||||
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
|
||||
{
|
||||
return TypeDescriptor.GetDefaultEvent(GetType());
|
||||
}
|
||||
|
||||
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
|
||||
{
|
||||
return TypeDescriptor.GetDefaultProperty(GetType());
|
||||
}
|
||||
|
||||
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
|
||||
{
|
||||
return TypeDescriptor.GetEditor(GetType(), editorBaseType);
|
||||
}
|
||||
|
||||
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
|
||||
{
|
||||
return TypeDescriptor.GetEvents(GetType());
|
||||
}
|
||||
|
||||
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
|
||||
{
|
||||
return TypeDescriptor.GetEvents(GetType(), attributes );
|
||||
}
|
||||
|
||||
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor propertyDescriptor)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
|
||||
{
|
||||
PropertyDescriptor[] propertiesDescriptors = new PropertyDescriptor[this.counters.Count];
|
||||
string description = SR.Keys.PerThreadCounterDescription;
|
||||
|
||||
if (this.counters.Count == 1)
|
||||
{
|
||||
PropertyValue property;
|
||||
if (this.counters[0].BrickIdSpecified)
|
||||
{
|
||||
property = new PropertyValue(SR.RuntimeCounterThreadOnInstance(this.counters[0].Thread, this.counters[0].BrickId), this.counters[0].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
property = new PropertyValue(SR.RuntimeCounterThreadAll, this.counters[0].Value);
|
||||
}
|
||||
property.SetDisplayNameAndDescription(property.Name, description);
|
||||
propertiesDescriptors[0] = property;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i=0; i<this.counters.Count; i++)
|
||||
{
|
||||
PropertyValue property;
|
||||
if (this.counters[i].BrickIdSpecified)
|
||||
{
|
||||
property = new PropertyValue(SR.RuntimeCounterThreadOnInstance(this.counters[i].Thread, this.counters[i].BrickId), this.counters[i].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
property = new PropertyValue(SR.RuntimeCounterThread(this.counters[i].Thread), this.counters[i].Value);
|
||||
}
|
||||
property.SetDisplayNameAndDescription(property.Name, description);
|
||||
propertiesDescriptors[i] = property;
|
||||
}
|
||||
}
|
||||
|
||||
return new PropertyDescriptorCollection(propertiesDescriptors);
|
||||
}
|
||||
|
||||
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
|
||||
{
|
||||
return ((ICustomTypeDescriptor)this).GetProperties();
|
||||
}
|
||||
|
||||
string ICustomTypeDescriptor.GetComponentName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeConverter ICustomTypeDescriptor.GetConverter()
|
||||
{
|
||||
return TypeDescriptor.GetConverter(GetType());
|
||||
}
|
||||
|
||||
string ICustomTypeDescriptor.GetClassName()
|
||||
{
|
||||
return GetType().Name;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// derived class that overrides ToString for memory grant related properties
|
||||
/// </summary>
|
||||
[TypeConverterAttribute(typeof(ExpandableObjectConverter))]
|
||||
internal class MemGrantRunTimeCounters : RunTimeCounters
|
||||
{
|
||||
/// <summary>
|
||||
/// string representation of MemGrantRunTimeCounters
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
ulong displayValue = this.TotalCounters;
|
||||
|
||||
// if there is more than one thread/counter, memory grant from thread 0 is not used so it doesn't carry meaningful counter value and needs to ignored
|
||||
if (this.NumOfCounters > 1)
|
||||
{
|
||||
// find thread 0 counter value, note it may not be the first element in counters list
|
||||
foreach (var ct in this.counters)
|
||||
{
|
||||
if (ct.Thread == 0)
|
||||
{
|
||||
displayValue -= ct.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return displayValue.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// 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.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses stytement type ShowPlan XML nodes
|
||||
/// </summary>
|
||||
internal class StatementParser : XmlPlanParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new node and adds it to the graph.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentItem">Parent item.</param>
|
||||
/// <param name="parentNode">Parent node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
/// <returns>The node that corresponds to the item being parsed.</returns>
|
||||
public override Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
return NewNode(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines Operation that corresponds to the object being parsed.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Operation that corresponds to the node.</returns>
|
||||
protected override Operation GetNodeOperation(Node node)
|
||||
{
|
||||
object statementType = node["StatementType"];
|
||||
Operation statement = statementType != null
|
||||
? OperationTable.GetStatement(statementType.ToString())
|
||||
: Operation.Unknown;
|
||||
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines node subtree cost from existing node properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Node subtree cost.</returns>
|
||||
protected override double GetNodeSubtreeCost(Node node)
|
||||
{
|
||||
object value = node["StatementSubTreeCost"];
|
||||
return value != null ? Convert.ToDouble(value, CultureInfo.CurrentCulture) : 0;
|
||||
}
|
||||
|
||||
protected override bool ShouldParseItem(object parsedItem)
|
||||
{
|
||||
// Special case. An empty statement without QueryPlan but with
|
||||
// a UDF or StoreProc should be skipped
|
||||
StmtSimpleType statement = parsedItem as StmtSimpleType;
|
||||
if (statement != null)
|
||||
{
|
||||
// We use hidden wrapper statements for UDFs and StoredProcs
|
||||
// Hidden statements don't have any of their properties defined
|
||||
// We can use one of properties which is always set by server
|
||||
// such as StatementIdSpecified to distinguish between a real
|
||||
// statement and a hidden wrapper statement
|
||||
if (!statement.StatementIdSpecified)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// By default, the statement is parsed
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates FunctionType blocks and removes all items from UDF and StoredProc properties.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public override IEnumerable<FunctionTypeItem> ExtractFunctions(object parsedItem)
|
||||
{
|
||||
StmtSimpleType statement = parsedItem as StmtSimpleType;
|
||||
if (statement != null)
|
||||
{
|
||||
// If this is a simple statement it may have UDF and StoredProc fields
|
||||
if (statement.UDF != null)
|
||||
{
|
||||
foreach (FunctionType function in statement.UDF)
|
||||
{
|
||||
yield return new FunctionTypeItem(function, FunctionTypeItem.ItemType.Udf);
|
||||
}
|
||||
statement.UDF = null;
|
||||
}
|
||||
|
||||
if (statement.StoredProc != null)
|
||||
{
|
||||
yield return new FunctionTypeItem(statement.StoredProc, FunctionTypeItem.ItemType.StoredProcedure);
|
||||
statement.StoredProc = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is some other type of Statement. Call ExtractFunctions for all its children
|
||||
foreach (object item in GetChildren(parsedItem))
|
||||
{
|
||||
XmlPlanParser parser = XmlPlanParserFactory.GetParser(item.GetType());
|
||||
foreach (FunctionTypeItem functionItem in parser.ExtractFunctions(item))
|
||||
{
|
||||
yield return functionItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// protected constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
protected StatementParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static StatementParser statementParser = null;
|
||||
public static StatementParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (statementParser == null)
|
||||
{
|
||||
statementParser = new StatementParser();
|
||||
}
|
||||
return statementParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// 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.ShowPlan
|
||||
{
|
||||
internal class XmlPlanHierarchyParser : XmlPlanParser
|
||||
{
|
||||
/// <summary>
|
||||
/// This function doesn't do anything. It simply returns the parent node
|
||||
/// passed it.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentItem">Parent item.</param>
|
||||
/// <param name="parentNode">Parent node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
/// <returns>The node that corresponds to the item being parsed.</returns>
|
||||
public override Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts FunctionType blocks.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public override IEnumerable<FunctionTypeItem> ExtractFunctions(object parsedItem)
|
||||
{
|
||||
// Recursively call ExtractFunctions for each children.
|
||||
foreach (object item in GetChildren(parsedItem))
|
||||
{
|
||||
XmlPlanParser parser = XmlPlanParserFactory.GetParser(item.GetType());
|
||||
foreach (FunctionTypeItem functionItem in parser.ExtractFunctions(item))
|
||||
{
|
||||
yield return functionItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor prevents this object from being externally instantiated
|
||||
/// </summary>
|
||||
protected XmlPlanHierarchyParser()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singelton instance
|
||||
/// </summary>
|
||||
private static XmlPlanHierarchyParser xmlPlanHierarchyParser = null;
|
||||
public static XmlPlanHierarchyParser Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (xmlPlanHierarchyParser == null)
|
||||
{
|
||||
xmlPlanHierarchyParser = new XmlPlanHierarchyParser();
|
||||
}
|
||||
return xmlPlanHierarchyParser;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds hierarchy of Graph objects from ShowPlan XML
|
||||
/// </summary>
|
||||
internal sealed class XmlPlanNodeBuilder : INodeBuilder, IXmlBatchParser
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
public XmlPlanNodeBuilder(ShowPlanType showPlanType)
|
||||
{
|
||||
this.showPlanType = showPlanType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region INodeBuilder
|
||||
|
||||
/// <summary>
|
||||
/// Builds one or more Graphs that
|
||||
/// represnet data from the data source.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data Source.</param>
|
||||
/// <returns>An array of AnalysisServices Graph objects.</returns>
|
||||
public ShowPlanGraph[] Execute(object dataSource)
|
||||
{
|
||||
ShowPlanXML plan = dataSource as ShowPlanXML;
|
||||
if (plan == null)
|
||||
{
|
||||
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
|
||||
this.currentNodeId = 0;
|
||||
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();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IXmlBatchParser
|
||||
|
||||
/// <summary>
|
||||
/// Returns an XML string for a specific ShowPlan statement.
|
||||
/// This is used to save a plan corresponding to a particular graph control.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data source that contains the full plan.</param>
|
||||
/// <param name="statementIndex">Statement index.</param>
|
||||
/// <returns>XML string that contains execution plan for the specified statement index.</returns>
|
||||
public string GetSingleStatementXml(object dataSource, int statementIndex)
|
||||
{
|
||||
StmtBlockType newStatementBlock = GetSingleStatementObject(dataSource, statementIndex);
|
||||
|
||||
// Now make the new plan based on the existing one that contains only one statement.
|
||||
ShowPlanXML plan = ReadXmlShowPlan(dataSource);
|
||||
plan.BatchSequence = new StmtBlockType[][]
|
||||
{
|
||||
new StmtBlockType[] { newStatementBlock }
|
||||
};
|
||||
|
||||
// Serialize the new plan.
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
Serializer.Serialize(new StringWriter(stringBuilder), plan);
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns single statement block type object
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data source</param>
|
||||
/// <param name="statementIndex">Statement index in the data source</param>
|
||||
/// <returns>Single statement block type object</returns>
|
||||
public StmtBlockType GetSingleStatementObject(object dataSource, int statementIndex)
|
||||
{
|
||||
// First read the whole plan from the data source
|
||||
ShowPlanXML plan = ReadXmlShowPlan(dataSource);
|
||||
|
||||
int index = 0;
|
||||
StmtBlockType newStatementBlock = new StmtBlockType();
|
||||
|
||||
// Locate the statement for the specified index
|
||||
foreach (BaseStmtInfoType statement in EnumStatements(plan))
|
||||
{
|
||||
if (statementIndex == index++)
|
||||
{
|
||||
// This is the statement we are looking for
|
||||
newStatementBlock.Items = new BaseStmtInfoType[] { statement };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newStatementBlock.Items == null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("statementIndex");
|
||||
}
|
||||
|
||||
return newStatementBlock;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets current node Id and internally increments the Id.
|
||||
/// </summary>
|
||||
/// <returns>ID.</returns>
|
||||
internal int GetCurrentNodeId()
|
||||
{
|
||||
return ++currentNodeId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Implementation details
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes XML ShowPlan from the data source
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Data Source</param>
|
||||
/// <returns>ShowPlanXML object which is the root of deserialized plan.</returns>
|
||||
private ShowPlanXML ReadXmlShowPlan(object dataSource)
|
||||
{
|
||||
ShowPlanXML result = null;
|
||||
|
||||
string stringData = dataSource as string;
|
||||
if (stringData != null)
|
||||
{
|
||||
using (StringReader reader = new StringReader(stringData))
|
||||
{
|
||||
result = Serializer.Deserialize(reader) as ShowPlanXML;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] binaryData = dataSource as byte[];
|
||||
if (binaryData != null)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream(binaryData))
|
||||
{
|
||||
// We need to use reflection to obtain private method of XmlReader class
|
||||
// that can create a binary reader. Public XmlReader.Create does not
|
||||
// support this.
|
||||
MethodInfo createSqlReaderMethodInfo = typeof(System.Xml.XmlReader).GetMethod("CreateSqlReader", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
object[] args = new object[3] { stream, null, null };
|
||||
|
||||
using (XmlReader reader = (XmlReader)createSqlReaderMethodInfo.Invoke(null, args))
|
||||
{
|
||||
result = Serializer.Deserialize(reader) as ShowPlanXML;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null == result)
|
||||
{
|
||||
Debug.Assert(false, "Unexpected ShowPlan source = " + dataSource.GetType().ToString());
|
||||
throw new ArgumentException(SR.Keys.UnknownShowPlanSource);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates statements in XML ShowPlan. This also looks inside each statement and
|
||||
/// enumerates sub-statements found in FunctionType blocks.
|
||||
/// </summary>
|
||||
/// <param name="plan">XML ShowPlan.</param>
|
||||
/// <returns>Statements enumerator.</returns>
|
||||
private IEnumerable<BaseStmtInfoType> EnumStatements(ShowPlanXML plan)
|
||||
{
|
||||
foreach (StmtBlockType[] statementBatch in plan.BatchSequence)
|
||||
{
|
||||
foreach (StmtBlockType statementBlock in statementBatch)
|
||||
{
|
||||
ExtractFunctions(statementBlock);
|
||||
|
||||
// flatten out any statements contained within then / else clauses to make it appear as though all code paths are
|
||||
// executed sequentially, this is useful for the Live show plan case because it only displays a single show-plan instance at any given time.
|
||||
if (showPlanType == ShowPlanType.Live)
|
||||
{
|
||||
FlattenConditionClauses(statementBlock);
|
||||
}
|
||||
|
||||
foreach (BaseStmtInfoType statement in EnumStatements(statementBlock))
|
||||
{
|
||||
yield return statement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We do some special handling of the showplan graphs to flatten out control nodes. See VSTS 3657984.
|
||||
/// Essentially the problem is that the Actual showplan and the predicted show plan are treated differently.
|
||||
/// The predicted show plan shows the control node (while, if-then-else) when the actual show plans only contain a single
|
||||
/// plan per statement. This difference makes it difficult to match up the running query against the predicted showplan. Further
|
||||
/// complicating the situation is that each statement may re-use nodeIDs which violates a fundamental assumption
|
||||
/// of the LQS tool and the progress estimators. We can work-around this by flattening out the predicted show plan graph
|
||||
/// to look as a series of statements without the control structures or nesting
|
||||
/// </summary>
|
||||
private void FlattenConditionClauses(StmtBlockType statementBlock)
|
||||
{
|
||||
if (statementBlock != null && statementBlock.Items != null)
|
||||
{
|
||||
ArrayList targetStatementList = new ArrayList();
|
||||
|
||||
foreach (BaseStmtInfoType statement in statementBlock.Items)
|
||||
{
|
||||
targetStatementList.Add(statement);
|
||||
|
||||
FlattenConditionClauses(statement, targetStatementList);
|
||||
}
|
||||
|
||||
// Make a new Items array for the statement block by combining existing items and
|
||||
// new wrapper statements
|
||||
statementBlock.Items = new BaseStmtInfoType[targetStatementList.Count];
|
||||
targetStatementList.CopyTo(statementBlock.Items);
|
||||
}
|
||||
}
|
||||
|
||||
private void FlattenConditionClauses(BaseStmtInfoType statement, ArrayList targetStatementList)
|
||||
{
|
||||
// Enum statement children and genetate wrapper statements for them
|
||||
XmlPlanParser parser = XmlPlanParserFactory.GetParser(statement.GetType());
|
||||
foreach (object child in parser.GetChildren(statement))
|
||||
{
|
||||
StmtCondTypeThen stmtThen = child as StmtCondTypeThen;
|
||||
if (stmtThen != null)
|
||||
{
|
||||
//add this element and its children
|
||||
if (stmtThen.Statements != null && stmtThen.Statements.Items != null)
|
||||
{
|
||||
foreach (BaseStmtInfoType subStatement in stmtThen.Statements.Items)
|
||||
{
|
||||
targetStatementList.Add(subStatement);
|
||||
FlattenConditionClauses(subStatement, targetStatementList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
StmtCondTypeElse stmtElse = child as StmtCondTypeElse;
|
||||
if (stmtElse != null)
|
||||
{
|
||||
//add this element and its children
|
||||
if (stmtElse.Statements != null && stmtElse.Statements.Items != null)
|
||||
{
|
||||
foreach (BaseStmtInfoType subStatement in stmtElse.Statements.Items)
|
||||
{
|
||||
targetStatementList.Add(subStatement);
|
||||
FlattenConditionClauses(subStatement, targetStatementList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts UDF and StoredProc items and places each of them at the top level
|
||||
/// wrapping each of them with an empty statement.
|
||||
/// </summary>
|
||||
/// <param name="statementBlock">Statement block</param>
|
||||
private void ExtractFunctions(StmtBlockType statementBlock)
|
||||
{
|
||||
if (statementBlock != null && statementBlock.Items != null)
|
||||
{
|
||||
ArrayList targetStatementList = new ArrayList();
|
||||
|
||||
foreach (BaseStmtInfoType statement in statementBlock.Items)
|
||||
{
|
||||
targetStatementList.Add(statement);
|
||||
|
||||
ExtractFunctions(statement, targetStatementList);
|
||||
}
|
||||
|
||||
// Make a new Items array for the statement block by combining existing items and
|
||||
// new wrapper statements
|
||||
statementBlock.Items = new BaseStmtInfoType[targetStatementList.Count];
|
||||
targetStatementList.CopyTo(statementBlock.Items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts UDF and StoredProc items from a statement and adds them to a target list.
|
||||
/// </summary>
|
||||
/// <param name="statement">Statement.</param>
|
||||
/// <param name="targetStatementList">Target list to add a newly generated statement to.</param>
|
||||
private void ExtractFunctions(BaseStmtInfoType statement, ArrayList targetStatementList)
|
||||
{
|
||||
// Enum FunctionType objects and generate wrapper statements for them
|
||||
XmlPlanParser parser = XmlPlanParserFactory.GetParser(statement.GetType());
|
||||
foreach (FunctionTypeItem functionItem in parser.ExtractFunctions(statement))
|
||||
{
|
||||
StmtSimpleType subStatement = null;
|
||||
|
||||
if (functionItem.Type == FunctionTypeItem.ItemType.StoredProcedure)
|
||||
{
|
||||
subStatement = new StmtSimpleType();
|
||||
subStatement.StoredProc = functionItem.Function;
|
||||
}
|
||||
else if (functionItem.Type == FunctionTypeItem.ItemType.Udf)
|
||||
{
|
||||
subStatement = new StmtSimpleType();
|
||||
subStatement.UDF = new FunctionType[] { functionItem.Function };
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Ivalid function type");
|
||||
}
|
||||
|
||||
if (subStatement != null)
|
||||
{
|
||||
targetStatementList.Add(subStatement);
|
||||
|
||||
// Call itself recursively.
|
||||
if (functionItem.Function.Statements != null && functionItem.Function.Statements.Items != null)
|
||||
{
|
||||
foreach (BaseStmtInfoType functionStatement in functionItem.Function.Statements.Items)
|
||||
{
|
||||
ExtractFunctions(functionStatement, targetStatementList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively enumerates statements in StmtBlockType.
|
||||
/// </summary>
|
||||
/// <param name="statementBlock">Statement block (may contain multiple statements).</param>
|
||||
/// <returns>Statement enumerator.</returns>
|
||||
private IEnumerable<BaseStmtInfoType> EnumStatements(StmtBlockType statementBlock)
|
||||
{
|
||||
if (statementBlock != null && statementBlock.Items != null)
|
||||
{
|
||||
foreach (BaseStmtInfoType statement in statementBlock.Items)
|
||||
{
|
||||
yield return statement;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
#region Private members
|
||||
|
||||
private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ShowPlanXML));
|
||||
|
||||
private ShowPlanType showPlanType;
|
||||
private int currentNodeId;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for enumerating FunctionType objects
|
||||
/// </summary>
|
||||
internal sealed class FunctionTypeItem
|
||||
{
|
||||
internal enum ItemType
|
||||
{
|
||||
Unknown,
|
||||
Udf,
|
||||
StoredProcedure
|
||||
};
|
||||
|
||||
internal FunctionTypeItem(FunctionType function, ItemType type)
|
||||
{
|
||||
this.function = function;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
internal FunctionType Function
|
||||
{
|
||||
get { return this.function; }
|
||||
}
|
||||
|
||||
internal ItemType Type
|
||||
{
|
||||
get { return this.type; }
|
||||
}
|
||||
|
||||
private FunctionType function;
|
||||
private ItemType type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all Xml Execution plan node parsers.
|
||||
/// </summary>
|
||||
internal abstract class XmlPlanParser : ObjectParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a ShowPlan item and either creates a new Node or adds properties to
|
||||
/// the provided Node.
|
||||
/// </summary>
|
||||
/// <param name="item">Item being parsed.</param>
|
||||
/// <param name="parentNode">Existing node which is used as a property host or a parent for the new node.</param>
|
||||
/// <param name="context">Node builder context.</param>
|
||||
public static void Parse(object item, object parentItem, Node parentNode, NodeBuilderContext context)
|
||||
{
|
||||
XmlPlanParser parser = XmlPlanParserFactory.GetParser(item.GetType());
|
||||
|
||||
if (parser != null)
|
||||
{
|
||||
Node node = null;
|
||||
|
||||
if (parser.ShouldParseItem(item))
|
||||
{
|
||||
node = parser.GetCurrentNode(item, parentItem, parentNode, context);
|
||||
if (node != null)
|
||||
{
|
||||
// add node/statement mapping to the ShowPlanGraph
|
||||
if (context != null && context.Graph != null && !context.Graph.NodeStmtMap.ContainsKey(node))
|
||||
{
|
||||
context.Graph.NodeStmtMap.Add(node, item);
|
||||
}
|
||||
parser.ParseProperties(item, node.Properties, context);
|
||||
}
|
||||
if(parentNode == null)
|
||||
{
|
||||
context.Graph.Root = node;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
node = parentNode;
|
||||
}
|
||||
|
||||
foreach (object child in parser.GetChildren(item))
|
||||
{
|
||||
XmlPlanParser.Parse(child, item, node, context);
|
||||
}
|
||||
|
||||
if (node != parentNode)
|
||||
{
|
||||
parser.SetNodeSpecialProperties(node);
|
||||
if (parentNode != null)
|
||||
{
|
||||
parentNode.AddChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Unexpected run type = " + item.ToString());
|
||||
// Debug.LogExThrow(); {{removed from ssms}}
|
||||
throw new InvalidOperationException(SR.Keys.UnexpectedRunType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="parentItem"></param>
|
||||
/// <param name="parentNode"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Node GetCurrentNode(object item, object parentItem, Node parentNode, NodeBuilderContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates children items of the item being parsed.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public virtual IEnumerable GetChildren(object parsedItem)
|
||||
{
|
||||
return EnumerateChildren(parsedItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts FunctionType blocks.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">The item being parsed.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public virtual IEnumerable<FunctionTypeItem> ExtractFunctions(object parsedItem)
|
||||
{
|
||||
// By default - no functions
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this node should be parsed
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">ShowPlan item</param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ShouldParseItem(object parsedItem)
|
||||
{
|
||||
// All items are parsed by default
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates node special properties such as Operator, Cost, SubtreeCost.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
protected virtual void SetNodeSpecialProperties(Node node)
|
||||
{
|
||||
if (node.Operation == null)
|
||||
{
|
||||
node.Operation = GetNodeOperation(node);
|
||||
}
|
||||
|
||||
// Retrieve Subtree cost for this node
|
||||
node.SubtreeCost = GetNodeSubtreeCost(node);
|
||||
|
||||
// EstimateExecutions = EstimateRebinds + EstimateRewinds + 1
|
||||
if (node["EstimateRebinds"] != null && node["EstimateRewinds"] != null)
|
||||
{
|
||||
double estimateRebinds = (double) node["EstimateRebinds"];
|
||||
double estimateRewinds = (double) node["EstimateRewinds"];
|
||||
node["EstimateExecutions"] = estimateRebinds + estimateRewinds + 1;
|
||||
}
|
||||
|
||||
// EstimateRowsAllExecs = EstimateRows * EstimateExecutions
|
||||
double estimateRows = node["EstimateRows"] == null ? 0.0 : Convert.ToDouble(node["EstimateRows"]);
|
||||
double estimateExecutions = node["EstimateExecutions"] == null ? 0.0 : Convert.ToDouble(node["EstimateExecutions"]);
|
||||
double actualExecutions = node["ActualExecutions"] == null ? 0.0 : ((RunTimeCounters)node["ActualExecutions"]).TotalCounters;
|
||||
|
||||
//It's unlikely the total number of rows would exceed DBL_MAX = 1.8*(10^308), thus safe to not check overflow.
|
||||
node["EstimateRowsAllExecs"] = estimateRows * estimateExecutions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the current property is used to reference a child item.
|
||||
/// Hierarchy properties are skipped when property wrappers are being created.
|
||||
/// </summary>
|
||||
/// <param name="property">Property subject to test.</param>
|
||||
/// <returns>True if the property is a hierarchy property;
|
||||
/// false if this is a regular property that should appear in the property grid.
|
||||
/// </returns>
|
||||
protected override bool ShouldSkipProperty(PropertyDescriptor property)
|
||||
{
|
||||
Type type = property.PropertyType;
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
type = type.GetElementType();
|
||||
}
|
||||
|
||||
return XmlPlanParserFactory.GetParser(type) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines Operation that corresponds to the object being parsed.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Operation that corresponds to the node.</returns>
|
||||
protected virtual Operation GetNodeOperation(Node node)
|
||||
{
|
||||
// STrace.Assert(false, "GetNodeOperation should not be called on base class."); {{aasim useless edit}}
|
||||
// STrace.LogExThrow(); {{aasim useless edit}}
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines node subtree cost from existing node properties.
|
||||
/// </summary>
|
||||
/// <param name="node">Node being parsed.</param>
|
||||
/// <returns>Node subtree cost.</returns>
|
||||
protected virtual double GetNodeSubtreeCost(Node node)
|
||||
{
|
||||
// STrace.Assert(false, "GetNodeSubtreeCost should not be called because it isn't defined for all node types"); {{aasim useless edit}}
|
||||
// STrace.LogExThrow(); {{aasim useless edit}}
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets children in a generic way.
|
||||
/// It should be avoided in the cases where performance matters.
|
||||
/// </summary>
|
||||
/// <param name="parsedItem">Item to enumerate children for</param>
|
||||
/// <returns>Enumeration of children</returns>
|
||||
public static IEnumerable EnumerateChildren(object parsedItem)
|
||||
{
|
||||
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parsedItem))
|
||||
{
|
||||
if (Type.GetTypeCode(property.PropertyType) == TypeCode.Object)
|
||||
{
|
||||
object value = property.GetValue(parsedItem);
|
||||
if (value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value is IEnumerable)
|
||||
{
|
||||
foreach (object item in (IEnumerable)value)
|
||||
{
|
||||
if (XmlPlanParserFactory.GetParser(item.GetType()) != null)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (XmlPlanParserFactory.GetParser(value.GetType()) != null)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Node.
|
||||
/// </summary>
|
||||
/// <param name="context">NodeBuilderContext.</param>
|
||||
/// <returns>New node instance.</returns>
|
||||
public static Node NewNode(NodeBuilderContext context)
|
||||
{
|
||||
XmlPlanNodeBuilder nodeBuilder = context.Context as XmlPlanNodeBuilder;
|
||||
Debug.Assert(nodeBuilder != null);
|
||||
|
||||
// We don't use "NodeId" property of the Node here because
|
||||
// not all nodes have Id and the same Id can repeat in different
|
||||
// statement branches
|
||||
return new Node(nodeBuilder.GetCurrentNodeId(), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
internal static class XmlPlanParserFactory
|
||||
{
|
||||
public static XmlPlanParser GetParser(Type type)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch (type.Name)
|
||||
{
|
||||
case "RelOpType":
|
||||
return RelOpTypeParser.Instance;
|
||||
|
||||
case "BaseStmtInfoType":
|
||||
return StatementParser.Instance;
|
||||
|
||||
case "RelOpBaseType":
|
||||
return RelOpBaseTypeParser.Instance;
|
||||
|
||||
case "FilterType":
|
||||
return FilterTypeParser.Instance;
|
||||
|
||||
case "MergeType":
|
||||
return MergeTypeParser.Instance;
|
||||
|
||||
case "StmtCursorType":
|
||||
return CursorStatementParser.Instance;
|
||||
|
||||
case "CursorPlanTypeOperation":
|
||||
return CursorOperationParser.Instance;
|
||||
|
||||
case "StmtBlockType":
|
||||
case "QueryPlanType":
|
||||
case "CursorPlanType":
|
||||
case "ReceivePlanTypeOperation":
|
||||
case "StmtCondTypeThen":
|
||||
case "StmtCondTypeElse":
|
||||
return XmlPlanHierarchyParser.Instance;
|
||||
|
||||
case "StmtCondTypeCondition":
|
||||
return ConditionParser.Instance;
|
||||
|
||||
case "FunctionType":
|
||||
return FunctionTypeParser.Instance;
|
||||
|
||||
case "IndexScanType":
|
||||
case "CreateIndexType":
|
||||
return IndexOpTypeParser.Instance;
|
||||
|
||||
case "Object":
|
||||
return null;
|
||||
|
||||
default:
|
||||
type = type.BaseType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that lists String constants common to XML Show Plan Node Parsing
|
||||
/// </summary>
|
||||
public sealed class NodeBuilderConstants
|
||||
{
|
||||
public static readonly string ActualExecutions = "ActualExecutions";
|
||||
public static readonly string ActualRows = "ActualRows";
|
||||
public static readonly string Argument = "Argument";
|
||||
public static readonly string AvgRowSize = "AvgRowSize";
|
||||
public static readonly string DefinedValues = "DefinedValues";
|
||||
public static readonly string ElapsedTime = "ElapsedTime";
|
||||
public static readonly string EstimateCPU = "EstimateCPU";
|
||||
public static readonly string EstimateExecutions = "EstimateExecutions";
|
||||
public static readonly string EstimateIO = "EstimateIO";
|
||||
public static readonly string EstimateRows = "EstimateRows";
|
||||
public static readonly string LogicalOp = "LogicalOp";
|
||||
public static readonly string NodeId = "NodeId";
|
||||
public static readonly string OutputList = "OutputList";
|
||||
public static readonly string Parallel = "Parallel";
|
||||
public static readonly string ParameterCompiledValue = "ParameterCompiledValue";
|
||||
public static readonly string ParameterList = "ParameterList";
|
||||
public static readonly string ParameterRuntimeValue = "ParameterRuntimeValue";
|
||||
public static readonly string PhysicalOp = "PhysicalOp";
|
||||
public static readonly string SeekPredicate = "SeekPredicate";
|
||||
public static readonly string SeekPredicates = "SeekPredicates";
|
||||
public static readonly string StatementText = "StatementText";
|
||||
public static readonly string StatementType = "StatementType";
|
||||
public static readonly string TotalSubtreeCost = "TotalSubtreeCost";
|
||||
public static readonly string Warnings = "Warnings";
|
||||
|
||||
public static readonly string Database = "Database";
|
||||
public static readonly string Table = "Table";
|
||||
public static readonly string Schema = "Schema";
|
||||
public static readonly string Predicate = "Predicate";
|
||||
public static readonly string Storage = "Storage";
|
||||
public static readonly string Index = "Index";
|
||||
public static readonly string Object = "Object";
|
||||
|
||||
//constants for Live Nodes
|
||||
public static readonly string Status = "Status";
|
||||
public static readonly string OpenTime = "OpenTime";
|
||||
public static readonly string CompletionEstimate = "CompletionEstimate";
|
||||
public static readonly string CloseTime = "CloseTime";
|
||||
|
||||
//constants for ShowPlan Comparison
|
||||
public static readonly string SkeletonNode = "SkeletonNode";
|
||||
public static readonly string SkeletonHasMatch = "SkeletonHasMatch";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ShowPlan type
|
||||
/// </summary>
|
||||
public enum ShowPlanType
|
||||
{
|
||||
Unknown,
|
||||
Actual,
|
||||
Estimated,
|
||||
Live
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan.ShowPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension of graph with some handy included methods specific for ShowPlan use
|
||||
/// </summary>
|
||||
public class ShowPlanGraph : Graph
|
||||
{
|
||||
private Dictionary<Node, object> nodeStmtMap = new Dictionary<Node, object>();
|
||||
|
||||
public Dictionary<Node, object> NodeStmtMap
|
||||
{
|
||||
get { return this.nodeStmtMap; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SQL Statement for this graph.
|
||||
/// </summary>
|
||||
public string Statement
|
||||
{
|
||||
get
|
||||
{
|
||||
// Special case: in the case of UDF or SP graphs thr root node doesn't
|
||||
// have StatementText. We should use Procedure Name instead
|
||||
return RootNode["StatementText"] as string ?? RootNode["ProcName"] as string ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The StatementId as recorded in the RootNode for this graph, -1 if not available
|
||||
/// </summary>
|
||||
public int StatementId
|
||||
{
|
||||
get
|
||||
{
|
||||
return PullIntFromRoot("StatementId");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The StatementCompId as recorded in the RootNode for this graph, -1 if not available
|
||||
/// </summary>
|
||||
public int StatementCompId
|
||||
{
|
||||
get
|
||||
{
|
||||
return PullIntFromRoot("StatementCompId");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public string QueryPlanHash
|
||||
{
|
||||
get
|
||||
{
|
||||
return RootNode["QueryPlanHash"] as string;
|
||||
}
|
||||
}
|
||||
|
||||
internal Node RootNode
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Root;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to parse an XMLString and return the set of ShowPlan graphs for it
|
||||
/// </summary>
|
||||
/// <param name="xmlString"></param>
|
||||
/// <returns></returns>
|
||||
public static ShowPlanGraph[] ParseShowPlanXML(object showPlan, ShowPlanType type = ShowPlanType.Unknown)
|
||||
{
|
||||
// Create a builder compatible with the data source
|
||||
INodeBuilder nodeBuilder = NodeBuilderFactory.Create(showPlan, type);
|
||||
|
||||
// Parse showplan data
|
||||
return nodeBuilder.Execute(showPlan);
|
||||
}
|
||||
|
||||
private int PullIntFromRoot(string name)
|
||||
{
|
||||
string statementId = RootNode[name].ToString();
|
||||
|
||||
if (statementId != null)
|
||||
{
|
||||
int id;
|
||||
if (Int32.TryParse(statementId, out id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
//error condition, return -1
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// 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.ExecutionPlan.ShowPlan;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.ExecutionPlan
|
||||
{
|
||||
public class ShowPlanGraphUtils
|
||||
{
|
||||
public static List<ExecutionPlanGraph> CreateShowPlanGraph(string xml, string fileName)
|
||||
{
|
||||
ShowPlan.ShowPlanGraph[] graphs = ShowPlan.ShowPlanGraph.ParseShowPlanXML(xml, ShowPlan.ShowPlanType.Unknown);
|
||||
return graphs.Select(g => new ExecutionPlanGraph
|
||||
{
|
||||
Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root),
|
||||
Query = g.Statement,
|
||||
GraphFile = new ExecutionPlanGraphInfo
|
||||
{
|
||||
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.ShowInTooltip,
|
||||
DisplayOrder = prop.DisplayOrder,
|
||||
PositionAtBottom = prop.IsLongString,
|
||||
DisplayValue = GetPropertyDisplayValue(prop)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyValue = GetProperties(complexProperty.Properties);
|
||||
propsList.Add(new NestedExecutionPlanGraphProperty()
|
||||
{
|
||||
Name = prop.DisplayName,
|
||||
Value = propertyValue,
|
||||
ShowInTooltip = prop.ShowInTooltip,
|
||||
DisplayOrder = prop.DisplayOrder,
|
||||
PositionAtBottom = prop.IsLongString,
|
||||
DisplayValue = GetPropertyDisplayValue(prop)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
return propsList;
|
||||
}
|
||||
|
||||
private static List<ExecutionPlanRecommendation> ParseRecommendations(ShowPlan.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
|
||||
*/
|
||||
";
|
||||
}
|
||||
|
||||
private static string GetPropertyDisplayValue(PropertyValue property)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the property value.
|
||||
object propertyValue = property.GetValue(property.Value);
|
||||
|
||||
if (propertyValue == null)
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
// Convert the property value to the text.
|
||||
return property.Converter.ConvertToString(propertyValue).Trim();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, e.ToString());
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user