From 1f7da97e6c896aeb97da1ecfc6eba791182a5c5e Mon Sep 17 00:00:00 2001 From: Justin M <63619224+JustinMDotNet@users.noreply.github.com> Date: Fri, 25 Jun 2021 21:40:45 -0700 Subject: [PATCH] Added AzureMonitor to Microsoft.Kusto.ServiceLayer (#1208) * Added AzureMonitor to Microsoft.Kusto.ServiceLayer. * Added Intellisense for AzureMonitor. Moved Intellisense logic from KustoIntellisenseClient to IntellisenseClientBase. * Added ServiceName as a command parameter for starting Kusto. * Added check to return null if connectionInfo is not in the connectionService. * Added support for Dashboard in MetadataService and AdminService. * Removed workspace id from databaseName for Monitor. Added logic for MetadataService and AdminService to return different information for AzureMonitor. * Moved providerName and providerDescription to DataSourceFactory. * Changed DatabaseName to include Name and Id. Changed ProviderName to LOGANALYTICS in DataSourceFactory * Fixed unit tests * Changed logic to use ServiceName instead of server to determine DataSourceType * Code review feedback and reverted changes to ObjectExplorerService. * Removed unused reference from HostLoader * Changed Parallel.Invoke to await Task.Run * Moved Kusto datasource and supporting classes to separate directory. * Removed unused datasourceFactory from ConnectionService. Added GetDatabases and GetDatabaseInfo to IDataSource and child classes * Renamed Instance variables in ObjectExplorerService. Removed unused attribute on TSqlFormatterService. Removed invalid comment in ConnectionService. * Fixed warnings in build. * Moved SizeInMB to DatabaseMetadata. Refactored ConvertToDatabaseInfo * Fixed unit test --- Packages.props | 1 + .../Admin/AdminService.cs | 36 +-- .../Connection/ConnectionService.cs | 256 ++++++------------ .../DataSource/DataSourceBase.cs | 7 +- .../DataSource/DataSourceFactory.cs | 38 ++- .../DataSource/DataSourceType.cs | 2 +- .../DataSource/IDataSource.cs | 4 + .../DataSource/IDataSourceFactory.cs | 2 +- .../Intellisense/IIntellisenseClient.cs | 15 - .../Intellisense/IntellisenseClientBase.cs | 141 ++++++++++ .../DataSource/{ => Kusto}/IKustoClient.cs | 2 +- .../DataSource/{ => Kusto}/KustoClient.cs | 2 +- .../DataSource/{ => Kusto}/KustoDataSource.cs | 59 +++- .../KustoIntellisenseClient.cs | 151 +---------- .../KustoIntellisenseHelper.cs | 3 +- .../{ => Kusto}/KustoResultsReader.cs | 2 +- .../DataSource/KustoQueryUtils.cs | 12 + .../Metadata/DataSourceObjectMetadata.cs | 2 +- .../DataSource/Metadata/DatabaseMetadata.cs | 2 + .../DataSource/Metadata/MetadataFactory.cs | 48 +++- .../DataSource/Monitor/MonitorClient.cs | 78 ++++++ .../DataSource/Monitor/MonitorDataSource.cs | 223 +++++++++++++++ .../DataSource/Monitor/MonitorExtensions.cs | 91 +++++++ .../Monitor/MonitorIntellisenseClient.cs | 43 +++ .../Monitor/Responses/Models/ColumnsModel.cs | 9 + .../Responses/Models/TableGroupsModel.cs | 10 + .../Monitor/Responses/Models/TablesModel.cs | 13 + .../Responses/Models/WorkspacesModel.cs | 12 + .../Monitor/Responses/WorkspaceResponse.cs | 11 + .../ReliableDataSourceConnection.cs | 4 +- .../Formatter/TSqlFormatterService.cs | 8 +- .../HostLoader.cs | 3 +- .../LanguageServices/ConnectedBindingQueue.cs | 2 +- .../LanguageServices/LanguageService.cs | 8 +- .../Metadata/MetadataService.cs | 66 ++--- .../Microsoft.Kusto.ServiceLayer.csproj | 1 + .../DataSourceModel/NodePathGenerator.cs | 4 +- .../ObjectExplorer/Nodes/TreeNode.cs | 4 +- .../ObjectExplorer/ObjectExplorerService.cs | 134 +++------ .../ObjectExplorer/ObjectExplorerSession.cs | 45 +++ src/Microsoft.Kusto.ServiceLayer/Program.cs | 4 + .../ServiceHost.cs | 47 ++-- .../Utility/CommandOptions.cs | 4 +- .../DataSource/DataSourceFactoryTests.cs | 14 +- .../KustoIntellisenseClientTests.cs | 2 +- .../KustoIntellisenseHelperTests.cs | 1 + .../DataSource/KustoClientTests.cs | 2 +- .../ConnectedBindingQueueTests.cs | 2 +- .../Metadata/MetadataServiceTests.cs | 43 ++- 49 files changed, 1070 insertions(+), 603 deletions(-) delete mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IntellisenseClientBase.cs rename src/Microsoft.Kusto.ServiceLayer/DataSource/{ => Kusto}/IKustoClient.cs (95%) rename src/Microsoft.Kusto.ServiceLayer/DataSource/{ => Kusto}/KustoClient.cs (99%) rename src/Microsoft.Kusto.ServiceLayer/DataSource/{ => Kusto}/KustoDataSource.cs (93%) rename src/Microsoft.Kusto.ServiceLayer/DataSource/{Intellisense => Kusto}/KustoIntellisenseClient.cs (55%) rename src/Microsoft.Kusto.ServiceLayer/DataSource/{Intellisense => Kusto}/KustoIntellisenseHelper.cs (97%) rename src/Microsoft.Kusto.ServiceLayer/DataSource/{ => Kusto}/KustoResultsReader.cs (96%) create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorClient.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorDataSource.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorExtensions.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorIntellisenseClient.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/ColumnsModel.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TableGroupsModel.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TablesModel.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/WorkspacesModel.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/WorkspaceResponse.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerSession.cs diff --git a/Packages.props b/Packages.props index 3e6fee77..507de312 100644 --- a/Packages.props +++ b/Packages.props @@ -25,6 +25,7 @@ + diff --git a/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs b/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs index eba00e04..ef7015b8 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs @@ -42,33 +42,22 @@ namespace Microsoft.Kusto.ServiceLayer.Admin /// /// Handle get database info request /// - private async Task HandleGetDatabaseInfoRequest( - GetDatabaseInfoParams databaseParams, - RequestContext requestContext) + private async Task HandleGetDatabaseInfoRequest(GetDatabaseInfoParams databaseParams, RequestContext requestContext) { try { - Func requestHandler = async () => + var infoResponse = await Task.Run(() => { - _connectionService.TryFindConnection(databaseParams.OwnerUri, out var connInfo); DatabaseInfo info = null; - - if (connInfo != null) + if (_connectionService.TryFindConnection(databaseParams.OwnerUri, out var connInfo)) { info = GetDatabaseInfo(connInfo); } - await requestContext.SendResult(new GetDatabaseInfoResponse() - { - DatabaseInfo = info - }); - }; - - Task task = Task.Run(async () => await requestHandler()).ContinueWithOnFaulted(async t => - { - await requestContext.SendError(t.Exception.ToString()); + return new GetDatabaseInfoResponse {DatabaseInfo = info}; }); - + + await requestContext.SendResult(infoResponse); } catch (Exception ex) { @@ -88,18 +77,9 @@ namespace Microsoft.Kusto.ServiceLayer.Admin return null; } - ReliableDataSourceConnection connection; - connInfo.TryGetConnection("Default", out connection); + connInfo.TryGetConnection(ConnectionType.Default, out ReliableDataSourceConnection connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - DataSourceObjectMetadata objectMetadata = - MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); - - List metadata = dataSource.GetChildObjects(objectMetadata, true).ToList(); - var databaseMetadata = metadata.Where(o => o.Name == connInfo.ConnectionDetails.DatabaseName); - - List databaseInfo = MetadataFactory.ConvertToDatabaseInfo(databaseMetadata); - - return databaseInfo.ElementAtOrDefault(0); + return dataSource.GetDatabaseInfo(connInfo.ConnectionDetails.ServerName, connInfo.ConnectionDetails.DatabaseName); } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs index 4d4c8987..c8ccfdb5 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs @@ -6,17 +6,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlServer.Management.Common; using Microsoft.Kusto.ServiceLayer.Connection.Contracts; -using Microsoft.Kusto.ServiceLayer.Admin.Contracts; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; -using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; using System.Diagnostics; using Microsoft.Kusto.ServiceLayer.DataSource; @@ -56,8 +53,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection private ConcurrentDictionary connectedQueues = new ConcurrentDictionary(); - private IDataSourceFactory _dataSourceFactory; - /// /// Map from script URIs to ConnectionInfo objects /// @@ -84,55 +79,9 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Service host object for sending/receiving requests/events. - /// Internal for testing purposes. /// - internal IProtocolEndpoint ServiceHost { get; set; } + private IProtocolEndpoint _serviceHost; - /// - /// Gets the connection queue - /// - internal IConnectedBindingQueue ConnectionQueue - { - get - { - return this.GetConnectedQueue("Default"); - } - } - - - /// - /// Default constructor should be private since it's a singleton class, but we need a constructor - /// for use in unit test mocking. - /// - public ConnectionService() - { - } - - /// - /// Returns a connection queue for given type - /// - /// - /// - public IConnectedBindingQueue GetConnectedQueue(string type) - { - IConnectedBindingQueue connectedBindingQueue; - if (connectedQueues.TryGetValue(type, out connectedBindingQueue)) - { - return connectedBindingQueue; - } - return null; - } - - /// - /// Returns all the connection queues - /// - public IEnumerable ConnectedQueues - { - get - { - return this.connectedQueues.Values; - } - } /// /// Register a new connection queue if not already registered @@ -182,9 +131,8 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// The params to validate /// A ConnectionCompleteParams object upon validation error, /// null upon validation success - public ConnectionCompleteParams ValidateConnectParams(ConnectParams connectionParams) + private ConnectionCompleteParams ValidateConnectParams(ConnectParams connectionParams) { - string paramValidationErrorMessage; if (connectionParams == null) { return new ConnectionCompleteParams @@ -192,7 +140,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection Messages = SR.ConnectionServiceConnectErrorNullParams }; } - if (!connectionParams.IsValid(out paramValidationErrorMessage)) + if (!connectionParams.IsValid(out string paramValidationErrorMessage)) { return new ConnectionCompleteParams { @@ -208,7 +156,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Open a connection with the specified ConnectParams /// - public virtual async Task Connect(ConnectParams connectionParams) + public async Task Connect(ConnectParams connectionParams) { // Validate parameters ConnectionCompleteParams validationResults = ValidateConnectParams(connectionParams); @@ -222,9 +170,8 @@ namespace Microsoft.Kusto.ServiceLayer.Connection connectionParams.Connection.ApplicationName = GetApplicationNameWithFeature(connectionParams.Connection.ApplicationName, connectionParams.Purpose); // If there is no ConnectionInfo in the map, create a new ConnectionInfo, // but wait until later when we are connected to add it to the map. - ConnectionInfo connectionInfo; bool connectionChanged = false; - if (!OwnerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) + if (!OwnerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out ConnectionInfo connectionInfo)) { connectionInfo = new ConnectionInfo(_dataSourceConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); } @@ -278,7 +225,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection Resource = "SQL" }; - var response = Instance.ServiceHost.SendRequest(SecurityTokenRequest.Type, requestMessage, true).Result; + var response = _serviceHost.SendRequest(SecurityTokenRequest.Type, requestMessage, true).Result; connection.UpdateAuthToken(response.Token); return response.Token; @@ -759,56 +706,31 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// List all databases on the server specified /// - public ListDatabasesResponse ListDatabases(ListDatabasesParams listDatabasesParams) + private ListDatabasesResponse ListDatabases(ListDatabasesParams listDatabasesParams) { // Verify parameters - var owner = listDatabasesParams.OwnerUri; - if (string.IsNullOrEmpty(owner)) + if (string.IsNullOrEmpty(listDatabasesParams.OwnerUri)) { throw new ArgumentException(SR.ConnectionServiceListDbErrorNullOwnerUri); } // Use the existing connection as a base for the search - ConnectionInfo info; - if (!TryFindConnection(owner, out info)) + if (!TryFindConnection(listDatabasesParams.OwnerUri, out ConnectionInfo info)) { - throw new Exception(SR.ConnectionServiceListDbErrorNotConnected(owner)); + throw new Exception(SR.ConnectionServiceListDbErrorNotConnected(listDatabasesParams.OwnerUri)); } info.TryGetConnection(ConnectionType.Default, out ReliableDataSourceConnection connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - - DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName); - ListDatabasesResponse response = new ListDatabasesResponse(); - - // Mainly used by "manage" dashboard - if (listDatabasesParams.IncludeDetails.HasTrue()) - { - IEnumerable databaseMetadataInfo = dataSource.GetChildObjects(objectMetadata, true); - List metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo); - response.Databases = metadata.ToArray(); - - return response; - } - - IEnumerable databaseMetadata = dataSource.GetChildObjects(objectMetadata); - if (databaseMetadata != null) - { - response.DatabaseNames = databaseMetadata - .Select(objMeta => objMeta.PrettyName == objMeta.Name ? objMeta.PrettyName : $"{objMeta.PrettyName} ({objMeta.Name})") - .ToArray(); - } - - return response; + return dataSource.GetDatabases(info.ConnectionDetails.ServerName, listDatabasesParams.IncludeDetails.HasTrue()); } public void InitializeService(IProtocolEndpoint serviceHost, IDataSourceConnectionFactory dataSourceConnectionFactory, - IConnectedBindingQueue connectedBindingQueue, IDataSourceFactory dataSourceFactory) + IConnectedBindingQueue connectedBindingQueue) { - ServiceHost = serviceHost; + _serviceHost = serviceHost; _dataSourceConnectionFactory = dataSourceConnectionFactory; - _dataSourceFactory = dataSourceFactory; connectedQueues.AddOrUpdate("Default", connectedBindingQueue, (key, old) => connectedBindingQueue); LockedDatabaseManager.ConnectionService = this; @@ -845,15 +767,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// /// - protected async Task HandleConnectRequest( - ConnectParams connectParams, - RequestContext requestContext) + private async Task HandleConnectRequest(ConnectParams connectParams, RequestContext requestContext) { Logger.Write(TraceEventType.Verbose, "HandleConnectRequest"); try { - RunConnectRequestHandlerTask(connectParams); + await Task.Run(async () => await RunConnectRequestHandlerTask(connectParams)); await requestContext.SendResult(true); } catch @@ -862,34 +782,22 @@ namespace Microsoft.Kusto.ServiceLayer.Connection } } - private void RunConnectRequestHandlerTask(ConnectParams connectParams) + private async Task RunConnectRequestHandlerTask(ConnectParams connectParams) { - // create a task to connect asynchronously so that other requests are not blocked in the meantime - Task.Run(async () => + try { - try + // open connection based on request details + ConnectionCompleteParams result = await Connect(connectParams); + await _serviceHost.SendEvent(ConnectionCompleteNotification.Type, result); + } + catch (Exception ex) + { + var result = new ConnectionCompleteParams { - // result is null if the ConnectParams was successfully validated - ConnectionCompleteParams result = ValidateConnectParams(connectParams); - if (result != null) - { - await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); - return; - } - - // open connection based on request details - result = await Connect(connectParams); - await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); - } - catch (Exception ex) - { - ConnectionCompleteParams result = new ConnectionCompleteParams() - { - Messages = ex.ToString() - }; - await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); - } - }).ContinueWithOnFaulted(null); + Messages = ex.ToString() + }; + await _serviceHost.SendEvent(ConnectionCompleteNotification.Type, result); + } } /// @@ -915,15 +823,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Handle disconnect requests /// - protected async Task HandleDisconnectRequest( - DisconnectParams disconnectParams, - RequestContext requestContext) + private async Task HandleDisconnectRequest(DisconnectParams disconnectParams, RequestContext requestContext) { Logger.Write(TraceEventType.Verbose, "HandleDisconnectRequest"); try { - bool result = Instance.Disconnect(disconnectParams); + bool result = Disconnect(disconnectParams); await requestContext.SendResult(result); } catch (Exception ex) @@ -936,15 +842,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Handle requests to list databases on the current server /// - protected async Task HandleListDatabasesRequest( - ListDatabasesParams listDatabasesParams, - RequestContext requestContext) + private async Task HandleListDatabasesRequest(ListDatabasesParams listDatabasesParams, RequestContext requestContext) { Logger.Write(TraceEventType.Verbose, "ListDatabasesRequest"); try { - ListDatabasesResponse result = ListDatabases(listDatabasesParams); + var result = await Task.Run(() => ListDatabases(listDatabasesParams)); await requestContext.SendResult(result); } catch (Exception ex) @@ -1025,11 +929,10 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Handles a request to change the database for a connection /// - public async Task HandleChangeDatabaseRequest( - ChangeDatabaseParams changeDatabaseParams, - RequestContext requestContext) + private async Task HandleChangeDatabaseRequest(ChangeDatabaseParams changeDatabaseParams, RequestContext requestContext) { - await requestContext.SendResult(ChangeConnectionDatabaseContext(changeDatabaseParams.OwnerUri, changeDatabaseParams.NewDatabase, true)); + bool result = await Task.Run(() => result = ChangeConnectionDatabaseContext(changeDatabaseParams.OwnerUri, changeDatabaseParams.NewDatabase, true)); + await requestContext.SendResult(result); } /// @@ -1037,60 +940,57 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// URI of the owner of the connection /// Name of the database to change the connection to - public bool ChangeConnectionDatabaseContext(string ownerUri, string newDatabaseName, bool force = false) + private bool ChangeConnectionDatabaseContext(string ownerUri, string newDatabaseName, bool force = false) { - ConnectionInfo info; - if (TryFindConnection(ownerUri, out info)) + if (!TryFindConnection(ownerUri, out ConnectionInfo info)) { - try + return false; + } + + try + { + info.ConnectionDetails.DatabaseName = newDatabaseName; + + foreach (string key in info.AllConnectionTypes) { - info.ConnectionDetails.DatabaseName = newDatabaseName; - - foreach (string key in info.AllConnectionTypes) + ReliableDataSourceConnection conn; + info.TryGetConnection(key, out conn); + if (conn != null && conn.Database != newDatabaseName) { - ReliableDataSourceConnection conn; - info.TryGetConnection(key, out conn); - if (conn != null && conn.Database != newDatabaseName) + if (info.IsCloud && force) { - if (info.IsCloud && force) - { - conn.Close(); - conn.Dispose(); - info.RemoveConnection(key); + conn.Close(); + conn.Dispose(); + info.RemoveConnection(key); - // create a kusto connection instance - ReliableDataSourceConnection connection = info.Factory.CreateDataSourceConnection(info.ConnectionDetails, ownerUri); - connection.Open(); - info.AddConnection(key, connection); - } - else - { - conn.ChangeDatabase(newDatabaseName); - } + // create a kusto connection instance + ReliableDataSourceConnection connection = info.Factory.CreateDataSourceConnection(info.ConnectionDetails, ownerUri); + connection.Open(); + info.AddConnection(key, connection); + } + else + { + conn.ChangeDatabase(newDatabaseName); } } + } - // Fire a connection changed event - ConnectionChangedParams parameters = new ConnectionChangedParams(); - IConnectionSummary summary = info.ConnectionDetails; - parameters.Connection = summary.Clone(); - parameters.OwnerUri = ownerUri; - ServiceHost.SendEvent(ConnectionChangedNotification.Type, parameters); - return true; - } - catch (Exception e) - { - Logger.Write( - TraceEventType.Error, - string.Format( - "Exception caught while trying to change database context to [{0}] for OwnerUri [{1}]. Exception:{2}", - newDatabaseName, - ownerUri, - e.ToString()) - ); - } + // Fire a connection changed event + ConnectionChangedParams parameters = new ConnectionChangedParams(); + IConnectionSummary summary = info.ConnectionDetails; + parameters.Connection = summary.Clone(); + parameters.OwnerUri = ownerUri; + _serviceHost.SendEvent(ConnectionChangedNotification.Type, parameters); + return true; + } + catch (Exception e) + { + Logger.Write( + TraceEventType.Error, + $"Exception caught while trying to change database context to [{newDatabaseName}] for OwnerUri [{ownerUri}]. Exception:{e}" + ); + return false; } - return false; } /// @@ -1129,12 +1029,12 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// private void HandleDisconnectTelemetry(ConnectionInfo connectionInfo) { - if (ServiceHost != null) + if (_serviceHost != null) { try { // Send a telemetry notification for intellisense performance metrics - ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() + _serviceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() { Params = new TelemetryProperties { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs index aff69352..26bb1762 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Threading; using System.Data; using System.Threading.Tasks; +using Microsoft.Kusto.ServiceLayer.Admin.Contracts; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; @@ -100,13 +102,16 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public abstract CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); + public abstract ListDatabasesResponse GetDatabases(string serverName, bool includeDetails); + public abstract DatabaseInfo GetDatabaseInfo(string serverName, string databaseName); + /// public DataSourceType DataSourceType { get; protected set; } /// public abstract string ClusterName { get; } - public abstract string DatabaseName { get; } + public abstract string DatabaseName { get; set; } #endregion } diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs index b22dd108..d76f324d 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs @@ -6,6 +6,8 @@ using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.DataSource.Kusto; +using Microsoft.Kusto.ServiceLayer.DataSource.Monitor; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Utility; @@ -16,8 +18,14 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource [Export(typeof(IDataSourceFactory))] public class DataSourceFactory : IDataSourceFactory { - public IDataSource Create(DataSourceType dataSourceType, ConnectionDetails connectionDetails, string ownerUri) + private const string KustoProviderName = "KUSTO"; + private const string LogAnalyticsProviderName = "LOGANALYTICS"; + private const string KustoProviderDescription = "Microsoft Azure Data Explorer"; + private const string LogAnalyticsProviderDescription = "Microsoft Azure Monitor Explorer"; + + public IDataSource Create(ConnectionDetails connectionDetails, string ownerUri) { + var dataSourceType = GetDataSourceType(); switch (dataSourceType) { case DataSourceType.Kusto: @@ -27,7 +35,12 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource var intellisenseClient = new KustoIntellisenseClient(kustoClient); return new KustoDataSource(kustoClient, intellisenseClient); } - + case DataSourceType.LogAnalytics: + { + var httpClient = new MonitorClient(connectionDetails.ServerName, connectionDetails.AccountToken); + var intellisenseClient = new MonitorIntellisenseClient(httpClient); + return new MonitorDataSource(httpClient, intellisenseClient); + } default: throw new ArgumentException($@"Unsupported data source type ""{dataSourceType}""", @@ -35,6 +48,11 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } } + private DataSourceType GetDataSourceType() + { + return Program.ServiceName.Contains("Kusto") ? DataSourceType.Kusto : DataSourceType.LogAnalytics; + } + private DataSourceConnectionDetails MapKustoConnectionDetails(ConnectionDetails connectionDetails) { if (connectionDetails.AuthenticationType == "dstsAuth" || connectionDetails.AuthenticationType == "AzureMFA") @@ -95,7 +113,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } default: - throw new ArgumentException($"Unsupported data source type \"{dataSourceType}\"", nameof(dataSourceType)); + throw new ArgumentException($@"Unsupported data source type ""{dataSourceType}""", nameof(dataSourceType)); } } @@ -109,7 +127,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } default: - throw new ArgumentException($"Unsupported data source type \"{dataSourceType}\"", nameof(dataSourceType)); + throw new ArgumentException($@"Unsupported data source type ""{dataSourceType}""", nameof(dataSourceType)); } } @@ -126,8 +144,18 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } default: - throw new ArgumentException($"Unsupported data source type \"{dataSourceType}\"", nameof(dataSourceType)); + throw new ArgumentException($@"Unsupported data source type ""{dataSourceType}""", nameof(dataSourceType)); } } + + public static string GetProviderName() + { + return Program.ServiceName.Contains("Kusto") ? KustoProviderName : LogAnalyticsProviderName; + } + + public static string GetProviderDescription() + { + return Program.ServiceName.Contains("Kusto") ? KustoProviderDescription : LogAnalyticsProviderDescription; + } } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceType.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceType.cs index 911a9116..2688211d 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceType.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceType.cs @@ -23,6 +23,6 @@ /// /// An Operations Management Suite (OMS) Log Analytics workspace. /// - OmsLogAnalytics + LogAnalytics } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs index 13f777a2..fe95e70b 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Data; using System.Threading; using System.Threading.Tasks; +using Microsoft.Kusto.ServiceLayer.Admin.Contracts; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.LanguageServices; @@ -117,5 +119,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false); Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); + ListDatabasesResponse GetDatabases(string serverName, bool includeDetails); + DatabaseInfo GetDatabaseInfo(string serverName, string databaseName); } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs index 8763dcde..978f1ef4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs @@ -4,6 +4,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource { public interface IDataSourceFactory { - IDataSource Create(DataSourceType dataSourceType, ConnectionDetails connectionDetails, string ownerUri); + IDataSource Create(ConnectionDetails connectionDetails, string ownerUri); } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs deleted file mode 100644 index 9c373ab6..00000000 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Kusto.ServiceLayer.LanguageServices; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; -using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; - -namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense -{ - public interface IIntellisenseClient - { - void UpdateDatabase(string databaseName); - ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText); - DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false); - Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); - CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); - } -} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IntellisenseClientBase.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IntellisenseClientBase.cs new file mode 100644 index 00000000..7108a2b7 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IntellisenseClientBase.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Kusto.Language; +using Kusto.Language.Editor; +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; +using CompletionItem = Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts.CompletionItem; +using Diagnostic = Kusto.Language.Diagnostic; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense +{ + public abstract class IntellisenseClientBase + { + protected GlobalState schemaState; + + public abstract void UpdateDatabase(string databaseName); + + public ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText) + { + var kustoCodeService = new KustoCodeService(queryText, schemaState); + var script = CodeScript.From(queryText, schemaState); + var parseResult = new List(); + + foreach (var codeBlock in script.Blocks) + { + parseResult.AddRange(codeBlock.Service.GetDiagnostics()); + } + + parseInfo.ParseResult = parseResult; + + if (!parseResult.Any()) + { + return Array.Empty(); + } + + // build a list of Kusto script file markers from the errors. + var markers = new List(); + + foreach (var error in parseResult) + { + script.TryGetLineAndOffset(error.Start, out var startLine, out var startOffset); + script.TryGetLineAndOffset(error.End, out var endLine, out var endOffset); + + // vscode specific format for error markers. + markers.Add(new ScriptFileMarker + { + Message = error.Message, + Level = ScriptFileMarkerLevel.Error, + ScriptRegion = new ScriptRegion + { + File = scriptFile.FilePath, + StartLineNumber = startLine, + StartColumnNumber = startOffset, + StartOffset = 0, + EndLineNumber = endLine, + EndColumnNumber = endOffset, + EndOffset = 0 + } + }); + } + + return markers.ToArray(); + } + + public DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, + bool throwOnError = false) + { + //TODOKusto: API wasnt working properly, need to check that part. + return null; + } + + public Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false) + { + var script = CodeScript.From(scriptDocumentInfo.Contents, schemaState); + script.TryGetTextPosition(textPosition.Line + 1, textPosition.Character + 1, out int position); + + var codeBlock = script.GetBlockAtPosition(position); + var quickInfo = codeBlock.Service.GetQuickInfo(position); + + return AutoCompleteHelper.ConvertQuickInfoToHover( + quickInfo.Text, + "kusto", + scriptDocumentInfo.StartLine, + scriptDocumentInfo.StartColumn, + textPosition.Character); + } + + public CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, + bool throwOnError = false) + { + var script = CodeScript.From(scriptDocumentInfo.Contents, schemaState); + script.TryGetTextPosition(textPosition.Line + 1, textPosition.Character + 1, out int position); // Gets the actual offset based on line and local offset + + var codeBlock = script.GetBlockAtPosition(position); + var completion = codeBlock.Service.GetCompletionItems(position); + scriptDocumentInfo.ScriptParseInfo.CurrentSuggestions = completion.Items; // this is declaration item so removed for now, but keep the info when api gets updated + + var completions = new List(); + foreach (var autoCompleteItem in completion.Items) + { + var label = autoCompleteItem.DisplayText; + var insertText = autoCompleteItem.Kind == CompletionKind.Table || autoCompleteItem.Kind == CompletionKind.Database + ? KustoQueryUtils.EscapeName(label) + : label; + + var completionKind = CreateCompletionItemKind(autoCompleteItem.Kind); + completions.Add(AutoCompleteHelper.CreateCompletionItem(label, autoCompleteItem.Kind.ToString(), + insertText, completionKind, scriptDocumentInfo.StartLine, scriptDocumentInfo.StartColumn, + textPosition.Character)); + } + + return completions.ToArray(); + } + + private CompletionItemKind CreateCompletionItemKind(CompletionKind kustoKind) + { + switch (kustoKind) + { + case CompletionKind.Syntax: + return CompletionItemKind.Module; + case CompletionKind.Column: + return CompletionItemKind.Field; + case CompletionKind.Variable: + return CompletionItemKind.Variable; + case CompletionKind.Table: + return CompletionItemKind.File; + case CompletionKind.Database: + return CompletionItemKind.Method; + case CompletionKind.LocalFunction: + case CompletionKind.DatabaseFunction: + case CompletionKind.BuiltInFunction: + case CompletionKind.AggregateFunction: + return CompletionItemKind.Function; + default: + return CompletionItemKind.Keyword; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/IKustoClient.cs similarity index 95% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/IKustoClient.cs index 9be38305..2cd2245f 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/IKustoClient.cs @@ -3,7 +3,7 @@ using System.Data; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Kusto.ServiceLayer.DataSource +namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto { public interface IKustoClient { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoClient.cs similarity index 99% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoClient.cs index f3603b83..fd914217 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoClient.cs @@ -17,7 +17,7 @@ using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.DataSource.Contracts; using Microsoft.Kusto.ServiceLayer.Utility; -namespace Microsoft.Kusto.ServiceLayer.DataSource +namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto { public class KustoClient : IKustoClient { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoDataSource.cs similarity index 93% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoDataSource.cs index c4a9c8d7..4bfc29e1 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoDataSource.cs @@ -1,19 +1,21 @@ // // Copyright (c) Microsoft. All Rights Reserved. // + using System; -using System.Collections.Generic; -using System.Threading; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; using System.Text; -using Newtonsoft.Json; +using System.Threading; using System.Threading.Tasks; using Kusto.Cloud.Platform.Data; using Kusto.Data; using Kusto.Data.Data; +using Microsoft.Kusto.ServiceLayer.Admin.Contracts; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.DataSource.Models; @@ -21,8 +23,9 @@ using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; +using Newtonsoft.Json; -namespace Microsoft.Kusto.ServiceLayer.DataSource +namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto { /// /// Represents Kusto utilities. @@ -30,7 +33,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public class KustoDataSource : DataSourceBase { private readonly IKustoClient _kustoClient; - private readonly IIntellisenseClient _intellisenseClient; + private readonly IntellisenseClientBase _intellisenseClient; /// /// List of databases. @@ -57,7 +60,11 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// private ConcurrentDictionary> _functionMetadata = new ConcurrentDictionary>(); - public override string DatabaseName => _kustoClient.DatabaseName; + public override string DatabaseName + { + get => _kustoClient.DatabaseName; + set => throw new NotImplementedException(); + } public override string ClusterName => _kustoClient.ClusterName; @@ -79,7 +86,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// /// Prevents a default instance of the class from being created. /// - public KustoDataSource(IKustoClient kustoClient, IIntellisenseClient intellisenseClient) + public KustoDataSource(IKustoClient kustoClient, IntellisenseClientBase intellisenseClient) { _kustoClient = kustoClient; _intellisenseClient = intellisenseClient; @@ -835,6 +842,44 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource return _intellisenseClient.GetAutoCompleteSuggestions(scriptDocumentInfo, textPosition, throwOnError); } + public override ListDatabasesResponse GetDatabases(string serverName, bool includeDetails) + { + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(serverName); + + // Mainly used by "manage" dashboard + if (includeDetails) + { + IEnumerable databaseMetadataInfo = GetChildObjects(objectMetadata, true); + List metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo); + + return new ListDatabasesResponse + { + Databases = metadata.ToArray() + }; + } + + IEnumerable databaseMetadata = GetChildObjects(objectMetadata); + if (databaseMetadata != null) + { + return new ListDatabasesResponse + { + DatabaseNames = databaseMetadata + .Select(objMeta => objMeta.PrettyName == objMeta.Name ? objMeta.PrettyName : $"{objMeta.PrettyName} ({objMeta.Name})") + .ToArray() + }; + } + + return new ListDatabasesResponse();; + } + + public override DatabaseInfo GetDatabaseInfo(string serverName, string databaseName) + { + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(serverName); + var metadata = GetChildObjects(objectMetadata, true).Where(o => o.Name == databaseName).ToList(); + List databaseInfo = MetadataFactory.ConvertToDatabaseInfo(metadata); + return databaseInfo.ElementAtOrDefault(0); + } + #endregion } } diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoIntellisenseClient.cs similarity index 55% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseClient.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoIntellisenseClient.cs index f321caa6..38f9007d 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseClient.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoIntellisenseClient.cs @@ -4,34 +4,25 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Kusto.Language; -using Kusto.Language.Editor; using Kusto.Language.Symbols; using Kusto.Language.Syntax; -using Microsoft.Kusto.ServiceLayer.LanguageServices; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; -using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; -using Diagnostic = Kusto.Language.Diagnostic; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; -namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto { - public class KustoIntellisenseClient : IIntellisenseClient + public class KustoIntellisenseClient : IntellisenseClientBase { private readonly IKustoClient _kustoClient; - /// - /// SchemaState used for getting intellisense info. - /// - private GlobalState _schemaState; - public KustoIntellisenseClient(IKustoClient kustoClient) { _kustoClient = kustoClient; - _schemaState = LoadSchemaState(kustoClient.DatabaseName, kustoClient.ClusterName); + schemaState = LoadSchemaState(kustoClient.DatabaseName, kustoClient.ClusterName); } - public void UpdateDatabase(string databaseName) + public override void UpdateDatabase(string databaseName) { - _schemaState = LoadSchemaState(databaseName, _kustoClient.ClusterName); + schemaState = LoadSchemaState(databaseName, _kustoClient.ClusterName); } private GlobalState LoadSchemaState(string databaseName, string clusterName) @@ -237,135 +228,5 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense return function.Signatures[0].Parameters; } - - public ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText) - { - var kustoCodeService = new KustoCodeService(queryText, _schemaState); - var script = CodeScript.From(queryText, _schemaState); - var parseResult = new List(); - - foreach (var codeBlock in script.Blocks) - { - parseResult.AddRange(codeBlock.Service.GetDiagnostics()); - } - - parseInfo.ParseResult = parseResult; - - if (!parseResult.Any()) - { - return Array.Empty(); - } - - // build a list of Kusto script file markers from the errors. - var markers = new List(); - - foreach (var error in parseResult) - { - script.TryGetLineAndOffset(error.Start, out var startLine, out var startOffset); - script.TryGetLineAndOffset(error.End, out var endLine, out var endOffset); - - // vscode specific format for error markers. - markers.Add(new ScriptFileMarker - { - Message = error.Message, - Level = ScriptFileMarkerLevel.Error, - ScriptRegion = new ScriptRegion - { - File = scriptFile.FilePath, - StartLineNumber = startLine, - StartColumnNumber = startOffset, - StartOffset = 0, - EndLineNumber = endLine, - EndColumnNumber = endOffset, - EndOffset = 0 - } - }); - } - - return markers.ToArray(); - } - - public DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false) - { - //TODOKusto: API wasnt working properly, need to check that part. - var abc = KustoCode.ParseAndAnalyze(queryText, _schemaState); - var kustoCodeService = new KustoCodeService(abc); - //var kustoCodeService = new KustoCodeService(queryText, globals); - var relatedInfo = kustoCodeService.GetRelatedElements(index); - - if (relatedInfo != null && relatedInfo.Elements.Count > 1) - { - } - - return null; - } - - public Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false) - { - var script = CodeScript.From(scriptDocumentInfo.Contents, _schemaState); - script.TryGetTextPosition(textPosition.Line + 1, textPosition.Character + 1, out int position); - - var codeBlock = script.GetBlockAtPosition(position); - var quickInfo = codeBlock.Service.GetQuickInfo(position); - - return AutoCompleteHelper.ConvertQuickInfoToHover( - quickInfo.Text, - "kusto", - scriptDocumentInfo.StartLine, - scriptDocumentInfo.StartColumn, - textPosition.Character); - } - - public LanguageServices.Contracts.CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false) - { - var script = CodeScript.From(scriptDocumentInfo.Contents, _schemaState); - script.TryGetTextPosition(textPosition.Line + 1, textPosition.Character + 1, - out int position); // Gets the actual offset based on line and local offset - - var codeBlock = script.GetBlockAtPosition(position); - var completion = codeBlock.Service.GetCompletionItems(position); - scriptDocumentInfo.ScriptParseInfo.CurrentSuggestions = - completion.Items; // this is declaration item so removed for now, but keep the info when api gets updated - - var completions = new List(); - foreach (var autoCompleteItem in completion.Items) - { - var label = autoCompleteItem.DisplayText; - var insertText = autoCompleteItem.Kind == CompletionKind.Table || autoCompleteItem.Kind == CompletionKind.Database - ? KustoQueryUtils.EscapeName(label) - : label; - - var completionKind = CreateCompletionItemKind(autoCompleteItem.Kind); - completions.Add(AutoCompleteHelper.CreateCompletionItem(label, autoCompleteItem.Kind.ToString(), - insertText, completionKind, scriptDocumentInfo.StartLine, scriptDocumentInfo.StartColumn, - textPosition.Character)); - } - - return completions.ToArray(); - } - - private CompletionItemKind CreateCompletionItemKind(CompletionKind kustoKind) - { - switch (kustoKind) - { - case CompletionKind.Syntax: - return CompletionItemKind.Module; - case CompletionKind.Column: - return CompletionItemKind.Field; - case CompletionKind.Variable: - return CompletionItemKind.Variable; - case CompletionKind.Table: - return CompletionItemKind.File; - case CompletionKind.Database: - return CompletionItemKind.Method; - case CompletionKind.LocalFunction: - case CompletionKind.DatabaseFunction: - case CompletionKind.BuiltInFunction: - case CompletionKind.AggregateFunction: - return CompletionItemKind.Function; - default: - return CompletionItemKind.Keyword; - } - } } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseHelper.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoIntellisenseHelper.cs similarity index 97% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseHelper.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoIntellisenseHelper.cs index 764c7543..093ba4b9 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseHelper.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoIntellisenseHelper.cs @@ -7,11 +7,12 @@ using System.Collections.Generic; using System.Linq; using Kusto.Language; using Kusto.Language.Editor; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; -namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto { /// /// Kusto specific class for intellisense helper functions. diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoResultsReader.cs similarity index 96% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoResultsReader.cs index 09fff1b1..e775692b 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Kusto/KustoResultsReader.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Data; -namespace Microsoft.Kusto.ServiceLayer.DataSource +namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto { internal class KustoResultsReader : DataReaderWrapper { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoQueryUtils.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoQueryUtils.cs index 63984df3..fcf1e796 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoQueryUtils.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoQueryUtils.cs @@ -92,6 +92,18 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } } + public static void SafeAdd(this Dictionary> dictionary, string key, DataSourceObjectMetadata node) + { + if (dictionary.ContainsKey(key)) + { + dictionary[key].Add(node); + } + else + { + dictionary[key] = new List {node}; + } + } + /// /// Add a range to a dictionary of ConcurrentDictionary. Adds range to existing IEnumerable within dictionary /// at the same key. diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DataSourceObjectMetadata.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DataSourceObjectMetadata.cs index 97d98c5a..91be060f 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DataSourceObjectMetadata.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DataSourceObjectMetadata.cs @@ -15,6 +15,6 @@ public string Urn { get; set; } - public string SizeInMB { get; set; } + } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DatabaseMetadata.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DatabaseMetadata.cs index 1b6e072a..83bfaea6 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DatabaseMetadata.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/DatabaseMetadata.cs @@ -6,5 +6,7 @@ public class DatabaseMetadata : DataSourceObjectMetadata { public string ClusterName { get; set; } + + public string SizeInMB { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs index 83336624..c4897aa5 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs @@ -59,24 +59,34 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.Metadata /// /// Converts database details shown on cluster manage dashboard to DatabaseInfo type. Add DataSourceType as param if required to show different properties /// - /// + /// /// - public static List ConvertToDatabaseInfo(IEnumerable clusterDBDetails) + public static List ConvertToDatabaseInfo(IEnumerable clusterDbDetails) { - var databaseDetails = new List(); - - if (clusterDBDetails.FirstOrDefault() is DatabaseMetadata) + if (clusterDbDetails.FirstOrDefault() is not DatabaseMetadata) { - foreach (var dbDetail in clusterDBDetails) - { - DatabaseInfo databaseInfo = new DatabaseInfo(); - long.TryParse(dbDetail.SizeInMB, out long sum_OriginalSize); - databaseInfo.Options["name"] = dbDetail.Name; - databaseInfo.Options["sizeInMB"] = (sum_OriginalSize / (1024 * 1024)).ToString(); - databaseDetails.Add(databaseInfo); - } + return new List(); } + var databaseDetails = new List(); + + foreach (var dataSourceObjectMetadata in clusterDbDetails) + { + var dbDetail = (DatabaseMetadata) dataSourceObjectMetadata; + long.TryParse(dbDetail.SizeInMB, out long sizeInMb); + + var databaseInfo = new DatabaseInfo + { + Options = + { + ["name"] = dbDetail.Name, + ["sizeInMB"] = (sizeInMb / (1024 * 1024)).ToString() + } + }; + + databaseDetails.Add(databaseInfo); + } + return databaseDetails; } @@ -100,5 +110,17 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.Metadata return databaseChildDetails; } + + public static DataSourceObjectMetadata CreateDataSourceObjectMetadata(DataSourceMetadataType datatype, string name, string urn) + { + return new DataSourceObjectMetadata + { + MetadataType = datatype, + MetadataTypeName = datatype.ToString(), + Name = name, + PrettyName = name, + Urn = $"{urn}", + }; + } } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorClient.cs new file mode 100644 index 00000000..07af32db --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorClient.cs @@ -0,0 +1,78 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.OperationalInsights; +using Microsoft.Azure.OperationalInsights.Models; +using Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses; +using Microsoft.Rest; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor +{ + public class MonitorClient + { + private readonly OperationalInsightsDataClient _queryClient; + private readonly HttpClient _httpClient; + private readonly string _workspaceId; + private readonly string _token; + private const string BaseUri = "https://api.loganalytics.io/v1/workspaces"; + private WorkspaceResponse _metadata; + + public string WorkspaceId => _workspaceId; + + public MonitorClient(string workspaceId, string token) + { + _workspaceId = workspaceId; + _token = token; + _httpClient = GetHttpClient(token); + _queryClient = new OperationalInsightsDataClient(new TokenCredentials(token)) + { + WorkspaceId = workspaceId + }; + } + + public WorkspaceResponse LoadMetadata(bool refresh = false) + { + if (_metadata != null && !refresh) + { + return _metadata; + } + + var url = $"{BaseUri}/{_workspaceId}/metadata"; + var httpResponseMessage = _httpClient.GetAsync(url).Result; + var results = httpResponseMessage.Content.ReadAsStringAsync().Result; + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + _metadata = JsonSerializer.Deserialize(results, options); + return _metadata; + } + + private HttpClient GetHttpClient(string token) + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + return httpClient; + } + + public async Task QueryAsync(string query, CancellationToken cancellationToken) + { + return await _queryClient.QueryAsync(query, cancellationToken: cancellationToken); + } + + public QueryResults Query(string query) + { + return _queryClient.Query(query); + } + + ~MonitorClient() + { + _httpClient.Dispose(); + _queryClient.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorDataSource.cs new file mode 100644 index 00000000..33d3f662 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorDataSource.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Kusto.ServiceLayer.Admin.Contracts; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; +using Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses; +using Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses.Models; +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor +{ + public class MonitorDataSource : DataSourceBase + { + private readonly MonitorClient _monitorClient; + private readonly IntellisenseClientBase _intellisenseClient; + private WorkspaceResponse _metadata; + private Dictionary> _nodes; + + public override string ClusterName => _monitorClient.WorkspaceId; + public override string DatabaseName { get; set; } + + public MonitorDataSource(MonitorClient monitorClient, IntellisenseClientBase intellisenseClient) + { + _monitorClient = monitorClient; + _intellisenseClient = intellisenseClient; + _nodes = new Dictionary>(); + _metadata = _monitorClient.LoadMetadata(); + DataSourceType = DataSourceType.LogAnalytics; + SetupTableGroups(monitorClient.WorkspaceId); + } + + private void SetupTableGroups(string workspaceId) + { + var workspace = _metadata.Workspaces.First(x => x.Id == workspaceId); + DatabaseName = $"{workspace.Name} ({workspace.Id})"; + var metadataTableGroups = _metadata.TableGroups.ToDictionary(x => x.Id); + + foreach (string workspaceTableGroup in workspace.TableGroups) + { + var tableGroup = metadataTableGroups[workspaceTableGroup]; + + var tableGroupNodeInfo = + MetadataFactory.CreateDataSourceObjectMetadata(DataSourceMetadataType.Folder, tableGroup.Name, $"{workspace.Id}.{tableGroup.Name}"); + + _nodes.SafeAdd($"{workspace.Id}", tableGroupNodeInfo); + + SetupTables(tableGroupNodeInfo); + } + } + + private void SetupTables(DataSourceObjectMetadata tableGroupNodeInfo) + { + var tables = GetNonEmptyTableNames(); + var metadataTables = _metadata.Tables.ToDictionary(x => x.Name); + + foreach (string tableName in tables) + { + var table = metadataTables[tableName]; + + var tableNodeInfo = MetadataFactory.CreateDataSourceObjectMetadata(DataSourceMetadataType.Table, table.Name, + $"{tableGroupNodeInfo.Urn}.{table.Name}"); + + _nodes.SafeAdd(tableGroupNodeInfo.Urn, tableNodeInfo); + + SetupColumns(table, tableNodeInfo); + } + } + + private IEnumerable GetNonEmptyTableNames() + { + string query = "union * | summarize count() by Type"; + var results = _monitorClient.Query(query); + return results.Tables[0].Rows.Select(x => x[0]).OrderBy(x => x); + } + + private void SetupColumns(TablesModel table, DataSourceObjectMetadata tableNodeInfo) + { + foreach (var column in table.Columns) + { + var columnNodeInfo = MetadataFactory.CreateDataSourceObjectMetadata(DataSourceMetadataType.Column, column.Name, + $"{tableNodeInfo.Urn}.{column.Name}"); + + _nodes.SafeAdd(tableNodeInfo.Urn, columnNodeInfo); + } + } + + public override async Task ExecuteQueryAsync(string query, CancellationToken cancellationToken, string databaseName = null) + { + var results = await _monitorClient.QueryAsync(query, cancellationToken); + return results.ToDataReader(); + } + + public override Task> ExecuteControlCommandAsync(string command, bool throwOnError, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override DiagnosticsInfo GetDiagnostics(DataSourceObjectMetadata parentMetadata) + { + return new DiagnosticsInfo(); + } + + public override IEnumerable GetChildObjects(DataSourceObjectMetadata parentMetadata, bool includeSizeDetails = false) + { + // columns are always leaf nodes + if (parentMetadata.MetadataType == DataSourceMetadataType.Column) + { + return Enumerable.Empty(); + } + + if (parentMetadata.MetadataType == DataSourceMetadataType.Cluster && includeSizeDetails) + { + var child = _nodes[parentMetadata.Urn].FirstOrDefault(); + return child == null ? Enumerable.Empty() : _nodes[child.Urn]; + } + + return _nodes[parentMetadata.Urn].OrderBy(x => x.PrettyName, StringComparer.OrdinalIgnoreCase); + } + + public override void Refresh(bool includeDatabase) + { + // reset the data source + _nodes = new Dictionary>(); + _metadata = _monitorClient.LoadMetadata(); + SetupTableGroups(_monitorClient.WorkspaceId); + } + + public override void Refresh(DataSourceObjectMetadata objectMetadata) + { + Refresh(false); + } + + public override void UpdateDatabase(string databaseName) + { + // LogAnalytics is treating the workspace name as the database name + var workspaceId = ParseWorkspaceId(databaseName); + _metadata = _monitorClient.LoadMetadata(true); + var workspace = _metadata.Workspaces.First(x => x.Id == workspaceId); + DatabaseName = $"{workspace.Name} ({workspace.Id})"; + _intellisenseClient.UpdateDatabase(databaseName); + } + + private string ParseWorkspaceId(string workspace) + { + var regex = new Regex(@"(?<=\().+?(?=\))"); + + return regex.IsMatch(workspace) + ? regex.Match(workspace).Value + : workspace; + } + + public override Task Exists() + { + return Task.FromResult(true); + } + + public override bool Exists(DataSourceObjectMetadata objectMetadata) + { + return true; + } + + public override string GenerateAlterFunctionScript(string functionName) + { + throw new NotImplementedException(); + } + + public override string GenerateExecuteFunctionScript(string functionName) + { + throw new NotImplementedException(); + } + + public override ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText) + { + return _intellisenseClient.GetSemanticMarkers(parseInfo, scriptFile, queryText); + } + + public override DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false) + { + return _intellisenseClient.GetDefinition(queryText, index, startLine, startColumn, throwOnError); + } + + public override Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false) + { + return _intellisenseClient.GetHoverHelp(scriptDocumentInfo, textPosition, throwOnError); + } + + public override CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false) + { + return _intellisenseClient.GetAutoCompleteSuggestions(scriptDocumentInfo, textPosition, throwOnError); + } + + public override ListDatabasesResponse GetDatabases(string serverName, bool includeDetails) + { + return new ListDatabasesResponse + { + DatabaseNames = new[] + { + DatabaseName + } + }; + } + + public override DatabaseInfo GetDatabaseInfo(string serverName, string databaseName) + { + return new DatabaseInfo + { + Options = new Dictionary + { + {"id", ClusterName}, + {"name", DatabaseName} + } + }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorExtensions.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorExtensions.cs new file mode 100644 index 00000000..15de0d56 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorExtensions.cs @@ -0,0 +1,91 @@ +using System; +using System.Data; +using System.Linq; +using Kusto.Language.Symbols; +using Microsoft.Azure.OperationalInsights.Models; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor +{ + public static class MonitorExtensions + { + /// + /// Converts QueryResults object into an IDataReader + /// + /// + /// + public static IDataReader ToDataReader(this QueryResults queryResults) + { + var resultTable = queryResults.Tables.FirstOrDefault(); + + if (resultTable == null) + { + return new DataTableReader(new DataTable()); + } + + var dataTable = new DataTable(resultTable.Name); + + foreach (var column in resultTable.Columns) + { + dataTable.Columns.Add(column.Name, MapType(column.Type)); + } + + foreach (var row in resultTable.Rows) + { + var dataRow = dataTable.NewRow(); + + for (int i = 0; i < row.Count; i++) + { + dataRow[i] = row[i] ?? DBNull.Value as object; + } + + dataTable.Rows.Add(dataRow); + } + + return new DataTableReader(dataTable); + } + + /// + /// Map Kusto type to .NET Type equivalent using scalar data types + /// + /// Here + /// Kusto Type + /// .NET Equivalent Type + private static Type MapType(string type) + { + switch (type) + { + case "bool": return Type.GetType("System.Boolean"); + case "datetime": return Type.GetType("System.DateTime"); + case "dynamic": return Type.GetType("System.Object"); + case "guid": return Type.GetType("System.Guid"); + case "int": return Type.GetType("System.Int32"); + case "long": return Type.GetType("System.Int64"); + case "real": return Type.GetType("System.Double"); + case "string": return Type.GetType("System.String"); + case "timespan": return Type.GetType("System.TimeSpan"); + case "decimal": return Type.GetType("System.Data.SqlTypes.SqlDecimal"); + + default: return typeof(string); + } + } + + public static ScalarSymbol ToSymbolType(this string type) + { + switch (type) + { + case "bool": return ScalarTypes.Bool; + case "datetime": return ScalarTypes.DateTime; + case "dynamic": return ScalarTypes.Dynamic; + case "guid": return ScalarTypes.Guid; + case "int": return ScalarTypes.Int; + case "long": return ScalarTypes.Long; + case "real": return ScalarTypes.Real; + case "string": return ScalarTypes.String; + case "timespan": return ScalarTypes.TimeSpan; + case "decimal": return ScalarTypes.Decimal; + + default: return ScalarTypes.String; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorIntellisenseClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorIntellisenseClient.cs new file mode 100644 index 00000000..664a6e94 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/MonitorIntellisenseClient.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using Kusto.Language; +using Kusto.Language.Symbols; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor +{ + public class MonitorIntellisenseClient : IntellisenseClientBase + { + private readonly MonitorClient _monitorClient; + + public MonitorIntellisenseClient(MonitorClient monitorClient) + { + _monitorClient = monitorClient; + schemaState = LoadSchemaState(monitorClient.LoadMetadata()); + } + + private GlobalState LoadSchemaState(WorkspaceResponse metadata) + { + var globalState = GlobalState.Default; + + var members = new List(); + foreach (var table in metadata.Tables) + { + var columnSymbols = table.Columns.Select(x => new ColumnSymbol(x.Name, x.Type.ToSymbolType())); + + var tableSymbol = new TableSymbol(table.Name, columnSymbols); + members.Add(tableSymbol); + } + + var databaseSymbol = new DatabaseSymbol(metadata.Workspaces.First().Id, members); + return globalState.WithDatabase(databaseSymbol); + } + + public override void UpdateDatabase(string databaseName) + { + var workspace = _monitorClient.LoadMetadata(); + schemaState = LoadSchemaState(workspace); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/ColumnsModel.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/ColumnsModel.cs new file mode 100644 index 00000000..76edbf26 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/ColumnsModel.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses.Models +{ + public class ColumnsModel + { + public string Name { get; set; } + public string Type { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TableGroupsModel.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TableGroupsModel.cs new file mode 100644 index 00000000..541a5baf --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TableGroupsModel.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses.Models +{ + public class TableGroupsModel + { + public string Id { get; set; } + public string Name { get; set; } + public string Source { get; set; } + public string[] Tables { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TablesModel.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TablesModel.cs new file mode 100644 index 00000000..c4e671be --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/TablesModel.cs @@ -0,0 +1,13 @@ +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses.Models +{ + public class TablesModel + { + public string Id { get; set; } + + public string Name { get; set; } + + public string TimeSpanColumn { get; set; } + + public ColumnsModel[] Columns { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/WorkspacesModel.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/WorkspacesModel.cs new file mode 100644 index 00000000..82d84909 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/Models/WorkspacesModel.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses.Models +{ + public class WorkspacesModel + { + public string Id { get; set; } + public string Name { get; set; } + public string Region { get; set; } + public string ResourceId { get; set; } + public string[] TableGroups { get; set; } + public string[] Tables { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/WorkspaceResponse.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/WorkspaceResponse.cs new file mode 100644 index 00000000..fac43ac2 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Monitor/Responses/WorkspaceResponse.cs @@ -0,0 +1,11 @@ +using Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses.Models; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Monitor.Responses +{ + public class WorkspaceResponse + { + public TableGroupsModel[] TableGroups { get; set; } + public TablesModel[] Tables { get; set; } + public WorkspacesModel[] Workspaces { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs index 56483e43..4a045f87 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs @@ -62,7 +62,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection _connectionDetails = connectionDetails; _dataSourceFactory = dataSourceFactory; _ownerUri = ownerUri; - _dataSource = dataSourceFactory.Create(DataSourceType.Kusto, connectionDetails, ownerUri); + _dataSource = dataSourceFactory.Create(connectionDetails, ownerUri); _connectionRetryPolicy = connectionRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); _commandRetryPolicy = commandRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); @@ -191,7 +191,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection { _connectionRetryPolicy.ExecuteAction(() => { - _dataSource = _dataSourceFactory.Create(DataSourceType.Kusto, _connectionDetails, _ownerUri); + _dataSource = _dataSourceFactory.Create(_connectionDetails, _ownerUri); }); } } diff --git a/src/Microsoft.Kusto.ServiceLayer/Formatter/TSqlFormatterService.cs b/src/Microsoft.Kusto.ServiceLayer/Formatter/TSqlFormatterService.cs index faeee98d..6feec8fb 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Formatter/TSqlFormatterService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Formatter/TSqlFormatterService.cs @@ -5,12 +5,9 @@ using System; using System.Collections.Generic; -using System.Composition; using System.Diagnostics; using System.IO; using System.Threading.Tasks; -//using Kusto.Language; -//using Kusto.Language.Editor; using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Protocol; @@ -25,8 +22,7 @@ using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range; namespace Microsoft.Kusto.ServiceLayer.Formatter { - - [Export(typeof(IHostedService))] + public class TSqlFormatterService : HostedService, IComposableService { private FormatterSettings settings; @@ -38,8 +34,6 @@ namespace Microsoft.Kusto.ServiceLayer.Formatter settings = new FormatterSettings(); } - - public override void InitializeService(IProtocolEndpoint serviceHost) { Logger.Write(TraceEventType.Verbose, "TSqlFormatter initialized"); diff --git a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs index 40c472a8..cd6e40df 100644 --- a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs @@ -70,7 +70,6 @@ namespace Microsoft.Kusto.ServiceLayer var scripter = serviceProvider.GetService(); var dataSourceConnectionFactory = serviceProvider.GetService(); var connectedBindingQueue = serviceProvider.GetService(); - var dataSourceFactory = serviceProvider.GetService(); // Initialize and register singleton services so they're accessible for any MEF service. In the future, these // could be updated to be IComposableServices, which would avoid the requirement to define a singleton instance @@ -81,7 +80,7 @@ namespace Microsoft.Kusto.ServiceLayer LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue); serviceProvider.RegisterSingleService(LanguageService.Instance); - ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue, dataSourceFactory); + ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue); serviceProvider.RegisterSingleService(ConnectionService.Instance); CredentialService.Instance.InitializeService(serviceHost); diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs index c7e8e683..4475f94d 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs @@ -96,7 +96,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices try { bindingContext.BindingLock.Reset(); - bindingContext.DataSource = _dataSourceFactory.Create(DataSourceType.Kusto, connInfo.ConnectionDetails, connInfo.OwnerUri); + bindingContext.DataSource = _dataSourceFactory.Create(connInfo.ConnectionDetails, connInfo.OwnerUri); bindingContext.BindingTimeout = DefaultBindingTimeout; bindingContext.IsConnected = true; } diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs index f756d489..1d00b0e8 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs @@ -782,10 +782,10 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices internal Hover GetHoverItem(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile) { ScriptParseInfo scriptParseInfo = GetScriptParseInfo(scriptFile.ClientUri); - ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection( - scriptFile.ClientUri, - out connInfo); + if (!ConnectionServiceInstance.TryFindConnection(scriptFile.ClientUri, out var connInfo)) + { + return null; + } if (scriptParseInfo != null && scriptParseInfo.ParseResult != null) // populate parseresult or check why it is used. { diff --git a/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs b/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs index e14aa695..2898cf26 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; -using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; @@ -20,13 +19,9 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata /// public sealed class MetadataService { - private static readonly Lazy LazyInstance = new Lazy(); - - public static MetadataService Instance => LazyInstance.Value; - private static ConnectionService _connectionService; - - internal Task MetadataListTask { get; private set; } + private static readonly Lazy LazyInstance = new Lazy(); + public static MetadataService Instance => LazyInstance.Value; /// /// Initializes the Metadata Service instance @@ -42,42 +37,17 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata /// /// Handle a metadata query request /// - internal async Task HandleMetadataListRequest( - MetadataQueryParams metadataParams, - RequestContext requestContext) + internal async Task HandleMetadataListRequest(MetadataQueryParams metadataParams, RequestContext requestContext) { try { - Func requestHandler = async () => + List metadata = await Task.Run(() => LoadMetadata(metadataParams)); + + await requestContext.SendResult(new MetadataQueryResult { - ConnectionInfo connInfo; - _connectionService.TryFindConnection(metadataParams.OwnerUri, out connInfo); - - var metadata = new List(); - if (connInfo != null) - { - ReliableDataSourceConnection connection; - connInfo.TryGetConnection("Default", out connection); - IDataSource dataSource = connection.GetUnderlyingConnection(); - - DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); - DataSourceObjectMetadata databaseMetadata = MetadataFactory.CreateDatabaseMetadata(objectMetadata, connInfo.ConnectionDetails.DatabaseName); - - IEnumerable databaseChildMetadataInfo = dataSource.GetChildObjects(databaseMetadata, true); - metadata = MetadataFactory.ConvertToObjectMetadata(databaseChildMetadataInfo); - } - - await requestContext.SendResult(new MetadataQueryResult - { - Metadata = metadata.ToArray() - }); - }; - - Task task = Task.Run(async () => await requestHandler()).ContinueWithOnFaulted(async t => - { - await requestContext.SendError(t.Exception.ToString()); + Metadata = metadata.ToArray() }); - MetadataListTask = task; + } catch (Exception ex) { @@ -85,6 +55,24 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata } } - + private List LoadMetadata(MetadataQueryParams metadataParams) + { + _connectionService.TryFindConnection(metadataParams.OwnerUri, out ConnectionInfo connInfo); + + if (connInfo == null) + { + return new List(); + } + + connInfo.TryGetConnection(ConnectionType.Default, out ReliableDataSourceConnection connection); + IDataSource dataSource = connection.GetUnderlyingConnection(); + + var clusterMetadata = MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); + var databaseMetadata = MetadataFactory.CreateDatabaseMetadata(clusterMetadata, connInfo.ConnectionDetails.DatabaseName); + var parentMetadata = dataSource.DataSourceType == DataSourceType.LogAnalytics ? clusterMetadata : databaseMetadata; + + var databaseChildMetadataInfo = dataSource.GetChildObjects(parentMetadata, true); + return MetadataFactory.ConvertToObjectMetadata(databaseChildMetadataInfo); + } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj b/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj index b5564549..c8f5cd44 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj +++ b/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/NodePathGenerator.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/NodePathGenerator.cs index 059fce41..f487089e 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/NodePathGenerator.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/NodePathGenerator.cs @@ -50,7 +50,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel NodeTypeDictionary.Add("Server", serverSet); } - internal static HashSet FindNodePaths(ObjectExplorerService.ObjectExplorerSession objectExplorerSession, string typeName, string schema, string name, string databaseName, List parentNames = null) + internal static HashSet FindNodePaths(ObjectExplorerSession objectExplorerSession, string typeName, string schema, string name, string databaseName, List parentNames = null) { if (TreeRoot == null) { @@ -86,7 +86,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel return returnSet; } - private static HashSet GenerateNodePath(ObjectExplorerService.ObjectExplorerSession objectExplorerSession, Node currentNode, string databaseName, List parentNames, string path) + private static HashSet GenerateNodePath(ObjectExplorerSession objectExplorerSession, Node currentNode, string databaseName, List parentNames, string path) { if (parentNames != null) { diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs index 8ffacbab..56566a55 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs @@ -361,7 +361,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes parent != null ? parent.GetNodePath() : "", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(TraceEventType.Error, error); - throw ex; + throw; } } @@ -412,7 +412,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes 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; + throw; } } diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index a1073fc1..131ff685 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -7,7 +7,6 @@ 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; @@ -24,8 +23,6 @@ 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 @@ -34,32 +31,29 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer /// 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. /// - [Export(typeof(IHostedService))] public class ObjectExplorerService : HostedService, IComposableService, IHostedService, IDisposable { private readonly IConnectedBindingQueue _connectedBindingQueue; - 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 ConnectionService _connectionService; private IProtocolEndpoint _serviceHost; - private ConcurrentDictionary sessionMap; - private IMultiServiceProvider serviceProvider; + private readonly ConcurrentDictionary _sessionMap; + private IMultiServiceProvider _serviceProvider; private string connectionName = "ObjectExplorer"; /// /// This timeout limits the amount of time that object explorer tasks can take to complete /// - private ObjectExplorerSettings settings; + private ObjectExplorerSettings _settings; /// /// Singleton constructor /// - [ImportingConstructor] public ObjectExplorerService(IConnectedBindingQueue connectedBindingQueue) { _connectedBindingQueue = connectedBindingQueue; - sessionMap = new ConcurrentDictionary(); + _sessionMap = new ConcurrentDictionary(); NodePathGenerator.Initialize(); } @@ -70,7 +64,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer { get { - return new ReadOnlyCollection(sessionMap.Keys.ToList()); + return new ReadOnlyCollection(_sessionMap.Keys.ToList()); } } @@ -82,11 +76,12 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer public override void SetServiceProvider(IMultiServiceProvider provider) { Validate.IsNotNull(nameof(provider), provider); - serviceProvider = provider; - connectionService = provider.GetService(); + _serviceProvider = provider; + _connectionService = provider.GetService(); + try { - connectionService.RegisterConnectedQueue(connectionName, _connectedBindingQueue); + _connectionService.RegisterConnectedQueue(connectionName, _connectedBindingQueue); } catch(Exception ex) @@ -112,7 +107,8 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer serviceHost.SetRequestHandler(RefreshRequest.Type, HandleRefreshRequest); serviceHost.SetRequestHandler(CloseSessionRequest.Type, HandleCloseSessionRequest); serviceHost.SetRequestHandler(FindNodesRequest.Type, HandleFindNodesRequest); - WorkspaceService workspaceService = WorkspaceService; + + WorkspaceService workspaceService = _serviceProvider.GetService>(); if (workspaceService != null) { workspaceService.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); @@ -120,15 +116,6 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } - /// - /// Gets the workspace service. Note: should handle case where this is null in cases where unit tests do not set this up - /// - private WorkspaceService WorkspaceService - { - get { return serviceProvider.GetService>(); } - } - - /// /// Ensure formatter settings are always up to date /// @@ -138,7 +125,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer EventContext eventContext) { // update the current settings to reflect any changes (assuming formatter settings exist) - settings = newSettings?.SqlTools?.ObjectExplorer ?? settings; + _settings = newSettings?.SqlTools?.ObjectExplorer ?? _settings; return Task.FromResult(true); } @@ -184,7 +171,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer string uri = expandParams.SessionId; ObjectExplorerSession session = null; - if (!sessionMap.TryGetValue(uri, out session)) + 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 @@ -197,7 +184,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } else { - RunExpandTask(session, expandParams); + await RunExpandTask(session, expandParams); return true; } }; @@ -214,7 +201,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer string uri = refreshParams.SessionId; ObjectExplorerSession session = null; - if (!sessionMap.TryGetValue(uri, out session)) + 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 @@ -226,7 +213,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } else { - RunExpandTask(session, refreshParams, true); + await RunExpandTask(session, refreshParams, true); } await context.SendResult(true); } @@ -249,7 +236,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer string uri = closeSessionParams.SessionId; ObjectExplorerSession session = null; bool success = false; - if (!sessionMap.TryGetValue(uri, out session)) + if (!_sessionMap.TryGetValue(uri, out session)) { Logger.Write(TraceEventType.Verbose, $"Cannot close object explorer session. Couldn't find session for uri. {uri} "); } @@ -282,17 +269,17 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer internal void CloseSession(string uri) { ObjectExplorerSession session; - if (sessionMap.TryGetValue(uri, out session)) + if (_sessionMap.TryGetValue(uri, out session)) { // Remove the session from active sessions and disconnect - if(sessionMap.TryRemove(session.Uri, out session)) + if(_sessionMap.TryRemove(session.Uri, out session)) { if (session != null && session.ConnectionInfo != null) { _connectedBindingQueue.RemoveBindingContext(session.ConnectionInfo); } } - connectionService.Disconnect(new DisconnectParams() + _connectionService.Disconnect(new DisconnectParams() { OwnerUri = uri }); @@ -306,11 +293,10 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer 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); + _settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout); if (result != null && !result.IsCompleted) { @@ -329,19 +315,10 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } } - /// - /// For tests only - /// - internal Task CreateSessionTask - { - get; - private set; - } - private async Task CreateSessionAsync(ConnectionDetails connectionDetails, string uri, CancellationToken cancellationToken) { ObjectExplorerSession session; - if (!sessionMap.TryGetValue(uri, out session)) + if (!_sessionMap.TryGetValue(uri, out session)) { // Establish a connection to the specified server/database session = await DoCreateSession(connectionDetails, uri); @@ -395,7 +372,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer { try { - int timeout = (int)TimeSpan.FromSeconds(settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout).TotalMilliseconds; + int timeout = (int)TimeSpan.FromSeconds(_settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout).TotalMilliseconds; QueueItem queueItem = _connectedBindingQueue.QueueBindingOperation( key: _connectedBindingQueue.AddConnectionContext(session.ConnectionInfo, false, connectionName, false), bindingTimeout: timeout, @@ -448,7 +425,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer ConnectionInfo connectionInfo; ConnectionCompleteParams connectionResult = await Connect(connectParams, uri); - if (!connectionService.TryFindConnection(uri, out connectionInfo)) + if (!_connectionService.TryFindConnection(uri, out connectionInfo)) { return null; } @@ -459,17 +436,17 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer return null; } - int timeout = (int)TimeSpan.FromSeconds(settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout).TotalMilliseconds; + int timeout = (int)TimeSpan.FromSeconds(_settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout).TotalMilliseconds; QueueItem queueItem = _connectedBindingQueue.QueueBindingOperation( key: _connectedBindingQueue.AddConnectionContext(connectionInfo, false, connectionName), bindingTimeout: timeout, waitForLockTimeout: timeout, bindOperation: (bindingContext, cancelToken) => { - session = ObjectExplorerSession.CreateSession(connectionResult, serviceProvider, bindingContext.DataSource, isDefaultOrSystemDatabase); + session = ObjectExplorerSession.CreateSession(connectionResult, _serviceProvider, bindingContext.DataSource, isDefaultOrSystemDatabase); session.ConnectionInfo = connectionInfo; - sessionMap.AddOrUpdate(uri, session, (key, oldSession) => session); + _sessionMap.AddOrUpdate(uri, session, (key, oldSession) => session); return session; }); @@ -493,7 +470,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer try { // open connection based on request details - ConnectionCompleteParams result = await connectionService.Connect(connectParams); + ConnectionCompleteParams result = await _connectionService.Connect(connectParams); connectionErrorMessage = result != null ? $"{result.Messages} error code:{result.ErrorNumber}" : string.Empty; if (result != null && !string.IsNullOrEmpty(result.ConnectionId)) { @@ -537,15 +514,14 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer await _serviceHost.SendEvent(SessionDisconnectedNotification.Type, result); } - private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) + private async Task 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 () => + await Task.Run(async () => { ObjectExplorerTaskResult result = await RunTaskWithTimeout(task, - settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout); + _settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout); if (result != null && !result.IsCompleted) { @@ -575,15 +551,6 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer return result; } - /// - /// For tests only - /// - internal Task ExpandTask - { - get; - set; - } - private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, CancellationToken cancellationToken, bool forceRefresh = false) { ExpandResponse response = null; @@ -628,7 +595,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer public List FindNodes(string sessionId, string typeName, string schema, string name, string databaseName, List parentNames = null) { var nodes = new List(); - var oeSession = sessionMap.GetValueOrDefault(sessionId); + var oeSession = _sessionMap.GetValueOrDefault(sessionId); if (oeSession == null) { return nodes; @@ -672,7 +639,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer private string LookupUriFromQueueKey(string queueKey) { - foreach (var session in this.sessionMap.Values) + foreach (var session in _sessionMap.Values) { var connInfo = session.ConnectionInfo; if (connInfo != null) @@ -686,39 +653,6 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } return string.Empty; } - - internal class ObjectExplorerSession - { - public ObjectExplorerSession(string uri, TreeNode root) - { - Validate.IsNotNullOrEmptyString("uri", uri); - Validate.IsNotNull("root", root); - Uri = uri; - Root = root; - } - - 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, IDataSource dataSource, bool isDefaultOrSystemDatabase) - { - DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(dataSource.ClusterName); - ServerNode rootNode = new ServerNode(response, serviceProvider, dataSource, objectMetadata); - - var session = new ObjectExplorerSession(response.OwnerUri, rootNode); - if (!isDefaultOrSystemDatabase) - { - DataSourceObjectMetadata databaseMetadata = MetadataFactory.CreateDatabaseMetadata(objectMetadata, response.ConnectionSummary.DatabaseName); - } - - return session; - } - - } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerSession.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerSession.cs new file mode 100644 index 00000000..b0f510b7 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerSession.cs @@ -0,0 +1,45 @@ +using Microsoft.Kusto.ServiceLayer.Connection; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; +using Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel; +using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes; +using Microsoft.SqlTools.Extensibility; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer +{ + internal class ObjectExplorerSession + { + internal ObjectExplorerSession(string uri, TreeNode root) + { + Validate.IsNotNullOrEmptyString("uri", uri); + Validate.IsNotNull("root", root); + Uri = uri; + Root = root; + } + + 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, + IDataSource dataSource, bool isDefaultOrSystemDatabase) + { + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(dataSource.ClusterName); + var rootNode = new ServerNode(response, serviceProvider, dataSource, objectMetadata); + + var session = new ObjectExplorerSession(response.OwnerUri, rootNode); + if (!isDefaultOrSystemDatabase) + { + DataSourceObjectMetadata databaseMetadata = + MetadataFactory.CreateDatabaseMetadata(objectMetadata, response.ConnectionSummary.DatabaseName); + } + + return session; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/Program.cs b/src/Microsoft.Kusto.ServiceLayer/Program.cs index 34abf395..9eda3d4a 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Program.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Program.cs @@ -14,6 +14,8 @@ namespace Microsoft.Kusto.ServiceLayer /// internal class Program { + internal static string ServiceName; + /// /// Main entry point into the SQL Tools API Service Layer /// @@ -28,6 +30,8 @@ namespace Microsoft.Kusto.ServiceLayer return; } + ServiceName = commandOptions.ServiceName; + string logFilePath = commandOptions.LogFilePath; if (string.IsNullOrWhiteSpace(logFilePath)) { diff --git a/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs b/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs index d6f66dc4..8cdf6c56 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.Kusto.ServiceLayer.Connection; +using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Hosting; @@ -27,8 +28,6 @@ namespace Microsoft.Kusto.ServiceLayer /// public sealed class ServiceHost : ServiceHostBase { - public const string ProviderName = "KUSTO"; - private const string ProviderDescription = "Microsoft Azure Data Explorer"; private const string ProviderProtocolVersion = "1.0"; /// @@ -36,8 +35,9 @@ namespace Microsoft.Kusto.ServiceLayer /// prior to the process shutting down. /// private const int ShutdownTimeoutInSeconds = 120; - public static readonly string[] CompletionTriggerCharacters = new string[] { ".", "-", ":", "\\", "[", "\"" }; - private IMultiServiceProvider serviceProvider; + + private static readonly string[] CompletionTriggerCharacters = new[] { ".", "-", ":", "\\", "[", "\"" }; + private IMultiServiceProvider _serviceProvider; #region Singleton Instance Code @@ -69,11 +69,11 @@ namespace Microsoft.Kusto.ServiceLayer { get { - return serviceProvider; + return _serviceProvider; } internal set { - serviceProvider = value; + _serviceProvider = value; } } @@ -196,32 +196,31 @@ namespace Microsoft.Kusto.ServiceLayer /// /// Handles a request for the capabilities request /// - internal async Task HandleCapabilitiesRequest( - CapabilitiesRequest initializeParams, - RequestContext requestContext) + private async Task HandleCapabilitiesRequest(CapabilitiesRequest initializeParams, RequestContext requestContext) { - await requestContext.SendResult( - new CapabilitiesResult + string providerName = DataSourceFactory.GetProviderName(); + string providerDescription = DataSourceFactory.GetProviderDescription(); + + var capabilitiesResult = new CapabilitiesResult + { + Capabilities = new DmpServerCapabilities { - Capabilities = new DmpServerCapabilities - { - ProtocolVersion = ServiceHost.ProviderProtocolVersion, - ProviderName = ServiceHost.ProviderName, - ProviderDisplayName = ServiceHost.ProviderDescription, - ConnectionProvider = ConnectionProviderOptionsHelper.BuildConnectionProviderOptions(), - // AdminServicesProvider = AdminServicesProviderOptionsHelper.BuildAdminServicesProviderOptions(), // TODOKusto: May need it later as its in SqlTools.ServiceLayer - Features = FeaturesMetadataProviderHelper.CreateFeatureMetadataProviders() - } + ProtocolVersion = ProviderProtocolVersion, + ProviderName = providerName, + ProviderDisplayName = providerDescription, + ConnectionProvider = ConnectionProviderOptionsHelper.BuildConnectionProviderOptions(), + // AdminServicesProvider = AdminServicesProviderOptionsHelper.BuildAdminServicesProviderOptions(), // TODOKusto: May need it later as its in SqlTools.ServiceLayer + Features = FeaturesMetadataProviderHelper.CreateFeatureMetadataProviders() } - ); + }; + + await requestContext.SendResult(capabilitiesResult); } /// /// Handles the version request. Sends back the server version as result. /// - private static async Task HandleVersionRequest( - object versionRequestParams, - RequestContext requestContext) + private static async Task HandleVersionRequest(object versionRequestParams, RequestContext requestContext) { await requestContext.SendResult(serviceVersion.ToString()); } diff --git a/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs b/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs index 3ffde1ad..ee99604b 100644 --- a/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs +++ b/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; -using System.IO; namespace Microsoft.SqlTools.Hosting.Utility { @@ -61,6 +60,9 @@ namespace Microsoft.SqlTools.Hosting.Utility case "-help": ShouldExit = true; return; + case "-service-name": + ServiceName = args[++i]; + break; default: ErrorMessage += string.Format("Unknown argument \"{0}\"" + Environment.NewLine, argName); break; diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs index 6d403b3b..24cae278 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs @@ -11,19 +11,19 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource { public class DataSourceFactoryTests { - [TestCase(typeof(ArgumentException), "ConnectionString", "AzureAccountToken")] - public void Create_Throws_Exceptions_For_InvalidParams(Type exceptionType, - string connectionString, - string azureAccountToken) + [TestCase(typeof(ArgumentException), "ConnectionString", "", "AzureMFA")] + [TestCase(typeof(ArgumentException), "ConnectionString", "", "dstsAuth")] + public void Create_Throws_Exceptions_For_InvalidAzureAccountToken(Type exceptionType, string connectionString, string azureAccountToken, string authType) { + Program.ServiceName = "Kusto"; var dataSourceFactory = new DataSourceFactory(); var connectionDetails = new ConnectionDetails { ConnectionString = connectionString, - AccountToken = azureAccountToken + AccountToken = azureAccountToken, + AuthenticationType = authType }; - Assert.Throws(exceptionType, - () => dataSourceFactory.Create(DataSourceType.None, connectionDetails, "")); + Assert.Throws(exceptionType, () => dataSourceFactory.Create(connectionDetails, "")); } [Test] diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs index 42295401..8c2ba4be 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.DataSource.Kusto; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Moq; diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs index ec24b83e..7c0c87ea 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs @@ -1,4 +1,5 @@ using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.DataSource.Kusto; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using NUnit.Framework; diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs index 06e1bd02..10e3a189 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs @@ -1,6 +1,6 @@ using System; -using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource.Kusto; using NUnit.Framework; namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/ConnectedBindingQueueTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/ConnectedBindingQueueTests.cs index dd8c4c11..3f4b59c3 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/ConnectedBindingQueueTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/ConnectedBindingQueueTests.cs @@ -100,7 +100,7 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices var dataSourceFactory = new Mock(); var dataSourceMock = new Mock(); dataSourceFactory - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny())) .Returns(dataSourceMock.Object); var connectedBindingQueue = diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/Metadata/MetadataServiceTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/Metadata/MetadataServiceTests.cs index 2a115fb2..658ec2e2 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/Metadata/MetadataServiceTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/Metadata/MetadataServiceTests.cs @@ -1,8 +1,14 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.Metadata; using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Moq; using NUnit.Framework; @@ -11,25 +17,42 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.Metadata public class MetadataServiceTests { [Test] - public void HandleMetadataListRequest_Sets_MetadataListTask() + public async Task HandleMetadataListRequest_Sets_MetadataListTask() { var serviceHostMock = new Mock(); var connectionServiceMock = new Mock(); var connectionFactoryMock = new Mock(); + var requestContextMock = new Mock>(); + requestContextMock.Setup(x => x.SendResult(It.IsAny())).Returns(Task.CompletedTask); + + var dataSourceMock = new Mock(); + dataSourceMock.Setup(x => x.GetChildObjects(It.IsAny(), It.IsAny())) + .Returns(new List {new DataSourceObjectMetadata {PrettyName = "TestName"}}); + + var dataSourceFactoryMock = new Mock(); + dataSourceFactoryMock.Setup(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(dataSourceMock.Object); + + var reliableDataSource = new ReliableDataSourceConnection(new ConnectionDetails(), RetryPolicyFactory.NoRetryPolicy, + RetryPolicyFactory.NoRetryPolicy, dataSourceFactoryMock.Object, ""); - var connectionInfo = new ConnectionInfo(connectionFactoryMock.Object, "", new ConnectionDetails()); + var connectionDetails = new ConnectionDetails + { + ServerName = "ServerName", + DatabaseName = "DatabaseName" + }; + var connectionInfo = new ConnectionInfo(connectionFactoryMock.Object, "", connectionDetails); + connectionInfo.AddConnection(ConnectionType.Default, reliableDataSource); + connectionServiceMock.Setup(x => x.TryFindConnection(It.IsAny(), out connectionInfo)); var metadataService = new MetadataService(); metadataService.InitializeService(serviceHostMock.Object, connectionServiceMock.Object); - - Assert.IsNull(metadataService.MetadataListTask); - - var task = metadataService.HandleMetadataListRequest(new MetadataQueryParams(), - new RequestContext()); - task.Wait(); - - Assert.IsNotNull(metadataService.MetadataListTask); + + await metadataService.HandleMetadataListRequest(new MetadataQueryParams(), requestContextMock.Object); + + requestContextMock.Verify(x => x.SendResult(It.Is(result => result.Metadata.First().Name == "TestName")), + Times.Once()); } } } \ No newline at end of file