Update docs (#200)

This is a documentation-only update so automerging.  Please review the commit if there are any follow-ups requested.

* Update .gitignore for docfx genertated files

* Update documenation (part 1)

* Update docs and add some samples

* More doc updates
This commit is contained in:
Karl Burtram
2016-12-20 15:52:46 -08:00
committed by GitHub
parent 1e59166147
commit 4184eae8a1
26 changed files with 2529 additions and 5 deletions

View File

@@ -0,0 +1,102 @@
//
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.JsonRpc.Driver
{
/// <summary>
/// Test driver for the service host
/// </summary>
public class ClientDriver : ClientDriverBase
{
public const string ServiceHostEnvironmentVariable = "SQLTOOLSSERVICE_EXE";
private Process[] serviceProcesses;
private DateTime startTime;
public ClientDriver()
{
string serviceHostExecutable = Environment.GetEnvironmentVariable(ServiceHostEnvironmentVariable);
string serviceHostArguments = "--enable-logging";
if (string.IsNullOrWhiteSpace(serviceHostExecutable))
{
// Include a fallback value to for running tests within visual studio
serviceHostExecutable = @"Microsoft.SqlTools.ServiceLayer.exe";
}
// Make sure it exists before continuing
if (!File.Exists(serviceHostExecutable))
{
throw new FileNotFoundException($"Failed to find Microsoft.SqlTools.ServiceLayer.exe at provided location '{serviceHostExecutable}'. " +
"Please set SQLTOOLSERVICE_EXE environment variable to location of exe");
}
this.clientChannel = new StdioClientChannel(serviceHostExecutable, serviceHostArguments);
this.protocolClient = new ProtocolEndpoint(clientChannel, MessageProtocolType.LanguageServer);
}
/// <summary>
/// Start the test driver, and launch the sqltoolsservice executable
/// </summary>
public async Task Start()
{
// Store the time we started
startTime = DateTime.Now;
// Launch the process
await this.protocolClient.Start();
await Task.Delay(1000); // Wait for the service host to start
Console.WriteLine("Successfully launched service");
// Setup events to queue for testing
this.QueueEventsForType(ConnectionCompleteNotification.Type);
this.QueueEventsForType(IntelliSenseReadyNotification.Type);
this.QueueEventsForType(QueryExecuteCompleteEvent.Type);
this.QueueEventsForType(PublishDiagnosticsNotification.Type);
}
/// <summary>
/// Stop the test driver, and shutdown the sqltoolsservice executable
/// </summary>
public async Task Stop()
{
await this.protocolClient.Stop();
}
private async Task GetServiceProcess(CancellationToken token)
{
while (serviceProcesses == null && !token.IsCancellationRequested)
{
var processes = Process.GetProcessesByName("Microsoft.SqlTools.ServiceLayer")
.Where(p => p.StartTime >= startTime).ToArray();
// Wait a second if we can't find the process
if (processes.Any())
{
serviceProcesses = processes;
}
else
{
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
}
}
}
}

View File

@@ -0,0 +1,187 @@
//
// 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.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.JsonRpc.Driver
{
/// <summary>
/// Wraps the ProtocolEndpoint class with queues to handle events/requests
/// </summary>
public class ClientDriverBase
{
protected ProtocolEndpoint protocolClient;
protected StdioClientChannel clientChannel;
private ConcurrentDictionary<string, AsyncQueue<object>> eventQueuePerType =
new ConcurrentDictionary<string, AsyncQueue<object>>();
private ConcurrentDictionary<string, AsyncQueue<object>> requestQueuePerType =
new ConcurrentDictionary<string, AsyncQueue<object>>();
public Process ServiceProcess
{
get
{
try
{
return Process.GetProcessById(clientChannel.ProcessId);
}
catch
{
return null;
}
}
}
public Task<TResult> SendRequest<TParams, TResult>(
RequestType<TParams, TResult> requestType,
TParams requestParams)
{
return
this.protocolClient.SendRequest(
requestType,
requestParams);
}
public Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
{
return
this.protocolClient.SendEvent(
eventType,
eventParams);
}
public void QueueEventsForType<TParams>(EventType<TParams> eventType)
{
var eventQueue =
this.eventQueuePerType.AddOrUpdate(
eventType.MethodName,
new AsyncQueue<object>(),
(key, queue) => queue);
this.protocolClient.SetEventHandler(
eventType,
(p, ctx) =>
{
return eventQueue.EnqueueAsync(p);
});
}
public async Task<TParams> WaitForEvent<TParams>(
EventType<TParams> eventType,
int timeoutMilliseconds = 5000)
{
Task<TParams> eventTask = null;
// Use the event queue if one has been registered
AsyncQueue<object> eventQueue = null;
if (this.eventQueuePerType.TryGetValue(eventType.MethodName, out eventQueue))
{
eventTask =
eventQueue
.DequeueAsync()
.ContinueWith<TParams>(
task => (TParams)task.Result);
}
else
{
TaskCompletionSource<TParams> eventTaskSource = new TaskCompletionSource<TParams>();
this.protocolClient.SetEventHandler(
eventType,
(p, ctx) =>
{
if (!eventTaskSource.Task.IsCompleted)
{
eventTaskSource.SetResult(p);
}
return Task.FromResult(true);
},
true); // Override any existing handler
eventTask = eventTaskSource.Task;
}
await
Task.WhenAny(
eventTask,
Task.Delay(timeoutMilliseconds));
if (!eventTask.IsCompleted)
{
throw new TimeoutException(
string.Format(
"Timed out waiting for '{0}' event!",
eventType.MethodName));
}
return await eventTask;
}
public async Task<Tuple<TParams, RequestContext<TResponse>>> WaitForRequest<TParams, TResponse>(
RequestType<TParams, TResponse> requestType,
int timeoutMilliseconds = 5000)
{
Task<Tuple<TParams, RequestContext<TResponse>>> requestTask = null;
// Use the request queue if one has been registered
AsyncQueue<object> requestQueue = null;
if (this.requestQueuePerType.TryGetValue(requestType.MethodName, out requestQueue))
{
requestTask =
requestQueue
.DequeueAsync()
.ContinueWith(
task => (Tuple<TParams, RequestContext<TResponse>>)task.Result);
}
else
{
var requestTaskSource =
new TaskCompletionSource<Tuple<TParams, RequestContext<TResponse>>>();
this.protocolClient.SetRequestHandler(
requestType,
(p, ctx) =>
{
if (!requestTaskSource.Task.IsCompleted)
{
requestTaskSource.SetResult(
new Tuple<TParams, RequestContext<TResponse>>(p, ctx));
}
return Task.FromResult(true);
});
requestTask = requestTaskSource.Task;
}
await
Task.WhenAny(
requestTask,
Task.Delay(timeoutMilliseconds));
if (!requestTask.IsCompleted)
{
throw new TimeoutException(
string.Format(
"Timed out waiting for '{0}' request!",
requestType.MethodName));
}
return await requestTask;
}
}
}

View File

@@ -0,0 +1,85 @@
//
// 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.Threading.Tasks;
using Microsoft.SqlTools.JsonRpc.Utility;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.JsonRpc.ExecuteQuery
{
/// <summary>
/// Simple JSON-RPC API sample to connect to a database, execute a query, and print the results
/// </summary>
internal class Program
{
internal static void Main(string[] args)
{
// set SQLTOOLSSERVICE_EXE to location of SQL Tools Service executable
Environment.SetEnvironmentVariable("SQLTOOLSSERVICE_EXE", @"Microsoft.SqlTools.ServiceLayer.exe");
// execute a query against localhost, master, with IntegratedAuth
ExecuteQuery("SELECT * FROM sys.objects").Wait();
}
internal static async Task ExecuteQuery(string query)
{
// create a temporary "workspace" file
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
// create the client helper which wraps the client driver objects
using (ClientHelper testHelper = new ClientHelper())
{
// connnection details
ConnectParams connectParams = new ConnectParams();
connectParams.Connection = new ConnectionDetails();
connectParams.Connection.ServerName = "localhost";
connectParams.Connection.DatabaseName = "master";
connectParams.Connection.AuthenticationType = "Integrated";
// connect to the database
await testHelper.Connect(queryTempFile.FilePath, connectParams);
// execute the query
QueryExecuteCompleteParams queryComplete =
await testHelper.RunQuery(queryTempFile.FilePath, query);
if (queryComplete.BatchSummaries != null && queryComplete.BatchSummaries.Length > 0)
{
var batch = queryComplete.BatchSummaries[0];
if (batch.ResultSetSummaries != null && batch.ResultSetSummaries.Length > 0)
{
var resultSet = batch.ResultSetSummaries[0];
// retrive the results
QueryExecuteSubsetResult querySubset = await testHelper.ExecuteSubset(
queryTempFile.FilePath, batch.Id,
resultSet.Id, 0, (int)resultSet.RowCount);
// print the header
foreach (var column in resultSet.ColumnInfo)
{
Console.Write(column.ColumnName + ", ");
}
Console.Write(Environment.NewLine);
// print the rows
foreach (var row in querySubset.ResultSubset.Rows)
{
for (int i = 0; i < resultSet.ColumnInfo.Length; ++i)
{
Console.Write(row.GetValue(i) + ", ");
}
Console.Write(Environment.NewLine);
}
}
}
// close database connection
await testHelper.Disconnect(queryTempFile.FilePath);
}
}
}
}

View File

@@ -0,0 +1,255 @@
//
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.SqlTools.JsonRpc.Driver;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.JsonRpc.Utility
{
/// <summary>
/// Base class for all test suites run by the test driver
/// </summary>
public sealed class ClientHelper : IDisposable
{
private bool isRunning = false;
public ClientHelper()
{
Driver = new ClientDriver();
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 ClientDriver Driver
{
get;
private set;
}
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 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<QueryExecuteSubsetResult> RequestQueryExecuteSubset(QueryExecuteSubsetParams subsetParams)
{
return await Driver.SendRequest(QueryExecuteSubsetRequest.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>
// /// Request a Read Credential for given credential id
// /// </summary>
// public async Task<Credential> ReadCredential(string credentialId)
// {
// var credentialParams = new Credential();
// credentialParams.CredentialId = credentialId;
// return await Driver.SendRequest(ReadCredentialRequest.Type, credentialParams);
// }
/// <summary>
/// Run a query using a given connection bound to a URI
/// </summary>
public async Task<QueryExecuteCompleteParams> RunQuery(string ownerUri, string query, int timeoutMilliseconds = 5000)
{
// Write the query text to a backing file
WriteToFile(ownerUri, query);
var queryParams = new QueryExecuteParams
{
OwnerUri = ownerUri,
QuerySelection = null
};
var result = await Driver.SendRequest(QueryExecuteRequest.Type, queryParams);
if (result != null && string.IsNullOrEmpty(result.Messages))
{
var eventResult = await Driver.WaitForEvent(QueryExecuteCompleteEvent.Type, timeoutMilliseconds);
return eventResult;
}
else
{
return null;
}
}
/// <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<QueryExecuteSubsetResult> ExecuteSubset(string ownerUri, int batchIndex, int resultSetIndex, int rowStartIndex, int rowCount)
{
var subsetParams = new QueryExecuteSubsetParams();
subsetParams.OwnerUri = ownerUri;
subsetParams.BatchIndex = batchIndex;
subsetParams.ResultSetIndex = resultSetIndex;
subsetParams.RowsStartIndex = rowStartIndex;
subsetParams.RowsCount = rowCount;
var result = await Driver.SendRequest(QueryExecuteSubsetRequest.Type, subsetParams);
return result;
}
public void WriteToFile(string ownerUri, string query)
{
lock (fileLock)
{
System.IO.File.WriteAllText(ownerUri, query);
}
}
}
}

View File

@@ -0,0 +1,53 @@
//
// 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.IO;
namespace Microsoft.SqlTools.JsonRpc.Utility
{
public class SelfCleaningTempFile : IDisposable
{
private bool disposed;
public SelfCleaningTempFile()
{
FilePath = Path.GetTempFileName();
}
public string FilePath { get; private set; }
#region IDisposable Implementation
public void Dispose()
{
if (!disposed)
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public void Dispose(bool disposing)
{
if (!disposed && disposing)
{
try
{
File.Delete(FilePath);
}
catch
{
Console.WriteLine($"Failed to cleanup {FilePath}");
}
}
disposed = true;
}
#endregion
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "Microsoft.SqlTools.JsonRpcTest.ExecuteQuery",
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.SqlTools.ServiceLayer": {
"target": "project"
},
"Newtonsoft.Json": "9.0.1",
"System.Data.Common": "4.1.0",
"System.Data.SqlClient": "4.4.0-sqltools-24613-04",
"Microsoft.SqlServer.Smo": "140.1.12",
"System.Security.SecureString": "4.0.0",
"System.Collections.Specialized": "4.0.1",
"System.ComponentModel.TypeConverter": "4.1.0",
"System.Diagnostics.Contracts": "4.0.1",
"System.Diagnostics.TraceSource": "4.0.0",
"NETStandard.Library": "1.6.0",
"Microsoft.NETCore.Runtime.CoreCLR": "1.0.2",
"Microsoft.NETCore.DotNetHostPolicy": "1.0.1",
"System.Diagnostics.Process": "4.1.0",
"System.Threading.Thread": "4.0.0"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0"
}
},
"imports": "dnxcore50"
}
},
"runtimes": {
"win7-x64": {},
"win7-x86": {},
"osx.10.11-x64": {},
"ubuntu.14.04-x64": {},
"ubuntu.16.04-x64": {},
"centos.7-x64": {},
"rhel.7.2-x64": {},
"debian.8-x64": {},
"fedora.23-x64": {},
"opensuse.13.2-x64": {}
}
}