mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-29 17:24:34 -05:00
Added new Kusto ServiceLayer (#1009)
* Copy smoModel some rename * Copy entire service layer * Building copy * Fixing some references * Launch profile * Resolve namespace issues * Compiling tests. Correct manifest. * Fixing localization resources * ReliableKustoClient * Some trimming of extra code and Kusto code * Kusto client creation in bindingContent * Removing Smo and new Kusto classes * More trimming * Kusto schema hookup * Solidying DataSource abstraction * Solidifying further * Latest refatoring * More refactoring * Building and launching Kusto service layer * Working model which enumerates databases * Refactoring to pass IDataSource to all tree nodes * Removing some dependencies on the context * Working with tables and schema * Comment checkin * Refactoring to give out select script * Query created and sent back to ADS * Fix query generation * Fix listing of databases * Tunneling the query through. * Successful query execution * Return only results table * Deleting Cms * Delete DacFx * Delete SchemaCompare and TaskServices * Change build definition to not stop at launch * Fix error after merge * Save Kusto results in different formats (#935) * save results as csv etc * some fixes Co-authored-by: Monica Gupta <mogupt@microsoft.com> * 2407 Added OrderBy clause in KustoDataSource > GetDatabaseMetaData and GetColumnMetadata (#959) * 2405 Defaulted Options when setting ServerInfo in ConnectionService > GetConnectionCompleteParams (#965) * 2747 Fixed IsUnknownType error for Kusto (#989) * 2747 Removed unused directives in Kusto > DbColumnWrapper. Refactored IsUnknownType to handle null DataTypeName * 2747 Reverted IsUnknownType change in DbColumnWrapper. Changed DataTypeName to get calue from ColumnType. Refactored SafeGetValue to type check before hard casting to reduce case exceptions. * Added EmbeddedResourceUseDependentUponConvention to Microsoft.Kusto.ServiceLayer.csproj. Also renamed DACfx to match Microsoft.SqlTools.ServiceLayer. Added to compile Exclude="**/obj/**/*.cs" * Srahman cleanup sql code (#992) * Removed Management and Security Service Code. * Remove FileBrowser service * Comment why we are using SqlServer library * Remove SQL specific type definitions * clean up formatter service (#996) Co-authored-by: Monica Gupta <mogupt@microsoft.com> * Code clean up and Kusto intellisense (#994) * Code clean up and Kusto intellisense * Addressed few comments * Addressed few comments * addressed comments Co-authored-by: Monica Gupta <mogupt@microsoft.com> * Return multiple tables for Kusto * Changes required for Kusto manage dashboard (#1039) * Changes required for manage dashboard * Addressed comments Co-authored-by: Monica Gupta <mogupt@microsoft.com> * 2728 Kusto function support (#1038) * loc update (#914) * loc update * loc updates * 2728 moved ColumnInfo and KustoResultsReader to separate files. Added Folder and Function to TreeNode.cs * 2728 Added FunctionInfo. Added Folder to ColumnInfo. Removed partial class from KustoResultsReader. Set Function.IsAlwaysLeaf=true in TreeNode.cs. In KustoDataSource changed tableMetadata type to TableMetaData. Added folder and function dictionaries. Refactored GetSchema function. Renamed GenerateColumnMetadataKey to GenerateMetadataKey * 2728 Added FunctionInfo. Added Folder to ColumnInfo. Removed partial class from KustoResultsReader. Set Function.IsAlwaysLeaf=true in TreeNode.cs. In KustoDataSource changed tableMetadata type to TableMetaData. Added folder and function dictionaries. Refactored GetSchema function. Renamed GenerateColumnMetadataKey to GenerateMetadataKey * 2728 Created new SqlConnection within using block. Refactored KustoDataSource > columnmetadata to sort on get instead of insert. * 2728 Added GetFunctionInfo function to KustoDataSource. * 2728 Reverted change to Microsoft.Kusto.ServiceLayer.csproj from merge * 2728 Reverted change to SqlTools.ServiceLayer\Localization\transXliff * 2728 Reverted change to sr.de.xlf and sr.zh-hans.xlf * 2728 Refactored KustoDataSource Function folders to support subfolders * 2728 Refactored KustoDataSource to use urn for folders, functions, and tables instead of name. * Merge remote-tracking branch 'origin/main' into feature-ADE # Conflicts: # Packages.props * 2728 Moved metadata files into Metadata subdirectory. Added GenerateAlterFunction to IDataSource and DataSourceBase. * 2728 Added summary information to SafeAdd in SystemExtensions. Renamed local variable in SetTableMetadata * 2728 Moved SafeAdd from SystemExtensions to KustoQueryUtils. Added check when getting database schema to return existing records before querying again. Added AddRange function to KustoQueryUtils. Created SetFolderMetadataForFunctions method. * 2728 Added DatabaseKeyPrefix to only return tables to a database for the dashboard. Added logic to store all database tables within the tableMetadata dictionary for the dashboard. * 2728 Created TableInfo and moved info objects into Models directory. Refactored KustoDataSource to lazy load columns for tables. Refactored logic to load tables using cslschema instead of schema. * 2728 Renamed LoadColumnSchema to GetTableSchema to be consistent. Co-authored-by: khoiph1 <khoiph@microsoft.com> * Addressed comments Co-authored-by: Shafiq Rahman <srahman@microsoft.com> Co-authored-by: Monica Gupta <mogupt@microsoft.com> Co-authored-by: Justin M <63619224+JustinMDotNet@users.noreply.github.com> Co-authored-by: rkselfhost <rkselfhost@outlook.com> Co-authored-by: khoiph1 <khoiph@microsoft.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// 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.Kusto.ServiceLayer.Connection.Contracts;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Information returned from a <see cref="CloseSessionRequest"/>.
|
||||
/// Contains success information, a <see cref="SessionId"/> to be used when
|
||||
/// requesting closing an existing session.
|
||||
/// </summary>
|
||||
public class CloseSessionResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean indicating if the session was closed successfully
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique ID to use when sending any requests for objects in the
|
||||
/// tree under the node
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to the <see cref="CloseSessionRequest"/>.
|
||||
/// </summary>
|
||||
public class CloseSessionParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The Id returned from a <see cref="CreateSessionRequest"/>. This
|
||||
/// is used to disambiguate between different trees.
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information returned when a session is disconnected.
|
||||
/// Contains success information and a <see cref="SessionId"/>
|
||||
/// </summary>
|
||||
public class SessionDisconnectedParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean indicating if the connection was successful
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique ID to use when sending any requests for objects in the
|
||||
/// tree under the node
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message returned from the engine for a object explorer session failure reason, if any.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public class CloseSessionRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<CloseSessionParams, CloseSessionResponse> Type =
|
||||
RequestType<CloseSessionParams, CloseSessionResponse>.Create("objectexplorer/closesession");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Session disconnected notification
|
||||
/// </summary>
|
||||
public class SessionDisconnectedNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<SessionDisconnectedParameters> Type =
|
||||
EventType<SessionDisconnectedParameters>.Create("objectexplorer/sessiondisconnected");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// 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.Kusto.ServiceLayer.Connection.Contracts;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Information returned from a <see cref="CreateSessionRequest"/>.
|
||||
/// Contains success information, a <see cref="SessionId"/> to be used when
|
||||
/// requesting expansion of nodes, and a root node to display for this area.
|
||||
/// </summary>
|
||||
public class CreateSessionResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique ID to use when sending any requests for objects in the
|
||||
/// tree under the node
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information returned from a <see cref="CreateSessionRequest"/>.
|
||||
/// Contains success information, a <see cref="SessionId"/> to be used when
|
||||
/// requesting expansion of nodes, and a root node to display for this area.
|
||||
/// </summary>
|
||||
public class SessionCreatedParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean indicating if the connection was successful
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique ID to use when sending any requests for objects in the
|
||||
/// tree under the node
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information describing the base node in the tree
|
||||
/// </summary>
|
||||
public NodeInfo RootNode { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Error message returned from the engine for a object explorer session failure reason, if any.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public class CreateSessionRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<ConnectionDetails, CreateSessionResponse> Type =
|
||||
RequestType<ConnectionDetails, CreateSessionResponse>.Create("objectexplorer/createsession");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Session notification mapping entry
|
||||
/// </summary>
|
||||
public class CreateSessionCompleteNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<SessionCreatedParameters> Type =
|
||||
EventType<SessionCreatedParameters>.Create("objectexplorer/sessioncreated");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// 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.Kusto.ServiceLayer.ObjectExplorer.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Information returned from a <see cref="ExpandRequest"/>.
|
||||
/// </summary>
|
||||
public class ExpandResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique ID to use when sending any requests for objects in the
|
||||
/// tree under the node
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information describing the expanded nodes in the tree
|
||||
/// </summary>
|
||||
public NodeInfo[] Nodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path identifying the node to expand. See <see cref="NodeInfo.NodePath"/> for details
|
||||
/// </summary>
|
||||
public string NodePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message returned from the engine for a object explorer expand failure reason, if any.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to the <see cref="ExpandRequest"/>.
|
||||
/// </summary>
|
||||
public class ExpandParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The Id returned from a <see cref="CreateSessionRequest"/>. This
|
||||
/// is used to disambiguate between different trees.
|
||||
/// </summary>
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path identifying the node to expand. See <see cref="NodeInfo.NodePath"/> for details
|
||||
/// </summary>
|
||||
public string NodePath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A request to expand a
|
||||
/// </summary>
|
||||
public class ExpandRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns children of a given node as a <see cref="NodeInfo"/> array.
|
||||
/// </summary>
|
||||
public static readonly
|
||||
RequestType<ExpandParams, bool> Type =
|
||||
RequestType<ExpandParams, bool>.Create("objectexplorer/expand");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expand notification mapping entry
|
||||
/// </summary>
|
||||
public class ExpandCompleteNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<ExpandResponse> Type =
|
||||
EventType<ExpandResponse>.Create("objectexplorer/expandCompleted");
|
||||
}
|
||||
}
|
||||
@@ -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.Kusto.ServiceLayer.Connection.Contracts;
|
||||
|
||||
namespace Microsoft.Kusto.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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Information describing a Node in the Object Explorer tree.
|
||||
/// Contains information required to display the Node to the user and
|
||||
/// to know whether actions such as expanding children is possible
|
||||
/// the node
|
||||
/// </summary>
|
||||
public class NodeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Path identifying this node: for example a table will be at ["server", "database", "tables", "tableName"].
|
||||
/// This enables rapid navigation of the tree without the need for a global registry of elements.
|
||||
/// The path functions as a unique ID and is used to disambiguate the node when sending requests for expansion.
|
||||
/// A common ID is needed since processes do not share address space and need a unique identifier
|
||||
/// </summary>
|
||||
public string NodePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the node - for example Server, Database, Folder, Table
|
||||
/// </summary>
|
||||
public string NodeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Label to display to the user, describing this node.
|
||||
/// </summary>
|
||||
public string Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Node Sub type - for example a key can have type as "Key" and sub type as "PrimaryKey"
|
||||
/// </summary>
|
||||
public string NodeSubType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Node status - for example login can be disabled/enabled
|
||||
/// </summary>
|
||||
public string NodeStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this a leaf node (in which case no children can be generated) or
|
||||
/// is it expandable?
|
||||
/// </summary>
|
||||
public bool IsLeaf { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object Metadata for smo objects to be used for scripting
|
||||
/// </summary>
|
||||
public DataSourceObjectMetadata Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message returned from the engine for a object explorer node failure reason, if any.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// 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.Kusto.ServiceLayer.ObjectExplorer.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters to the <see cref="ExpandRequest"/>.
|
||||
/// </summary>
|
||||
public class RefreshParams: ExpandParams
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A request to expand a
|
||||
/// </summary>
|
||||
public class RefreshRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns children of a given node as a <see cref="NodeInfo"/> array.
|
||||
/// </summary>
|
||||
public static readonly
|
||||
RequestType<RefreshParams, bool> Type =
|
||||
RequestType<RefreshParams, bool>.Create("objectexplorer/refresh");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// 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.Threading;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ChildFactory"/> supports creation of <see cref="TreeNode"/> children
|
||||
/// for a class of objects in the tree. The
|
||||
/// </summary>
|
||||
public abstract class ChildFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The set of applicable parents for which the factory can create children.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the string names for each <see cref="TreeNode.NodeType"/> that
|
||||
/// this factory can create children for
|
||||
/// </returns>
|
||||
public abstract IEnumerable<string> ApplicableParents();
|
||||
|
||||
/// <summary>
|
||||
/// Expands an element in the
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent Node</param>
|
||||
/// <param name="refresh">force to refresh</param>
|
||||
/// <param name="refresh">name of the sql object to filter</param>
|
||||
/// <returns></returns>
|
||||
public abstract IEnumerable<TreeNode> Expand(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// The list of filters that should be applied on the smo object list
|
||||
/// </summary>
|
||||
public abstract IEnumerable<NodeFilter> Filters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of properties to be loaded with the object
|
||||
/// </summary>
|
||||
public abstract IEnumerable<NodeSmoProperty> SmoProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node sub type if the object can have sub types otehr wise returns empty string
|
||||
/// </summary>
|
||||
public abstract string GetNodeSubType(object objectMetadata, QueryContext oeContext);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the status of the object assigned to node. If the object doesn't spport status returns empty string
|
||||
/// </summary>
|
||||
public abstract string GetNodeStatus(object objectMetadata, QueryContext oeContext);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the custom name of the object assigned to the node. If the object doesn't have custom name, returns empty string
|
||||
/// </summary>
|
||||
public abstract string GetNodeCustomName(object objectMetadata, QueryContext oeContext);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the object as shown in its Object Explorer node path
|
||||
/// </summary>
|
||||
public abstract string GetNodePathName(object objectMetadata);
|
||||
|
||||
public abstract bool CanCreateChild(TreeNode parent, object context); // TODOKusto: Can this context be changed to DataSourceObjectMetadata
|
||||
public abstract TreeNode CreateChild(TreeNode parent, DataSourceObjectMetadata childMetadata);
|
||||
|
||||
// TODO Consider whether Remove operations need to be supported
|
||||
//public abstract bool CanRemoveChild(TreeNode parent, object context);
|
||||
//public abstract int GetChildIndexToRemove(TreeNode parent, object context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Has information for filtering a SMO object by properties
|
||||
/// </summary>
|
||||
public class NodeFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Property name
|
||||
/// </summary>
|
||||
public string Property { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filter values
|
||||
/// </summary>
|
||||
public List<object> Values { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of the filter values
|
||||
/// </summary>
|
||||
public Type Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which platforms a filter is valid for
|
||||
/// </summary>
|
||||
public ValidForFlag ValidFor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the Querier the filter can be applied to
|
||||
/// </summary>
|
||||
public Type TypeToReverse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the filter can be apply to the given type and Server type
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the querier</param>
|
||||
/// <param name="validForFlag">Server Type</param>
|
||||
/// <returns></returns>
|
||||
public bool CanApplyFilter(Type type, ValidForFlag validForFlag)
|
||||
{
|
||||
bool canApplyFilter = false;
|
||||
canApplyFilter = TypeToReverse == null || TypeToReverse == type;
|
||||
canApplyFilter = canApplyFilter && (ValidFor == 0 || ValidFor.HasFlag(validForFlag));
|
||||
|
||||
return canApplyFilter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string from the filter property and values to be used in the Urn to query the SQL objects
|
||||
/// Example of the output:[@ IsSystemObject = 0]
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ToPropertyFilterString()
|
||||
{
|
||||
string filter = "";
|
||||
List<object> values = Values;
|
||||
if (values != null)
|
||||
{
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
var value = values[i];
|
||||
object propertyValue = value;
|
||||
if (Type == typeof(string))
|
||||
{
|
||||
propertyValue = $"'{propertyValue}'";
|
||||
}
|
||||
if (Type == typeof(Enum))
|
||||
{
|
||||
propertyValue = (int)Convert.ChangeType(value, Type);
|
||||
|
||||
}
|
||||
string orPrefix = i == 0 ? string.Empty : "or";
|
||||
filter = $"{filter} {orPrefix} @{Property} = {propertyValue}";
|
||||
}
|
||||
}
|
||||
filter = $"({filter})";
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static string ConcatProperties(IEnumerable<NodeFilter> filters)
|
||||
{
|
||||
string filter = "";
|
||||
var list = filters.ToList();
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var value = list[i];
|
||||
|
||||
string andPrefix = i == 0 ? string.Empty : "and";
|
||||
filter = $"{filter} {andPrefix} {value.ToPropertyFilterString()}";
|
||||
}
|
||||
filter = $"[{filter}]";
|
||||
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection class for <see cref="TreeNode"/>
|
||||
/// </summary>
|
||||
public sealed class NodeObservableCollection : ObservableCollection<TreeNode>
|
||||
{
|
||||
public event EventHandler Initialized;
|
||||
private int? numInits;
|
||||
private static int cleanupBlocker;
|
||||
|
||||
public bool IsInitialized
|
||||
{
|
||||
get { return numInits.HasValue && numInits == 0; }
|
||||
}
|
||||
|
||||
public bool IsPopulating
|
||||
{
|
||||
get { return numInits.HasValue && numInits != 0; }
|
||||
}
|
||||
|
||||
public bool IsSorted
|
||||
{
|
||||
get
|
||||
{
|
||||
// SMO objects are already sorted so no need to sort them again
|
||||
return this.FirstOrDefault() is DataSourceTreeNode;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginInit()
|
||||
{
|
||||
if (!numInits.HasValue)
|
||||
{
|
||||
numInits = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
numInits = numInits + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void EndInit()
|
||||
{
|
||||
IList<TreeNode> empty = null;
|
||||
EndInit(null, ref empty);
|
||||
}
|
||||
|
||||
public void EndInit(TreeNode parent, ref IList<TreeNode> deferredChildren)
|
||||
{
|
||||
if (numInits.HasValue &&
|
||||
numInits.Value == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsSorted)
|
||||
{
|
||||
DoSort();
|
||||
}
|
||||
|
||||
if (deferredChildren != null)
|
||||
{
|
||||
// Set the parents so the children know how to sort themselves
|
||||
foreach (var item in deferredChildren)
|
||||
{
|
||||
item.Parent = parent;
|
||||
}
|
||||
|
||||
deferredChildren = deferredChildren.OrderBy(x => x).ToList();
|
||||
|
||||
// Add the deferredChildren
|
||||
foreach (var item in deferredChildren)
|
||||
{
|
||||
this.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (deferredChildren != null)
|
||||
{
|
||||
deferredChildren.Clear();
|
||||
}
|
||||
numInits = numInits - 1;
|
||||
}
|
||||
|
||||
Initialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
numInits = numInits - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Repositions this child in the list
|
||||
/// </summary>
|
||||
public void ReSortChild(TreeNode child)
|
||||
{
|
||||
if (child == null)
|
||||
return;
|
||||
|
||||
List<TreeNode> sorted = this.OrderBy(x => x).ToList();
|
||||
|
||||
// Remove without cleanup
|
||||
try
|
||||
{
|
||||
cleanupBlocker++;
|
||||
Remove(child);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cleanupBlocker--;
|
||||
}
|
||||
|
||||
// Then insert
|
||||
for (int i = 0; i < sorted.Count; i++)
|
||||
{
|
||||
if (sorted[i] == child)
|
||||
{
|
||||
Insert(i, child);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
// Cleanup all the children
|
||||
Cleanup(this[index]);
|
||||
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
// Cleanup all the children
|
||||
foreach (var child in this)
|
||||
{
|
||||
Cleanup(child);
|
||||
}
|
||||
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
private static void Cleanup(TreeNode parent)
|
||||
{
|
||||
if (cleanupBlocker > 0 ||
|
||||
parent.Parent == null)
|
||||
return;
|
||||
|
||||
// TODO implement cleanup policy / pattern
|
||||
//ICleanupPattern parentAsCleanup = parent as ICleanupPattern;
|
||||
//if (parentAsCleanup != null)
|
||||
// parentAsCleanup.DoCleanup();
|
||||
|
||||
//foreach (var child in parent.Children)
|
||||
//{
|
||||
// Cleanup(child);
|
||||
//}
|
||||
|
||||
parent.Parent = null;
|
||||
}
|
||||
|
||||
private void DoSort()
|
||||
{
|
||||
List<TreeNode> sorted = this.OrderBy(x => x).ToList();
|
||||
for (int i = 0; i < sorted.Count(); i++)
|
||||
{
|
||||
int index = IndexOf(sorted[i]);
|
||||
if (index != i)
|
||||
{
|
||||
Move(IndexOf(sorted[i]), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Has information for SMO object properties to be loaded with the SMO object
|
||||
/// </summary>
|
||||
public class NodeSmoProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Property name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which platforms a filter is valid for
|
||||
/// </summary>
|
||||
public ValidForFlag ValidFor { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum listing possible node types in the object explorer tree
|
||||
/// </summary>
|
||||
// TODO Consider replacing this with an auto-gen'd version
|
||||
public enum NodeTypes
|
||||
{
|
||||
None,
|
||||
SqlServersRoot,
|
||||
Database,
|
||||
Server,
|
||||
ScalarValuedFunction,
|
||||
TableValuedFunction,
|
||||
AggregateFunction,
|
||||
FileGroup,
|
||||
StoredProcedure,
|
||||
UserDefinedTableType,
|
||||
View,
|
||||
Table,
|
||||
HistoryTable,
|
||||
Folder,
|
||||
Databases,
|
||||
ExternalResources,
|
||||
ServerLevelSecurity,
|
||||
ServerLevelServerObjects,
|
||||
ServerLevelManagement,
|
||||
SystemDatabases,
|
||||
ServerLevelLinkedServerLogins,
|
||||
ServerLevelServerAudits,
|
||||
ServerLevelCryptographicProviders,
|
||||
ServerLevelCredentials,
|
||||
ServerLevelServerRoles,
|
||||
ServerLevelLogins,
|
||||
ServerLevelEventSessions,
|
||||
ServerLevelServerAuditSpecifications,
|
||||
ServerLevelEventNotifications,
|
||||
ServerLevelErrorMessages,
|
||||
ServerLevelServerTriggers,
|
||||
ServerLevelLinkedServers,
|
||||
ServerLevelEndpoints,
|
||||
SystemScalarValuedFunctions,
|
||||
SystemTableValuedFunctions,
|
||||
SystemFunctions,
|
||||
DacInstancesFolder,
|
||||
Tables,
|
||||
Views,
|
||||
Synonyms,
|
||||
Programmability,
|
||||
ServiceBroker,
|
||||
Storage,
|
||||
Security,
|
||||
SystemTables,
|
||||
FileTables,
|
||||
SystemViews,
|
||||
StoredProcedures,
|
||||
Functions,
|
||||
ExtendedStoredProcedures,
|
||||
DatabaseTriggers,
|
||||
Defaults,
|
||||
Rules,
|
||||
Types,
|
||||
Assemblies,
|
||||
MessageTypes,
|
||||
Contracts,
|
||||
Queues,
|
||||
Services,
|
||||
Routes,
|
||||
DatabaseAndQueueEventNotifications,
|
||||
RemoteServiceBindings,
|
||||
BrokerPriorities,
|
||||
FileGroups,
|
||||
FullTextCatalogs,
|
||||
FullTextStopLists,
|
||||
SqlLogFiles,
|
||||
PartitionFunctions,
|
||||
PartitionSchemes,
|
||||
SearchPropertyLists,
|
||||
Users,
|
||||
Roles,
|
||||
Schemas,
|
||||
AsymmetricKeys,
|
||||
Certificates,
|
||||
SymmetricKeys,
|
||||
DatabaseEncryptionKeys,
|
||||
MasterKeys,
|
||||
Signatures,
|
||||
DatabaseAuditSpecifications,
|
||||
Columns,
|
||||
Keys,
|
||||
Constraints,
|
||||
Triggers,
|
||||
Indexes,
|
||||
Statistics,
|
||||
TableValuedFunctions,
|
||||
ScalarValuedFunctions,
|
||||
AggregateFunctions,
|
||||
SystemDataTypes,
|
||||
UserDefinedDataTypes,
|
||||
UserDefinedTableTypes,
|
||||
UserDefinedTypes,
|
||||
XmlSchemaCollections,
|
||||
SystemExactNumerics,
|
||||
SystemApproximateNumerics,
|
||||
SystemDateAndTimes,
|
||||
SystemCharacterStrings,
|
||||
SystemUnicodeCharacterStrings,
|
||||
SystemBinaryStrings,
|
||||
SystemOtherDataTypes,
|
||||
SystemClrDataTypes,
|
||||
SystemSpatialDataTypes,
|
||||
UserDefinedTableTypeColumns,
|
||||
UserDefinedTableTypeKeys,
|
||||
UserDefinedTableTypeConstraints,
|
||||
SystemStoredProcedures,
|
||||
StoredProcedureParameters,
|
||||
TableValuedFunctionParameters,
|
||||
ScalarValuedFunctionParameters,
|
||||
AggregateFunctionParameters,
|
||||
DatabaseRoles,
|
||||
ApplicationRoles,
|
||||
FileGroupFiles,
|
||||
SystemMessageTypes,
|
||||
SystemContracts,
|
||||
SystemServices,
|
||||
SystemQueues,
|
||||
Sequences,
|
||||
SecurityPolicies,
|
||||
DatabaseScopedCredentials,
|
||||
ExternalTables,
|
||||
ExternalResource,
|
||||
ExternalDataSources,
|
||||
ExternalFileFormats,
|
||||
ExternalTable,
|
||||
AlwaysEncryptedKeys,
|
||||
ColumnMasterKeys,
|
||||
ColumnEncryptionKeys
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Contracts;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel;
|
||||
using Microsoft.Kusto.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for elements in the object explorer tree. Provides common methods for tree navigation
|
||||
/// and other core functionality
|
||||
/// </summary>
|
||||
public class TreeNode : IComparable<TreeNode>
|
||||
{
|
||||
private NodeObservableCollection children = new NodeObservableCollection();
|
||||
private TreeNode parent;
|
||||
private string nodePath;
|
||||
private string label;
|
||||
private string nodePathName;
|
||||
public const char PathPartSeperator = '/';
|
||||
|
||||
/// <summary>
|
||||
/// Object metadata
|
||||
/// </summary>
|
||||
public DataSourceObjectMetadata ObjectMetadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The DataSource this tree node is representing
|
||||
/// </summary>
|
||||
public IDataSource DataSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with no required inputs
|
||||
/// </summary>
|
||||
public TreeNode(IDataSource dataSource, DataSourceObjectMetadata objectMetadata)
|
||||
{
|
||||
DataSource = dataSource;
|
||||
ObjectMetadata = objectMetadata;
|
||||
NodeValue = objectMetadata.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that accepts a label to identify the node
|
||||
/// </summary>
|
||||
/// <param name="value">Label identifying the node</param>
|
||||
public TreeNode(string value, IDataSource dataSource, DataSourceObjectMetadata objectMetadata)
|
||||
: this(dataSource, objectMetadata)
|
||||
{
|
||||
// We intentionally do not valid this being null or empty since
|
||||
// some nodes may need to set it
|
||||
NodeValue = value;
|
||||
}
|
||||
|
||||
private object buildingMetadataLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Event which tells if MetadataProvider is built fully or not
|
||||
/// </summary>
|
||||
public object BuildingMetadataLock
|
||||
{
|
||||
get { return this.buildingMetadataLock; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value describing this node
|
||||
/// </summary>
|
||||
public string NodeValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this object as included in its node path
|
||||
/// </summary>
|
||||
public string NodePathName {
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(nodePathName))
|
||||
{
|
||||
return NodeValue;
|
||||
}
|
||||
return nodePathName;
|
||||
}
|
||||
set
|
||||
{
|
||||
nodePathName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the node - for example Server, Database, Folder, Table
|
||||
/// </summary>
|
||||
public string NodeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
// True if the node includes system object
|
||||
/// </summary>
|
||||
public bool IsSystemObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enum defining the type of the node - for example Server, Database, Folder, Table
|
||||
/// </summary>
|
||||
public NodeTypes NodeTypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Node Sub type - for example a key can have type as "Key" and sub type as "PrimaryKey"
|
||||
/// </summary>
|
||||
public string NodeSubType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message returned from the engine for a object explorer node failure reason, if any.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Node status - for example login can be disabled/enabled
|
||||
/// </summary>
|
||||
public string NodeStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Label to display to the user, describing this node.
|
||||
/// If not explicitly set this will fall back to the <see cref="NodeValue"/> but
|
||||
/// for many nodes such as the server, the display label will be different
|
||||
/// to the value.
|
||||
/// </summary>
|
||||
public string Label {
|
||||
get
|
||||
{
|
||||
if(label == null)
|
||||
{
|
||||
return NodeValue;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
set
|
||||
{
|
||||
label = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this a leaf node (in which case no children can be generated) or
|
||||
/// is it expandable?
|
||||
/// </summary>
|
||||
public bool IsAlwaysLeaf { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message to show if this Node is in an error state. This indicates
|
||||
/// that children could be retrieved
|
||||
/// </summary>
|
||||
public string ErrorStateMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parent of this node
|
||||
/// </summary>
|
||||
public TreeNode Parent
|
||||
{
|
||||
get
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
set
|
||||
{
|
||||
parent = value;
|
||||
// Reset the node path since it's no longer valid
|
||||
nodePath = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path identifying this node: for example a table will be at ["server", "database", "tables", "tableName"].
|
||||
/// This enables rapid navigation of the tree without the need for a global registry of elements.
|
||||
/// The path functions as a unique ID and is used to disambiguate the node when sending requests for expansion.
|
||||
/// A common ID is needed since processes do not share address space and need a unique identifier
|
||||
/// </summary>
|
||||
public string GetNodePath()
|
||||
{
|
||||
if (nodePath == null)
|
||||
{
|
||||
GenerateNodePath();
|
||||
}
|
||||
return nodePath;
|
||||
}
|
||||
|
||||
private void GenerateNodePath()
|
||||
{
|
||||
string path = "";
|
||||
ObjectExplorerUtils.VisitChildAndParents(this, node =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(node.NodeValue))
|
||||
{
|
||||
// Hit a node with no NodeValue. This indicates we need to stop traversing
|
||||
return false;
|
||||
}
|
||||
// Otherwise add this value to the beginning of the path and keep iterating up
|
||||
path = string.Format(CultureInfo.InvariantCulture,
|
||||
"{0}{1}{2}", node.NodePathName, string.IsNullOrEmpty(path) ? "" : PathPartSeperator.ToString(), path);
|
||||
return true;
|
||||
});
|
||||
nodePath = path;
|
||||
}
|
||||
|
||||
public TreeNode FindNodeByPath(string path, bool expandIfNeeded = false)
|
||||
{
|
||||
TreeNode nodeForPath = ObjectExplorerUtils.FindNode(this, node =>
|
||||
{
|
||||
return node.GetNodePath() == path;
|
||||
}, nodeToFilter =>
|
||||
{
|
||||
return path.StartsWith(nodeToFilter.GetNodePath());
|
||||
}, expandIfNeeded);
|
||||
|
||||
return nodeForPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to a <see cref="NodeInfo"/> object for serialization with just the relevant properties
|
||||
/// needed to identify the node
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public NodeInfo ToNodeInfo()
|
||||
{
|
||||
return new NodeInfo()
|
||||
{
|
||||
IsLeaf = this.IsAlwaysLeaf,
|
||||
Label = this.Label,
|
||||
NodePath = this.GetNodePath(),
|
||||
NodeType = this.NodeType,
|
||||
Metadata = this.ObjectMetadata,
|
||||
NodeStatus = this.NodeStatus,
|
||||
NodeSubType = this.NodeSubType,
|
||||
ErrorMessage = this.ErrorMessage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands this node and returns its children
|
||||
/// </summary>
|
||||
/// <returns>Children as an IList. This is the raw children collection, not a copy</returns>
|
||||
public IList<TreeNode> Expand(string name, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO consider why solution explorer has separate Children and Items options
|
||||
if (children.IsInitialized)
|
||||
{
|
||||
return children;
|
||||
}
|
||||
PopulateChildren(false, name, cancellationToken);
|
||||
return children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands this node and returns its children
|
||||
/// </summary>
|
||||
/// <returns>Children as an IList. This is the raw children collection, not a copy</returns>
|
||||
public IList<TreeNode> Expand(CancellationToken cancellationToken)
|
||||
{
|
||||
return Expand(null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh this node and returns its children
|
||||
/// </summary>
|
||||
/// <returns>Children as an IList. This is the raw children collection, not a copy</returns>
|
||||
public virtual IList<TreeNode> Refresh(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO consider why solution explorer has separate Children and Items options
|
||||
PopulateChildren(true, null, cancellationToken);
|
||||
return children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a readonly view of the currently defined children for this node.
|
||||
/// This does not expand the node at all
|
||||
/// Since the tree needs to keep track of parent relationships, directly
|
||||
/// adding to the list is not supported.
|
||||
/// </summary>
|
||||
/// <returns><see cref="IList{TreeNode}"/> containing all children for this node</returns>
|
||||
public IList<TreeNode> GetChildren()
|
||||
{
|
||||
return new ReadOnlyCollection<TreeNode>(children);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a child to the list of children under this node
|
||||
/// </summary>
|
||||
/// <param name="newChild"><see cref="TreeNode"/></param>
|
||||
public void AddChild(TreeNode newChild)
|
||||
{
|
||||
Validate.IsNotNull(nameof(newChild), newChild);
|
||||
children.Add(newChild);
|
||||
newChild.Parent = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional context to help with lookup of children
|
||||
/// </summary>
|
||||
public virtual object GetContext()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to convert context to expected format
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert to</typeparam>
|
||||
/// <returns>context as expected type of null if it doesn't match</returns>
|
||||
public T GetContextAs<T>()
|
||||
where T : class
|
||||
{
|
||||
return GetContext() as T;
|
||||
}
|
||||
|
||||
public T ParentAs<T>()
|
||||
where T : TreeNode
|
||||
{
|
||||
return Parent as T;
|
||||
}
|
||||
|
||||
protected virtual void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, string.Format(CultureInfo.InvariantCulture, "Populating oe node :{0}", this.GetNodePath()));
|
||||
Debug.Assert(IsAlwaysLeaf == false);
|
||||
|
||||
QueryContext context = this.GetContextAs<QueryContext>();
|
||||
|
||||
if (children.IsPopulating || context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
children.Clear();
|
||||
BeginChildrenInit();
|
||||
|
||||
try
|
||||
{
|
||||
ErrorMessage = null;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
IEnumerable<TreeNode> items = ExpandChildren(this, refresh, name, true, cancellationToken);
|
||||
if (items != null)
|
||||
{
|
||||
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(TraceEventType.Error, error);
|
||||
ErrorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndChildrenInit();
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<TreeNode> ExpandChildren(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken)
|
||||
{
|
||||
List<TreeNode> allChildren = new List<TreeNode>();
|
||||
|
||||
try
|
||||
{
|
||||
OnExpandPopulateNonFolders(allChildren, parent, refresh, name, cancellationToken);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
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(TraceEventType.Error, error);
|
||||
throw ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
return allChildren;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates any non-folder nodes such as specific items in the tree.
|
||||
/// </summary>
|
||||
/// <param name="allChildren">List to which nodes should be added</param>
|
||||
/// <param name="parent">Parent the nodes are being added to</param>
|
||||
private void OnExpandPopulateNonFolders(IList<TreeNode> allChildren, TreeNode parent, bool refresh, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, string.Format(CultureInfo.InvariantCulture, "child factory parent :{0}", parent.GetNodePath()));
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var objectMetadataList = Enumerable.Empty<DataSourceObjectMetadata>();
|
||||
|
||||
if (parent.DataSource != null)
|
||||
{
|
||||
objectMetadataList = parent.DataSource.GetChildObjects(parent.ObjectMetadata);
|
||||
}
|
||||
|
||||
foreach (var objectMetadata in objectMetadataList)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (objectMetadata == null)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, "kustoMetadata should not be null");
|
||||
}
|
||||
TreeNode childNode = CreateChild(parent, objectMetadata);
|
||||
if (childNode != null)
|
||||
{
|
||||
allChildren.Add(childNode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string error = string.Format(CultureInfo.InvariantCulture, "Failed getting child objects. parent:{0} error:{1} inner:{2} stacktrace:{3}",
|
||||
parent != null ? parent.GetNodePath() : "", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace);
|
||||
Logger.Write(TraceEventType.Error, error);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The glue between the DataSource and the Object Explorer models. Creates the right tree node for each data source type
|
||||
/// </summary>
|
||||
protected TreeNode CreateChild(TreeNode parent, DataSourceObjectMetadata childMetadata)
|
||||
{
|
||||
ValidationUtils.IsNotNull(parent, nameof(parent));
|
||||
ValidationUtils.IsNotNull(childMetadata, nameof(childMetadata));
|
||||
|
||||
switch(childMetadata.MetadataType)
|
||||
{
|
||||
case DataSourceMetadataType.Database:
|
||||
return new DataSourceTreeNode(parent.DataSource, childMetadata) {
|
||||
Parent = parent as ServerNode,
|
||||
NodeType = "Database",
|
||||
NodeTypeId = NodeTypes.Database
|
||||
};
|
||||
|
||||
case DataSourceMetadataType.Table:
|
||||
return new DataSourceTreeNode(parent.DataSource, childMetadata) {
|
||||
NodeType = "Table",
|
||||
NodeTypeId = NodeTypes.Table
|
||||
};
|
||||
|
||||
case DataSourceMetadataType.Column:
|
||||
return new DataSourceTreeNode(parent.DataSource, childMetadata) {
|
||||
IsAlwaysLeaf = true,
|
||||
NodeType = "Column",
|
||||
SortPriority = DataSourceTreeNode.NextSortPriority
|
||||
};
|
||||
|
||||
case DataSourceMetadataType.Folder:
|
||||
return new DataSourceTreeNode(parent.DataSource, childMetadata)
|
||||
{
|
||||
Parent = parent,
|
||||
NodeType = "Folder",
|
||||
NodeTypeId = NodeTypes.Folder
|
||||
};
|
||||
|
||||
case DataSourceMetadataType.Function:
|
||||
return new DataSourceTreeNode(parent.DataSource, childMetadata)
|
||||
{
|
||||
parent = parent,
|
||||
NodeType = "Function",
|
||||
NodeTypeId = NodeTypes.Functions,
|
||||
IsAlwaysLeaf = true,
|
||||
};
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unexpected type {childMetadata.MetadataType}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginChildrenInit()
|
||||
{
|
||||
children.BeginInit();
|
||||
}
|
||||
|
||||
public void EndChildrenInit()
|
||||
{
|
||||
children.EndInit();
|
||||
// TODO consider use of deferred children and if it's necessary
|
||||
// children.EndInit(this, ref deferredChildren);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sort Priority to help when ordering elements in the tree
|
||||
/// </summary>
|
||||
public int? SortPriority { get; set; }
|
||||
|
||||
protected virtual int CompareSamePriorities(TreeNode thisItem, TreeNode otherItem)
|
||||
{
|
||||
return string.Compare(thisItem.NodeValue, otherItem.NodeValue, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public int CompareTo(TreeNode other)
|
||||
{
|
||||
|
||||
if (!this.SortPriority.HasValue &&
|
||||
!other.SortPriority.HasValue)
|
||||
{
|
||||
return CompareSamePriorities(this, other);
|
||||
}
|
||||
|
||||
if (this.SortPriority.HasValue &&
|
||||
!other.SortPriority.HasValue)
|
||||
{
|
||||
return -1; // this is above other
|
||||
}
|
||||
if (!this.SortPriority.HasValue)
|
||||
{
|
||||
return 1; // this is below other
|
||||
}
|
||||
|
||||
// Both have sort priority
|
||||
int priDiff = this.SortPriority.Value - other.SortPriority.Value;
|
||||
if (priDiff < 0)
|
||||
return -1; // this is below other
|
||||
if (priDiff == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes
|
||||
{
|
||||
public class TreeNodeWithContext
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,839 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common; // For ServerConnection
|
||||
using Microsoft.SqlTools.Extensibility;
|
||||
using Microsoft.SqlTools.Hosting;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.Kusto.ServiceLayer.Connection;
|
||||
using Microsoft.Kusto.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.Kusto.ServiceLayer.LanguageServices;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Contracts;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel;
|
||||
using Microsoft.Kusto.ServiceLayer.SqlContext;
|
||||
using Microsoft.Kusto.ServiceLayer.Utility;
|
||||
using Microsoft.Kusto.ServiceLayer.Workspace;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// A Service to support querying server and database information as an Object Explorer tree.
|
||||
/// The APIs used for this are modeled closely on the VSCode TreeExplorerNodeProvider API.
|
||||
/// </summary>
|
||||
[Export(typeof(IHostedService))]
|
||||
public class ObjectExplorerService : HostedService<ObjectExplorerService>, IComposableService, IHostedService, IDisposable
|
||||
{
|
||||
internal const string uriPrefix = "objectexplorer://";
|
||||
|
||||
// Instance of the connection service, used to get the connection info for a given owner URI
|
||||
private ConnectionService connectionService;
|
||||
private IProtocolEndpoint serviceHost;
|
||||
private ConcurrentDictionary<string, ObjectExplorerSession> sessionMap;
|
||||
private readonly Lazy<Dictionary<string, HashSet<ChildFactory>>> applicableNodeChildFactories;
|
||||
private IMultiServiceProvider serviceProvider;
|
||||
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>
|
||||
private ObjectExplorerSettings settings;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton constructor
|
||||
/// </summary>
|
||||
public ObjectExplorerService()
|
||||
{
|
||||
sessionMap = new ConcurrentDictionary<string, ObjectExplorerSession>();
|
||||
applicableNodeChildFactories = new Lazy<Dictionary<string, HashSet<ChildFactory>>>(() => PopulateFactories());
|
||||
NodePathGenerator.Initialize();
|
||||
}
|
||||
|
||||
internal ConnectedBindingQueue ConnectedBindingQueue
|
||||
{
|
||||
get
|
||||
{
|
||||
return bindingQueue;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.bindingQueue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing only
|
||||
/// </summary>
|
||||
internal ObjectExplorerService(ExtensionServiceProvider serviceProvider)
|
||||
: this()
|
||||
{
|
||||
SetServiceProvider(serviceProvider);
|
||||
}
|
||||
|
||||
private Dictionary<string, HashSet<ChildFactory>> ApplicableNodeChildFactories
|
||||
{
|
||||
get
|
||||
{
|
||||
return applicableNodeChildFactories.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the session ids
|
||||
/// </summary>
|
||||
internal IReadOnlyCollection<string> SessionIds
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ReadOnlyCollection<string>(sessionMap.Keys.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// As an <see cref="IComposableService"/>, this will be set whenever the service is initialized
|
||||
/// via an <see cref="IMultiServiceProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
public override void SetServiceProvider(IMultiServiceProvider provider)
|
||||
{
|
||||
Validate.IsNotNull(nameof(provider), provider);
|
||||
serviceProvider = provider;
|
||||
connectionService = provider.GetService<ConnectionService>();
|
||||
try
|
||||
{
|
||||
connectionService.RegisterConnectedQueue(connectionName, bindingQueue);
|
||||
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the service with the service host and registers request handlers.
|
||||
/// </summary>
|
||||
/// <param name="serviceHost">The service host instance to register with</param>
|
||||
public override void InitializeService(IProtocolEndpoint serviceHost)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, "ObjectExplorer service initialized");
|
||||
this.serviceHost = serviceHost;
|
||||
|
||||
this.ConnectedBindingQueue.OnUnhandledException += OnUnhandledException;
|
||||
|
||||
// Register handlers for requests
|
||||
serviceHost.SetRequestHandler(CreateSessionRequest.Type, HandleCreateSessionRequest);
|
||||
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)
|
||||
{
|
||||
workspaceService.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the workspace service. Note: should handle case where this is null in cases where unit tests do not set this up
|
||||
/// </summary>
|
||||
private WorkspaceService<SqlToolsSettings> WorkspaceService
|
||||
{
|
||||
get { return serviceProvider.GetService<WorkspaceService<SqlToolsSettings>>(); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensure formatter settings are always up to date
|
||||
/// </summary>
|
||||
public Task HandleDidChangeConfigurationNotification(
|
||||
SqlToolsSettings newSettings,
|
||||
SqlToolsSettings oldSettings,
|
||||
EventContext eventContext)
|
||||
{
|
||||
// update the current settings to reflect any changes (assuming formatter settings exist)
|
||||
settings = newSettings?.SqlTools?.ObjectExplorer ?? settings;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
|
||||
internal async Task HandleCreateSessionRequest(ConnectionDetails connectionDetails, RequestContext<CreateSessionResponse> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, "HandleCreateSessionRequest");
|
||||
Func<Task<CreateSessionResponse>> 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);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.SendError(ex.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal async Task HandleExpandRequest(ExpandParams expandParams, RequestContext<bool> context)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, "HandleExpandRequest");
|
||||
|
||||
Func<Task<bool>> expandNode = async () =>
|
||||
{
|
||||
Validate.IsNotNull(nameof(expandParams), expandParams);
|
||||
Validate.IsNotNull(nameof(context), context);
|
||||
|
||||
string uri = expandParams.SessionId;
|
||||
ObjectExplorerSession session = null;
|
||||
if (!sessionMap.TryGetValue(uri, out session))
|
||||
{
|
||||
Logger.Write(TraceEventType.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<bool> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, "HandleRefreshRequest");
|
||||
Validate.IsNotNull(nameof(refreshParams), refreshParams);
|
||||
Validate.IsNotNull(nameof(context), context);
|
||||
|
||||
string uri = refreshParams.SessionId;
|
||||
ObjectExplorerSession session = null;
|
||||
if (!sessionMap.TryGetValue(uri, out session))
|
||||
{
|
||||
Logger.Write(TraceEventType.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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.SendError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task HandleCloseSessionRequest(CloseSessionParams closeSessionParams, RequestContext<CloseSessionResponse> context)
|
||||
{
|
||||
|
||||
Logger.Write(TraceEventType.Verbose, "HandleCloseSessionRequest");
|
||||
Func<Task<CloseSessionResponse>> 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(TraceEventType.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 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;
|
||||
if (sessionMap.TryGetValue(uri, out session))
|
||||
{
|
||||
// Remove the session from active sessions and disconnect
|
||||
if(sessionMap.TryRemove(session.Uri, out session))
|
||||
{
|
||||
if (session != null && session.ConnectionInfo != null)
|
||||
{
|
||||
bindingQueue.RemoveBindigContext(session.ConnectionInfo);
|
||||
}
|
||||
}
|
||||
connectionService.Disconnect(new DisconnectParams()
|
||||
{
|
||||
OwnerUri = uri
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCreateSessionTask(ConnectionDetails connectionDetails, string uri)
|
||||
{
|
||||
Logger.Write(TraceEventType.Information, "Creating OE session");
|
||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
if (connectionDetails != null && !string.IsNullOrEmpty(uri))
|
||||
{
|
||||
Task task = CreateSessionAsync(connectionDetails, uri, cancellationTokenSource.Token);
|
||||
CreateSessionTask = task;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
ObjectExplorerTaskResult result = await RunTaskWithTimeout(task,
|
||||
settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout);
|
||||
|
||||
if (result != null && !result.IsCompleted)
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
SessionCreatedParameters response = new SessionCreatedParameters
|
||||
{
|
||||
Success = false,
|
||||
SessionId = uri,
|
||||
ErrorMessage = result.Exception != null ? result.Exception.Message : $"Failed to create session for session id {uri}"
|
||||
|
||||
};
|
||||
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response);
|
||||
}
|
||||
return result;
|
||||
}).ContinueWithOnFaulted(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For tests only
|
||||
/// </summary>
|
||||
internal Task CreateSessionTask
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private async Task<SessionCreatedParameters> CreateSessionAsync(ConnectionDetails connectionDetails, string uri, CancellationToken cancellationToken)
|
||||
{
|
||||
ObjectExplorerSession session;
|
||||
if (!sessionMap.TryGetValue(uri, out session))
|
||||
{
|
||||
// Establish a connection to the specified server/database
|
||||
session = await DoCreateSession(connectionDetails, uri);
|
||||
}
|
||||
|
||||
SessionCreatedParameters response;
|
||||
if (session != null && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Else we have a session available, response with existing session information
|
||||
response = new SessionCreatedParameters
|
||||
{
|
||||
Success = true,
|
||||
RootNode = session.Root.ToNodeInfo(),
|
||||
SessionId = uri,
|
||||
ErrorMessage = session.ErrorMessage
|
||||
};
|
||||
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response);
|
||||
return response;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
internal async Task<ExpandResponse> ExpandNode(ObjectExplorerSession session, string nodePath, bool forceRefresh = false)
|
||||
{
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
return QueueExpandNodeRequest(session, nodePath, forceRefresh);
|
||||
});
|
||||
}
|
||||
internal ExpandResponse QueueExpandNodeRequest(ObjectExplorerSession session, string nodePath, bool forceRefresh = false)
|
||||
{
|
||||
NodeInfo[] nodes = null;
|
||||
TreeNode node = session.Root.FindNodeByPath(nodePath);
|
||||
ExpandResponse response = null;
|
||||
|
||||
// This node was likely returned from a different node provider. Ignore expansion and return an empty array
|
||||
// since we don't need to add any nodes under this section of the tree.
|
||||
if (node == null)
|
||||
{
|
||||
response = new ExpandResponse { Nodes = new NodeInfo[] { }, ErrorMessage = string.Empty, SessionId = session.Uri, NodePath = nodePath };
|
||||
response.Nodes = new NodeInfo[0];
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
response = new ExpandResponse { Nodes = new NodeInfo[] { }, ErrorMessage = node.ErrorMessage, SessionId = session.Uri, NodePath = nodePath };
|
||||
}
|
||||
|
||||
if (node != null && Monitor.TryEnter(node.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||
{
|
||||
try
|
||||
{
|
||||
int timeout = (int)TimeSpan.FromSeconds(settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout).TotalMilliseconds;
|
||||
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||
key: bindingQueue.AddConnectionContext(session.ConnectionInfo, connectionName),
|
||||
bindingTimeout: timeout,
|
||||
waitForLockTimeout: timeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
if (forceRefresh)
|
||||
{
|
||||
nodes = node.Refresh(cancelToken).Select(x => x.ToNodeInfo()).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes = node.Expand(cancelToken).Select(x => x.ToNodeInfo()).ToArray();
|
||||
}
|
||||
response.Nodes = nodes;
|
||||
response.ErrorMessage = node.ErrorMessage;
|
||||
try
|
||||
{
|
||||
// SMO changes the database when getting sql objects. Make sure the database is changed back to the original one
|
||||
if (bindingContext.ServerConnection.CurrentDatabase != bindingContext.ServerConnection.DatabaseName)
|
||||
{
|
||||
bindingContext.ServerConnection.SqlConnectionObject.ChangeDatabase(bindingContext.ServerConnection.DatabaseName);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Write(TraceEventType.Warning, $"Failed to change the database in OE connection. error: {ex.Message}");
|
||||
// We should just try to change the connection. If it fails, there's not much we can do
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
if (queueItem.GetResultAsT<ExpandResponse>() != null)
|
||||
{
|
||||
response = queueItem.GetResultAsT<ExpandResponse>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(node.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Establishes a new session and stores its information
|
||||
/// </summary>
|
||||
/// <returns><see cref="ObjectExplorerSession"/> object if successful, null if unsuccessful</returns>
|
||||
internal async Task<ObjectExplorerSession> DoCreateSession(ConnectionDetails connectionDetails, string uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
ObjectExplorerSession session = null;
|
||||
connectionDetails.PersistSecurityInfo = true;
|
||||
ConnectParams connectParams = new ConnectParams() { OwnerUri = uri, Connection = connectionDetails, Type = Connection.ConnectionType.ObjectExplorer };
|
||||
bool isDefaultOrSystemDatabase = DatabaseUtils.IsSystemDatabaseConnection(connectionDetails.DatabaseName) || string.IsNullOrWhiteSpace(connectionDetails.DatabaseDisplayName);
|
||||
|
||||
ConnectionInfo connectionInfo;
|
||||
ConnectionCompleteParams connectionResult = await Connect(connectParams, uri);
|
||||
if (!connectionService.TryFindConnection(uri, out connectionInfo))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (connectionResult == null)
|
||||
{
|
||||
// Connection failed and notification is already sent
|
||||
return null;
|
||||
}
|
||||
|
||||
int timeout = (int)TimeSpan.FromSeconds(settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout).TotalMilliseconds;
|
||||
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||
key: bindingQueue.AddConnectionContext(connectionInfo, connectionName),
|
||||
bindingTimeout: timeout,
|
||||
waitForLockTimeout: timeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
session = ObjectExplorerSession.CreateSession(connectionResult, serviceProvider, bindingContext.ServerConnection, bindingContext.DataSource, isDefaultOrSystemDatabase);
|
||||
session.ConnectionInfo = connectionInfo;
|
||||
|
||||
sessionMap.AddOrUpdate(uri, session, (key, oldSession) => session);
|
||||
return session;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
if (queueItem.GetResultAsT<ObjectExplorerSession>() != null)
|
||||
{
|
||||
session = queueItem.GetResultAsT<ObjectExplorerSession>();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
await SendSessionFailedNotification(uri, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ConnectionCompleteParams> Connect(ConnectParams connectParams, string uri)
|
||||
{
|
||||
string connectionErrorMessage = string.Empty;
|
||||
try
|
||||
{
|
||||
// open connection based on request details
|
||||
ConnectionCompleteParams result = await connectionService.Connect(connectParams);
|
||||
connectionErrorMessage = result != null ? $"{result.Messages} error code:{result.ErrorNumber}" : string.Empty;
|
||||
if (result != null && !string.IsNullOrEmpty(result.ConnectionId))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendSessionFailedNotification(uri, result.ErrorMessage);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SendSessionFailedNotification(uri, ex.ToString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendSessionFailedNotification(string uri, string errorMessage)
|
||||
{
|
||||
Logger.Write(TraceEventType.Warning, $"Failed To create OE session: {errorMessage}");
|
||||
SessionCreatedParameters result = new SessionCreatedParameters()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = errorMessage,
|
||||
SessionId = uri
|
||||
};
|
||||
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result);
|
||||
}
|
||||
|
||||
internal async Task SendSessionDisconnectedNotification(string uri, bool success, string errorMessage)
|
||||
{
|
||||
Logger.Write(TraceEventType.Information, $"OE session disconnected: {errorMessage}");
|
||||
SessionDisconnectedParameters result = new SessionDisconnectedParameters()
|
||||
{
|
||||
Success = success,
|
||||
ErrorMessage = errorMessage,
|
||||
SessionId = uri
|
||||
};
|
||||
await serviceHost.SendEvent(SessionDisconnectedNotification.Type, result);
|
||||
}
|
||||
|
||||
private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false)
|
||||
{
|
||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
Task task = ExpandNodeAsync(session, expandParams, cancellationTokenSource.Token, forceRefresh);
|
||||
ExpandTask = task;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
ObjectExplorerTaskResult result = await RunTaskWithTimeout(task,
|
||||
settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout);
|
||||
|
||||
if (result != null && !result.IsCompleted)
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
ExpandResponse response = CreateExpandResponse(session, expandParams);
|
||||
response.ErrorMessage = result.Exception != null ? result.Exception.Message: $"Failed to expand node: {expandParams.NodePath} in session {session.Uri}";
|
||||
await serviceHost.SendEvent(ExpandCompleteNotification.Type, response);
|
||||
}
|
||||
return result;
|
||||
}).ContinueWithOnFaulted(null);
|
||||
}
|
||||
|
||||
private async Task<ObjectExplorerTaskResult> RunTaskWithTimeout(Task task, int timeoutInSec)
|
||||
{
|
||||
ObjectExplorerTaskResult result = new ObjectExplorerTaskResult();
|
||||
TimeSpan timeout = TimeSpan.FromSeconds(timeoutInSec);
|
||||
await Task.WhenAny(task, Task.Delay(timeout));
|
||||
result.IsCompleted = task.IsCompleted;
|
||||
if(task.Exception != null)
|
||||
{
|
||||
result.Exception = task.Exception;
|
||||
}
|
||||
else if (!task.IsCompleted)
|
||||
{
|
||||
result.Exception = new TimeoutException($"Object Explorer task didn't complete within {timeoutInSec} seconds.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For tests only
|
||||
/// </summary>
|
||||
internal Task ExpandTask
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, CancellationToken cancellationToken, bool forceRefresh = false)
|
||||
{
|
||||
ExpandResponse response = null;
|
||||
response = await ExpandNode(session, expandParams.NodePath, forceRefresh);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, "OE expand canceled");
|
||||
}
|
||||
else
|
||||
{
|
||||
await serviceHost.SendEvent(ExpandCompleteNotification.Type, response);
|
||||
}
|
||||
}
|
||||
|
||||
private ExpandResponse CreateExpandResponse(ObjectExplorerSession session, ExpandParams expandParams)
|
||||
{
|
||||
return new ExpandResponse() { SessionId = session.Uri, NodePath = expandParams.NodePath };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a URI for object explorer using a similar pattern to Mongo DB (which has URI-based database definition)
|
||||
/// as this should ensure uniqueness
|
||||
/// </summary>
|
||||
/// <param name="details"></param>
|
||||
/// <returns>string representing a URI</returns>
|
||||
/// <remarks>Internal for testing purposes only</remarks>
|
||||
internal static string GenerateUri(ConnectionDetails details)
|
||||
{
|
||||
return ConnectedBindingQueue.GetConnectionContextKey(details);
|
||||
}
|
||||
|
||||
public IEnumerable<ChildFactory> GetApplicableChildFactories(TreeNode item)
|
||||
{
|
||||
if (ApplicableNodeChildFactories != null)
|
||||
{
|
||||
HashSet<ChildFactory> applicableFactories;
|
||||
if (ApplicableNodeChildFactories.TryGetValue(item.NodeTypeId.ToString(), out applicableFactories))
|
||||
{
|
||||
return applicableFactories;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal Dictionary<string, HashSet<ChildFactory>> PopulateFactories()
|
||||
{
|
||||
VerifyServicesInitialized();
|
||||
|
||||
var childFactories = new Dictionary<string, HashSet<ChildFactory>>();
|
||||
// Create our list of all NodeType to ChildFactory objects so we can expand appropriately
|
||||
foreach (var factory in serviceProvider.GetServices<ChildFactory>())
|
||||
{
|
||||
var parents = factory.ApplicableParents();
|
||||
if (parents != null)
|
||||
{
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
AddToApplicableChildFactories(childFactories, factory, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return childFactories;
|
||||
}
|
||||
|
||||
private void VerifyServicesInitialized()
|
||||
{
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException(SqlTools.Hosting.SR.ServiceProviderNotSet);
|
||||
}
|
||||
if (connectionService == null)
|
||||
{
|
||||
throw new InvalidOperationException(SqlTools.Hosting.SR.ServiceProviderNotSet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddToApplicableChildFactories(Dictionary<string, HashSet<ChildFactory>> childFactories, ChildFactory factory, string parent)
|
||||
{
|
||||
HashSet<ChildFactory> applicableFactories;
|
||||
if (!childFactories.TryGetValue(parent, out applicableFactories))
|
||||
{
|
||||
applicableFactories = new HashSet<ChildFactory>();
|
||||
childFactories[parent] = applicableFactories;
|
||||
}
|
||||
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; }
|
||||
public Exception Exception { get; set; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (bindingQueue != null)
|
||||
{
|
||||
bindingQueue.OnUnhandledException -= OnUnhandledException;
|
||||
bindingQueue.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnUnhandledException(string queueKey, Exception ex)
|
||||
{
|
||||
string sessionUri = LookupUriFromQueueKey(queueKey);
|
||||
if (!string.IsNullOrWhiteSpace(sessionUri))
|
||||
{
|
||||
await SendSessionDisconnectedNotification(uri: sessionUri, success: false, errorMessage: ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private string LookupUriFromQueueKey(string queueKey)
|
||||
{
|
||||
foreach (var session in this.sessionMap.Values)
|
||||
{
|
||||
var connInfo = session.ConnectionInfo;
|
||||
if (connInfo != null)
|
||||
{
|
||||
string currentKey = ConnectedBindingQueue.GetConnectionContextKey(connInfo.ConnectionDetails);
|
||||
if (queueKey == currentKey)
|
||||
{
|
||||
return session.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
internal class ObjectExplorerSession
|
||||
{
|
||||
private ConnectionService connectionService;
|
||||
private IMultiServiceProvider serviceProvider;
|
||||
|
||||
// TODO decide whether a cache is needed to handle lookups in elements with a large # children
|
||||
//private const int Cachesize = 10000;
|
||||
//private Cache<string, NodeMapping> cache;
|
||||
|
||||
public ObjectExplorerSession(string uri, TreeNode root, IMultiServiceProvider serviceProvider, ConnectionService connectionService)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("uri", uri);
|
||||
Validate.IsNotNull("root", root);
|
||||
Uri = uri;
|
||||
Root = root;
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.connectionService = connectionService;
|
||||
}
|
||||
|
||||
public string Uri { get; private set; }
|
||||
public TreeNode Root { get; private set; }
|
||||
|
||||
public ConnectionInfo ConnectionInfo { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public static ObjectExplorerSession CreateSession(ConnectionCompleteParams response, IMultiServiceProvider serviceProvider, ServerConnection serverConnection, IDataSource dataSource, bool isDefaultOrSystemDatabase)
|
||||
{
|
||||
DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(dataSource.ClusterName);
|
||||
ServerNode rootNode = new ServerNode(response, serviceProvider, serverConnection, dataSource, objectMetadata);
|
||||
|
||||
var session = new ObjectExplorerSession(response.OwnerUri, rootNode, serviceProvider, serviceProvider.GetService<ConnectionService>());
|
||||
if (!isDefaultOrSystemDatabase)
|
||||
{
|
||||
DataSourceObjectMetadata databaseMetadata = DataSourceFactory.CreateDatabaseMetadata(objectMetadata, response.ConnectionSummary.DatabaseName);
|
||||
|
||||
// Assuming the databases are in a folder under server node
|
||||
DataSourceTreeNode databaseNode = new DataSourceTreeNode(dataSource, databaseMetadata) {
|
||||
Parent = rootNode,
|
||||
NodeType = "Database",
|
||||
NodeTypeId = NodeTypes.Database
|
||||
};
|
||||
session.Root = databaseNode;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for Object Explorer related operations
|
||||
/// </summary>
|
||||
public static class ObjectExplorerUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Visitor that walks all nodes from the child to the root node, unless the
|
||||
/// <paramref name="visitor"/> function indicates that this should stop traversing
|
||||
/// </summary>
|
||||
/// <param name="child">node to start traversing at</param>
|
||||
/// <param name="visitor">Predicate function that accesses the tree and
|
||||
/// determines whether to stop going further up the tree</param>
|
||||
/// <returns>
|
||||
/// boolean - true to continue navigating up the tree, false to end the loop
|
||||
/// and return early
|
||||
/// </returns>
|
||||
public static bool VisitChildAndParents(TreeNode child, Predicate<TreeNode> visitor)
|
||||
{
|
||||
if (child == null)
|
||||
{
|
||||
// End case: all nodes have been visited
|
||||
return true;
|
||||
}
|
||||
|
||||
// Visit the child first, then go up the parents
|
||||
if (!visitor(child))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
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>
|
||||
/// <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>
|
||||
/// <returns>A Tree Node that matches the condition</returns>
|
||||
public static TreeNode FindNode(TreeNode node, Predicate<TreeNode> condition, Predicate<TreeNode> filter, bool expandIfNeeded = false)
|
||||
{
|
||||
if(node == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (condition(node))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
var children = expandIfNeeded && !node.IsAlwaysLeaf ? node.Expand(new CancellationToken()) : node.GetChildren();
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (filter != null && filter(child))
|
||||
{
|
||||
TreeNode childNode = FindNode(child, condition, filter, expandIfNeeded);
|
||||
if (childNode != null)
|
||||
{
|
||||
return childNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// This file was generated by a T4 Template. Do not modify directly, instead update the SmoQueryModelDefinition.xml file
|
||||
// and re-run the T4 template. This can be done in Visual Studio by right-click in and choosing "Run Custom Tool",
|
||||
// or from the command-line on any platform by running "build.cmd -Target=CodeGen" or "build.sh -Target=CodeGen".
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
|
||||
// TODOKusto: Do we need the Querier. This has been short circuited in smoChildFactoryBase. There is no caller to the "Query" function.
|
||||
[Export(typeof(DataSourceQuerier))]
|
||||
internal partial class DatabaseQuerier: DataSourceQuerier
|
||||
{
|
||||
public override IEnumerable<DataSourceObjectMetadata> Query(QueryContext context, string filter, bool refresh, IEnumerable<string> extraProperties)
|
||||
{
|
||||
if (context.DataSource != null)
|
||||
{
|
||||
return context.DataSource.GetChildObjects(context.ParentObjectMetadata);
|
||||
}
|
||||
return Enumerable.Empty<DataSourceObjectMetadata>();
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(DataSourceQuerier))]
|
||||
internal partial class TableQuerier: DataSourceQuerier
|
||||
{
|
||||
public override IEnumerable<DataSourceObjectMetadata> Query(QueryContext context, string filter, bool refresh, IEnumerable<string> extraProperties)
|
||||
{
|
||||
if (context.ParentObjectMetadata != null)
|
||||
{
|
||||
return context.DataSource.GetChildObjects(context.ParentObjectMetadata);
|
||||
}
|
||||
return Enumerable.Empty<DataSourceObjectMetadata>();
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(DataSourceQuerier))]
|
||||
internal partial class ColumnQuerier: DataSourceQuerier
|
||||
{
|
||||
public override IEnumerable<DataSourceObjectMetadata> Query(QueryContext context, string filter, bool refresh, IEnumerable<string> extraProperties)
|
||||
{
|
||||
if (context.ParentObjectMetadata != null)
|
||||
{
|
||||
return context.DataSource.GetChildObjects(context.ParentObjectMetadata);
|
||||
}
|
||||
return Enumerable.Empty<DataSourceObjectMetadata>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a folder node in the tree
|
||||
/// </summary>
|
||||
public class FolderNode : DataSourceTreeNode
|
||||
{
|
||||
public FolderNode(IDataSource dataSource, DataSourceObjectMetadata objectMetadata)
|
||||
: base(dataSource, objectMetadata)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For folders, this copies the context of its parent if available
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override object GetContext()
|
||||
{
|
||||
return Parent?.GetContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For folders, searches for its parent's SMO object rather than copying for itself
|
||||
/// </summary>
|
||||
/// <returns><see cref="KustoMetadata"/> from this parent's parent, or null if not found</returns>
|
||||
public override DataSourceObjectMetadata GetParentObjectMetadata()
|
||||
{
|
||||
return ParentAs<DataSourceTreeNode>()?.GetParentObjectMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
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.Kusto.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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlTools.Extensibility;
|
||||
using Microsoft.Kusto.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.Utility;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Server node implementation
|
||||
/// </summary>
|
||||
public class ServerNode : TreeNode
|
||||
{
|
||||
private ConnectionSummary connectionSummary;
|
||||
private ServerInfo serverInfo;
|
||||
private Lazy<QueryContext> context;
|
||||
private ServerConnection serverConnection;
|
||||
|
||||
public ServerNode(ConnectionCompleteParams connInfo, IMultiServiceProvider serviceProvider, ServerConnection serverConnection, IDataSource dataSource, DataSourceObjectMetadata objectMetadata)
|
||||
: base(dataSource, objectMetadata)
|
||||
{
|
||||
Validate.IsNotNull(nameof(connInfo), connInfo);
|
||||
Validate.IsNotNull("connInfo.ConnectionSummary", connInfo.ConnectionSummary);
|
||||
Validate.IsNotNull(nameof(serviceProvider), serviceProvider);
|
||||
|
||||
this.connectionSummary = connInfo.ConnectionSummary;
|
||||
this.serverInfo = connInfo.ServerInfo;
|
||||
|
||||
this.context = new Lazy<QueryContext>(() => CreateContext(serviceProvider));
|
||||
this.serverConnection = serverConnection;
|
||||
|
||||
NodeValue = connectionSummary.ServerName;
|
||||
IsAlwaysLeaf = false;
|
||||
NodeType = NodeTypes.Server.ToString();
|
||||
NodeTypeId = NodeTypes.Server;
|
||||
Label = GetConnectionLabel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the label to display to the user.
|
||||
/// </summary>
|
||||
internal string GetConnectionLabel()
|
||||
{
|
||||
string userName = connectionSummary.UserName;
|
||||
|
||||
// TODO Domain and username is not yet supported on .Net Core.
|
||||
// Consider passing as an input from the extension where this can be queried
|
||||
//if (string.IsNullOrWhiteSpace(userName))
|
||||
//{
|
||||
// userName = Environment.UserDomainName + @"\" + Environment.UserName;
|
||||
//}
|
||||
|
||||
// TODO Consider adding IsAuthenticatingDatabaseMaster check in the code and
|
||||
// referencing result here
|
||||
if (!DatabaseUtils.IsSystemDatabaseConnection(connectionSummary.DatabaseName))
|
||||
{
|
||||
// We either have an azure with a database specified or a Denali database using a contained user
|
||||
if (string.IsNullOrWhiteSpace(userName))
|
||||
{
|
||||
userName = connectionSummary.DatabaseName;
|
||||
}
|
||||
else
|
||||
{
|
||||
userName += ", " + connectionSummary.DatabaseName;
|
||||
}
|
||||
}
|
||||
|
||||
string label;
|
||||
if (string.IsNullOrWhiteSpace(userName))
|
||||
{
|
||||
label = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} ({1} {2})",
|
||||
connectionSummary.ServerName,
|
||||
"SQL Server",
|
||||
serverInfo.ServerVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
label = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} ({1} {2} - {3})",
|
||||
connectionSummary.ServerName,
|
||||
"SQL Server",
|
||||
serverInfo.ServerVersion,
|
||||
userName);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private QueryContext CreateContext(IMultiServiceProvider serviceProvider)
|
||||
{
|
||||
string exceptionMessage;
|
||||
|
||||
try
|
||||
{
|
||||
return new QueryContext(DataSource, serviceProvider)
|
||||
{
|
||||
ParentObjectMetadata = this.ObjectMetadata
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptionMessage = ex.Message;
|
||||
}
|
||||
|
||||
Logger.Write(TraceEventType.Error, "Exception at ServerNode.CreateContext() : " + exceptionMessage);
|
||||
this.ErrorStateMessage = string.Format(SR.TreeNodeError, exceptionMessage);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override object GetContext()
|
||||
{
|
||||
return context.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
public class DataSourceChildFactoryBase : ChildFactory
|
||||
{
|
||||
private IEnumerable<NodeSmoProperty> smoProperties;
|
||||
public override IEnumerable<string> ApplicableParents()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IEnumerable<TreeNode> Expand(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException(); // Moved to TreeNode.cs
|
||||
}
|
||||
|
||||
private bool ShouldFilterNode(TreeNode childNode, ValidForFlag validForFlag)
|
||||
{
|
||||
bool filterTheNode = false;
|
||||
|
||||
return filterTheNode;
|
||||
}
|
||||
|
||||
private string GetProperyFilter(IEnumerable<NodeFilter> filters, Type querierType, ValidForFlag validForFlag)
|
||||
{
|
||||
string filter = string.Empty;
|
||||
if (filters != null)
|
||||
{
|
||||
var filtersToApply = filters.Where(f => f.CanApplyFilter(querierType, validForFlag)).ToList();
|
||||
filter = string.Empty;
|
||||
if (filtersToApply.Any())
|
||||
{
|
||||
filter = NodeFilter.ConcatProperties(filtersToApply);
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private bool IsCompatibleQuerier(DataSourceQuerier querier)
|
||||
{
|
||||
if (ChildQuerierTypes == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type actualType = querier.GetType();
|
||||
foreach (Type childType in ChildQuerierTypes)
|
||||
{
|
||||
// We will accept any querier that is compatible with the listed querier type
|
||||
if (childType.IsAssignableFrom(actualType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public override bool CanCreateChild(TreeNode parent, object context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override TreeNode CreateChild(TreeNode parent, DataSourceObjectMetadata childMetadata)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual void InitializeChild(TreeNode parent, TreeNode child, object context)
|
||||
{
|
||||
DataSourceObjectMetadata objectMetadata = context as DataSourceObjectMetadata;
|
||||
if (objectMetadata == null)
|
||||
{
|
||||
Debug.WriteLine("context is not a DataSourceObjectMetadata type: " + context.GetType());
|
||||
}
|
||||
else
|
||||
{
|
||||
smoProperties = SmoProperties;
|
||||
DataSourceTreeNode childAsMeItem = (DataSourceTreeNode)child;
|
||||
childAsMeItem.CacheInfoFromModel(objectMetadata);
|
||||
QueryContext oeContext = parent.GetContextAs<QueryContext>();
|
||||
|
||||
// If node has custom name, replaced it with the name already set
|
||||
string customizedName = GetNodeCustomName(context, oeContext);
|
||||
if (!string.IsNullOrEmpty(customizedName))
|
||||
{
|
||||
childAsMeItem.NodeValue = customizedName;
|
||||
childAsMeItem.NodePathName = GetNodePathName(context);
|
||||
}
|
||||
|
||||
childAsMeItem.NodeSubType = GetNodeSubType(context, oeContext);
|
||||
childAsMeItem.NodeStatus = GetNodeStatus(context, oeContext);
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual Type[] ChildQuerierTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<NodeFilter> Filters
|
||||
{
|
||||
get
|
||||
{
|
||||
return Enumerable.Empty<NodeFilter>();
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<NodeSmoProperty> SmoProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
return Enumerable.Empty<NodeSmoProperty>();
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<NodeSmoProperty> CachedSmoProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
return smoProperties == null ? SmoProperties : smoProperties;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any final validation of the object to be added passes, and false
|
||||
/// if validation fails. This provides a chance to filter specific items out of a list
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="contextObject"></param>
|
||||
/// <returns>boolean</returns>
|
||||
public virtual bool PassesFinalFilters(TreeNode parent, object context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override string GetNodeStatus(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static bool IsPropertySupported(string propertyName, QueryContext context, DataSourceObjectMetadata objectMetadata, IEnumerable<NodeSmoProperty> supportedProperties)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return (objectMetadata as DataSourceObjectMetadata).PrettyName;
|
||||
}
|
||||
|
||||
public override string GetNodePathName(object objectMetadata)
|
||||
{
|
||||
return (objectMetadata as DataSourceObjectMetadata).Urn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper to convert non-generic Smo enumerables to generic enumerable types for easier use in
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class SmoCollectionWrapper<T> : IEnumerable<T>
|
||||
where T : SqlSmoObject
|
||||
{
|
||||
private SmoCollectionBase collection;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor which accepts a <see cref="SmoCollectionBase"/> containing the objects
|
||||
/// to wrap
|
||||
/// </summary>
|
||||
/// <param name="collection"><see cref="SmoCollectionBase"/> or null if none were set</param>
|
||||
public SmoCollectionWrapper(SmoCollectionBase collection)
|
||||
{
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable{T}.GetEnumerator"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach(Object obj in collection)
|
||||
{
|
||||
yield return (T)obj;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IEnumerable.GetEnumerator"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return collection?.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom name for Columns
|
||||
/// </summary>
|
||||
internal partial class ColumnsChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return SmoColumnCustomNodeHelper.CalculateCustomLabel(objectMetadata, oeContext);
|
||||
}
|
||||
|
||||
private readonly Lazy<List<NodeSmoProperty>> smoPropertiesLazy = new Lazy<List<NodeSmoProperty>>(() => new List<NodeSmoProperty>
|
||||
{
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "Computed",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "IsColumnSet",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "Nullable",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "DataType",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "InPrimaryKey",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "IsForeignKey",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "SystemType",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "Length",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "NumericPrecision",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "NumericScale",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "XmlSchemaNamespaceSchema",
|
||||
ValidFor = ValidForFlag.NotSqlDw
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "XmlSchemaNamespace",
|
||||
ValidFor = ValidForFlag.NotSqlDw
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "XmlDocumentConstraint",
|
||||
ValidFor = ValidForFlag.NotSqlDw
|
||||
}
|
||||
});
|
||||
|
||||
public override IEnumerable<NodeSmoProperty> SmoProperties => smoPropertiesLazy.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom name for UserDefinedTableTypeColumn
|
||||
/// </summary>
|
||||
internal partial class UserDefinedTableTypeColumnsChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return SmoColumnCustomNodeHelper.CalculateCustomLabel(objectMetadata, oeContext);
|
||||
}
|
||||
}
|
||||
|
||||
static class SmoColumnCustomNodeHelper
|
||||
{
|
||||
private const string SimpleColumnLabelWithType = "{0} ({1}{2}, {3})";
|
||||
private const string SimpleColumnLabelWithoutType = "{0} ({1})";
|
||||
private const string SimpleColumnLabelWithTypeAndKeyString = "{0} ({1}, {2}, {3})";
|
||||
|
||||
internal static string CalculateCustomLabel(object context, QueryContext oeContext)
|
||||
{
|
||||
UserDefinedDataTypeCollection uddts = null; // TODOKusto: Remove. Not needed.
|
||||
Column column = context as Column;
|
||||
if(column != null)
|
||||
{
|
||||
return GetCustomizedLabel(column, uddts);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string GetCustomizedLabel(Column column, UserDefinedDataTypeCollection uddts)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (column.Computed)
|
||||
{
|
||||
return GetComputedColumnLabel(column, uddts);
|
||||
}
|
||||
else if (column.IsColumnSet)
|
||||
{
|
||||
return GetColumnSetLabel(column, uddts);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetSimpleColumnLabel(column, uddts);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, $"Failed to get customized column name. error:{ex.Message}");
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string GetTypeSpecifierLabel(DataType dataType, UserDefinedDataTypeCollection uddts)
|
||||
{
|
||||
string typeName = string.Empty;
|
||||
if (dataType != null)
|
||||
{
|
||||
// typeSpecifier might still be in a resolve candidate status. If so then the
|
||||
// name might be null. Don't ask for the type specifier name in this case.
|
||||
typeName = dataType.Name;
|
||||
|
||||
// This may return [dbo].[MyType], but for the purposes of display we only want MyType
|
||||
if (!string.IsNullOrWhiteSpace(typeName) &&
|
||||
typeName.EndsWith("]", StringComparison.Ordinal))
|
||||
{
|
||||
int nameStart = typeName.LastIndexOf('[');
|
||||
typeName = typeName.Substring(nameStart + 1, typeName.Length - nameStart - 2);
|
||||
|
||||
}
|
||||
|
||||
if(dataType.SqlDataType == SqlDataType.UserDefinedDataType && uddts != null)
|
||||
{
|
||||
foreach (UserDefinedDataType item in uddts)
|
||||
{
|
||||
if(item.Name == dataType.Name)
|
||||
{
|
||||
typeName += $"({item.SystemType})";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These types supports detailed information
|
||||
switch (dataType.SqlDataType)
|
||||
{
|
||||
case SqlDataType.Char:
|
||||
case SqlDataType.NChar:
|
||||
case SqlDataType.Binary:
|
||||
case SqlDataType.VarChar:
|
||||
case SqlDataType.NVarChar:
|
||||
case SqlDataType.VarBinary:
|
||||
typeName += $"({dataType.MaximumLength})";
|
||||
break;
|
||||
case SqlDataType.Numeric:
|
||||
case SqlDataType.Decimal:
|
||||
typeName += $"({dataType.NumericPrecision},{dataType.NumericScale})";
|
||||
break;
|
||||
case SqlDataType.DateTime2:
|
||||
case SqlDataType.Time:
|
||||
case SqlDataType.DateTimeOffset:
|
||||
typeName += $"({dataType.NumericScale})";
|
||||
break;
|
||||
case SqlDataType.VarBinaryMax:
|
||||
case SqlDataType.NVarCharMax:
|
||||
case SqlDataType.VarCharMax:
|
||||
typeName += "(max)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return typeName;
|
||||
}
|
||||
|
||||
private static string GetKeyString(Column column)
|
||||
{
|
||||
// Get if it's a PK or FK (or both)
|
||||
// Here's how it could be both...notice t2c1 is both a primary and foreign key
|
||||
//
|
||||
// Create table t1 (t1c1 int, t1c2 int not null primary key)
|
||||
// Create table t2 (t2c1 int primary key, t2c2 int not null)
|
||||
// Alter table t2 add FOREIGN KEY(t2c1) references t1(t1c2)
|
||||
//
|
||||
string keyString = null;
|
||||
if (column.InPrimaryKey)
|
||||
keyString = "PK";
|
||||
if (column.IsForeignKey)
|
||||
{
|
||||
keyString = (keyString == null) ? "FK" :
|
||||
"PK, FK";
|
||||
}
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
private static string GetColumnSetLabel(Column column, UserDefinedDataTypeCollection uddts)
|
||||
{
|
||||
// This is the simple name
|
||||
string label = column.Name;
|
||||
|
||||
// Get the column type
|
||||
string columnType = GetTypeSpecifierLabel(column.DataType, uddts);
|
||||
string keyString = GetKeyString(column);
|
||||
|
||||
if (keyString != null && !string.IsNullOrWhiteSpace(columnType))
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SR.SchemaHierarchy_ColumnSetLabelWithTypeAndKeyString,
|
||||
label,
|
||||
keyString,
|
||||
columnType,
|
||||
SR.SchemaHierarchy_NullColumn_Label);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(columnType))
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SR.SchemaHierarchy_ColumnSetLabelWithType,
|
||||
label,
|
||||
keyString,
|
||||
columnType,
|
||||
SR.SchemaHierarchy_NullColumn_Label);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SR.SchemaHierarchy_ColumnSetLabelWithoutType,
|
||||
label,
|
||||
SR.SchemaHierarchy_NullColumn_Label);
|
||||
}
|
||||
|
||||
private static string GetSimpleColumnLabel(Column column, UserDefinedDataTypeCollection uddts)
|
||||
{
|
||||
// This is the simple name
|
||||
string label = column.Name;
|
||||
|
||||
// Get the nullability
|
||||
string isNullable = column.Nullable ? SR.SchemaHierarchy_NullColumn_Label : SR.SchemaHierarchy_NotNullColumn_Label;
|
||||
|
||||
// Get the column type
|
||||
string columnType = GetTypeSpecifierLabel(column.DataType, uddts);
|
||||
|
||||
string keyString = GetKeyString(column);
|
||||
|
||||
if (keyString != null && !string.IsNullOrWhiteSpace(columnType))
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SimpleColumnLabelWithTypeAndKeyString,
|
||||
label,
|
||||
keyString,
|
||||
columnType,
|
||||
isNullable);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(columnType))
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SimpleColumnLabelWithType,
|
||||
label,
|
||||
keyString,
|
||||
columnType,
|
||||
isNullable);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SimpleColumnLabelWithoutType,
|
||||
label,
|
||||
isNullable);
|
||||
}
|
||||
|
||||
private static string GetComputedColumnLabel(Column column, UserDefinedDataTypeCollection uddts)
|
||||
{
|
||||
string columnType = null;
|
||||
|
||||
// Display the type name as fully qualified
|
||||
string label = column.Name;
|
||||
|
||||
// Get the nullability
|
||||
string isNullable = column.Nullable ? SR.SchemaHierarchy_NullColumn_Label : SR.SchemaHierarchy_NotNullColumn_Label;
|
||||
|
||||
string keyString = GetKeyString(column);
|
||||
|
||||
// Get the column type
|
||||
columnType = GetTypeSpecifierLabel(column.DataType, uddts);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(columnType))
|
||||
{
|
||||
if (column.Parent is View)
|
||||
{
|
||||
// View columns are always computed, but SSMS shows then as never computed, so
|
||||
// treat them as simple columns
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SimpleColumnLabelWithType,
|
||||
label,
|
||||
keyString,
|
||||
columnType,
|
||||
isNullable);
|
||||
}
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SR.SchemaHierarchy_ComputedColumnLabelWithType,
|
||||
label,
|
||||
keyString,
|
||||
columnType,
|
||||
isNullable);
|
||||
}
|
||||
|
||||
if (column.Parent is View)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SimpleColumnLabelWithoutType,
|
||||
label,
|
||||
keyString);
|
||||
}
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SR.SchemaHierarchy_ComputedColumnLabelWithoutType,
|
||||
label,
|
||||
keyString);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.SqlServer.Management.Smo;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Status for databases
|
||||
/// </summary>
|
||||
internal partial class DatabasesChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeStatus(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return DatabasesCustomNodeHelper.GetStatus(objectMetadata, oeContext, CachedSmoProperties);
|
||||
}
|
||||
|
||||
protected override void InitializeChild(TreeNode parent, TreeNode child, object context)
|
||||
{
|
||||
base.InitializeChild(parent, child, context);
|
||||
var dsTreeNode = child as DataSourceTreeNode;
|
||||
if (dsTreeNode != null && dsTreeNode.ObjectMetadata != null
|
||||
&& DatabasesCustomNodeHelper.GetDatabaseIsUnavailable(dsTreeNode.ObjectMetadata, parent.GetContextAs<QueryContext>(), CachedSmoProperties))
|
||||
{
|
||||
child.IsAlwaysLeaf = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DatabasesCustomNodeHelper
|
||||
{
|
||||
private static readonly DatabaseStatus[] UnavailableDatabaseStatuses = { DatabaseStatus.Inaccessible, DatabaseStatus.Offline, DatabaseStatus.Recovering,
|
||||
DatabaseStatus.RecoveryPending, DatabaseStatus.Restoring, DatabaseStatus.Suspect, DatabaseStatus.Shutdown };
|
||||
|
||||
internal static bool GetDatabaseIsUnavailable(object objectMetadata, QueryContext oeContext, IEnumerable<NodeSmoProperty> supportedProperties)
|
||||
{
|
||||
if(oeContext.DataSource == null) return false; // Assume that database is available
|
||||
|
||||
return !oeContext.DataSource.Exists(objectMetadata as DataSourceObjectMetadata);
|
||||
}
|
||||
|
||||
internal static string GetStatus(object objectMetadata, QueryContext oeContext, IEnumerable<NodeSmoProperty> supportedProperties)
|
||||
{
|
||||
// TODOKusto: Remove if not needed. Returning a value appends it to the database name
|
||||
// if(oeContext.DataSource.Exists(objectMetadata as DataSourceObjectMetadata)) return "Online";
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Index = Microsoft.SqlServer.Management.Smo.Index;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Subtye for keys
|
||||
/// </summary>
|
||||
internal partial class KeysChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return IndexCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sub types and custom name for indexes
|
||||
/// </summary>
|
||||
internal partial class IndexesChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
private readonly Lazy<List<NodeSmoProperty>> smoPropertiesLazy = new Lazy<List<NodeSmoProperty>>(() => new List<NodeSmoProperty>
|
||||
{
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "IsUnique",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "IsClustered",
|
||||
ValidFor = ValidForFlag.All
|
||||
},
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "IndexKeyType",
|
||||
ValidFor = ValidForFlag.All
|
||||
}
|
||||
});
|
||||
|
||||
public override IEnumerable<NodeSmoProperty> SmoProperties => smoPropertiesLazy.Value;
|
||||
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return IndexCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return IndexCustomeNodeHelper.GetCustomLabel(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sub type for UserDefinedTableTypeKeys
|
||||
/// </summary>
|
||||
internal partial class UserDefinedTableTypeKeysChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return IndexCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class IndexCustomeNodeHelper
|
||||
{
|
||||
internal static string GetCustomLabel(object context)
|
||||
{
|
||||
Index index = context as Index;
|
||||
if (index != null)
|
||||
{
|
||||
string name = index.Name;
|
||||
string unique = index.IsUnique ? SR.UniqueIndex_LabelPart : SR.NonUniqueIndex_LabelPart;
|
||||
string clustered = index.IsClustered ? SR.ClusteredIndex_LabelPart : SR.NonClusteredIndex_LabelPart;
|
||||
name = name + $" ({unique}, {clustered})";
|
||||
return name;
|
||||
}
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
|
||||
internal static string GetSubType(object context)
|
||||
{
|
||||
|
||||
Index index = context as Index;
|
||||
if (index != null)
|
||||
{
|
||||
switch (index.IndexKeyType)
|
||||
{
|
||||
case IndexKeyType.DriPrimaryKey:
|
||||
return "PrimaryKey";
|
||||
case IndexKeyType.DriUniqueKey:
|
||||
return "UniqueKey";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ForeignKey foreignKey = context as ForeignKey;
|
||||
if (foreignKey != null)
|
||||
{
|
||||
return "ForeignKey";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Status for logins
|
||||
/// </summary>
|
||||
internal partial class ServerLevelLoginsChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeStatus(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return LoginCustomNodeHelper.GetStatus(objectMetadata);
|
||||
}
|
||||
|
||||
private readonly Lazy<List<NodeSmoProperty>> smoPropertiesLazy = new Lazy<List<NodeSmoProperty>>(() => new List<NodeSmoProperty>
|
||||
{
|
||||
new NodeSmoProperty
|
||||
{
|
||||
Name = "IsDisabled",
|
||||
ValidFor = ValidForFlag.All
|
||||
}
|
||||
});
|
||||
|
||||
public override IEnumerable<NodeSmoProperty> SmoProperties => smoPropertiesLazy.Value;
|
||||
}
|
||||
|
||||
internal static class LoginCustomNodeHelper
|
||||
{
|
||||
internal static string GetStatus(object context)
|
||||
{
|
||||
Login login = context as Login;
|
||||
if (login != null)
|
||||
{
|
||||
if (login.IsDisabled)
|
||||
{
|
||||
return "Disabled";
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom name for parameters
|
||||
/// </summary>
|
||||
internal partial class TableValuedFunctionParametersChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetCustomLabel(objectMetadata, oeContext);
|
||||
}
|
||||
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom name for parameters
|
||||
/// </summary>
|
||||
internal partial class ScalarValuedFunctionParametersChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetCustomLabel(objectMetadata, oeContext);
|
||||
}
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom name for parameters
|
||||
/// </summary>
|
||||
internal partial class AggregateFunctionParametersChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetCustomLabel(objectMetadata, oeContext);
|
||||
}
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom name for parameters
|
||||
/// </summary>
|
||||
internal partial class StoredProcedureParametersChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodeCustomName(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetCustomLabel(objectMetadata, oeContext);
|
||||
}
|
||||
public override string GetNodeSubType(object objectMetadata, QueryContext oeContext)
|
||||
{
|
||||
return ParameterCustomeNodeHelper.GetSubType(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
static class ParameterCustomeNodeHelper
|
||||
{
|
||||
internal static string GetSubType(object context)
|
||||
{
|
||||
Parameter parameter = context as Parameter;
|
||||
if (parameter != null)
|
||||
{
|
||||
StoredProcedureParameter stordProcedureParameter = parameter as StoredProcedureParameter;
|
||||
if (stordProcedureParameter != null && stordProcedureParameter.IsOutputParameter)
|
||||
{
|
||||
return "Output";
|
||||
}
|
||||
return "Input";
|
||||
//TODO return parameters
|
||||
}
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
|
||||
internal static string GetCustomLabel(object context, QueryContext oeContext)
|
||||
{
|
||||
Parameter parameter = context as Parameter;
|
||||
if (parameter != null)
|
||||
{
|
||||
return GetParameterCustomLabel(parameter);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
internal static string GetParameterCustomLabel(Parameter parameter)
|
||||
{
|
||||
string label = parameter.Name;
|
||||
string defaultString = SR.SchemaHierarchy_SubroutineParameterNoDefaultLabel;
|
||||
string inputOutputString = SR.SchemaHierarchy_SubroutineParameterInputLabel;
|
||||
string typeName = parameter.DataType.ToString();
|
||||
|
||||
if (parameter.DefaultValue != null &&
|
||||
!string.IsNullOrEmpty(parameter.DefaultValue))
|
||||
{
|
||||
defaultString = SR.SchemaHierarchy_SubroutineParameterDefaultLabel;
|
||||
}
|
||||
|
||||
StoredProcedureParameter stordProcedureParameter = parameter as StoredProcedureParameter;
|
||||
if (stordProcedureParameter != null && stordProcedureParameter.IsOutputParameter)
|
||||
{
|
||||
inputOutputString = SR.SchemaHierarchy_SubroutineParameterInputOutputLabel;
|
||||
if (parameter.IsReadOnly)
|
||||
{
|
||||
inputOutputString = SR.SchemaHierarchy_SubroutineParameterInputOutputReadOnlyLabel;
|
||||
}
|
||||
}
|
||||
else if (parameter.IsReadOnly)
|
||||
{
|
||||
inputOutputString = SR.SchemaHierarchy_SubroutineParameterInputReadOnlyLabel;
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
SR.SchemaHierarchy_SubroutineParameterLabelFormatString,
|
||||
label,
|
||||
typeName,
|
||||
inputOutputString,
|
||||
defaultString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// 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.Data;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.Extensibility;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="DataSourceQuerier"/> handles SMO queries for one or more SMO object types.
|
||||
/// The <see cref="SupportedObjectTypes"/> property defines which types can be queried.
|
||||
///
|
||||
/// To query multiple
|
||||
/// </summary>
|
||||
public abstract class DataSourceQuerier : IComposableService
|
||||
{
|
||||
private static object lockObject = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Queries SMO for a collection of objects using the <see cref="QueryContext"/>
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public abstract IEnumerable<DataSourceObjectMetadata> Query(QueryContext context, string filter, bool refresh, IEnumerable<string> extraProperties);
|
||||
|
||||
internal IMultiServiceProvider ServiceProvider
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void SetServiceProvider(IMultiServiceProvider provider)
|
||||
{
|
||||
ServiceProvider = provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the data to data reader is possible
|
||||
/// </summary>
|
||||
protected IDataReader GetDataReader(object data)
|
||||
{
|
||||
IDataReader reader = null;
|
||||
if (data is IDataReader)
|
||||
{
|
||||
|
||||
reader = data as IDataReader;
|
||||
}
|
||||
else if(data is DataTable)
|
||||
{
|
||||
reader = ((DataTable)data).CreateDataReader();
|
||||
}
|
||||
|
||||
else if (data is DataSet)
|
||||
{
|
||||
reader = ((DataSet)data).Tables[0].CreateDataReader();
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mthod used to do custom filtering on smo objects if cannot be implemented using the filters
|
||||
/// </summary>
|
||||
protected virtual bool PassesFinalFilters(SqlSmoObject parent, SqlSmoObject objectMetadata)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which platforms the querier is valid for
|
||||
/// </summary>
|
||||
public virtual ValidForFlag ValidFor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ValidForFlag.All;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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.Extensibility;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Context object containing key properties needed to query for SMO objects
|
||||
/// </summary>
|
||||
public class QueryContext
|
||||
{
|
||||
public IDataSource DataSource { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a context object with a server to use as the basis for any queries
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
public QueryContext(IDataSource dataSource, IMultiServiceProvider serviceProvider)
|
||||
{
|
||||
DataSource = dataSource;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent of a give node to use for queries
|
||||
/// </summary>
|
||||
public DataSourceObjectMetadata ParentObjectMetadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A query loader that can be used to find <see cref="DataSourceQuerier"/> objects
|
||||
/// for specific SMO types
|
||||
/// </summary>
|
||||
public IMultiServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to cast a parent to a specific type
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T ParentAs<T>()
|
||||
where T : TreeNode
|
||||
{
|
||||
return ParentObjectMetadata as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the context for use by another node
|
||||
/// </summary>
|
||||
/// <param name="parent">New Parent to set</param>
|
||||
/// <returns>new <see cref="QueryContext"/> with all fields except <see cref="ParentObjectMetadata"/> the same</returns>
|
||||
public QueryContext CopyWithParent(DataSourceObjectMetadata parent)
|
||||
{
|
||||
QueryContext context = new QueryContext(this.DataSource, this.ServiceProvider)
|
||||
{
|
||||
ParentObjectMetadata = parent
|
||||
};
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<#@ template debug="false" hostspecific="true" language="C#" #>
|
||||
<#@ output extension=".cs" #>
|
||||
<#@ assembly name="System.Xml.dll" #>
|
||||
<#@ import namespace="System" #>
|
||||
<#@ import namespace="System.Globalization" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Xml" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ import namespace="System.IO" #>
|
||||
// This file was generated by a T4 Template. Do not modify directly, instead update the SmoQueryModelDefinition.xml file
|
||||
// and re-run the T4 template. This can be done in Visual Studio by right-click in and choosing "Run Custom Tool",
|
||||
// or from the command-line on any platform by running "build.cmd -Target=CodeGen" or "build.sh -Target=CodeGen".
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Smo.Broker;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.SmoModel
|
||||
{
|
||||
<#
|
||||
var directory = Path.GetDirectoryName(Host.TemplateFile);
|
||||
string xmlFile = Path.Combine(directory, "SmoQueryModelDefinition.xml");
|
||||
|
||||
/////////
|
||||
// Now generate all the Query methods
|
||||
/////////
|
||||
var allNodes = GetNodes(xmlFile);
|
||||
var indent = " ";
|
||||
foreach (var nodeName in allNodes)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, nodeName);
|
||||
IList<string> parents = GetParents(nodeElement, xmlFile, nodeName);
|
||||
string nodeType = GetNodeType(nodeElement, nodeName);
|
||||
var validFor = nodeElement.GetAttribute("ValidFor");
|
||||
|
||||
string queryBaseClass = "SmoQuerier";
|
||||
PushIndent(indent);
|
||||
WriteLine("");
|
||||
WriteLine(string.Format("[Export(typeof({0}))]", queryBaseClass));
|
||||
WriteLine(string.Format("internal partial class {0}Querier: {1}", nodeName, queryBaseClass));
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
|
||||
// Supported Types
|
||||
WriteLine("Type[] supportedTypes = new Type[] { typeof("+ nodeType + ") };");
|
||||
if (!string.IsNullOrWhiteSpace(validFor))
|
||||
{
|
||||
WriteLine("");
|
||||
WriteLine(string.Format("public override ValidForFlag ValidFor {{ get {{ return {0}; }} }}", GetValidForFlags(validFor)));
|
||||
WriteLine("");
|
||||
}
|
||||
|
||||
WriteLine("");
|
||||
WriteLine("public override Type[] SupportedObjectTypes { get { return supportedTypes; } }");
|
||||
WriteLine("");
|
||||
|
||||
// Query impl
|
||||
WriteLine("public override IEnumerable<SqlSmoObject> Query(SmoQueryContext context, string filter, bool refresh, IEnumerable<string> extraProperties)");
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
|
||||
// TODO Allow override of the navigation path
|
||||
foreach(var parentType in parents)
|
||||
{
|
||||
string parentVar = string.Format("parent{0}", parentType);
|
||||
WriteLine(string.Format("{0} {1} = context.Parent as {0};", parentType, parentVar));
|
||||
WriteLine(string.Format("if ({0} != null)", parentVar));
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
|
||||
XmlElement navPathElement = GetNavPathElement(xmlFile, nodeName, parentType);
|
||||
string navigationPath = GetNavigationPath(nodeElement, nodeName, navPathElement);
|
||||
string subField = GetNavPathAttribute(navPathElement, "SubField");
|
||||
string fieldType = GetNavPathAttribute(navPathElement, "FieldType");
|
||||
|
||||
|
||||
WriteLine(string.Format("var retValue = {0}.{1};", parentVar, navigationPath));
|
||||
WriteLine("if (retValue != null)");
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
|
||||
|
||||
if (IsCollection(nodeElement))
|
||||
{
|
||||
WriteLine(string.Format("retValue.ClearAndInitialize(filter, extraProperties);"));
|
||||
if (string.IsNullOrEmpty(subField) )
|
||||
{
|
||||
WriteLine(string.Format("return new SmoCollectionWrapper<{0}>(retValue).Where(c => PassesFinalFilters({1}, c));", nodeType, parentVar));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine(string.Format("List<{0}> subFieldResult = new List<{0}>();", nodeType));
|
||||
WriteLine(string.Format("foreach({0} field in retValue)", fieldType));
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
WriteLine(string.Format("{0} subField = field.{1};", nodeType, subField));
|
||||
WriteLine(string.Format("if (subField != null)"));
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
WriteLine(string.Format("subFieldResult.Add(subField);"));
|
||||
PopIndent();
|
||||
WriteLine("}");
|
||||
PopIndent();
|
||||
WriteLine("}");
|
||||
WriteLine(string.Format("return subFieldResult.Where(c => PassesFinalFilters({1}, c));", nodeType, parentVar));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("if (refresh)");
|
||||
WriteLine("{");
|
||||
PushIndent(indent);
|
||||
WriteLine(string.Format("{0}.{1}.Refresh();", parentVar, navigationPath));
|
||||
PopIndent();
|
||||
WriteLine("}");
|
||||
WriteLine("return new SqlSmoObject[] { retValue };");
|
||||
}
|
||||
|
||||
PopIndent();
|
||||
WriteLine("}");
|
||||
PopIndent();
|
||||
WriteLine("}"); // close If
|
||||
}
|
||||
|
||||
WriteLine("return Enumerable.Empty<SqlSmoObject>();");
|
||||
|
||||
PopIndent();
|
||||
WriteLine("}"); // close Query method
|
||||
PopIndent();
|
||||
WriteLine("}"); // close Class
|
||||
PopIndent();
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
<#+
|
||||
|
||||
public static string[] GetNodes(string xmlFile)
|
||||
{
|
||||
List<string> typesList = new List<string>();
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
XmlNodeList treeTypes = doc.SelectNodes("/SmoQueryModel/Node");
|
||||
if (treeTypes != null)
|
||||
{
|
||||
foreach (var type in treeTypes)
|
||||
{
|
||||
XmlElement element = type as XmlElement;
|
||||
if (element != null)
|
||||
{
|
||||
typesList.Add(element.GetAttribute("Name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return typesList.ToArray();
|
||||
}
|
||||
|
||||
public static XmlElement GetNodeElement(string xmlFile, string nodeName)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
return (XmlElement)doc.SelectSingleNode(string.Format("/SmoQueryModel/Node[@Name='{0}']", nodeName));
|
||||
}
|
||||
|
||||
public static XmlElement GetNavPathElement(string xmlFile, string nodeName, string parent)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
XmlElement navPathElement = (XmlElement)doc.SelectSingleNode(string.Format("/SmoQueryModel/Node[@Name='{0}']/NavigationPath[@Parent='{1}']", nodeName, parent));
|
||||
|
||||
return navPathElement;
|
||||
}
|
||||
|
||||
public static string GetNavPathAttribute(XmlElement navPathElement, string attributeName)
|
||||
{
|
||||
return navPathElement == null ? null : navPathElement.GetAttribute(attributeName);
|
||||
}
|
||||
|
||||
public static string GetNavigationPath(XmlElement nodeElement, string nodeName, XmlElement navPathElement)
|
||||
{
|
||||
string navPathField = GetNavPathAttribute(navPathElement, "Field");
|
||||
if (!string.IsNullOrEmpty(navPathField))
|
||||
{
|
||||
return navPathField;
|
||||
}
|
||||
// else use pluralized type as this is the most common scenario
|
||||
string nodeType = GetNodeType(nodeElement, nodeName);
|
||||
|
||||
string nodeTypeAccessor = IsCollection(nodeElement) ? string.Format("{0}s", nodeType) : nodeType;
|
||||
return nodeTypeAccessor;
|
||||
}
|
||||
|
||||
public static string GetNodeType(XmlElement nodeElement, string nodeName)
|
||||
{
|
||||
var type = nodeElement.GetAttribute("Type");
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
// Otherwise assume the type is the node name without "Sql" at the start
|
||||
var prefix = "Sql";
|
||||
return nodeName.IndexOf(prefix) == 0 ? nodeName.Substring(prefix.Length) : nodeName;
|
||||
}
|
||||
|
||||
public static bool IsCollection(XmlElement nodeElement)
|
||||
{
|
||||
var collection = nodeElement.GetAttribute("Collection");
|
||||
bool result;
|
||||
if (bool.TryParse(collection, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
// Default is true
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IList<string> GetParents(XmlElement nodeElement, string xmlFile, string parentName)
|
||||
{
|
||||
var parentAttr = nodeElement.GetAttribute("Parent");
|
||||
if (!string.IsNullOrEmpty(parentAttr))
|
||||
{
|
||||
return new string[] { parentAttr };
|
||||
}
|
||||
|
||||
var parentNodes = GetChildren(xmlFile, parentName, "Parent");
|
||||
if (parentNodes != null && parentNodes.Count > 0)
|
||||
{
|
||||
List<string> parents = new List<string>();
|
||||
foreach(var node in parentNodes)
|
||||
{
|
||||
parents.Add(node.InnerText);
|
||||
}
|
||||
return parents;
|
||||
}
|
||||
|
||||
// default to assuming a type is under Database
|
||||
return new string[] { "Database" };
|
||||
}
|
||||
|
||||
public static List<XmlElement> GetChildren(string xmlFile, string parentName, string childNode)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
|
||||
List<XmlElement> retElements = new List<XmlElement>();
|
||||
XmlNodeList nodeList = doc.SelectNodes(string.Format("/SmoQueryModel/Node[@Name='{0}']/{1}", parentName, childNode));
|
||||
foreach (var item in nodeList)
|
||||
{
|
||||
XmlElement itemAsElement = item as XmlElement;
|
||||
if (itemAsElement != null)
|
||||
{
|
||||
retElements.Add(itemAsElement);
|
||||
}
|
||||
}
|
||||
return retElements;
|
||||
}
|
||||
|
||||
public static string GetValidForFlags(string validForStr)
|
||||
{
|
||||
List<string> flags = new List<string>();
|
||||
if (validForStr.Contains("Sql2005"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2005");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2008"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2008");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2012"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2012");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2014"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2014");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2016"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2016");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2017"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2017");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("AzureV12"))
|
||||
{
|
||||
flags.Add("ValidForFlag.AzureV12");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("AllOnPrem"))
|
||||
{
|
||||
flags.Add("ValidForFlag.AllOnPrem");
|
||||
}
|
||||
if (validForStr.Contains("AllAzure"))
|
||||
{
|
||||
flags.Add("ValidForFlag.AllAzure");
|
||||
}
|
||||
if (validForStr.Contains("NotSqlDw"))
|
||||
{
|
||||
flags.Add("ValidForFlag.NotSqlDw");
|
||||
}
|
||||
if (validForStr == "All")
|
||||
{
|
||||
flags.Add("ValidForFlag.All");
|
||||
}
|
||||
|
||||
return string.Join("|", flags);
|
||||
}
|
||||
|
||||
#>
|
||||
@@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
This XML file defines how to query for a given SMO type based on a parent/child or
|
||||
XPath relationship. From this file, the paths to query each type should be buildable
|
||||
into code using a T4 template.
|
||||
|
||||
Key properties:
|
||||
Name: This maps
|
||||
Type: Optional SMO type. If not specified, the Name without the Sql prefix is used
|
||||
Parent: Expected parent type. Needed to codegen the response. if there are multiple parents
|
||||
then each one
|
||||
NavigationPath: For types whose access path differs based on parent or needs custom navigation
|
||||
this can be used.
|
||||
|
||||
-->
|
||||
<SmoQueryModel>
|
||||
<!--<Node Name="SqlServer"/> -->
|
||||
<Node Name="SqlDatabase" Type="Database" Parent="Server" />
|
||||
<Node Name="SqlLinkedServerLogin" Type="LinkedServer" Parent="Server"/>
|
||||
<Node Name="SqlLogin" Type="Login" Parent="Server" />
|
||||
<Node Name="SqlServerRole" Type="" Parent="Server" >
|
||||
<NavigationPath Parent="Server" Field="Roles" />
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlCredential" Parent="Server" />
|
||||
<Node Name="SqlCryptographicProvider" Parent="Server" />
|
||||
<Node Name="SqlServerAudit" Type="Audit" Parent="Server" />
|
||||
<Node Name="SqlServerAuditSpecification" Type="ServerAuditSpecification" Parent="Server"/>
|
||||
|
||||
<Node Name="SqlEndpoint" Parent="Server"/>
|
||||
<Node Name="SqlLinkedServer" Parent="Server" />
|
||||
<Node Name="SqlServerDdlTrigger" Parent="Server" ValidFor="NotSqlDw" >
|
||||
<NavigationPath Parent="Server" Field="Triggers" />
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlErrorMessage" Type="UserDefinedMessage" Parent="Server" />
|
||||
|
||||
<Node Name="SqlTable" Parent="Database" />
|
||||
<Node Name="SqlHistoryTable" Type="Table" Parent="Table" >
|
||||
<NavigationPath Parent="Table" Type="Table" Field="Parent.Tables" />
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlView" Parent="Database" />
|
||||
|
||||
<Node Name="SqlSynonym" Parent="Database" ValidFor="NotSqlDw" />
|
||||
|
||||
<Node Name="SqlColumn" Parent="TableViewTableTypeBase"/>
|
||||
<Node Name="SqlIndex" Parent="TableViewTableTypeBase">
|
||||
<NavigationPath Parent="TableViewTableTypeBase" Field="Indexes" />
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlCheck" Parent="Table"/>
|
||||
<Node Name="SqlForeignKeyConstraint" Type="ForeignKey" Parent="Table" />
|
||||
<Node Name="SqlDefaultConstraint" Collection="True" >
|
||||
<Parent>Table</Parent>
|
||||
<Parent>UserDefinedTableType</Parent>
|
||||
<NavigationPath Parent="Table" Field="Columns" SubField="DefaultConstraint" FieldType="Column" />
|
||||
<NavigationPath Parent="UserDefinedTableType" Field="Columns" SubField="DefaultConstraint" FieldType="Column" />
|
||||
</Node>
|
||||
<Node Name="SqlDmlTrigger" Type="Trigger" ValidFor="NotSqlDw">
|
||||
<Parent>Table</Parent>
|
||||
<Parent>View</Parent>
|
||||
</Node>
|
||||
<Node Name="SqlFullTextIndex" Parent="Table" Collection="False" ValidFor="NotSqlDw" />
|
||||
<Node Name="SqlStatistic" Parent="TableViewBase"/>
|
||||
|
||||
<Node Name="SqlDatabaseDdlTrigger" Type="DatabaseDdlTrigger" Parent="Database" ValidFor="NotSqlDw">
|
||||
<NavigationPath Parent="Database" Field="Triggers" />
|
||||
</Node>
|
||||
<Node Name="SqlAssembly" Type="SqlAssembly" Parent="Database" >
|
||||
<NavigationPath Parent="Database" Field="Assemblies" />
|
||||
</Node>
|
||||
|
||||
<!-- Deprecated
|
||||
<Node Name="SqlRule" Parent="Database" />
|
||||
<Node Name="SqlDefault" Parent="Database" />
|
||||
-->
|
||||
<Node Name="SqlSequence" Parent="Database" />
|
||||
|
||||
<Node Name="SqlUserDefinedDataType" Parent="Database" ValidFor="NotSqlDw" />
|
||||
|
||||
<Node Name="SqlUserDefinedTableType" Parent="Database" />
|
||||
<Node Name="SqlXmlSchemaCollection" />
|
||||
<Node Name="SqlUserDefinedType" />
|
||||
|
||||
<Node Name="SqlUserDefinedFunction" />
|
||||
|
||||
<Node Name="SqlUserDefinedAggregate" />
|
||||
|
||||
<Node Name="SqlFileGroup" />
|
||||
<Node Name="SqlFile" Type="DataFile" Parent="FileGroup" >
|
||||
<NavigationPath Parent="FileGroup" Field="Files" />
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlFullTextCatalog"/>
|
||||
<Node Name="SqlFullTextStopList" />
|
||||
<Node Name="SqlPartitionFunction"/>
|
||||
<Node Name="SqlPartitionScheme"/>
|
||||
<Node Name="SqlSearchPropertyList" />
|
||||
<Node Name="SqlUser"/>
|
||||
<Node Name="SqlSchema"/>
|
||||
<Node Name="SqlAsymmetricKey" />
|
||||
<Node Name="SqlCertificate" />
|
||||
<Node Name="SqlSymmetricKey" />
|
||||
<Node Name="SqlDatabaseEncryptionKey" Collection="False" />
|
||||
<Node Name="SqlMasterKey" Collection="False" />
|
||||
<Node Name="SqlDatabaseAuditSpecification" />
|
||||
<Node Name="SqlSecurityPolicy" >
|
||||
<NavigationPath Parent="Database" Field="SecurityPolicies" />
|
||||
</Node>
|
||||
<Node Name="SqlDatabaseCredential" Type="DatabaseScopedCredential"/>
|
||||
<Node Name="SqlRole" Type="DatabaseRole" >
|
||||
<NavigationPath Parent="Database" Field="Roles" />
|
||||
</Node>
|
||||
<Node Name="SqlApplicationRole" />
|
||||
<Node Name="SqlColumnMasterKey" />
|
||||
<Node Name="SqlColumnEncryptionKey" />
|
||||
|
||||
<Node Name="SqlServiceBroker" Type="ServiceBroker" Collection="False" />
|
||||
<Node Name="SqlService" Type="BrokerService" Parent="ServiceBroker" >
|
||||
<NavigationPath Parent="ServiceBroker" Field="Services" />
|
||||
</Node>
|
||||
-
|
||||
<Node Name="SqlContract" Type="ServiceContract" Parent="ServiceBroker" />
|
||||
<Node Name="SqlQueue" Type="ServiceQueue" Parent="ServiceBroker" >
|
||||
<NavigationPath Parent="ServiceBroker" Field="Queues" />
|
||||
</Node>
|
||||
<Node Name="SqlRemoteServiceBinding" Parent="ServiceBroker" />
|
||||
<Node Name="SqlBrokerPriority" Parent="ServiceBroker" >
|
||||
<NavigationPath Parent="ServiceBroker" Field="Priorities" />
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlMessageType" Parent="ServiceBroker"/>
|
||||
|
||||
<Node Name="SqlExternalDataSource" />
|
||||
<Node Name="SqlExternalFileFormat" />
|
||||
|
||||
<Node Name="SqlProcedure" Type="StoredProcedure"/>
|
||||
<Node Name="SqlExtendedStoredProcedure" />
|
||||
<Node Name="SqlSubroutineParameter" Type="Parameter" >
|
||||
<Parent>StoredProcedure</Parent>
|
||||
<Parent>UserDefinedAggregate</Parent>
|
||||
<Parent>UserDefinedFunction</Parent>
|
||||
</Node>
|
||||
|
||||
<Node Name="SqlPartitionFunctionParameter" Parent="PartitionFunction" />
|
||||
|
||||
<Node Name="SqlBuiltInType" Type="SystemDataType" Parent="Database">
|
||||
<NavigationPath Parent="Database" Field="Parent.SystemDataTypes" FieldForUrn="Parent" />
|
||||
</Node>
|
||||
<!-- TODO Enable all types
|
||||
<Node Name="SqlRoute"/>
|
||||
-->
|
||||
<!-- Signatures appear to be missing entirely from SMO and SSMS object explorer...
|
||||
<Node Name="SqlSignature" />
|
||||
-->
|
||||
<!-- TODO find mapping - exists in SSDT but not SSMS / SMO?
|
||||
<Node Name="SqlEventNotification" Parent="ServiceBroker" />
|
||||
-->
|
||||
<!-- TODO Requires XEvents SMO DLL in .Net Core
|
||||
<Node Name="SqlEventSession" Type="Session" />
|
||||
<Node Name="SqlServerEventNotification" Type="Event" Parent="Server" />
|
||||
-->
|
||||
</SmoQueryModel>
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
|
||||
|
||||
// TODOKusto: Remove this file. These classes might not be needed.
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom name for table
|
||||
/// </summary>
|
||||
internal partial class TablesChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
// TODOKusto: If we are always passing DataSourceMetadataObject, stop passing object. Make it type safe.
|
||||
public override string GetNodePathName(object objectMetadata)
|
||||
{
|
||||
return base.GetNodePathName(objectMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom name for history table
|
||||
/// </summary>
|
||||
internal partial class TableChildFactory : DataSourceChildFactoryBase
|
||||
{
|
||||
public override string GetNodePathName(object objectMetadata)
|
||||
{
|
||||
return base.GetNodePathName(objectMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource;
|
||||
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
|
||||
{
|
||||
/// <summary>
|
||||
/// A Node in the tree representing a SMO-based object
|
||||
/// </summary>
|
||||
public class DataSourceTreeNode : TreeNode
|
||||
{
|
||||
public static int FolderSortPriority = 0;
|
||||
private static int _nextSortPriority = FolderSortPriority + 1; // 0 is reserved for folders
|
||||
|
||||
protected QueryContext context;
|
||||
|
||||
public DataSourceTreeNode(IDataSource dataSource, DataSourceObjectMetadata objectMetadata)
|
||||
: base(dataSource, objectMetadata)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which platforms a node is valid for
|
||||
/// </summary>
|
||||
public ValidForFlag ValidFor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an incrementing sort priority value to assist in automatically sorting
|
||||
/// elements in a tree
|
||||
/// </summary>
|
||||
public static int NextSortPriority
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.Threading.Interlocked.Increment(ref _nextSortPriority);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void CacheInfoFromModel(DataSourceObjectMetadata objectMetadata)
|
||||
{
|
||||
base.ObjectMetadata = objectMetadata;
|
||||
NodeValue = objectMetadata.Name;
|
||||
}
|
||||
|
||||
public virtual DataSourceObjectMetadata GetParentObjectMetadata()
|
||||
{
|
||||
if (ObjectMetadata != null)
|
||||
{
|
||||
return ObjectMetadata;
|
||||
}
|
||||
// Return the parent's object, or null if it's not set / not a OETreeNode
|
||||
return ParentAs<DataSourceTreeNode>()?.GetParentObjectMetadata();
|
||||
}
|
||||
|
||||
public override object GetContext()
|
||||
{
|
||||
EnsureContextInitialized();
|
||||
return context;
|
||||
}
|
||||
|
||||
protected virtual void EnsureContextInitialized()
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
DataSourceObjectMetadata oeParent = GetParentObjectMetadata();
|
||||
QueryContext parentContext = Parent?.GetContextAs<QueryContext>();
|
||||
if (oeParent != null && parentContext != null)
|
||||
{
|
||||
context = parentContext.CopyWithParent(oeParent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ServerExplorerTree>
|
||||
<Node Name="Server" LocLabel="SR.SchemaHierarchy_Server" Image="Server">
|
||||
<Child Name="Databases"/>
|
||||
<Child Name="ServerLevelSecurity"/>
|
||||
<Child Name="ServerLevelServerObjects"/>
|
||||
</Node>
|
||||
<Node Name="Databases" LocLabel="SR.SchemaHierarchy_Databases" IsAsyncLoad="" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlDatabase" TreeNode="DatabaseTreeNode">
|
||||
<Filters >
|
||||
<Filter Property="IsSystemObject" Value="0" Type="bool" />
|
||||
</Filters>
|
||||
<Properties>
|
||||
<Property Name="Status" ValidFor="All"/>
|
||||
</Properties>
|
||||
<Child Name="SystemDatabases" IsSystemObject="1"/>
|
||||
</Node>
|
||||
<Node Name="ServerLevelSecurity" LocLabel="SR.SchemaHierarchy_Security" BaseClass="ModelBased" ValidFor="All">
|
||||
<Child Name="ServerLevelLinkedServerLogins"/>
|
||||
<Child Name="ServerLevelLogins"/>
|
||||
<Child Name="ServerLevelServerRoles"/>
|
||||
<Child Name="ServerLevelCredentials"/>
|
||||
<Child Name="ServerLevelCryptographicProviders"/>
|
||||
<Child Name="ServerLevelServerAudits"/>
|
||||
<Child Name="ServerLevelServerAuditSpecifications"/>
|
||||
<!-- TODO Support XEvents in .Net Core SMO
|
||||
<Child Name="ServerLevelEventSessions"/>
|
||||
-->
|
||||
</Node>
|
||||
<Node Name="ServerLevelServerObjects" LocLabel="SR.SchemaHierarchy_ServerObjects" BaseClass="ModelBased" NodeType="ServerLevelServerObject" ValidFor="AllOnPrem">
|
||||
<Child Name="ServerLevelEndpoints"/>
|
||||
<Child Name="ServerLevelLinkedServers"/>
|
||||
<Child Name="ServerLevelServerTriggers"/>
|
||||
<Child Name="ServerLevelErrorMessages"/>
|
||||
<!-- TODO Support XEvents in .Net Core SMO
|
||||
<Child Name="ServerLevelEventNotifications"/>
|
||||
-->
|
||||
</Node>
|
||||
|
||||
<Node Name="SystemDatabases" LocLabel="SR.SchemaHierarchy_SystemDatabases" BaseClass="ModelBased" NodeType="SystemDatabase" ChildQuerierTypes="SqlDatabase" Strategy="MultipleElementsOfType" TreeNode="DatabaseTreeNode" ValidFor="All">
|
||||
<Filters >
|
||||
<Filter Property="IsSystemObject" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<!-- TODO Support XEvents in .Net Core SMO
|
||||
<Node Name="ServerLevelEventSessions" LocLabel="SR.SchemaHierarchy_EventSessions" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlEventSession" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017"/>
|
||||
<Node Name="ServerLevelEventNotifications" LocLabel="SR.SchemaHierarchy_ServerEventNotifications" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlServerEventNotification"/>
|
||||
-->
|
||||
|
||||
<Node Name="ServerLevelLinkedServerLogins" LocLabel="SR.SchemaHierarchy_LinkedServerLogins" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelLinkedServerLogin" ChildQuerierTypes="SqlLinkedServerLogin" ValidFor="AllOnPrem"/>
|
||||
<Node Name="ServerLevelLogins" LocLabel="SR.SchemaHierarchy_Logins" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelLogin" ChildQuerierTypes="SqlLogin"/>
|
||||
<Node Name="ServerLevelServerRoles" LocLabel="SR.SchemaHierarchy_ServerRoles" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelServerRole" ChildQuerierTypes="SqlServerRole" ValidFor="AllOnPrem"/>
|
||||
<Node Name="ServerLevelCredentials" LocLabel="SR.SchemaHierarchy_Credentials" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelCredential" ChildQuerierTypes="SqlCredential" ValidFor="AllOnPrem"/>
|
||||
<Node Name="ServerLevelCryptographicProviders" LocLabel="SR.SchemaHierarchy_CryptographicProviders" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelCryptographicProvider" ChildQuerierTypes="SqlCryptographicProvider" ValidFor="AllOnPrem"/>
|
||||
<Node Name="ServerLevelServerAudits" LocLabel="SR.SchemaHierarchy_ServerAudits" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelServerAudit" ChildQuerierTypes="SqlServerAudit" ValidFor="AllOnPrem"/>
|
||||
<Node Name="ServerLevelServerAuditSpecifications" LocLabel="SR.SchemaHierarchy_ServerAuditSpecifications" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelServerAuditSpecification" ChildQuerierTypes="SqlServerAuditSpecification" ValidFor="AllOnPrem"/>
|
||||
|
||||
<Node Name="ServerLevelEndpoints" LocLabel="SR.SchemaHierarchy_Endpoints" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelEndpoint" ChildQuerierTypes="SqlEndpoint" ValidFor="AllOnPrem"/>
|
||||
<Node Name="ServerLevelLinkedServers" LocLabel="SR.SchemaHierarchy_LinkedServers" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelLinkedServer" ChildQuerierTypes="SqlLinkedServer"/>
|
||||
<Node Name="ServerLevelServerTriggers" LocLabel="SR.SchemaHierarchy_ServerTriggers" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelServerTrigger" ChildQuerierTypes="SqlServerDdlTrigger"/>
|
||||
<Node Name="ServerLevelErrorMessages" LocLabel="SR.SchemaHierarchy_ErrorMessages" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="ServerLevelErrorMessage" ChildQuerierTypes="SqlErrorMessage"/>
|
||||
|
||||
<Node Name="Database" LocLabel="string.Empty" Image="Database" BaseClass="ModelBased" NodeType="Database" IsAsyncLoad="" Strategy="CreateModel">
|
||||
<Child Name="Tables"/>
|
||||
<Child Name="Views"/>
|
||||
<Child Name="Synonyms"/>
|
||||
<Child Name="Programmability"/>
|
||||
<Child Name="ExternalResources"/>
|
||||
<Child Name="ServiceBroker"/>
|
||||
<Child Name="Storage"/>
|
||||
<Child Name="Security"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="Tables" LocLabel="SR.SchemaHierarchy_Tables" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlTable" TreeNode="TableTreeNode">
|
||||
<Filters >
|
||||
<Filter Property="IsSystemObject" Value="0" Type="bool" />
|
||||
<Filter Property="TemporalType" Type="Enum" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Value>TableTemporalType.None</Value>
|
||||
<Value>TableTemporalType.SystemVersioned</Value>
|
||||
</Filter>
|
||||
</Filters>
|
||||
<Properties>
|
||||
<Property Name="IsFileTable" ValidFor="Sql2012|Sql2014|Sql2016|Sql2017"/>
|
||||
<Property Name="IsSystemVersioned" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
<Property Name="TemporalType" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
<Property Name="IsExternal" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
</Properties>
|
||||
<Child Name="SystemTables" IsSystemObject="1"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="Views" LocLabel="SR.SchemaHierarchy_Views" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlView" TreeNode="ViewTreeNode">
|
||||
<Filters>
|
||||
<Filter Property="IsSystemObject" Value="0" Type="bool" />
|
||||
</Filters>
|
||||
<Child Name="SystemViews" IsSystemObject="1"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="Synonyms" LocLabel="SR.SchemaHierarchy_Synonyms" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="Synonym" ChildQuerierTypes="SqlSynonym" ValidFor="Sql2005|Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="Programmability" LocLabel="SR.SchemaHierarchy_Programmability" BaseClass="ModelBased">
|
||||
<Child Name="StoredProcedures"/>
|
||||
<Child Name="Functions"/>
|
||||
<Child Name="DatabaseTriggers"/>
|
||||
<Child Name="Assemblies"/>
|
||||
<Child Name="Types"/>
|
||||
<!--
|
||||
<Child Name="Rules"/>
|
||||
<Child Name="Defaults"/>
|
||||
-->
|
||||
<Child Name="Sequences"/>
|
||||
</Node>
|
||||
<Node Name="ExternalResources" LocLabel="SR.SchemaHierarchy_ExternalResources" BaseClass="ModelBased" TreeNode="ExternalResourceTreeNode" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Child Name="ExternalDataSources"/>
|
||||
<Child Name="ExternalFileFormats"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="ServiceBroker" LocLabel="SR.SchemaHierarchy_ServiceBroker" BaseClass="ModelBased" ValidFor="AllOnPrem">
|
||||
<Child Name="MessageTypes"/>
|
||||
<Child Name="Contracts"/>
|
||||
<Child Name="Queues"/>
|
||||
<Child Name="Services"/>
|
||||
<!--Child Name="Routes"/-->
|
||||
<Child Name="DatabaseAndQueueEventNotifications"/>
|
||||
<Child Name="RemoteServiceBindings"/>
|
||||
<Child Name="BrokerPriorities"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="Storage" LocLabel="SR.SchemaHierarchy_Storage" BaseClass="ModelBased" ValidFor="AllOnPrem|AzureV12">
|
||||
<Child Name="FileGroups"/>
|
||||
<Child Name="FullTextCatalogs"/>
|
||||
<Child Name="FullTextStopLists"/>
|
||||
<Child Name="SqlLogFiles"/>
|
||||
<Child Name="PartitionFunctions"/>
|
||||
<Child Name="PartitionSchemes"/>
|
||||
<Child Name="SearchPropertyLists"/>
|
||||
</Node>
|
||||
<Node Name="Security" LocLabel="SR.SchemaHierarchy_Security" BaseClass="ModelBased" >
|
||||
<Child Name="Users"/>
|
||||
<Child Name="Roles"/>
|
||||
<Child Name="Schemas"/>
|
||||
<Child Name="AsymmetricKeys"/>
|
||||
<Child Name="Certificates"/>
|
||||
<Child Name="SymmetricKeys"/>
|
||||
<Child Name="DatabaseScopedCredentials"/>
|
||||
<Child Name="DatabaseEncryptionKeys"/>
|
||||
<Child Name="MasterKeys"/>
|
||||
<Child Name="Signatures"/>
|
||||
<Child Name="DatabaseAuditSpecifications"/>
|
||||
<Child Name="SecurityPolicies"/>
|
||||
<Child Name="AlwaysEncryptedKeys"/>
|
||||
</Node>
|
||||
|
||||
<!-- Childs of Tables -->
|
||||
<Node Name="SystemTables" LocLabel="SR.SchemaHierarchy_SystemTables" BaseClass="ModelBased" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlTable" TreeNode="TableTreeNode">
|
||||
<Filters >
|
||||
<Filter Property="IsSystemObject" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<!--
|
||||
<Node Name="FileTables" LocLabel="SR.SchemaHierarchy_FileTables" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlTable" TreeNode="TableTreeNode" ValidFor="Sql2012|Sql2014|Sql2016|Sql2017">
|
||||
<Filters >
|
||||
<Filter Property="IsFileTable" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="ExternalTables" LocLabel="SR.SchemaHierarchy_ExternalTables" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlTable" TreeNode="ExternalTableTreeNode" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Filters >
|
||||
<Filter Property="IsExternal" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
-->
|
||||
<Node Name="Table" LocLabel="string.Empty" BaseClass="ModelBased" IsAsyncLoad="" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlTable;SqlHistoryTable" TreeNode="HistoryTableTreeNode">
|
||||
<Filters>
|
||||
<Filter TypeToReverse="SqlHistoryTable" Property="TemporalType" Type="Enum" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Value>TableTemporalType.HistoryTable</Value>
|
||||
</Filter>
|
||||
</Filters>
|
||||
<Child Name="Columns"/>
|
||||
<Child Name="Keys"/>
|
||||
<Child Name="Constraints"/>
|
||||
<Child Name="Triggers"/>
|
||||
<Child Name="Indexes"/>
|
||||
<Child Name="Statistics"/>
|
||||
</Node>
|
||||
|
||||
<!-- TODO This should use display item not ChildQuerierTypes -->
|
||||
|
||||
<Node Name="HistoryTable" LocLabel="string.Empty" BaseClass="ModelBased" IsAsyncLoad="" Strategy="PopulateDetails" NodeType="Table" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Child Name="Columns"/>
|
||||
<Child Name="Constraints"/>
|
||||
<Child Name="Indexes"/>
|
||||
<Child Name="Statistics"/>
|
||||
</Node>
|
||||
|
||||
<!-- TODO This should use display item not ChildQuerierTypes -->
|
||||
<Node Name="ExternalTable" LocLabel="string.Empty" BaseClass="ModelBased" IsAsyncLoad="" Strategy="PopulateDetails" NodeType="Table" ChildQuerierTypes="SqlTable" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Child Name="Columns"/>
|
||||
<Child Name="Statistics"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="Columns" LocLabel="SR.SchemaHierarchy_Columns" BaseClass="ModelBased" Strategy="PopulateParentDetails" NodeType="Column" ChildQuerierTypes="SqlColumn" DisableSort=""/>
|
||||
<Node Name="Keys" LocLabel="SR.SchemaHierarchy_Keys" BaseClass="ModelBased" Strategy="ElementsInRelationship" NodeType="Key" ChildQuerierTypes="SqlIndex;SqlForeignKeyConstraint" ValidFor="NotSqlDw">
|
||||
<Filters>
|
||||
<Filter TypeToReverse="SqlIndex" Property="IndexKeyType" Type="Enum" ValidFor="AllOnPrem|AzureV12">
|
||||
<Value>IndexKeyType.DriPrimaryKey</Value>
|
||||
<Value>IndexKeyType.DriUniqueKey</Value>
|
||||
</Filter>
|
||||
</Filters>
|
||||
|
||||
</Node>
|
||||
<Node Name="Constraints" LocLabel="SR.SchemaHierarchy_Constraints" BaseClass="ModelBased" NodeType="Constraint" Strategy="ElementsInRelationship" ChildQuerierTypes="SqlDefaultConstraint;SqlCheck"/>
|
||||
<Node Name="Triggers" LocLabel="SR.SchemaHierarchy_Triggers" BaseClass="ModelBased" Strategy="ElementsInRelationship" NodeType="Trigger" ChildQuerierTypes="SqlDmlTrigger" ValidFor="Sql2005|Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="Indexes" LocLabel="SR.SchemaHierarchy_Indexes" BaseClass="ModelBased" Strategy="ElementsInRelationship" NodeType="Index" ChildQuerierTypes="SqlIndex;SqlFullTextIndex">
|
||||
<Filters>
|
||||
<Filter TypeToReverse="SqlIndex" Property="IndexKeyType" Type="Enum" ValidFor="AllOnPrem|AzureV12">
|
||||
<Value>IndexKeyType.None</Value>
|
||||
<Value>IndexKeyType.DriPrimaryKey</Value>
|
||||
<Value>IndexKeyType.DriUniqueKey</Value>
|
||||
</Filter>
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="Statistics" LocLabel="SR.SchemaHierarchy_Statistics" BaseClass="ModelBased" Strategy="ElementsInRelationship" NodeType="Statistic" ChildQuerierTypes="SqlStatistic"/>
|
||||
|
||||
<Node Name="SystemViews" LocLabel="SR.SchemaHierarchy_SystemViews" BaseClass="ModelBased" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlView" TreeNode="ViewTreeNode">
|
||||
<Filters>
|
||||
<Filter Property="IsSystemObject" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
|
||||
<Node Name="View" LocLabel="string.Empty" BaseClass="ModelBased" IsAsyncLoad="" NodeType="View" Strategy="PopulateDetails">
|
||||
<Child Name="Columns"/>
|
||||
<Child Name="Triggers"/>
|
||||
<Child Name="Indexes"/>
|
||||
<Child Name="Statistics"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="Functions" LocLabel="SR.SchemaHierarchy_Functions" BaseClass="ModelBased" >
|
||||
<Child Name="SystemFunctions" IsSystemObject="1"/>
|
||||
<Child Name="TableValuedFunctions"/>
|
||||
<Child Name="ScalarValuedFunctions"/>
|
||||
<Child Name="AggregateFunctions"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="SystemFunctions" LocLabel="SR.SchemaHierarchy_SystemFunctions" BaseClass="ModelBased" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" TreeNode="TableValuedFunctionTreeNode">
|
||||
<Child Name="SystemTableValuedFunctions" IsSystemObject="1"/>
|
||||
<Child Name="SystemScalarValuedFunctions" IsSystemObject="1"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="DatabaseTriggers" LocLabel="SR.SchemaHierarchy_DatabaseTriggers" BaseClass="ModelBased" NodeType="DatabaseTrigger" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlDatabaseDdlTrigger" ValidFor="Sql2005|Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="Assemblies" LocLabel="SR.SchemaHierarchy_Assemblies" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="Assembly" ChildQuerierTypes="SqlAssembly" ValidFor="AllOnPrem|AzureV12"/>
|
||||
<Node Name="Types" LocLabel="SR.SchemaHierarchy_Types" BaseClass="ModelBased" >
|
||||
<Child Name="SystemDataTypes" IsSystemObject="1"/>
|
||||
<Child Name="UserDefinedDataTypes"/>
|
||||
<Child Name="UserDefinedTableTypes"/>
|
||||
<Child Name="UserDefinedTypes"/>
|
||||
<Child Name="XmlSchemaCollections"/>
|
||||
</Node>
|
||||
<!--==
|
||||
<Node Name="Rules" LocLabel="SR.SchemaHierarchy_Rules" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="Rule" ChildQuerierTypes="SqlRule" ValidFor="AllOnPrem|AzureV12"/>
|
||||
<Node Name="Defaults" LocLabel="SR.SchemaHierarchy_Defaults" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="Default" ChildQuerierTypes="SqlDefault" ValidFor="AllOnPrem|AzureV12"/>
|
||||
-->
|
||||
<Node Name="Sequences" LocLabel="SR.SchemaHierarchy_Sequences" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="Sequence" ChildQuerierTypes="SqlSequence" ValidFor="Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
|
||||
<Node Name="SystemDataTypes" LocLabel="SR.SchemaHierarchy_SystemDataTypes" BaseClass="ModelBased" >
|
||||
<Child Name="SystemExactNumerics"/>
|
||||
<Child Name="SystemApproximateNumerics"/>
|
||||
<Child Name="SystemDateAndTimes"/>
|
||||
<Child Name="SystemCharacterStrings"/>
|
||||
<Child Name="SystemUnicodeCharacterStrings"/>
|
||||
<Child Name="SystemBinaryStrings"/>
|
||||
<Child Name="SystemOtherDataTypes"/>
|
||||
<Child Name="SystemClrDataTypes"/>
|
||||
<Child Name="SystemSpatialDataTypes"/>
|
||||
</Node>
|
||||
<Node Name="UserDefinedDataTypes" LocLabel="SR.SchemaHierarchy_UserDefinedDataTypes" BaseClass="ModelBased" NodeType="UserDefinedDataType" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedDataType" ValidFor="Sql2005|Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="UserDefinedTableTypes" LocLabel="SR.SchemaHierarchy_UserDefinedTableTypes" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedTableType" TreeNode="UserDefinedTableTypeTreeNode" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="UserDefinedTypes" LocLabel="SR.SchemaHierarchy_UserDefinedTypes" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="UserDefinedType" ChildQuerierTypes="SqlUserDefinedType" ValidFor="AllOnPrem|AzureV12"/>
|
||||
<Node Name="XmlSchemaCollections" LocLabel="SR.SchemaHierarchy_XMLSchemaCollections" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="XmlSchemaCollection" ChildQuerierTypes="SqlXmlSchemaCollection" ValidFor="AllOnPrem|AzureV12"/>
|
||||
|
||||
<Node Name="UserDefinedTableType" LocLabel="string.Empty" BaseClass="ModelBased" ChildQuerierTypes="" NodeType="UserDefinedTableType" IsAsyncLoad="" Strategy="PopulateDetails">
|
||||
<Child Name="UserDefinedTableTypeColumns"/>
|
||||
<Child Name="UserDefinedTableTypeKeys"/>
|
||||
<Child Name="UserDefinedTableTypeConstraints"/>
|
||||
</Node>
|
||||
<Node Name="UserDefinedTableTypeColumns" LocLabel="SR.SchemaHierarchy_Columns" BaseClass="ModelBased" NodeType="UserDefinedTableTypeColumn" Strategy="PopulateParentDetails" ChildQuerierTypes="SqlColumn" DisableSort=""/>
|
||||
<Node Name="UserDefinedTableTypeKeys" LocLabel="SR.SchemaHierarchy_Keys" BaseClass="ModelBased" NodeType="UserDefinedTableTypeKey" Strategy="PopulateParentDetails" ChildQuerierTypes="SqlIndex">
|
||||
<Filters>
|
||||
<Filter TypeToReverse="SqlIndex" Property="IndexKeyType" Type="Enum" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Value>IndexKeyType.DriPrimaryKey</Value>
|
||||
<Value>IndexKeyType.DriUniqueKey</Value>
|
||||
</Filter>
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="UserDefinedTableTypeConstraints" LocLabel="SR.SchemaHierarchy_Constraints" BaseClass="ModelBased" NodeType="UserDefinedTableTypeConstraint" Strategy="PopulateParentDetails" ChildQuerierTypes="SqlDefaultConstraint;SqlCheck"/>/>
|
||||
|
||||
<Node Name="SystemExactNumerics" LocLabel="SR.SchemaHierarchy_SystemExactNumerics" BaseClass="ModelBased" NodeType="SystemExactNumeric" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemApproximateNumerics" LocLabel="SR.SchemaHierarchy_SystemApproximateNumerics" BaseClass="ModelBased" NodeType="SystemApproximateNumeric" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemDateAndTimes" LocLabel="SR.SchemaHierarchy_SystemDateAndTime" BaseClass="ModelBased" NodeType="SystemDateAndTime" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemCharacterStrings" LocLabel="SR.SchemaHierarchy_SystemCharacterStrings" BaseClass="ModelBased" NodeType="SystemCharacterString" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemUnicodeCharacterStrings" LocLabel="SR.SchemaHierarchy_SystemUnicodeCharacterStrings" BaseClass="ModelBased" NodeType="SystemUnicodeCharacterString" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemBinaryStrings" LocLabel="SR.SchemaHierarchy_SystemBinaryStrings" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="SystemBinaryString" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemOtherDataTypes" LocLabel="SR.SchemaHierarchy_SystemOtherDataTypes" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="SystemOtherDataType" ChildQuerierTypes="SqlBuiltInType"/>
|
||||
<Node Name="SystemClrDataTypes" LocLabel="SR.SchemaHierarchy_SystemCLRDataTypes" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="SystemClrDataType" ChildQuerierTypes="SqlBuiltInType" ValidFor="All"/>
|
||||
<Node Name="SystemSpatialDataTypes" LocLabel="SR.SchemaHierarchy_SystemSpatialDataTypes" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="SystemSpatialDataType" ChildQuerierTypes="SqlBuiltInType" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
|
||||
<!-- Childs of ExternalResources -->
|
||||
<Node Name="ExternalDataSources" LocLabel="SR.SchemaHierarchy_ExternalDataSources" BaseClass="ModelBased" NodeType="ExternalDataSource" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlExternalDataSource" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="ExternalFileFormats" LocLabel="SR.SchemaHierarchy_ExternalFileFormats" BaseClass="ModelBased" NodeType="ExternalFileFormat" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlExternalFileFormat" ValidFor="Sql2016|Sql2017"/>
|
||||
|
||||
<Node Name="StoredProcedures" LocLabel="SR.SchemaHierarchy_StoredProcedures" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlProcedure" TreeNode="StoredProcedureTreeNode">
|
||||
<Filters >
|
||||
<Filter Property="IsSystemObject" Value="0" Type="bool" />
|
||||
</Filters>
|
||||
<Child Name="SystemStoredProcedures" IsSystemObject="1"/>
|
||||
</Node>
|
||||
<Node Name="SystemStoredProcedures" LocLabel="SR.SchemaHierarchy_SystemStoredProcedures" BaseClass="ModelBased" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlProcedure" TreeNode="StoredProcedureTreeNode">
|
||||
<Filters >
|
||||
<Filter Property="IsSystemObject" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="StoredProcedure" LocLabel="string.Empty" BaseClass="ModelBased" ChildQuerierTypes="" NodeType="StoredProcedure" IsAsyncLoad="" Strategy="PopulateDetails">
|
||||
<Child Name="StoredProcedureParameters"/>
|
||||
</Node>
|
||||
<Node Name="StoredProcedureParameters" LocLabel="SR.SchemaHierarchy_Parameters" BaseClass="ModelBased" NodeType="StoredProcedureParameter" Strategy="StoredProcedureParameters" ChildQuerierTypes="SqlSubroutineParameter" DisableSort=""/>
|
||||
|
||||
<Node Name="TableValuedFunctions" LocLabel="SR.SchemaHierarchy_TableValuedFunctions" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedFunction" TreeNode="TableValuedFunctionTreeNode" ValidFor="NotSqlDw">
|
||||
<Filters >
|
||||
<Filter Property="FunctionType" Type="Enum">
|
||||
<Value>UserDefinedFunctionType.Table</Value>
|
||||
<Value>UserDefinedFunctionType.Inline</Value>
|
||||
</Filter>
|
||||
<Filter Property="IsSystemObject" Value="0" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="SystemTableValuedFunctions" LocLabel="SR.SchemaHierarchy_TableValuedFunctions" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedFunction" TreeNode="TableValuedFunctionTreeNode" ValidFor="NotSqlDw">
|
||||
<Filters >
|
||||
<Filter Property="FunctionType" Type="Enum">
|
||||
<Value>UserDefinedFunctionType.Table</Value>
|
||||
<Value>UserDefinedFunctionType.Inline</Value>
|
||||
</Filter>
|
||||
<Filter Property="IsSystemObject" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="TableValuedFunction" LocLabel="string.Empty" BaseClass="ModelBased" ChildQuerierTypes="" NodeType="TableValuedFunction" IsAsyncLoad="" Strategy="PopulateDetails">
|
||||
<Child Name="TableValuedFunctionParameters"/>
|
||||
</Node>
|
||||
<Node Name="TableValuedFunctionParameters" LocLabel="SR.SchemaHierarchy_Parameters" BaseClass="ModelBased" NodeType="TableValuedFunctionParameter" Strategy="FunctionParameters" ChildQuerierTypes="SqlSubroutineParameter" DisableSort="">
|
||||
</Node>
|
||||
|
||||
<Node Name="ScalarValuedFunctions" LocLabel="SR.SchemaHierarchy_ScalarValuedFunctions" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedFunction" TreeNode="ScalarValuedFunctionTreeNode" >
|
||||
<Filters>
|
||||
<Filter Property="FunctionType" Type="Enum" ValidFor="NotSqlDw">
|
||||
<Value>UserDefinedFunctionType.Scalar</Value>
|
||||
</Filter>
|
||||
<Filter Property="IsSystemObject" Value="0" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="SystemScalarValuedFunctions" LocLabel="SR.SchemaHierarchy_ScalarValuedFunctions" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedFunction" TreeNode="ScalarValuedFunctionTreeNode" >
|
||||
<Filters>
|
||||
<Filter Property="FunctionType" Type="Enum" ValidFor="NotSqlDw">
|
||||
<Value>UserDefinedFunctionType.Scalar</Value>
|
||||
</Filter>
|
||||
<Filter Property="IsSystemObject" Value="1" Type="bool" />
|
||||
</Filters>
|
||||
</Node>
|
||||
<Node Name="ScalarValuedFunction" LocLabel="string.Empty" BaseClass="ModelBased" NodeType="ScalarValuedFunction" ChildQuerierTypes="" IsAsyncLoad="" Strategy="PopulateDetails">
|
||||
<Child Name="ScalarValuedFunctionParameters"/>
|
||||
</Node>
|
||||
<Node Name="ScalarValuedFunctionParameters" LocLabel="SR.SchemaHierarchy_Parameters" NodeType="ScalarValuedFunctionParameter" BaseClass="ModelBased" Strategy="FunctionParameters" ChildQuerierTypes="SqlSubroutineParameter" DisableSort=""/>
|
||||
|
||||
<Node Name="AggregateFunctions" LocLabel="SR.SchemaHierarchy_AggregateFunctions" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUserDefinedAggregate" TreeNode="AggregateFunctionTreeNode" ValidFor="AllOnPrem|AzureV12">
|
||||
</Node>
|
||||
<Node Name="AggregateFunction" LocLabel="string.Empty" BaseClass="ModelBased" ChildQuerierTypes="" NodeType="AggregateFunction" IsAsyncLoad="" Strategy="PopulateDetails">
|
||||
<Child Name="AggregateFunctionParameters"/>
|
||||
</Node>
|
||||
<Node Name="AggregateFunctionParameters" LocLabel="SR.SchemaHierarchy_Parameters" BaseClass="ModelBased" NodeType="AggregateFunctionParameter" Strategy="PopulateParentDetails" ChildQuerierTypes="SqlSubroutineParameter" DisableSort=""/>
|
||||
|
||||
<!-- TODO Support Route in SMO
|
||||
<Node Name="Routes" LocLabel="SR.SchemaHierarchy_Routes" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlRoute"/>
|
||||
-->
|
||||
<!-- TODO support events
|
||||
<Node Name="DatabaseAndQueueEventNotifications" LocLabel="SR.SchemaHierarchy_EventNotifications" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlEventNotification"/>
|
||||
-->
|
||||
|
||||
<Node Name="RemoteServiceBindings" LocLabel="SR.SchemaHierarchy_RemoteServiceBindings" BaseClass="ModelBased" NodeType="RemoteServiceBinding" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlRemoteServiceBinding"/>
|
||||
<Node Name="BrokerPriorities" LocLabel="SR.SchemaHierarchy_BrokerPriorities" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="BrokerPriority" ChildQuerierTypes="SqlBrokerPriority" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017"/>
|
||||
|
||||
<Node Name="FileGroups" LocLabel="SR.SchemaHierarchy_FileGroups" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlFileGroup" TreeNode="FileGroupTreeNode"/>
|
||||
<Node Name="FullTextCatalogs" LocLabel="SR.SchemaHierarchy_FullTextCatalogs" BaseClass="ModelBased" NodeType="FullTextCatalog" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlFullTextCatalog"/>
|
||||
<Node Name="FullTextStopLists" LocLabel="SR.SchemaHierarchy_FullTextStopLists" BaseClass="ModelBased" NodeType="FullTextStopList" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlFullTextStopList" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="SqlLogFiles" LocLabel="SR.SchemaHierarchy_LogFiles" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="SqlLogFile" ChildQuerierTypes="SqlFile"/>
|
||||
<Node Name="PartitionFunctions" LocLabel="SR.SchemaHierarchy_PartitionFunctions" BaseClass="ModelBased" NodeType="PartitionFunction" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlPartitionFunction"/>
|
||||
<Node Name="PartitionSchemes" LocLabel="SR.SchemaHierarchy_PartitionSchemes" BaseClass="ModelBased" NodeType="PartitionScheme" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlPartitionScheme"/>
|
||||
<Node Name="SearchPropertyLists" LocLabel="SR.SchemaHierarchy_SearchPropertyLists" BaseClass="ModelBased" NodeType="SearchPropertyList" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlSearchPropertyList" ValidFor="Sql2012|Sql2014|Sql2016|Sql2017|AzureV12"/>
|
||||
|
||||
<Node Name="FileGroup" LocLabel="string.Empty" BaseClass="ModelBased" ChildQuerierTypes="">
|
||||
<Child Name="FileGroupFiles"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="FileGroupFiles" LocLabel="SR.SchemaHierarchy_FilegroupFiles" BaseClass="ModelBased" NodeType="FileGroupFile" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlFile" >
|
||||
</Node>
|
||||
|
||||
<Node Name="Users" LocLabel="SR.SchemaHierarchy_Users" BaseClass="ModelBased" NodeType="User" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlUser"/>
|
||||
<Node Name="Roles" LocLabel="SR.SchemaHierarchy_Roles" NodeType="Role" BaseClass="ModelBased" >
|
||||
<Child Name="DatabaseRoles"/>
|
||||
<Child Name="ApplicationRoles"/>
|
||||
</Node>
|
||||
<Node Name="Schemas" LocLabel="SR.SchemaHierarchy_Schemas" BaseClass="ModelBased" NodeType="Schema" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlSchema"/>
|
||||
<Node Name="AsymmetricKeys" LocLabel="SR.SchemaHierarchy_AsymmetricKeys" BaseClass="ModelBased" NodeType="AsymmetricKey" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlAsymmetricKey" ValidFor="AllOnPrem"/>
|
||||
<Node Name="Certificates" LocLabel="SR.SchemaHierarchy_Certificates" BaseClass="ModelBased" NodeType="Certificate" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlCertificate" ValidFor="AllOnPrem"/>
|
||||
<Node Name="SymmetricKeys" LocLabel="SR.SchemaHierarchy_SymmetricKeys" BaseClass="ModelBased" NodeType="SymmetricKey" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlSymmetricKey" ValidFor="AllOnPrem"/>
|
||||
<Node Name="DatabaseEncryptionKeys" LocLabel="SR.SchemaHierarchy_DatabaseEncryptionKeys" BaseClass="ModelBased" NodeType="DatabaseEncryptionKey" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlDatabaseEncryptionKey" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017"/>
|
||||
<Node Name="MasterKeys" LocLabel="SR.SchemaHierarchy_MasterKeys" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="MasterKey" ChildQuerierTypes="SqlMasterKey" ValidFor="AllOnPrem"/>
|
||||
|
||||
<!-- TODO Support signatures
|
||||
<Node Name="Signatures" LocLabel="SR.SchemaHierarchy_Signatures" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlSignature" ValidFor="Sql2005|Sql2008|Sql2012|Sql2014|Sql2016|Sql2017"/>
|
||||
-->
|
||||
<Node Name="DatabaseAuditSpecifications" LocLabel="SR.SchemaHierarchy_DatabaseAuditSpecifications" BaseClass="ModelBased" NodeType="DatabaseAuditSpecification" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlDatabaseAuditSpecification" ValidFor="Sql2008|Sql2012|Sql2014|Sql2016|Sql2017"/>
|
||||
<Node Name="SecurityPolicies" LocLabel="SR.SchemaHierarchy_SecurityPolicies" BaseClass="ModelBased" Strategy="MultipleElementsOfType" NodeType="SecurityPolicy" ChildQuerierTypes="SqlSecurityPolicy" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="DatabaseScopedCredentials" LocLabel="SR.SchemaHierarchy_DatabaseScopedCredentials" BaseClass="ModelBased" NodeType="DatabaseScopedCredential" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlDatabaseCredential" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="AlwaysEncryptedKeys" LocLabel="SR.SchemaHierarchy_AlwaysEncryptedKeys" BaseClass="ModelBased" ValidFor="Sql2016|Sql2017|AzureV12">
|
||||
<Child Name="ColumnMasterKeys"/>
|
||||
<Child Name="ColumnEncryptionKeys"/>
|
||||
</Node>
|
||||
|
||||
<Node Name="DatabaseRoles" LocLabel="SR.SchemaHierarchy_DatabaseRoles" BaseClass="ModelBased" NodeType="DatabaseRole" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlRole"/>
|
||||
<Node Name="ApplicationRoles" LocLabel="SR.SchemaHierarchy_ApplicationRoles" BaseClass="ModelBased" NodeType="ApplicationRole" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlApplicationRole" ValidFor="AllOnPrem|AzureV12"/>
|
||||
<Node Name="ColumnMasterKeys" LocLabel="SR.SchemaHierarchy_ColumnMasterKeys" BaseClass="ModelBased" NodeType="ColumnMasterKey" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlColumnMasterKey" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
<Node Name="ColumnEncryptionKeys" LocLabel="SR.SchemaHierarchy_ColumnEncryptionKeys" BaseClass="ModelBased" NodeType="ColumnEncryptionKey" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlColumnEncryptionKey" ValidFor="Sql2016|Sql2017|AzureV12"/>
|
||||
|
||||
<Node Name="MessageTypes" LocLabel="SR.SchemaHierarchy_MessageTypes" BaseClass="ModelBased" NodeType="MessageType" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlMessageType">
|
||||
<Child Name="SystemMessageTypes" IsSystemObject="1"/>
|
||||
</Node>
|
||||
<Node Name="SystemMessageTypes" LocLabel="SR.SchemaHierarchy_SystemMessageTypes" BaseClass="ModelBased" NodeType="SystemMessageType" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlMessageType"/>
|
||||
|
||||
<Node Name="Contracts" LocLabel="SR.SchemaHierarchy_Contracts" BaseClass="ModelBased" NodeType="Contract" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlContract">
|
||||
<Child Name="SystemContracts" IsSystemObject="1"/>
|
||||
</Node>
|
||||
<Node Name="SystemContracts" LocLabel="SR.SchemaHierarchy_SystemContracts" BaseClass="ModelBased" NodeType="SystemContract" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlContract"/>
|
||||
|
||||
<Node Name="Queues" LocLabel="SR.SchemaHierarchy_Queues" BaseClass="ModelBased" NodeType="Queue" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlQueue">
|
||||
<Child Name="SystemQueues" IsSystemObject="1"/>
|
||||
</Node>
|
||||
<Node Name="SystemQueues" LocLabel="SR.SchemaHierarchy_SystemQueues" BaseClass="ModelBased" NodeType="SystemQueue" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlQueue"/>
|
||||
|
||||
<Node Name="Services" LocLabel="SR.SchemaHierarchy_Services" BaseClass="ModelBased" NodeType="Service" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlService">
|
||||
<Child Name="SystemServices" IsSystemObject="1"/>
|
||||
</Node>
|
||||
<Node Name="SystemServices" LocLabel="SR.SchemaHierarchy_SystemServices" BaseClass="ModelBased" NodeType="SystemService" IsMsShippedOwned="true" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlService"/>
|
||||
|
||||
<CodeGenOptions>
|
||||
<UniqueTreeNode Name="DatabaseTreeNode"/>
|
||||
<UniqueTreeNode Name="TableTreeNode"/>
|
||||
<UniqueTreeNode Name="ViewTreeNode"/>
|
||||
<UniqueTreeNode Name="UserDefinedTableTypeTreeNode"/>
|
||||
<UniqueTreeNode Name="StoredProcedureTreeNode"/>
|
||||
<UniqueTreeNode Name="TableValuedFunctionTreeNode"/>
|
||||
<UniqueTreeNode Name="ScalarValuedFunctionTreeNode"/>
|
||||
<UniqueTreeNode Name="AggregateFunctionTreeNode"/>
|
||||
<UniqueTreeNode Name="FileGroupTreeNode"/>
|
||||
<UniqueTreeNode Name="ExternalTableTreeNode"/>
|
||||
<UniqueTreeNode Name="ExternalResourceTreeNode"/>
|
||||
<UniqueTreeNode Name="HistoryTableTreeNode" />
|
||||
</CodeGenOptions>
|
||||
|
||||
<!-- WARNING: Specifying reverse dependencies that could load large numbers of objects will be detrimental to performance. -->
|
||||
<ReverseDependencyList>
|
||||
<ReverseDependency Type="SqlUser" DependsOn="SqlRole;SqlRoleMembership"/>
|
||||
</ReverseDependencyList>
|
||||
</ServerExplorerTree>
|
||||
@@ -0,0 +1,564 @@
|
||||
<#@ template debug="false" hostspecific="true" language="C#" #>
|
||||
<#@ output extension=".cs" #>
|
||||
<#@ assembly name="System.Xml.dll" #>
|
||||
<#@ import namespace="System" #>
|
||||
<#@ import namespace="System.Globalization" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Xml" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ import namespace="System.IO" #>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.Kusto.ServiceLayer;
|
||||
using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.SmoModel
|
||||
{
|
||||
|
||||
<#
|
||||
var directory = Path.GetDirectoryName(Host.TemplateFile);
|
||||
string xmlFile = Path.Combine(directory, "TreeNodeDefinition.xml");
|
||||
|
||||
/////////
|
||||
// TODO - is Generate all the ReverseDependencies needed?
|
||||
/////////
|
||||
// var allReverseDependencies = GetReverseDependencies(xmlFile);
|
||||
// WriteLine(" internal static class TreeNodeRules");
|
||||
// WriteLine(" {");
|
||||
// WriteLine(" internal static Dictionary<Type, IList<Type>> TypeReverseDependencyMap = new Dictionary<Type, IList<Type>>()");
|
||||
// WriteLine(" {");
|
||||
// foreach (var reverseDependencyKey in allReverseDependencies.Keys)
|
||||
// {
|
||||
// bool isFirstDependentType = true;
|
||||
// StringBuilder dependentListBuilder = new StringBuilder("{");
|
||||
// foreach (var dependentType in allReverseDependencies[reverseDependencyKey])
|
||||
// {
|
||||
// if (isFirstDependentType)
|
||||
// {
|
||||
// isFirstDependentType = false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dependentListBuilder.Append(",");
|
||||
// }
|
||||
//
|
||||
// dependentListBuilder.Append(string.Format(CultureInfo.InvariantCulture, " typeof({0})", dependentType));
|
||||
// }
|
||||
// dependentListBuilder.Append(" }");
|
||||
//
|
||||
// WriteLine(string.Format(CultureInfo.InvariantCulture, " {{ typeof({0}), new List<Type> {1} }}", reverseDependencyKey, dependentListBuilder.ToString()));
|
||||
// }
|
||||
// WriteLine(" };");
|
||||
// WriteLine(" }");
|
||||
// WriteLine("");
|
||||
|
||||
/////////
|
||||
// First generate all the TreeNodes
|
||||
/////////
|
||||
var allTreeNodes = GetUniqueTreeNodes(xmlFile);
|
||||
foreach (var TreeNode in allTreeNodes)
|
||||
{
|
||||
var name = TreeNode.GetAttribute("Name");
|
||||
WriteLine(string.Format(" internal sealed partial class {0} : OETreeNode", name));
|
||||
WriteLine(" {");
|
||||
WriteLine(string.Format(" public {0}() : base()", name));
|
||||
WriteLine(" {");
|
||||
WriteLine(" NodeValue = string.Empty;");
|
||||
WriteLine(string.Format(" this.NodeType = \"{0}\";", name.Replace("TreeNode", string.Empty)));
|
||||
WriteLine(string.Format(" this.NodeTypeId = NodeTypes.{0};", name.Replace("TreeNode", string.Empty)));
|
||||
WriteLine(" OnInitialize();");
|
||||
WriteLine(" }");
|
||||
WriteLine(" }");
|
||||
WriteLine("");
|
||||
}
|
||||
|
||||
/////////
|
||||
// Now generate all the ChildFactories
|
||||
/////////
|
||||
var allNodes = GetNodes(xmlFile);
|
||||
foreach (var type in allNodes)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, type);
|
||||
var imageAttr = nodeElement.GetAttribute("Image");
|
||||
var isAlwaysLeaf = nodeElement.GetAttributeNode("IsAlwaysLeaf");
|
||||
var baseClass = nodeElement.GetAttribute("BaseClass");
|
||||
var strategy = nodeElement.GetAttribute("Strategy");
|
||||
var nodeType = nodeElement.GetAttribute("NodeType");
|
||||
var ChildQuerierTypes = nodeElement.GetAttribute("ChildQuerierTypes");
|
||||
var TreeNode = nodeElement.GetAttribute("TreeNode");
|
||||
var isAsync = nodeElement.GetAttributeNode("IsAsyncLoad");
|
||||
var disableSort = nodeElement.GetAttributeNode("DisableSort");
|
||||
|
||||
string childFactoryBaseClass = "SmoChildFactoryBase";
|
||||
|
||||
// TODO Will we need alternative child factories? If so, add code here to support this
|
||||
|
||||
if (isAlwaysLeaf == null)
|
||||
{
|
||||
WriteLine(" [Export(typeof(ChildFactory))]");
|
||||
WriteLine(" [Shared]");
|
||||
|
||||
WriteLine(string.Format(" internal partial class {0}ChildFactory : {1}", type, childFactoryBaseClass));
|
||||
|
||||
WriteLine(" {");
|
||||
WriteLine(string.Format(" public override IEnumerable<string> ApplicableParents() {{ return new[] {{ \"{0}\" }}; }}", type));
|
||||
|
||||
List<XmlElement> children = GetChildren(xmlFile, type);
|
||||
List<XmlElement> filters = GetNodeFilters(xmlFile, type);
|
||||
List<XmlElement> smoProperties = GetNodeSmoProperties(xmlFile, type);
|
||||
|
||||
if (filters.Count > 0)
|
||||
{
|
||||
WriteLine("");
|
||||
WriteLine(" public override IEnumerable<NodeFilter> Filters");
|
||||
WriteLine(" {");
|
||||
WriteLine(" get");
|
||||
WriteLine(" {");
|
||||
|
||||
WriteLine(" var filters = new List<NodeFilter>();");
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
var propertyName = filter.GetAttribute("Property");
|
||||
var propertyType = filter.GetAttribute("Type");
|
||||
var propertyValue = filter.GetAttribute("Value");
|
||||
var validFor = filter.GetAttribute("ValidFor");
|
||||
var typeToReverse = filter.GetAttribute("TypeToReverse");
|
||||
|
||||
List<XmlElement> filterValues = GetNodeFilterValues(xmlFile, type, propertyName);
|
||||
|
||||
|
||||
WriteLine(" filters.Add(new NodeFilter");
|
||||
WriteLine(" {");
|
||||
WriteLine(string.Format(" Property = \"{0}\",", propertyName));
|
||||
WriteLine(string.Format(" Type = typeof({0}),", propertyType));
|
||||
if (!string.IsNullOrWhiteSpace(typeToReverse))
|
||||
{
|
||||
WriteLine(string.Format(" TypeToReverse = typeof({0}Querier),", typeToReverse));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(validFor))
|
||||
{
|
||||
WriteLine(string.Format(" ValidFor = {0},", GetValidForFlags(validFor)));
|
||||
}
|
||||
if (propertyValue != null && (filterValues == null || filterValues.Count == 0))
|
||||
{
|
||||
WriteLine(string.Format(" Values = new List<object> {{ {0} }},", propertyValue));
|
||||
}
|
||||
if (filterValues != null && filterValues.Count > 0)
|
||||
{
|
||||
string filterValueType = "object";
|
||||
if (propertyType == "Enum")
|
||||
{
|
||||
|
||||
}
|
||||
WriteLine(string.Format(" Values = new List<object>"));
|
||||
WriteLine(string.Format(" {{"));
|
||||
for(int i = 0; i < filterValues.Count; i++)
|
||||
{
|
||||
string separator = "";
|
||||
if (i != filterValues.Count - 1)
|
||||
{
|
||||
separator = ",";
|
||||
}
|
||||
var filterValue = filterValues[i];
|
||||
WriteLine(string.Format(" {{ {0} }}{1}", filterValue.InnerText, separator ));
|
||||
|
||||
}
|
||||
WriteLine(string.Format(" }}"));
|
||||
}
|
||||
WriteLine(" });");
|
||||
|
||||
|
||||
}
|
||||
|
||||
WriteLine(" return filters;");
|
||||
WriteLine(" }");
|
||||
WriteLine(" }");
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (smoProperties.Count > 0)
|
||||
{
|
||||
WriteLine("");
|
||||
WriteLine(" public override IEnumerable<NodeSmoProperty> SmoProperties");
|
||||
WriteLine(" {");
|
||||
WriteLine(" get");
|
||||
WriteLine(" {");
|
||||
|
||||
WriteLine(" var properties = new List<NodeSmoProperty>();");
|
||||
foreach (var smoPropertiy in smoProperties)
|
||||
{
|
||||
var propertyName = smoPropertiy.GetAttribute("Name");
|
||||
var validFor = smoPropertiy.GetAttribute("ValidFor");
|
||||
|
||||
|
||||
|
||||
|
||||
WriteLine(" properties.Add(new NodeSmoProperty");
|
||||
WriteLine(" {");
|
||||
WriteLine(string.Format(" Name = \"{0}\",", propertyName));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(validFor))
|
||||
{
|
||||
WriteLine(string.Format(" ValidFor = {0}", GetValidForFlags(validFor)));
|
||||
}
|
||||
WriteLine(" });");
|
||||
|
||||
|
||||
}
|
||||
|
||||
WriteLine(" return properties;");
|
||||
WriteLine(" }");
|
||||
WriteLine(" }");
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (children.Count > 0)
|
||||
{
|
||||
WriteLine("");
|
||||
WriteLine(" protected override void OnExpandPopulateFolders(IList<TreeNode> currentChildren, TreeNode parent)");
|
||||
WriteLine(" {");
|
||||
foreach (var child in children)
|
||||
{
|
||||
XmlElement childAsXmlElement = GetNodeElement(xmlFile, child.GetAttribute("Name"));
|
||||
if (childAsXmlElement == null)
|
||||
{
|
||||
// TODO SHould we error with clear message that this needs to be fixed?
|
||||
continue;
|
||||
}
|
||||
string childImage = childAsXmlElement.GetAttribute("Image");
|
||||
var msShippedOwned = childAsXmlElement.GetAttributeNode("IsMsShippedOwned");
|
||||
var validFor = childAsXmlElement.GetAttribute("ValidFor");
|
||||
|
||||
if (TreeNodeExists(xmlFile, child.GetAttribute("Name") + "TreeNode"))
|
||||
{
|
||||
WriteLine(string.Format(" currentChildren.Add(new {0}TreeNode {{ SortPriority = OETreeNode.NextSortPriority }} );", child.GetAttribute("Name")));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine(" currentChildren.Add(new FolderNode {");
|
||||
WriteLine(string.Format(" NodeValue = {0},", childAsXmlElement.GetAttribute("LocLabel")));
|
||||
WriteLine(string.Format(" NodeType = \"{0}\",", "Folder"));
|
||||
WriteLine(string.Format(" NodeTypeId = NodeTypes.{0},", child.GetAttribute("Name")));
|
||||
WriteLine(string.Format(" IsSystemObject = {0},", child.GetAttribute("IsSystemObject") == "1" ? "true" : "false"));
|
||||
|
||||
if (msShippedOwned != null)
|
||||
{
|
||||
WriteLine(" IsMsShippedOwned = true,");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(validFor))
|
||||
{
|
||||
WriteLine(string.Format(" ValidFor = {0},", GetValidForFlags(validFor)));
|
||||
}
|
||||
WriteLine(" SortPriority = OETreeNode.NextSortPriority,");
|
||||
WriteLine(" });");
|
||||
}
|
||||
}
|
||||
WriteLine(" }");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(strategy))
|
||||
{
|
||||
string[] allTypes = ChildQuerierTypes.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
WriteLine("");
|
||||
WriteLine(" internal override Type[] ChildQuerierTypes");
|
||||
WriteLine(" {");
|
||||
WriteLine(" get");
|
||||
WriteLine(" {");
|
||||
if (!string.IsNullOrWhiteSpace(ChildQuerierTypes))
|
||||
{
|
||||
Write(" return new [] {");
|
||||
foreach (var typeToRe in allTypes)
|
||||
{
|
||||
Write(string.Format(" typeof({0}Querier),", typeToRe));
|
||||
}
|
||||
WriteLine(" };");
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(" return new Type[0];");
|
||||
}
|
||||
WriteLine(" }");
|
||||
WriteLine(" }");
|
||||
|
||||
WriteLine("");
|
||||
|
||||
WriteLine(" public override TreeNode CreateChild(TreeNode parent, object context)");
|
||||
WriteLine(" {");
|
||||
if (string.IsNullOrWhiteSpace(TreeNode))
|
||||
{
|
||||
WriteLine(" var child = new OETreeNode();");
|
||||
WriteLine(" child.IsAlwaysLeaf = true;");
|
||||
|
||||
if (!string.IsNullOrEmpty(nodeType))
|
||||
{
|
||||
|
||||
WriteLine(string.Format(" child.NodeType = \"{0}\";", nodeType));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var modelNodeChildren = GetNodeElement(xmlFile, TreeNode.Replace("TreeNode",string.Empty));
|
||||
WriteLine(string.Format(" var child = new {0}();", TreeNode));
|
||||
if (modelNodeChildren.ChildNodes.Count == 0)
|
||||
{
|
||||
WriteLine(" child.IsAlwaysLeaf = true;");
|
||||
}
|
||||
}
|
||||
if (disableSort != null)
|
||||
{
|
||||
WriteLine(" child.SortPriority = OETreeNode.NextSortPriority;");
|
||||
}
|
||||
WriteLine(" InitializeChild(parent, child, context);");
|
||||
|
||||
|
||||
WriteLine(" return child;");
|
||||
WriteLine(" }");
|
||||
}
|
||||
else if (baseClass == "ModelBased")
|
||||
{
|
||||
WriteLine("");
|
||||
WriteLine(" internal override Type[] ChildQuerierTypes { get {return null;} }");
|
||||
WriteLine("");
|
||||
// TODO Is reverse engineering strategy every needed?
|
||||
// WriteLine(" protected override ReverseEngineeringStrategy Strategy { get {return ReverseEngineeringStrategy.None;} }");
|
||||
WriteLine("");
|
||||
WriteLine(" public override TreeNode CreateChild(TreeNode parent, object context)");
|
||||
WriteLine(" {");
|
||||
WriteLine(" return null;");
|
||||
WriteLine(" }");
|
||||
}
|
||||
|
||||
WriteLine(" }");
|
||||
WriteLine("");
|
||||
}
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
<#+
|
||||
public static string GetValidForFlags(string validForStr)
|
||||
{
|
||||
List<string> flags = new List<string>();
|
||||
if (validForStr.Contains("Sql2005"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2005");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2008"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2008");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2012"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2012");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2014"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2014");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2016"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2016");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("Sql2017"))
|
||||
{
|
||||
flags.Add("ValidForFlag.Sql2017");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("AzureV12"))
|
||||
{
|
||||
flags.Add("ValidForFlag.AzureV12");
|
||||
}
|
||||
|
||||
if (validForStr.Contains("AllOnPrem"))
|
||||
{
|
||||
flags.Add("ValidForFlag.AllOnPrem");
|
||||
}
|
||||
if (validForStr.Contains("AllAzure"))
|
||||
{
|
||||
flags.Add("ValidForFlag.AllAzure");
|
||||
}
|
||||
if (validForStr.Contains("NotSqlDw"))
|
||||
{
|
||||
flags.Add("ValidForFlag.NotSqlDw");
|
||||
}
|
||||
if (validForStr == "All")
|
||||
{
|
||||
flags.Add("ValidForFlag.All");
|
||||
}
|
||||
|
||||
return string.Join("|", flags);
|
||||
}
|
||||
public static string[] GetNodes(string xmlFile)
|
||||
{
|
||||
List<string> typesList = new List<string>();
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
XmlNodeList treeTypes = doc.SelectNodes("/ServerExplorerTree/Node");
|
||||
if (treeTypes != null)
|
||||
{
|
||||
foreach (var type in treeTypes)
|
||||
{
|
||||
XmlElement element = type as XmlElement;
|
||||
if (element != null)
|
||||
{
|
||||
typesList.Add(element.GetAttribute("Name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return typesList.ToArray();
|
||||
}
|
||||
|
||||
public static Dictionary<string, List<string>> GetReverseDependencies(string xmlFile)
|
||||
{
|
||||
Dictionary<string, List<string>> dependencyMap = new Dictionary<string, List<string>>();
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
XmlNodeList treeTypes = doc.SelectNodes("/ServerExplorerTree/ReverseDependencyList/ReverseDependency");
|
||||
if (treeTypes != null)
|
||||
{
|
||||
foreach (var type in treeTypes)
|
||||
{
|
||||
XmlElement element = type as XmlElement;
|
||||
if (element != null)
|
||||
{
|
||||
string typeName = element.GetAttribute("Type");
|
||||
string dependency = element.GetAttribute("DependsOn");
|
||||
List<string> dependenciesForType;
|
||||
if (dependencyMap.TryGetValue(typeName, out dependenciesForType))
|
||||
{
|
||||
dependenciesForType.Add(dependency);
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] allDepedencies = dependency.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
dependenciesForType = new List<string>();
|
||||
dependenciesForType.AddRange(allDepedencies);
|
||||
dependencyMap.Add(typeName, dependenciesForType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependencyMap;
|
||||
}
|
||||
|
||||
public static XmlElement GetNodeElement(string xmlFile, string nodeName)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
return (XmlElement)doc.SelectSingleNode(string.Format("/ServerExplorerTree/Node[@Name='{0}']", nodeName));
|
||||
}
|
||||
|
||||
public static bool TreeNodeExists(string xmlFile, string TreeNode)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
var found = (XmlElement)doc.SelectSingleNode(string.Format("/ServerExplorerTree/CodeGenOptions/UniqueTreeNode[@Name='{0}']", TreeNode));
|
||||
|
||||
return (found != null);
|
||||
}
|
||||
|
||||
public static List<XmlElement> GetUniqueTreeNodes(string xmlFile)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
|
||||
List<XmlElement> retElements = new List<XmlElement>();
|
||||
XmlNodeList nodeList = doc.SelectNodes("/ServerExplorerTree/CodeGenOptions/UniqueTreeNode");
|
||||
foreach (var item in nodeList)
|
||||
{
|
||||
XmlElement itemAsElement = item as XmlElement;
|
||||
if (itemAsElement != null)
|
||||
{
|
||||
retElements.Add(itemAsElement);
|
||||
}
|
||||
}
|
||||
return retElements;
|
||||
}
|
||||
|
||||
public static List<XmlElement> GetChildren(string xmlFile, string parentName)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
|
||||
List<XmlElement> retElements = new List<XmlElement>();
|
||||
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Child", parentName));
|
||||
foreach (var item in nodeList)
|
||||
{
|
||||
XmlElement itemAsElement = item as XmlElement;
|
||||
if (itemAsElement != null)
|
||||
{
|
||||
retElements.Add(itemAsElement);
|
||||
}
|
||||
}
|
||||
return retElements;
|
||||
}
|
||||
|
||||
public static List<XmlElement> GetNodeFilters(string xmlFile, string parentName)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
|
||||
List<XmlElement> retElements = new List<XmlElement>();
|
||||
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Filter", parentName));
|
||||
foreach (var item in nodeList)
|
||||
{
|
||||
XmlElement itemAsElement = item as XmlElement;
|
||||
if (itemAsElement != null)
|
||||
{
|
||||
retElements.Add(itemAsElement);
|
||||
}
|
||||
}
|
||||
return retElements;
|
||||
}
|
||||
|
||||
|
||||
public static List<XmlElement> GetNodeSmoProperties(string xmlFile, string parentName)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
|
||||
List<XmlElement> retElements = new List<XmlElement>();
|
||||
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Properties/Property", parentName));
|
||||
foreach (var item in nodeList)
|
||||
{
|
||||
XmlElement itemAsElement = item as XmlElement;
|
||||
if (itemAsElement != null)
|
||||
{
|
||||
retElements.Add(itemAsElement);
|
||||
}
|
||||
}
|
||||
return retElements;
|
||||
}
|
||||
|
||||
public static List<XmlElement> GetNodeFilterValues(string xmlFile, string parentName, string filterProperty)
|
||||
{
|
||||
XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(xmlFile);
|
||||
|
||||
List<XmlElement> retElements = new List<XmlElement>();
|
||||
XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Filter[@Property='{1}']/Value", parentName, filterProperty));
|
||||
foreach (var item in nodeList)
|
||||
{
|
||||
XmlElement itemAsElement = item as XmlElement;
|
||||
if (itemAsElement != null)
|
||||
{
|
||||
retElements.Add(itemAsElement);
|
||||
}
|
||||
}
|
||||
return retElements;
|
||||
}
|
||||
#>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates which type of server a given node type is valid for
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ValidForFlag
|
||||
{
|
||||
None = 0x00,
|
||||
Sql2005 = 0x01,
|
||||
Sql2008 = 0x02,
|
||||
Sql2012 = 0x04,
|
||||
Sql2014 = 0x08,
|
||||
AzureV12 = 0x10,
|
||||
Sql2016 = 0x20,
|
||||
Sql2017 = 0x40,
|
||||
SqlDw = 0x80,
|
||||
AllOnPrem = Sql2005 | Sql2008 | Sql2012 | Sql2014 | Sql2016 | Sql2017,
|
||||
AllAzure = AzureV12,
|
||||
All = Sql2005 | Sql2008 | Sql2012 | Sql2014 | Sql2016 | Sql2017 | AzureV12 | SqlDw,
|
||||
NotSqlDw = Sql2005 | Sql2008 | Sql2012 | Sql2014 | Sql2016 | Sql2017 | AzureV12
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user