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
This commit is contained in:
Justin M
2021-06-25 21:40:45 -07:00
committed by GitHub
parent 9877af54b9
commit 1f7da97e6c
49 changed files with 1070 additions and 603 deletions

View File

@@ -25,6 +25,7 @@
<PackageReference Update="Microsoft.Azure.Kusto.Language" Version="9.0.4"/>
<PackageReference Update="Microsoft.SqlServer.Assessment" Version="[1.0.305]" />
<PackageReference Update="Microsoft.SqlServer.Migration.Assessment" Version="1.0.20210614.603" />
<PackageReference Update="Microsoft.Azure.OperationalInsights" Version="1.0.0" />
<PackageReference Update="Moq" Version="4.8.2" />
<PackageReference Update="nunit" Version="3.12.0" />

View File

@@ -42,33 +42,22 @@ namespace Microsoft.Kusto.ServiceLayer.Admin
/// <summary>
/// Handle get database info request
/// </summary>
private async Task HandleGetDatabaseInfoRequest(
GetDatabaseInfoParams databaseParams,
RequestContext<GetDatabaseInfoResponse> requestContext)
private async Task HandleGetDatabaseInfoRequest(GetDatabaseInfoParams databaseParams, RequestContext<GetDatabaseInfoResponse> requestContext)
{
try
{
Func<Task> 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<DataSourceObjectMetadata> metadata = dataSource.GetChildObjects(objectMetadata, true).ToList();
var databaseMetadata = metadata.Where(o => o.Name == connInfo.ConnectionDetails.DatabaseName);
List<DatabaseInfo> databaseInfo = MetadataFactory.ConvertToDatabaseInfo(databaseMetadata);
return databaseInfo.ElementAtOrDefault(0);
return dataSource.GetDatabaseInfo(connInfo.ConnectionDetails.ServerName, connInfo.ConnectionDetails.DatabaseName);
}
}
}

View File

@@ -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<string, IConnectedBindingQueue> connectedQueues = new ConcurrentDictionary<string, IConnectedBindingQueue>();
private IDataSourceFactory _dataSourceFactory;
/// <summary>
/// Map from script URIs to ConnectionInfo objects
/// </summary>
@@ -84,55 +79,9 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <summary>
/// Service host object for sending/receiving requests/events.
/// Internal for testing purposes.
/// </summary>
internal IProtocolEndpoint ServiceHost { get; set; }
private IProtocolEndpoint _serviceHost;
/// <summary>
/// Gets the connection queue
/// </summary>
internal IConnectedBindingQueue ConnectionQueue
{
get
{
return this.GetConnectedQueue("Default");
}
}
/// <summary>
/// Default constructor should be private since it's a singleton class, but we need a constructor
/// for use in unit test mocking.
/// </summary>
public ConnectionService()
{
}
/// <summary>
/// Returns a connection queue for given type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public IConnectedBindingQueue GetConnectedQueue(string type)
{
IConnectedBindingQueue connectedBindingQueue;
if (connectedQueues.TryGetValue(type, out connectedBindingQueue))
{
return connectedBindingQueue;
}
return null;
}
/// <summary>
/// Returns all the connection queues
/// </summary>
public IEnumerable<IConnectedBindingQueue> ConnectedQueues
{
get
{
return this.connectedQueues.Values;
}
}
/// <summary>
/// Register a new connection queue if not already registered
@@ -182,9 +131,8 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <param name="connectionParams">The params to validate</param>
/// <returns>A ConnectionCompleteParams object upon validation error,
/// null upon validation success</returns>
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
/// <summary>
/// Open a connection with the specified ConnectParams
/// </summary>
public virtual async Task<ConnectionCompleteParams> Connect(ConnectParams connectionParams)
public async Task<ConnectionCompleteParams> 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
/// <summary>
/// List all databases on the server specified
/// </summary>
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<DataSourceObjectMetadata> databaseMetadataInfo = dataSource.GetChildObjects(objectMetadata, true);
List<DatabaseInfo> metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo);
response.Databases = metadata.ToArray();
return response;
}
IEnumerable<DataSourceObjectMetadata> 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
/// <param name="connectParams"></param>
/// <param name="requestContext"></param>
/// <returns></returns>
protected async Task HandleConnectRequest(
ConnectParams connectParams,
RequestContext<bool> requestContext)
private async Task HandleConnectRequest(ConnectParams connectParams, RequestContext<bool> 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);
}
}
/// <summary>
@@ -915,15 +823,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <summary>
/// Handle disconnect requests
/// </summary>
protected async Task HandleDisconnectRequest(
DisconnectParams disconnectParams,
RequestContext<bool> requestContext)
private async Task HandleDisconnectRequest(DisconnectParams disconnectParams, RequestContext<bool> 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
/// <summary>
/// Handle requests to list databases on the current server
/// </summary>
protected async Task HandleListDatabasesRequest(
ListDatabasesParams listDatabasesParams,
RequestContext<ListDatabasesResponse> requestContext)
private async Task HandleListDatabasesRequest(ListDatabasesParams listDatabasesParams, RequestContext<ListDatabasesResponse> 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
/// <summary>
/// Handles a request to change the database for a connection
/// </summary>
public async Task HandleChangeDatabaseRequest(
ChangeDatabaseParams changeDatabaseParams,
RequestContext<bool> requestContext)
private async Task HandleChangeDatabaseRequest(ChangeDatabaseParams changeDatabaseParams, RequestContext<bool> 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);
}
/// <summary>
@@ -1037,60 +940,57 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// </summary>
/// <param name="ownerUri">URI of the owner of the connection</param>
/// <param name="newDatabaseName">Name of the database to change the connection to</param>
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;
}
/// <summary>
@@ -1129,12 +1029,12 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <param name="info"></param>
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
{

View File

@@ -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);
/// <inheritdoc/>
public DataSourceType DataSourceType { get; protected set; }
/// <inheritdoc/>
public abstract string ClusterName { get; }
public abstract string DatabaseName { get; }
public abstract string DatabaseName { get; set; }
#endregion
}

View File

@@ -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;
}
}
}

View File

@@ -23,6 +23,6 @@
/// <summary>
/// An Operations Management Suite (OMS) Log Analytics workspace.
/// </summary>
OmsLogAnalytics
LogAnalytics
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<Diagnostic>();
foreach (var codeBlock in script.Blocks)
{
parseResult.AddRange(codeBlock.Service.GetDiagnostics());
}
parseInfo.ParseResult = parseResult;
if (!parseResult.Any())
{
return Array.Empty<ScriptFileMarker>();
}
// build a list of Kusto script file markers from the errors.
var markers = new List<ScriptFileMarker>();
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<CompletionItem>();
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;
}
}
}
}

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -1,19 +1,21 @@
// <copyright file="KustoDataSource.cs" company="Microsoft">
// Copyright (c) Microsoft. All Rights Reserved.
// </copyright>
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
{
/// <summary>
/// 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;
/// <summary>
/// List of databases.
@@ -57,7 +60,11 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
/// </summary>
private ConcurrentDictionary<string, IEnumerable<FunctionMetadata>> _functionMetadata = new ConcurrentDictionary<string, IEnumerable<FunctionMetadata>>();
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
/// <summary>
/// Prevents a default instance of the <see cref="IDataSource"/> class from being created.
/// </summary>
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<DataSourceObjectMetadata> databaseMetadataInfo = GetChildObjects(objectMetadata, true);
List<DatabaseInfo> metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo);
return new ListDatabasesResponse
{
Databases = metadata.ToArray()
};
}
IEnumerable<DataSourceObjectMetadata> 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> databaseInfo = MetadataFactory.ConvertToDatabaseInfo(metadata);
return databaseInfo.ElementAtOrDefault(0);
}
#endregion
}
}

View File

@@ -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;
/// <summary>
/// SchemaState used for getting intellisense info.
/// </summary>
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<Diagnostic>();
foreach (var codeBlock in script.Blocks)
{
parseResult.AddRange(codeBlock.Service.GetDiagnostics());
}
parseInfo.ParseResult = parseResult;
if (!parseResult.Any())
{
return Array.Empty<ScriptFileMarker>();
}
// build a list of Kusto script file markers from the errors.
var markers = new List<ScriptFileMarker>();
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<LanguageServices.Contracts.CompletionItem>();
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;
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Kusto specific class for intellisense helper functions.

View File

@@ -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
{

View File

@@ -92,6 +92,18 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
}
}
public static void SafeAdd(this Dictionary<string, List<DataSourceObjectMetadata>> dictionary, string key, DataSourceObjectMetadata node)
{
if (dictionary.ContainsKey(key))
{
dictionary[key].Add(node);
}
else
{
dictionary[key] = new List<DataSourceObjectMetadata> {node};
}
}
/// <summary>
/// Add a range to a dictionary of ConcurrentDictionary. Adds range to existing IEnumerable within dictionary
/// at the same key.

View File

@@ -15,6 +15,6 @@
public string Urn { get; set; }
public string SizeInMB { get; set; }
}
}

View File

@@ -6,5 +6,7 @@
public class DatabaseMetadata : DataSourceObjectMetadata
{
public string ClusterName { get; set; }
public string SizeInMB { get; set; }
}
}

View File

@@ -59,24 +59,34 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.Metadata
/// <summary>
/// Converts database details shown on cluster manage dashboard to DatabaseInfo type. Add DataSourceType as param if required to show different properties
/// </summary>
/// <param name="clusterDBDetails"></param>
/// <param name="clusterDbDetails"></param>
/// <returns></returns>
public static List<DatabaseInfo> ConvertToDatabaseInfo(IEnumerable<DataSourceObjectMetadata> clusterDBDetails)
public static List<DatabaseInfo> ConvertToDatabaseInfo(IEnumerable<DataSourceObjectMetadata> clusterDbDetails)
{
var databaseDetails = new List<DatabaseInfo>();
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<DatabaseInfo>();
}
var databaseDetails = new List<DatabaseInfo>();
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}",
};
}
}
}

View File

@@ -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<WorkspaceResponse>(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<QueryResults> 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();
}
}
}

View File

@@ -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<string, List<DataSourceObjectMetadata>> _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<string, List<DataSourceObjectMetadata>>();
_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<string> 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<IDataReader> ExecuteQueryAsync(string query, CancellationToken cancellationToken, string databaseName = null)
{
var results = await _monitorClient.QueryAsync(query, cancellationToken);
return results.ToDataReader();
}
public override Task<IEnumerable<T>> ExecuteControlCommandAsync<T>(string command, bool throwOnError, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public override DiagnosticsInfo GetDiagnostics(DataSourceObjectMetadata parentMetadata)
{
return new DiagnosticsInfo();
}
public override IEnumerable<DataSourceObjectMetadata> GetChildObjects(DataSourceObjectMetadata parentMetadata, bool includeSizeDetails = false)
{
// columns are always leaf nodes
if (parentMetadata.MetadataType == DataSourceMetadataType.Column)
{
return Enumerable.Empty<DataSourceObjectMetadata>();
}
if (parentMetadata.MetadataType == DataSourceMetadataType.Cluster && includeSizeDetails)
{
var child = _nodes[parentMetadata.Urn].FirstOrDefault();
return child == null ? Enumerable.Empty<DataSourceObjectMetadata>() : _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<string, List<DataSourceObjectMetadata>>();
_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<bool> 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<string, object>
{
{"id", ClusterName},
{"name", DatabaseName}
}
};
}
}
}

View File

@@ -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
{
/// <summary>
/// Converts QueryResults object into an IDataReader
/// </summary>
/// <param name="queryResults"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Map Kusto type to .NET Type equivalent using scalar data types
/// </summary>
/// <seealso href="https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/scalar-data-types/">Here</seealso>
/// <param name="type">Kusto Type</param>
/// <returns>.NET Equivalent Type</returns>
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;
}
}
}
}

View File

@@ -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<Symbol>();
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);
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
});
}
}

View File

@@ -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<TSqlFormatterService>, 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");

View File

@@ -70,7 +70,6 @@ namespace Microsoft.Kusto.ServiceLayer
var scripter = serviceProvider.GetService<IScripter>();
var dataSourceConnectionFactory = serviceProvider.GetService<IDataSourceConnectionFactory>();
var connectedBindingQueue = serviceProvider.GetService<IConnectedBindingQueue>();
var dataSourceFactory = serviceProvider.GetService<IDataSourceFactory>();
// 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);

View File

@@ -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;
}

View File

@@ -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.
{

View File

@@ -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
/// </summary>
public sealed class MetadataService
{
private static readonly Lazy<MetadataService> LazyInstance = new Lazy<MetadataService>();
public static MetadataService Instance => LazyInstance.Value;
private static ConnectionService _connectionService;
internal Task MetadataListTask { get; private set; }
private static readonly Lazy<MetadataService> LazyInstance = new Lazy<MetadataService>();
public static MetadataService Instance => LazyInstance.Value;
/// <summary>
/// Initializes the Metadata Service instance
@@ -42,42 +37,17 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata
/// <summary>
/// Handle a metadata query request
/// </summary>
internal async Task HandleMetadataListRequest(
MetadataQueryParams metadataParams,
RequestContext<MetadataQueryResult> requestContext)
internal async Task HandleMetadataListRequest(MetadataQueryParams metadataParams, RequestContext<MetadataQueryResult> requestContext)
{
try
{
Func<Task> requestHandler = async () =>
List<ObjectMetadata> metadata = await Task.Run(() => LoadMetadata(metadataParams));
await requestContext.SendResult(new MetadataQueryResult
{
ConnectionInfo connInfo;
_connectionService.TryFindConnection(metadataParams.OwnerUri, out connInfo);
var metadata = new List<ObjectMetadata>();
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<DataSourceObjectMetadata> 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<ObjectMetadata> LoadMetadata(MetadataQueryParams metadataParams)
{
_connectionService.TryFindConnection(metadataParams.OwnerUri, out ConnectionInfo connInfo);
if (connInfo == null)
{
return new List<ObjectMetadata>();
}
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);
}
}
}

View File

@@ -22,6 +22,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.OperationalInsights" />
<PackageReference Include="Microsoft.SqlServer.DACFx" />
<PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="Microsoft.Azure.Kusto.Data" />

View File

@@ -50,7 +50,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
NodeTypeDictionary.Add("Server", serverSet);
}
internal static HashSet<string> FindNodePaths(ObjectExplorerService.ObjectExplorerSession objectExplorerSession, string typeName, string schema, string name, string databaseName, List<string> parentNames = null)
internal static HashSet<string> FindNodePaths(ObjectExplorerSession objectExplorerSession, string typeName, string schema, string name, string databaseName, List<string> parentNames = null)
{
if (TreeRoot == null)
{
@@ -86,7 +86,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel
return returnSet;
}
private static HashSet<string> GenerateNodePath(ObjectExplorerService.ObjectExplorerSession objectExplorerSession, Node currentNode, string databaseName, List<string> parentNames, string path)
private static HashSet<string> GenerateNodePath(ObjectExplorerSession objectExplorerSession, Node currentNode, string databaseName, List<string> parentNames, string path)
{
if (parentNames != null)
{

View File

@@ -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;
}
}

View File

@@ -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.
/// </summary>
[Export(typeof(IHostedService))]
public class ObjectExplorerService : HostedService<ObjectExplorerService>, 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<string, ObjectExplorerSession> sessionMap;
private IMultiServiceProvider serviceProvider;
private readonly ConcurrentDictionary<string, ObjectExplorerSession> _sessionMap;
private IMultiServiceProvider _serviceProvider;
private string connectionName = "ObjectExplorer";
/// <summary>
/// This timeout limits the amount of time that object explorer tasks can take to complete
/// </summary>
private ObjectExplorerSettings settings;
private ObjectExplorerSettings _settings;
/// <summary>
/// Singleton constructor
/// </summary>
[ImportingConstructor]
public ObjectExplorerService(IConnectedBindingQueue connectedBindingQueue)
{
_connectedBindingQueue = connectedBindingQueue;
sessionMap = new ConcurrentDictionary<string, ObjectExplorerSession>();
_sessionMap = new ConcurrentDictionary<string, ObjectExplorerSession>();
NodePathGenerator.Initialize();
}
@@ -70,7 +64,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
{
get
{
return new ReadOnlyCollection<string>(sessionMap.Keys.ToList());
return new ReadOnlyCollection<string>(_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<ConnectionService>();
_serviceProvider = provider;
_connectionService = provider.GetService<ConnectionService>();
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<SqlToolsSettings> workspaceService = WorkspaceService;
WorkspaceService<SqlToolsSettings> workspaceService = _serviceProvider.GetService<WorkspaceService<SqlToolsSettings>>();
if (workspaceService != null)
{
workspaceService.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
@@ -120,15 +116,6 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
}
/// <summary>
/// Gets the workspace service. Note: should handle case where this is null in cases where unit tests do not set this up
/// </summary>
private WorkspaceService<SqlToolsSettings> WorkspaceService
{
get { return serviceProvider.GetService<WorkspaceService<SqlToolsSettings>>(); }
}
/// <summary>
/// Ensure formatter settings are always up to date
/// </summary>
@@ -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
}
}
/// <summary>
/// For tests only
/// </summary>
internal Task CreateSessionTask
{
get;
private set;
}
private async Task<SessionCreatedParameters> CreateSessionAsync(ConnectionDetails connectionDetails, string uri, CancellationToken cancellationToken)
{
ObjectExplorerSession session;
if (!sessionMap.TryGetValue(uri, out session))
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;
}
/// <summary>
/// For tests only
/// </summary>
internal Task ExpandTask
{
get;
set;
}
private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, CancellationToken cancellationToken, bool forceRefresh = false)
{
ExpandResponse response = null;
@@ -628,7 +595,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
public List<TreeNode> FindNodes(string sessionId, string typeName, string schema, string name, string databaseName, List<string> parentNames = null)
{
var nodes = new List<TreeNode>();
var oeSession = sessionMap.GetValueOrDefault(sessionId);
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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -14,6 +14,8 @@ namespace Microsoft.Kusto.ServiceLayer
/// </summary>
internal class Program
{
internal static string ServiceName;
/// <summary>
/// Main entry point into the SQL Tools API Service Layer
/// </summary>
@@ -28,6 +30,8 @@ namespace Microsoft.Kusto.ServiceLayer
return;
}
ServiceName = commandOptions.ServiceName;
string logFilePath = commandOptions.LogFilePath;
if (string.IsNullOrWhiteSpace(logFilePath))
{

View File

@@ -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
/// </summary>
public sealed class ServiceHost : ServiceHostBase
{
public const string ProviderName = "KUSTO";
private const string ProviderDescription = "Microsoft Azure Data Explorer";
private const string ProviderProtocolVersion = "1.0";
/// <summary>
@@ -36,8 +35,9 @@ namespace Microsoft.Kusto.ServiceLayer
/// prior to the process shutting down.
/// </summary>
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
/// <summary>
/// Handles a request for the capabilities request
/// </summary>
internal async Task HandleCapabilitiesRequest(
CapabilitiesRequest initializeParams,
RequestContext<CapabilitiesResult> requestContext)
private async Task HandleCapabilitiesRequest(CapabilitiesRequest initializeParams, RequestContext<CapabilitiesResult> 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);
}
/// <summary>
/// Handles the version request. Sends back the server version as result.
/// </summary>
private static async Task HandleVersionRequest(
object versionRequestParams,
RequestContext<string> requestContext)
private static async Task HandleVersionRequest(object versionRequestParams, RequestContext<string> requestContext)
{
await requestContext.SendResult(serviceVersion.ToString());
}

View File

@@ -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;

View File

@@ -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]

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -100,7 +100,7 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices
var dataSourceFactory = new Mock<IDataSourceFactory>();
var dataSourceMock = new Mock<IDataSource>();
dataSourceFactory
.Setup(x => x.Create(It.IsAny<DataSourceType>(), It.IsAny<ConnectionDetails>(), It.IsAny<string>()))
.Setup(x => x.Create(It.IsAny<ConnectionDetails>(), It.IsAny<string>()))
.Returns(dataSourceMock.Object);
var connectedBindingQueue =

View File

@@ -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<IProtocolEndpoint>();
var connectionServiceMock = new Mock<ConnectionService>();
var connectionFactoryMock = new Mock<IDataSourceConnectionFactory>();
var requestContextMock = new Mock<RequestContext<MetadataQueryResult>>();
requestContextMock.Setup(x => x.SendResult(It.IsAny<MetadataQueryResult>())).Returns(Task.CompletedTask);
var dataSourceMock = new Mock<IDataSource>();
dataSourceMock.Setup(x => x.GetChildObjects(It.IsAny<DataSourceObjectMetadata>(), It.IsAny<bool>()))
.Returns(new List<DataSourceObjectMetadata> {new DataSourceObjectMetadata {PrettyName = "TestName"}});
var dataSourceFactoryMock = new Mock<IDataSourceFactory>();
dataSourceFactoryMock.Setup(x => x.Create(It.IsAny<ConnectionDetails>(), It.IsAny<string>()))
.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<string>(), out connectionInfo));
var metadataService = new MetadataService();
metadataService.InitializeService(serviceHostMock.Object, connectionServiceMock.Object);
Assert.IsNull(metadataService.MetadataListTask);
var task = metadataService.HandleMetadataListRequest(new MetadataQueryParams(),
new RequestContext<MetadataQueryResult>());
task.Wait();
Assert.IsNotNull(metadataService.MetadataListTask);
await metadataService.HandleMetadataListRequest(new MetadataQueryParams(), requestContextMock.Object);
requestContextMock.Verify(x => x.SendResult(It.Is<MetadataQueryResult>(result => result.Metadata.First().Name == "TestName")),
Times.Once());
}
}
}