mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Merge dev to master for 0.0.7 release (#86)
* Disable failing test while investigating * Made connection errors more user-friendly (#57) * Bug/negativeOneRowsAffected strings file fix (#61) * Adding changes to sr.strings files * Fixing bug by changing valid filename check to fail on whitespace (#55) Fixing a bug from the unit tests on OSX/Unix where attempting to create a file with a name that's all whitespace succeeds when it should fail. This was passing in Windows because File.Open throws an ArgumentException when an all whitespace name is provided. In Unix systems, File.Open does not throw, causing the test to fail. Solution is to check for whitespace in the sanity check. * Format Cell Values (#62) * WIP for ability to localize cell values * Changing how DateTimeOffsets are stored, getting unit tests going * Reworking BufferFileStreamWriter to use dictionary approach * Plumbing the DbCellValue type the rest of the way through * Removing unused components to simplify contract * Cleanup and making sure byte[] appears in parity with SSMS * CR comments, small tweaks for optimizing LINQ * Feature/batch line info (#56) * inital pipe of line numbers and getting text from workspace services * tests compile * Fixed bug regarding tests using connections on mac * updated tests * fixed workspace service and fixed tests * integrated feedback * Remove deleted file with whitespace name * Feature/autocomp options (#63) * Enable IntelliSense settings * Fix up some bugs in the IntelliSense settings. * Code cleans for PR * Fix a couple exceptions that are breaks query execute and intellisense. * Add useLowerCase flag and settings tests * Remove task.wait from test to avoid break on build machine. (#67) This is to fix test breaks so I'll merge now. Please let me know if there are comments on this commit. * Do not use ReliableCommand in the query execution service (#66) * Do not use ReliableCommand in the query execution service. * Fixing the logic to remove InfoMessage handlers from ReliableSqlConnection * Adding test to query UDT * Bump SMO to 140.1.6 to pick up perf fixes (#69) * Enable Quick Info hover tooltips (#65) Pushing to include in tomorrow's partner release build. Please send me any feedback and I'll address in the next Intellisense PR. * Added grouping between system/user dbs when listing (#70) * Feature/timestamp messages (#68) * added support for timestamps * fixed tests * Moved message class to own file; added 'z' to end of date strings * added default time constructor * removed unnecessary z * added time string format info in comment * changed from utc time to using local time * Feature/save selection (#64) * Save selection * Add tests * Change filename in test * Code cleanup * Refactor handler * Code cleanup * Modify tests to have query selection * Change variable declaration * Bump SMO to 14.0.7 to pick Batchparser updates (#72) ...bumping versions. No review needed. * Lingering File Handles (#71) Fixing a bug where in various situations, the files used for temporary storage of query results would be leftover. In particular, the following changes were made: * When the dispose query request is submitted, the corresponding query is now disposed in addition from being removed from the list of active queries * When a query is cancelled, it is disposed after it is cancelled * If a query already exists for a given ownerURI, the existing query is disposed before creating a new query * All queries are disposed when the query execution service is disposed (ie, at shutdown of the service) A unit test to verify the action of the dispose method for a ResultSet was added. * Ensuring queries are disposed Adding logic to dispose any queries when: * URI that already has a query executes another query * A request to dispose a query is submitted * A request to cancel a query is submitted * Small tweaks for cleanup of query execution service * Add IntelliSense binding queue (#73) * Initial code for binding queue * Fix-up some of the timeout wait code * Add some initial test code * Add missing test file * Update the binding queue tests * Add more test coverage and refactor a bit. Disable reliabile connection until we can fix it..it's holding an open data reader connection. * A few more test updates * Initial integrate queue with language service. * Hook up the connected binding queue into al binding calls. * Cleanup comments and remove dead code * More missing comments * Fix build break. Reenable ReliabileConnection. * Revert all changes to SqlConnectionFactory * Resolve merge conflicts * Cleanup some more of the timeouts and sync code * Address code review feedback * Address more code review feedback * Feature/connect cancel (#74) * Implemented connection cancellation * Made connect requests return immediately and created a separate connection complete notification * Fix spelling * Fix sorting * Add separate lock for cancellation source map * Fix an issue with queue deadlocks causing test failures (#77) * Fixed issue where connecting could take very long and cancellation would not work (#78) * Fixed issue where connecting could take very long and cancellation would not work * Addressing feedback * Remove warning suppression * Adding unlimited timeout for query execution (#76) Adding explicitly setting the timeout for command execution to unlimited. We can change this to be user configurable at a later time * Support 'for XML and for JSON' queries (#75) * Set isXMl and isJson for 'for xml/json' resultSets * Change string comparison * Modify if-else * VSTS 8499785. Close SqlToolsService after VS Code exits. (#80) VSTS 8499785. Close SqlToolsService after VS Code exits. * Remove extra level of tasks in binding queue (#79) * Remove extra layer of tasks in binding queue * Change order of assigning result to avoid race condition * Add timeout log for the metadata lock event * Fix test cases
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -14,6 +14,10 @@ project.lock.json
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.exe
|
||||
scratch.txt
|
||||
|
||||
# mergetool conflict files
|
||||
*.orig
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
@@ -52,6 +56,7 @@ cross/rootfs/
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
test*json
|
||||
|
||||
#NUNIT
|
||||
*.VisualState.xml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration >
|
||||
<packageSources>
|
||||
<add key="Nuget" value="https://www.nuget.org/api/v2" />
|
||||
<add key="DataTools Nuget" value="http://dtnuget/api/v2/" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
@@ -47,6 +49,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
private Dictionary<string, ConnectionInfo> ownerToConnectionMap = new Dictionary<string, ConnectionInfo>();
|
||||
|
||||
private ConcurrentDictionary<string, CancellationTokenSource> ownerToCancellationTokenSourceMap = new ConcurrentDictionary<string, CancellationTokenSource>();
|
||||
|
||||
private Object cancellationTokenSourceLock = new Object();
|
||||
|
||||
/// <summary>
|
||||
/// Map from script URIs to ConnectionInfo objects
|
||||
/// This is internal for testing access only
|
||||
/// </summary>
|
||||
internal Dictionary<string, ConnectionInfo> OwnerToConnectionMap
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ownerToConnectionMap;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service host object for sending/receiving requests/events.
|
||||
/// Internal for testing purposes.
|
||||
@@ -119,21 +137,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// Open a connection with the specified connection details
|
||||
/// </summary>
|
||||
/// <param name="connectionParams"></param>
|
||||
public ConnectResponse Connect(ConnectParams connectionParams)
|
||||
public async Task<ConnectionCompleteParams> Connect(ConnectParams connectionParams)
|
||||
{
|
||||
// Validate parameters
|
||||
string paramValidationErrorMessage;
|
||||
if (connectionParams == null)
|
||||
{
|
||||
return new ConnectResponse
|
||||
return new ConnectionCompleteParams
|
||||
{
|
||||
Messages = SR.ConnectionServiceConnectErrorNullParams
|
||||
};
|
||||
}
|
||||
if (!connectionParams.IsValid(out paramValidationErrorMessage))
|
||||
{
|
||||
return new ConnectResponse
|
||||
return new ConnectionCompleteParams
|
||||
{
|
||||
OwnerUri = connectionParams.OwnerUri,
|
||||
Messages = paramValidationErrorMessage
|
||||
};
|
||||
}
|
||||
@@ -152,7 +171,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
|
||||
|
||||
// try to connect
|
||||
var response = new ConnectResponse();
|
||||
var response = new ConnectionCompleteParams();
|
||||
response.OwnerUri = connectionParams.OwnerUri;
|
||||
CancellationTokenSource source = null;
|
||||
try
|
||||
{
|
||||
// build the connection string from the input parameters
|
||||
@@ -160,13 +181,75 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
// create a sql connection instance
|
||||
connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString);
|
||||
connectionInfo.SqlConnection.Open();
|
||||
|
||||
// turning on MARS to avoid break in LanguageService with multiple editors
|
||||
// we'll remove this once ConnectionService is refactored to not own the LanguageService connection
|
||||
connectionInfo.ConnectionDetails.MultipleActiveResultSets = true;
|
||||
|
||||
// Add a cancellation token source so that the connection OpenAsync() can be cancelled
|
||||
using (source = new CancellationTokenSource())
|
||||
{
|
||||
// Locking here to perform two operations as one atomic operation
|
||||
lock (cancellationTokenSourceLock)
|
||||
{
|
||||
// If the URI is currently connecting from a different request, cancel it before we try to connect
|
||||
CancellationTokenSource currentSource;
|
||||
if (ownerToCancellationTokenSourceMap.TryGetValue(connectionParams.OwnerUri, out currentSource))
|
||||
{
|
||||
currentSource.Cancel();
|
||||
}
|
||||
ownerToCancellationTokenSourceMap[connectionParams.OwnerUri] = source;
|
||||
}
|
||||
|
||||
// Create a task to handle cancellation requests
|
||||
var cancellationTask = Task.Run(() =>
|
||||
{
|
||||
source.Token.WaitHandle.WaitOne();
|
||||
source.Token.ThrowIfCancellationRequested();
|
||||
});
|
||||
|
||||
var openTask = Task.Run(async () => {
|
||||
await connectionInfo.SqlConnection.OpenAsync(source.Token);
|
||||
});
|
||||
|
||||
// Open the connection
|
||||
await Task.WhenAny(openTask, cancellationTask).Unwrap();
|
||||
source.Cancel();
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (SqlException ex)
|
||||
{
|
||||
response.ErrorNumber = ex.Number;
|
||||
response.ErrorMessage = ex.Message;
|
||||
response.Messages = ex.ToString();
|
||||
return response;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// OpenAsync was cancelled
|
||||
response.Messages = SR.ConnectionServiceConnectionCanceled;
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.ErrorMessage = ex.Message;
|
||||
response.Messages = ex.ToString();
|
||||
return response;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Remove our cancellation token from the map since we're no longer connecting
|
||||
// Using a lock here to perform two operations as one atomic operation
|
||||
lock (cancellationTokenSourceLock)
|
||||
{
|
||||
// Only remove the token from the map if it is the same one created by this request
|
||||
CancellationTokenSource sourceValue;
|
||||
if (ownerToCancellationTokenSourceMap.TryGetValue(connectionParams.OwnerUri, out sourceValue) && sourceValue == source)
|
||||
{
|
||||
ownerToCancellationTokenSourceMap.TryRemove(connectionParams.OwnerUri, out sourceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo;
|
||||
|
||||
@@ -181,15 +264,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
};
|
||||
|
||||
// invoke callback notifications
|
||||
foreach (var activity in this.onConnectionActivities)
|
||||
{
|
||||
activity(connectionInfo);
|
||||
}
|
||||
invokeOnConnectionActivities(connectionInfo);
|
||||
|
||||
// try to get information about the connected SQL Server instance
|
||||
try
|
||||
{
|
||||
ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connectionInfo.SqlConnection);
|
||||
var reliableConnection = connectionInfo.SqlConnection as ReliableSqlConnection;
|
||||
DbConnection connection = reliableConnection != null ? reliableConnection.GetUnderlyingConnection() : connectionInfo.SqlConnection;
|
||||
|
||||
ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connection);
|
||||
response.ServerInfo = new Contracts.ServerInfo()
|
||||
{
|
||||
ServerMajorVersion = serverInfo.ServerMajorVersion,
|
||||
@@ -214,6 +297,37 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel a connection that is in the process of opening.
|
||||
/// </summary>
|
||||
public bool CancelConnect(CancelConnectParams cancelParams)
|
||||
{
|
||||
// Validate parameters
|
||||
if (cancelParams == null || string.IsNullOrEmpty(cancelParams.OwnerUri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cancel any current connection attempts for this URI
|
||||
CancellationTokenSource source;
|
||||
if (ownerToCancellationTokenSourceMap.TryGetValue(cancelParams.OwnerUri, out source))
|
||||
{
|
||||
try
|
||||
{
|
||||
source.Cancel();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a connection with the specified connection details.
|
||||
/// </summary>
|
||||
@@ -225,6 +339,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cancel if we are in the middle of connecting
|
||||
if (CancelConnect(new CancelConnectParams() { OwnerUri = disconnectParams.OwnerUri }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lookup the connection owned by the URI
|
||||
ConnectionInfo info;
|
||||
if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info))
|
||||
@@ -274,7 +394,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
connection.Open();
|
||||
|
||||
DbCommand command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT name FROM sys.databases";
|
||||
command.CommandText = "SELECT name FROM sys.databases ORDER BY database_id ASC";
|
||||
command.CommandTimeout = 15;
|
||||
command.CommandType = CommandType.Text;
|
||||
var reader = command.ExecuteReader();
|
||||
@@ -299,6 +419,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
// Register request and event handlers with the Service Host
|
||||
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
|
||||
serviceHost.SetRequestHandler(CancelConnectRequest.Type, HandleCancelConnectRequest);
|
||||
serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest);
|
||||
serviceHost.SetRequestHandler(ListDatabasesRequest.Type, HandleListDatabasesRequest);
|
||||
|
||||
@@ -331,14 +452,55 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// <returns></returns>
|
||||
protected async Task HandleConnectRequest(
|
||||
ConnectParams connectParams,
|
||||
RequestContext<ConnectResponse> requestContext)
|
||||
RequestContext<bool> requestContext)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "HandleConnectRequest");
|
||||
|
||||
try
|
||||
{
|
||||
// open connection base on request details
|
||||
ConnectResponse result = ConnectionService.Instance.Connect(connectParams);
|
||||
RunConnectRequestHandlerTask(connectParams, requestContext);
|
||||
await requestContext.SendResult(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await requestContext.SendResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void RunConnectRequestHandlerTask(ConnectParams connectParams, RequestContext<bool> requestContext)
|
||||
{
|
||||
// create a task to connect asynchronously so that other requests are not blocked in the meantime
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// open connection based on request details
|
||||
ConnectionCompleteParams result = await ConnectionService.Instance.Connect(connectParams);
|
||||
await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConnectionCompleteParams result = new ConnectionCompleteParams()
|
||||
{
|
||||
Messages = ex.ToString()
|
||||
};
|
||||
await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle cancel connect requests
|
||||
/// </summary>
|
||||
protected async Task HandleCancelConnectRequest(
|
||||
CancelConnectParams cancelParams,
|
||||
RequestContext<bool> requestContext)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "HandleCancelConnectRequest");
|
||||
|
||||
try
|
||||
{
|
||||
bool result = ConnectionService.Instance.CancelConnect(cancelParams);
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch(Exception ex)
|
||||
@@ -563,5 +725,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeOnConnectionActivities(ConnectionInfo connectionInfo)
|
||||
{
|
||||
foreach (var activity in this.onConnectionActivities)
|
||||
{
|
||||
// not awaiting here to allow handlers to run in the background
|
||||
activity(connectionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// 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.Connection.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for the Cancel Connect Request.
|
||||
/// </summary>
|
||||
public class CancelConnectParams
|
||||
{
|
||||
/// <summary>
|
||||
/// A URI identifying the owner of the connection. This will most commonly be a file in the workspace
|
||||
/// or a virtual file representing an object in a database.
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// 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.Connection.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Cancel connect request mapping entry
|
||||
/// </summary>
|
||||
public class CancelConnectRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<CancelConnectParams, bool> Type =
|
||||
RequestType<CancelConnectParams, bool>.Create("connection/cancelconnect");
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// 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.Connection.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Message format for the connection result response
|
||||
/// </summary>
|
||||
public class ConnectResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// A GUID representing a unique connection ID
|
||||
/// </summary>
|
||||
public string ConnectionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets any connection error messages
|
||||
/// </summary>
|
||||
public string Messages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the connected server.
|
||||
/// </summary>
|
||||
public ServerInfo ServerInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual Connection established, including Database Name
|
||||
/// </summary>
|
||||
public ConnectionSummary ConnectionSummary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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.Connection.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters to be sent back with a connection complete event
|
||||
/// </summary>
|
||||
public class ConnectionCompleteParams
|
||||
{
|
||||
/// <summary>
|
||||
/// A URI identifying the owner of the connection. This will most commonly be a file in the workspace
|
||||
/// or a virtual file representing an object in a database.
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A GUID representing a unique connection ID
|
||||
/// </summary>
|
||||
public string ConnectionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets any detailed connection error messages.
|
||||
/// </summary>
|
||||
public string Messages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message returned from the engine for a connection failure reason, if any.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error number returned from the engine for connection failure reason, if any.
|
||||
/// </summary>
|
||||
public int ErrorNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the connected server.
|
||||
/// </summary>
|
||||
public ServerInfo ServerInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual Connection established, including Database Name
|
||||
/// </summary>
|
||||
public ConnectionSummary ConnectionSummary { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ConnectionComplete notification mapping entry
|
||||
/// </summary>
|
||||
public class ConnectionCompleteNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<ConnectionCompleteParams> Type =
|
||||
EventType<ConnectionCompleteParams>.Create("connection/complete");
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||
public class ConnectionRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<ConnectParams, ConnectResponse> Type =
|
||||
RequestType<ConnectParams, ConnectResponse>.Create("connection/connect");
|
||||
RequestType<ConnectParams, bool> Type =
|
||||
RequestType<ConnectParams, bool>.Create("connection/connect");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||
using System.Reflection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
@@ -22,6 +22,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
/// </summary>
|
||||
public sealed class ServiceHost : ServiceHostBase
|
||||
{
|
||||
/// <summary>
|
||||
/// This timeout limits the amount of time that shutdown tasks can take to complete
|
||||
/// prior to the process shutting down.
|
||||
/// </summary>
|
||||
private const int ShutdownTimeoutInSeconds = 120;
|
||||
|
||||
#region Singleton Instance Code
|
||||
|
||||
/// <summary>
|
||||
@@ -63,8 +69,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// Delegate definition for the host shutdown event
|
||||
/// </summary>
|
||||
/// <param name="shutdownParams"></param>
|
||||
/// <param name="shutdownRequestContext"></param>
|
||||
public delegate Task ShutdownCallback(object shutdownParams, RequestContext<object> shutdownRequestContext);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate definition for the host initialization event
|
||||
/// </summary>
|
||||
/// <param name="startupParams"></param>
|
||||
/// <param name="requestContext"></param>
|
||||
public delegate Task InitializeCallback(InitializeRequest startupParams, RequestContext<InitializeResult> requestContext);
|
||||
|
||||
private readonly List<ShutdownCallback> shutdownCallbacks;
|
||||
@@ -108,7 +124,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
|
||||
// Call all the shutdown methods provided by the service components
|
||||
Task[] shutdownTasks = shutdownCallbacks.Select(t => t(shutdownParams, requestContext)).ToArray();
|
||||
await Task.WhenAll(shutdownTasks);
|
||||
TimeSpan shutdownTimeout = TimeSpan.FromSeconds(ShutdownTimeoutInSeconds);
|
||||
// shut down once all tasks are completed, or after the timeout expires, whichever comes first.
|
||||
await Task.WhenAny(Task.WhenAll(shutdownTasks), Task.Delay(shutdownTimeout)).ContinueWith(t => Environment.Exit(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,8 +137,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
/// <returns></returns>
|
||||
private async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext<InitializeResult> requestContext)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "HandleInitializationRequest");
|
||||
|
||||
// Call all tasks that registered on the initialize request
|
||||
var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext));
|
||||
await Task.WhenAll(initializeTasks);
|
||||
@@ -136,7 +152,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
||||
DefinitionProvider = true,
|
||||
ReferencesProvider = true,
|
||||
DocumentHighlightProvider = true,
|
||||
DocumentHighlightProvider = true,
|
||||
HoverProvider = true,
|
||||
CompletionProvider = new CompletionOptions
|
||||
{
|
||||
ResolveProvider = true,
|
||||
@@ -144,7 +161,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
},
|
||||
SignatureHelpProvider = new SignatureHelpOptions
|
||||
{
|
||||
TriggerCharacters = new string[] { " " } // TODO: Other characters here?
|
||||
TriggerCharacters = new string[] { " ", "," }
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -157,7 +174,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
object versionRequestParams,
|
||||
RequestContext<string> requestContext)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "HandleVersionRequest");
|
||||
await requestContext.SendResult(serviceVersion.ToString());
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
public static class AutoCompleteHelper
|
||||
{
|
||||
private const int PrepopulateBindTimeout = 60000;
|
||||
|
||||
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
|
||||
|
||||
private static readonly string[] DefaultCompletionText = new string[]
|
||||
{
|
||||
"absolute",
|
||||
@@ -421,16 +425,44 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
"zone"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current workspace service instance
|
||||
/// Setter for internal testing purposes only
|
||||
/// </summary>
|
||||
internal static WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AutoCompleteHelper.workspaceServiceInstance == null)
|
||||
{
|
||||
AutoCompleteHelper.workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
|
||||
}
|
||||
return AutoCompleteHelper.workspaceServiceInstance;
|
||||
}
|
||||
set
|
||||
{
|
||||
AutoCompleteHelper.workspaceServiceInstance = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the default completion list from hard-coded list
|
||||
/// </summary>
|
||||
/// <param name="row"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <param name="endColumn"></param>
|
||||
/// <param name="useLowerCase"></param>
|
||||
internal static CompletionItem[] GetDefaultCompletionItems(
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
int endColumn,
|
||||
bool useLowerCase)
|
||||
{
|
||||
var completionItems = new CompletionItem[DefaultCompletionText.Length];
|
||||
for (int i = 0; i < DefaultCompletionText.Length; ++i)
|
||||
{
|
||||
completionItems[i] = CreateDefaultCompletionItem(
|
||||
DefaultCompletionText[i].ToUpper(),
|
||||
useLowerCase ? DefaultCompletionText[i].ToLower() : DefaultCompletionText[i].ToUpper(),
|
||||
row,
|
||||
startColumn,
|
||||
endColumn);
|
||||
@@ -438,6 +470,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a completion item from the default item text
|
||||
/// </summary>
|
||||
/// <param name="label"></param>
|
||||
/// <param name="row"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <param name="endColumn"></param>
|
||||
private static CompletionItem CreateDefaultCompletionItem(
|
||||
string label,
|
||||
int row,
|
||||
@@ -523,11 +562,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="scriptInfo"></param>
|
||||
internal static void PrepopulateCommonMetadata(ConnectionInfo info, ScriptParseInfo scriptInfo)
|
||||
internal static void PrepopulateCommonMetadata(
|
||||
ConnectionInfo info,
|
||||
ScriptParseInfo scriptInfo,
|
||||
ConnectedBindingQueue bindingQueue)
|
||||
{
|
||||
if (scriptInfo.IsConnected)
|
||||
{
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
||||
var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
|
||||
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
||||
|
||||
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
|
||||
@@ -536,44 +578,53 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
scriptInfo.BuildingMetadataEvent.Reset();
|
||||
|
||||
// parse a simple statement that returns common metadata
|
||||
ParseResult parseResult = Parser.Parse(
|
||||
"select ",
|
||||
scriptInfo.ParseOptions);
|
||||
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||
key: scriptInfo.ConnectionKey,
|
||||
bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// parse a simple statement that returns common metadata
|
||||
ParseResult parseResult = Parser.Parse(
|
||||
"select ",
|
||||
bindingContext.ParseOptions);
|
||||
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
scriptInfo.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
var suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 8,
|
||||
scriptInfo.MetadataDisplayInfoProvider);
|
||||
// get the completion list from SQL Parser
|
||||
var suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 8,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
||||
|
||||
parseResult = Parser.Parse(
|
||||
"exec ",
|
||||
scriptInfo.ParseOptions);
|
||||
parseResult = Parser.Parse(
|
||||
"exec ",
|
||||
bindingContext.ParseOptions);
|
||||
|
||||
parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
scriptInfo.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 6,
|
||||
scriptInfo.MetadataDisplayInfoProvider);
|
||||
// get the completion list from SQL Parser
|
||||
suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 6,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
||||
return null;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -585,5 +636,53 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a SQL Parser QuickInfo object into a VS Code Hover object
|
||||
/// </summary>
|
||||
/// <param name="quickInfo"></param>
|
||||
/// <param name="row"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <param name="endColumn"></param>
|
||||
internal static Hover ConvertQuickInfoToHover(
|
||||
Babel.CodeObjectQuickInfo quickInfo,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
// convert from the parser format to the VS Code wire format
|
||||
var markedStrings = new MarkedString[1];
|
||||
if (quickInfo != null)
|
||||
{
|
||||
markedStrings[0] = new MarkedString()
|
||||
{
|
||||
Language = "SQL",
|
||||
Value = quickInfo.Text
|
||||
};
|
||||
|
||||
return new Hover()
|
||||
{
|
||||
Contents = markedStrings,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = startColumn
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = endColumn
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for the Binding Queue
|
||||
/// </summary>
|
||||
public class BindingQueue<T> where T : IBindingContext, new()
|
||||
{
|
||||
private CancellationTokenSource processQueueCancelToken = new CancellationTokenSource();
|
||||
|
||||
private ManualResetEvent itemQueuedEvent = new ManualResetEvent(initialState: false);
|
||||
|
||||
private object bindingQueueLock = new object();
|
||||
|
||||
private LinkedList<QueueItem> bindingQueue = new LinkedList<QueueItem>();
|
||||
|
||||
private object bindingContextLock = new object();
|
||||
|
||||
private Task queueProcessorTask;
|
||||
|
||||
/// <summary>
|
||||
/// Map from context keys to binding context instances
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal Dictionary<string, IBindingContext> BindingContextMap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a binding queue instance
|
||||
/// </summary>
|
||||
public BindingQueue()
|
||||
{
|
||||
this.BindingContextMap = new Dictionary<string, IBindingContext>();
|
||||
|
||||
this.queueProcessorTask = StartQueueProcessor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the binding queue by sending cancellation request
|
||||
/// </summary>
|
||||
/// <param name="timeout"></param>
|
||||
public bool StopQueueProcessor(int timeout)
|
||||
{
|
||||
this.processQueueCancelToken.Cancel();
|
||||
return this.queueProcessorTask.Wait(timeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a binding request item
|
||||
/// </summary>
|
||||
public QueueItem QueueBindingOperation(
|
||||
string key,
|
||||
Func<IBindingContext, CancellationToken, object> bindOperation,
|
||||
Func<IBindingContext, object> timeoutOperation = null,
|
||||
int? bindingTimeout = null)
|
||||
{
|
||||
// don't add null operations to the binding queue
|
||||
if (bindOperation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
QueueItem queueItem = new QueueItem()
|
||||
{
|
||||
Key = key,
|
||||
BindOperation = bindOperation,
|
||||
TimeoutOperation = timeoutOperation,
|
||||
BindingTimeout = bindingTimeout
|
||||
};
|
||||
|
||||
lock (this.bindingQueueLock)
|
||||
{
|
||||
this.bindingQueue.AddLast(queueItem);
|
||||
}
|
||||
|
||||
this.itemQueuedEvent.Set();
|
||||
|
||||
return queueItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a binding context for the provided context key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
protected IBindingContext GetOrCreateBindingContext(string key)
|
||||
{
|
||||
// use a default binding context for disconnected requests
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
key = "disconnected_binding_context";
|
||||
}
|
||||
|
||||
lock (this.bindingContextLock)
|
||||
{
|
||||
if (!this.BindingContextMap.ContainsKey(key))
|
||||
{
|
||||
this.BindingContextMap.Add(key, new T());
|
||||
}
|
||||
|
||||
return this.BindingContextMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasPendingQueueItems
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this.bindingQueueLock)
|
||||
{
|
||||
return this.bindingQueue.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next pending queue item
|
||||
/// </summary>
|
||||
private QueueItem GetNextQueueItem()
|
||||
{
|
||||
lock (this.bindingQueueLock)
|
||||
{
|
||||
if (this.bindingQueue.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
QueueItem queueItem = this.bindingQueue.First.Value;
|
||||
this.bindingQueue.RemoveFirst();
|
||||
return queueItem;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the queue processing thread
|
||||
/// </summary>
|
||||
private Task StartQueueProcessor()
|
||||
{
|
||||
return Task.Factory.StartNew(
|
||||
ProcessQueue,
|
||||
this.processQueueCancelToken.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The core queue processing method
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
private void ProcessQueue()
|
||||
{
|
||||
CancellationToken token = this.processQueueCancelToken.Token;
|
||||
WaitHandle[] waitHandles = new WaitHandle[2]
|
||||
{
|
||||
this.itemQueuedEvent,
|
||||
token.WaitHandle
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
// wait for with an item to be queued or the a cancellation request
|
||||
WaitHandle.WaitAny(waitHandles);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// dispatch all pending queue items
|
||||
while (this.HasPendingQueueItems)
|
||||
{
|
||||
QueueItem queueItem = GetNextQueueItem();
|
||||
if (queueItem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
||||
if (bindingContext == null)
|
||||
{
|
||||
queueItem.ItemProcessed.Set();
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// prefer the queue item binding item, otherwise use the context default timeout
|
||||
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
|
||||
|
||||
// handle the case a previous binding operation is still running
|
||||
if (!bindingContext.BindingLocked.WaitOne(bindTimeout))
|
||||
{
|
||||
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
|
||||
queueItem.ItemProcessed.Set();
|
||||
continue;
|
||||
}
|
||||
|
||||
// execute the binding operation
|
||||
object result = null;
|
||||
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||
var bindTask = Task.Run(() =>
|
||||
{
|
||||
result = queueItem.BindOperation(
|
||||
bindingContext,
|
||||
cancelToken.Token);
|
||||
});
|
||||
|
||||
// check if the binding tasks completed within the binding timeout
|
||||
if (bindTask.Wait(bindTimeout))
|
||||
{
|
||||
queueItem.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the task didn't complete then call the timeout callback
|
||||
if (queueItem.TimeoutOperation != null)
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// catch and log any exceptions raised in the binding calls
|
||||
// set item processed to avoid deadlocks
|
||||
Logger.Write(LogLevel.Error, "Binding queue threw exception " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
bindingContext.BindingLocked.Set();
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
|
||||
// if a queue processing cancellation was requested then exit the loop
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// reset the item queued event since we've processed all the pending items
|
||||
this.itemQueuedEvent.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// 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;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for the binding context for connected sessions
|
||||
/// </summary>
|
||||
public class ConnectedBindingContext : IBindingContext
|
||||
{
|
||||
private ParseOptions parseOptions;
|
||||
|
||||
private ServerConnection serverConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Connected binding context constructor
|
||||
/// </summary>
|
||||
public ConnectedBindingContext()
|
||||
{
|
||||
this.BindingLocked = new ManualResetEvent(initialState: true);
|
||||
this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
|
||||
this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if the binder is connected
|
||||
/// </summary>
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding server connection
|
||||
/// </summary>
|
||||
public ServerConnection ServerConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.serverConnection;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.serverConnection = value;
|
||||
|
||||
// reset the parse options so the get recreated for the current connection
|
||||
this.parseOptions = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the metadata display info provider
|
||||
/// </summary>
|
||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SMO metadata provider
|
||||
/// </summary>
|
||||
public SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binder
|
||||
/// </summary>
|
||||
public IBinder Binder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an event to signal if a binding operation is in progress
|
||||
/// </summary>
|
||||
public ManualResetEvent BindingLocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding operation timeout in milliseconds
|
||||
/// </summary>
|
||||
public int BindingTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Language Service ServerVersion
|
||||
/// </summary>
|
||||
public ServerVersion ServerVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ServerConnection != null
|
||||
? this.ServerConnection.ServerVersion
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DataEngineType
|
||||
/// </summary>
|
||||
public DatabaseEngineType DatabaseEngineType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ServerConnection != null
|
||||
? this.ServerConnection.DatabaseEngineType
|
||||
: DatabaseEngineType.Standalone;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current connections TransactSqlVersion
|
||||
/// </summary>
|
||||
public TransactSqlVersion TransactSqlVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsConnected
|
||||
? GetTransactSqlVersion(this.ServerVersion)
|
||||
: TransactSqlVersion.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DatabaseCompatibilityLevel
|
||||
/// </summary>
|
||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsConnected
|
||||
? GetDatabaseCompatibilityLevel(this.ServerVersion)
|
||||
: DatabaseCompatibilityLevel.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current ParseOptions
|
||||
/// </summary>
|
||||
public ParseOptions ParseOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.parseOptions == null)
|
||||
{
|
||||
this.parseOptions = new ParseOptions(
|
||||
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||
isQuotedIdentifierSet: true,
|
||||
compatibilityLevel: DatabaseCompatibilityLevel,
|
||||
transactSqlVersion: TransactSqlVersion);
|
||||
}
|
||||
return this.parseOptions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database compatibility level from a server version
|
||||
/// </summary>
|
||||
/// <param name="serverVersion"></param>
|
||||
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
|
||||
{
|
||||
int versionMajor = Math.Max(serverVersion.Major, 8);
|
||||
|
||||
switch (versionMajor)
|
||||
{
|
||||
case 8:
|
||||
return DatabaseCompatibilityLevel.Version80;
|
||||
case 9:
|
||||
return DatabaseCompatibilityLevel.Version90;
|
||||
case 10:
|
||||
return DatabaseCompatibilityLevel.Version100;
|
||||
case 11:
|
||||
return DatabaseCompatibilityLevel.Version110;
|
||||
case 12:
|
||||
return DatabaseCompatibilityLevel.Version120;
|
||||
case 13:
|
||||
return DatabaseCompatibilityLevel.Version130;
|
||||
default:
|
||||
return DatabaseCompatibilityLevel.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transaction sql version from a server version
|
||||
/// </summary>
|
||||
/// <param name="serverVersion"></param>
|
||||
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
|
||||
{
|
||||
int versionMajor = Math.Max(serverVersion.Major, 9);
|
||||
|
||||
switch (versionMajor)
|
||||
{
|
||||
case 9:
|
||||
case 10:
|
||||
// In case of 10.0 we still use Version 10.5 as it is the closest available.
|
||||
return TransactSqlVersion.Version105;
|
||||
case 11:
|
||||
return TransactSqlVersion.Version110;
|
||||
case 12:
|
||||
return TransactSqlVersion.Version120;
|
||||
case 13:
|
||||
return TransactSqlVersion.Version130;
|
||||
default:
|
||||
return TransactSqlVersion.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// 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.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// ConnectedBindingQueue class for processing online binding requests
|
||||
/// </summary>
|
||||
public class ConnectedBindingQueue : BindingQueue<ConnectedBindingContext>
|
||||
{
|
||||
internal const int DefaultBindingTimeout = 60000;
|
||||
|
||||
internal const int DefaultMinimumConnectionTimeout = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current settings
|
||||
/// </summary>
|
||||
internal SqlToolsSettings CurrentSettings
|
||||
{
|
||||
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a unique key based on the ConnectionInfo object
|
||||
/// </summary>
|
||||
/// <param name="connInfo"></param>
|
||||
private string GetConnectionContextKey(ConnectionInfo connInfo)
|
||||
{
|
||||
ConnectionDetails details = connInfo.ConnectionDetails;
|
||||
return string.Format("{0}_{1}_{2}_{3}",
|
||||
details.ServerName ?? "NULL",
|
||||
details.DatabaseName ?? "NULL",
|
||||
details.UserName ?? "NULL",
|
||||
details.AuthenticationType ?? "NULL"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a ConnectionInfo item to create a connected binding context
|
||||
/// </summary>
|
||||
/// <param name="connInfo"></param>
|
||||
public virtual string AddConnectionContext(ConnectionInfo connInfo)
|
||||
{
|
||||
if (connInfo == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// lookup the current binding context
|
||||
string connectionKey = GetConnectionContextKey(connInfo);
|
||||
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey);
|
||||
|
||||
try
|
||||
{
|
||||
// increase the connection timeout to at least 30 seconds and and build connection string
|
||||
// enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections
|
||||
int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout;
|
||||
bool? originalPersistSecurityInfo = connInfo.ConnectionDetails.PersistSecurityInfo;
|
||||
connInfo.ConnectionDetails.ConnectTimeout = Math.Max(DefaultMinimumConnectionTimeout, originalTimeout ?? 0);
|
||||
connInfo.ConnectionDetails.PersistSecurityInfo = true;
|
||||
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
|
||||
connInfo.ConnectionDetails.ConnectTimeout = originalTimeout;
|
||||
connInfo.ConnectionDetails.PersistSecurityInfo = originalPersistSecurityInfo;
|
||||
|
||||
// open a dedicated binding server connection
|
||||
SqlConnection sqlConn = new SqlConnection(connectionString);
|
||||
if (sqlConn != null)
|
||||
{
|
||||
sqlConn.Open();
|
||||
|
||||
// populate the binding context to work with the SMO metadata provider
|
||||
ServerConnection serverConn = new ServerConnection(sqlConn);
|
||||
bindingContext.SmoMetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
|
||||
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
bindingContext.MetadataDisplayInfoProvider.BuiltInCasing =
|
||||
this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value
|
||||
? CasingStyle.Lowercase : CasingStyle.Uppercase;
|
||||
bindingContext.Binder = BinderProvider.CreateBinder(bindingContext.SmoMetadataProvider);
|
||||
bindingContext.ServerConnection = serverConn;
|
||||
bindingContext.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
|
||||
bindingContext.IsConnected = true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
bindingContext.IsConnected = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
bindingContext.BindingLocked.Set();
|
||||
}
|
||||
|
||||
return connectionKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// The context used for binding requests
|
||||
/// </summary>
|
||||
public interface IBindingContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if the context is connected
|
||||
/// </summary>
|
||||
bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding server connection
|
||||
/// </summary>
|
||||
ServerConnection ServerConnection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the metadata display info provider
|
||||
/// </summary>
|
||||
MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SMO metadata provider
|
||||
/// </summary>
|
||||
SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binder
|
||||
/// </summary>
|
||||
IBinder Binder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an event to signal if a binding operation is in progress
|
||||
/// </summary>
|
||||
ManualResetEvent BindingLocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding operation timeout in milliseconds
|
||||
/// </summary>
|
||||
int BindingTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current connection parse options
|
||||
/// </summary>
|
||||
ParseOptions ParseOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current connection server version
|
||||
/// </summary>
|
||||
ServerVersion ServerVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the database engine type
|
||||
/// </summary>
|
||||
DatabaseEngineType DatabaseEngineType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the T-SQL version
|
||||
/// </summary>
|
||||
TransactSqlVersion TransactSqlVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the database compatibility level
|
||||
/// </summary>
|
||||
DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,11 @@ using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
@@ -39,31 +37,54 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
internal const int DiagnosticParseDelay = 750;
|
||||
|
||||
internal const int FindCompletionsTimeout = 3000;
|
||||
internal const int HoverTimeout = 3000;
|
||||
|
||||
internal const int BindingTimeout = 3000;
|
||||
|
||||
internal const int FindCompletionStartTimeout = 50;
|
||||
|
||||
internal const int OnConnectionWaitTimeout = 300000;
|
||||
|
||||
private static ConnectionService connectionService = null;
|
||||
|
||||
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
|
||||
|
||||
private object parseMapLock = new object();
|
||||
|
||||
private ScriptParseInfo currentCompletionParseInfo;
|
||||
|
||||
internal bool ShouldEnableAutocomplete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
|
||||
private ConnectionService connectionService = null;
|
||||
private ParseOptions defaultParseOptions = new ParseOptions(
|
||||
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||
isQuotedIdentifierSet: true,
|
||||
compatibilityLevel: DatabaseCompatibilityLevel.Current,
|
||||
transactSqlVersion: TransactSqlVersion.Current);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding queue instance
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal ConnectedBindingQueue BindingQueue
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.bindingQueue;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.bindingQueue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal ConnectionService ConnectionServiceInstance
|
||||
internal static ConnectionService ConnectionServiceInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if(connectionService == null)
|
||||
if (connectionService == null)
|
||||
{
|
||||
connectionService = ConnectionService.Instance;
|
||||
}
|
||||
@@ -83,6 +104,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
|
||||
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
|
||||
/// </summary>
|
||||
internal Dictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
||||
{
|
||||
get
|
||||
@@ -91,11 +115,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton instance object
|
||||
/// </summary>
|
||||
public static LanguageService Instance
|
||||
{
|
||||
get { return instance.Value; }
|
||||
}
|
||||
|
||||
private ParseOptions DefaultParseOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.defaultParseOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default, parameterless constructor.
|
||||
/// </summary>
|
||||
@@ -109,14 +144,40 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
private static CancellationTokenSource ExistingRequestCancellation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current settings
|
||||
/// </summary>
|
||||
internal SqlToolsSettings CurrentSettings
|
||||
{
|
||||
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current workspace service instance
|
||||
/// Setter for internal testing purposes only
|
||||
/// </summary>
|
||||
internal static WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LanguageService.workspaceServiceInstance == null)
|
||||
{
|
||||
LanguageService.workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
|
||||
}
|
||||
return LanguageService.workspaceServiceInstance;
|
||||
}
|
||||
set
|
||||
{
|
||||
LanguageService.workspaceServiceInstance = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current workspace instance
|
||||
/// </summary>
|
||||
internal Workspace.Workspace CurrentWorkspace
|
||||
{
|
||||
get { return WorkspaceService<SqlToolsSettings>.Instance.Workspace; }
|
||||
get { return LanguageService.WorkspaceServiceInstance.Workspace; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -181,23 +242,31 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
/// <param name="requestContext"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task HandleCompletionRequest(
|
||||
internal static async Task HandleCompletionRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<CompletionItem[]> requestContext)
|
||||
{
|
||||
// get the current list of completion items and return to client
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
// check if Intellisense suggestions are enabled
|
||||
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the current list of completion items and return to client
|
||||
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
ConnectionInfo connInfo;
|
||||
ConnectionService.Instance.TryFindConnection(
|
||||
scriptFile.ClientFilePath,
|
||||
out connInfo);
|
||||
ConnectionInfo connInfo;
|
||||
LanguageService.ConnectionServiceInstance.TryFindConnection(
|
||||
scriptFile.ClientFilePath,
|
||||
out connInfo);
|
||||
|
||||
var completionItems = Instance.GetCompletionItems(
|
||||
textDocumentPosition, scriptFile, connInfo);
|
||||
var completionItems = Instance.GetCompletionItems(
|
||||
textDocumentPosition, scriptFile, connInfo);
|
||||
|
||||
await requestContext.SendResult(completionItems);
|
||||
await requestContext.SendResult(completionItems);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,8 +280,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
CompletionItem completionItem,
|
||||
RequestContext<CompletionItem> requestContext)
|
||||
{
|
||||
completionItem = LanguageService.Instance.ResolveCompletionItem(completionItem);
|
||||
await requestContext.SendResult(completionItem);
|
||||
// check if Intellisense suggestions are enabled
|
||||
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionItem = LanguageService.Instance.ResolveCompletionItem(completionItem);
|
||||
await requestContext.SendResult(completionItem);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleDefinitionRequest(
|
||||
@@ -246,8 +323,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
private static async Task HandleHoverRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<Hover> requestContext)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
{
|
||||
// check if Quick Info hover tooltips are enabled
|
||||
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsQuickInfoEnabled)
|
||||
{
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
var hover = LanguageService.Instance.GetHoverItem(textDocumentPosition, scriptFile);
|
||||
if (hover != null)
|
||||
{
|
||||
await requestContext.SendResult(hover);
|
||||
}
|
||||
}
|
||||
|
||||
await requestContext.SendResult(new Hover());
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -264,7 +354,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
ScriptFile scriptFile,
|
||||
EventContext eventContext)
|
||||
{
|
||||
if (!IsPreviewWindow(scriptFile))
|
||||
// if not in the preview window and diagnostics are enabled then run diagnostics
|
||||
if (!IsPreviewWindow(scriptFile)
|
||||
&& WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsDiagnositicsEnabled)
|
||||
{
|
||||
await RunScriptDiagnostics(
|
||||
new ScriptFile[] { scriptFile },
|
||||
@@ -278,13 +370,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// Handles text document change events
|
||||
/// </summary>
|
||||
/// <param name="textChangeParams"></param>
|
||||
/// <param name="eventContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="eventContext"></param>
|
||||
public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext)
|
||||
{
|
||||
await this.RunScriptDiagnostics(
|
||||
changedFiles.ToArray(),
|
||||
eventContext);
|
||||
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsDiagnositicsEnabled)
|
||||
{
|
||||
await this.RunScriptDiagnostics(
|
||||
changedFiles.ToArray(),
|
||||
eventContext);
|
||||
}
|
||||
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
@@ -300,13 +394,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
SqlToolsSettings oldSettings,
|
||||
EventContext eventContext)
|
||||
{
|
||||
// If script analysis settings have changed we need to clear & possibly update the current diagnostic records.
|
||||
bool oldScriptAnalysisEnabled = oldSettings.ScriptAnalysis.Enable.HasValue;
|
||||
if ((oldScriptAnalysisEnabled != newSettings.ScriptAnalysis.Enable))
|
||||
bool oldEnableIntelliSense = oldSettings.SqlTools.EnableIntellisense;
|
||||
bool? oldEnableDiagnostics = oldSettings.SqlTools.IntelliSense.EnableDiagnostics;
|
||||
|
||||
// update the current settings to reflect any changes
|
||||
CurrentSettings.Update(newSettings);
|
||||
|
||||
// if script analysis settings have changed we need to clear the current diagnostic markers
|
||||
if (oldEnableIntelliSense != newSettings.SqlTools.EnableIntellisense
|
||||
|| oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableDiagnostics)
|
||||
{
|
||||
// If the user just turned off script analysis or changed the settings path, send a diagnostics
|
||||
// event to clear the analysis markers that they already have.
|
||||
if (!newSettings.ScriptAnalysis.Enable.Value)
|
||||
// if the user just turned off diagnostics then send an event to clear the error markers
|
||||
if (!newSettings.IsDiagnositicsEnabled)
|
||||
{
|
||||
ScriptFileMarker[] emptyAnalysisDiagnostics = new ScriptFileMarker[0];
|
||||
|
||||
@@ -315,15 +414,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext);
|
||||
}
|
||||
}
|
||||
// otherwise rerun diagnostic analysis on all opened SQL files
|
||||
else
|
||||
{
|
||||
await this.RunScriptDiagnostics(CurrentWorkspace.GetOpenedFiles(), eventContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the settings in the current
|
||||
CurrentSettings.EnableProfileLoading = newSettings.EnableProfileLoading;
|
||||
CurrentSettings.ScriptAnalysis.Update(newSettings.ScriptAnalysis, CurrentWorkspace.WorkspacePath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -350,52 +446,85 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="sqlText"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>The ParseResult instance returned from SQL Parser</returns>
|
||||
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||
{
|
||||
// get or create the current parse info object
|
||||
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true);
|
||||
|
||||
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionsTimeout))
|
||||
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.BindingTimeout))
|
||||
{
|
||||
try
|
||||
{
|
||||
parseInfo.BuildingMetadataEvent.Reset();
|
||||
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
parseInfo.ParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
|
||||
if (connInfo != null && parseInfo.IsConnected)
|
||||
if (connInfo == null || !parseInfo.IsConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
parseInfo.Binder.Bind(
|
||||
parseResults,
|
||||
connInfo.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
}
|
||||
catch (ConnectionException)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (SqlParserInternalBinderError)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
this.DefaultParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: parseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
bindingContext.ParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
connInfo.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
}
|
||||
catch (ConnectionException)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (SqlParserInternalBinderError)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// reset the parse result to do a full parse next time
|
||||
parseInfo.ParseResult = null;
|
||||
Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
parseInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Write(LogLevel.Warning, "Binding metadata lock timeout in ParseAndBind");
|
||||
}
|
||||
|
||||
return parseInfo.ParseResult;
|
||||
}
|
||||
@@ -406,42 +535,32 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="info"></param>
|
||||
public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||
{
|
||||
await Task.Run( () =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (ShouldEnableAutocomplete())
|
||||
ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true);
|
||||
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
|
||||
{
|
||||
ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true);
|
||||
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
scriptInfo.BuildingMetadataEvent.Reset();
|
||||
var sqlConn = info.SqlConnection as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
ServerConnection serverConn = new ServerConnection(sqlConn.GetUnderlyingConnection());
|
||||
scriptInfo.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
scriptInfo.MetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
|
||||
scriptInfo.Binder = BinderProvider.CreateBinder(scriptInfo.MetadataProvider);
|
||||
scriptInfo.ServerConnection = new ServerConnection(sqlConn.GetUnderlyingConnection());
|
||||
scriptInfo.IsConnected = true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
scriptInfo.IsConnected = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Set Metadata Build event to Signal state.
|
||||
// (Tell Language Service that I am ready with Metadata Provider Object)
|
||||
scriptInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
scriptInfo.BuildingMetadataEvent.Reset();
|
||||
scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(info);
|
||||
scriptInfo.IsConnected = true;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Unknown error in OnConnection " + ex.ToString());
|
||||
scriptInfo.IsConnected = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Set Metadata Build event to Signal state.
|
||||
// (Tell Language Service that I am ready with Metadata Provider Object)
|
||||
scriptInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
// populate SMO metadata provider with most common info
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo);
|
||||
}
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -469,22 +588,88 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="completionItem"></param>
|
||||
internal CompletionItem ResolveCompletionItem(CompletionItem completionItem)
|
||||
{
|
||||
var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo;
|
||||
if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null)
|
||||
try
|
||||
{
|
||||
foreach (var suggestion in scriptParseInfo.CurrentSuggestions)
|
||||
var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo;
|
||||
if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null)
|
||||
{
|
||||
if (string.Equals(suggestion.Title, completionItem.Label))
|
||||
foreach (var suggestion in scriptParseInfo.CurrentSuggestions)
|
||||
{
|
||||
completionItem.Detail = suggestion.DatabaseQualifiedName;
|
||||
completionItem.Documentation = suggestion.Description;
|
||||
break;
|
||||
if (string.Equals(suggestion.Title, completionItem.Label))
|
||||
{
|
||||
completionItem.Detail = suggestion.DatabaseQualifiedName;
|
||||
completionItem.Documentation = suggestion.Description;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// if any exceptions are raised looking up extended completion metadata
|
||||
// then just return the original completion item
|
||||
Logger.Write(LogLevel.Error, "Exeception in ResolveCompletionItem " + ex.ToString());
|
||||
}
|
||||
|
||||
return completionItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get quick info hover tooltips for the current position
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
/// <param name="scriptFile"></param>
|
||||
internal Hover GetHoverItem(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||
{
|
||||
int startLine = textDocumentPosition.Position.Line;
|
||||
int startColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int endColumn = textDocumentPosition.Position.Character;
|
||||
|
||||
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
|
||||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null)
|
||||
{
|
||||
if (scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||
try
|
||||
{
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.HoverTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// get the current quick info text
|
||||
Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo(
|
||||
scriptParseInfo.ParseResult,
|
||||
startLine + 1,
|
||||
endColumn + 1,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// convert from the parser format to the VS Code wire format
|
||||
return AutoCompleteHelper.ConvertQuickInfoToHover(
|
||||
quickInfo,
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn);
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
return queueItem.GetResultAsT<Hover>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return null if there isn't a tooltip for the current location
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the completion item list for the current text position.
|
||||
/// This method does not await cache builds since it expects to return quickly
|
||||
@@ -502,6 +687,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int endColumn = textDocumentPosition.Position.Character;
|
||||
bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value;
|
||||
|
||||
this.currentCompletionParseInfo = null;
|
||||
|
||||
@@ -510,7 +696,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
|
||||
if (connInfo == null || scriptParseInfo == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
// reparse and bind the SQL statement if needed
|
||||
@@ -521,49 +707,60 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
if (scriptParseInfo.ParseResult == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
if (scriptParseInfo.IsConnected
|
||||
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||
Task<CompletionItem[]> findCompletionsTask = Task.Run(() => {
|
||||
try
|
||||
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// get the completion list from SQL Parser
|
||||
scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
textDocumentPosition.Position.Line + 1,
|
||||
textDocumentPosition.Position.Character + 1,
|
||||
scriptParseInfo.MetadataDisplayInfoProvider);
|
||||
CompletionItem[] completions = null;
|
||||
try
|
||||
{
|
||||
// get the completion list from SQL Parser
|
||||
scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
textDocumentPosition.Position.Line + 1,
|
||||
textDocumentPosition.Position.Character + 1,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// cache the current script parse info object to resolve completions later
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
// cache the current script parse info object to resolve completions later
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
scriptParseInfo.CurrentSuggestions,
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn);
|
||||
}
|
||||
finally
|
||||
// convert the suggestion list to the VS Code format
|
||||
completions = AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
scriptParseInfo.CurrentSuggestions,
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
|
||||
return completions;
|
||||
},
|
||||
timeoutOperation: (bindingContext) =>
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
});
|
||||
|
||||
findCompletionsTask.Wait(LanguageService.FindCompletionsTimeout);
|
||||
if (findCompletionsTask.IsCompleted
|
||||
&& findCompletionsTask.Result != null
|
||||
&& findCompletionsTask.Result.Length > 0)
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
|
||||
if (completionItems != null && completionItems.Length > 0)
|
||||
{
|
||||
return findCompletionsTask.Result;
|
||||
return completionItems;
|
||||
}
|
||||
}
|
||||
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -614,7 +811,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="eventContext"></param>
|
||||
private Task RunScriptDiagnostics(ScriptFile[] filesToAnalyze, EventContext eventContext)
|
||||
{
|
||||
if (!CurrentSettings.ScriptAnalysis.Enable.Value)
|
||||
if (!CurrentSettings.IsDiagnositicsEnabled)
|
||||
{
|
||||
// If the user has disabled script analysis, skip it entirely
|
||||
return Task.FromResult(true);
|
||||
@@ -710,7 +907,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
#endregion
|
||||
|
||||
private void AddOrUpdateScriptParseInfo(string uri, ScriptParseInfo scriptInfo)
|
||||
/// <summary>
|
||||
/// Adds a new or updates an existing script parse info instance in local cache
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <param name="scriptInfo"></param>
|
||||
internal void AddOrUpdateScriptParseInfo(string uri, ScriptParseInfo scriptInfo)
|
||||
{
|
||||
lock (this.parseMapLock)
|
||||
{
|
||||
@@ -726,7 +928,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
|
||||
private ScriptParseInfo GetScriptParseInfo(string uri, bool createIfNotExists = false)
|
||||
/// <summary>
|
||||
/// Gets a script parse info object for a file from the local cache
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <param name="createIfNotExists">Creates a new instance if one doesn't exist</param>
|
||||
internal ScriptParseInfo GetScriptParseInfo(string uri, bool createIfNotExists = false)
|
||||
{
|
||||
lock (this.parseMapLock)
|
||||
{
|
||||
@@ -736,6 +944,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
else if (createIfNotExists)
|
||||
{
|
||||
// create a new script parse info object and initialize with the current settings
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||
this.ScriptParseInfoMap.Add(uri, scriptInfo);
|
||||
return scriptInfo;
|
||||
@@ -752,10 +961,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
lock (this.parseMapLock)
|
||||
{
|
||||
if (this.ScriptParseInfoMap.ContainsKey(uri))
|
||||
{
|
||||
var scriptInfo = this.ScriptParseInfoMap[uri];
|
||||
scriptInfo.ServerConnection.Disconnect();
|
||||
scriptInfo.ServerConnection = null;
|
||||
{
|
||||
return this.ScriptParseInfoMap.Remove(uri);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that stores the state of a binding queue request item
|
||||
/// </summary>
|
||||
public class QueueItem
|
||||
{
|
||||
/// <summary>
|
||||
/// QueueItem constructor
|
||||
/// </summary>
|
||||
public QueueItem()
|
||||
{
|
||||
this.ItemProcessed = new ManualResetEvent(initialState: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the queue item key
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bind operation callback method
|
||||
/// </summary>
|
||||
public Func<IBindingContext, CancellationToken, object> BindOperation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout operation to call if the bind operation doesn't finish within timeout period
|
||||
/// </summary>
|
||||
public Func<IBindingContext, object> TimeoutOperation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an event to signal when this queue item has been processed
|
||||
/// </summary>
|
||||
public ManualResetEvent ItemProcessed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result of the queued task
|
||||
/// </summary>
|
||||
public object Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding operation timeout in milliseconds
|
||||
/// </summary>
|
||||
public int? BindingTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the result of the execution to type T
|
||||
/// </summary>
|
||||
public T GetResultAsT<T>() where T : class
|
||||
{
|
||||
//var task = this.ResultsTask;
|
||||
return (this.Result != null)
|
||||
? this.Result as T
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,9 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
@@ -23,10 +17,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
|
||||
|
||||
private ParseOptions parseOptions = new ParseOptions();
|
||||
|
||||
private ServerConnection serverConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Event which tells if MetadataProvider is built fully or not
|
||||
/// </summary>
|
||||
@@ -41,163 +31,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LanguageService SMO ServerConnection
|
||||
/// Gets or sets the binding queue connection context key
|
||||
/// </summary>
|
||||
public ServerConnection ServerConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.serverConnection;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.serverConnection = value;
|
||||
this.parseOptions = new ParseOptions(
|
||||
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||
isQuotedIdentifierSet: true,
|
||||
compatibilityLevel: DatabaseCompatibilityLevel,
|
||||
transactSqlVersion: TransactSqlVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Language Service ServerVersion
|
||||
/// </summary>
|
||||
public ServerVersion ServerVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ServerConnection != null
|
||||
? this.ServerConnection.ServerVersion
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DataEngineType
|
||||
/// </summary>
|
||||
public DatabaseEngineType DatabaseEngineType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ServerConnection != null
|
||||
? this.ServerConnection.DatabaseEngineType
|
||||
: DatabaseEngineType.Standalone;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current connections TransactSqlVersion
|
||||
/// </summary>
|
||||
public TransactSqlVersion TransactSqlVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsConnected
|
||||
? GetTransactSqlVersion(this.ServerVersion)
|
||||
: TransactSqlVersion.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DatabaseCompatibilityLevel
|
||||
/// </summary>
|
||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsConnected
|
||||
? GetDatabaseCompatibilityLevel(this.ServerVersion)
|
||||
: DatabaseCompatibilityLevel.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current ParseOptions
|
||||
/// </summary>
|
||||
public ParseOptions ParseOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.parseOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SMO binder for schema-aware intellisense
|
||||
/// </summary>
|
||||
public IBinder Binder { get; set; }
|
||||
public string ConnectionKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the previous SQL parse result
|
||||
/// </summary>
|
||||
public ParseResult ParseResult { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the SMO metadata provider that's bound to the current connection
|
||||
/// </summary>
|
||||
public SmoMetadataProvider MetadataProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SMO metadata display info provider
|
||||
/// </summary>
|
||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current autocomplete suggestion list
|
||||
/// </summary>
|
||||
public IEnumerable<Declaration> CurrentSuggestions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database compatibility level from a server version
|
||||
/// </summary>
|
||||
/// <param name="serverVersion"></param>
|
||||
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
|
||||
{
|
||||
int versionMajor = Math.Max(serverVersion.Major, 8);
|
||||
|
||||
switch (versionMajor)
|
||||
{
|
||||
case 8:
|
||||
return DatabaseCompatibilityLevel.Version80;
|
||||
case 9:
|
||||
return DatabaseCompatibilityLevel.Version90;
|
||||
case 10:
|
||||
return DatabaseCompatibilityLevel.Version100;
|
||||
case 11:
|
||||
return DatabaseCompatibilityLevel.Version110;
|
||||
case 12:
|
||||
return DatabaseCompatibilityLevel.Version120;
|
||||
case 13:
|
||||
return DatabaseCompatibilityLevel.Version130;
|
||||
default:
|
||||
return DatabaseCompatibilityLevel.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transaction sql version from a server version
|
||||
/// </summary>
|
||||
/// <param name="serverVersion"></param>
|
||||
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
|
||||
{
|
||||
int versionMajor = Math.Max(serverVersion.Major, 9);
|
||||
|
||||
switch (versionMajor)
|
||||
{
|
||||
case 9:
|
||||
case 10:
|
||||
// In case of 10.0 we still use Version 10.5 as it is the closest available.
|
||||
return TransactSqlVersion.Version105;
|
||||
case 11:
|
||||
return TransactSqlVersion.Version110;
|
||||
case 12:
|
||||
return TransactSqlVersion.Version120;
|
||||
case 13:
|
||||
return TransactSqlVersion.Version130;
|
||||
default:
|
||||
return TransactSqlVersion.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,26 +16,22 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
/// <summary>
|
||||
/// Main application class for SQL Tools API Service Host executable
|
||||
/// </summary>
|
||||
class Program
|
||||
internal class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Main entry point into the SQL Tools API Service Host
|
||||
/// </summary>
|
||||
static void Main(string[] args)
|
||||
internal static void Main(string[] args)
|
||||
{
|
||||
// turn on Verbose logging during early development
|
||||
// we need to switch to Normal when preparing for public preview
|
||||
Logger.Initialize(minimumLogLevel: LogLevel.Verbose);
|
||||
Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host");
|
||||
|
||||
const string hostName = "SQL Tools Service Host";
|
||||
const string hostProfileId = "SQLToolsService";
|
||||
Version hostVersion = new Version(1,0);
|
||||
|
||||
// set up the host details and profile paths
|
||||
var hostDetails = new HostDetails(hostName, hostProfileId, hostVersion);
|
||||
var profilePaths = new ProfilePaths(hostProfileId, "baseAllUsersPath", "baseCurrentUserPath");
|
||||
SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails, profilePaths);
|
||||
var hostDetails = new HostDetails(version: new Version(1,0));
|
||||
|
||||
SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails);
|
||||
|
||||
// Grab the instance of the service host
|
||||
ServiceHost serviceHost = ServiceHost.Instance;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -37,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// Internal representation of the messages so we can modify internally
|
||||
/// </summary>
|
||||
private readonly List<string> resultMessages;
|
||||
private readonly List<ResultMessage> resultMessages;
|
||||
|
||||
/// <summary>
|
||||
/// Internal representation of the result sets so we can modify internally
|
||||
@@ -46,7 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
#endregion
|
||||
|
||||
internal Batch(string batchText, int startLine, IFileStreamFactory outputFileFactory)
|
||||
internal Batch(string batchText, int startLine, int startColumn, int endLine, int endColumn, IFileStreamFactory outputFileFactory)
|
||||
{
|
||||
// Sanity check for input
|
||||
Validate.IsNotNullOrEmptyString(nameof(batchText), batchText);
|
||||
@@ -54,10 +55,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
// Initialize the internal state
|
||||
BatchText = batchText;
|
||||
StartLine = startLine - 1; // -1 to make sure that the line number of the batch is 0-indexed, since SqlParser gives 1-indexed line numbers
|
||||
Selection = new SelectionData(startLine, startColumn, endLine, endColumn);
|
||||
HasExecuted = false;
|
||||
resultSets = new List<ResultSet>();
|
||||
resultMessages = new List<string>();
|
||||
resultMessages = new List<ResultMessage>();
|
||||
this.outputFileFactory = outputFileFactory;
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// Messages that have come back from the server
|
||||
/// </summary>
|
||||
public IEnumerable<string> ResultMessages
|
||||
public IEnumerable<ResultMessage> ResultMessages
|
||||
{
|
||||
get { return resultMessages; }
|
||||
}
|
||||
@@ -111,9 +112,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The 0-indexed line number that this batch started on
|
||||
/// The range from the file that is this batch
|
||||
/// </summary>
|
||||
internal int StartLine { get; set; }
|
||||
internal SelectionData Selection { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -134,19 +135,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
try
|
||||
{
|
||||
DbCommand command = null;
|
||||
|
||||
// Register the message listener to *this instance* of the batch
|
||||
// Note: This is being done to associate messages with batches
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage += StoreDbMessage;
|
||||
command = sqlConn.GetUnderlyingConnection().CreateCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
command = conn.CreateCommand();
|
||||
}
|
||||
|
||||
// Make sure we aren't using a ReliableCommad since we do not want automatic retry
|
||||
Debug.Assert(!(command is ReliableSqlConnection.ReliableSqlCommand), "ReliableSqlCommand command should not be used to execute queries");
|
||||
|
||||
// Create a command that we'll use for executing the query
|
||||
using (DbCommand command = conn.CreateCommand())
|
||||
using (command)
|
||||
{
|
||||
command.CommandText = BatchText;
|
||||
command.CommandType = CommandType.Text;
|
||||
command.CommandTimeout = 0;
|
||||
|
||||
// Execute the command to get back a reader
|
||||
using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken))
|
||||
@@ -157,9 +169,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
if (!reader.HasRows && reader.FieldCount == 0)
|
||||
{
|
||||
// Create a message with the number of affected rows -- IF the query affects rows
|
||||
resultMessages.Add(reader.RecordsAffected >= 0
|
||||
? SR.QueryServiceAffectedRows(reader.RecordsAffected)
|
||||
: SR.QueryServiceCompletedSuccessfully);
|
||||
resultMessages.Add(new ResultMessage(reader.RecordsAffected >= 0
|
||||
? SR.QueryServiceAffectedRows(reader.RecordsAffected)
|
||||
: SR.QueryServiceCompletedSuccessfully));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -172,7 +184,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
resultSets.Add(resultSet);
|
||||
|
||||
// Add a message for the number of rows the query returned
|
||||
resultMessages.Add(SR.QueryServiceAffectedRows(resultSet.RowCount));
|
||||
resultMessages.Add(new ResultMessage(SR.QueryServiceAffectedRows(resultSet.RowCount)));
|
||||
} while (await reader.NextResultAsync(cancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -190,10 +202,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
finally
|
||||
{
|
||||
// Remove the message event handler from the connection
|
||||
SqlConnection sqlConn = conn as SqlConnection;
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
sqlConn.InfoMessage -= StoreDbMessage;
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage -= StoreDbMessage;
|
||||
}
|
||||
|
||||
// Mark that we have executed
|
||||
@@ -233,7 +245,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <param name="args">Arguments from the event</param>
|
||||
private void StoreDbMessage(object sender, SqlInfoMessageEventArgs args)
|
||||
{
|
||||
resultMessages.Add(args.Message);
|
||||
resultMessages.Add(new ResultMessage(args.Message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -253,16 +265,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
SqlError sqlError = error as SqlError;
|
||||
if (sqlError != null)
|
||||
{
|
||||
int lineNumber = sqlError.LineNumber + StartLine;
|
||||
string message = SR.QueryServiceErrorFormat(sqlError.Number, sqlError.Class, sqlError.State,
|
||||
lineNumber, Environment.NewLine, sqlError.Message);
|
||||
resultMessages.Add(message);
|
||||
int lineNumber = sqlError.LineNumber + Selection.StartLine;
|
||||
string message = string.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}",
|
||||
sqlError.Number, sqlError.Class, sqlError.State, lineNumber,
|
||||
Environment.NewLine, sqlError.Message);
|
||||
resultMessages.Add(new ResultMessage(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultMessages.Add(dbe.Message);
|
||||
resultMessages.Add(new ResultMessage(dbe.Message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The selection from the file for this batch
|
||||
/// </summary>
|
||||
public SelectionData Selection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any messages that came back from the server during execution of the batch
|
||||
/// </summary>
|
||||
public string[] Messages { get; set; }
|
||||
public ResultMessage[] Messages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summaries of the result sets inside the batch
|
||||
|
||||
@@ -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 for internally passing results from a cell around.
|
||||
/// </summary>
|
||||
public class DbCellValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Display value for the cell, suitable to be passed back to the client
|
||||
/// </summary>
|
||||
public string DisplayValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw object for the cell, for use internally
|
||||
/// </summary>
|
||||
internal object RawObject { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// <summary>
|
||||
/// Whether or not the column is XML
|
||||
/// </summary>
|
||||
public bool IsXml { get; private set; }
|
||||
public bool IsXml { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is JSON
|
||||
/// </summary>
|
||||
public bool IsJson { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -7,15 +7,30 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Container class for a selection range from file
|
||||
/// </summary>
|
||||
public class SelectionData {
|
||||
public int StartLine { get; set; }
|
||||
public int StartColumn { get; set; }
|
||||
public int EndLine { get; set; }
|
||||
public int EndColumn { get; set; }
|
||||
public SelectionData(int startLine, int startColumn, int endLine, int endColumn) {
|
||||
StartLine = startLine;
|
||||
StartColumn = startColumn;
|
||||
EndLine = endLine;
|
||||
EndColumn = endColumn;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Parameters for the query execute request
|
||||
/// </summary>
|
||||
public class QueryExecuteParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The text of the query to execute
|
||||
/// The selection from the document
|
||||
/// </summary>
|
||||
public string QueryText { get; set; }
|
||||
public SelectionData QuerySelection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URI for the editor that is asking for the query execute
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// 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.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Result message object with timestamp and actual message
|
||||
/// </summary>
|
||||
public class ResultMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Timestamp of the message
|
||||
/// Stored in UTC ISO 8601 format; should be localized before displaying to any user
|
||||
/// </summary>
|
||||
public string Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message contents
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full constructor
|
||||
/// </summary>
|
||||
public ResultMessage(string timeStamp, string message)
|
||||
{
|
||||
Time = timeStamp;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with default "Now" time
|
||||
/// </summary>
|
||||
public ResultMessage(string message)
|
||||
{
|
||||
Time = DateTime.Now.ToString("o");
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// <summary>
|
||||
/// 2D array of the cell values requested from result set
|
||||
/// </summary>
|
||||
public object[][] Rows { get; set; }
|
||||
public string[][] Rows { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// URI for the editor that called save results
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start index of the selected rows (inclusive)
|
||||
/// </summary>
|
||||
public int? RowStartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// End index of the selected rows (inclusive)
|
||||
/// </summary>
|
||||
public int? RowEndIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start index of the selected columns (inclusive)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int? ColumnStartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// End index of the selected columns (inclusive)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int? ColumnEndIndex { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,25 +3,16 @@
|
||||
// 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.QueryExecution.DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a value returned from a read from a file stream. This is used to eliminate ref
|
||||
/// parameters used in the read methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value that was read</typeparam>
|
||||
public struct FileStreamReadResult<T>
|
||||
public struct FileStreamReadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not the value of the field is null
|
||||
/// </summary>
|
||||
public bool IsNull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the field. If <see cref="IsNull"/> is true, this will be set to <c>default(T)</c>
|
||||
/// </summary>
|
||||
public T Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total length in bytes of the value, (including the bytes used to store the length
|
||||
/// of the value)
|
||||
@@ -34,17 +25,20 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </remarks>
|
||||
public int TotalLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value of the cell
|
||||
/// </summary>
|
||||
public DbCellValue Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new FileStreamReadResult
|
||||
/// </summary>
|
||||
/// <param name="value">The value of the result</param>
|
||||
/// <param name="totalLength">The number of bytes for the used to store the value's length and value</param>
|
||||
/// <param name="isNull">Whether or not the value is <c>null</c></param>
|
||||
public FileStreamReadResult(T value, int totalLength, bool isNull)
|
||||
/// <param name="value">The value of the result, ready for consumption by a client</param>
|
||||
/// <param name="totalLength">The number of bytes for the used to store the value's length and value</param>s
|
||||
public FileStreamReadResult(DbCellValue value, int totalLength)
|
||||
{
|
||||
Value = value;
|
||||
TotalLength = totalLength;
|
||||
IsNull = isNull;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
// Sanity check for valid buffer length, fileName, and accessMethod
|
||||
Validate.IsGreaterThan(nameof(bufferLength), bufferLength, 0);
|
||||
Validate.IsNotNullOrEmptyString(nameof(fileName), fileName);
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(fileName), fileName);
|
||||
if (accessMethod == FileAccess.Write)
|
||||
{
|
||||
throw new ArgumentException(SR.QueryServiceFileWrapperWriteOnly, nameof(fileName));
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
@@ -15,21 +14,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
public interface IFileStreamReader : IDisposable
|
||||
{
|
||||
object[] ReadRow(long offset, IEnumerable<DbColumnWrapper> columns);
|
||||
FileStreamReadResult<short> ReadInt16(long i64Offset);
|
||||
FileStreamReadResult<int> ReadInt32(long i64Offset);
|
||||
FileStreamReadResult<long> ReadInt64(long i64Offset);
|
||||
FileStreamReadResult<byte> ReadByte(long i64Offset);
|
||||
FileStreamReadResult<char> ReadChar(long i64Offset);
|
||||
FileStreamReadResult<bool> ReadBoolean(long i64Offset);
|
||||
FileStreamReadResult<float> ReadSingle(long i64Offset);
|
||||
FileStreamReadResult<double> ReadDouble(long i64Offset);
|
||||
FileStreamReadResult<SqlDecimal> ReadSqlDecimal(long i64Offset);
|
||||
FileStreamReadResult<decimal> ReadDecimal(long i64Offset);
|
||||
FileStreamReadResult<DateTime> ReadDateTime(long i64Offset);
|
||||
FileStreamReadResult<TimeSpan> ReadTimeSpan(long i64Offset);
|
||||
FileStreamReadResult<string> ReadString(long i64Offset);
|
||||
FileStreamReadResult<byte[]> ReadBytes(long i64Offset);
|
||||
FileStreamReadResult<DateTimeOffset> ReadDateTimeOffset(long i64Offset);
|
||||
IList<DbCellValue> ReadRow(long offset, IEnumerable<DbColumnWrapper> columns);
|
||||
FileStreamReadResult ReadInt16(long i64Offset);
|
||||
FileStreamReadResult ReadInt32(long i64Offset);
|
||||
FileStreamReadResult ReadInt64(long i64Offset);
|
||||
FileStreamReadResult ReadByte(long i64Offset);
|
||||
FileStreamReadResult ReadChar(long i64Offset);
|
||||
FileStreamReadResult ReadBoolean(long i64Offset);
|
||||
FileStreamReadResult ReadSingle(long i64Offset);
|
||||
FileStreamReadResult ReadDouble(long i64Offset);
|
||||
FileStreamReadResult ReadSqlDecimal(long i64Offset);
|
||||
FileStreamReadResult ReadDecimal(long i64Offset);
|
||||
FileStreamReadResult ReadDateTime(long i64Offset);
|
||||
FileStreamReadResult ReadTimeSpan(long i64Offset);
|
||||
FileStreamReadResult ReadString(long i64Offset);
|
||||
FileStreamReadResult ReadBytes(long i64Offset);
|
||||
FileStreamReadResult ReadDateTimeOffset(long i64Offset);
|
||||
FileStreamReadResult ReadGuid(long offset);
|
||||
FileStreamReadResult ReadMoney(long offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
int WriteDateTimeOffset(DateTimeOffset dtoVal);
|
||||
int WriteTimeSpan(TimeSpan val);
|
||||
int WriteString(string val);
|
||||
int WriteBytes(byte[] bytes, int length);
|
||||
int WriteBytes(byte[] bytes);
|
||||
int WriteGuid(Guid val);
|
||||
int WriteMoney(SqlMoney val);
|
||||
void FlushBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
@@ -26,6 +25,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
private readonly IFileStreamWrapper fileStream;
|
||||
|
||||
private Dictionary<Type, Func<long, FileStreamReadResult>> readMethods;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -41,6 +42,40 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
// Create internal buffer
|
||||
buffer = new byte[DefaultBufferSize];
|
||||
|
||||
// Create the methods that will be used to read back
|
||||
readMethods = new Dictionary<Type, Func<long, FileStreamReadResult>>
|
||||
{
|
||||
{typeof(string), ReadString},
|
||||
{typeof(short), ReadInt16},
|
||||
{typeof(int), ReadInt32},
|
||||
{typeof(long), ReadInt64},
|
||||
{typeof(byte), ReadByte},
|
||||
{typeof(char), ReadChar},
|
||||
{typeof(bool), ReadBoolean},
|
||||
{typeof(double), ReadDouble},
|
||||
{typeof(float), ReadSingle},
|
||||
{typeof(decimal), ReadDecimal},
|
||||
{typeof(DateTime), ReadDateTime},
|
||||
{typeof(DateTimeOffset), ReadDateTimeOffset},
|
||||
{typeof(TimeSpan), ReadTimeSpan},
|
||||
{typeof(byte[]), ReadBytes},
|
||||
|
||||
{typeof(SqlString), ReadString},
|
||||
{typeof(SqlInt16), ReadInt16},
|
||||
{typeof(SqlInt32), ReadInt32},
|
||||
{typeof(SqlInt64), ReadInt64},
|
||||
{typeof(SqlByte), ReadByte},
|
||||
{typeof(SqlBoolean), ReadBoolean},
|
||||
{typeof(SqlDouble), ReadDouble},
|
||||
{typeof(SqlSingle), ReadSingle},
|
||||
{typeof(SqlDecimal), ReadSqlDecimal},
|
||||
{typeof(SqlDateTime), ReadDateTime},
|
||||
{typeof(SqlBytes), ReadBytes},
|
||||
{typeof(SqlBinary), ReadBytes},
|
||||
{typeof(SqlGuid), ReadGuid},
|
||||
{typeof(SqlMoney), ReadMoney},
|
||||
};
|
||||
}
|
||||
|
||||
#region IFileStreamStorage Implementation
|
||||
@@ -50,12 +85,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file where the row starts</param>
|
||||
/// <param name="columns">The columns that were encoded</param>
|
||||
/// <returns>The objects from the row</returns>
|
||||
public object[] ReadRow(long fileOffset, IEnumerable<DbColumnWrapper> columns)
|
||||
/// <returns>The objects from the row, ready for output to the client</returns>
|
||||
public IList<DbCellValue> ReadRow(long fileOffset, IEnumerable<DbColumnWrapper> columns)
|
||||
{
|
||||
// Initialize for the loop
|
||||
long currentFileOffset = fileOffset;
|
||||
List<object> results = new List<object>();
|
||||
List<DbCellValue> results = new List<DbCellValue>();
|
||||
|
||||
// Iterate over the columns
|
||||
foreach (DbColumnWrapper column in columns)
|
||||
@@ -65,22 +100,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
if (column.IsSqlVariant)
|
||||
{
|
||||
// For SQL Variant columns, the type is written first in string format
|
||||
FileStreamReadResult<string> sqlVariantTypeResult = ReadString(currentFileOffset);
|
||||
FileStreamReadResult sqlVariantTypeResult = ReadString(currentFileOffset);
|
||||
currentFileOffset += sqlVariantTypeResult.TotalLength;
|
||||
string sqlVariantType = (string)sqlVariantTypeResult.Value.RawObject;
|
||||
|
||||
// If the typename is null, then the whole value is null
|
||||
if (sqlVariantTypeResult.IsNull)
|
||||
if (sqlVariantTypeResult.Value == null || string.IsNullOrEmpty(sqlVariantType))
|
||||
{
|
||||
results.Add(null);
|
||||
results.Add(sqlVariantTypeResult.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The typename is stored in the string
|
||||
colType = Type.GetType(sqlVariantTypeResult.Value);
|
||||
colType = Type.GetType(sqlVariantType);
|
||||
|
||||
// Workaround .NET bug, see sqlbu# 440643 and vswhidbey# 599834
|
||||
// TODO: Is this workaround necessary for .NET Core?
|
||||
if (colType == null && sqlVariantTypeResult.Value == @"System.Data.SqlTypes.SqlSingle")
|
||||
if (colType == null && sqlVariantType == "System.Data.SqlTypes.SqlSingle")
|
||||
{
|
||||
colType = typeof(SqlSingle);
|
||||
}
|
||||
@@ -90,380 +126,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
colType = column.DataType;
|
||||
}
|
||||
|
||||
if (colType == typeof(string))
|
||||
{
|
||||
// String - most frequently used data type
|
||||
FileStreamReadResult<string> result = ReadString(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
results.Add(result.IsNull ? null : result.Value);
|
||||
}
|
||||
else if (colType == typeof(SqlString))
|
||||
{
|
||||
// SqlString
|
||||
FileStreamReadResult<string> result = ReadString(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
results.Add(result.IsNull ? null : (SqlString) result.Value);
|
||||
}
|
||||
else if (colType == typeof(short))
|
||||
{
|
||||
// Int16
|
||||
FileStreamReadResult<short> result = ReadInt16(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlInt16))
|
||||
{
|
||||
// SqlInt16
|
||||
FileStreamReadResult<short> result = ReadInt16(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlInt16)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(int))
|
||||
{
|
||||
// Int32
|
||||
FileStreamReadResult<int> result = ReadInt32(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlInt32))
|
||||
{
|
||||
// SqlInt32
|
||||
FileStreamReadResult<int> result = ReadInt32(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlInt32)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(long))
|
||||
{
|
||||
// Int64
|
||||
FileStreamReadResult<long> result = ReadInt64(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlInt64))
|
||||
{
|
||||
// SqlInt64
|
||||
FileStreamReadResult<long> result = ReadInt64(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlInt64)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(byte))
|
||||
{
|
||||
// byte
|
||||
FileStreamReadResult<byte> result = ReadByte(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlByte))
|
||||
{
|
||||
// SqlByte
|
||||
FileStreamReadResult<byte> result = ReadByte(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlByte)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(char))
|
||||
{
|
||||
// Char
|
||||
FileStreamReadResult<char> result = ReadChar(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(bool))
|
||||
{
|
||||
// Bool
|
||||
FileStreamReadResult<bool> result = ReadBoolean(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlBoolean))
|
||||
{
|
||||
// SqlBoolean
|
||||
FileStreamReadResult<bool> result = ReadBoolean(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlBoolean)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(double))
|
||||
{
|
||||
// double
|
||||
FileStreamReadResult<double> result = ReadDouble(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlDouble))
|
||||
{
|
||||
// SqlByte
|
||||
FileStreamReadResult<double> result = ReadDouble(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlDouble)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(float))
|
||||
{
|
||||
// float
|
||||
FileStreamReadResult<float> result = ReadSingle(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlSingle))
|
||||
{
|
||||
// SqlSingle
|
||||
FileStreamReadResult<float> result = ReadSingle(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlSingle)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(decimal))
|
||||
{
|
||||
// Decimal
|
||||
FileStreamReadResult<decimal> result = ReadDecimal(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlDecimal))
|
||||
{
|
||||
// SqlDecimal
|
||||
FileStreamReadResult<SqlDecimal> result = ReadSqlDecimal(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(DateTime))
|
||||
{
|
||||
// DateTime
|
||||
FileStreamReadResult<DateTime> result = ReadDateTime(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlDateTime))
|
||||
{
|
||||
// SqlDateTime
|
||||
FileStreamReadResult<DateTime> result = ReadDateTime(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add((SqlDateTime)result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(DateTimeOffset))
|
||||
{
|
||||
// DateTimeOffset
|
||||
FileStreamReadResult<DateTimeOffset> result = ReadDateTimeOffset(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(TimeSpan))
|
||||
{
|
||||
// TimeSpan
|
||||
FileStreamReadResult<TimeSpan> result = ReadTimeSpan(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(byte[]))
|
||||
{
|
||||
// Byte Array
|
||||
FileStreamReadResult<byte[]> result = ReadBytes(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull || (column.IsUdt && result.Value.Length == 0))
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(result.Value);
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlBytes))
|
||||
{
|
||||
// SqlBytes
|
||||
FileStreamReadResult<byte[]> result = ReadBytes(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
results.Add(result.IsNull ? null : new SqlBytes(result.Value));
|
||||
}
|
||||
else if (colType == typeof(SqlBinary))
|
||||
{
|
||||
// SqlBinary
|
||||
FileStreamReadResult<byte[]> result = ReadBytes(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
results.Add(result.IsNull ? null : new SqlBinary(result.Value));
|
||||
}
|
||||
else if (colType == typeof(SqlGuid))
|
||||
{
|
||||
// SqlGuid
|
||||
FileStreamReadResult<byte[]> result = ReadBytes(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(new SqlGuid(result.Value));
|
||||
}
|
||||
}
|
||||
else if (colType == typeof(SqlMoney))
|
||||
{
|
||||
// SqlMoney
|
||||
FileStreamReadResult<decimal> result = ReadDecimal(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
if (result.IsNull)
|
||||
{
|
||||
results.Add(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(new SqlMoney(result.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
// Use the right read function for the type to read the data from the file
|
||||
Func<long, FileStreamReadResult> readFunc;
|
||||
if(!readMethods.TryGetValue(colType, out readFunc))
|
||||
{
|
||||
// Treat everything else as a string
|
||||
FileStreamReadResult<string> result = ReadString(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
results.Add(result.IsNull ? null : result.Value);
|
||||
}
|
||||
readFunc = ReadString;
|
||||
}
|
||||
FileStreamReadResult result = readFunc(currentFileOffset);
|
||||
currentFileOffset += result.TotalLength;
|
||||
results.Add(result.Value);
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -471,21 +146,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the short from</param>
|
||||
/// <returns>A short</returns>
|
||||
public FileStreamReadResult<short> ReadInt16(long fileOffset)
|
||||
public FileStreamReadResult ReadInt16(long fileOffset)
|
||||
{
|
||||
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 2, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
short val = default(short);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = BitConverter.ToInt16(buffer, 0);
|
||||
}
|
||||
|
||||
return new FileStreamReadResult<short>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => BitConverter.ToInt16(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -493,19 +156,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the int from</param>
|
||||
/// <returns>An int</returns>
|
||||
public FileStreamReadResult<int> ReadInt32(long fileOffset)
|
||||
public FileStreamReadResult ReadInt32(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 4, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
int val = default(int);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
return new FileStreamReadResult<int>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => BitConverter.ToInt32(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -513,19 +166,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the long from</param>
|
||||
/// <returns>A long</returns>
|
||||
public FileStreamReadResult<long> ReadInt64(long fileOffset)
|
||||
public FileStreamReadResult ReadInt64(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 8, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
long val = default(long);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
return new FileStreamReadResult<long>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => BitConverter.ToInt64(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -533,19 +176,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the byte from</param>
|
||||
/// <returns>A byte</returns>
|
||||
public FileStreamReadResult<byte> ReadByte(long fileOffset)
|
||||
public FileStreamReadResult ReadByte(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 1, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
byte val = default(byte);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = buffer[0];
|
||||
}
|
||||
return new FileStreamReadResult<byte>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => buffer[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -553,19 +186,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the char from</param>
|
||||
/// <returns>A char</returns>
|
||||
public FileStreamReadResult<char> ReadChar(long fileOffset)
|
||||
public FileStreamReadResult ReadChar(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 2, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
char val = default(char);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = BitConverter.ToChar(buffer, 0);
|
||||
}
|
||||
return new FileStreamReadResult<char>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => BitConverter.ToChar(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -573,19 +196,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the bool from</param>
|
||||
/// <returns>A bool</returns>
|
||||
public FileStreamReadResult<bool> ReadBoolean(long fileOffset)
|
||||
public FileStreamReadResult ReadBoolean(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 1, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
bool val = default(bool);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = buffer[0] == 0x01;
|
||||
}
|
||||
return new FileStreamReadResult<bool>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => buffer[0] == 0x1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -593,19 +206,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the single from</param>
|
||||
/// <returns>A single</returns>
|
||||
public FileStreamReadResult<float> ReadSingle(long fileOffset)
|
||||
public FileStreamReadResult ReadSingle(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 4, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
float val = default(float);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = BitConverter.ToSingle(buffer, 0);
|
||||
}
|
||||
return new FileStreamReadResult<float>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => BitConverter.ToSingle(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -613,19 +216,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="fileOffset">Offset into the file to read the double from</param>
|
||||
/// <returns>A double</returns>
|
||||
public FileStreamReadResult<double> ReadDouble(long fileOffset)
|
||||
public FileStreamReadResult ReadDouble(long fileOffset)
|
||||
{
|
||||
LengthResult length = ReadLength(fileOffset);
|
||||
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 8, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
double val = default(double);
|
||||
if (!isNull)
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
val = BitConverter.ToDouble(buffer, 0);
|
||||
}
|
||||
return new FileStreamReadResult<double>(val, length.TotalLength, isNull);
|
||||
return ReadCellHelper(fileOffset, length => BitConverter.ToDouble(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -633,23 +226,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the SqlDecimal from</param>
|
||||
/// <returns>A SqlDecimal</returns>
|
||||
public FileStreamReadResult<SqlDecimal> ReadSqlDecimal(long offset)
|
||||
public FileStreamReadResult ReadSqlDecimal(long offset)
|
||||
{
|
||||
LengthResult length = ReadLength(offset);
|
||||
Debug.Assert(length.ValueLength == 0 || (length.ValueLength - 3)%4 == 0,
|
||||
string.Format("Invalid data length: {0}", length.ValueLength));
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
SqlDecimal val = default(SqlDecimal);
|
||||
if (!isNull)
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
|
||||
int[] arrInt32 = new int[(length.ValueLength - 3)/4];
|
||||
Buffer.BlockCopy(buffer, 3, arrInt32, 0, length.ValueLength - 3);
|
||||
val = new SqlDecimal(buffer[0], buffer[1], 1 == buffer[2], arrInt32);
|
||||
}
|
||||
return new FileStreamReadResult<SqlDecimal>(val, length.TotalLength, isNull);
|
||||
int[] arrInt32 = new int[(length - 3) / 4];
|
||||
Buffer.BlockCopy(buffer, 3, arrInt32, 0, length - 3);
|
||||
return new SqlDecimal(buffer[0], buffer[1], buffer[2] == 1, arrInt32);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -657,22 +241,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the decimal from</param>
|
||||
/// <returns>A decimal</returns>
|
||||
public FileStreamReadResult<decimal> ReadDecimal(long offset)
|
||||
public FileStreamReadResult ReadDecimal(long offset)
|
||||
{
|
||||
LengthResult length = ReadLength(offset);
|
||||
Debug.Assert(length.ValueLength%4 == 0, "Invalid data length");
|
||||
|
||||
bool isNull = length.ValueLength == 0;
|
||||
decimal val = default(decimal);
|
||||
if (!isNull)
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
|
||||
int[] arrInt32 = new int[length.ValueLength/4];
|
||||
Buffer.BlockCopy(buffer, 0, arrInt32, 0, length.ValueLength);
|
||||
val = new decimal(arrInt32);
|
||||
}
|
||||
return new FileStreamReadResult<decimal>(val, length.TotalLength, isNull);
|
||||
int[] arrInt32 = new int[length / 4];
|
||||
Buffer.BlockCopy(buffer, 0, arrInt32, 0, length);
|
||||
return new decimal(arrInt32);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -680,15 +256,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the DateTime from</param>
|
||||
/// <returns>A DateTime</returns>
|
||||
public FileStreamReadResult<DateTime> ReadDateTime(long offset)
|
||||
public FileStreamReadResult ReadDateTime(long offset)
|
||||
{
|
||||
FileStreamReadResult<long> ticks = ReadInt64(offset);
|
||||
DateTime val = default(DateTime);
|
||||
if (!ticks.IsNull)
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
val = new DateTime(ticks.Value);
|
||||
}
|
||||
return new FileStreamReadResult<DateTime>(val, ticks.TotalLength, ticks.IsNull);
|
||||
long ticks = BitConverter.ToInt64(buffer, 0);
|
||||
return new DateTime(ticks);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -696,27 +270,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the DateTimeOffset from</param>
|
||||
/// <returns>A DateTimeOffset</returns>
|
||||
public FileStreamReadResult<DateTimeOffset> ReadDateTimeOffset(long offset)
|
||||
public FileStreamReadResult ReadDateTimeOffset(long offset)
|
||||
{
|
||||
// DateTimeOffset is represented by DateTime.Ticks followed by TimeSpan.Ticks
|
||||
// both as Int64 values
|
||||
|
||||
// read the DateTime ticks
|
||||
DateTimeOffset val = default(DateTimeOffset);
|
||||
FileStreamReadResult<long> dateTimeTicks = ReadInt64(offset);
|
||||
int totalLength = dateTimeTicks.TotalLength;
|
||||
if (dateTimeTicks.TotalLength > 0 && !dateTimeTicks.IsNull)
|
||||
{
|
||||
// read the TimeSpan ticks
|
||||
FileStreamReadResult<long> timeSpanTicks = ReadInt64(offset + dateTimeTicks.TotalLength);
|
||||
Debug.Assert(!timeSpanTicks.IsNull, "TimeSpan ticks cannot be null if DateTime ticks are not null!");
|
||||
|
||||
totalLength += timeSpanTicks.TotalLength;
|
||||
|
||||
// build the DateTimeOffset
|
||||
val = new DateTimeOffset(new DateTime(dateTimeTicks.Value), new TimeSpan(timeSpanTicks.Value));
|
||||
}
|
||||
return new FileStreamReadResult<DateTimeOffset>(val, totalLength, dateTimeTicks.IsNull);
|
||||
return ReadCellHelper(offset, length => {
|
||||
long dtTicks = BitConverter.ToInt64(buffer, 0);
|
||||
long dtOffset = BitConverter.ToInt64(buffer, 8);
|
||||
return new DateTimeOffset(new DateTime(dtTicks), new TimeSpan(dtOffset));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -724,15 +286,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the TimeSpan from</param>
|
||||
/// <returns>A TimeSpan</returns>
|
||||
public FileStreamReadResult<TimeSpan> ReadTimeSpan(long offset)
|
||||
public FileStreamReadResult ReadTimeSpan(long offset)
|
||||
{
|
||||
FileStreamReadResult<long> timeSpanTicks = ReadInt64(offset);
|
||||
TimeSpan val = default(TimeSpan);
|
||||
if (!timeSpanTicks.IsNull)
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
val = new TimeSpan(timeSpanTicks.Value);
|
||||
}
|
||||
return new FileStreamReadResult<TimeSpan>(val, timeSpanTicks.TotalLength, timeSpanTicks.IsNull);
|
||||
long ticks = BitConverter.ToInt64(buffer, 0);
|
||||
return new TimeSpan(ticks);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -740,24 +300,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the string from</param>
|
||||
/// <returns>A string</returns>
|
||||
public FileStreamReadResult<string> ReadString(long offset)
|
||||
public FileStreamReadResult ReadString(long offset)
|
||||
{
|
||||
LengthResult fieldLength = ReadLength(offset);
|
||||
Debug.Assert(fieldLength.ValueLength%2 == 0, "Invalid data length");
|
||||
|
||||
if (fieldLength.ValueLength == 0) // there is no data
|
||||
{
|
||||
// If the total length is 5 (5 bytes for length, 0 for value), then the string is empty
|
||||
// Otherwise, the string is null
|
||||
bool isNull = fieldLength.TotalLength != 5;
|
||||
return new FileStreamReadResult<string>(isNull ? null : string.Empty,
|
||||
fieldLength.TotalLength, isNull);
|
||||
}
|
||||
|
||||
// positive length
|
||||
AssureBufferLength(fieldLength.ValueLength);
|
||||
fileStream.ReadData(buffer, fieldLength.ValueLength);
|
||||
return new FileStreamReadResult<string>(Encoding.Unicode.GetString(buffer, 0, fieldLength.ValueLength), fieldLength.TotalLength, false);
|
||||
return ReadCellHelper(offset, length =>
|
||||
length > 0
|
||||
? Encoding.Unicode.GetString(buffer, 0, length)
|
||||
: string.Empty, totalLength => totalLength == 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -765,23 +313,54 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the bytes from</param>
|
||||
/// <returns>A byte array</returns>
|
||||
public FileStreamReadResult<byte[]> ReadBytes(long offset)
|
||||
public FileStreamReadResult ReadBytes(long offset)
|
||||
{
|
||||
LengthResult fieldLength = ReadLength(offset);
|
||||
|
||||
if (fieldLength.ValueLength == 0)
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
// If the total length is 5 (5 bytes for length, 0 for value), then the byte array is 0x
|
||||
// Otherwise, the byte array is null
|
||||
bool isNull = fieldLength.TotalLength != 5;
|
||||
return new FileStreamReadResult<byte[]>(isNull ? null : new byte[0],
|
||||
fieldLength.TotalLength, isNull);
|
||||
}
|
||||
byte[] output = new byte[length];
|
||||
Buffer.BlockCopy(buffer, 0, output, 0, length);
|
||||
return output;
|
||||
}, totalLength => totalLength == 1,
|
||||
bytes =>
|
||||
{
|
||||
StringBuilder sb = new StringBuilder("0x");
|
||||
foreach (byte b in bytes)
|
||||
{
|
||||
sb.AppendFormat("{0:X2}", b);
|
||||
}
|
||||
return sb.ToString();
|
||||
});
|
||||
}
|
||||
|
||||
// positive length
|
||||
byte[] val = new byte[fieldLength.ValueLength];
|
||||
fileStream.ReadData(val, fieldLength.ValueLength);
|
||||
return new FileStreamReadResult<byte[]>(val, fieldLength.TotalLength, false);
|
||||
/// <summary>
|
||||
/// Reads the bytes that make up a GUID at the offset provided
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read the bytes from</param>
|
||||
/// <returns>A guid type object</returns>
|
||||
public FileStreamReadResult ReadGuid(long offset)
|
||||
{
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
byte[] output = new byte[length];
|
||||
Buffer.BlockCopy(buffer, 0, output, 0, length);
|
||||
return new SqlGuid(output);
|
||||
}, totalLength => totalLength == 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a SqlMoney type from the offset provided
|
||||
/// into a
|
||||
/// </summary>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns>A sql money type object</returns>
|
||||
public FileStreamReadResult ReadMoney(long offset)
|
||||
{
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
int[] arrInt32 = new int[length / 4];
|
||||
Buffer.BlockCopy(buffer, 0, arrInt32, 0, length);
|
||||
return new SqlMoney(new decimal(arrInt32));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -813,6 +392,58 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer that is of the specified length if the buffer is not already
|
||||
/// at least as long as specified.
|
||||
/// </summary>
|
||||
/// <param name="newBufferLength">The minimum buffer size</param>
|
||||
private void AssureBufferLength(int newBufferLength)
|
||||
{
|
||||
if (buffer.Length < newBufferLength)
|
||||
{
|
||||
buffer = new byte[newBufferLength];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the value of a cell from the file wrapper, checks to see if it null using
|
||||
/// <paramref name="isNullFunc"/>, and converts it to the proper output type using
|
||||
/// <paramref name="convertFunc"/>.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to read from</param>
|
||||
/// <param name="convertFunc">Function to use to convert the buffer to the target type</param>
|
||||
/// <param name="isNullFunc">
|
||||
/// If provided, this function will be used to determine if the value is null
|
||||
/// </param>
|
||||
/// <param name="toStringFunc">Optional function to use to convert the object to a string.</param>
|
||||
/// <typeparam name="T">The expected type of the cell. Used to keep the code honest</typeparam>
|
||||
/// <returns>The object, a display value, and the length of the value + its length</returns>
|
||||
private FileStreamReadResult ReadCellHelper<T>(long offset, Func<int, T> convertFunc, Func<int, bool> isNullFunc = null, Func<T, string> toStringFunc = null)
|
||||
{
|
||||
LengthResult length = ReadLength(offset);
|
||||
DbCellValue result = new DbCellValue();
|
||||
|
||||
if (isNullFunc == null ? length.ValueLength == 0 : isNullFunc(length.TotalLength))
|
||||
{
|
||||
result.RawObject = null;
|
||||
result.DisplayValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssureBufferLength(length.ValueLength);
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
T resultObject = convertFunc(length.ValueLength);
|
||||
result.RawObject = resultObject;
|
||||
result.DisplayValue = toStringFunc == null ? result.RawObject.ToString() : toStringFunc(resultObject);
|
||||
}
|
||||
|
||||
return new FileStreamReadResult(result, length.TotalLength);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Internal struct used for representing the length of a field from the file
|
||||
/// </summary>
|
||||
@@ -837,19 +468,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer that is of the specified length if the buffer is not already
|
||||
/// at least as long as specified.
|
||||
/// </summary>
|
||||
/// <param name="newBufferLength">The minimum buffer size</param>
|
||||
private void AssureBufferLength(int newBufferLength)
|
||||
{
|
||||
if (buffer.Length < newBufferLength)
|
||||
{
|
||||
buffer = new byte[newBufferLength];
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
private bool disposed;
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
@@ -19,14 +19,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
public class ServiceBufferFileStreamWriter : IFileStreamWriter
|
||||
{
|
||||
#region Properties
|
||||
private const int DefaultBufferLength = 8192;
|
||||
|
||||
public const int DefaultBufferLength = 8192;
|
||||
|
||||
private int MaxCharsToStore { get; set; }
|
||||
private int MaxXmlCharsToStore { get; set; }
|
||||
#region Member Variables
|
||||
|
||||
private readonly IFileStreamWrapper fileStream;
|
||||
private readonly int maxCharsToStore;
|
||||
private readonly int maxXmlCharsToStore;
|
||||
|
||||
private IFileStreamWrapper FileStream { get; set; }
|
||||
private byte[] byteBuffer;
|
||||
private readonly short[] shortBuffer;
|
||||
private readonly int[] intBuffer;
|
||||
@@ -35,6 +35,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
private readonly double[] doubleBuffer;
|
||||
private readonly float[] floatBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Functions to use for writing various types to a file
|
||||
/// </summary>
|
||||
private readonly Dictionary<Type, Func<object, int>> writeMethods;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -47,8 +52,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
public ServiceBufferFileStreamWriter(IFileStreamWrapper fileWrapper, string fileName, int maxCharsToStore, int maxXmlCharsToStore)
|
||||
{
|
||||
// open file for reading/writing
|
||||
FileStream = fileWrapper;
|
||||
FileStream.Init(fileName, DefaultBufferLength, FileAccess.ReadWrite);
|
||||
fileStream = fileWrapper;
|
||||
fileStream.Init(fileName, DefaultBufferLength, FileAccess.ReadWrite);
|
||||
|
||||
// create internal buffer
|
||||
byteBuffer = new byte[DefaultBufferLength];
|
||||
@@ -63,8 +68,42 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
floatBuffer = new float[1];
|
||||
|
||||
// Store max chars to store
|
||||
MaxCharsToStore = maxCharsToStore;
|
||||
MaxXmlCharsToStore = maxXmlCharsToStore;
|
||||
this.maxCharsToStore = maxCharsToStore;
|
||||
this.maxXmlCharsToStore = maxXmlCharsToStore;
|
||||
|
||||
// Define what methods to use to write a type to the file
|
||||
writeMethods = new Dictionary<Type, Func<object, int>>
|
||||
{
|
||||
{typeof(string), val => WriteString((string) val)},
|
||||
{typeof(short), val => WriteInt16((short) val)},
|
||||
{typeof(int), val => WriteInt32((int) val)},
|
||||
{typeof(long), val => WriteInt64((long) val)},
|
||||
{typeof(byte), val => WriteByte((byte) val)},
|
||||
{typeof(char), val => WriteChar((char) val)},
|
||||
{typeof(bool), val => WriteBoolean((bool) val)},
|
||||
{typeof(double), val => WriteDouble((double) val) },
|
||||
{typeof(float), val => WriteSingle((float) val) },
|
||||
{typeof(decimal), val => WriteDecimal((decimal) val) },
|
||||
{typeof(DateTime), val => WriteDateTime((DateTime) val) },
|
||||
{typeof(DateTimeOffset), val => WriteDateTimeOffset((DateTimeOffset) val) },
|
||||
{typeof(TimeSpan), val => WriteTimeSpan((TimeSpan) val) },
|
||||
{typeof(byte[]), val => WriteBytes((byte[]) val)},
|
||||
|
||||
{typeof(SqlString), val => WriteNullable((SqlString) val, obj => WriteString((string) obj))},
|
||||
{typeof(SqlInt16), val => WriteNullable((SqlInt16) val, obj => WriteInt16((short) obj))},
|
||||
{typeof(SqlInt32), val => WriteNullable((SqlInt32) val, obj => WriteInt32((int) obj))},
|
||||
{typeof(SqlInt64), val => WriteNullable((SqlInt64) val, obj => WriteInt64((long) obj)) },
|
||||
{typeof(SqlByte), val => WriteNullable((SqlByte) val, obj => WriteByte((byte) obj)) },
|
||||
{typeof(SqlBoolean), val => WriteNullable((SqlBoolean) val, obj => WriteBoolean((bool) obj)) },
|
||||
{typeof(SqlDouble), val => WriteNullable((SqlDouble) val, obj => WriteDouble((double) obj)) },
|
||||
{typeof(SqlSingle), val => WriteNullable((SqlSingle) val, obj => WriteSingle((float) obj)) },
|
||||
{typeof(SqlDecimal), val => WriteNullable((SqlDecimal) val, obj => WriteSqlDecimal((SqlDecimal) obj)) },
|
||||
{typeof(SqlDateTime), val => WriteNullable((SqlDateTime) val, obj => WriteDateTime((DateTime) obj)) },
|
||||
{typeof(SqlBytes), val => WriteNullable((SqlBytes) val, obj => WriteBytes((byte[]) obj)) },
|
||||
{typeof(SqlBinary), val => WriteNullable((SqlBinary) val, obj => WriteBytes((byte[]) obj)) },
|
||||
{typeof(SqlGuid), val => WriteNullable((SqlGuid) val, obj => WriteGuid((Guid) obj)) },
|
||||
{typeof(SqlMoney), val => WriteNullable((SqlMoney) val, obj => WriteMoney((SqlMoney) obj)) }
|
||||
};
|
||||
}
|
||||
|
||||
#region IFileStreamWriter Implementation
|
||||
@@ -76,22 +115,20 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>Number of bytes used to write the row</returns>
|
||||
public int WriteRow(StorageDataReader reader)
|
||||
{
|
||||
// Determine if we have any long fields
|
||||
bool hasLongFields = reader.Columns.Any(column => column.IsLong);
|
||||
|
||||
// Read the values in from the db
|
||||
object[] values = new object[reader.Columns.Length];
|
||||
int rowBytes = 0;
|
||||
if (!hasLongFields)
|
||||
if (!reader.HasLongColumns)
|
||||
{
|
||||
// get all record values in one shot if there are no extra long fields
|
||||
reader.GetValues(values);
|
||||
}
|
||||
|
||||
// Loop over all the columns and write the values to the temp file
|
||||
int rowBytes = 0;
|
||||
for (int i = 0; i < reader.Columns.Length; i++)
|
||||
{
|
||||
DbColumnWrapper ci = reader.Columns[i];
|
||||
if (hasLongFields)
|
||||
if (reader.HasLongColumns)
|
||||
{
|
||||
if (reader.IsDBNull(i))
|
||||
{
|
||||
@@ -111,18 +148,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
// this is a long field
|
||||
if (ci.IsBytes)
|
||||
{
|
||||
values[i] = reader.GetBytesWithMaxCapacity(i, MaxCharsToStore);
|
||||
values[i] = reader.GetBytesWithMaxCapacity(i, maxCharsToStore);
|
||||
}
|
||||
else if (ci.IsChars)
|
||||
{
|
||||
Debug.Assert(MaxCharsToStore > 0);
|
||||
Debug.Assert(maxCharsToStore > 0);
|
||||
values[i] = reader.GetCharsWithMaxCapacity(i,
|
||||
ci.IsXml ? MaxXmlCharsToStore : MaxCharsToStore);
|
||||
ci.IsXml ? maxXmlCharsToStore : maxCharsToStore);
|
||||
}
|
||||
else if (ci.IsXml)
|
||||
{
|
||||
Debug.Assert(MaxXmlCharsToStore > 0);
|
||||
values[i] = reader.GetXmlWithMaxCapacity(i, MaxXmlCharsToStore);
|
||||
Debug.Assert(maxXmlCharsToStore > 0);
|
||||
values[i] = reader.GetXmlWithMaxCapacity(i, maxXmlCharsToStore);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -133,8 +170,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
}
|
||||
}
|
||||
|
||||
Type tVal = values[i].GetType(); // get true type of the object
|
||||
// Get true type of the object
|
||||
Type tVal = values[i].GetType();
|
||||
|
||||
// Write the object to a file
|
||||
if (tVal == typeof(DBNull))
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
@@ -148,272 +187,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
rowBytes += WriteString(val);
|
||||
}
|
||||
|
||||
if (tVal == typeof(string))
|
||||
// Use the appropriate writing method for the type
|
||||
Func<object, int> writeMethod;
|
||||
if (writeMethods.TryGetValue(tVal, out writeMethod))
|
||||
{
|
||||
// String - most frequently used data type
|
||||
string val = (string)values[i];
|
||||
rowBytes += WriteString(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlString))
|
||||
{
|
||||
// SqlString
|
||||
SqlString val = (SqlString)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteString(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(short))
|
||||
{
|
||||
// Int16
|
||||
short val = (short)values[i];
|
||||
rowBytes += WriteInt16(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlInt16))
|
||||
{
|
||||
// SqlInt16
|
||||
SqlInt16 val = (SqlInt16)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteInt16(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(int))
|
||||
{
|
||||
// Int32
|
||||
int val = (int)values[i];
|
||||
rowBytes += WriteInt32(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlInt32))
|
||||
{
|
||||
// SqlInt32
|
||||
SqlInt32 val = (SqlInt32)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteInt32(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(long))
|
||||
{
|
||||
// Int64
|
||||
long val = (long)values[i];
|
||||
rowBytes += WriteInt64(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlInt64))
|
||||
{
|
||||
// SqlInt64
|
||||
SqlInt64 val = (SqlInt64)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteInt64(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(byte))
|
||||
{
|
||||
// Byte
|
||||
byte val = (byte)values[i];
|
||||
rowBytes += WriteByte(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlByte))
|
||||
{
|
||||
// SqlByte
|
||||
SqlByte val = (SqlByte)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteByte(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(char))
|
||||
{
|
||||
// Char
|
||||
char val = (char)values[i];
|
||||
rowBytes += WriteChar(val);
|
||||
}
|
||||
else if (tVal == typeof(bool))
|
||||
{
|
||||
// Boolean
|
||||
bool val = (bool)values[i];
|
||||
rowBytes += WriteBoolean(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlBoolean))
|
||||
{
|
||||
// SqlBoolean
|
||||
SqlBoolean val = (SqlBoolean)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteBoolean(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(double))
|
||||
{
|
||||
// Double
|
||||
double val = (double)values[i];
|
||||
rowBytes += WriteDouble(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlDouble))
|
||||
{
|
||||
// SqlDouble
|
||||
SqlDouble val = (SqlDouble)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteDouble(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(SqlSingle))
|
||||
{
|
||||
// SqlSingle
|
||||
SqlSingle val = (SqlSingle)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteSingle(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(decimal))
|
||||
{
|
||||
// Decimal
|
||||
decimal val = (decimal)values[i];
|
||||
rowBytes += WriteDecimal(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlDecimal))
|
||||
{
|
||||
// SqlDecimal
|
||||
SqlDecimal val = (SqlDecimal)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteSqlDecimal(val);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(DateTime))
|
||||
{
|
||||
// DateTime
|
||||
DateTime val = (DateTime)values[i];
|
||||
rowBytes += WriteDateTime(val);
|
||||
}
|
||||
else if (tVal == typeof(DateTimeOffset))
|
||||
{
|
||||
// DateTimeOffset
|
||||
DateTimeOffset val = (DateTimeOffset)values[i];
|
||||
rowBytes += WriteDateTimeOffset(val);
|
||||
}
|
||||
else if (tVal == typeof(SqlDateTime))
|
||||
{
|
||||
// SqlDateTime
|
||||
SqlDateTime val = (SqlDateTime)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteDateTime(val.Value);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(TimeSpan))
|
||||
{
|
||||
// TimeSpan
|
||||
TimeSpan val = (TimeSpan)values[i];
|
||||
rowBytes += WriteTimeSpan(val);
|
||||
}
|
||||
else if (tVal == typeof(byte[]))
|
||||
{
|
||||
// Bytes
|
||||
byte[] val = (byte[])values[i];
|
||||
rowBytes += WriteBytes(val, val.Length);
|
||||
}
|
||||
else if (tVal == typeof(SqlBytes))
|
||||
{
|
||||
// SqlBytes
|
||||
SqlBytes val = (SqlBytes)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteBytes(val.Value, val.Value.Length);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(SqlBinary))
|
||||
{
|
||||
// SqlBinary
|
||||
SqlBinary val = (SqlBinary)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteBytes(val.Value, val.Value.Length);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(SqlGuid))
|
||||
{
|
||||
// SqlGuid
|
||||
SqlGuid val = (SqlGuid)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] bytesVal = val.ToByteArray();
|
||||
rowBytes += WriteBytes(bytesVal, bytesVal.Length);
|
||||
}
|
||||
}
|
||||
else if (tVal == typeof(SqlMoney))
|
||||
{
|
||||
// SqlMoney
|
||||
SqlMoney val = (SqlMoney)values[i];
|
||||
if (val.IsNull)
|
||||
{
|
||||
rowBytes += WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
rowBytes += WriteDecimal(val.Value);
|
||||
}
|
||||
rowBytes += writeMethod(values[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// treat everything else as string
|
||||
string val = values[i].ToString();
|
||||
rowBytes += WriteString(val);
|
||||
rowBytes += WriteString(values[i].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,7 +212,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
public int WriteNull()
|
||||
{
|
||||
byteBuffer[0] = 0x00;
|
||||
return FileStream.WriteData(byteBuffer, 1);
|
||||
return fileStream.WriteData(byteBuffer, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -442,7 +224,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x02; // length
|
||||
shortBuffer[0] = val;
|
||||
Buffer.BlockCopy(shortBuffer, 0, byteBuffer, 1, 2);
|
||||
return FileStream.WriteData(byteBuffer, 3);
|
||||
return fileStream.WriteData(byteBuffer, 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -454,7 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x04; // length
|
||||
intBuffer[0] = val;
|
||||
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 1, 4);
|
||||
return FileStream.WriteData(byteBuffer, 5);
|
||||
return fileStream.WriteData(byteBuffer, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -466,7 +248,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x08; // length
|
||||
longBuffer[0] = val;
|
||||
Buffer.BlockCopy(longBuffer, 0, byteBuffer, 1, 8);
|
||||
return FileStream.WriteData(byteBuffer, 9);
|
||||
return fileStream.WriteData(byteBuffer, 9);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -478,7 +260,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x02; // length
|
||||
charBuffer[0] = val;
|
||||
Buffer.BlockCopy(charBuffer, 0, byteBuffer, 1, 2);
|
||||
return FileStream.WriteData(byteBuffer, 3);
|
||||
return fileStream.WriteData(byteBuffer, 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -489,7 +271,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
byteBuffer[0] = 0x01; // length
|
||||
byteBuffer[1] = (byte) (val ? 0x01 : 0x00);
|
||||
return FileStream.WriteData(byteBuffer, 2);
|
||||
return fileStream.WriteData(byteBuffer, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -500,7 +282,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
byteBuffer[0] = 0x01; // length
|
||||
byteBuffer[1] = val;
|
||||
return FileStream.WriteData(byteBuffer, 2);
|
||||
return fileStream.WriteData(byteBuffer, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -512,7 +294,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x04; // length
|
||||
floatBuffer[0] = val;
|
||||
Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 1, 4);
|
||||
return FileStream.WriteData(byteBuffer, 5);
|
||||
return fileStream.WriteData(byteBuffer, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -524,7 +306,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x08; // length
|
||||
doubleBuffer[0] = val;
|
||||
Buffer.BlockCopy(doubleBuffer, 0, byteBuffer, 1, 8);
|
||||
return FileStream.WriteData(byteBuffer, 9);
|
||||
return fileStream.WriteData(byteBuffer, 9);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -548,7 +330,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
// data value
|
||||
Buffer.BlockCopy(arrInt32, 0, byteBuffer, 3, iLen - 3);
|
||||
iTotalLen += FileStream.WriteData(byteBuffer, iLen);
|
||||
iTotalLen += fileStream.WriteData(byteBuffer, iLen);
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
|
||||
@@ -564,7 +346,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
int iTotalLen = WriteLength(iLen); // length
|
||||
|
||||
Buffer.BlockCopy(arrInt32, 0, byteBuffer, 0, iLen);
|
||||
iTotalLen += FileStream.WriteData(byteBuffer, iLen);
|
||||
iTotalLen += fileStream.WriteData(byteBuffer, iLen);
|
||||
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
@@ -584,9 +366,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>Number of bytes used to store the DateTimeOffset</returns>
|
||||
public int WriteDateTimeOffset(DateTimeOffset dtoVal)
|
||||
{
|
||||
// DateTimeOffset gets written as a DateTime + TimeOffset
|
||||
// both represented as 'Ticks' written as Int64's
|
||||
return WriteInt64(dtoVal.Ticks) + WriteInt64(dtoVal.Offset.Ticks);
|
||||
// Write the length, which is the 2*sizeof(long)
|
||||
byteBuffer[0] = 0x10; // length (16)
|
||||
|
||||
// Write the two longs, the datetime and the offset
|
||||
long[] longBufferOffset = new long[2];
|
||||
longBufferOffset[0] = dtoVal.Ticks;
|
||||
longBufferOffset[1] = dtoVal.Offset.Ticks;
|
||||
Buffer.BlockCopy(longBufferOffset, 0, byteBuffer, 1, 16);
|
||||
return fileStream.WriteData(byteBuffer, 17);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -618,7 +406,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[3] = 0x00;
|
||||
byteBuffer[4] = 0x00;
|
||||
|
||||
iTotalLen = FileStream.WriteData(byteBuffer, 5);
|
||||
iTotalLen = fileStream.WriteData(byteBuffer, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -627,7 +415,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
// convert char array into byte array and write it out
|
||||
iTotalLen = WriteLength(bytes.Length);
|
||||
iTotalLen += FileStream.WriteData(bytes, bytes.Length);
|
||||
iTotalLen += fileStream.WriteData(bytes, bytes.Length);
|
||||
}
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
@@ -636,32 +424,76 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// Writes a byte[] to the file
|
||||
/// </summary>
|
||||
/// <returns>Number of bytes used to store the byte[]</returns>
|
||||
public int WriteBytes(byte[] bytesVal, int iLen)
|
||||
public int WriteBytes(byte[] bytesVal)
|
||||
{
|
||||
Validate.IsNotNull(nameof(bytesVal), bytesVal);
|
||||
|
||||
int iTotalLen;
|
||||
if (0 == iLen) // special case of 0 length byte array "0x"
|
||||
if (bytesVal.Length == 0) // special case of 0 length byte array "0x"
|
||||
{
|
||||
iLen = 5;
|
||||
|
||||
AssureBufferLength(iLen);
|
||||
AssureBufferLength(5);
|
||||
byteBuffer[0] = 0xFF;
|
||||
byteBuffer[1] = 0x00;
|
||||
byteBuffer[2] = 0x00;
|
||||
byteBuffer[3] = 0x00;
|
||||
byteBuffer[4] = 0x00;
|
||||
|
||||
iTotalLen = FileStream.WriteData(byteBuffer, iLen);
|
||||
iTotalLen = fileStream.WriteData(byteBuffer, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
iTotalLen = WriteLength(iLen);
|
||||
iTotalLen += FileStream.WriteData(bytesVal, iLen);
|
||||
iTotalLen = WriteLength(bytesVal.Length);
|
||||
iTotalLen += fileStream.WriteData(bytesVal, bytesVal.Length);
|
||||
}
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores a GUID value to the file by treating it as a byte array
|
||||
/// </summary>
|
||||
/// <param name="val">The GUID to write to the file</param>
|
||||
/// <returns>Number of bytes written to the file</returns>
|
||||
public int WriteGuid(Guid val)
|
||||
{
|
||||
byte[] guidBytes = val.ToByteArray();
|
||||
return WriteBytes(guidBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores a SqlMoney value to the file by treating it as a decimal
|
||||
/// </summary>
|
||||
/// <param name="val">The SqlMoney value to write to the file</param>
|
||||
/// <returns>Number of bytes written to the file</returns>
|
||||
public int WriteMoney(SqlMoney val)
|
||||
{
|
||||
return WriteDecimal(val.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the internal buffer to the file stream
|
||||
/// </summary>
|
||||
public void FlushBuffer()
|
||||
{
|
||||
fileStream.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer that is of the specified length if the buffer is not already
|
||||
/// at least as long as specified.
|
||||
/// </summary>
|
||||
/// <param name="newBufferLength">The minimum buffer size</param>
|
||||
private void AssureBufferLength(int newBufferLength)
|
||||
{
|
||||
if (newBufferLength > byteBuffer.Length)
|
||||
{
|
||||
byteBuffer = new byte[byteBuffer.Length];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the length of the field using the appropriate number of bytes (ie, 1 if the
|
||||
/// length is <255, 5 if the length is >=255)
|
||||
@@ -675,7 +507,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
int iTmp = iLen & 0x000000FF;
|
||||
|
||||
byteBuffer[0] = Convert.ToByte(iTmp);
|
||||
return FileStream.WriteData(byteBuffer, 1);
|
||||
return fileStream.WriteData(byteBuffer, 1);
|
||||
}
|
||||
// The length won't fit in 1 byte, so we need to use 1 byte to signify that the length
|
||||
// is a full 4 bytes.
|
||||
@@ -684,27 +516,24 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
// convert int32 into array of bytes
|
||||
intBuffer[0] = iLen;
|
||||
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 1, 4);
|
||||
return FileStream.WriteData(byteBuffer, 5);
|
||||
return fileStream.WriteData(byteBuffer, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the internal buffer to the file stream
|
||||
/// Writes a Nullable type (generally a Sql* type) to the file. The function provided by
|
||||
/// <paramref name="valueWriteFunc"/> is used to write to the file if <paramref name="val"/>
|
||||
/// is not null. <see cref="WriteNull"/> is used if <paramref name="val"/> is null.
|
||||
/// </summary>
|
||||
public void FlushBuffer()
|
||||
/// <param name="val">The value to write to the file</param>
|
||||
/// <param name="valueWriteFunc">The function to use if val is not null</param>
|
||||
/// <returns>Number of bytes used to write value to the file</returns>
|
||||
private int WriteNullable(INullable val, Func<object, int> valueWriteFunc)
|
||||
{
|
||||
FileStream.Flush();
|
||||
return val.IsNull ? WriteNull() : valueWriteFunc(val);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void AssureBufferLength(int newBufferLength)
|
||||
{
|
||||
if (newBufferLength > byteBuffer.Length)
|
||||
{
|
||||
byteBuffer = new byte[byteBuffer.Length];
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
private bool disposed;
|
||||
@@ -724,8 +553,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
FileStream.Flush();
|
||||
FileStream.Dispose();
|
||||
fileStream.Flush();
|
||||
fileStream.Dispose();
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
// Read the columns into a set of wrappers
|
||||
Columns = DbDataReader.GetColumnSchema().Select(column => new DbColumnWrapper(column)).ToArray();
|
||||
HasLongColumns = Columns.Any(column => column.IsLong);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@@ -71,6 +72,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
public DbDataReader DbDataReader { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not any of the columns of this reader are 'long', such as nvarchar(max)
|
||||
/// </summary>
|
||||
public bool HasLongColumns { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region DbDataReader Methods
|
||||
|
||||
@@ -86,7 +86,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
});
|
||||
// NOTE: We only want to process batches that have statements (ie, ignore comments and empty lines)
|
||||
Batches = parseResult.Script.Batches.Where(b => b.Statements.Count > 0)
|
||||
.Select(b => new Batch(b.Sql, b.StartLocation.LineNumber, outputFileFactory)).ToArray();
|
||||
.Select(b => new Batch(b.Sql,
|
||||
b.StartLocation.LineNumber - 1,
|
||||
b.StartLocation.ColumnNumber - 1,
|
||||
b.EndLocation.LineNumber - 1,
|
||||
b.EndLocation.ColumnNumber - 1,
|
||||
outputFileFactory)).ToArray();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@@ -113,7 +118,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
Id = index,
|
||||
HasError = batch.HasError,
|
||||
Messages = batch.ResultMessages.ToArray(),
|
||||
ResultSetSummaries = batch.ResultSummaries
|
||||
ResultSetSummaries = batch.ResultSummaries,
|
||||
Selection = batch.Selection
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -189,10 +195,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage += OnInfoMessage;
|
||||
}
|
||||
|
||||
// We need these to execute synchronously, otherwise the user will be very unhappy
|
||||
foreach (Batch b in Batches)
|
||||
try
|
||||
{
|
||||
await b.Execute(conn, cancellationSource.Token);
|
||||
// We need these to execute synchronously, otherwise the user will be very unhappy
|
||||
foreach (Batch b in Batches)
|
||||
{
|
||||
await b.Execute(conn, cancellationSource.Token);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (sqlConn != null)
|
||||
{
|
||||
// Subscribe to database informational messages
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage -= OnInfoMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Close connection after eliminating using statement for above TODO
|
||||
|
||||
@@ -13,7 +13,9 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
@@ -38,11 +40,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
private QueryExecutionService()
|
||||
{
|
||||
ConnectionService = ConnectionService.Instance;
|
||||
WorkspaceService = WorkspaceService<SqlToolsSettings>.Instance;
|
||||
}
|
||||
|
||||
internal QueryExecutionService(ConnectionService connService)
|
||||
internal QueryExecutionService(ConnectionService connService, WorkspaceService<SqlToolsSettings> workspaceService)
|
||||
{
|
||||
ConnectionService = connService;
|
||||
WorkspaceService = workspaceService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -78,6 +82,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
private ConnectionService ConnectionService { get; set; }
|
||||
|
||||
private WorkspaceService<SqlToolsSettings> WorkspaceService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal storage of active queries, lazily constructed as a threadsafe dictionary
|
||||
/// </summary>
|
||||
@@ -111,7 +117,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
});
|
||||
|
||||
// Register a handler for when the configuration changes
|
||||
WorkspaceService<SqlToolsSettings>.Instance.RegisterConfigChangeCallback((oldSettings, newSettings, eventContext) =>
|
||||
WorkspaceService.RegisterConfigChangeCallback((oldSettings, newSettings, eventContext) =>
|
||||
{
|
||||
Settings.QueryExecutionSettings.Update(newSettings.QueryExecutionSettings);
|
||||
return Task.FromResult(0);
|
||||
@@ -202,6 +208,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return;
|
||||
}
|
||||
|
||||
// Cleanup the query
|
||||
result.Dispose();
|
||||
|
||||
// Success
|
||||
await requestContext.SendResult(new QueryDisposeResult
|
||||
{
|
||||
@@ -232,6 +241,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
// Cancel the query
|
||||
result.Cancel();
|
||||
result.Dispose();
|
||||
|
||||
// Attempt to dispose the query
|
||||
if (!ActiveQueries.TryRemove(cancelParams.OwnerUri, out result))
|
||||
@@ -263,7 +273,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in CSV format
|
||||
/// </summary>
|
||||
public async Task HandleSaveResultsAsCsvRequest( SaveResultsAsCsvRequestParams saveParams,
|
||||
public async Task HandleSaveResultsAsCsvRequest(SaveResultsAsCsvRequestParams saveParams,
|
||||
RequestContext<SaveResultRequestResult> requestContext)
|
||||
{
|
||||
// retrieve query for OwnerUri
|
||||
@@ -283,17 +293,40 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex];
|
||||
if (saveParams.IncludeHeaders)
|
||||
{
|
||||
// write column names to csv
|
||||
await csvFile.WriteLineAsync( string.Join( ",", selectedResultSet.Columns.Select( column => SaveResults.EncodeCsvField(column.ColumnName) ?? string.Empty)));
|
||||
int columnCount = 0;
|
||||
int rowCount = 0;
|
||||
int columnStartIndex = 0;
|
||||
int rowStartIndex = 0;
|
||||
|
||||
// set column, row counts depending on whether save request is for entire result set or a subset
|
||||
if (SaveResults.isSaveSelection(saveParams))
|
||||
{
|
||||
columnCount = saveParams.ColumnEndIndex.Value - saveParams.ColumnStartIndex.Value + 1;
|
||||
rowCount = saveParams.RowEndIndex.Value - saveParams.RowStartIndex.Value + 1;
|
||||
columnStartIndex = saveParams.ColumnStartIndex.Value;
|
||||
rowStartIndex =saveParams.RowStartIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnCount = selectedResultSet.Columns.Length;
|
||||
rowCount = (int)selectedResultSet.RowCount;
|
||||
}
|
||||
|
||||
// write rows to csv
|
||||
foreach (var row in selectedResultSet.Rows)
|
||||
// write column names if include headers option is chosen
|
||||
if (saveParams.IncludeHeaders)
|
||||
{
|
||||
await csvFile.WriteLineAsync( string.Join( ",", row.Select( field => SaveResults.EncodeCsvField((field != null) ? field.ToString(): string.Empty))));
|
||||
await csvFile.WriteLineAsync( string.Join( ",", selectedResultSet.Columns.Skip(columnStartIndex).Take(columnCount).Select( column =>
|
||||
SaveResults.EncodeCsvField(column.ColumnName) ?? string.Empty)));
|
||||
}
|
||||
|
||||
// retrieve rows and write as csv
|
||||
ResultSetSubset resultSubset = await result.GetSubset(saveParams.BatchIndex, saveParams.ResultSetIndex, rowStartIndex, rowCount);
|
||||
foreach (var row in resultSubset.Rows)
|
||||
{
|
||||
await csvFile.WriteLineAsync( string.Join( ",", row.Skip(columnStartIndex).Take(columnCount).Select( field =>
|
||||
SaveResults.EncodeCsvField((field != null) ? field.ToString(): "NULL"))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Successfully wrote file, send success result
|
||||
@@ -313,7 +346,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in JSON format
|
||||
/// </summary>
|
||||
public async Task HandleSaveResultsAsJsonRequest( SaveResultsAsJsonRequestParams saveParams,
|
||||
public async Task HandleSaveResultsAsJsonRequest(SaveResultsAsJsonRequestParams saveParams,
|
||||
RequestContext<SaveResultRequestResult> requestContext)
|
||||
{
|
||||
// retrieve query for OwnerUri
|
||||
@@ -333,26 +366,49 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
jsonWriter.Formatting = Formatting.Indented;
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
|
||||
// get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex];
|
||||
ResultSet selectedResultSet = selectedBatch.ResultSets.ToList()[saveParams.ResultSetIndex];
|
||||
int rowCount = 0;
|
||||
int rowStartIndex = 0;
|
||||
int columnStartIndex = 0;
|
||||
int columnEndIndex = 0;
|
||||
|
||||
// write each row to JSON
|
||||
foreach (var row in selectedResultSet.Rows)
|
||||
// set column, row counts depending on whether save request is for entire result set or a subset
|
||||
if (SaveResults.isSaveSelection(saveParams))
|
||||
{
|
||||
|
||||
rowCount = saveParams.RowEndIndex.Value - saveParams.RowStartIndex.Value + 1;
|
||||
rowStartIndex = saveParams.RowStartIndex.Value;
|
||||
columnStartIndex = saveParams.ColumnStartIndex.Value;
|
||||
columnEndIndex = saveParams.ColumnEndIndex.Value + 1 ; // include the last column
|
||||
}
|
||||
else
|
||||
{
|
||||
rowCount = (int)selectedResultSet.RowCount;
|
||||
columnEndIndex = selectedResultSet.Columns.Length;
|
||||
}
|
||||
|
||||
// retrieve rows and write as json
|
||||
ResultSetSubset resultSubset = await result.GetSubset(saveParams.BatchIndex, saveParams.ResultSetIndex, rowStartIndex, rowCount);
|
||||
foreach (var row in resultSubset.Rows)
|
||||
{
|
||||
jsonWriter.WriteStartObject();
|
||||
foreach (var field in row.Select((value,i) => new {value, i}))
|
||||
for (int i = columnStartIndex ; i < columnEndIndex; i++)
|
||||
{
|
||||
jsonWriter.WritePropertyName(selectedResultSet.Columns[field.i].ColumnName);
|
||||
if (field.value != null)
|
||||
{
|
||||
jsonWriter.WriteValue(field.value);
|
||||
}
|
||||
else
|
||||
//get column name
|
||||
DbColumnWrapper col = selectedResultSet.Columns[i];
|
||||
string val = row[i]?.ToString();
|
||||
jsonWriter.WritePropertyName(col.ColumnName);
|
||||
if (val == null)
|
||||
{
|
||||
jsonWriter.WriteNull();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonWriter.WriteValue(val);
|
||||
}
|
||||
}
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
@@ -371,6 +427,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
await requestContext.SendError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
@@ -394,14 +451,41 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
Query oldQuery;
|
||||
if (ActiveQueries.TryGetValue(executeParams.OwnerUri, out oldQuery) && oldQuery.HasExecuted)
|
||||
{
|
||||
oldQuery.Dispose();
|
||||
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
|
||||
}
|
||||
|
||||
// Retrieve the current settings for executing the query with
|
||||
QueryExecutionSettings settings = WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.QueryExecutionSettings;
|
||||
QueryExecutionSettings settings = WorkspaceService.CurrentSettings.QueryExecutionSettings;
|
||||
|
||||
// Get query text from the workspace.
|
||||
ScriptFile queryFile = WorkspaceService.Workspace.GetFile(executeParams.OwnerUri);
|
||||
|
||||
string queryText;
|
||||
|
||||
if (executeParams.QuerySelection != null)
|
||||
{
|
||||
string[] queryTextArray = queryFile.GetLinesInRange(
|
||||
new BufferRange(
|
||||
new BufferPosition(
|
||||
executeParams.QuerySelection.StartLine + 1,
|
||||
executeParams.QuerySelection.StartColumn + 1
|
||||
),
|
||||
new BufferPosition(
|
||||
executeParams.QuerySelection.EndLine + 1,
|
||||
executeParams.QuerySelection.EndColumn + 1
|
||||
)
|
||||
)
|
||||
);
|
||||
queryText = queryTextArray.Aggregate((a, b) => a + '\r' + '\n' + b);
|
||||
}
|
||||
else
|
||||
{
|
||||
queryText = queryFile.Contents;
|
||||
}
|
||||
|
||||
// If we can't add the query now, it's assumed the query is in progress
|
||||
Query newQuery = new Query(executeParams.QueryText, connectionInfo, settings, BufferFileFactory);
|
||||
Query newQuery = new Query(queryText, connectionInfo, settings, BufferFileFactory);
|
||||
if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery))
|
||||
{
|
||||
await requestContext.SendResult(new QueryExecuteResult
|
||||
@@ -469,8 +553,22 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
foreach (var query in ActiveQueries)
|
||||
{
|
||||
if (!query.Value.HasExecuted)
|
||||
{
|
||||
try
|
||||
{
|
||||
query.Value.Cancel();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// We don't particularly care if we fail to cancel during shutdown
|
||||
string message = string.Format("Failed to cancel query {0} during query service disposal: {1}", query.Key, e);
|
||||
Logger.Write(LogLevel.Warning, message);
|
||||
}
|
||||
}
|
||||
query.Value.Dispose();
|
||||
}
|
||||
ActiveQueries.Clear();
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// xml is a special case so number of chars to store is usually greater than for other long types
|
||||
private const int DefaultMaxXmlCharsToStore = 2097152; // 2 MB - QE default
|
||||
|
||||
// Column names of 'for xml' and 'for json' queries
|
||||
private const string NameOfForXMLColumn = "XML_F52E2B61-18A1-11d1-B105-00805F49916B";
|
||||
private const string NameOfForJSONColumn = "JSON_F52E2B61-18A1-11d1-B105-00805F49916B";
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Member Variables
|
||||
@@ -112,9 +117,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// The rows of this result set
|
||||
/// </summary>
|
||||
public IEnumerable<object[]> Rows
|
||||
public IEnumerable<string[]> Rows
|
||||
{
|
||||
get { return FileOffsets.Select(offset => fileStreamReader.ReadRow(offset, Columns)); }
|
||||
get
|
||||
{
|
||||
return FileOffsets.Select(
|
||||
offset => fileStreamReader.ReadRow(offset, Columns).Select(cell => cell.DisplayValue).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -151,7 +160,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
IEnumerable<long> rowOffsets = FileOffsets.Skip(startRow).Take(rowCount);
|
||||
|
||||
// Iterate over the rows we need and process them into output
|
||||
object[][] rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)).ToArray();
|
||||
string[][] rows = rowOffsets.Select(rowOffset =>
|
||||
fileStreamReader.ReadRow(rowOffset, Columns).Select(cell => cell.DisplayValue).ToArray())
|
||||
.ToArray();
|
||||
|
||||
// Retrieve the subset of the results as per the request
|
||||
return new ResultSetSubset
|
||||
@@ -186,6 +197,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
currentFileOffset += fileWriter.WriteRow(DataReader);
|
||||
}
|
||||
}
|
||||
// Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata
|
||||
SingleColumnXmlJsonResultSet();
|
||||
|
||||
// Mark that result has been read
|
||||
hasBeenRead = true;
|
||||
@@ -219,5 +232,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// If the result set represented by this class corresponds to a single XML
|
||||
/// column that contains results of "for xml" query, set isXml = true
|
||||
/// If the result set represented by this class corresponds to a single JSON
|
||||
/// column that contains results of "for json" query, set isJson = true
|
||||
/// </summary>
|
||||
private void SingleColumnXmlJsonResultSet() {
|
||||
|
||||
if (Columns?.Length == 1)
|
||||
{
|
||||
if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal))
|
||||
{
|
||||
Columns[0].IsXml = true;
|
||||
}
|
||||
else if (Columns[0].ColumnName.Equals(NameOfForJSONColumn, StringComparison.Ordinal))
|
||||
{
|
||||
Columns[0].IsJson = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
@@ -79,6 +80,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal static bool isSaveSelection(SaveResultsRequestParams saveParams)
|
||||
{
|
||||
return (saveParams.ColumnStartIndex != null && saveParams.ColumnEndIndex != null
|
||||
&& saveParams.RowEndIndex != null && saveParams.RowEndIndex != null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,13 +19,13 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// The default host name for SqlTools Editor Services. Used
|
||||
/// if no host name is specified by the host application.
|
||||
/// </summary>
|
||||
public const string DefaultHostName = "SqlTools Editor Services Host";
|
||||
public const string DefaultHostName = "SqlTools Service Host";
|
||||
|
||||
/// <summary>
|
||||
/// The default host ID for SqlTools Editor Services. Used
|
||||
/// for the host-specific profile path if no host ID is specified.
|
||||
/// </summary>
|
||||
public const string DefaultHostProfileId = "Microsoft.SqlToolsEditorServices";
|
||||
public const string DefaultHostProfileId = "Microsoft.SqlToolsServiceHost";
|
||||
|
||||
/// <summary>
|
||||
/// The default host version for SqlTools Editor Services. If
|
||||
@@ -78,9 +78,9 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// </param>
|
||||
/// <param name="version">The host application's version.</param>
|
||||
public HostDetails(
|
||||
string name,
|
||||
string profileId,
|
||||
Version version)
|
||||
string name = null,
|
||||
string profileId = null,
|
||||
Version version = null)
|
||||
{
|
||||
this.Name = name ?? DefaultHostName;
|
||||
this.ProfileId = profileId ?? DefaultHostProfileId;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// 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.SqlContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for serialization and deserialization of IntelliSense settings
|
||||
/// </summary>
|
||||
public class IntelliSenseSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize the IntelliSense settings defaults
|
||||
/// </summary>
|
||||
public IntelliSenseSettings()
|
||||
{
|
||||
this.EnableSuggestions = true;
|
||||
this.LowerCaseSuggestions = false;
|
||||
this.EnableDiagnostics = true;
|
||||
this.EnableQuickInfo = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if suggestions are enabled
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool? EnableSuggestions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if built-in suggestions should be lowercase
|
||||
/// </summary>
|
||||
public bool? LowerCaseSuggestions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if diagnostics are enabled
|
||||
/// </summary>
|
||||
public bool? EnableDiagnostics { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if quick info is enabled
|
||||
/// </summary>
|
||||
public bool? EnableQuickInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update the Intellisense settings
|
||||
/// </summary>
|
||||
/// <param name="settings"></param>
|
||||
public void Update(IntelliSenseSettings settings)
|
||||
{
|
||||
if (settings != null)
|
||||
{
|
||||
this.EnableSuggestions = settings.EnableSuggestions;
|
||||
this.LowerCaseSuggestions = settings.LowerCaseSuggestions;
|
||||
this.EnableDiagnostics = settings.EnableDiagnostics;
|
||||
this.EnableQuickInfo = settings.EnableQuickInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides profile path resolution behavior relative to the name
|
||||
/// of a particular SqlTools host.
|
||||
/// </summary>
|
||||
public class ProfilePaths
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// The file name for the "all hosts" profile. Also used as the
|
||||
/// suffix for the host-specific profile filenames.
|
||||
/// </summary>
|
||||
public const string AllHostsProfileName = "profile.ps1";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile path for all users, all hosts.
|
||||
/// </summary>
|
||||
public string AllUsersAllHosts { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile path for all users, current host.
|
||||
/// </summary>
|
||||
public string AllUsersCurrentHost { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile path for the current user, all hosts.
|
||||
/// </summary>
|
||||
public string CurrentUserAllHosts { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile path for the current user and host.
|
||||
/// </summary>
|
||||
public string CurrentUserCurrentHost { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the ProfilePaths class.
|
||||
/// </summary>
|
||||
/// <param name="hostProfileId">
|
||||
/// The identifier of the host used in the host-specific X_profile.ps1 filename.
|
||||
/// </param>
|
||||
/// <param name="baseAllUsersPath">The base path to use for constructing AllUsers profile paths.</param>
|
||||
/// <param name="baseCurrentUserPath">The base path to use for constructing CurrentUser profile paths.</param>
|
||||
public ProfilePaths(
|
||||
string hostProfileId,
|
||||
string baseAllUsersPath,
|
||||
string baseCurrentUserPath)
|
||||
{
|
||||
this.Initialize(hostProfileId, baseAllUsersPath, baseCurrentUserPath);
|
||||
}
|
||||
|
||||
private void Initialize(
|
||||
string hostProfileId,
|
||||
string baseAllUsersPath,
|
||||
string baseCurrentUserPath)
|
||||
{
|
||||
string currentHostProfileName =
|
||||
string.Format(
|
||||
"{0}_{1}",
|
||||
hostProfileId,
|
||||
AllHostsProfileName);
|
||||
|
||||
this.AllUsersCurrentHost = Path.Combine(baseAllUsersPath, currentHostProfileName);
|
||||
this.CurrentUserCurrentHost = Path.Combine(baseCurrentUserPath, currentHostProfileName);
|
||||
this.AllUsersAllHosts = Path.Combine(baseAllUsersPath, AllHostsProfileName);
|
||||
this.CurrentUserAllHosts = Path.Combine(baseCurrentUserPath, AllHostsProfileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of profile paths that exist on the filesystem.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerable of profile path strings to be loaded.</returns>
|
||||
public IEnumerable<string> GetLoadableProfilePaths()
|
||||
{
|
||||
var profilePaths =
|
||||
new string[]
|
||||
{
|
||||
this.AllUsersAllHosts,
|
||||
this.AllUsersCurrentHost,
|
||||
this.CurrentUserAllHosts,
|
||||
this.CurrentUserCurrentHost
|
||||
};
|
||||
|
||||
return profilePaths.Where(p => File.Exists(p));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,14 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public SqlToolsContext(HostDetails hostDetails, ProfilePaths profilePaths)
|
||||
/// <summary>
|
||||
/// Initalizes the SQL Tools context instance
|
||||
/// </summary>
|
||||
/// <param name="hostDetails"></param>
|
||||
public SqlToolsContext(HostDetails hostDetails)
|
||||
{
|
||||
|
||||
this.SqlToolsVersion = hostDetails.Version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
//
|
||||
// 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.SqlContext
|
||||
{
|
||||
@@ -8,76 +10,114 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// </summary>
|
||||
public class SqlToolsSettings
|
||||
{
|
||||
public SqlToolsSettings()
|
||||
{
|
||||
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
||||
this.QueryExecutionSettings = new QueryExecutionSettings();
|
||||
}
|
||||
private SqlToolsSettingsValues sqlTools = null;
|
||||
|
||||
public bool EnableProfileLoading { get; set; }
|
||||
|
||||
public ScriptAnalysisSettings ScriptAnalysis { get; set; }
|
||||
|
||||
public void Update(SqlToolsSettings settings, string workspaceRootPath)
|
||||
{
|
||||
if (settings != null)
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying settings value object
|
||||
/// </summary>
|
||||
public SqlToolsSettingsValues SqlTools
|
||||
{
|
||||
get
|
||||
{
|
||||
this.EnableProfileLoading = settings.EnableProfileLoading;
|
||||
this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath);
|
||||
if (this.sqlTools == null)
|
||||
{
|
||||
this.sqlTools = new SqlToolsSettingsValues();
|
||||
}
|
||||
return this.sqlTools;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.sqlTools = value;
|
||||
}
|
||||
}
|
||||
|
||||
public QueryExecutionSettings QueryExecutionSettings { get; set; }
|
||||
/// <summary>
|
||||
/// Query excution settings forwarding property
|
||||
/// </summary>
|
||||
public QueryExecutionSettings QueryExecutionSettings
|
||||
{
|
||||
get { return this.SqlTools.QueryExecutionSettings; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the extension settings
|
||||
/// </summary>
|
||||
/// <param name="settings"></param>
|
||||
public void Update(SqlToolsSettings settings)
|
||||
{
|
||||
if (settings != null)
|
||||
{
|
||||
this.SqlTools.EnableIntellisense = settings.SqlTools.EnableIntellisense;
|
||||
this.SqlTools.IntelliSense.Update(settings.SqlTools.IntelliSense);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag determining if diagnostics are enabled
|
||||
/// </summary>
|
||||
public bool IsDiagnositicsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableDiagnostics.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag determining if suggestons are enabled
|
||||
/// </summary>
|
||||
public bool IsSuggestionsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableSuggestions.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag determining if quick info is enabled
|
||||
/// </summary>
|
||||
public bool IsQuickInfoEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableQuickInfo.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sub class for serialization and deserialization of script analysis settings
|
||||
/// Class that is used to serialize and deserialize SQL Tools settings
|
||||
/// </summary>
|
||||
public class ScriptAnalysisSettings
|
||||
public class SqlToolsSettingsValues
|
||||
{
|
||||
public bool? Enable { get; set; }
|
||||
|
||||
public string SettingsPath { get; set; }
|
||||
|
||||
public ScriptAnalysisSettings()
|
||||
/// <summary>
|
||||
/// Initializes the Sql Tools settings values
|
||||
/// </summary>
|
||||
public SqlToolsSettingsValues()
|
||||
{
|
||||
this.Enable = true;
|
||||
this.EnableIntellisense = true;
|
||||
this.IntelliSense = new IntelliSenseSettings();
|
||||
this.QueryExecutionSettings = new QueryExecutionSettings();
|
||||
}
|
||||
|
||||
public void Update(ScriptAnalysisSettings settings, string workspaceRootPath)
|
||||
{
|
||||
if (settings != null)
|
||||
{
|
||||
this.Enable = settings.Enable;
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if IntelliSense is enabled
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool EnableIntellisense { get; set; }
|
||||
|
||||
string settingsPath = settings.SettingsPath;
|
||||
/// <summary>
|
||||
/// Gets or sets the detailed IntelliSense settings
|
||||
/// </summary>
|
||||
public IntelliSenseSettings IntelliSense { get; set; }
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settingsPath))
|
||||
{
|
||||
settingsPath = null;
|
||||
}
|
||||
else if (!Path.IsPathRooted(settingsPath))
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspaceRootPath))
|
||||
{
|
||||
// The workspace root path could be an empty string
|
||||
// when the user has opened a SqlTools script file
|
||||
// without opening an entire folder (workspace) first.
|
||||
// In this case we should just log an error and let
|
||||
// the specified settings path go through even though
|
||||
// it will fail to load.
|
||||
Logger.Write(
|
||||
LogLevel.Error,
|
||||
"Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath.");
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsPath = Path.GetFullPath(Path.Combine(workspaceRootPath, settingsPath));
|
||||
}
|
||||
}
|
||||
|
||||
this.SettingsPath = settingsPath;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the query execution settings
|
||||
/// </summary>
|
||||
public QueryExecutionSettings QueryExecutionSettings { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
private static LogWriter logWriter;
|
||||
|
||||
private static bool isEnabled;
|
||||
|
||||
private static bool isInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Logger for the current session.
|
||||
/// </summary>
|
||||
@@ -56,8 +60,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
/// </param>
|
||||
public static void Initialize(
|
||||
string logFilePath = "sqltools",
|
||||
LogLevel minimumLogLevel = LogLevel.Normal)
|
||||
LogLevel minimumLogLevel = LogLevel.Normal,
|
||||
bool isEnabled = true)
|
||||
{
|
||||
Logger.isEnabled = isEnabled;
|
||||
|
||||
// return if the logger is not enabled or already initialized
|
||||
if (!Logger.isEnabled || Logger.isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.isInitialized = true;
|
||||
|
||||
// get a unique number to prevent conflicts of two process launching at the same time
|
||||
int uniqueId;
|
||||
try
|
||||
@@ -89,6 +104,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
minimumLogLevel,
|
||||
fullFileName,
|
||||
true);
|
||||
|
||||
Logger.Write(LogLevel.Normal, "Initializing SQL Tools Service Host logger");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -116,7 +133,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
[CallerMemberName] string callerName = null,
|
||||
[CallerFilePath] string callerSourceFile = null,
|
||||
[CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
{
|
||||
// return if the logger is not enabled or not initialized
|
||||
if (!Logger.isEnabled || !Logger.isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (logWriter != null)
|
||||
{
|
||||
logWriter.Write(
|
||||
|
||||
@@ -36,8 +36,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
/// <summary>
|
||||
/// Gets or sets the path which the editor client uses to identify this file.
|
||||
/// Setter for testing purposes only
|
||||
/// virtual to allow mocking.
|
||||
/// </summary>
|
||||
public string ClientFilePath { get; internal set; }
|
||||
public virtual string ClientFilePath { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean that determines whether
|
||||
@@ -56,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
/// Gets or sets a string containing the full contents of the file.
|
||||
/// Setter for testing purposes only
|
||||
/// </summary>
|
||||
public string Contents
|
||||
public virtual string Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -109,7 +110,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
/// <summary>
|
||||
/// Add a default constructor for testing
|
||||
/// </summary>
|
||||
internal ScriptFile()
|
||||
public ScriptFile()
|
||||
{
|
||||
ClientFilePath = "test.sql";
|
||||
}
|
||||
@@ -171,11 +172,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a range of lines from the file's contents.
|
||||
/// Gets a range of lines from the file's contents. Virtual method to allow for
|
||||
/// mocking.
|
||||
/// </summary>
|
||||
/// <param name="bufferRange">The buffer range from which lines will be extracted.</param>
|
||||
/// <returns>An array of strings from the specified range of the file.</returns>
|
||||
public string[] GetLinesInRange(BufferRange bufferRange)
|
||||
public virtual string[] GetLinesInRange(BufferRange bufferRange)
|
||||
{
|
||||
this.ValidatePosition(bufferRange.Start);
|
||||
this.ValidatePosition(bufferRange.End);
|
||||
|
||||
@@ -50,7 +50,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
|
||||
/// <summary>
|
||||
/// Gets an open file in the workspace. If the file isn't open but
|
||||
/// exists on the filesystem, load and return it.
|
||||
/// exists on the filesystem, load and return it. Virtual method to
|
||||
/// allow for mocking
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path at which the script resides.</param>
|
||||
/// <exception cref="FileNotFoundException">
|
||||
@@ -59,7 +60,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="filePath"/> contains a null or empty string.
|
||||
/// </exception>
|
||||
public ScriptFile GetFile(string filePath)
|
||||
public virtual ScriptFile GetFile(string filePath)
|
||||
{
|
||||
Validate.IsNotNullOrWhitespaceString("filePath", filePath);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
|
||||
#region Singleton Instance Implementation
|
||||
|
||||
private static readonly Lazy<WorkspaceService<TConfig>> instance = new Lazy<WorkspaceService<TConfig>>(() => new WorkspaceService<TConfig>());
|
||||
private static Lazy<WorkspaceService<TConfig>> instance = new Lazy<WorkspaceService<TConfig>>(() => new WorkspaceService<TConfig>());
|
||||
|
||||
public static WorkspaceService<TConfig> Instance
|
||||
{
|
||||
@@ -52,8 +52,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
|
||||
#region Properties
|
||||
|
||||
public Workspace Workspace { get; private set; }
|
||||
/// <summary>
|
||||
/// Workspace object for the service. Virtual to allow for mocking
|
||||
/// </summary>
|
||||
public virtual Workspace Workspace { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current settings for the workspace
|
||||
/// </summary>
|
||||
public TConfig CurrentSettings { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"Microsoft.SqlServer.SqlParser": "140.1.5",
|
||||
"System.Data.Common": "4.1.0",
|
||||
"System.Data.SqlClient": "4.1.0",
|
||||
"Microsoft.SqlServer.Smo": "140.1.5",
|
||||
"Microsoft.SqlServer.Smo": "140.1.8",
|
||||
"System.Security.SecureString": "4.0.0",
|
||||
"System.Collections.Specialized": "4.0.1",
|
||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||
|
||||
@@ -45,6 +45,14 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
}
|
||||
}
|
||||
|
||||
public static string ConnectionServiceConnectionCanceled
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ConnectionServiceConnectionCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ConnectionParamsValidateNullOwnerUri
|
||||
{
|
||||
get
|
||||
@@ -368,7 +376,7 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Keys
|
||||
{
|
||||
static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.SR", typeof(SR).GetTypeInfo().Assembly);
|
||||
static ResourceManager resourceManager = new ResourceManager(typeof(SR));
|
||||
|
||||
static CultureInfo _culture = null;
|
||||
|
||||
@@ -388,6 +396,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string ConnectionServiceConnStringInvalidIntent = "ConnectionServiceConnStringInvalidIntent";
|
||||
|
||||
|
||||
public const string ConnectionServiceConnectionCanceled = "ConnectionServiceConnectionCanceled";
|
||||
|
||||
|
||||
public const string ConnectionParamsValidateNullOwnerUri = "ConnectionParamsValidateNullOwnerUri";
|
||||
|
||||
|
||||
|
||||
@@ -140,6 +140,10 @@
|
||||
<comment>.
|
||||
Parameters: 0 - intent (string) </comment>
|
||||
</data>
|
||||
<data name="ConnectionServiceConnectionCanceled" xml:space="preserve">
|
||||
<value>Connection canceled</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ConnectionParamsValidateNullOwnerUri" xml:space="preserve">
|
||||
<value>OwnerUri cannot be null or empty</value>
|
||||
<comment></comment>
|
||||
@@ -240,7 +244,7 @@
|
||||
<data name="QueryServiceAffectedRows" xml:space="preserve">
|
||||
<value>({0} row(s) affected)</value>
|
||||
<comment>.
|
||||
Parameters: 0 - rows (int) </comment>
|
||||
Parameters: 0 - rows (long) </comment>
|
||||
</data>
|
||||
<data name="QueryServiceCompletedSuccessfully" xml:space="preserve">
|
||||
<value>Command(s) copleted successfully.</value>
|
||||
|
||||
@@ -33,6 +33,8 @@ ConnectionServiceConnStringInvalidAuthType(string authType) = Invalid value '{0}
|
||||
|
||||
ConnectionServiceConnStringInvalidIntent(string intent) = Invalid value '{0}' for ApplicationIntent. Valid values are 'ReadWrite' and 'ReadOnly'.
|
||||
|
||||
ConnectionServiceConnectionCanceled = Connection canceled
|
||||
|
||||
######
|
||||
### Connection Params Validation Errors
|
||||
|
||||
@@ -103,7 +105,7 @@ QueryServiceFileWrapperReadOnly = This FileStreamWrapper cannot be used for writ
|
||||
|
||||
### Query Request
|
||||
|
||||
QueryServiceAffectedRows(int rows) = ({0} row(s) affected)
|
||||
QueryServiceAffectedRows(long rows) = ({0} row(s) affected)
|
||||
|
||||
QueryServiceCompletedSuccessfully = Command(s) copleted successfully.
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
@@ -52,6 +53,214 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
return connectionMock.Object;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCancelConnectRequest()
|
||||
{
|
||||
var testFile = "file:///my/test/file.sql";
|
||||
|
||||
// Given a connection that times out and responds to cancellation
|
||||
var mockConnection = new Mock<DbConnection> { CallBase = true };
|
||||
CancellationToken token;
|
||||
bool ready = false;
|
||||
mockConnection.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
||||
.Callback<CancellationToken>(t =>
|
||||
{
|
||||
// Pass the token to the return handler and signal the main thread to cancel
|
||||
token = t;
|
||||
ready = true;
|
||||
})
|
||||
.Returns(() =>
|
||||
{
|
||||
if (TestUtils.WaitFor(() => token.IsCancellationRequested))
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
});
|
||||
|
||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||
.Returns(mockConnection.Object);
|
||||
|
||||
|
||||
var connectionService = new ConnectionService(mockFactory.Object);
|
||||
|
||||
// Connect the connection asynchronously in a background thread
|
||||
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
var connectTask = Task.Run(async () =>
|
||||
{
|
||||
return await connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = testFile,
|
||||
Connection = connectionDetails
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for the connection to call OpenAsync()
|
||||
Assert.True(TestUtils.WaitFor(() => ready));
|
||||
|
||||
// Send a cancellation request
|
||||
var cancelResult = connectionService
|
||||
.CancelConnect(new CancelConnectParams()
|
||||
{
|
||||
OwnerUri = testFile
|
||||
});
|
||||
|
||||
// Wait for the connection task to finish
|
||||
connectTask.Wait();
|
||||
|
||||
// Verify that the connection was cancelled (no connection was created)
|
||||
Assert.Null(connectTask.Result.ConnectionId);
|
||||
|
||||
// Verify that the cancel succeeded
|
||||
Assert.True(cancelResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void CanCancelConnectRequestByConnecting()
|
||||
{
|
||||
var testFile = "file:///my/test/file.sql";
|
||||
|
||||
// Given a connection that times out and responds to cancellation
|
||||
var mockConnection = new Mock<DbConnection> { CallBase = true };
|
||||
CancellationToken token;
|
||||
bool ready = false;
|
||||
mockConnection.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
||||
.Callback<CancellationToken>(t =>
|
||||
{
|
||||
// Pass the token to the return handler and signal the main thread to cancel
|
||||
token = t;
|
||||
ready = true;
|
||||
})
|
||||
.Returns(() =>
|
||||
{
|
||||
if (TestUtils.WaitFor(() => token.IsCancellationRequested))
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
});
|
||||
|
||||
// Given a second connection that succeeds
|
||||
var mockConnection2 = new Mock<DbConnection> { CallBase = true };
|
||||
mockConnection2.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
||||
.Returns(() => Task.Run(() => {}));
|
||||
|
||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||
mockFactory.SetupSequence(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||
.Returns(mockConnection.Object)
|
||||
.Returns(mockConnection2.Object);
|
||||
|
||||
|
||||
var connectionService = new ConnectionService(mockFactory.Object);
|
||||
|
||||
// Connect the first connection asynchronously in a background thread
|
||||
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
var connectTask = Task.Run(async () =>
|
||||
{
|
||||
return await connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = testFile,
|
||||
Connection = connectionDetails
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for the connection to call OpenAsync()
|
||||
Assert.True(TestUtils.WaitFor(() => ready));
|
||||
|
||||
// Send a cancellation by trying to connect again
|
||||
var connectResult = await connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = testFile,
|
||||
Connection = connectionDetails
|
||||
});
|
||||
|
||||
// Wait for the first connection task to finish
|
||||
connectTask.Wait();
|
||||
|
||||
// Verify that the first connection was cancelled (no connection was created)
|
||||
Assert.Null(connectTask.Result.ConnectionId);
|
||||
|
||||
// Verify that the second connection succeeded
|
||||
Assert.NotEmpty(connectResult.ConnectionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCancelConnectRequestByDisconnecting()
|
||||
{
|
||||
var testFile = "file:///my/test/file.sql";
|
||||
|
||||
// Given a connection that times out and responds to cancellation
|
||||
var mockConnection = new Mock<DbConnection> { CallBase = true };
|
||||
CancellationToken token;
|
||||
bool ready = false;
|
||||
mockConnection.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
||||
.Callback<CancellationToken>(t =>
|
||||
{
|
||||
// Pass the token to the return handler and signal the main thread to cancel
|
||||
token = t;
|
||||
ready = true;
|
||||
})
|
||||
.Returns(() =>
|
||||
{
|
||||
if (TestUtils.WaitFor(() => token.IsCancellationRequested))
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
});
|
||||
|
||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||
.Returns(mockConnection.Object);
|
||||
|
||||
|
||||
var connectionService = new ConnectionService(mockFactory.Object);
|
||||
|
||||
// Connect the first connection asynchronously in a background thread
|
||||
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
var connectTask = Task.Run(async () =>
|
||||
{
|
||||
return await connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = testFile,
|
||||
Connection = connectionDetails
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for the connection to call OpenAsync()
|
||||
Assert.True(TestUtils.WaitFor(() => ready));
|
||||
|
||||
// Send a cancellation by trying to disconnect
|
||||
var disconnectResult = connectionService
|
||||
.Disconnect(new DisconnectParams()
|
||||
{
|
||||
OwnerUri = testFile
|
||||
});
|
||||
|
||||
// Wait for the first connection task to finish
|
||||
connectTask.Wait();
|
||||
|
||||
// Verify that the first connection was cancelled (no connection was created)
|
||||
Assert.Null(connectTask.Result.ConnectionId);
|
||||
|
||||
// Verify that the disconnect failed (since it caused a cancellation)
|
||||
Assert.False(disconnectResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that we can connect to the default database when no database name is
|
||||
/// provided as a parameter.
|
||||
@@ -59,12 +268,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
[Theory]
|
||||
[InlineDataAttribute(null)]
|
||||
[InlineDataAttribute("")]
|
||||
public void CanConnectWithEmptyDatabaseName(string databaseName)
|
||||
public async void CanConnectWithEmptyDatabaseName(string databaseName)
|
||||
{
|
||||
// Connect
|
||||
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
connectionDetails.DatabaseName = databaseName;
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
TestObjects.GetTestConnectionService()
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -83,7 +292,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
[Theory]
|
||||
[InlineDataAttribute("master")]
|
||||
[InlineDataAttribute("nonMasterDb")]
|
||||
public void ConnectToDefaultDatabaseRespondsWithActualDbName(string expectedDbName)
|
||||
public async void ConnectToDefaultDatabaseRespondsWithActualDbName(string expectedDbName)
|
||||
{
|
||||
// Given connecting with empty database name will return the expected DB name
|
||||
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
||||
@@ -99,7 +308,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
connectionDetails.DatabaseName = string.Empty;
|
||||
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -118,14 +327,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// connection, we disconnect first before connecting.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ConnectingWhenConnectionExistCausesDisconnectThenConnect()
|
||||
public async void ConnectingWhenConnectionExistCausesDisconnectThenConnect()
|
||||
{
|
||||
bool callbackInvoked = false;
|
||||
|
||||
// first connect
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -146,7 +355,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
);
|
||||
|
||||
// send annother connect request
|
||||
connectionResult =
|
||||
connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -165,7 +374,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify that when connecting with invalid credentials, an error is thrown.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ConnectingWithInvalidCredentialsYieldsErrorMessage()
|
||||
public async void ConnectingWithInvalidCredentialsYieldsErrorMessage()
|
||||
{
|
||||
var testConnectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
var invalidConnectionDetails = new ConnectionDetails();
|
||||
@@ -175,7 +384,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
invalidConnectionDetails.Password = "invalidPassword";
|
||||
|
||||
// Connect to test db with invalid credentials
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
TestObjects.GetTestConnectionService()
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -204,10 +413,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
[InlineData("Integrated", "file://my/sample/file.sql", null, "test", "sa", "123456")]
|
||||
[InlineData("Integrated", "", "my-server", "test", "sa", "123456")]
|
||||
[InlineData("Integrated", "file://my/sample/file.sql", "", "test", "sa", "123456")]
|
||||
public void ConnectingWithInvalidParametersYieldsErrorMessage(string authType, string ownerUri, string server, string database, string userName, string password)
|
||||
public async void ConnectingWithInvalidParametersYieldsErrorMessage(string authType, string ownerUri, string server, string database, string userName, string password)
|
||||
{
|
||||
// Connect with invalid parameters
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
TestObjects.GetTestConnectionService()
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -238,10 +447,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
[InlineData("sa", "")]
|
||||
[InlineData(null, "12345678")]
|
||||
[InlineData("", "12345678")]
|
||||
public void ConnectingWithNoUsernameOrPasswordWorksForIntegratedAuth(string userName, string password)
|
||||
public async void ConnectingWithNoUsernameOrPasswordWorksForIntegratedAuth(string userName, string password)
|
||||
{
|
||||
// Connect
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
TestObjects.GetTestConnectionService()
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -263,10 +472,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify that when connecting with a null parameters object, an error is thrown.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ConnectingWithNullParametersObjectYieldsErrorMessage()
|
||||
public async void ConnectingWithNullParametersObjectYieldsErrorMessage()
|
||||
{
|
||||
// Connect with null parameters
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
TestObjects.GetTestConnectionService()
|
||||
.Connect(null);
|
||||
|
||||
@@ -330,7 +539,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify that a connection changed event is fired when the database context changes.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ConnectionChangedEventIsFiredWhenDatabaseContextChanges()
|
||||
public async void ConnectionChangedEventIsFiredWhenDatabaseContextChanges()
|
||||
{
|
||||
var serviceHostMock = new Mock<IProtocolEndpoint>();
|
||||
|
||||
@@ -339,7 +548,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
|
||||
// Set up an initial connection
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -364,11 +573,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify that the SQL parser correctly detects errors in text
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ConnectToDatabaseTest()
|
||||
public async void ConnectToDatabaseTest()
|
||||
{
|
||||
// connect to a database instance
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
TestObjects.GetTestConnectionService()
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -384,12 +593,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify that we can disconnect from an active connection succesfully
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DisconnectFromDatabaseTest()
|
||||
public async void DisconnectFromDatabaseTest()
|
||||
{
|
||||
// first connect
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -414,14 +623,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Test that when a disconnect is performed, the callback event is fired
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DisconnectFiresCallbackEvent()
|
||||
public async void DisconnectFiresCallbackEvent()
|
||||
{
|
||||
bool callbackInvoked = false;
|
||||
|
||||
// first connect
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -458,12 +667,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Test that disconnecting an active connection removes the Owner URI -> ConnectionInfo mapping
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DisconnectRemovesOwnerMapping()
|
||||
public async void DisconnectRemovesOwnerMapping()
|
||||
{
|
||||
// first connect
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -498,12 +707,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
[InlineDataAttribute(null)]
|
||||
[InlineDataAttribute("")]
|
||||
|
||||
public void DisconnectValidatesParameters(string disconnectUri)
|
||||
public async void DisconnectValidatesParameters(string disconnectUri)
|
||||
{
|
||||
// first connect
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -530,7 +739,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verifies the the list databases operation lists database names for the server used by a connection.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ListDatabasesOnServerForCurrentConnectionReturnsDatabaseNames()
|
||||
public async void ListDatabasesOnServerForCurrentConnectionReturnsDatabaseNames()
|
||||
{
|
||||
// Result set for the query of database names
|
||||
Dictionary<string, string>[] data =
|
||||
@@ -550,7 +759,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
|
||||
// connect to a database instance
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionResult =
|
||||
var connectionResult = await
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
@@ -579,7 +788,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify that the SQL parser correctly detects errors in text
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void OnConnectionCallbackHandlerTest()
|
||||
public async void OnConnectionCallbackHandlerTest()
|
||||
{
|
||||
bool callbackInvoked = false;
|
||||
|
||||
@@ -593,7 +802,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
);
|
||||
|
||||
// connect to a database instance
|
||||
var connectionResult = connectionService.Connect(TestObjects.GetTestConnectionParams());
|
||||
var connectionResult = await connectionService.Connect(TestObjects.GetTestConnectionParams());
|
||||
|
||||
// verify that a valid connection id was returned
|
||||
Assert.True(callbackInvoked);
|
||||
@@ -603,14 +812,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// Verify when a connection is created that the URI -> Connection mapping is created in the connection service.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestConnectRequestRegistersOwner()
|
||||
public async void TestConnectRequestRegistersOwner()
|
||||
{
|
||||
// Given a request to connect to a database
|
||||
var service = TestObjects.GetTestConnectionService();
|
||||
var connectParams = TestObjects.GetTestConnectionParams();
|
||||
|
||||
// connect to a database instance
|
||||
var connectionResult = service.Connect(connectParams);
|
||||
var connectionResult = await service.Connect(connectParams);
|
||||
|
||||
// verify that a valid connection id was returned
|
||||
Assert.NotNull(connectionResult.ConnectionId);
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the language service autocomplete component
|
||||
/// </summary>
|
||||
public class AutocompleteTests
|
||||
{
|
||||
private const int TaskTimeout = 60000;
|
||||
|
||||
private readonly string testScriptUri = TestObjects.ScriptUri;
|
||||
|
||||
private readonly string testConnectionKey = "testdbcontextkey";
|
||||
|
||||
private Mock<ConnectedBindingQueue> bindingQueue;
|
||||
|
||||
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
|
||||
|
||||
private Mock<RequestContext<CompletionItem[]>> requestContext;
|
||||
|
||||
private Mock<IBinder> binder;
|
||||
|
||||
private TextDocumentPosition textDocument;
|
||||
|
||||
private void InitializeTestObjects()
|
||||
{
|
||||
// initial cursor position in the script file
|
||||
textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
}
|
||||
};
|
||||
|
||||
// default settings are stored in the workspace service
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
|
||||
// set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
|
||||
|
||||
// set up workspace mock
|
||||
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// setup binding queue mock
|
||||
bindingQueue = new Mock<ConnectedBindingQueue>();
|
||||
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>()))
|
||||
.Returns(this.testConnectionKey);
|
||||
|
||||
// inject mock instances into the Language Service
|
||||
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
|
||||
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
|
||||
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
|
||||
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
|
||||
LanguageService.Instance.BindingQueue = bindingQueue.Object;
|
||||
|
||||
// setup the mock for SendResult
|
||||
requestContext = new Mock<RequestContext<CompletionItem[]>>();
|
||||
requestContext.Setup(rc => rc.SendResult(It.IsAny<CompletionItem[]>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
|
||||
// setup the IBinder mock
|
||||
binder = new Mock<IBinder>();
|
||||
binder.Setup(b => b.Bind(
|
||||
It.IsAny<IEnumerable<ParseResult>>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BindMode>()));
|
||||
|
||||
var testScriptParseInfo = new ScriptParseInfo();
|
||||
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
|
||||
testScriptParseInfo.IsConnected = true;
|
||||
testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
|
||||
|
||||
// setup the binding context object
|
||||
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
|
||||
bindingContext.Binder = binder.Object;
|
||||
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the primary completion list event handler
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCompletionsHandlerTest()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
|
||||
// request the completion list
|
||||
Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object);
|
||||
handleCompletion.Wait(TaskTimeout);
|
||||
|
||||
// verify that send result was called with a completion array
|
||||
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void UpdateLanguageServiceOnConnection()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
|
||||
AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object;
|
||||
|
||||
ConnectionInfo connInfo = TestObjects.GetTestConnectionInfo();
|
||||
|
||||
await LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test class for the test binding context
|
||||
/// </summary>
|
||||
public class TestBindingContext : IBindingContext
|
||||
{
|
||||
public TestBindingContext()
|
||||
{
|
||||
this.BindingLocked = new ManualResetEvent(initialState: true);
|
||||
this.BindingTimeout = 3000;
|
||||
}
|
||||
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
public ServerConnection ServerConnection { get; set; }
|
||||
|
||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
public SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||
|
||||
public IBinder Binder { get; set; }
|
||||
|
||||
public ManualResetEvent BindingLocked { get; set; }
|
||||
|
||||
public int BindingTimeout { get; set; }
|
||||
|
||||
public ParseOptions ParseOptions { get; }
|
||||
|
||||
public ServerVersion ServerVersion { get; }
|
||||
|
||||
public DatabaseEngineType DatabaseEngineType { get; }
|
||||
|
||||
public TransactSqlVersion TransactSqlVersion { get; }
|
||||
|
||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Binding Queue
|
||||
/// </summary>
|
||||
public class BindingQueueTests
|
||||
{
|
||||
private int bindCallCount = 0;
|
||||
|
||||
private int timeoutCallCount = 0;
|
||||
|
||||
private int bindCallbackDelay = 0;
|
||||
|
||||
private bool isCancelationRequested = false;
|
||||
|
||||
private IBindingContext bindingContext = null;
|
||||
|
||||
private BindingQueue<TestBindingContext> bindingQueue = null;
|
||||
|
||||
private void InitializeTestSettings()
|
||||
{
|
||||
this.bindCallCount = 0;
|
||||
this.timeoutCallCount = 0;
|
||||
this.bindCallbackDelay = 10;
|
||||
this.isCancelationRequested = false;
|
||||
this.bindingContext = GetMockBindingContext();
|
||||
this.bindingQueue = new BindingQueue<TestBindingContext>();
|
||||
}
|
||||
|
||||
private IBindingContext GetMockBindingContext()
|
||||
{
|
||||
return new TestBindingContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test bind operation callback
|
||||
/// </summary>
|
||||
private object TestBindOperation(
|
||||
IBindingContext bindContext,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay);
|
||||
this.isCancelationRequested = cancelToken.IsCancellationRequested;
|
||||
if (!this.isCancelationRequested)
|
||||
{
|
||||
++this.bindCallCount;
|
||||
}
|
||||
return new CompletionItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test callback for the bind timeout operation
|
||||
/// </summary>
|
||||
private object TestTimeoutOperation(
|
||||
IBindingContext bindingContext)
|
||||
{
|
||||
++this.timeoutCallCount;
|
||||
return new CompletionItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a single task
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void QueueOneBindingOperationTest()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 1);
|
||||
Assert.True(this.timeoutCallCount == 0);
|
||||
Assert.False(this.isCancelationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a 100 short tasks
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Queue100BindingOperationTest()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
}
|
||||
|
||||
Thread.Sleep(2000);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 100);
|
||||
Assert.True(this.timeoutCallCount == 0);
|
||||
Assert.False(this.isCancelationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an task with a long operation causing a timeout
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void QueueWithTimeout()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
this.bindCallbackDelay = 1000;
|
||||
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindingTimeout: bindCallbackDelay / 2,
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
|
||||
Thread.Sleep(this.bindCallbackDelay + 100);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 0);
|
||||
Assert.True(this.timeoutCallCount == 1);
|
||||
Assert.True(this.isCancelationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
@@ -43,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
#region "Diagnostics tests"
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the latest SqlParser (2016 as of this writing) is used by default
|
||||
/// </summary>
|
||||
@@ -154,61 +145,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
|
||||
#region "General Language Service tests"
|
||||
|
||||
/// <summary>
|
||||
/// Check that autocomplete is enabled by default
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CheckAutocompleteEnabledByDefault()
|
||||
{
|
||||
// get test service
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
Assert.True(service.ShouldEnableAutocomplete());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
[Fact]
|
||||
// Test is causing failures in build lab..investigating to reenable
|
||||
//[Fact]
|
||||
public void ServiceInitiailzation()
|
||||
{
|
||||
InitializeTestServices();
|
||||
|
||||
Assert.True(LanguageService.Instance.Context != null);
|
||||
Assert.True(LanguageService.Instance.ConnectionServiceInstance != null);
|
||||
Assert.True(LanguageService.ConnectionServiceInstance != null);
|
||||
Assert.True(LanguageService.Instance.CurrentSettings != null);
|
||||
Assert.True(LanguageService.Instance.CurrentWorkspace != null);
|
||||
|
||||
LanguageService.Instance.ConnectionServiceInstance = null;
|
||||
Assert.True(LanguageService.Instance.ConnectionServiceInstance == null);
|
||||
LanguageService.ConnectionServiceInstance = null;
|
||||
Assert.True(LanguageService.ConnectionServiceInstance == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void UpdateLanguageServiceOnConnection()
|
||||
{
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = ownerUri,
|
||||
Connection = TestObjects.GetTestConnectionDetails()
|
||||
});
|
||||
|
||||
ConnectionInfo connInfo = null;
|
||||
connectionService.TryFindConnection(ownerUri, out connInfo);
|
||||
|
||||
var task = LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo);
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
[Fact]
|
||||
// Test is causing failures in build lab..investigating to reenable
|
||||
//[Fact]
|
||||
public void PrepopulateCommonMetadata()
|
||||
{
|
||||
InitializeTestServices();
|
||||
@@ -232,7 +191,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||
scriptInfo.IsConnected = true;
|
||||
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo);
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo, null);
|
||||
}
|
||||
|
||||
private string GetTestSqlFile()
|
||||
@@ -259,8 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
|
||||
// set up the host details and profile paths
|
||||
var hostDetails = new HostDetails(hostName, hostProfileId, hostVersion);
|
||||
var profilePaths = new ProfilePaths(hostProfileId, "baseAllUsersPath", "baseCurrentUserPath");
|
||||
SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails, profilePaths);
|
||||
SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails);
|
||||
|
||||
// Grab the instance of the service host
|
||||
Hosting.ServiceHost serviceHost = Hosting.ServiceHost.Instance;
|
||||
@@ -281,9 +239,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
private Hosting.ServiceHost GetTestServiceHost()
|
||||
{
|
||||
// set up the host details and profile paths
|
||||
var hostDetails = new HostDetails("Test Service Host", "SQLToolsService", new Version(1,0));
|
||||
var profilePaths = new ProfilePaths("SQLToolsService", "baseAllUsersPath", "baseCurrentUserPath");
|
||||
SqlToolsContext context = new SqlToolsContext(hostDetails, profilePaths);
|
||||
var hostDetails = new HostDetails("Test Service Host", "SQLToolsService", new Version(1,0));
|
||||
SqlToolsContext context = new SqlToolsContext(hostDetails);
|
||||
|
||||
// Grab the instance of the service host
|
||||
Hosting.ServiceHost host = Hosting.ServiceHost.Instance;
|
||||
|
||||
@@ -7,7 +7,10 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -16,12 +19,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public class CancelTests
|
||||
{
|
||||
[Fact]
|
||||
public void CancelInProgressQueryTest()
|
||||
public async void CancelInProgressQueryTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.Setup(file => file.GetLinesInRange(It.IsAny<BufferRange>()))
|
||||
.Returns(new string[] { Common.StandardQuery });
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind) and execute it
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.GetSubSectionDocument(), OwnerUri = Common.OwnerUri };
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
@@ -43,12 +55,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelExecutedQueryTest()
|
||||
public async void CancelExecutedQueryTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind) and wait for execution
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri};
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
@@ -69,11 +89,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelNonExistantTest()
|
||||
public async void CancelNonExistantTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I request to cancel a query that doesn't exist
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var cancelParams = new QueryCancelParams {OwnerUri = "Doesn't Exist"};
|
||||
QueryCancelResult result = null;
|
||||
var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null);
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
@@ -18,9 +19,11 @@ using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
@@ -29,12 +32,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
public class Common
|
||||
{
|
||||
public const SelectionData WholeDocument = null;
|
||||
|
||||
public const string StandardQuery = "SELECT * FROM sys.objects";
|
||||
|
||||
public const string InvalidQuery = "SELECT *** FROM sys.objects";
|
||||
|
||||
public const string NoOpQuery = "-- No ops here, just us chickens.";
|
||||
|
||||
public const string UdtQuery = "SELECT hierarchyid::Parse('/')";
|
||||
|
||||
public const string OwnerUri = "testFile";
|
||||
|
||||
public const int StandardRows = 5;
|
||||
@@ -72,9 +79,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
return output;
|
||||
}
|
||||
|
||||
public static SelectionData GetSubSectionDocument()
|
||||
{
|
||||
return new SelectionData(0, 0, 2, 2);
|
||||
}
|
||||
|
||||
public static Batch GetBasicExecutedBatch()
|
||||
{
|
||||
Batch batch = new Batch(StandardQuery, 1, GetFileStreamFactory());
|
||||
Batch batch = new Batch(StandardQuery, 0, 0, 2, 2, GetFileStreamFactory());
|
||||
batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait();
|
||||
return batch;
|
||||
}
|
||||
@@ -184,6 +196,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
connectionMock.Protected()
|
||||
.Setup<DbCommand>("CreateDbCommand")
|
||||
.Returns(CreateTestCommand(data, throwOnRead));
|
||||
connectionMock.Setup(dbc => dbc.Open())
|
||||
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Open));
|
||||
connectionMock.Setup(dbc => dbc.Close())
|
||||
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Closed));
|
||||
|
||||
return connectionMock.Object;
|
||||
}
|
||||
@@ -232,19 +248,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
|
||||
connInfo = Common.CreateTestConnectionInfo(null, false);
|
||||
|
||||
var srvConn = GetServerConnection(connInfo);
|
||||
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
||||
var binder = BinderProvider.CreateBinder(metadataProvider);
|
||||
|
||||
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri,
|
||||
new ScriptParseInfo
|
||||
{
|
||||
Binder = binder,
|
||||
MetadataProvider = metadataProvider,
|
||||
MetadataDisplayInfoProvider = displayInfoProvider
|
||||
});
|
||||
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo());
|
||||
|
||||
scriptFile = new ScriptFile {ClientFilePath = textDocument.TextDocument.Uri};
|
||||
|
||||
@@ -268,18 +273,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
}
|
||||
|
||||
public static QueryExecutionService GetPrimedExecutionService(ISqlConnectionFactory factory, bool isConnected)
|
||||
public static async Task<QueryExecutionService> GetPrimedExecutionService(ISqlConnectionFactory factory, bool isConnected, WorkspaceService<SqlToolsSettings> workspaceService)
|
||||
{
|
||||
var connectionService = new ConnectionService(factory);
|
||||
if (isConnected)
|
||||
{
|
||||
connectionService.Connect(new ConnectParams
|
||||
await connectionService.Connect(new ConnectParams
|
||||
{
|
||||
Connection = GetTestConnectionDetails(),
|
||||
OwnerUri = OwnerUri
|
||||
});
|
||||
}
|
||||
return new QueryExecutionService(connectionService) {BufferFileStreamFactory = GetFileStreamFactory()};
|
||||
return new QueryExecutionService(connectionService, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory()};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
{
|
||||
public class ReaderWriterPairTest
|
||||
{
|
||||
private static void VerifyReadWrite<T>(int valueLength, T value, Func<ServiceBufferFileStreamWriter, T, int> writeFunc, Func<ServiceBufferFileStreamReader, FileStreamReadResult<T>> readFunc)
|
||||
private static void VerifyReadWrite<T>(int valueLength, T value, Func<ServiceBufferFileStreamWriter, T, int> writeFunc, Func<ServiceBufferFileStreamReader, FileStreamReadResult> readFunc)
|
||||
{
|
||||
// Setup: Create a mock file stream wrapper
|
||||
Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper();
|
||||
@@ -29,16 +29,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
}
|
||||
|
||||
// ... And read the type T back
|
||||
FileStreamReadResult<T> outValue;
|
||||
FileStreamReadResult outValue;
|
||||
using (ServiceBufferFileStreamReader reader = new ServiceBufferFileStreamReader(mockWrapper, "abc"))
|
||||
{
|
||||
outValue = readFunc(reader);
|
||||
}
|
||||
|
||||
// Then:
|
||||
Assert.Equal(value, outValue.Value);
|
||||
Assert.Equal(value, outValue.Value.RawObject);
|
||||
Assert.Equal(valueLength, outValue.TotalLength);
|
||||
Assert.False(outValue.IsNull);
|
||||
Assert.NotNull(outValue.Value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -200,7 +200,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
};
|
||||
foreach (DateTimeOffset value in testValues)
|
||||
{
|
||||
VerifyReadWrite((sizeof(long) + 1)*2, value, (writer, val) => writer.WriteDateTimeOffset(val), reader => reader.ReadDateTimeOffset(0));
|
||||
VerifyReadWrite(sizeof(long)*2 + 1, value, (writer, val) => writer.WriteDateTimeOffset(val), reader => reader.ReadDateTimeOffset(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteBytes(null, 0));
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteBytes(null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,38 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
byte[] value = sb.ToArray();
|
||||
int lengthLength = length == 0 || length > 255 ? 5 : 1;
|
||||
int valueLength = sizeof(byte)*length + lengthLength;
|
||||
VerifyReadWrite(valueLength, value, (writer, val) => writer.WriteBytes(value, length), reader => reader.ReadBytes(0));
|
||||
VerifyReadWrite(valueLength, value, (writer, val) => writer.WriteBytes(value), reader => reader.ReadBytes(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GuidTest()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because Guid type can't be written as constant expressions
|
||||
Guid[] guids =
|
||||
{
|
||||
Guid.Empty, Guid.NewGuid(), Guid.NewGuid()
|
||||
};
|
||||
foreach (Guid guid in guids)
|
||||
{
|
||||
VerifyReadWrite(guid.ToByteArray().Length + 1, new SqlGuid(guid), (writer, val) => writer.WriteGuid(guid), reader => reader.ReadGuid(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoneyTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because SqlMoney can't be written as a constant expression
|
||||
SqlMoney[] monies =
|
||||
{
|
||||
SqlMoney.Zero, SqlMoney.MinValue, SqlMoney.MaxValue, new SqlMoney(1.02)
|
||||
};
|
||||
foreach (SqlMoney money in monies)
|
||||
{
|
||||
VerifyReadWrite(sizeof(decimal) + 1, money, (writer, val) => writer.WriteMoney(money), reader => reader.ReadMoney(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -16,19 +22,43 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public class DisposeTests
|
||||
{
|
||||
[Fact]
|
||||
public void DisposeExecutedQuery()
|
||||
public void DisposeResultSet()
|
||||
{
|
||||
// Setup: Mock file stream factory, mock db reader
|
||||
var mockFileStreamFactory = new Mock<IFileStreamFactory>();
|
||||
var mockDataReader = Common.CreateTestConnection(null, false).CreateCommand().ExecuteReaderAsync().Result;
|
||||
|
||||
// If: I setup a single resultset and then dispose it
|
||||
ResultSet rs = new ResultSet(mockDataReader, mockFileStreamFactory.Object);
|
||||
rs.Dispose();
|
||||
|
||||
// Then: The file that was created should have been deleted
|
||||
mockFileStreamFactory.Verify(fsf => fsf.DisposeFile(It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void DisposeExecutedQuery()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns("doesn't matter");
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind)
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri};
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// ... And then I dispose of the query
|
||||
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri};
|
||||
QueryDisposeResult result = null;
|
||||
var disposeRequest = GetQueryDisposeResultContextMock(qdr => result = qdr, null);
|
||||
var disposeRequest = GetQueryDisposeResultContextMock(qdr => {
|
||||
result = qdr;
|
||||
}, null);
|
||||
queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -40,11 +70,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryDisposeMissingQuery()
|
||||
public async void QueryDisposeMissingQuery()
|
||||
{
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I attempt to dispose a query that doesn't exist
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri};
|
||||
QueryDisposeResult result = null;
|
||||
var disposeRequest = GetQueryDisposeResultContextMock(qdr => result = qdr, null);
|
||||
@@ -57,6 +88,37 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Assert.NotEmpty(result.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceDispose()
|
||||
{
|
||||
// Setup:
|
||||
// ... We need a workspace service that returns a file
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// ... We need a query service
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true,
|
||||
workspaceService.Object);
|
||||
|
||||
// If:
|
||||
// ... I execute some bogus query
|
||||
var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
|
||||
// ... And it sticks around as an active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
|
||||
// ... The query execution service is disposed, like when the service is shutdown
|
||||
queryService.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... There should no longer be an active query
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
#region Mocking
|
||||
|
||||
private Mock<RequestContext<QueryDisposeResult>> GetQueryDisposeResultContextMock(
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
//#define USE_LIVE_CONNECTION
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
@@ -16,6 +18,8 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -29,7 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public void BatchCreationTest()
|
||||
{
|
||||
// If I create a new batch...
|
||||
Batch batch = new Batch(Common.StandardQuery, 1, Common.GetFileStreamFactory());
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... The text of the batch should be stored
|
||||
@@ -45,14 +49,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Assert.Empty(batch.ResultMessages);
|
||||
|
||||
// ... The start line of the batch should be 0
|
||||
Assert.Equal(0, batch.StartLine);
|
||||
Assert.Equal(0, batch.Selection.StartLine);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteNoResultSets()
|
||||
{
|
||||
// If I execute a query that should get no result sets
|
||||
Batch batch = new Batch(Common.StandardQuery, 1, Common.GetFileStreamFactory());
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -70,7 +74,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(1, batch.ResultMessages.Count());
|
||||
Assert.Contains("1 ", batch.ResultMessages.First());
|
||||
Assert.Contains("1 ", batch.ResultMessages.First().Message);
|
||||
// NOTE: 1 is expected because this test simulates a 'update' statement where 1 row was affected.
|
||||
// The 1 in quotes is to make sure the 1 isn't part of a larger number
|
||||
}
|
||||
@@ -82,7 +86,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
|
||||
|
||||
// If I execute a query that should get one result set
|
||||
Batch batch = new Batch(Common.StandardQuery, 1, Common.GetFileStreamFactory());
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -104,7 +108,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||
Assert.Contains(Common.StandardRows.ToString(), batch.ResultMessages.First());
|
||||
Assert.Contains(Common.StandardRows.ToString(), batch.ResultMessages.First().Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -115,7 +119,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false);
|
||||
|
||||
// If I execute a query that should get two result sets
|
||||
Batch batch = new Batch(Common.StandardQuery, 1, Common.GetFileStreamFactory());
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 1, 1, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -151,7 +155,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||
foreach (var rsm in batch.ResultMessages)
|
||||
{
|
||||
Assert.Contains(Common.StandardRows.ToString(), rsm);
|
||||
Assert.Contains(Common.StandardRows.ToString(), rsm.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +165,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||
|
||||
// If I execute a batch that is invalid
|
||||
Batch batch = new Batch(Common.StandardQuery, 1, Common.GetFileStreamFactory());
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -183,7 +187,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
|
||||
|
||||
// If I execute a batch
|
||||
Batch batch = new Batch(Common.StandardQuery, 1, Common.GetFileStreamFactory());
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
@@ -213,7 +217,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... I create a batch that has an empty query
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() => new Batch(query, 1, Common.GetFileStreamFactory()));
|
||||
Assert.Throws<ArgumentException>(() => new Batch(query, 0, 0, 2, 2, Common.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -223,7 +227,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... I create a batch that has no file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Batch("stuff", 1, null));
|
||||
Assert.Throws<ArgumentNullException>(() => new Batch("stuff", 0, 0, 2, 2, null));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -414,16 +418,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
#region Service Tests
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteValidNoResultsTest()
|
||||
public async void QueryExecuteValidNoResultsTest()
|
||||
{
|
||||
// Given:
|
||||
// ... Default settings are stored in the workspace service
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a valid query with no results
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var queryParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
@@ -450,12 +461,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteValidResultsTest()
|
||||
public async void QueryExecuteValidResultsTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a valid query with results
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true,
|
||||
workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
@@ -483,12 +503,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteUnconnectedUriTest()
|
||||
public async void QueryExecuteUnconnectedUriTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I request to execute a query using a file URI that isn't connected
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QueryText = Common.StandardQuery };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
var requestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
@@ -506,12 +528,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteInProgressTest()
|
||||
public async void QueryExecuteInProgressTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
@@ -535,12 +566,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteCompletedTest()
|
||||
public async void QueryExecuteCompletedTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
@@ -565,14 +605,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void QueryExecuteMissingQueryTest(string query)
|
||||
public async void QueryExecuteMissingSelectionTest(SelectionData selection)
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns("");
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a query with a missing query string
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = query };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = selection };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
var requestContext =
|
||||
@@ -592,12 +639,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteInvalidQueryTest()
|
||||
public async void QueryExecuteInvalidQueryTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a query that is invalid
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, true), true);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, true), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
@@ -616,7 +670,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Assert.NotEmpty(complete.BatchSummaries[0].Messages);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#if USE_LIVE_CONNECTION
|
||||
[Fact]
|
||||
public void QueryUdtShouldNotRetry()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query with a udt column in the result set
|
||||
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
|
||||
Query query = new Query(Common.UdtQuery, connectionInfo, new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
DateTime startTime = DateTime.Now;
|
||||
query.Execute().Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should complete within 2 seconds since retry logic should not kick in
|
||||
Assert.True(DateTime.Now.Subtract(startTime) < TimeSpan.FromSeconds(2), "Query completed slower than expected, did retry logic execute?");
|
||||
|
||||
// Then:
|
||||
// ... There should be an error on the batch
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
Assert.True(query.BatchSummaries[0].HasError);
|
||||
Assert.NotEmpty(query.BatchSummaries[0].Messages);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
private void VerifyQueryExecuteCallCount(Mock<RequestContext<QueryExecuteResult>> mock, Times sendResultCalls, Times sendEventCalls, Times sendErrorCalls)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,9 @@ using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -23,11 +26,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test save results to a file as CSV with correct parameters
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsCsvSuccessTest()
|
||||
public async void SaveResultsAsCsvSuccessTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
@@ -57,15 +68,75 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test save results to a file as CSV with a selection of cells and correct parameters
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvWithSelectionSuccessTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as csv with correct parameters
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
OwnerUri = Common.OwnerUri,
|
||||
ResultSetIndex = 0,
|
||||
BatchIndex = 0,
|
||||
FilePath = "testwrite_2.csv",
|
||||
IncludeHeaders = true,
|
||||
RowStartIndex = 0,
|
||||
RowEndIndex = 0,
|
||||
ColumnStartIndex = 0,
|
||||
ColumnEndIndex = 0
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test handling exception in saving results to CSV file
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsCsvExceptionTest()
|
||||
public async void SaveResultsAsCsvExceptionTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
@@ -93,11 +164,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test saving results to CSV file when the requested result set is no longer active
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsCsvQueryNotFoundTest()
|
||||
public async void SaveResultsAsCsvQueryNotFoundTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
@@ -123,11 +196,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test save results to a file as JSON with correct parameters
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsJsonSuccessTest()
|
||||
public async void SaveResultsAsJsonSuccessTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
@@ -137,13 +218,62 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
OwnerUri = Common.OwnerUri,
|
||||
ResultSetIndex = 0,
|
||||
BatchIndex = 0,
|
||||
FilePath = "testwrite_4.json"
|
||||
FilePath = "testwrite_4.json"
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test save results to a file as JSON with a selection of cells and correct parameters
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void SaveResultsAsJsonWithSelectionSuccessTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as json with correct parameters
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
{
|
||||
OwnerUri = Common.OwnerUri,
|
||||
ResultSetIndex = 0,
|
||||
BatchIndex = 0,
|
||||
FilePath = "testwrite_5.json",
|
||||
RowStartIndex = 0,
|
||||
RowEndIndex = 0,
|
||||
ColumnStartIndex = 0,
|
||||
ColumnEndIndex = 0
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
@@ -160,11 +290,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test handling exception in saving results to JSON file
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsJsonExceptionTest()
|
||||
public async void SaveResultsAsJsonExceptionTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
@@ -192,11 +329,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test saving results to JSON file when the requested result set is no longer active
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsJsonQueryNotFoundTest()
|
||||
public async void SaveResultsAsJsonQueryNotFoundTest()
|
||||
{
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -17,6 +20,48 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
public class SubsetTests
|
||||
{
|
||||
#region ResultSet Class Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(0,2)]
|
||||
[InlineData(0,20)]
|
||||
[InlineData(1,2)]
|
||||
public void ResultSetValidTest(int startRow, int rowCount)
|
||||
{
|
||||
// Setup:
|
||||
// ... I have a batch that has been executed
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
|
||||
// If:
|
||||
// ... I have a result set and I ask for a subset with valid arguments
|
||||
ResultSet rs = b.ResultSets.First();
|
||||
ResultSetSubset subset = rs.GetSubset(startRow, rowCount).Result;
|
||||
|
||||
// Then:
|
||||
// ... I should get the requested number of rows back
|
||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.RowCount);
|
||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.Rows.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, 2)] // Invalid start index, too low
|
||||
[InlineData(10, 2)] // Invalid start index, too high
|
||||
[InlineData(0, -1)] // Invalid row count, too low
|
||||
[InlineData(0, 0)] // Invalid row count, zero
|
||||
public void ResultSetInvalidParmsTest(int rowStartIndex, int rowCount)
|
||||
{
|
||||
// If:
|
||||
// I have an executed batch with a resultset in it and request invalid result set from it
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
ResultSet rs = b.ResultSets.First();
|
||||
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => rs.GetSubset(rowStartIndex, rowCount)).Wait();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Batch Class Tests
|
||||
|
||||
[Theory]
|
||||
@@ -37,13 +82,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, 0, 2)] // Invalid result set, too low
|
||||
[InlineData(2, 0, 2)] // Invalid result set, too high
|
||||
[InlineData(0, -1, 2)] // Invalid start index, too low
|
||||
[InlineData(0, 10, 2)] // Invalid start index, too high
|
||||
[InlineData(0, 0, -1)] // Invalid row count, too low
|
||||
[InlineData(0, 0, 0)] // Invalid row count, zero
|
||||
public void BatchSubsetInvalidParamsTest(int resultSetIndex, int rowStartInex, int rowCount)
|
||||
[InlineData(-1)] // Invalid result set, too low
|
||||
[InlineData(2)] // Invalid result set, too high
|
||||
public void BatchSubsetInvalidParamsTest(int resultSetIndex)
|
||||
{
|
||||
// If I have an executed batch
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
@@ -51,7 +92,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... And I ask for a subset with an invalid result set index
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => b.GetSubset(resultSetIndex, rowStartInex, rowCount)).Wait();
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => b.GetSubset(resultSetIndex, 0, 2)).Wait();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -91,11 +132,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async Task SubsetServiceValidTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I have a query that has results (doesn't matter what)
|
||||
var queryService =Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri};
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true,
|
||||
workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
|
||||
@@ -115,11 +165,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubsetServiceMissingQueryTest()
|
||||
public async void SubsetServiceMissingQueryTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I ask for a set of results for a file that hasn't executed a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
QueryExecuteSubsetResult result = null;
|
||||
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
||||
@@ -135,13 +187,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubsetServiceUnexecutedQueryTest()
|
||||
public async void SubsetServiceUnexecutedQueryTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I have a query that hasn't finished executing (doesn't matter what)
|
||||
var queryService = Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true,
|
||||
workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false;
|
||||
@@ -162,13 +223,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubsetServiceOutOfRangeSubsetTest()
|
||||
public async void SubsetServiceOutOfRangeSubsetTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I have a query that doesn't have any result sets
|
||||
var queryService = Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri };
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(null, false), true,
|
||||
workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
@@ -191,7 +255,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
#region Mocking
|
||||
|
||||
private Mock<RequestContext<QueryExecuteSubsetResult>> GetQuerySubsetResultContextMock(
|
||||
private static Mock<RequestContext<QueryExecuteSubsetResult>> GetQuerySubsetResultContextMock(
|
||||
Action<QueryExecuteSubsetResult> resultCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
@@ -218,7 +282,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
private void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls,
|
||||
private static void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls,
|
||||
Times sendErrorCalls)
|
||||
{
|
||||
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteSubsetResult>()), sendResultCalls);
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// 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.SqlContext;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the SqlContext settins
|
||||
/// </summary>
|
||||
public class SettingsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate that the Language Service default settings are as expected
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateLanguageServiceDefaults()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
Assert.True(sqlToolsSettings.SqlTools.EnableIntellisense);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo);
|
||||
Assert.False(sqlToolsSettings.SqlTools.IntelliSense.LowerCaseSuggestions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the IsDiagnositicsEnabled flag behavior
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateIsDiagnosticsEnabled()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// diagnostics is enabled if IntelliSense and Diagnostics flags are set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = true;
|
||||
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
|
||||
// diagnostics is disabled if either IntelliSense and Diagnostics flags is not set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = true;
|
||||
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = false;
|
||||
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the IsSuggestionsEnabled flag behavior
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateIsSuggestionsEnabled()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// suggestions is enabled if IntelliSense and Suggestions flags are set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
|
||||
Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
|
||||
// suggestions is disabled if either IntelliSense and Suggestions flags is not set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
|
||||
Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = false;
|
||||
Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the IsQuickInfoEnabled flag behavior
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateIsQuickInfoEnabled()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// quick info is enabled if IntelliSense and quick info flags are set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
|
||||
Assert.True(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
|
||||
// quick info is disabled if either IntelliSense and quick info flags is not set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
|
||||
Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = false;
|
||||
Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ namespace Microsoft.SqlTools.Test.Utility
|
||||
/// </summary>
|
||||
public class TestObjects
|
||||
{
|
||||
public const string ScriptUri = "file://some/file.sql";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test connection service
|
||||
/// </summary>
|
||||
@@ -35,11 +37,22 @@ namespace Microsoft.SqlTools.Test.Utility
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test connection info instance.
|
||||
/// </summary>
|
||||
public static ConnectionInfo GetTestConnectionInfo()
|
||||
{
|
||||
return new ConnectionInfo(
|
||||
GetTestSqlConnectionFactory(),
|
||||
ScriptUri,
|
||||
GetTestConnectionDetails());
|
||||
}
|
||||
|
||||
public static ConnectParams GetTestConnectionParams()
|
||||
{
|
||||
return new ConnectParams()
|
||||
{
|
||||
OwnerUri = "file://some/file.sql",
|
||||
OwnerUri = ScriptUri,
|
||||
Connection = GetTestConnectionDetails()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
||||
{
|
||||
@@ -21,5 +22,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
||||
test();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a condition to be true for a limited amount of time.
|
||||
/// </summary>
|
||||
/// <param name="condition">Function that returns a boolean on a condition</param>
|
||||
/// <param name="intervalMilliseconds">Number of milliseconds to wait between test intervals.</param>
|
||||
/// <param name="intervalCount">Number of test intervals to perform before giving up.</param>
|
||||
/// <returns>True if the condition was met before the test interval limit.</returns>
|
||||
public static bool WaitFor(Func<bool> condition, int intervalMilliseconds = 10, int intervalCount = 200)
|
||||
{
|
||||
int count = 0;
|
||||
while (count++ < intervalCount && !condition.Invoke())
|
||||
{
|
||||
Thread.Sleep(intervalMilliseconds);
|
||||
}
|
||||
|
||||
return (count < intervalCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"System.Runtime.Serialization.Primitives": "4.1.1",
|
||||
"System.Data.Common": "4.1.0",
|
||||
"System.Data.SqlClient": "4.1.0",
|
||||
"Microsoft.SqlServer.Smo": "140.1.5",
|
||||
"Microsoft.SqlServer.Smo": "140.1.8",
|
||||
"System.Security.SecureString": "4.0.0",
|
||||
"System.Collections.Specialized": "4.0.1",
|
||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||
|
||||
Reference in New Issue
Block a user