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:
Karl Burtram
2016-10-08 19:18:26 +00:00
committed by GitHub
parent 7d6aabb618
commit 52b5f222db
65 changed files with 3739 additions and 1800 deletions

View File

@@ -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);
}
}
}
}