Feature execution plan settings and request implementation (#213)

* experimental showplan implementation (tools side only)

* fix for redundant massages from showplan executions

* moved showplan batches to new variables to make it less confusing

* returns showplan as part of batch summary with in each result summary

* cleaned up showplan resultsets

* cleaning up code and making showplan var optional

* changes all var names to showplan

* adding estimated support

* small fixes

* updatin var names and adding EPOptions struct

* adding ssms execution plan logic based on server types

* adding special actions logic

* removing redundant name changes

* execution plan query handler added

* cleaning up functions and data structures

* seperated special actions into its own class

* cleaning up special actions

* cleaning up code

* small new line fixes

* commenting out pre-yukon code

* removing all pre yukon code

* last yukon code commented out

* fixes broken tests

* adding related unit tests; integration tests incoming

* finishing tests and cleaning up code

* semantic changes

* cleaning up semantics

* changes and test fixes, also adding new exceptions into RS

* fixing special actions and cleaning up request logic

* fixing comment to trigger new build

* triggering another  build

* fixed up specialaction and added tests for it
This commit is contained in:
Raymond Martin
2017-01-17 19:37:42 -08:00
committed by GitHub
parent 0e29b181c9
commit 8a8d4338f1
20 changed files with 855 additions and 1 deletions

View File

@@ -55,6 +55,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary>
private readonly List<ResultSet> resultSets;
/// <summary>
/// Special action which this batch performed
/// </summary>
private SpecialAction specialAction;
#endregion
internal Batch(string batchText, SelectionData selection, int ordinalId, IFileStreamFactory outputFileFactory)
@@ -72,6 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
Id = ordinalId;
resultSets = new List<ResultSet>();
this.outputFileFactory = outputFileFactory;
specialAction = new SpecialAction();
}
#region Events
@@ -201,6 +207,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
summary.ResultSetSummaries = ResultSummaries;
summary.ExecutionEnd = ExecutionEndTimeStamp;
summary.ExecutionElapsed = ExecutionElapsedTime;
summary.SpecialAction = ProcessResultSetSpecialActions();
}
return summary;
@@ -370,6 +377,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return targetResultSet.GetSubset(startRow, rowCount);
}
/// <summary>
/// Generates an execution plan
/// </summary>
/// <param name="resultSetIndex">The index for selecting the result set</param>
/// <returns>An exeuction plan object</returns>
public Task<ExecutionPlan> GetExecutionPlan(int resultSetIndex)
{
ResultSet targetResultSet;
lock (resultSets)
{
// Sanity check to make sure we have valid numbers
if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count)
{
throw new ArgumentOutOfRangeException(nameof(resultSetIndex),
SR.QueryServiceSubsetResultSetOutOfRange);
}
targetResultSet = resultSets[resultSetIndex];
}
// Retrieve the result set
return targetResultSet.GetExecutionPlan();
}
/// <summary>
/// Saves a result to a file format selected by the user
/// </summary>
@@ -482,6 +513,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Aggregates all result sets in the batch into a single special action
/// </summary>
private SpecialAction ProcessResultSetSpecialActions()
{
foreach (ResultSet resultSet in resultSets)
{
specialAction.CombineSpecialAction(resultSet.Summary.SpecialAction);
}
return specialAction;
}
#endregion
#region IDisposable Implementation

View File

@@ -44,5 +44,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// The summaries of the result sets inside the batch
/// </summary>
public ResultSetSummary[] ResultSetSummaries { get; set; }
/// <summary>
/// The special action of the batch
/// </summary>
public SpecialAction SpecialAction { get; set; }
}
}

View File

@@ -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.QueryExecution.Contracts
{
/// <summary>
/// Class used to represent an execution plan from a query for transmission across JSON RPC
/// </summary>
public class ExecutionPlan
{
/// <summary>
/// The format of the execution plan
/// </summary>
public string Format { get; set; }
/// <summary>
/// The execution plan content
/// </summary>
public string Content { get; set; }
}
}

View File

@@ -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.QueryExecution.Contracts
{
/// <summary>
/// Incoming execution plan options from the extension
/// </summary>
public struct ExecutionPlanOptions
{
/// <summary>
/// Setting to return the actual execution plan as XML
/// </summary>
public bool IncludeActualExecutionPlanXml { get; set; }
/// <summary>
/// Setting to return the estimated execution plan as XML
/// </summary>
public bool IncludeEstimatedExecutionPlanXml { get; set; }
}
}

View File

@@ -21,6 +21,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// URI for the editor that is asking for the query execute
/// </summary>
public string OwnerUri { get; set; }
/// <summary>
/// Execution plan options
/// </summary>
public ExecutionPlanOptions ExecutionPlanOptions { get; set; }
}
/// <summary>

View File

@@ -0,0 +1,49 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
{
/// <summary>
/// Parameters for query execution plan request
/// </summary>
public class QueryExecutionPlanParams
{
/// <summary>
/// URI for the file that owns the query to look up the results for
/// </summary>
public string OwnerUri { get; set; }
/// <summary>
/// Index of the batch to get the results from
/// </summary>
public int BatchIndex { get; set; }
/// <summary>
/// Index of the result set to get the results from
/// </summary>
public int ResultSetIndex { get; set; }
}
/// <summary>
/// Parameters for the query execution plan request
/// </summary>
public class QueryExecutionPlanResult
{
/// <summary>
/// The requested execution plan. Optional, can be set to null to indicate an error
/// </summary>
public ExecutionPlan ExecutionPlan { get; set; }
}
public class QueryExecutionPlanRequest
{
public static readonly
RequestType<QueryExecutionPlanParams, QueryExecutionPlanResult> Type =
RequestType<QueryExecutionPlanParams, QueryExecutionPlanResult>.Create("query/executionPlan");
}
}

View File

@@ -29,5 +29,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// Details about the columns that are provided as solutions
/// </summary>
public DbColumnWrapper[] ColumnInfo { get; set; }
/// <summary>
/// The special action definition of the result set
/// </summary>
public SpecialAction SpecialAction { get; set; }
}
}

View File

@@ -15,6 +15,7 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Utility;
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
@@ -51,6 +52,36 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary>
private bool hasExecuteBeenCalled;
/// <summary>
/// Settings for query runtime
/// </summary>
private QueryExecutionSettings querySettings;
/// <summary>
/// Streaming output factory for the query
/// </summary>
private IFileStreamFactory streamOutputFactory;
/// <summary>
/// ON keyword
/// </summary>
private const string On = "ON";
/// <summary>
/// OFF keyword
/// </summary>
private const string Off = "OFF";
/// <summary>
/// showplan_xml statement
/// </summary>
private const string SetShowPlanXml = "SET SHOWPLAN_XML {0}";
/// <summary>
/// statistics xml statement
/// </summary>
private const string SetStatisticsXml = "SET STATISTICS XML {0}";
#endregion
/// <summary>
@@ -72,6 +103,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
QueryText = queryText;
editorConnection = connection;
cancellationSource = new CancellationTokenSource();
querySettings = settings;
streamOutputFactory = outputFactory;
// Process the query into batches
ParseResult parseResult = Parser.Parse(queryText, new ParseOptions
@@ -89,7 +122,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
batch.EndLocation.LineNumber - 1,
batch.EndLocation.ColumnNumber - 1),
index, outputFactory));
Batches = batchSelection.ToArray();
// Create our batch lists
BeforeBatches = new List<Batch>();
AfterBatches = new List<Batch>();
if (DoesSupportExecutionPlan(connection))
{
// Checking settings for execution plan options
if (querySettings.ExecutionPlanOptions.IncludeEstimatedExecutionPlanXml)
{
// Enable set showplan xml
addBatch(string.Format(SetShowPlanXml, On), BeforeBatches, streamOutputFactory);
addBatch(string.Format(SetShowPlanXml, Off), AfterBatches, streamOutputFactory);
}
else if (querySettings.ExecutionPlanOptions.IncludeActualExecutionPlanXml)
{
addBatch(string.Format(SetStatisticsXml, On), BeforeBatches, streamOutputFactory);
addBatch(string.Format(SetStatisticsXml, Off), AfterBatches, streamOutputFactory);
}
}
}
#region Events
@@ -145,11 +199,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="q">The query that completed</param>
public delegate Task QueryAsyncEventHandler(Query q);
/// <summary>
/// The batches which should run before the user batches
/// </summary>
internal List<Batch> BeforeBatches { get; set; }
/// <summary>
/// The batches underneath this query
/// </summary>
internal Batch[] Batches { get; set; }
/// <summary>
/// The batches which should run after the user batches
/// </summary>
internal List<Batch> AfterBatches { get; set; }
/// <summary>
/// The summaries of the batches underneath this query
/// </summary>
@@ -241,6 +305,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return Batches[batchIndex].GetSubset(resultSetIndex, startRow, rowCount);
}
/// <summary>
/// Retrieves a subset of the result sets
/// </summary>
/// <param name="batchIndex">The index for selecting the batch item</param>
/// <param name="resultSetIndex">The index for selecting the result set</param>
/// <returns>The Execution Plan, if the result set has one</returns>
public Task<ExecutionPlan> GetExecutionPlan(int batchIndex, int resultSetIndex)
{
// Sanity check to make sure that the batch is within bounds
if (batchIndex < 0 || batchIndex >= Batches.Length)
{
throw new ArgumentOutOfRangeException(nameof(batchIndex), SR.QueryServiceSubsetBatchOutOfRange);
}
return Batches[batchIndex].GetExecutionPlan(resultSetIndex);
}
/// <summary>
/// Saves the requested results to a file format of the user's choice
/// </summary>
@@ -316,9 +397,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
try
{
// Execute beforeBatches synchronously, before the user defined batches
foreach (Batch b in BeforeBatches)
{
await b.Execute(conn, cancellationSource.Token);
}
// We need these to execute synchronously, otherwise the user will be very unhappy
foreach (Batch b in Batches)
{
// Add completion callbacks
b.BatchStart += BatchStarted;
b.BatchCompletion += BatchCompleted;
b.BatchMessageSent += BatchMessageSent;
@@ -326,6 +414,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
await b.Execute(conn, cancellationSource.Token);
}
// Execute afterBatches synchronously, after the user defined batches
foreach (Batch b in AfterBatches)
{
await b.Execute(conn, cancellationSource.Token);
}
// Call the query execution callback
if (QueryCompleted != null)
{
@@ -374,6 +468,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Function to add a new batch to a Batch set
/// </summary>
private void addBatch(string query, List<Batch> batchSet, IFileStreamFactory outputFactory)
{
batchSet.Add(new Batch(query, null, batchSet.Count, outputFactory));
}
#endregion
#region IDisposable Implementation
@@ -403,6 +505,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
disposed = true;
}
/// <summary>
/// Does this connection support XML Execution plans
/// </summary>
private bool DoesSupportExecutionPlan(ConnectionInfo connectionInfo) {
// Determining which execution plan options may be applied (may be added to for pre-yukon support)
return (!connectionInfo.IsSqlDW && connectionInfo.MajorVersion >= 9);
}
#endregion
}
}

View File

@@ -126,6 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
serviceHost.SetRequestHandler(QueryCancelRequest.Type, HandleCancelRequest);
serviceHost.SetRequestHandler(SaveResultsAsCsvRequest.Type, HandleSaveResultsAsCsvRequest);
serviceHost.SetRequestHandler(SaveResultsAsJsonRequest.Type, HandleSaveResultsAsJsonRequest);
serviceHost.SetRequestHandler(QueryExecutionPlanRequest.Type, HandleExecutionPlanRequest);
// Register handler for shutdown event
serviceHost.RegisterShutdownTask((shutdownParams, requestContext) =>
@@ -208,6 +209,36 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Handles a request to get an execution plan
/// </summary>
public async Task HandleExecutionPlanRequest(QueryExecutionPlanParams planParams,
RequestContext<QueryExecutionPlanResult> requestContext)
{
try
{
// Attempt to load the query
Query query;
if (!ActiveQueries.TryGetValue(planParams.OwnerUri, out query))
{
await requestContext.SendError(SR.QueryServiceRequestsNoQuery);
return;
}
// Retrieve the requested execution plan and return it
var result = new QueryExecutionPlanResult
{
ExecutionPlan = await query.GetExecutionPlan(planParams.BatchIndex, planParams.ResultSetIndex)
};
await requestContext.SendResult(result);
}
catch (Exception e)
{
// This was unexpected, so send back as error
await requestContext.SendError(e.Message);
}
}
/// <summary>
/// Handles a request to dispose of this query
/// </summary>
@@ -334,6 +365,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Retrieve the current settings for executing the query with
QueryExecutionSettings settings = WorkspaceService.CurrentSettings.QueryExecutionSettings;
// Apply execution parameter settings
settings.ExecutionPlanOptions = executeParams.ExecutionPlanOptions;
// Get query text from the workspace.
ScriptFile queryFile = WorkspaceService.Workspace.GetFile(executeParams.OwnerUri);
@@ -425,6 +459,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
BatchSummary = b.Summary,
OwnerUri = executeParams.OwnerUri
};
await requestContext.SendEvent(QueryExecuteBatchStartEvent.Type, eventParams);
};
query.BatchStarted += batchStartCallback;
@@ -436,6 +471,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
BatchSummary = b.Summary,
OwnerUri = executeParams.OwnerUri
};
await requestContext.SendEvent(QueryExecuteBatchCompleteEvent.Type, eventParams);
};
query.BatchCompleted += batchCompleteCallback;

View File

@@ -27,6 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Column names of 'for xml' and 'for json' queries
private const string NameOfForXMLColumn = "XML_F52E2B61-18A1-11d1-B105-00805F49916B";
private const string NameOfForJSONColumn = "JSON_F52E2B61-18A1-11d1-B105-00805F49916B";
private const string YukonXmlShowPlanColumn = "Microsoft SQL Server 2005 XML Showplan";
#endregion
@@ -68,6 +69,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary>
private readonly string outputFileName;
/// <summary>
/// The special action which applied to this result set
/// </summary>
private SpecialAction specialAction;
#endregion
/// <summary>
@@ -89,6 +95,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Initialize the storage
outputFileName = factory.CreateFile();
fileOffsets = new LongList<long>();
specialAction = new SpecialAction();
// Store the factory
fileStreamFactory = factory;
@@ -165,7 +172,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
ColumnInfo = Columns,
Id = Id,
BatchId = BatchId,
RowCount = RowCount
RowCount = RowCount,
SpecialAction = ProcessSpecialAction()
};
}
}
@@ -236,6 +245,51 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
});
}
/// <summary>
/// Generates the execution plan from the table returned
/// </summary>
/// <returns>An execution plan object</returns>
public Task<ExecutionPlan> GetExecutionPlan()
{
// Proccess the action just incase is hasn't been yet
ProcessSpecialAction();
// Sanity check to make sure that the results have been read beforehand
if (!hasBeenRead)
{
throw new InvalidOperationException(SR.QueryServiceResultSetNotRead);
}
// Check that we this result set contains a showplan
else if (!specialAction.ExpectYukonXMLShowPlan)
{
throw new Exception(SR.QueryServiceExecutionPlanNotFound);
}
return Task.Factory.StartNew(() =>
{
string content = null;
string format = null;
using (IFileStreamReader fileStreamReader = fileStreamFactory.GetReader(outputFileName))
{
// Determine the format and get the first col/row of XML
content = fileStreamReader.ReadRow(0, Columns)[0].DisplayValue;
if (specialAction.ExpectYukonXMLShowPlan)
{
format = "xml";
}
}
return new ExecutionPlan
{
Format = format,
Content = content
};
});
}
/// <summary>
/// Reads from the reader until there are no more results to read
/// </summary>
@@ -436,6 +490,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Determine the special action, if any, for this result set
/// </summary>
private SpecialAction ProcessSpecialAction()
{
// Check if this result set is a showplan
if (dataReader.Columns.Length == 1 && string.Compare(dataReader.Columns[0].ColumnName, YukonXmlShowPlanColumn, StringComparison.OrdinalIgnoreCase) == 0)
{
specialAction.ExpectYukonXMLShowPlan = true;
}
return specialAction;
}
#endregion
}
}

View File

@@ -0,0 +1,80 @@
//
// 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.QueryExecution
{
/// <summary>
/// Class that represents a Special Action which occured by user request during the query
/// </summary>
public class SpecialAction {
#region Private Class variables
// Underlying representation as bitwise flags to simplify logic
[Flags]
private enum ActionFlags
{
None = 0,
// All added options must be powers of 2
ExpectYukonXmlShowPlan = 1
}
private ActionFlags flags;
#endregion
/// <summary>
/// The type of XML execution plan that is contained with in a result set
/// </summary>
public SpecialAction()
{
flags = ActionFlags.None;
}
#region Public Functions
/// <summary>
/// No Special action performed
/// </summary>
public bool None
{
get { return flags == ActionFlags.None; }
set
{
flags = ActionFlags.None;
}
}
/// <summary>
/// Contains an XML execution plan result set
/// </summary>
public bool ExpectYukonXMLShowPlan
{
get { return flags.HasFlag(ActionFlags.ExpectYukonXmlShowPlan); }
set
{
if (value)
{
// OR flags with value to apply
flags |= ActionFlags.ExpectYukonXmlShowPlan;
}
else
{
// AND flags with the inverse of the value we want to remove
flags &= ~(ActionFlags.ExpectYukonXmlShowPlan);
}
}
}
/// <summary>
/// Aggregate this special action with the input
/// </summary>
public void CombineSpecialAction(SpecialAction action)
{
flags |= action.flags;
}
#endregion
};
}