mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -60,5 +60,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
|||||||
/// Returns true is the db connection is to a SQL db
|
/// Returns true is the db connection is to a SQL db
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsAzure { get; set; }
|
public bool IsAzure { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the sql connection is to a DW instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSqlDW { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the major version number of the db we are connected to
|
||||||
|
/// </summary>
|
||||||
|
public int MajorVersion { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
{
|
{
|
||||||
@@ -292,6 +293,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
|||||||
OsVersion = serverInfo.OsVersion
|
OsVersion = serverInfo.OsVersion
|
||||||
};
|
};
|
||||||
connectionInfo.IsAzure = serverInfo.IsCloud;
|
connectionInfo.IsAzure = serverInfo.IsCloud;
|
||||||
|
connectionInfo.MajorVersion = serverInfo.ServerMajorVersion;
|
||||||
|
connectionInfo.IsSqlDW = (serverInfo.EngineEditionId == (int)DatabaseEngineEdition.SqlDataWarehouse);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<ResultSet> resultSets;
|
private readonly List<ResultSet> resultSets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Special action which this batch performed
|
||||||
|
/// </summary>
|
||||||
|
private SpecialAction specialAction;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
internal Batch(string batchText, SelectionData selection, int ordinalId, IFileStreamFactory outputFileFactory)
|
internal Batch(string batchText, SelectionData selection, int ordinalId, IFileStreamFactory outputFileFactory)
|
||||||
@@ -72,6 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
Id = ordinalId;
|
Id = ordinalId;
|
||||||
resultSets = new List<ResultSet>();
|
resultSets = new List<ResultSet>();
|
||||||
this.outputFileFactory = outputFileFactory;
|
this.outputFileFactory = outputFileFactory;
|
||||||
|
specialAction = new SpecialAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
@@ -201,6 +207,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
summary.ResultSetSummaries = ResultSummaries;
|
summary.ResultSetSummaries = ResultSummaries;
|
||||||
summary.ExecutionEnd = ExecutionEndTimeStamp;
|
summary.ExecutionEnd = ExecutionEndTimeStamp;
|
||||||
summary.ExecutionElapsed = ExecutionElapsedTime;
|
summary.ExecutionElapsed = ExecutionElapsedTime;
|
||||||
|
summary.SpecialAction = ProcessResultSetSpecialActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
@@ -370,6 +377,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
return targetResultSet.GetSubset(startRow, rowCount);
|
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>
|
/// <summary>
|
||||||
/// Saves a result to a file format selected by the user
|
/// Saves a result to a file format selected by the user
|
||||||
/// </summary>
|
/// </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
|
#endregion
|
||||||
|
|
||||||
#region IDisposable Implementation
|
#region IDisposable Implementation
|
||||||
|
|||||||
@@ -44,5 +44,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
/// The summaries of the result sets inside the batch
|
/// The summaries of the result sets inside the batch
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ResultSetSummary[] ResultSetSummaries { get; set; }
|
public ResultSetSummary[] ResultSetSummaries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special action of the batch
|
||||||
|
/// </summary>
|
||||||
|
public SpecialAction SpecialAction { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
/// URI for the editor that is asking for the query execute
|
/// URI for the editor that is asking for the query execute
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string OwnerUri { get; set; }
|
public string OwnerUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execution plan options
|
||||||
|
/// </summary>
|
||||||
|
public ExecutionPlanOptions ExecutionPlanOptions { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,5 +29,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
/// Details about the columns that are provided as solutions
|
/// Details about the columns that are provided as solutions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DbColumnWrapper[] ColumnInfo { get; set; }
|
public DbColumnWrapper[] ColumnInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special action definition of the result set
|
||||||
|
/// </summary>
|
||||||
|
public SpecialAction SpecialAction { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||||
{
|
{
|
||||||
@@ -51,6 +52,36 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool hasExecuteBeenCalled;
|
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
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -72,6 +103,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
QueryText = queryText;
|
QueryText = queryText;
|
||||||
editorConnection = connection;
|
editorConnection = connection;
|
||||||
cancellationSource = new CancellationTokenSource();
|
cancellationSource = new CancellationTokenSource();
|
||||||
|
querySettings = settings;
|
||||||
|
streamOutputFactory = outputFactory;
|
||||||
|
|
||||||
// Process the query into batches
|
// Process the query into batches
|
||||||
ParseResult parseResult = Parser.Parse(queryText, new ParseOptions
|
ParseResult parseResult = Parser.Parse(queryText, new ParseOptions
|
||||||
@@ -89,7 +122,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
batch.EndLocation.LineNumber - 1,
|
batch.EndLocation.LineNumber - 1,
|
||||||
batch.EndLocation.ColumnNumber - 1),
|
batch.EndLocation.ColumnNumber - 1),
|
||||||
index, outputFactory));
|
index, outputFactory));
|
||||||
|
|
||||||
Batches = batchSelection.ToArray();
|
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
|
#region Events
|
||||||
@@ -145,11 +199,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// <param name="q">The query that completed</param>
|
/// <param name="q">The query that completed</param>
|
||||||
public delegate Task QueryAsyncEventHandler(Query q);
|
public delegate Task QueryAsyncEventHandler(Query q);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The batches which should run before the user batches
|
||||||
|
/// </summary>
|
||||||
|
internal List<Batch> BeforeBatches { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The batches underneath this query
|
/// The batches underneath this query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Batch[] Batches { get; set; }
|
internal Batch[] Batches { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The batches which should run after the user batches
|
||||||
|
/// </summary>
|
||||||
|
internal List<Batch> AfterBatches { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The summaries of the batches underneath this query
|
/// The summaries of the batches underneath this query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -241,6 +305,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
return Batches[batchIndex].GetSubset(resultSetIndex, startRow, rowCount);
|
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>
|
/// <summary>
|
||||||
/// Saves the requested results to a file format of the user's choice
|
/// Saves the requested results to a file format of the user's choice
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -316,9 +397,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
|
|
||||||
try
|
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
|
// We need these to execute synchronously, otherwise the user will be very unhappy
|
||||||
foreach (Batch b in Batches)
|
foreach (Batch b in Batches)
|
||||||
{
|
{
|
||||||
|
// Add completion callbacks
|
||||||
b.BatchStart += BatchStarted;
|
b.BatchStart += BatchStarted;
|
||||||
b.BatchCompletion += BatchCompleted;
|
b.BatchCompletion += BatchCompleted;
|
||||||
b.BatchMessageSent += BatchMessageSent;
|
b.BatchMessageSent += BatchMessageSent;
|
||||||
@@ -326,6 +414,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
await b.Execute(conn, cancellationSource.Token);
|
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
|
// Call the query execution callback
|
||||||
if (QueryCompleted != null)
|
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
|
#endregion
|
||||||
|
|
||||||
#region IDisposable Implementation
|
#region IDisposable Implementation
|
||||||
@@ -403,6 +505,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
disposed = true;
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
serviceHost.SetRequestHandler(QueryCancelRequest.Type, HandleCancelRequest);
|
serviceHost.SetRequestHandler(QueryCancelRequest.Type, HandleCancelRequest);
|
||||||
serviceHost.SetRequestHandler(SaveResultsAsCsvRequest.Type, HandleSaveResultsAsCsvRequest);
|
serviceHost.SetRequestHandler(SaveResultsAsCsvRequest.Type, HandleSaveResultsAsCsvRequest);
|
||||||
serviceHost.SetRequestHandler(SaveResultsAsJsonRequest.Type, HandleSaveResultsAsJsonRequest);
|
serviceHost.SetRequestHandler(SaveResultsAsJsonRequest.Type, HandleSaveResultsAsJsonRequest);
|
||||||
|
serviceHost.SetRequestHandler(QueryExecutionPlanRequest.Type, HandleExecutionPlanRequest);
|
||||||
|
|
||||||
// Register handler for shutdown event
|
// Register handler for shutdown event
|
||||||
serviceHost.RegisterShutdownTask((shutdownParams, requestContext) =>
|
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>
|
/// <summary>
|
||||||
/// Handles a request to dispose of this query
|
/// Handles a request to dispose of this query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -334,6 +365,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
// Retrieve the current settings for executing the query with
|
// Retrieve the current settings for executing the query with
|
||||||
QueryExecutionSettings settings = WorkspaceService.CurrentSettings.QueryExecutionSettings;
|
QueryExecutionSettings settings = WorkspaceService.CurrentSettings.QueryExecutionSettings;
|
||||||
|
|
||||||
|
// Apply execution parameter settings
|
||||||
|
settings.ExecutionPlanOptions = executeParams.ExecutionPlanOptions;
|
||||||
|
|
||||||
// Get query text from the workspace.
|
// Get query text from the workspace.
|
||||||
ScriptFile queryFile = WorkspaceService.Workspace.GetFile(executeParams.OwnerUri);
|
ScriptFile queryFile = WorkspaceService.Workspace.GetFile(executeParams.OwnerUri);
|
||||||
|
|
||||||
@@ -425,6 +459,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
BatchSummary = b.Summary,
|
BatchSummary = b.Summary,
|
||||||
OwnerUri = executeParams.OwnerUri
|
OwnerUri = executeParams.OwnerUri
|
||||||
};
|
};
|
||||||
|
|
||||||
await requestContext.SendEvent(QueryExecuteBatchStartEvent.Type, eventParams);
|
await requestContext.SendEvent(QueryExecuteBatchStartEvent.Type, eventParams);
|
||||||
};
|
};
|
||||||
query.BatchStarted += batchStartCallback;
|
query.BatchStarted += batchStartCallback;
|
||||||
@@ -436,6 +471,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
BatchSummary = b.Summary,
|
BatchSummary = b.Summary,
|
||||||
OwnerUri = executeParams.OwnerUri
|
OwnerUri = executeParams.OwnerUri
|
||||||
};
|
};
|
||||||
|
|
||||||
await requestContext.SendEvent(QueryExecuteBatchCompleteEvent.Type, eventParams);
|
await requestContext.SendEvent(QueryExecuteBatchCompleteEvent.Type, eventParams);
|
||||||
};
|
};
|
||||||
query.BatchCompleted += batchCompleteCallback;
|
query.BatchCompleted += batchCompleteCallback;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
// Column names of 'for xml' and 'for json' queries
|
// Column names of 'for xml' and 'for json' queries
|
||||||
private const string NameOfForXMLColumn = "XML_F52E2B61-18A1-11d1-B105-00805F49916B";
|
private const string NameOfForXMLColumn = "XML_F52E2B61-18A1-11d1-B105-00805F49916B";
|
||||||
private const string NameOfForJSONColumn = "JSON_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
|
#endregion
|
||||||
|
|
||||||
@@ -68,6 +69,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly string outputFileName;
|
private readonly string outputFileName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The special action which applied to this result set
|
||||||
|
/// </summary>
|
||||||
|
private SpecialAction specialAction;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -89,6 +95,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
// Initialize the storage
|
// Initialize the storage
|
||||||
outputFileName = factory.CreateFile();
|
outputFileName = factory.CreateFile();
|
||||||
fileOffsets = new LongList<long>();
|
fileOffsets = new LongList<long>();
|
||||||
|
specialAction = new SpecialAction();
|
||||||
|
|
||||||
// Store the factory
|
// Store the factory
|
||||||
fileStreamFactory = factory;
|
fileStreamFactory = factory;
|
||||||
@@ -165,7 +172,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
ColumnInfo = Columns,
|
ColumnInfo = Columns,
|
||||||
Id = Id,
|
Id = Id,
|
||||||
BatchId = BatchId,
|
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>
|
/// <summary>
|
||||||
/// Reads from the reader until there are no more results to read
|
/// Reads from the reader until there are no more results to read
|
||||||
/// </summary>
|
/// </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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,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 Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -26,6 +29,16 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const int DefaultMaxXmlCharsToStore = 2097152; // 2 MB - QE default
|
private const int DefaultMaxXmlCharsToStore = 2097152; // 2 MB - QE default
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default selection of returning an actual XML showplan with all batches
|
||||||
|
/// Do not return any execution plan by default
|
||||||
|
/// </summary>
|
||||||
|
private ExecutionPlanOptions DefaultExecutionPlanOptions = new ExecutionPlanOptions()
|
||||||
|
{
|
||||||
|
IncludeActualExecutionPlanXml = false,
|
||||||
|
IncludeEstimatedExecutionPlanXml = false
|
||||||
|
};
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Member Variables
|
#region Member Variables
|
||||||
@@ -36,6 +49,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
|
|
||||||
private int? maxXmlCharsToStore;
|
private int? maxXmlCharsToStore;
|
||||||
|
|
||||||
|
private ExecutionPlanOptions? executionPlanOptions;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -61,6 +76,12 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
set { maxXmlCharsToStore = value; }
|
set { maxXmlCharsToStore = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ExecutionPlanOptions ExecutionPlanOptions
|
||||||
|
{
|
||||||
|
get { return executionPlanOptions ?? DefaultExecutionPlanOptions; }
|
||||||
|
set { executionPlanOptions = value; }
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Public Methods
|
#region Public Methods
|
||||||
@@ -74,6 +95,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
BatchSeparator = newSettings.BatchSeparator;
|
BatchSeparator = newSettings.BatchSeparator;
|
||||||
MaxCharsToStore = newSettings.MaxCharsToStore;
|
MaxCharsToStore = newSettings.MaxCharsToStore;
|
||||||
MaxXmlCharsToStore = newSettings.MaxXmlCharsToStore;
|
MaxXmlCharsToStore = newSettings.MaxXmlCharsToStore;
|
||||||
|
ExecutionPlanOptions = newSettings.ExecutionPlanOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -365,6 +365,14 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string QueryServiceExecutionPlanNotFound
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Keys.GetString(Keys.QueryServiceExecutionPlanNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string PeekDefinitionNoResultsError
|
public static string PeekDefinitionNoResultsError
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -623,6 +631,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
public const string QueryServiceResultSetNoColumnSchema = "QueryServiceResultSetNoColumnSchema";
|
public const string QueryServiceResultSetNoColumnSchema = "QueryServiceResultSetNoColumnSchema";
|
||||||
|
|
||||||
|
|
||||||
|
public const string QueryServiceExecutionPlanNotFound = "QueryServiceExecutionPlanNotFound";
|
||||||
|
|
||||||
|
|
||||||
public const string PeekDefinitionAzureError = "PeekDefinitionAzureError";
|
public const string PeekDefinitionAzureError = "PeekDefinitionAzureError";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -325,6 +325,10 @@
|
|||||||
<value>Could not retrieve column schema for result set</value>
|
<value>Could not retrieve column schema for result set</value>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="QueryServiceExecutionPlanNotFound" xml:space="preserve">
|
||||||
|
<value>Could not retrieve an execution plan from the result set </value>
|
||||||
|
<comment></comment>
|
||||||
|
</data>
|
||||||
<data name="PeekDefinitionAzureError" xml:space="preserve">
|
<data name="PeekDefinitionAzureError" xml:space="preserve">
|
||||||
<value>This feature is currently not supported on Azure SQL DB and Data Warehouse: {0}</value>
|
<value>This feature is currently not supported on Azure SQL DB and Data Warehouse: {0}</value>
|
||||||
<comment>.
|
<comment>.
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ QueryServiceResultSetRowCountOutOfRange = Row count must be a positive integer
|
|||||||
|
|
||||||
QueryServiceResultSetNoColumnSchema = Could not retrieve column schema for result set
|
QueryServiceResultSetNoColumnSchema = Could not retrieve column schema for result set
|
||||||
|
|
||||||
|
QueryServiceExecutionPlanNotFound = Could not retrieve an execution plan from the result set
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
# Language Service
|
# Language Service
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
return batch;
|
return batch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Batch GetExecutedBatchWithExecutionPlan()
|
||||||
|
{
|
||||||
|
Batch batch = new Batch(StandardQuery, SubsectionDocument, 1, GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
||||||
|
batch.Execute(CreateTestConnection(new[] {GetExecutionPlanTestData()}, false), CancellationToken.None).Wait();
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
|
||||||
public static Query GetBasicExecutedQuery()
|
public static Query GetBasicExecutedQuery()
|
||||||
{
|
{
|
||||||
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
|
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
|
||||||
@@ -81,6 +88,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Query GetBasicExecutedQuery(QueryExecutionSettings querySettings)
|
||||||
|
{
|
||||||
|
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
|
||||||
|
Query query = new Query(StandardQuery, ci, querySettings, GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
||||||
|
query.Execute();
|
||||||
|
query.ExecutionTask.Wait();
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
public static Dictionary<string, string>[] GetTestData(int columns, int rows)
|
public static Dictionary<string, string>[] GetTestData(int columns, int rows)
|
||||||
{
|
{
|
||||||
Dictionary<string, string>[] output = new Dictionary<string, string>[rows];
|
Dictionary<string, string>[] output = new Dictionary<string, string>[rows];
|
||||||
@@ -97,6 +113,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Dictionary<string, string>[] GetExecutionPlanTestData()
|
||||||
|
{
|
||||||
|
Dictionary<string, string>[] output = new Dictionary<string, string>[1];
|
||||||
|
int col = 0;
|
||||||
|
int row = 0;
|
||||||
|
Dictionary<string, string> rowDictionary = new Dictionary<string, string>();
|
||||||
|
rowDictionary.Add(string.Format("Microsoft SQL Server 2005 XML Showplan", col), string.Format("Execution Plan", col, row));
|
||||||
|
output[row] = rowDictionary;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
public static Dictionary<string, string>[][] GetTestDataSet(int dataSets)
|
public static Dictionary<string, string>[][] GetTestDataSet(int dataSets)
|
||||||
{
|
{
|
||||||
List<Dictionary<string, string>[]> output = new List<Dictionary<string, string>[]>();
|
List<Dictionary<string, string>[]> output = new List<Dictionary<string, string>[]>();
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
//
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||||
|
{
|
||||||
|
public class ExecutionPlanTests
|
||||||
|
{
|
||||||
|
#region ResultSet Class Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecutionPlanValid()
|
||||||
|
{
|
||||||
|
// Setup:
|
||||||
|
// ... I have a batch that has been executed with a execution plan
|
||||||
|
Batch b = Common.GetExecutedBatchWithExecutionPlan();
|
||||||
|
|
||||||
|
// If:
|
||||||
|
// ... I have a result set and I ask for a valid execution plan
|
||||||
|
ResultSet planResultSet = b.ResultSets.First();
|
||||||
|
ExecutionPlan plan = planResultSet.GetExecutionPlan().Result;
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... I should get the execution plan back
|
||||||
|
Assert.Equal("xml", plan.Format);
|
||||||
|
Assert.Contains("Execution Plan", plan.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExecutionPlanInvalid()
|
||||||
|
{
|
||||||
|
// Setup:
|
||||||
|
// ... I have a batch that has been executed
|
||||||
|
Batch b = Common.GetBasicExecutedBatch();
|
||||||
|
|
||||||
|
// If:
|
||||||
|
// ... I have a result set and I ask for an execution plan that doesn't exist
|
||||||
|
ResultSet planResultSet = b.ResultSets.First();
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... It should throw an exception
|
||||||
|
Assert.ThrowsAsync<Exception>(async () => await planResultSet.GetExecutionPlan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Batch Class Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BatchExecutionPlanValidTest()
|
||||||
|
{
|
||||||
|
// If I have an executed batch which has an execution plan
|
||||||
|
Batch b = Common.GetExecutedBatchWithExecutionPlan();
|
||||||
|
|
||||||
|
// ... And I ask for a valid execution plan
|
||||||
|
ExecutionPlan plan = b.GetExecutionPlan(0).Result;
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... I should get the execution plan back
|
||||||
|
Assert.Equal("xml", plan.Format);
|
||||||
|
Assert.Contains("Execution Plan", plan.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BatchExecutionPlanInvalidTest()
|
||||||
|
{
|
||||||
|
// Setup:
|
||||||
|
// ... I have a batch that has been executed without an execution plan
|
||||||
|
Batch b = Common.GetBasicExecutedBatch();
|
||||||
|
|
||||||
|
// If:
|
||||||
|
// ... I ask for an invalid execution plan
|
||||||
|
Assert.ThrowsAsync<Exception>(async () => await b.GetExecutionPlan(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(-1)] // Invalid result set, too low
|
||||||
|
[InlineData(2)] // Invalid result set, too high
|
||||||
|
public void BatchExecutionPlanInvalidParamsTest(int resultSetIndex)
|
||||||
|
{
|
||||||
|
// If I have an executed batch which has an execution plan
|
||||||
|
Batch b = Common.GetExecutedBatchWithExecutionPlan();
|
||||||
|
|
||||||
|
// ... And I ask for an execution plan with an invalid result set index
|
||||||
|
// Then:
|
||||||
|
// ... It should throw an exception
|
||||||
|
Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await b.GetExecutionPlan(resultSetIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Query Class Tests
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(-1)] // Invalid batch, too low
|
||||||
|
[InlineData(2)] // Invalid batch, too high
|
||||||
|
public void QueryExecutionPlanInvalidParamsTest(int batchIndex)
|
||||||
|
{
|
||||||
|
// Setup query settings
|
||||||
|
QueryExecutionSettings querySettings = new QueryExecutionSettings();
|
||||||
|
querySettings.ExecutionPlanOptions = new ExecutionPlanOptions()
|
||||||
|
{
|
||||||
|
IncludeActualExecutionPlanXml = false,
|
||||||
|
IncludeEstimatedExecutionPlanXml = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// If I have an executed query
|
||||||
|
Query q = Common.GetBasicExecutedQuery(querySettings);
|
||||||
|
|
||||||
|
// ... And I ask for a subset with an invalid result set index
|
||||||
|
// Then:
|
||||||
|
// ... It should throw an exception
|
||||||
|
Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await q.GetExecutionPlan(batchIndex, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Service Intergration Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecutionPlanServiceValidTest()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I have a query that has results in the form of an execution plan
|
||||||
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||||
|
var queryService = Common.GetPrimedExecutionService(new[] {Common.GetExecutionPlanTestData()}, true, false, workspaceService);
|
||||||
|
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||||
|
executeParams.ExecutionPlanOptions = new ExecutionPlanOptions()
|
||||||
|
{
|
||||||
|
IncludeActualExecutionPlanXml = false,
|
||||||
|
IncludeEstimatedExecutionPlanXml = true
|
||||||
|
};
|
||||||
|
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||||
|
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||||
|
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||||
|
|
||||||
|
// ... And I then ask for a valid execution plan
|
||||||
|
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Common.OwnerUri, BatchIndex = 0, ResultSetIndex = 0 };
|
||||||
|
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||||
|
.AddResultValidation(r =>
|
||||||
|
{
|
||||||
|
// Then: Messages should be null and execution plan should not be null
|
||||||
|
Assert.NotNull(r.ExecutionPlan);
|
||||||
|
}).Complete();
|
||||||
|
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||||
|
executionPlanRequest.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecutionPlanServiceMissingQueryTest()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I ask for an execution plan for a file that hasn't executed a query
|
||||||
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||||
|
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||||
|
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||||
|
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||||
|
.AddErrorValidation<string>(r =>
|
||||||
|
{
|
||||||
|
// Then: It should return a populated error
|
||||||
|
Assert.NotNull(r);
|
||||||
|
}).Complete();
|
||||||
|
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||||
|
executionPlanRequest.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecutionPlanServiceUnexecutedQueryTest()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I have a query that hasn't finished executing (doesn't matter what)
|
||||||
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||||
|
var queryService = Common.GetPrimedExecutionService(new[] { Common.GetExecutionPlanTestData() }, true, false, workspaceService);
|
||||||
|
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||||
|
executeParams.ExecutionPlanOptions = new ExecutionPlanOptions()
|
||||||
|
{
|
||||||
|
IncludeActualExecutionPlanXml = false,
|
||||||
|
IncludeEstimatedExecutionPlanXml = true
|
||||||
|
};
|
||||||
|
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||||
|
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||||
|
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||||
|
queryService.ActiveQueries[Common.OwnerUri].Batches[0].ResultSets[0].hasBeenRead = false;
|
||||||
|
|
||||||
|
// ... And I then ask for a valid execution plan from it
|
||||||
|
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||||
|
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||||
|
.AddErrorValidation<string>(r =>
|
||||||
|
{
|
||||||
|
// Then: It should return a populated error
|
||||||
|
Assert.NotNull(r);
|
||||||
|
}).Complete();
|
||||||
|
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||||
|
executionPlanRequest.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecutionPlanServiceOutOfRangeSubsetTest()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I have a query that doesn't have any result sets
|
||||||
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||||
|
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||||
|
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||||
|
executeParams.ExecutionPlanOptions = new ExecutionPlanOptions()
|
||||||
|
{
|
||||||
|
IncludeActualExecutionPlanXml = false,
|
||||||
|
IncludeEstimatedExecutionPlanXml = true
|
||||||
|
};
|
||||||
|
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||||
|
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||||
|
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||||
|
|
||||||
|
// ... And I then ask for an execution plan from a result set
|
||||||
|
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||||
|
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||||
|
.AddErrorValidation<string>(r =>
|
||||||
|
{
|
||||||
|
// Then: It should return a populated error
|
||||||
|
Assert.NotNull(r);
|
||||||
|
}).Complete();
|
||||||
|
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||||
|
executionPlanRequest.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// 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.QueryExecution;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||||
|
{
|
||||||
|
public class SpecialActionTests
|
||||||
|
{
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SpecialActionInstantiation()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I create a special action object
|
||||||
|
var specialAction = new SpecialAction();
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... The special action should be set to none and only none
|
||||||
|
Assert.Equal(true, specialAction.None);
|
||||||
|
Assert.Equal(false, specialAction.ExpectYukonXMLShowPlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SpecialActionNoneProperty()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I create a special action object and add properties but set it back to none
|
||||||
|
var specialAction = new SpecialAction();
|
||||||
|
specialAction.ExpectYukonXMLShowPlan = true;
|
||||||
|
specialAction.None = true;
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... The special action should be set to none and only none
|
||||||
|
Assert.Equal(true, specialAction.None);
|
||||||
|
Assert.Equal(false, specialAction.ExpectYukonXMLShowPlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SpecialActionExpectYukonXmlShowPlanReset()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I create a special action object and add properties but set the property back to false
|
||||||
|
var specialAction = new SpecialAction();
|
||||||
|
specialAction.ExpectYukonXMLShowPlan = true;
|
||||||
|
specialAction.ExpectYukonXMLShowPlan = false;
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... The special action should be set to none and only none
|
||||||
|
Assert.Equal(true, specialAction.None);
|
||||||
|
Assert.Equal(false, specialAction.ExpectYukonXMLShowPlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SpecialActionCombiningProperties()
|
||||||
|
{
|
||||||
|
// If:
|
||||||
|
// ... I create a special action object and add properties and combine with the same property
|
||||||
|
var specialAction = new SpecialAction();
|
||||||
|
specialAction.ExpectYukonXMLShowPlan = true;
|
||||||
|
|
||||||
|
var specialAction2 = new SpecialAction();
|
||||||
|
specialAction2.ExpectYukonXMLShowPlan = true;
|
||||||
|
|
||||||
|
specialAction.CombineSpecialAction(specialAction2);
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// ... The special action should be set to none and only none
|
||||||
|
Assert.Equal(false, specialAction.None);
|
||||||
|
Assert.Equal(true, specialAction.ExpectYukonXMLShowPlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user