Files
sqltoolsservice/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceDriverProvider.cs
Benjamin Russell 857b526553 query/executeString (#217)
This is a small API addition that allows us to execute queries directly as strings. This will make it easier to execute queries outside the confines of a workspace like VS Code.

* Refactor execution requests and events are now named less redundantly and moved into a separate namespace for organization. This is the bulk of the changes.
    * QueryExecuteBatchNotification -> ExecuteRequests/BatchEvents
    * QueryExecuteMessageNotification -> ExecuteRequests/MessageEvent
    * QueryExecuteCompleteNotification -> ExecuteRequests/QueryCompleteEvent
    * QueryExecuteResultSetCompleteNotification -> ExecuteRequests/ResultSetEvents
    * QueryExecuteSubsetRequest -> SubsetRequest.cs
* Creating an inheritance pattern where
    * `ExecuteRequestParamsBase` has execution options and ID for a query execute request
    * `ExecuteDocumentSelectionParams` inherits from `ExecuteRequestParamsBase` and provides a document selection
    * `ExecuteStringParams`  inherits from `ExecuteRequestParamsBase` and provides the query text
* Adding a helper method to get SQL text based on request type
* Through the AWESOME POWER OF POLYMORPHISM, we are able to create a request for executing straight SQL basically for free.
* **Breaking change:** query/execute => query/executeDocumentSelection to make it more obvious what is expected.
* Adding unit tests for the code that gets SQL text

* Refactoring of execute contracts into their own namespace

* Refactoring application

* Adding new request for executing queries as strings

* Adding forgotten string request

* Changing the logic for checking the request param types

* Removing redundant declarations
2017-01-30 15:24:12 -08:00

443 lines
16 KiB
C#

//
// 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.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.Test.Common
{
/// <summary>
/// Service class to execute SQL tools commands using the test driver or calling the service classed directly
/// </summary>
public sealed class TestServiceDriverProvider : IDisposable
{
private bool isRunning = false;
private TestConnectionProfileService testConnectionService;
public TestServiceDriverProvider()
{
Driver = new ServiceTestDriver();
Driver.Start().Wait();
this.isRunning = true;
}
public void Dispose()
{
if (this.isRunning)
{
WaitForExit();
}
}
public void WaitForExit()
{
try
{
this.isRunning = false;
Driver.Stop().Wait();
Console.WriteLine("Successfully killed process.");
}
catch (Exception e)
{
Console.WriteLine($"Exception while waiting for service exit: {e.Message}");
}
}
/// <summary>
/// The driver object used to read/write data to the service
/// </summary>
public ServiceTestDriver Driver
{
get;
private set;
}
public TestConnectionProfileService TestConnectionService
{
get
{
return (testConnectionService = testConnectionService ?? TestConnectionProfileService.Instance);
}
}
private object fileLock = new Object();
/// <summary>
/// Request a new connection to be created
/// </summary>
/// <returns>True if the connection completed successfully</returns>
public async Task<bool> Connect(string ownerUri, ConnectParams connectParams, int timeout = 15000)
{
connectParams.OwnerUri = ownerUri;
var connectResult = await Driver.SendRequest(ConnectionRequest.Type, connectParams);
if (connectResult)
{
var completeEvent = await Driver.WaitForEvent(ConnectionCompleteNotification.Type, timeout);
return !string.IsNullOrEmpty(completeEvent.ConnectionId);
}
else
{
return false;
}
}
/// <summary>
/// Request a new connection to be created for given query
/// </summary>
public async Task<bool> ConnectForQuery(TestServerType serverType, string query, string ownerUri, string databaseName = null, int timeout = 15000)
{
if (!string.IsNullOrEmpty(query))
{
WriteToFile(ownerUri, query);
}
return await Connect(serverType, ownerUri, databaseName, timeout);
}
/// <summary>
/// Request a new connection to be created for url
/// </summary>
public async Task<bool> Connect(TestServerType serverType, string ownerUri, string databaseName = null, int timeout = 15000)
{
var connectParams = GetConnectionParameters(serverType, databaseName);
bool connected = await Connect(ownerUri, connectParams, timeout);
Assert.True(connected, "Connection is successful");
Console.WriteLine($"Connection to {connectParams.Connection.ServerName} is successful");
return connected;
}
/// <summary>
/// Request a disconnect
/// </summary>
public async Task<bool> Disconnect(string ownerUri)
{
var disconnectParams = new DisconnectParams();
disconnectParams.OwnerUri = ownerUri;
var disconnectResult = await Driver.SendRequest(DisconnectRequest.Type, disconnectParams);
return disconnectResult;
}
/// <summary>
/// Request a cancel connect
/// </summary>
public async Task<bool> CancelConnect(string ownerUri)
{
var cancelParams = new CancelConnectParams();
cancelParams.OwnerUri = ownerUri;
return await Driver.SendRequest(CancelConnectRequest.Type, cancelParams);
}
/// <summary>
/// Request a cancel connect
/// </summary>
public async Task<ListDatabasesResponse> ListDatabases(string ownerUri)
{
var listParams = new ListDatabasesParams();
listParams.OwnerUri = ownerUri;
return await Driver.SendRequest(ListDatabasesRequest.Type, listParams);
}
/// <summary>
/// Request the active SQL script is parsed for errors
/// </summary>
public async Task<SubsetResult> RequestQueryExecuteSubset(SubsetParams subsetParams)
{
return await Driver.SendRequest(SubsetRequest.Type, subsetParams);
}
/// <summary>
/// Request the active SQL script is parsed for errors
/// </summary>
public async Task RequestOpenDocumentNotification(DidOpenTextDocumentNotification openParams)
{
await Driver.SendEvent(DidOpenTextDocumentNotification.Type, openParams);
}
/// <summary>
/// Request a configuration change notification
/// </summary>
public async Task RequestChangeConfigurationNotification(DidChangeConfigurationParams<SqlToolsSettings> configParams)
{
await Driver.SendEvent(DidChangeConfigurationNotification<SqlToolsSettings>.Type, configParams);
}
/// <summary>
/// /// Request the active SQL script is parsed for errors
/// </summary>
public async Task RequestChangeTextDocumentNotification(DidChangeTextDocumentParams changeParams)
{
await Driver.SendEvent(DidChangeTextDocumentNotification.Type, changeParams);
}
/// <summary>
/// Request completion item resolve to look-up additional info
/// </summary>
public async Task<CompletionItem> RequestResolveCompletion(CompletionItem item)
{
var result = await Driver.SendRequest(CompletionResolveRequest.Type, item);
return result;
}
/// <summary>
/// Returns database connection parameters for given server type
/// </summary>
public ConnectParams GetConnectionParameters(TestServerType serverType, string databaseName = null)
{
return TestConnectionService.GetConnectionParameters(serverType, databaseName);
}
/// <summary>
/// Request a list of completion items for a position in a block of text
/// </summary>
public async Task<CompletionItem[]> RequestCompletion(string ownerUri, string text, int line, int character)
{
// Write the text to a backing file
lock (fileLock)
{
System.IO.File.WriteAllText(ownerUri, text);
}
var completionParams = new TextDocumentPosition();
completionParams.TextDocument = new TextDocumentIdentifier();
completionParams.TextDocument.Uri = ownerUri;
completionParams.Position = new Position();
completionParams.Position.Line = line;
completionParams.Position.Character = character;
var result = await Driver.SendRequest(CompletionRequest.Type, completionParams);
return result;
}
/// <summary>
/// Request a a hover tooltop
/// </summary>
public async Task<Hover> RequestHover(string ownerUri, string text, int line, int character)
{
// Write the text to a backing file
lock (fileLock)
{
System.IO.File.WriteAllText(ownerUri, text);
}
var completionParams = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier { Uri = ownerUri },
Position = new Position
{
Line = line,
Character = character
}
};
var result = await Driver.SendRequest(HoverRequest.Type, completionParams);
return result;
}
/// <summary>
/// Request definition( peek definition/go to definition) for a sql object in a sql string
/// </summary>
public async Task<Location[]> RequestDefinition(string ownerUri, string text, int line, int character)
{
// Write the text to a backing file
lock (fileLock)
{
System.IO.File.WriteAllText(ownerUri, text);
}
var definitionParams = new TextDocumentPosition();
definitionParams.TextDocument = new TextDocumentIdentifier();
definitionParams.TextDocument.Uri = ownerUri;
definitionParams.Position = new Position();
definitionParams.Position.Line = line;
definitionParams.Position.Character = character;
// Send definition request
var result = await Driver.SendRequest(DefinitionRequest.Type, definitionParams);
return result;
}
/// <summary>
/// Run a query using a given connection bound to a URI
/// </summary>
public async Task<QueryCompleteParams> RunQueryAndWaitToComplete(string ownerUri, string query, int timeoutMilliseconds = 5000)
{
// Write the query text to a backing file
WriteToFile(ownerUri, query);
var queryParams = new ExecuteDocumentSelectionParams
{
OwnerUri = ownerUri,
QuerySelection = null
};
var result = await Driver.SendRequest(ExecuteDocumentSelectionRequest.Type, queryParams);
if (result != null)
{
var eventResult = await Driver.WaitForEvent(QueryCompleteEvent.Type, timeoutMilliseconds);
return eventResult;
}
else
{
return null;
}
}
/// <summary>
/// Run a query using a given connection bound to a URI. This method only waits for the initial response from query
/// execution (QueryExecuteResult). It is up to the caller to wait for the QueryCompleteEvent if they are interested.
/// </summary>
public async Task<ExecuteRequestResult> RunQueryAsync(string ownerUri, string query, int timeoutMilliseconds = 5000)
{
WriteToFile(ownerUri, query);
var queryParams = new ExecuteDocumentSelectionParams
{
OwnerUri = ownerUri,
QuerySelection = null
};
return await Driver.SendRequest(ExecuteDocumentSelectionRequest.Type, queryParams);
}
public async Task RunQuery(TestServerType serverType, string databaseName, string query)
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
{
await ConnectForQuery(serverType, query, queryTempFile.FilePath, databaseName);
var queryResult = await CalculateRunTime(() => RunQueryAndWaitToComplete(queryTempFile.FilePath, query, 50000), false);
Assert.NotNull(queryResult);
Assert.NotNull(queryResult.BatchSummaries);
await Disconnect(queryTempFile.FilePath);
}
}
public async Task<T> CalculateRunTime<T>(Func<Task<T>> testToRun, bool printResult, [CallerMemberName] string testName = "")
{
TestTimer timer = new TestTimer() { PrintResult = printResult };
T result = await testToRun();
timer.EndAndPrint(testName);
return result;
}
public async Task ExecuteWithTimeout(TestTimer timer, int timeout, Func<Task<bool>> repeatedCode,
TimeSpan? delay = null, [CallerMemberName] string testName = "")
{
while (true)
{
if (await repeatedCode())
{
timer.EndAndPrint(testName);
break;
}
if (timer.TotalMilliSecondsUntilNow >= timeout)
{
Assert.True(false, $"{testName} timed out after {timeout} milliseconds");
break;
}
if (delay.HasValue)
{
await Task.Delay(delay.Value);
}
}
}
/// <summary>
/// Request to cancel an executing query
/// </summary>
public async Task<QueryCancelResult> CancelQuery(string ownerUri)
{
var cancelParams = new QueryCancelParams { OwnerUri = ownerUri };
var result = await Driver.SendRequest(QueryCancelRequest.Type, cancelParams);
return result;
}
/// <summary>
/// Request to save query results as CSV
/// </summary>
public async Task<SaveResultRequestResult> SaveAsCsv(string ownerUri, string filename, int batchIndex, int resultSetIndex)
{
var saveParams = new SaveResultsAsCsvRequestParams
{
OwnerUri = ownerUri,
BatchIndex = batchIndex,
ResultSetIndex = resultSetIndex,
FilePath = filename
};
var result = await Driver.SendRequest(SaveResultsAsCsvRequest.Type, saveParams);
return result;
}
/// <summary>
/// Request to save query results as JSON
/// </summary>
public async Task<SaveResultRequestResult> SaveAsJson(string ownerUri, string filename, int batchIndex, int resultSetIndex)
{
var saveParams = new SaveResultsAsJsonRequestParams
{
OwnerUri = ownerUri,
BatchIndex = batchIndex,
ResultSetIndex = resultSetIndex,
FilePath = filename
};
var result = await Driver.SendRequest(SaveResultsAsJsonRequest.Type, saveParams);
return result;
}
/// <summary>
/// Request a subset of results from a query
/// </summary>
public async Task<SubsetResult> ExecuteSubset(string ownerUri, int batchIndex, int resultSetIndex, int rowStartIndex, int rowCount)
{
var subsetParams = new SubsetParams();
subsetParams.OwnerUri = ownerUri;
subsetParams.BatchIndex = batchIndex;
subsetParams.ResultSetIndex = resultSetIndex;
subsetParams.RowsStartIndex = rowStartIndex;
subsetParams.RowsCount = rowCount;
var result = await Driver.SendRequest(SubsetRequest.Type, subsetParams);
return result;
}
/// <summary>
/// Waits for a message to be returned by the service
/// </summary>
/// <returns>A message from the service layer</returns>
public async Task<MessageParams> WaitForMessage()
{
return await Driver.WaitForEvent(MessageEvent.Type);
}
public void WriteToFile(string ownerUri, string query)
{
lock (fileLock)
{
System.IO.File.WriteAllText(ownerUri, query);
}
}
}
}