diff --git a/nuget.config b/nuget.config
index a839b559..933ad9ee 100644
--- a/nuget.config
+++ b/nuget.config
@@ -9,7 +9,6 @@
-
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
index 83b860a8..8f430e29 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
@@ -38,17 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
public ConnectionDetails ConnectionDetails { get; private set; }
- public DbConnection SqlConnection { get; private set; }
-
- public void OpenConnection()
- {
- // build the connection string from the input parameters
- string connectionString = ConnectionService.BuildConnectionString(ConnectionDetails);
-
- // create a sql connection instance
- SqlConnection = Factory.CreateSqlConnection(connectionString);
- SqlConnection.Open();
- }
+ public DbConnection SqlConnection { get; set; }
}
///
@@ -93,11 +83,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
///
public delegate Task OnConnectionHandler(ConnectionInfo info);
+ ///
+ // Callback for ondisconnect handler
+ ///
+ public delegate Task OnDisconnectHandler(ConnectionSummary summary);
+
///
/// List of onconnection handlers
///
private readonly List onConnectionActivities = new List();
+ ///
+ /// List of ondisconnect handlers
+ ///
+ private readonly List onDisconnectActivities = new List();
+
///
/// Gets the SQL connection factory instance
///
@@ -127,16 +127,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
{
return this.ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo);
}
-
- private static ConnectionSummary CopySummary(ConnectionSummary summary)
- {
- return new ConnectionSummary()
- {
- ServerName = summary.ServerName,
- DatabaseName = summary.DatabaseName,
- UserName = summary.UserName
- };
- }
///
/// Open a connection with the specified connection details
@@ -153,10 +143,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
};
}
+ // Resolve if it is an existing connection
+ // Disconnect active connection if the URI is already connected
ConnectionInfo connectionInfo;
if (ownerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo) )
{
- // TODO disconnect
+ var disconnectParams = new DisconnectParams()
+ {
+ OwnerUri = connectionParams.OwnerUri
+ };
+ Disconnect(disconnectParams);
}
connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
@@ -164,7 +160,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
var response = new ConnectResponse();
try
{
- connectionInfo.OpenConnection();
+ // build the connection string from the input parameters
+ string connectionString = ConnectionService.BuildConnectionString(connectionInfo.ConnectionDetails);
+
+ // create a sql connection instance
+ connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString);
+ connectionInfo.SqlConnection.Open();
}
catch(Exception ex)
{
@@ -185,10 +186,45 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
return response;
}
+ ///
+ /// Close a connection with the specified connection details.
+ ///
+ public bool Disconnect(DisconnectParams disconnectParams)
+ {
+ // Validate parameters
+ if (disconnectParams == null || String.IsNullOrEmpty(disconnectParams.OwnerUri))
+ {
+ return false;
+ }
+
+ // Lookup the connection owned by the URI
+ ConnectionInfo info;
+ if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info))
+ {
+ return false;
+ }
+
+ // Close the connection
+ info.SqlConnection.Close();
+
+ // Remove URI mapping
+ ownerToConnectionMap.Remove(disconnectParams.OwnerUri);
+
+ // Invoke callback notifications
+ foreach (var activity in this.onDisconnectActivities)
+ {
+ activity(info.ConnectionDetails);
+ }
+
+ // Success
+ return true;
+ }
+
public void InitializeService(IProtocolEndpoint serviceHost)
{
// Register request and event handlers with the Service Host
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
+ serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest);
// Register the configuration update handler
WorkspaceService.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
@@ -202,6 +238,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
{
onConnectionActivities.Add(activity);
}
+
+ ///
+ /// Add a new method to be called when the ondisconnect request is submitted
+ ///
+ public void RegisterOnDisconnectTask(OnDisconnectHandler activity)
+ {
+ onDisconnectActivities.Add(activity);
+ }
///
/// Handle new connection requests
@@ -226,6 +270,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
await requestContext.SendError(ex.Message);
}
}
+
+ ///
+ /// Handle disconnect requests
+ ///
+ protected async Task HandleDisconnectRequest(
+ DisconnectParams disconnectParams,
+ RequestContext requestContext)
+ {
+ Logger.Write(LogLevel.Verbose, "HandleDisconnectRequest");
+
+ try
+ {
+ bool result = ConnectionService.Instance.Disconnect(disconnectParams);
+ await requestContext.SendResult(result);
+ }
+ catch(Exception ex)
+ {
+ await requestContext.SendError(ex.Message);
+ }
+
+ }
public Task HandleDidChangeConfigurationNotification(
SqlToolsSettings newSettings,
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionMessages.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectMessages.cs
similarity index 62%
rename from src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionMessages.cs
rename to src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectMessages.cs
index baa426e2..543b18f5 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionMessages.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectMessages.cs
@@ -7,85 +7,23 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
{
- ///
- /// Parameters for the Connect Request.
- ///
- public class ConnectParams
- {
- ///
- /// 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.
- ///
- public string OwnerUri { get; set; }
- ///
- /// Contains the required parameters to initialize a connection to a database.
- /// A connection will identified by its server name, database name and user name.
- /// This may be changed in the future to support multiple connections with different
- /// connection properties to the same database.
- ///
- public ConnectionDetails Connection { get; set; }
- }
-
- ///
- /// Parameters for the Disconnect Request.
- ///
- public class DisconnectParams
- {
- ///
- /// 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.
- ///
- public string ownerUri { get; set; }
- }
-
- ///
- /// Parameters for the ConnectionChanged Notification.
- ///
- public class ConnectionChangedParams
- {
- ///
- /// 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.
- ///
- public string ownerUri { get; set; }
- ///
- /// Contains the high-level properties about the connection, for display to the user.
- ///
- public ConnectionSummary Connection { get; set; }
- }
-
- ///
- /// Provides high level information about a connection.
- ///
- public class ConnectionSummary
- {
- ///
- /// Gets or sets the connection server name
- ///
- public string ServerName { get; set; }
-
- ///
- /// Gets or sets the connection database name
- ///
- public string DatabaseName { get; set; }
-
- ///
- /// Gets or sets the connection user name
- ///
- public string UserName { get; set; }
- }
///
- /// Message format for the initial connection request
+ /// Parameters for the Connect Request.
///
- public class ConnectionDetails : ConnectionSummary
+ public class ConnectParams
{
///
- /// Gets or sets the connection password
+ /// 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.
///
- ///
- public string Password { get; set; }
-
- // TODO Handle full set of properties
+ public string OwnerUri { get; set; }
+ ///
+ /// Contains the required parameters to initialize a connection to a database.
+ /// A connection will identified by its server name, database name and user name.
+ /// This may be changed in the future to support multiple connections with different
+ /// connection properties to the same database.
+ ///
+ public ConnectionDetails Connection { get; set; }
}
///
@@ -102,8 +40,43 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
/// Gets or sets any connection error messages
///
public string Messages { get; set; }
- }
-
+ }
+
+ ///
+ /// Provides high level information about a connection.
+ ///
+ public class ConnectionSummary
+ {
+ ///
+ /// Gets or sets the connection server name
+ ///
+ public string ServerName { get; set; }
+
+ ///
+ /// Gets or sets the connection database name
+ ///
+ public string DatabaseName { get; set; }
+
+ ///
+ /// Gets or sets the connection user name
+ ///
+ public string UserName { get; set; }
+ }
+
+ ///
+ /// Message format for the initial connection request
+ ///
+ public class ConnectionDetails : ConnectionSummary
+ {
+ ///
+ /// Gets or sets the connection password
+ ///
+ ///
+ public string Password { get; set; }
+
+ // TODO Handle full set of properties
+ }
+
///
/// Connect request mapping entry
///
@@ -113,25 +86,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
RequestType Type =
RequestType.Create("connection/connect");
}
-
- ///
- /// Disconnect request mapping entry
- ///
- public class DisconnectRequest
- {
- public static readonly
- RequestType Type =
- RequestType.Create("connection/disconnect");
- }
-
- ///
- /// ConnectionChanged notification mapping entry
- ///
- public class ConnectionChangedNotification
- {
- public static readonly
- EventType Type =
- EventType.Create("connection/connectionchanged");
- }
-
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionMessagesExtensions.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectMessagesExtensions.cs
similarity index 100%
rename from src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionMessagesExtensions.cs
rename to src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectMessagesExtensions.cs
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionChangedMessages.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionChangedMessages.cs
new file mode 100644
index 00000000..94454bc5
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionChangedMessages.cs
@@ -0,0 +1,36 @@
+//
+// 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
+{
+ ///
+ /// Parameters for the ConnectionChanged Notification.
+ ///
+ public class ConnectionChangedParams
+ {
+ ///
+ /// 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.
+ ///
+ public string OwnerUri { get; set; }
+ ///
+ /// Contains the high-level properties about the connection, for display to the user.
+ ///
+ public ConnectionSummary Connection { get; set; }
+ }
+
+ ///
+ /// ConnectionChanged notification mapping entry
+ ///
+ public class ConnectionChangedNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("connection/connectionchanged");
+ }
+
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/DisconnectMessages.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/DisconnectMessages.cs
new file mode 100644
index 00000000..c078b308
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/DisconnectMessages.cs
@@ -0,0 +1,31 @@
+//
+// 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
+{
+ ///
+ /// Parameters for the Disconnect Request.
+ ///
+ public class DisconnectParams
+ {
+ ///
+ /// 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.
+ ///
+ public string OwnerUri { get; set; }
+ }
+
+ ///
+ /// Disconnect request mapping entry
+ ///
+ public class DisconnectRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("connection/disconnect");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs
index 7ecc7106..14148778 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs
@@ -21,8 +21,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// connection used to query for intellisense info
private DbConnection connection;
+ // number of documents (URI's) that are using the cache for the same database
+ // the autocomplete service uses this to remove unreferenced caches
+ public int ReferenceCount { get; set; }
+
public IntellisenseCache(ISqlConnectionFactory connectionFactory, ConnectionDetails connectionDetails)
{
+ ReferenceCount = 0;
DatabaseInfo = CopySummary(connectionDetails);
// TODO error handling on this. Intellisense should catch or else the service should handle
@@ -130,10 +135,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
public bool Equals(ConnectionSummary x, ConnectionSummary y)
{
- if (x == y) { return true; }
- else if (x != null)
+ if(x == y) { return true; }
+ else if(x != null)
{
- if (y == null) { return false; }
+ if(y == null) { return false; }
// Compare server, db, username. Note: server is case-insensitive in the driver
return string.Compare(x.ServerName, y.ServerName, StringComparison.OrdinalIgnoreCase) == 0
@@ -146,9 +151,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public int GetHashCode(ConnectionSummary obj)
{
int hashcode = 31;
- if (obj != null)
+ if(obj != null)
{
- if (obj.ServerName != null)
+ if(obj.ServerName != null)
{
hashcode ^= obj.ServerName.GetHashCode();
}
@@ -174,13 +179,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
/// Singleton service instance
///
- private static Lazy instance
+ private static Lazy instance
= new Lazy(() => new AutoCompleteService());
///
/// Gets the singleton service instance
///
- public static AutoCompleteService Instance
+ public static AutoCompleteService Instance
{
get
{
@@ -193,16 +198,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// TODO: Figure out how to make this truely singleton even with dependency injection for tests
///
public AutoCompleteService()
- {
+ {
}
#endregion
// Dictionary of unique intellisense caches for each Connection
- private Dictionary caches =
+ private Dictionary caches =
new Dictionary(new ConnectionSummaryComparer());
+ private Object cachesLock = new Object(); // Used when we insert/remove something from the cache dictionary
private ISqlConnectionFactory factory;
+ private Object factoryLock = new Object();
///
/// Internal for testing purposes only
@@ -211,22 +218,30 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
get
{
- // TODO consider protecting against multi-threaded access
- if (factory == null)
+ lock(factoryLock)
{
- factory = new SqlConnectionFactory();
+ if(factory == null)
+ {
+ factory = new SqlConnectionFactory();
+ }
}
return factory;
}
set
{
- factory = value;
+ lock(factoryLock)
+ {
+ factory = value;
+ }
}
}
public void InitializeService(ServiceHost serviceHost)
{
// Register a callback for when a connection is created
ConnectionService.Instance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
+
+ // Register a callback for when a connection is closed
+ ConnectionService.Instance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
}
private async Task UpdateAutoCompleteCache(ConnectionInfo connectionInfo)
@@ -237,20 +252,50 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
+ ///
+ /// Remove a reference to an autocomplete cache from a URI. If
+ /// it is the last URI connected to a particular connection,
+ /// then remove the cache.
+ ///
+ public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
+ {
+ await Task.Run( () =>
+ {
+ lock(cachesLock)
+ {
+ IntellisenseCache cache;
+ if( caches.TryGetValue(summary, out cache) )
+ {
+ cache.ReferenceCount--;
+
+ // Remove unused caches
+ if( cache.ReferenceCount == 0 )
+ {
+ caches.Remove(summary);
+ }
+ }
+ }
+ });
+ }
+
+
///
/// Update the cached autocomplete candidate list when the user connects to a database
- /// TODO: Update with refactoring/async
///
///
public async Task UpdateAutoCompleteCache(ConnectionDetails details)
{
IntellisenseCache cache;
- if (!caches.TryGetValue(details, out cache))
+ lock(cachesLock)
{
- cache = new IntellisenseCache(ConnectionFactory, details);
- caches[cache.DatabaseInfo] = cache;
+ if(!caches.TryGetValue(details, out cache))
+ {
+ cache = new IntellisenseCache(ConnectionFactory, details);
+ caches[cache.DatabaseInfo] = cache;
+ }
+ cache.ReferenceCount++;
}
-
+
await cache.UpdateCache();
}
@@ -263,19 +308,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
// Try to find a cache for the document's backing connection (if available)
// If we have a connection but no cache, we don't care - assuming the OnConnect and OnDisconnect listeners
- // behave well, there should be a cache for any actively connected document. This also helps skip documents
+ // behave well, there should be a cache for any actively connected document. This also helps skip documents
// that are not backed by a SQL connection
- ConnectionInfo connectionInfo;
+ ConnectionInfo info;
IntellisenseCache cache;
- if (ConnectionService.Instance.TryFindConnection(textDocumentPosition.Uri, out connectionInfo)
- && caches.TryGetValue(connectionInfo.ConnectionDetails, out cache))
+ if (ConnectionService.Instance.TryFindConnection(textDocumentPosition.Uri, out info)
+ && caches.TryGetValue((ConnectionSummary)info.ConnectionDetails, out cache))
{
return cache.GetAutoCompleteItems(textDocumentPosition).ToArray();
}
-
+
return new CompletionItem[0];
}
-
+
}
}
-
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
index 35ee0ebd..6cdbd745 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
@@ -103,10 +103,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
WorkspaceService.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification);
// Register the file open update handler
- WorkspaceService.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
-
- // register an OnConnection callback
- ConnectionService.Instance.RegisterOnConnectionTask(OnConnection);
+ WorkspaceService.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
// Store the SqlToolsContext for future use
Context = context;
@@ -305,16 +302,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
CurrentSettings.ScriptAnalysis.Update(newSettings.ScriptAnalysis, CurrentWorkspace.WorkspacePath);
}
- ///
- /// Callback for when a user connection is done processing
- ///
- ///
- public async Task OnConnection(ConnectionInfo connectionInfo)
- {
- // TODO consider whether this is needed at all - currently AutoComplete service handles its own updating
- await Task.FromResult(true);
- }
-
#endregion
#region Private Helpers
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs
index d0c991f8..9e3d5339 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs
@@ -19,6 +19,53 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
///
public class ConnectionServiceTests
{
+ ///
+ /// Verify that when a connection is started for a URI with an already existing
+ /// connection, we disconnect first before connecting.
+ ///
+ [Fact]
+ public void ConnectingWhenConnectionExistCausesDisconnectThenConnect()
+ {
+ bool callbackInvoked = false;
+
+ // first connect
+ string ownerUri = "file://my/sample/file.sql";
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ // verify that we are connected
+ Assert.NotEmpty(connectionResult.ConnectionId);
+
+ // register disconnect callback
+ connectionService.RegisterOnDisconnectTask(
+ (result) => {
+ callbackInvoked = true;
+ return Task.FromResult(true);
+ }
+ );
+
+ // send annother connect request
+ connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ // verify that the event was fired (we disconnected first before connecting)
+ Assert.True(callbackInvoked);
+
+ // verify that we connected again
+ Assert.NotEmpty(connectionResult.ConnectionId);
+ }
+
///
/// Verify that when connecting with invalid credentials, an error is thrown.
///
@@ -117,6 +164,151 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
Assert.NotEmpty(connectionResult.ConnectionId);
}
+ ///
+ /// Verify that we can disconnect from an active connection succesfully
+ ///
+ [Fact]
+ public void DisconnectFromDatabaseTest()
+ {
+ // first connect
+ string ownerUri = "file://my/sample/file.sql";
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ // verify that we are connected
+ Assert.NotEmpty(connectionResult.ConnectionId);
+
+ // send disconnect request
+ var disconnectResult =
+ connectionService
+ .Disconnect(new DisconnectParams()
+ {
+ OwnerUri = ownerUri
+ });
+ Assert.True(disconnectResult);
+ }
+
+ ///
+ /// Test that when a disconnect is performed, the callback event is fired
+ ///
+ [Fact]
+ public void DisconnectFiresCallbackEvent()
+ {
+ bool callbackInvoked = false;
+
+ // first connect
+ string ownerUri = "file://my/sample/file.sql";
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ // verify that we are connected
+ Assert.NotEmpty(connectionResult.ConnectionId);
+
+ // register disconnect callback
+ connectionService.RegisterOnDisconnectTask(
+ (result) => {
+ callbackInvoked = true;
+ return Task.FromResult(true);
+ }
+ );
+
+ // send disconnect request
+ var disconnectResult =
+ connectionService
+ .Disconnect(new DisconnectParams()
+ {
+ OwnerUri = ownerUri
+ });
+ Assert.True(disconnectResult);
+
+ // verify that the event was fired
+ Assert.True(callbackInvoked);
+ }
+
+ ///
+ /// Test that disconnecting an active connection removes the Owner URI -> ConnectionInfo mapping
+ ///
+ [Fact]
+ public void DisconnectRemovesOwnerMapping()
+ {
+ // first connect
+ string ownerUri = "file://my/sample/file.sql";
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ // verify that we are connected
+ Assert.NotEmpty(connectionResult.ConnectionId);
+
+ // check that the owner mapping exists
+ ConnectionInfo info;
+ Assert.True(connectionService.TryFindConnection(ownerUri, out info));
+
+ // send disconnect request
+ var disconnectResult =
+ connectionService
+ .Disconnect(new DisconnectParams()
+ {
+ OwnerUri = ownerUri
+ });
+ Assert.True(disconnectResult);
+
+ // check that the owner mapping no longer exists
+ Assert.False(connectionService.TryFindConnection(ownerUri, out info));
+ }
+
+ ///
+ /// Test that disconnecting validates parameters and doesn't succeed when they are invalid
+ ///
+ [Theory]
+ [InlineDataAttribute(null)]
+ [InlineDataAttribute("")]
+
+ public void DisconnectValidatesParameters(string disconnectUri)
+ {
+ // first connect
+ string ownerUri = "file://my/sample/file.sql";
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ // verify that we are connected
+ Assert.NotEmpty(connectionResult.ConnectionId);
+
+ // send disconnect request
+ var disconnectResult =
+ connectionService
+ .Disconnect(new DisconnectParams()
+ {
+ OwnerUri = disconnectUri
+ });
+
+ // verify that disconnect failed
+ Assert.False(disconnectResult);
+ }
+
///
/// Verify that the SQL parser correctly detects errors in text
///
@@ -151,24 +343,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
var service = TestObjects.GetTestConnectionService();
var connectParams = TestObjects.GetTestConnectionParams();
- //var endpoint = new Mock();
- //Func, Task> connectRequestHandler = null;
- //endpoint.Setup(e => e.SetRequestHandler(ConnectionRequest.Type, It.IsAny, Task>>()))
- // .Callback, Task>>(handler => connectRequestHandler = handler);
-
- // when I initialize the service
- //service.InitializeService(endpoint.Object);
-
- // then I expect the handler to be captured
- //Assert.NotNull(connectRequestHandler);
-
- // when I call the service
- //var requestContext = new Mock>();
-
- //connectRequestHandler(connectParams, requestContext.Object);
- // then I should get a live connection
-
- // and then I should have
// connect to a database instance
var connectionResult = service.Connect(connectParams);
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs
index 5ca94d2b..b1ee31bb 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs
@@ -169,7 +169,7 @@ namespace Microsoft.SqlTools.Test.Utility
public override void Close()
{
- throw new NotImplementedException();
+ // No Op
}
public override void Open()