diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CreateSessionRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CreateSessionRequest.cs index 51226097..3fa4d541 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CreateSessionRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CreateSessionRequest.cs @@ -1,45 +1,76 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - +// +// 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.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; - -namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts -{ - /// - /// Information returned from a . - /// Contains success information, a to be used when - /// requesting expansion of nodes, and a root node to display for this area. - /// - public class CreateSessionResponse - { - /// - /// Boolean indicating if the connection was successful - /// - public bool Success { get; set; } - - /// - /// Unique ID to use when sending any requests for objects in the - /// tree under the node - /// - public string SessionId { get; set; } - - /// - /// Information describing the base node in the tree - /// - public NodeInfo RootNode { get; set; } - } - /// - /// Establishes an Object Explorer tree session for a specific connection. - /// This will create a connection to a specific server or database, register - /// it for use in the - /// - public class CreateSessionRequest - { - public static readonly - RequestType Type = - RequestType.Create("objectexplorer/createsession"); - } -} +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts +{ + /// + /// Information returned from a . + /// Contains success information, a to be used when + /// requesting expansion of nodes, and a root node to display for this area. + /// + public class CreateSessionResponse + { + /// + /// Unique ID to use when sending any requests for objects in the + /// tree under the node + /// + public string SessionId { get; set; } + + } + + /// + /// Information returned from a . + /// Contains success information, a to be used when + /// requesting expansion of nodes, and a root node to display for this area. + /// + public class SessionCreatedParameters + { + /// + /// Boolean indicating if the connection was successful + /// + public bool Success { get; set; } + + /// + /// Unique ID to use when sending any requests for objects in the + /// tree under the node + /// + public string SessionId { get; set; } + + /// + /// Information describing the base node in the tree + /// + public NodeInfo RootNode { get; set; } + + + /// + /// Error message returned from the engine for a object explorer session failure reason, if any. + /// + public string ErrorMessage { get; set; } + } + /// + /// Establishes an Object Explorer tree session for a specific connection. + /// This will create a connection to a specific server or database, register + /// it for use in the + /// + public class CreateSessionRequest + { + public static readonly + RequestType Type = + RequestType.Create("objectexplorer/createsession"); + } + + /// + /// Session notification mapping entry + /// + public class CreateSessionCompleteNotification + { + public static readonly + EventType Type = + EventType.Create("objectexplorer/sessionCreated"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs index a004d172..3db9f926 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/ExpandRequest.cs @@ -1,56 +1,76 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - +// +// 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.Hosting.Protocol.Contracts; - -namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts + +namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts { - /// - /// Information returned from a . - /// + /// + /// Information returned from a . + /// public class ExpandResponse { - /// - /// Unique ID to use when sending any requests for objects in the - /// tree under the node - /// + /// + /// Unique ID to use when sending any requests for objects in the + /// tree under the node + /// public string SessionId { get; set; } - /// - /// Information describing the expanded nodes in the tree - /// + /// + /// Information describing the expanded nodes in the tree + /// public NodeInfo[] Nodes { get; set; } - } - - /// - /// Parameters to the . - /// - public class ExpandParams - { - /// - /// The Id returned from a . This - /// is used to disambiguate between different trees. - /// - public string SessionId { get; set; } - - /// - /// Path identifying the node to expand. See for details - /// - public string NodePath { get; set; } - } - - /// - /// A request to expand a - /// - public class ExpandRequest - { - /// - /// Returns children of a given node as a array. - /// - public static readonly - RequestType Type = - RequestType.Create("objectexplorer/expand"); - } -} + + /// + /// Path identifying the node to expand. See for details + /// + public string NodePath { get; set; } + + /// + /// Error message returned from the engine for a object explorer expand failure reason, if any. + /// + public string ErrorMessage { get; set; } + } + + /// + /// Parameters to the . + /// + public class ExpandParams + { + /// + /// The Id returned from a . This + /// is used to disambiguate between different trees. + /// + public string SessionId { get; set; } + + /// + /// Path identifying the node to expand. See for details + /// + public string NodePath { get; set; } + } + + /// + /// A request to expand a + /// + public class ExpandRequest + { + /// + /// Returns children of a given node as a array. + /// + public static readonly + RequestType Type = + RequestType.Create("objectexplorer/expand"); + } + + /// + /// Expand notification mapping entry + /// + public class ExpandCompleteNotification + { + public static readonly + EventType Type = + EventType.Create("objectexplorer/expandCompleted"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs index 55246bdc..c276fe08 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/NodeInfo.cs @@ -53,5 +53,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts /// Object Metadata for smo objects to be used for scripting /// public ObjectMetadata Metadata { get; set; } + + /// + /// Error message returned from the engine for a object explorer node failure reason, if any. + /// + public string ErrorMessage { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/RefreshRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/RefreshRequest.cs index c9974c6f..15fb4c20 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/RefreshRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/RefreshRequest.cs @@ -7,38 +7,11 @@ using Microsoft.SqlTools.Hosting.Protocol.Contracts; namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts { - /// - /// Information returned from a . - /// - public class RefreshResponse - { - /// - /// Unique ID to use when sending any requests for objects in the - /// tree under the node - /// - public string SessionId { get; set; } - - /// - /// Information describing the expanded nodes in the tree - /// - public NodeInfo[] Nodes { get; set; } - } - /// /// Parameters to the . /// - public class RefreshParams + public class RefreshParams: ExpandParams { - /// - /// The Id returned from a . This - /// is used to disambiguate between different trees. - /// - public string SessionId { get; set; } - - /// - /// Path identifying the node to refresh. See for details - /// - public string NodePath { get; set; } } /// @@ -50,7 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts /// Returns children of a given node as a array. /// public static readonly - RequestType Type = - RequestType.Create("objectexplorer/refresh"); + RequestType Type = + RequestType.Create("objectexplorer/refresh"); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs index b00cde92..5c208e40 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs @@ -77,6 +77,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes /// public string NodeSubType { get; set; } + /// + /// Error message returned from the engine for a object explorer node failure reason, if any. + /// + public string ErrorMessage { get; set; } + /// /// Node status - for example login can be disabled/enabled /// @@ -193,7 +198,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes NodeType = this.NodeType, Metadata = this.ObjectMetadata, NodeStatus = this.NodeStatus, - NodeSubType = this.NodeSubType + NodeSubType = this.NodeSubType, + ErrorMessage = this.ErrorMessage }; } @@ -289,8 +295,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes bool includeSystemObjects = context != null && context.Database != null ? ObjectExplorerUtils.IsSystemDatabaseConnection(context.Database.Name) : true; - if (children.IsPopulating || context == null) - return; + if (children.IsPopulating || context == null) + return; children.Clear(); BeginChildrenInit(); @@ -302,16 +308,26 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes { foreach (var factory in childFactories) { - IEnumerable items = factory.Expand(this, refresh, name, includeSystemObjects); - if (items != null) + try { - foreach (TreeNode item in items) + IEnumerable items = factory.Expand(this, refresh, name, includeSystemObjects); + if (items != null) { - children.Add(item); - item.Parent = this; + foreach (TreeNode item in items) + { + children.Add(item); + item.Parent = this; + } } } + catch (Exception ex) + { + string error = string.Format(CultureInfo.InvariantCulture, "Failed populating oe children. error:{0} inner:{1} stacktrace:{2}", + ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); + Logger.Write(LogLevel.Error, error); + ErrorMessage = ex.Message; + } } } } @@ -320,6 +336,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes string error = string.Format(CultureInfo.InvariantCulture, "Failed populating oe children. error:{0} inner:{1} stacktrace:{2}", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(LogLevel.Error, error); + ErrorMessage = ex.Message; } finally { diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index 7497add6..9f4bc96c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -103,12 +103,130 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer serviceHost.SetRequestHandler(CloseSessionRequest.Type, HandleCloseSessionRequest); } - public void CloseSession(string uri) + + + internal async Task HandleCreateSessionRequest(ConnectionDetails connectionDetails, RequestContext context) + { + Logger.Write(LogLevel.Verbose, "HandleCreateSessionRequest"); + + Func> doCreateSession = async () => + { + Validate.IsNotNull(nameof(connectionDetails), connectionDetails); + Validate.IsNotNull(nameof(context), context); + return await Task.Factory.StartNew(() => + { + string uri = GenerateUri(connectionDetails); + + return new CreateSessionResponse { SessionId = uri }; + }); + }; + + CreateSessionResponse response = await HandleRequestAsync(doCreateSession, context, "HandleCreateSessionRequest"); + if (response != null) + { + RunCreateSessionTask(connectionDetails, response.SessionId); + } + + } + + internal async Task HandleExpandRequest(ExpandParams expandParams, RequestContext context) + { + Logger.Write(LogLevel.Verbose, "HandleExpandRequest"); + + Func> expandNode = async () => + { + Validate.IsNotNull(nameof(expandParams), expandParams); + Validate.IsNotNull(nameof(context), context); + + string uri = expandParams.SessionId; + ObjectExplorerSession session = null; + NodeInfo[] nodes = null; + ExpandResponse response; + if (!sessionMap.TryGetValue(uri, out session)) + { + Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); + await serviceHost.SendEvent(ExpandCompleteNotification.Type, new ExpandResponse + { + SessionId = expandParams.SessionId, + NodePath = expandParams.NodePath, + ErrorMessage = $"Couldn't find session for session: {uri}" + }); + return false; + } + else + { + RunExpandTask(session, expandParams); + return true; + } + }; + await HandleRequestAsync(expandNode, context, "HandleExpandRequest"); + } + + internal async Task HandleRefreshRequest(RefreshParams refreshParams, RequestContext context) + { + Logger.Write(LogLevel.Verbose, "HandleRefreshRequest"); + Validate.IsNotNull(nameof(refreshParams), refreshParams); + Validate.IsNotNull(nameof(context), context); + + string uri = refreshParams.SessionId; + ObjectExplorerSession session = null; + ExpandResponse response; + if (!sessionMap.TryGetValue(uri, out session)) + { + Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); + await serviceHost.SendEvent(ExpandCompleteNotification.Type, new ExpandResponse + { + SessionId = refreshParams.SessionId, + NodePath = refreshParams.NodePath, + ErrorMessage = $"Couldn't find session for session: {uri}" + }); + } + else + { + RunExpandTask(session, refreshParams, true); + } + await context.SendResult(true); + } + + internal async Task HandleCloseSessionRequest(CloseSessionParams closeSessionParams, RequestContext context) + { + + Logger.Write(LogLevel.Verbose, "HandleCloseSessionRequest"); + Func> closeSession = () => + { + Validate.IsNotNull(nameof(closeSessionParams), closeSessionParams); + Validate.IsNotNull(nameof(context), context); + return Task.Factory.StartNew(() => + { + string uri = closeSessionParams.SessionId; + ObjectExplorerSession session = null; + bool success = false; + if (!sessionMap.TryGetValue(uri, out session)) + { + Logger.Write(LogLevel.Verbose, $"Cannot close object explorer session. Couldn't find session for uri. {uri} "); + } + + if (session != null) + { + // refresh the nodes for given node path + CloseSession(uri); + success = true; + } + + var response = new CloseSessionResponse() { Success = success, SessionId = uri }; + return response; + }); + }; + + await HandleRequestAsync(closeSession, context, "HandleCloseSessionRequest"); + } + + internal void CloseSession(string uri) { ObjectExplorerSession session; if (sessionMap.TryGetValue(uri, out session)) { - // Establish a connection to the specified server/database + // Remove the session from active sessions and disconnect sessionMap.TryRemove(session.Uri, out session); connectionService.Disconnect(new DisconnectParams() { @@ -116,43 +234,52 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer }); } } - - internal async Task HandleCreateSessionRequest(ConnectionDetails connectionDetails, RequestContext context) + + private void RunCreateSessionTask(ConnectionDetails connectionDetails, string uri) { - Logger.Write(LogLevel.Verbose, "HandleCreateSessionRequest"); - Func> doCreateSession = async () => + if (connectionDetails != null && !string.IsNullOrEmpty(uri)) { - Validate.IsNotNull(nameof(connectionDetails), connectionDetails); - Validate.IsNotNull(nameof(context), context); + Task task = CreateSessionAsync(connectionDetails, uri); + CreateSessionTask = task; + Task.Run(async () => await task); + } + } - string uri = GenerateUri(connectionDetails); + /// + /// For tests only + /// + internal Task CreateSessionTask + { + get; + private set; + } - ObjectExplorerSession session; - if (!sessionMap.TryGetValue(uri, out session)) - { - // Establish a connection to the specified server/database - session = await DoCreateSession(connectionDetails, uri); - } + private async Task CreateSessionAsync(ConnectionDetails connectionDetails, string uri) + { + ObjectExplorerSession session; + if (!sessionMap.TryGetValue(uri, out session)) + { + // Establish a connection to the specified server/database + session = await DoCreateSession(connectionDetails, uri); + } - CreateSessionResponse response; - if (session == null) + SessionCreatedParameters response; + if (session != null) + { + // Else we have a session available, response with existing session information + response = new SessionCreatedParameters { - response = new CreateSessionResponse() { Success = false }; - } - else - { - // Else we have a session available, response with existing session information - response = new CreateSessionResponse() - { - Success = true, - RootNode = session.Root.ToNodeInfo(), - SessionId = session.Uri - }; - } + Success = true, + RootNode = session.Root.ToNodeInfo(), + SessionId = uri, + ErrorMessage = session.ErrorMessage + + }; + await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); return response; - }; + } + return null; - await HandleRequestAsync(doCreateSession, context, "HandleCreateSessionRequest"); } internal async Task ExpandNode(ObjectExplorerSession session, string nodePath, bool forceRefresh = false) @@ -186,9 +313,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer connectionDetails.PersistSecurityInfo = true; ConnectParams connectParams = new ConnectParams() { OwnerUri = uri, Connection = connectionDetails }; - ConnectionCompleteParams connectionResult = await Connect(connectParams); + ConnectionCompleteParams connectionResult = await Connect(connectParams, uri); if (connectionResult == null) { + // Connection failed and notification is already sent return null; } @@ -198,7 +326,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer } - private async Task Connect(ConnectParams connectParams) + private async Task Connect(ConnectParams connectParams, string uri) { string connectionErrorMessage = string.Empty; try @@ -213,7 +341,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer else { Logger.Write(LogLevel.Warning, $"Connection Failed for OE. connection error: {connectionErrorMessage}"); - await serviceHost.SendEvent(ConnectionCompleteNotification.Type, result); + await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, new SessionCreatedParameters + { + ErrorMessage = result.ErrorMessage, + SessionId = uri + }); return null; } @@ -222,108 +354,46 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { Logger.Write(LogLevel.Warning, $"Connection Failed for OE. connection error:{connectionErrorMessage} error: {ex.Message}"); // Send a connection failed error message in this case. - ConnectionCompleteParams result = new ConnectionCompleteParams() + SessionCreatedParameters result = new SessionCreatedParameters() { - Messages = ex.ToString() + ErrorMessage = ex.ToString(), + SessionId = uri }; - await serviceHost.SendEvent(ConnectionCompleteNotification.Type, result); + await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result); return null; } } - internal async Task HandleExpandRequest(ExpandParams expandParams, RequestContext context) + private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) { - Logger.Write(LogLevel.Verbose, "HandleExpandRequest"); - Func> expandNode = async () => + Task task = ExpandNodeAsync(session, expandParams, forceRefresh); + ExpandTask = task; + Task.Run(async () => { - Validate.IsNotNull(nameof(expandParams), expandParams); - Validate.IsNotNull(nameof(context), context); - - string uri = expandParams.SessionId; - ObjectExplorerSession session = null; - NodeInfo[] nodes = null; - ExpandResponse response; - if (!sessionMap.TryGetValue(uri, out session)) - { - Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); - } - - if (session != null) - { - // expand the nodes for given node path - nodes = await ExpandNode(session, expandParams.NodePath); - } - - response = new ExpandResponse() { Nodes = nodes, SessionId = uri }; - return response; - }; - - await HandleRequestAsync(expandNode, context, "HandleExpandRequest"); + await task; + }); } - internal async Task HandleRefreshRequest(RefreshParams refreshParams, RequestContext context) + /// + /// For tests only + /// + internal Task ExpandTask { - Logger.Write(LogLevel.Verbose, "HandleRefreshRequest"); - Func> refreshNode = async () => - { - Validate.IsNotNull(nameof(refreshParams), refreshParams); - Validate.IsNotNull(nameof(context), context); - - string uri = refreshParams.SessionId; - ObjectExplorerSession session = null; - NodeInfo[] nodes = null; - RefreshResponse response; - if (!sessionMap.TryGetValue(uri, out session)) - { - Logger.Write(LogLevel.Verbose, $"Cannot refresh object explorer node. Couldn't find session for uri. {uri} "); - } - - if (session != null) - { - // refresh the nodes for given node path - nodes = await ExpandNode(session, refreshParams.NodePath, true); - } - - response = new RefreshResponse() { Nodes = nodes, SessionId = uri }; - return response; - }; - - await HandleRequestAsync(refreshNode, context, "HandleRefreshRequest"); + get; + set; } - internal async Task HandleCloseSessionRequest(CloseSessionParams closeSessionParams, RequestContext context) + private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) { - Validate.IsNotNull(nameof(closeSessionParams), closeSessionParams); - Validate.IsNotNull(nameof(context), context); - Logger.Write(LogLevel.Verbose, "HandleCloseSessionRequest"); - Func> closeSession = () => - { - return Task.Factory.StartNew(() => - { - string uri = closeSessionParams.SessionId; - ObjectExplorerSession session = null; - bool success = false; - if (!sessionMap.TryGetValue(uri, out session)) - { - Logger.Write(LogLevel.Verbose, $"Cannot close object explorer session. Couldn't find session for uri. {uri} "); - } - - if (session != null) - { - // refresh the nodes for given node path - CloseSession(uri); - success = true; - } - - var response = new CloseSessionResponse() {Success = success, SessionId = uri}; - return response; - }); - }; - - await HandleRequestAsync(closeSession, context, "HandleCloseSessionRequest"); + NodeInfo[] nodes = null; + nodes = await ExpandNode(session, expandParams.NodePath, forceRefresh); + ExpandResponse response = new ExpandResponse() { Nodes = nodes, SessionId = session.Uri, NodePath = expandParams.NodePath }; + await serviceHost.SendEvent(ExpandCompleteNotification.Type, response); } - private async Task HandleRequestAsync(Func> handler, RequestContext requestContext, string requestType) + + + private async Task HandleRequestAsync(Func> handler, RequestContext requestContext, string requestType) { Logger.Write(LogLevel.Verbose, requestType); @@ -331,11 +401,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { T result = await handler(); await requestContext.SendResult(result); + return result; } catch (Exception ex) { await requestContext.SendError(ex.ToString()); } + return default(T); } /// @@ -441,21 +513,19 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer public string Uri { get; private set; } public TreeNode Root { get; private set; } + public string ErrorMessage { get; set; } + public static ObjectExplorerSession CreateSession(ConnectionCompleteParams response, IMultiServiceProvider serviceProvider) { - TreeNode rootNode = new ServerNode(response, serviceProvider); + ServerNode rootNode = new ServerNode(response, serviceProvider); var session = new ObjectExplorerSession(response.OwnerUri, rootNode, serviceProvider, serviceProvider.GetService()); if (!ObjectExplorerUtils.IsSystemDatabaseConnection(response.ConnectionSummary.DatabaseName)) { // Assuming the databases are in a folder under server node - var children = rootNode.Expand(); - var databasesRoot = children.FirstOrDefault(x => x.NodeTypeId == NodeTypes.Databases); - var databasesChildren = databasesRoot.Expand(response.ConnectionSummary.DatabaseName); - var databases = databasesChildren.Where(x => x.NodeType == NodeTypes.Database.ToString()); - var databaseNode = databases.FirstOrDefault(d => d.Label == response.ConnectionSummary.DatabaseName); - databaseNode.Label = rootNode.Label; + DatabaseTreeNode databaseNode = new DatabaseTreeNode(rootNode, response.ConnectionSummary.DatabaseName); session.Root = databaseNode; } + return session; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs index d31c5db7..bd12bcaf 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs @@ -10,6 +10,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel { internal partial class DatabaseTreeNode { + public DatabaseTreeNode(ServerNode serverNode, string databaseName): this() + { + Parent = serverNode; + NodeValue = databaseName; + Database db = new Database(serverNode.GetContextAs().Server, this.NodeValue); + db.Refresh(); + CacheInfoFromModel(db); + } /// /// Initializes the context and ensures that diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs index d22c5720..839bffe1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoChildFactoryBase.cs @@ -42,6 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel string error = string.Format(CultureInfo.InvariantCulture, "Failed expanding oe children. parent:{0} error:{1} inner:{2} stacktrace:{3}", parent != null ? parent.GetNodePath() : "", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(LogLevel.Error, error); + throw ex; } finally { @@ -97,7 +98,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel string propertyFilter = GetProperyFilter(filters, querier.GetType(), validForFlag); try { - foreach (var smoObject in querier.Query(context, propertyFilter, refresh)) + var smoObjectList = querier.Query(context, propertyFilter, refresh).ToList(); + foreach (var smoObject in smoObjectList) { if (smoObject == null) { @@ -116,6 +118,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel string error = string.Format(CultureInfo.InvariantCulture, "Failed getting smo objects. parent:{0} querier: {1} error:{2} inner:{3} stacktrace:{4}", parent != null ? parent.GetNodePath() : "", querier.GetType(), ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(LogLevel.Error, error); + throw ex; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoQuerier.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoQuerier.cs index 79004da3..05d64440 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoQuerier.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoQuerier.cs @@ -83,7 +83,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel string urn = string.Empty; try { - string parentUrn = smoObject.Urn; + string parentUrn = smoObject.Urn != null ? smoObject.Urn.Value : string.Empty; urn = parentUrn != null ? $"{parentUrn.ToString()}/{objectName}" + filter : string.Empty; if (!string.IsNullOrEmpty(urn)) @@ -102,6 +102,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel string error = string.Format(CultureInfo.InvariantCulture, "Failed getting urns. error:{0} inner:{1} stacktrace:{2}", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(LogLevel.Error, error); + throw ex; } return urns; diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs index 3bf70273..fc8bd2bd 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ObjectExplorer/ObjectExplorerServiceTests.cs @@ -50,8 +50,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer { // Given the connection service fails to connect ConnectionDetails details = TestObjects.GetTestConnectionDetails(); - ConnectionCompleteParams completeParams = null; - serviceHostMock.AddEventHandling(ConnectionCompleteNotification.Type, (et, p) => completeParams = p); string expectedExceptionText = "Error!!!"; connectionServiceMock.Setup(c => c.Connect(It.IsAny())) @@ -59,21 +57,19 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer // when creating a new session // then expect the create session request to return false - await RunAndVerify( - test: (requestContext) => service.HandleCreateSessionRequest(details, requestContext), + await RunAndVerify( + test: (requestContext) => CallCreateSession(details, requestContext), verify: (actual => { - Assert.False(actual.Success); - Assert.Null(actual.SessionId); - Assert.Null(actual.RootNode); + Assert.NotNull(actual.SessionId); + Assert.NotNull(actual); + Assert.True(actual.ErrorMessage.Contains(expectedExceptionText)); })); // And expect error notification to be sent - serviceHostMock.Verify(x => x.SendEvent(ConnectionCompleteNotification.Type, It.IsAny()), Times.Once()); - Assert.NotNull(completeParams); - Assert.True(completeParams.Messages.Contains(expectedExceptionText)); + serviceHostMock.Verify(x => x.SendEvent(CreateSessionCompleteNotification.Type, It.IsAny()), Times.Once()); } - + [Fact] public async Task CreateSessionRequestWithMasterConnectionReturnsServerSuccessAndNodeInfo() @@ -138,10 +134,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer NodePath = "Any path" }; + // when expanding // then expect the nodes are server children - await RunAndVerify( - test: (requestContext) => service.HandleExpandRequest(expandParams, requestContext), + await RunAndVerify( + test: (requestContext) => CallServiceExpand(expandParams, requestContext), verify: (actual => { Assert.Equal(actual.SessionId, expandParams.SessionId); @@ -160,8 +157,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer // when expanding // then expect the nodes are server children - await RunAndVerify( - test: (requestContext) => service.HandleRefreshRequest(expandParams, requestContext), + await RunAndVerify( + test: (requestContext) => CallServiceRefresh(expandParams, requestContext), verify: (actual => { Assert.Equal(actual.SessionId, expandParams.SessionId); @@ -179,8 +176,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer // when expanding // then expect the nodes are server children - await RunAndVerify( - test: (requestContext) => service.HandleCloseSessionRequest(closeSessionParamsparams, requestContext), + await RunAndVerify( + test: (requestContext) => CallCloseSession(closeSessionParamsparams, requestContext), verify: (actual => { Assert.Equal(actual.SessionId, closeSessionParamsparams.SessionId); @@ -199,8 +196,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer // when expanding // then expect the nodes are server children - await RunAndVerify( - test: (requestContext) => service.HandleCloseSessionRequest(closeSessionParamsparams, requestContext), + await RunAndVerify( + test: (requestContext) => CallCloseSession(closeSessionParamsparams, requestContext), verify: (actual => { Assert.Equal(actual.SessionId, closeSessionParamsparams.SessionId); @@ -211,7 +208,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer connectionServiceMock.Verify(c => c.Disconnect(It.IsAny())); } - private async Task CreateSession() + private async Task CreateSession() { ConnectionDetails details = new ConnectionDetails() { @@ -221,9 +218,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer ServerName = "serverName" }; - serviceHostMock.AddEventHandling(ConnectionCompleteNotification.Type, null); + SessionCreatedParameters sessionResult = null; + serviceHostMock.AddEventHandling(CreateSessionCompleteNotification.Type, (et, p) => sessionResult = p); CreateSessionResponse result = default(CreateSessionResponse); var contextMock = RequestContextMocks.Create(r => result = r).AddErrorHandling(null); + connectionServiceMock.Setup(c => c.Connect(It.IsAny())) .Returns((ConnectParams connectParams) => Task.FromResult(GetCompleteParamsForConnection(connectParams.OwnerUri, details))); @@ -236,8 +235,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer connectionServiceMock.Setup(c => c.Disconnect(It.IsAny())).Returns(true); await service.HandleCreateSessionRequest(details, contextMock.Object); + await service.CreateSessionTask; - return result; + return sessionResult; } private async Task ExpandAndVerifyServerNodes() @@ -251,8 +251,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer // when expanding // then expect the nodes are server children - await RunAndVerify( - test: (requestContext) => service.HandleExpandRequest(expandParams, requestContext), + await RunAndVerify( + test: (requestContext) => CallServiceExpand(expandParams, requestContext), verify: (actual => { Assert.Equal(actual.SessionId, session.SessionId); @@ -272,8 +272,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer // when expanding // then expect the nodes are server children - await RunAndVerify( - test: (requestContext) => service.HandleRefreshRequest(expandParams, requestContext), + await RunAndVerify( + test: (requestContext) => CallServiceRefresh(expandParams, requestContext), verify: (actual => { Assert.Equal(actual.SessionId, session.SessionId); @@ -282,17 +282,75 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer })); } + private async Task CallServiceRefresh(RefreshParams expandParams, RequestContext requestContext) + { + ExpandResponse result = null; + serviceHostMock.AddEventHandling(ExpandCompleteNotification.Type, (et, p) => result = p); + + await service.HandleRefreshRequest(expandParams, requestContext); + Task task = service.ExpandTask; + if (task != null) + { + await task; + } + + return result; + + } + + private async Task CallServiceExpand(ExpandParams expandParams, RequestContext requestContext) + { + ExpandResponse result = null; + serviceHostMock.AddEventHandling(ExpandCompleteNotification.Type, (et, p) => result = p); + + await service.HandleExpandRequest(expandParams, requestContext); + Task task = service.ExpandTask; + if (task != null) + { + await task; + } + return result; + } + + private async Task CallCreateSession(ConnectionDetails connectionDetails, RequestContext context) + { + SessionCreatedParameters result = null; + serviceHostMock.AddEventHandling(CreateSessionCompleteNotification.Type, (et, p) => result = p); + + await service.HandleCreateSessionRequest(connectionDetails, context); + Task task = service.CreateSessionTask; + if (task != null) + { + await task; + } + return result; + } + + private async Task CallCloseSession(CloseSessionParams closeSessionParams, RequestContext context) + { + SessionCreatedParameters result = null; + serviceHostMock.AddEventHandling(CreateSessionCompleteNotification.Type, (et, p) => result = p); + + await service.HandleCloseSessionRequest(closeSessionParams, context); + return null; + } + private async Task CreateSessionRequestAndVerifyServerNodeHelper(ConnectionDetails details) { serviceHostMock.AddEventHandling(ConnectionCompleteNotification.Type, null); + //SessionCreatedParameters sessionResult + connectionServiceMock.Setup(c => c.Connect(It.IsAny())) .Returns((ConnectParams connectParams) => Task.FromResult(GetCompleteParamsForConnection(connectParams.OwnerUri, details))); // when creating a new session // then expect the create session request to return false - await RunAndVerify( - test: (requestContext) => service.HandleCreateSessionRequest(details, requestContext), + await RunAndVerify( + test: (requestContext) => + { + return CallCreateSession(details, requestContext); + }, verify: (actual => { Assert.True(actual.Success); @@ -339,15 +397,19 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer }; } - private async Task RunAndVerify(Func, Task> test, Action verify) + private async Task RunAndVerify(Func, Task> test, Action verify) { T result = default(T); var contextMock = RequestContextMocks.Create(r => result = r).AddErrorHandling(null); - await test(contextMock.Object); - VerifyResult(contextMock, verify, result); + TResult actualResult = await test(contextMock.Object); + if (actualResult == null && typeof(TResult) == typeof(T)) + { + actualResult = (TResult)Convert.ChangeType(result, typeof(TResult)); + } + VerifyResult(contextMock, verify, actualResult); } - private void VerifyResult(Mock> contextMock, Action verify, T actual) + private void VerifyResult(Mock> contextMock, Action verify, TResult actual) { contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); contextMock.Verify(c => c.SendError(It.IsAny(), It.IsAny()), Times.Never); @@ -359,6 +421,5 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Never); contextMock.Verify(c => c.SendError(It.IsAny(), It.IsAny()), Times.Once); } - } }