Query execution settings support (#812)

* WIP

* WIP 3

* WIP 3

* Additional query settings updates

* Add settings tets

* Address code review feeback

* Updates from testing
This commit is contained in:
Karl Burtram
2019-05-16 15:21:17 -07:00
committed by GitHub
parent db70f421ae
commit 151a2de625
14 changed files with 1119 additions and 69 deletions

View File

@@ -109,6 +109,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary>
internal ConcurrentDictionary<string, Query> ActiveQueries => queries.Value;
/// <summary>
/// The collection of query execution options
/// </summary>
internal ConcurrentDictionary<string, QueryExecutionSettings> ActiveQueryExecutionSettings => queryExecutionSettings.Value;
/// <summary>
/// Internal task for testability
/// </summary>
@@ -127,6 +132,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
private readonly Lazy<ConcurrentDictionary<string, Query>> queries =
new Lazy<ConcurrentDictionary<string, Query>>(() => new ConcurrentDictionary<string, Query>());
/// <summary>
/// Internal storage of active query settings
/// </summary>
private readonly Lazy<ConcurrentDictionary<string, QueryExecutionSettings>> queryExecutionSettings =
new Lazy<ConcurrentDictionary<string, QueryExecutionSettings>>(() => new ConcurrentDictionary<string, QueryExecutionSettings>());
/// <summary>
/// Settings that will be used to execute queries. Internal for unit testing
/// </summary>
@@ -165,6 +176,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
serviceHost.SetRequestHandler(SaveResultsAsXmlRequest.Type, HandleSaveResultsAsXmlRequest);
serviceHost.SetRequestHandler(QueryExecutionPlanRequest.Type, HandleExecutionPlanRequest);
serviceHost.SetRequestHandler(SimpleExecuteRequest.Type, HandleSimpleExecuteRequest);
serviceHost.SetRequestHandler(QueryExecutionOptionsRequest.Type, HandleQueryExecutionOptionsRequest);
// Register the file open update handler
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocCloseCallback(HandleDidCloseTextDocumentNotification);
// Register handler for shutdown event
serviceHost.RegisterShutdownTask((shutdownParams, requestContext) =>
@@ -203,7 +218,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Use the internal handler to launch the query
WorkTask = Task.Run(async () =>
{
await InterServiceExecuteQuery(executeParams, null, requestContext, queryCreateSuccessAction, queryCreateFailureAction, null, null);
await InterServiceExecuteQuery(
executeParams,
null,
requestContext,
queryCreateSuccessAction,
queryCreateFailureAction,
null,
null,
isQueryEditor(executeParams.OwnerUri));
});
}
catch (Exception ex)
@@ -351,6 +374,33 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Handles a request to set query execution options
/// </summary>
internal async Task HandleQueryExecutionOptionsRequest(QueryExecutionOptionsParams queryExecutionOptionsParams,
RequestContext<bool> requestContext)
{
try
{
string uri = queryExecutionOptionsParams.OwnerUri;
if (ActiveQueryExecutionSettings.ContainsKey(uri))
{
QueryExecutionSettings settings;
ActiveQueryExecutionSettings.TryRemove(uri, out settings);
}
ActiveQueryExecutionSettings.TryAdd(uri, queryExecutionOptionsParams.Options);
await requestContext.SendResult(true);
}
catch (Exception e)
{
// This was unexpected, so send back as error
await requestContext.SendError(e.Message);
}
}
/// <summary>
/// Handles a request to get an execution plan
/// </summary>
@@ -524,7 +574,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
Func<Query, Task<bool>> queryCreateSuccessFunc,
Func<string, Task> queryCreateFailFunc,
Query.QueryAsyncEventHandler querySuccessFunc,
Query.QueryAsyncErrorEventHandler queryFailureFunc)
Query.QueryAsyncErrorEventHandler queryFailureFunc,
bool applyExecutionSettings = false)
{
Validate.IsNotNull(nameof(executeParams), executeParams);
Validate.IsNotNull(nameof(queryEventSender), queryEventSender);
@@ -533,7 +584,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
try
{
// Get a new active query
newQuery = CreateQuery(executeParams, connInfo);
newQuery = CreateQuery(executeParams, connInfo, applyExecutionSettings);
if (queryCreateSuccessFunc != null && !await queryCreateSuccessFunc(newQuery))
{
// The callback doesn't want us to continue, for some reason
@@ -615,19 +666,49 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
subsetParams.RowsStartIndex, subsetParams.RowsCount);
}
/// <summary>
/// Handle the file open notification
/// </summary>
/// <param name="scriptFile"></param>
/// <param name="eventContext"></param>
/// <returns></returns>
public async Task HandleDidCloseTextDocumentNotification(
string uri,
ScriptFile scriptFile,
EventContext eventContext)
{
try
{
// remove any query execution settings when an editor is closed
if (this.ActiveQueryExecutionSettings.ContainsKey(uri))
{
QueryExecutionSettings settings;
this.ActiveQueryExecutionSettings.TryRemove(uri, out settings);
}
}
catch (Exception ex)
{
Logger.Write(TraceEventType.Error, "Unknown error " + ex.ToString());
}
await Task.FromResult(true);
}
#endregion
#region Private Helpers
private Query CreateQuery(ExecuteRequestParamsBase executeParams, ConnectionInfo connInfo)
private Query CreateQuery(
ExecuteRequestParamsBase executeParams,
ConnectionInfo connInfo,
bool applyExecutionSettings)
{
// Attempt to get the connection for the editor
ConnectionInfo connectionInfo;
if (connInfo != null) {
if (connInfo != null)
{
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);
}
@@ -643,15 +724,39 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
oldQuery.Dispose();
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
}
// Retrieve the current settings for executing the query with
QueryExecutionSettings settings = Settings.QueryExecutionSettings;
// Apply execution parameter settings
settings.ExecutionPlanOptions = executeParams.ExecutionPlanOptions;
// check if there are active query execution settings for the editor, otherwise, use the global settings
QueryExecutionSettings settings;
if (this.ActiveQueryExecutionSettings.TryGetValue(executeParams.OwnerUri, out settings))
{
// 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
// request a plan then enable the query option
ExecutionPlanOptions executionPlanOptions = executeParams.ExecutionPlanOptions;
if (settings.IncludeActualExecutionPlanXml)
{
executionPlanOptions.IncludeActualExecutionPlanXml = settings.IncludeActualExecutionPlanXml;
}
if (settings.IncludeEstimatedExecutionPlanXml)
{
executionPlanOptions.IncludeEstimatedExecutionPlanXml = settings.IncludeEstimatedExecutionPlanXml;
}
settings.ExecutionPlanOptions = executionPlanOptions;
}
else
{
settings = Settings.QueryExecutionSettings;
settings.ExecutionPlanOptions = executeParams.ExecutionPlanOptions;
}
// If we can't add the query now, it's assumed the query is in progress
Query newQuery = new Query(GetSqlText(executeParams), connectionInfo, settings, BufferFileFactory, executeParams.GetFullColumnSchema);
Query newQuery = new Query(
GetSqlText(executeParams),
connectionInfo,
settings,
BufferFileFactory,
executeParams.GetFullColumnSchema,
applyExecutionSettings);
if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery))
{
newQuery.Dispose();
@@ -949,6 +1054,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
disposed = true;
}
/// <summary>
/// Verify if the URI maps to a query editor document
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
private bool isQueryEditor(string uri)
{
return (!string.IsNullOrWhiteSpace(uri)
&& (uri.StartsWith("untitled:")
|| uri.StartsWith("file:")));
}
~QueryExecutionService()
{
Dispose(false);