mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
Sending show plan graph to ADS on Result Set updated event (#1300)
* Sending showplan graph over json rpc in Result updated event Translating showplan graph into simple objects to be sent over JSON RPC * Revert "Sending showplan graph over json rpc in Result updated event" This reverts commit 2d63a625fd200d057bf6093e233f05dea440347c. * Added string for localization * Sending showplan graph over json rpc in Result updated event Translating showplan graph into simple objects to be sent over JSON RPC * Refactoring class * Removing test warning * Removing unused imports Adding copyright * Removing unused prop * removing formatted string out .strings file * Formatting files Adding Errors in show plan graph * Adding a separate event for execution plan * Now sending mulitple graphs when a batch has more than one query.
This commit is contained in:
@@ -8774,6 +8774,16 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
return Keys.GetString(Keys.NameValuePair, name, value);
|
return Keys.GetString(Keys.NameValuePair, name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string OperatorDisplayCost(double cost, int percentage)
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.OperatorDisplayCost, cost, percentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ActualOfEstimated(string actual, string estimated, decimal percent)
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.ActualOfEstimated, actual, estimated, percent);
|
||||||
|
}
|
||||||
|
|
||||||
public static string TableNotInitializedException(string tableId)
|
public static string TableNotInitializedException(string tableId)
|
||||||
{
|
{
|
||||||
return Keys.GetString(Keys.TableNotInitializedException, tableId);
|
return Keys.GetString(Keys.TableNotInitializedException, tableId);
|
||||||
@@ -12107,6 +12117,12 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
public const string SizeInTeraBytesFormat = "SizeInTeraBytesFormat";
|
public const string SizeInTeraBytesFormat = "SizeInTeraBytesFormat";
|
||||||
|
|
||||||
|
|
||||||
|
public const string OperatorDisplayCost = "OperatorDisplayCost";
|
||||||
|
|
||||||
|
|
||||||
|
public const string ActualOfEstimated = "ActualOfEstimated";
|
||||||
|
|
||||||
|
|
||||||
public const string TableNotInitializedException = "TableNotInitializedException";
|
public const string TableNotInitializedException = "TableNotInitializedException";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4590,6 +4590,17 @@
|
|||||||
<value>{0} TB</value>
|
<value>{0} TB</value>
|
||||||
<comment>Size in TeraBytes format</comment>
|
<comment>Size in TeraBytes format</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="OperatorDisplayCost" xml:space="preserve">
|
||||||
|
<value>{0:0.#######} ({1}%)</value>
|
||||||
|
<comment> display string for the operator cost property - 0.###### - is the float number format specifier.
|
||||||
|
Parameters: 0 - cost (double), 1 - percentage (int) </comment>
|
||||||
|
</data>
|
||||||
|
<data name="ActualOfEstimated" xml:space="preserve">
|
||||||
|
<value>{0} of
|
||||||
|
{1} ({2}%)</value>
|
||||||
|
<comment>.
|
||||||
|
Parameters: 0 - actual (string), 1 - estimated (string), 2 - percent (decimal) </comment>
|
||||||
|
</data>
|
||||||
<data name="TableNotInitializedException" xml:space="preserve">
|
<data name="TableNotInitializedException" xml:space="preserve">
|
||||||
<value>Initialization is not properly done for table with id '{0}'</value>
|
<value>Initialization is not properly done for table with id '{0}'</value>
|
||||||
<comment>.
|
<comment>.
|
||||||
|
|||||||
@@ -2213,6 +2213,10 @@ SizeInMegaBytesFormat = {0} MB
|
|||||||
SizeInGigaBytesFormat = {0} GB
|
SizeInGigaBytesFormat = {0} GB
|
||||||
;Size in TeraBytes format
|
;Size in TeraBytes format
|
||||||
SizeInTeraBytesFormat = {0} TB
|
SizeInTeraBytesFormat = {0} TB
|
||||||
|
; display string for the operator cost property - 0.###### - is the float number format specifier
|
||||||
|
OperatorDisplayCost(double cost, int percentage) = {0:0.#######} ({1}%)
|
||||||
|
#Would like to display actual rows and estimated rows in two lines: <number_actual_rows> of\n <number_estimated_rows> (xx%)
|
||||||
|
ActualOfEstimated(string actual, string estimated, decimal percent) = {0} of\n{1} ({2}%)
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
# Table Designer
|
# Table Designer
|
||||||
|
|||||||
@@ -5631,6 +5631,20 @@
|
|||||||
<note>.
|
<note>.
|
||||||
Parameters: 0 - path (string), 1 - editType (string) </note>
|
Parameters: 0 - path (string), 1 - editType (string) </note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="OperatorDisplayCost">
|
||||||
|
<source>{0:0.#######} ({1}%)</source>
|
||||||
|
<target state="new">{0:0.#######} ({1}%)</target>
|
||||||
|
<note> display string for the operator cost property - 0.###### - is the float number format specifier.
|
||||||
|
Parameters: 0 - cost (double), 1 - percentage (int) </note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="ActualOfEstimated">
|
||||||
|
<source>{0} of
|
||||||
|
{1} ({2}%)</source>
|
||||||
|
<target state="new">{0} of
|
||||||
|
{1} ({2}%)</target>
|
||||||
|
<note>.
|
||||||
|
Parameters: 0 - actual (string), 1 - estimated (string), 2 - percent (decimal) </note>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.ShowPlan;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests
|
||||||
{
|
{
|
||||||
@@ -35,9 +37,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteReques
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ResultSetUpdatedEventParams : ResultSetEventParams
|
public class ResultSetUpdatedEventParams : ResultSetEventParams
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Execution plans for statements in the current batch.
|
||||||
|
/// </summary>
|
||||||
|
public List<ExecutionPlanGraph> ExecutionPlans { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Error message for exception raised while generating execution plan.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionPlanErrorMessage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ResultSetCompleteEvent
|
public class ResultSetCompleteEvent
|
||||||
{
|
{
|
||||||
public static string MethodName { get; } = "query/resultSetComplete";
|
public static string MethodName { get; } = "query/resultSetComplete";
|
||||||
|
|
||||||
@@ -46,7 +56,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteReques
|
|||||||
EventType<ResultSetCompleteEventParams>.Create(MethodName);
|
EventType<ResultSetCompleteEventParams>.Create(MethodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ResultSetAvailableEvent
|
public class ResultSetAvailableEvent
|
||||||
{
|
{
|
||||||
public static string MethodName { get; } = "query/resultSetAvailable";
|
public static string MethodName { get; } = "query/resultSetAvailable";
|
||||||
|
|
||||||
@@ -55,7 +65,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteReques
|
|||||||
EventType<ResultSetAvailableEventParams>.Create(MethodName);
|
EventType<ResultSetAvailableEventParams>.Create(MethodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ResultSetUpdatedEvent
|
public class ResultSetUpdatedEvent
|
||||||
{
|
{
|
||||||
public static string MethodName { get; } = "query/resultSetUpdated";
|
public static string MethodName { get; } = "query/resultSetUpdated";
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,21 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.Hosting.Protocol;
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.ShowPlan;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
using Microsoft.SqlTools.Utility;
|
using Microsoft.SqlTools.Utility;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||||
{
|
{
|
||||||
@@ -136,7 +138,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// Internal storage of active query settings
|
/// Internal storage of active query settings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Lazy<ConcurrentDictionary<string, QueryExecutionSettings>> queryExecutionSettings =
|
private readonly Lazy<ConcurrentDictionary<string, QueryExecutionSettings>> queryExecutionSettings =
|
||||||
new Lazy<ConcurrentDictionary<string, QueryExecutionSettings>>(() => new ConcurrentDictionary<string, QueryExecutionSettings>());
|
new Lazy<ConcurrentDictionary<string, QueryExecutionSettings>>(() => new ConcurrentDictionary<string, QueryExecutionSettings>());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Settings that will be used to execute queries. Internal for unit testing
|
/// Settings that will be used to execute queries. Internal for unit testing
|
||||||
@@ -220,12 +222,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
WorkTask = Task.Run(async () =>
|
WorkTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await InterServiceExecuteQuery(
|
await InterServiceExecuteQuery(
|
||||||
executeParams,
|
executeParams,
|
||||||
null,
|
null,
|
||||||
requestContext,
|
requestContext,
|
||||||
queryCreateSuccessAction,
|
queryCreateSuccessAction,
|
||||||
queryCreateFailureAction,
|
queryCreateFailureAction,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
isQueryEditor(executeParams.OwnerUri));
|
isQueryEditor(executeParams.OwnerUri));
|
||||||
});
|
});
|
||||||
@@ -267,7 +269,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
Type = ConnectionType.Default
|
Type = ConnectionType.Default
|
||||||
};
|
};
|
||||||
|
|
||||||
Task workTask = Task.Run(async () => {
|
Task workTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
await ConnectionService.Connect(connectParams);
|
await ConnectionService.Connect(connectParams);
|
||||||
|
|
||||||
ConnectionInfo newConn;
|
ConnectionInfo newConn;
|
||||||
@@ -328,7 +331,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
// remove the active query since we are done with it
|
// remove the active query since we are done with it
|
||||||
ActiveQueries.TryRemove(randomUri, out removedQuery);
|
ActiveQueries.TryRemove(randomUri, out removedQuery);
|
||||||
ActiveSimpleExecuteRequests.TryRemove(randomUri, out removedTask);
|
ActiveSimpleExecuteRequests.TryRemove(randomUri, out removedTask);
|
||||||
ConnectionService.Disconnect(new DisconnectParams(){
|
ConnectionService.Disconnect(new DisconnectParams()
|
||||||
|
{
|
||||||
OwnerUri = randomUri,
|
OwnerUri = randomUri,
|
||||||
Type = null
|
Type = null
|
||||||
});
|
});
|
||||||
@@ -359,12 +363,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
internal Task HandleConnectionUriChangedNotification(ConnectionUriChangedParams changeUriParams,
|
internal Task HandleConnectionUriChangedNotification(ConnectionUriChangedParams changeUriParams,
|
||||||
EventContext eventContext)
|
EventContext eventContext)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
string OriginalOwnerUri = changeUriParams.OriginalOwnerUri;
|
string OriginalOwnerUri = changeUriParams.OriginalOwnerUri;
|
||||||
string NewOwnerUri = changeUriParams.NewOwnerUri;
|
string NewOwnerUri = changeUriParams.NewOwnerUri;
|
||||||
// Attempt to load the query
|
// Attempt to load the query
|
||||||
Query query;
|
Query query;
|
||||||
if(!ActiveQueries.TryRemove(OriginalOwnerUri, out query)){
|
if (!ActiveQueries.TryRemove(OriginalOwnerUri, out query))
|
||||||
|
{
|
||||||
throw new Exception("Uri: " + OriginalOwnerUri + " is not associated with an active query.");
|
throw new Exception("Uri: " + OriginalOwnerUri + " is not associated with an active query.");
|
||||||
}
|
}
|
||||||
ConnectionService.ReplaceUri(OriginalOwnerUri, NewOwnerUri);
|
ConnectionService.ReplaceUri(OriginalOwnerUri, NewOwnerUri);
|
||||||
@@ -372,7 +378,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
ActiveQueries.TryAdd(NewOwnerUri, query);
|
ActiveQueries.TryAdd(NewOwnerUri, query);
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Write(TraceEventType.Error, "Error encountered " + ex.ToString());
|
Logger.Write(TraceEventType.Error, "Error encountered " + ex.ToString());
|
||||||
return Task.FromException(ex);
|
return Task.FromException(ex);
|
||||||
@@ -402,7 +408,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles a request to set query execution options
|
/// Handles a request to set query execution options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -411,7 +417,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string uri = queryExecutionOptionsParams.OwnerUri;
|
string uri = queryExecutionOptionsParams.OwnerUri;
|
||||||
if (ActiveQueryExecutionSettings.ContainsKey(uri))
|
if (ActiveQueryExecutionSettings.ContainsKey(uri))
|
||||||
{
|
{
|
||||||
QueryExecutionSettings settings;
|
QueryExecutionSettings settings;
|
||||||
@@ -426,7 +432,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
{
|
{
|
||||||
// This was unexpected, so send back as error
|
// This was unexpected, so send back as error
|
||||||
await requestContext.SendError(e.Message);
|
await requestContext.SendError(e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -712,30 +718,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
{
|
{
|
||||||
QueryExecutionSettings settings;
|
QueryExecutionSettings settings;
|
||||||
this.ActiveQueryExecutionSettings.TryRemove(uri, out settings);
|
this.ActiveQueryExecutionSettings.TryRemove(uri, out settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Write(TraceEventType.Error, "Unknown error " + ex.ToString());
|
Logger.Write(TraceEventType.Error, "Unknown error " + ex.ToString());
|
||||||
}
|
}
|
||||||
await Task.FromResult(true);
|
await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Helpers
|
#region Private Helpers
|
||||||
|
|
||||||
private Query CreateQuery(
|
private Query CreateQuery(
|
||||||
ExecuteRequestParamsBase executeParams,
|
ExecuteRequestParamsBase executeParams,
|
||||||
ConnectionInfo connInfo,
|
ConnectionInfo connInfo,
|
||||||
bool applyExecutionSettings)
|
bool applyExecutionSettings)
|
||||||
{
|
{
|
||||||
// Attempt to get the connection for the editor
|
// Attempt to get the connection for the editor
|
||||||
ConnectionInfo connectionInfo;
|
ConnectionInfo connectionInfo;
|
||||||
if (connInfo != null)
|
if (connInfo != null)
|
||||||
{
|
{
|
||||||
connectionInfo = connInfo;
|
connectionInfo = connInfo;
|
||||||
}
|
}
|
||||||
else if (!ConnectionService.TryFindConnection(executeParams.OwnerUri, out connectionInfo))
|
else if (!ConnectionService.TryFindConnection(executeParams.OwnerUri, out connectionInfo))
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(executeParams.OwnerUri), SR.QueryServiceQueryInvalidOwnerUri);
|
throw new ArgumentOutOfRangeException(nameof(executeParams.OwnerUri), SR.QueryServiceQueryInvalidOwnerUri);
|
||||||
@@ -752,11 +758,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
oldQuery.Dispose();
|
oldQuery.Dispose();
|
||||||
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
|
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there are active query execution settings for the editor, otherwise, use the global settings
|
// check if there are active query execution settings for the editor, otherwise, use the global settings
|
||||||
QueryExecutionSettings settings;
|
QueryExecutionSettings settings;
|
||||||
if (this.ActiveQueryExecutionSettings.TryGetValue(executeParams.OwnerUri, out settings))
|
if (this.ActiveQueryExecutionSettings.TryGetValue(executeParams.OwnerUri, out settings))
|
||||||
{
|
{
|
||||||
// special-case handling for query plan options to maintain compat with query execution API parameters
|
// special-case handling for query plan options to maintain compat with query execution API parameters
|
||||||
// the logic is that if either the query execute API parameters or the active query setttings
|
// the logic is that if either the query execute API parameters or the active query setttings
|
||||||
// request a plan then enable the query option
|
// request a plan then enable the query option
|
||||||
@@ -772,17 +778,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
settings.ExecutionPlanOptions = executionPlanOptions;
|
settings.ExecutionPlanOptions = executionPlanOptions;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
settings = Settings.QueryExecutionSettings;
|
settings = Settings.QueryExecutionSettings;
|
||||||
settings.ExecutionPlanOptions = executeParams.ExecutionPlanOptions;
|
settings.ExecutionPlanOptions = executeParams.ExecutionPlanOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we can't add the query now, it's assumed the query is in progress
|
// If we can't add the query now, it's assumed the query is in progress
|
||||||
Query newQuery = new Query(
|
Query newQuery = new Query(
|
||||||
GetSqlText(executeParams),
|
GetSqlText(executeParams),
|
||||||
connectionInfo,
|
connectionInfo,
|
||||||
settings,
|
settings,
|
||||||
BufferFileFactory,
|
BufferFileFactory,
|
||||||
executeParams.GetFullColumnSchema,
|
executeParams.GetFullColumnSchema,
|
||||||
applyExecutionSettings);
|
applyExecutionSettings);
|
||||||
|
|
||||||
@@ -893,13 +899,33 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
// Setup the ResultSet updated callback
|
// Setup the ResultSet updated callback
|
||||||
ResultSet.ResultSetAsyncEventHandler resultUpdatedCallback = async r =>
|
ResultSet.ResultSetAsyncEventHandler resultUpdatedCallback = async r =>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//Generating and sending an execution plan graphs if it is requested.
|
||||||
|
List<ExecutionPlanGraph> plans = null;
|
||||||
|
string planErrors = "";
|
||||||
|
if (r.Summary.Complete && r.Summary.SpecialAction.ExpectYukonXMLShowPlan && r.RowCount == 1 && r.GetRow(0)[0] != null)
|
||||||
|
{
|
||||||
|
var xmlString = r.GetRow(0)[0].DisplayValue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
plans = ShowPlanGraphUtils.CreateShowPlanGraph(xmlString);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// In case of error we are sending an empty execution plan graph with the error message.
|
||||||
|
Logger.Write(TraceEventType.Error, String.Format("Failed to generate show plan graph{0}{1}", Environment.NewLine, ex.Message));
|
||||||
|
planErrors = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
ResultSetUpdatedEventParams eventParams = new ResultSetUpdatedEventParams
|
ResultSetUpdatedEventParams eventParams = new ResultSetUpdatedEventParams
|
||||||
{
|
{
|
||||||
ResultSetSummary = r.Summary,
|
ResultSetSummary = r.Summary,
|
||||||
OwnerUri = ownerUri
|
OwnerUri = ownerUri,
|
||||||
|
ExecutionPlans = plans,
|
||||||
|
ExecutionPlanErrorMessage = planErrors
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.Write(TraceEventType.Information, $"Result:'{r.Summary} on Query:'{ownerUri}' is updated with additional rows");
|
|
||||||
await eventSender.SendEvent(ResultSetUpdatedEvent.Type, eventParams);
|
await eventSender.SendEvent(ResultSetUpdatedEvent.Type, eventParams);
|
||||||
};
|
};
|
||||||
query.ResultSetUpdated += resultUpdatedCallback;
|
query.ResultSetUpdated += resultUpdatedCallback;
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// 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.ShowPlan
|
||||||
|
{
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ExecutionPlanGraphElementProperties> 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 ExecutionPlanGraphElementProperties
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the property
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Formatted value for the property
|
||||||
|
/// </summary>
|
||||||
|
public string FormattedValue { 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 indicate if the property has a longer value so that it will be shown at the bottom of the tooltip
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLongString { 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>
|
||||||
|
/// <value></value>
|
||||||
|
public double RowSize { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Edge properties to be shown in the tooltip.
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public List<ExecutionPlanGraphElementProperties> Properties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.ShowPlan.ShowPlanGraph
|
||||||
|
{
|
||||||
|
public class Constants
|
||||||
|
{
|
||||||
|
public static string Parenthesis(string text)
|
||||||
|
{
|
||||||
|
return string.Format("({0})", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,7 +90,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
|
|
||||||
if (parentNode != null)
|
if (parentNode != null)
|
||||||
{
|
{
|
||||||
parentNode.Children.AddLast(node);
|
parentNode.Children.Add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add node to the hashtable
|
// Add node to the hashtable
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
||||||
{
|
{
|
||||||
@@ -29,12 +31,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
{
|
{
|
||||||
this.ID = id;
|
this.ID = id;
|
||||||
this.properties = new PropertyDescriptorCollection(new PropertyDescriptor[0]);
|
this.properties = new PropertyDescriptorCollection(new PropertyDescriptor[0]);
|
||||||
this.children = new LinkedList<Node>();
|
this.children = new List<Node>();
|
||||||
this.childrenEdges = new LinkedList<Edge>();
|
this.childrenEdges = new List<Edge>();
|
||||||
this.LogicalOpUnlocName = null;
|
this.LogicalOpUnlocName = null;
|
||||||
this.PhysicalOpUnlocName = null;
|
this.PhysicalOpUnlocName = null;
|
||||||
this.root = context.Graph.Root;
|
this.root = context.Graph.Root;
|
||||||
if(this.root == null)
|
if (this.root == null)
|
||||||
{
|
{
|
||||||
this.root = this;
|
this.root = this;
|
||||||
}
|
}
|
||||||
@@ -55,6 +57,159 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
get; set;
|
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>
|
/// <summary>
|
||||||
/// Gets the cost associated with the current Node.
|
/// Gets the cost associated with the current Node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -95,6 +250,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cost associated with the Node subtree.
|
/// Gets the cost associated with the Node subtree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ShowInToolTip, DisplayOrder(9), DisplayNameDescription(SR.Keys.EstimatedSubtreeCost, SR.Keys.EstimatedSubtreeCostDescription)]
|
||||||
public double SubtreeCost
|
public double SubtreeCost
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -116,6 +272,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max Children X Position.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxChildrenXPosition;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the operation information (localized name, description, image, etc)
|
/// Gets the operation information (localized name, description, image, etc)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -174,11 +336,20 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets collection of node children.
|
/// Gets collection of node children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LinkedList<Node> Children
|
public List<Node> Children
|
||||||
{
|
{
|
||||||
get { return this.children; }
|
get { return this.children; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets collection of node children.
|
||||||
|
/// </summary>
|
||||||
|
public List<Edge> Edges
|
||||||
|
{
|
||||||
|
get { return this.childrenEdges; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets current node parent.
|
/// Gets current node parent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -320,6 +491,26 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
return true;
|
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>
|
/// <summary>
|
||||||
/// ENU name for Logical Operator
|
/// ENU name for Logical Operator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -334,6 +525,33 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
|
|
||||||
#region Implementation details
|
#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>
|
/// <summary>
|
||||||
/// used to compare multiple string type PropertyValue in Object properties,
|
/// used to compare multiple string type PropertyValue in Object properties,
|
||||||
/// for ex: Server, Database, Schema, Table, Index, etc...
|
/// for ex: Server, Database, Schema, Table, Index, etc...
|
||||||
@@ -358,6 +576,130 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
return true;
|
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
|
#endregion
|
||||||
|
|
||||||
#region Private variables
|
#region Private variables
|
||||||
@@ -367,13 +709,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
private double subtreeCost;
|
private double subtreeCost;
|
||||||
private Operation operation;
|
private Operation operation;
|
||||||
private PropertyDescriptorCollection properties;
|
private PropertyDescriptorCollection properties;
|
||||||
private LinkedList<Node> children;
|
private List<Node> children;
|
||||||
private readonly string objectProperty = NodeBuilderConstants.Object;
|
private readonly string objectProperty = NodeBuilderConstants.Object;
|
||||||
private readonly string predicateProperty = NodeBuilderConstants.LogicalOp;
|
private readonly string predicateProperty = NodeBuilderConstants.LogicalOp;
|
||||||
private Node parent;
|
private Node parent;
|
||||||
private Graph graph;
|
private Graph graph;
|
||||||
private Edge parentEdge;
|
private Edge parentEdge;
|
||||||
private LinkedList<Edge> childrenEdges;
|
private List<Edge> childrenEdges;
|
||||||
private string nodeType;
|
private string nodeType;
|
||||||
|
|
||||||
private Node root;
|
private Node root;
|
||||||
@@ -388,9 +730,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
public void AddChild(Node child)
|
public void AddChild(Node child)
|
||||||
{
|
{
|
||||||
Edge edge = new Edge(this, child);
|
Edge edge = new Edge(this, child);
|
||||||
this.childrenEdges.AddLast(edge);
|
this.childrenEdges.Add(edge);
|
||||||
child.parentEdge = edge;
|
child.parentEdge = edge;
|
||||||
this.children.AddLast(child);
|
this.children.Add(child);
|
||||||
child.parent = this;
|
child.parent = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph
|
|||||||
set { this.propertyValue = value; }
|
set { this.propertyValue = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string DisplayValue
|
||||||
|
{
|
||||||
|
get => this.Converter.ConvertToString(null, null, this.Value);
|
||||||
|
}
|
||||||
|
|
||||||
public int DisplayOrder
|
public int DisplayOrder
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// 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.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
||||||
|
{
|
||||||
|
public class ShowPlanGraphUtils
|
||||||
|
{
|
||||||
|
public static List<ExecutionPlanGraph> CreateShowPlanGraph(string xml)
|
||||||
|
{
|
||||||
|
ShowPlanGraph.ShowPlanGraph[] graphs = ShowPlanGraph.ShowPlanGraph.ParseShowPlanXML(xml, ShowPlanGraph.ShowPlanType.Unknown);
|
||||||
|
return graphs.Select(g => new ExecutionPlanGraph
|
||||||
|
{
|
||||||
|
Root = ConvertShowPlanTreeToExecutionPlanTree(g.Root),
|
||||||
|
Query = g.Statement
|
||||||
|
}).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<ExecutionPlanGraphElementProperties> GetProperties(PropertyDescriptorCollection props)
|
||||||
|
{
|
||||||
|
List<ExecutionPlanGraphElementProperties> propsList = new List<ExecutionPlanGraphElementProperties>();
|
||||||
|
foreach (PropertyValue prop in props)
|
||||||
|
{
|
||||||
|
propsList.Add(new ExecutionPlanGraphElementProperties()
|
||||||
|
{
|
||||||
|
Name = prop.DisplayName,
|
||||||
|
FormattedValue = prop.DisplayValue,
|
||||||
|
ShowInTooltip = prop.IsBrowsable,
|
||||||
|
DisplayOrder = prop.DisplayOrder,
|
||||||
|
IsLongString = prop.IsLongString
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return propsList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.SqlServer.DataCollection.Common;
|
|
||||||
using Microsoft.SqlTools.Hosting.Protocol;
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
namespace Microsoft.SqlTools.ServiceLayer.ShowPlan
|
||||||
|
|||||||
@@ -3,12 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Microsoft.SqlTools.ServiceLayer.ShowPlan.ShowPlanGraph;
|
using Microsoft.SqlTools.ServiceLayer.ShowPlan;
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
|
||||||
@@ -16,17 +14,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ShowPlan
|
|||||||
public class ShowPlanXMLTests
|
public class ShowPlanXMLTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ParseXMLFileReturnsValidShowPlanGraph()
|
public void ParseXMLFileReturnsValidShowPlanGraph()
|
||||||
{
|
{
|
||||||
Assembly assembly = Assembly.GetAssembly(typeof(ShowPlanXMLTests));
|
Assembly assembly = Assembly.GetAssembly(typeof(ShowPlanXMLTests));
|
||||||
Stream scriptStream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".ShowPlan.TestExecutionPlan.xml");
|
Stream scriptStream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".ShowPlan.TestExecutionPlan.xml");
|
||||||
StreamReader reader = new StreamReader(scriptStream);
|
StreamReader reader = new StreamReader(scriptStream);
|
||||||
string text = reader.ReadToEnd();
|
string text = reader.ReadToEnd();
|
||||||
var showPlanGraphs = ShowPlanGraph.ParseShowPlanXML(text, ShowPlanType.Actual);
|
var showPlanGraphs = ShowPlanGraphUtils.CreateShowPlanGraph(text);
|
||||||
Assert.AreEqual(1, showPlanGraphs.Length, "Single show plan graph not generated from the test xml file");
|
Assert.AreEqual(1, showPlanGraphs.Count, "exactly one show plan graph should be returned");
|
||||||
var testShowPlanGraph = showPlanGraphs[0];
|
Assert.NotNull(showPlanGraphs[0], "graph should not be null");
|
||||||
Assert.NotNull(testShowPlanGraph, "graph should not be null");
|
Assert.NotNull(showPlanGraphs[0].Root, "graph should have a root");
|
||||||
Assert.NotNull(testShowPlanGraph.Root, "graph should have a root");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user