From 7d3d593a80c9ed534316a82cba09db01bbcfb153 Mon Sep 17 00:00:00 2001 From: Mitchell Sternke Date: Wed, 24 Aug 2016 16:03:08 -0700 Subject: [PATCH] Added request to list databases on the server for the current connection --- .../Connection/ConnectionService.cs | 68 ++++++++++++++++ .../Contracts/ConnectionDetailsExtensions.cs | 27 +++++++ .../Contracts/ListDatabasesParams.cs | 18 +++++ .../Contracts/ListDatabasesRequest.cs | 19 +++++ .../Contracts/ListDatabasesResponse.cs | 18 +++++ .../Connection/ConnectionServiceTests.cs | 81 +++++++++++++++++++ 6 files changed, 231 insertions(+) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesParams.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesResponse.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 8905ab92..cd143fd4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; +using System.Data; +using System.Data.Common; using System.Data.SqlClient; using System.Threading.Tasks; using Microsoft.SqlTools.EditorServices.Utility; @@ -194,11 +196,57 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection return true; } + /// + /// List all databases on the server specified + /// + public ListDatabasesResponse ListDatabases(ListDatabasesParams listDatabasesParams) + { + // Verify parameters + var owner = listDatabasesParams.OwnerUri; + if (String.IsNullOrEmpty(owner)) + { + throw new ArgumentException("OwnerUri cannot be null or empty"); + } + + // Use the existing connection as a base for the search + ConnectionInfo info; + if (!TryFindConnection(owner, out info)) + { + throw new Exception("Specified OwnerUri \"" + owner + "\" does not have an existing connection"); + } + ConnectionDetails connectionDetails = info.ConnectionDetails.Clone(); + + // Connect to master and query sys.databases + connectionDetails.DatabaseName = "master"; + var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails)); + connection.Open(); + + DbCommand command = connection.CreateCommand(); + command.CommandText = "SELECT name FROM sys.databases"; + command.CommandTimeout = 15; + command.CommandType = CommandType.Text; + var reader = command.ExecuteReader(); + + List results = new List(); + while (reader.Read()) + { + results.Add(reader[0].ToString()); + } + + connection.Close(); + + ListDatabasesResponse response = new ListDatabasesResponse(); + response.DatabaseNames = results.ToArray(); + + return response; + } + public void InitializeService(IProtocolEndpoint serviceHost) { // Register request and event handlers with the Service Host serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest); serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest); + serviceHost.SetRequestHandler(ListDatabasesRequest.Type, HandleListDatabasesRequest); // Register the configuration update handler WorkspaceService.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); @@ -265,6 +313,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } } + + /// + /// Handle requests to list databases on the current server + /// + protected async Task HandleListDatabasesRequest( + ListDatabasesParams listDatabasesParams, + RequestContext requestContext) + { + Logger.Write(LogLevel.Verbose, "ListDatabasesRequest"); + + try + { + ListDatabasesResponse result = ConnectionService.Instance.ListDatabases(listDatabasesParams); + await requestContext.SendResult(result); + } + catch(Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + } public Task HandleDidChangeConfigurationNotification( SqlToolsSettings newSettings, diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs new file mode 100644 index 00000000..de278dbc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs @@ -0,0 +1,27 @@ +// +// 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 +{ + /// + /// Extension methods for the ConnectionDetails contract class + /// + public static class ConnectionDetailsExtensions + { + /// + /// Create a copy of a connection details object. + /// + public static ConnectionDetails Clone(this ConnectionDetails details) + { + return new ConnectionDetails() + { + ServerName = details.ServerName, + DatabaseName = details.DatabaseName, + UserName = details.UserName, + Password = details.Password + }; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesParams.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesParams.cs new file mode 100644 index 00000000..ecc32568 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesParams.cs @@ -0,0 +1,18 @@ +// +// 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 +{ + /// + /// Parameters for the List Databases Request. + /// + public class ListDatabasesParams + { + /// + /// URI of the owner of the connection requesting the list of databases. + /// + public string OwnerUri; + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesRequest.cs new file mode 100644 index 00000000..01c12a45 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesRequest.cs @@ -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 +{ + /// + /// List databases request mapping entry + /// + public class ListDatabasesRequest + { + public static readonly + RequestType Type = + RequestType.Create("connection/listdatabases"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesResponse.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesResponse.cs new file mode 100644 index 00000000..68610803 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ListDatabasesResponse.cs @@ -0,0 +1,18 @@ +// +// 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 +{ + /// + /// Message format for the list databases response + /// + public class ListDatabasesResponse + { + /// + /// Gets or sets the list of database names. + /// + public string[] DatabaseNames { get; set; } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs index 9e3d5339..3bde10e3 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs @@ -4,12 +4,17 @@ // using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.Test.Utility; using Moq; +using Moq.Protected; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.Connection @@ -19,6 +24,33 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection /// public class ConnectionServiceTests { + /// + /// Creates a mock db command that returns a predefined result set + /// + public static DbCommand CreateTestCommand(Dictionary[][] data) + { + var commandMock = new Mock { CallBase = true }; + var commandMockSetup = commandMock.Protected() + .Setup("ExecuteDbDataReader", It.IsAny()); + + commandMockSetup.Returns(new TestDbDataReader(data)); + + return commandMock.Object; + } + + /// + /// Creates a mock db connection that returns predefined data when queried for a result set + /// + public DbConnection CreateMockDbConnection(Dictionary[][] data) + { + var connectionMock = new Mock { CallBase = true }; + connectionMock.Protected() + .Setup("CreateDbCommand") + .Returns(CreateTestCommand(data)); + + return connectionMock.Object; + } + /// /// Verify that when a connection is started for a URI with an already existing /// connection, we disconnect first before connecting. @@ -309,6 +341,55 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.False(disconnectResult); } + /// + /// Verifies the the list databases operation lists database names for the server used by a connection. + /// + [Fact] + public void ListDatabasesOnServerForCurrentConnectionReturnsDatabaseNames() + { + // Result set for the query of database names + Dictionary[] data = + { + new Dictionary { {"name", "master" } }, + new Dictionary { {"name", "model" } }, + new Dictionary { {"name", "msdb" } }, + new Dictionary { {"name", "tempdb" } }, + new Dictionary { {"name", "mydatabase" } }, + }; + + // Setup mock connection factory to inject query results + var mockFactory = new Mock(); + mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny())) + .Returns(CreateMockDbConnection(new[] {data})); + var connectionService = new ConnectionService(mockFactory.Object); + + // connect to a database instance + string ownerUri = "file://my/sample/file.sql"; + var connectionResult = + connectionService + .Connect(new ConnectParams() + { + OwnerUri = ownerUri, + Connection = TestObjects.GetTestConnectionDetails() + }); + + // verify that a valid connection id was returned + Assert.NotEmpty(connectionResult.ConnectionId); + + // list databases for the connection + ListDatabasesParams parameters = new ListDatabasesParams(); + parameters.OwnerUri = ownerUri; + var listDatabasesResult = connectionService.ListDatabases(parameters); + string[] databaseNames = listDatabasesResult.DatabaseNames; + + Assert.Equal(databaseNames.Length, 5); + Assert.Equal(databaseNames[0], "master"); + Assert.Equal(databaseNames[1], "model"); + Assert.Equal(databaseNames[2], "msdb"); + Assert.Equal(databaseNames[3], "tempdb"); + Assert.Equal(databaseNames[4], "mydatabase"); + } + /// /// Verify that the SQL parser correctly detects errors in text ///