From 01d7cde0e3004758da0f3a4bd86f164deb1c5126 Mon Sep 17 00:00:00 2001 From: Kevin Cunnane Date: Wed, 20 Sep 2017 16:04:12 -0700 Subject: [PATCH] Add support for Dedicated Administrator Connection (#466) * Prototype support for admin connection * Added test and used correct default for ADMIN connection casing --- .../Connection/ConnectionService.cs | 33 ++++++++++++++- .../LanguageServices/LanguageService.cs | 5 +++ .../Connection/ConnectionServiceTests.cs | 42 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 63c4da66..f09e39a9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -29,6 +29,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// public class ConnectionService { + public const string AdminConnectionPrefix = "ADMIN:"; + /// /// Singleton service instance /// @@ -444,6 +446,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection throw new InvalidOperationException(SR.ConnectionServiceDbErrorDefaultNotConnected(ownerUri)); } + if(IsDedicatedAdminConnection(connectionInfo.ConnectionDetails)) + { + // Since this is a dedicated connection only 1 is allowed at any time. Return the default connection for use in the requested action + return defaultConnection; + } + // Try to get the DbConnection DbConnection connection; if (!connectionInfo.TryGetConnection(connectionType, out connection) && ConnectionType.Default != connectionType) @@ -842,12 +850,33 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection await requestContext.SendError(ex.ToString()); } } - + + /// + /// Checks if a ConnectionDetails object represents a DAC connection + /// + /// + public static bool IsDedicatedAdminConnection(ConnectionDetails connectionDetails) + { + Validate.IsNotNull(nameof(connectionDetails), connectionDetails); + SqlConnectionStringBuilder builder = CreateConnectionStringBuilder(connectionDetails); + string serverName = builder.DataSource; + return serverName != null && serverName.StartsWith(AdminConnectionPrefix, StringComparison.OrdinalIgnoreCase); + } + /// /// Build a connection string from a connection details instance /// /// public static string BuildConnectionString(ConnectionDetails connectionDetails) + { + return CreateConnectionStringBuilder(connectionDetails).ToString(); + } + + /// + /// Build a connection string builder a connection details instance + /// + /// + public static SqlConnectionStringBuilder CreateConnectionStringBuilder(ConnectionDetails connectionDetails) { SqlConnectionStringBuilder connectionBuilder; @@ -981,7 +1010,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion; } - return connectionBuilder.ToString(); + return connectionBuilder; } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 3c1d1dee..61177c09 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -834,6 +834,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { await Task.Run(() => { + if (ConnectionService.IsDedicatedAdminConnection(info.ConnectionDetails)) + { + // Intellisense cannot be run on these connections as only 1 SqlConnection can be opened on them at a time + return; + } ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true); if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) { diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs index 9171d7cf..db82dba7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Connection/ConnectionServiceTests.cs @@ -1210,6 +1210,48 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection await Assert.ThrowsAsync( () => service.GetOrOpenConnection(TestObjects.ScriptUri, ConnectionType.Query)); } + + [Fact] + public async Task GetOrOpenAdminDefaultConnection() + { + // Setup: Create a connection service with an empty connection info obj + var service = TestObjects.GetTestConnectionService(); + var connInfo = new ConnectionInfo(null, null, null); + service.OwnerToConnectionMap[TestObjects.ScriptUri] = connInfo; + + // If: I ask for a connection on a connection that doesn't have a default connection + // Then: An exception should be thrown + await Assert.ThrowsAsync( + () => service.GetOrOpenConnection(TestObjects.ScriptUri, ConnectionType.Query)); + } + + [Fact] + public async Task ConnectionWithAdminConnectionEnsuresOnlyOneConnectionCreated() + { + // If I try to connect using a connection string, it overrides the server name and username for the connection + ConnectParams connectionParameters = TestObjects.GetTestConnectionParams(); + string serverName = "ADMIN:overriddenServerName"; + string userName = "overriddenUserName"; + connectionParameters.Connection.ServerName = serverName; + connectionParameters.Connection.UserName = userName; + + // Connect + ConnectionService service = TestObjects.GetTestConnectionService(); + var connectionResult = await service.Connect(connectionParameters); + + // Verify you can get the connection for default + DbConnection defaultConn = await service.GetOrOpenConnection(connectionParameters.OwnerUri, ConnectionType.Default); + ConnectionInfo connInfo = service.OwnerToConnectionMap[connectionParameters.OwnerUri]; + Assert.NotNull(defaultConn); + Assert.Equal(connInfo.AllConnections.Count, 1); + + // Verify that for the Query, no new connection is created + DbConnection queryConn = await service.GetOrOpenConnection(connectionParameters.OwnerUri, ConnectionType.Query); + connInfo = service.OwnerToConnectionMap[connectionParameters.OwnerUri]; + Assert.NotNull(defaultConn); + Assert.Equal(connInfo.AllConnections.Count, 1); + + } [Fact] public async Task ConnectionWithConnectionStringSucceeds()