Support Object Explorer FindNodes request (#589)

This commit is contained in:
Matt Irvine
2018-03-15 10:47:52 -07:00
committed by GitHub
parent d36efb578c
commit 365fe2282e
8 changed files with 584 additions and 33 deletions

View File

@@ -0,0 +1,55 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts
{
/// <summary>
/// Information returned from a <see cref="FindNodesRequest"/>.
/// </summary>
public class FindNodesResponse
{
/// <summary>
/// Information describing the matching nodes in the tree
/// </summary>
public List<NodeInfo> Nodes { get; set; }
}
/// <summary>
/// Parameters to the <see cref="FindNodesRequest"/>.
/// </summary>
public class FindNodesParams
{
/// <summary>
/// The Id returned from a <see cref="CreateSessionRequest"/>. This
/// is used to disambiguate between different trees.
/// </summary>
public string SessionId { get; set; }
public string Type { get; set; }
public string Schema { get; set; }
public string Name { get; set; }
public string Database { get; set; }
public List<string> ParentObjectNames { get; set; }
}
/// <summary>
/// TODO
/// </summary>
public class FindNodesRequest
{
public static readonly
RequestType<FindNodesParams, FindNodesResponse> Type =
RequestType<FindNodesParams, FindNodesResponse>.Create("objectexplorer/findnodes");
}
}

View File

@@ -181,7 +181,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
nodePath = path;
}
public TreeNode FindNodeByPath(string path)
public TreeNode FindNodeByPath(string path, bool refreshChildren = false)
{
TreeNode nodeForPath = ObjectExplorerUtils.FindNode(this, node =>
{
@@ -189,7 +189,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
}, nodeToFilter =>
{
return path.StartsWith(nodeToFilter.GetNodePath());
});
}, refreshChildren);
return nodeForPath;
}

View File

@@ -47,7 +47,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(needsMetadata: false);
private string connectionName = "ObjectExplorer";
/// <summary>
/// This timeout limits the amount of time that object explorer tasks can take to complete
/// </summary>
@@ -60,6 +59,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
{
sessionMap = new ConcurrentDictionary<string, ObjectExplorerSession>();
applicableNodeChildFactories = new Lazy<Dictionary<string, HashSet<ChildFactory>>>(() => PopulateFactories());
NodePathGenerator.Initialize();
}
internal ConnectedBindingQueue ConnectedBindingQueue
@@ -136,6 +136,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
serviceHost.SetRequestHandler(ExpandRequest.Type, HandleExpandRequest);
serviceHost.SetRequestHandler(RefreshRequest.Type, HandleRefreshRequest);
serviceHost.SetRequestHandler(CloseSessionRequest.Type, HandleCloseSessionRequest);
serviceHost.SetRequestHandler(FindNodesRequest.Type, HandleFindNodesRequest);
WorkspaceService<SqlToolsSettings> workspaceService = WorkspaceService;
if (workspaceService != null)
{
@@ -293,6 +294,16 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
await HandleRequestAsync(closeSession, context, "HandleCloseSessionRequest");
}
internal async Task HandleFindNodesRequest(FindNodesParams findNodesParams, RequestContext<FindNodesResponse> context)
{
var foundNodes = FindNodes(findNodesParams.SessionId, findNodesParams.Type, findNodesParams.Schema, findNodesParams.Name, findNodesParams.Database, findNodesParams.ParentObjectNames);
if (foundNodes == null)
{
foundNodes = new List<TreeNode>();
}
await context.SendResult(new FindNodesResponse { Nodes = foundNodes.Select(node => node.ToNodeInfo()).ToList() });
}
internal void CloseSession(string uri)
{
ObjectExplorerSession session;
@@ -689,6 +700,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
applicableFactories.Add(factory);
}
/// <summary>
/// Find all tree nodes matching the given node information
/// </summary>
/// <param name="sessionId">The ID of the object explorer session to find nodes for</param>
/// <param name="typeName">The requested node type</param>
/// <param name="schema">The schema for the requested object, or null if not applicable</param>
/// <param name="name">The name of the requested object</param>
/// <param name="databaseName">The name of the database containing the requested object, or null if not applicable</param>
/// <param name="parentNames">The name of any other parent objects in the object explorer tree, from highest in the tree to lowest</param>
/// <returns>A list of nodes matching the given information, or an empty list if no nodes match</returns>
public List<TreeNode> FindNodes(string sessionId, string typeName, string schema, string name, string databaseName, List<string> parentNames = null)
{
var nodes = new List<TreeNode>();
var oeSession = sessionMap.GetValueOrDefault(sessionId);
if (oeSession == null)
{
return nodes;
}
var outputPaths = NodePathGenerator.FindNodePaths(oeSession, typeName, schema, name, databaseName, parentNames);
foreach (var outputPath in outputPaths)
{
var treeNode = oeSession.Root.FindNodeByPath(outputPath, true);
if (treeNode != null)
{
nodes.Add(treeNode);
}
}
return nodes;
}
internal class ObjectExplorerTaskResult
{
public bool IsCompleted { get; set; }

View File

@@ -41,37 +41,38 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
return VisitChildAndParents(child.Parent, visitor);
}
/// <summary>
/// Finds a node by traversing the tree starting from the given node through all the children
/// </summary>
/// <param name="node">node to start traversing at</param>
/// <summary>
/// Finds a node by traversing the tree starting from the given node through all the children
/// </summary>
/// <param name="node">node to start traversing at</param>
/// <param name="condition">Predicate function that accesses the tree and
/// determines whether to stop going further up the tree</param>
/// <param name="filter">Predicate function to filter the children when traversing</param>
/// determines whether to stop going further up the tree</param>
/// <param name="filter">Predicate function to filter the children when traversing</param>
/// <returns>A Tree Node that matches the condition</returns>
public static TreeNode FindNode(TreeNode node, Predicate<TreeNode> condition, Predicate<TreeNode> filter)
{
if(node == null)
{
return null;
}
if (condition(node))
{
return node;
}
foreach (var child in node.GetChildren())
{
if (filter != null && filter(child))
{
TreeNode childNode = FindNode(child, condition, filter);
if (childNode != null)
{
return childNode;
}
}
}
return null;
}
public static TreeNode FindNode(TreeNode node, Predicate<TreeNode> condition, Predicate<TreeNode> filter, bool refreshChildren = false)
{
if(node == null)
{
return null;
}
if (condition(node))
{
return node;
}
var children = refreshChildren && !node.IsAlwaysLeaf ? node.Refresh() : node.GetChildren();
foreach (var child in children)
{
if (filter != null && filter(child))
{
TreeNode childNode = FindNode(child, condition, filter, refreshChildren);
if (childNode != null)
{
return childNode;
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,268 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
public class NodePathGenerator
{
private static ServerExplorerTree TreeRoot { get; set; }
private static Dictionary<string, HashSet<Node>> NodeTypeDictionary { get; set; }
internal static void Initialize()
{
if (TreeRoot != null)
{
return;
}
var assembly = typeof(ObjectExplorerService).Assembly;
var resource = assembly.GetManifestResourceStream("Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel.TreeNodeDefinition.xml");
var serializer = new XmlSerializer(typeof(ServerExplorerTree));
NodeTypeDictionary = new Dictionary<string, HashSet<Node>>();
using (var reader = new StreamReader(resource))
{
TreeRoot = (ServerExplorerTree)serializer.Deserialize(reader);
}
foreach (var node in TreeRoot.Nodes)
{
var containedType = node.ContainedType();
if (containedType != null && node.Label() != string.Empty)
{
if (!NodeTypeDictionary.ContainsKey(containedType))
{
NodeTypeDictionary.Add(containedType, new HashSet<Node>());
}
NodeTypeDictionary.GetValueOrDefault(containedType).Add(node);
}
}
var serverNode = TreeRoot.Nodes.FirstOrDefault(node => node.Name == "Server");
var serverSet = new HashSet<Node>();
serverSet.Add(serverNode);
NodeTypeDictionary.Add("Server", serverSet);
}
internal static HashSet<string> FindNodePaths(ObjectExplorerService.ObjectExplorerSession objectExplorerSession, string typeName, string schema, string name, string databaseName, List<string> parentNames = null)
{
if (TreeRoot == null)
{
Initialize();
}
var returnSet = new HashSet<string>();
var matchingNodes = NodeTypeDictionary.GetValueOrDefault(typeName);
if (matchingNodes == null)
{
return returnSet;
}
var path = name;
if (schema != null)
{
path = schema + "." + path;
}
if (path == null)
{
path = "";
}
foreach (var matchingNode in matchingNodes)
{
var paths = GenerateNodePath(objectExplorerSession, matchingNode, databaseName, parentNames, path);
foreach (var newPath in paths)
{
returnSet.Add(newPath);
}
}
return returnSet;
}
private static HashSet<string> GenerateNodePath(ObjectExplorerService.ObjectExplorerSession objectExplorerSession, Node currentNode, string databaseName, List<string> parentNames, string path)
{
if (parentNames != null)
{
parentNames = parentNames.ToList();
}
if (currentNode.Name == "Server" || (currentNode.Name == "Database" && objectExplorerSession.Root.NodeType == "Database"))
{
var serverRoot = objectExplorerSession.Root;
if (objectExplorerSession.Root.NodeType == "Database")
{
serverRoot = objectExplorerSession.Root.Parent;
path = objectExplorerSession.Root.NodeValue + (path.Length > 0 ? ("/" + path) : "");
}
path = serverRoot.NodeValue + (path.Length > 0 ? ("/" + path) : "");
var returnSet = new HashSet<string>();
returnSet.Add(path);
return returnSet;
}
var currentLabel = currentNode.Label();
if (currentLabel != string.Empty)
{
path = currentLabel + "/" + path;
var returnSet = new HashSet<string>();
foreach (var parent in currentNode.ParentNodes())
{
var paths = GenerateNodePath(objectExplorerSession, parent, databaseName, parentNames, path);
foreach (var newPath in paths)
{
returnSet.Add(newPath);
}
}
return returnSet;
}
else
{
var returnSet = new HashSet<string>();
if (currentNode.ContainedType() == "Database")
{
path = databaseName + "/" + path;
}
else if (parentNames != null && parentNames.Count > 0)
{
var parentName = parentNames.Last();
parentNames.RemoveAt(parentNames.Count - 1);
path = parentName + "/" + path;
}
else
{
return returnSet;
}
foreach (var parentNode in currentNode.ParentNodes())
{
var newPaths = GenerateNodePath(objectExplorerSession, parentNode, databaseName, parentNames, path);
foreach (var newPath in newPaths)
{
returnSet.Add(newPath);
}
}
return returnSet;
}
}
[XmlRoot("ServerExplorerTree")]
public class ServerExplorerTree
{
[XmlElement("Node", typeof(Node))]
public List<Node> Nodes { get; set; }
public Node GetNode(string name)
{
foreach (var node in this.Nodes)
{
if (node.Name == name)
{
return node;
}
}
return null;
}
}
public class Node
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string LocLabel { get; set; }
[XmlAttribute]
public string TreeNode { get; set; }
[XmlAttribute]
public string NodeType { get; set; }
[XmlElement("Child", typeof(Child))]
public List<Child> Children { get; set; }
public HashSet<Node> ChildFolders()
{
var childSet = new HashSet<Node>();
foreach (var child in this.Children)
{
var node = TreeRoot.GetNode(child.Name);
if (node != null)
{
childSet.Add(node);
}
}
return childSet;
}
public string ContainedType()
{
if (this.TreeNode != null)
{
return this.TreeNode.Replace("TreeNode", "");
}
else if (this.NodeType != null)
{
return this.NodeType;
}
return null;
}
public Node ContainedObject()
{
var containedType = this.ContainedType();
if (containedType == null)
{
return null;
}
var containedNode = TreeRoot.GetNode(containedType);
if (containedNode == this)
{
return null;
}
return containedNode;
}
public string Label()
{
if (this.LocLabel.StartsWith("SR."))
{
return SR.Keys.GetString(this.LocLabel.Remove(0, 3));
}
return string.Empty;
}
public HashSet<Node> ParentNodes()
{
var parentNodes = new HashSet<Node>();
foreach (var node in TreeRoot.Nodes)
{
if (this != node && (node.ContainedType() == this.Name || node.Children.Any(child => child.Name == this.Name)))
{
parentNodes.Add(node);
}
}
return parentNodes;
}
}
public class Child
{
[XmlAttribute]
public string Name { get; set; }
}
}
}