diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs index adb9bcf6..aff69352 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs @@ -7,9 +7,12 @@ using System.Collections.Generic; using System.Threading; using System.Data; using System.Threading.Tasks; -using Kusto.Language; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; namespace Microsoft.Kusto.ServiceLayer.DataSource { @@ -88,6 +91,14 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public abstract string GenerateAlterFunctionScript(string functionName); public abstract string GenerateExecuteFunctionScript(string functionName); + public abstract ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText); + + public abstract DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false); + + public abstract Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); + + public abstract CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, + bool throwOnError = false); /// public DataSourceType DataSourceType { get; protected set; } @@ -96,7 +107,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public abstract string ClusterName { get; } public abstract string DatabaseName { get; } - public abstract GlobalState SchemaState { get; } #endregion } diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs index e54809db..24b27736 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs @@ -5,10 +5,10 @@ using Kusto.Data; using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; -using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; using Microsoft.Kusto.ServiceLayer.Utility; namespace Microsoft.Kusto.ServiceLayer.DataSource @@ -26,7 +26,8 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource { var kustoConnectionDetails = MapKustoConnectionDetails(connectionDetails); var kustoClient = new KustoClient(kustoConnectionDetails, ownerUri); - return new KustoDataSource(kustoClient); + var intellisenseClient = new KustoIntellisenseClient(kustoClient); + return new KustoDataSource(kustoClient, intellisenseClient); } default: diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs index 7429016f..13f777a2 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Data; using System.Threading; using System.Threading.Tasks; -using Kusto.Language; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; namespace Microsoft.Kusto.ServiceLayer.DataSource { @@ -27,8 +30,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// The current database name, if there is one. /// string DatabaseName { get; } - - GlobalState SchemaState { get; } /// /// Executes a query. @@ -111,5 +112,10 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// /// string GenerateExecuteFunctionScript(string functionName); + + ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText); + DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false); + Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); + CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs index de2bed90..9be38305 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/IKustoClient.cs @@ -2,17 +2,11 @@ using System.Collections.Generic; using System.Data; using System.Threading; using System.Threading.Tasks; -using Kusto.Language; namespace Microsoft.Kusto.ServiceLayer.DataSource { public interface IKustoClient { - /// - /// SchemaState used for getting intellisense info. - /// - GlobalState SchemaState { get; } - string ClusterName { get; } string DatabaseName { get; } diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs new file mode 100644 index 00000000..9c373ab6 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/IIntellisenseClient.cs @@ -0,0 +1,15 @@ +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense +{ + public interface IIntellisenseClient + { + void UpdateDatabase(string databaseName); + ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText); + DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false); + Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); + CompletionItem[] GetAutoCompleteSuggestions(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false); + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseClient.cs similarity index 55% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseClient.cs index d5533106..8345c056 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseClient.cs @@ -1,32 +1,141 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Kusto.Language; -using KustoDiagnostic = Kusto.Language.Diagnostic; using Kusto.Language.Editor; -using Kusto.Language.Syntax; using Kusto.Language.Symbols; +using Kusto.Language.Syntax; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; +using Diagnostic = Kusto.Language.Diagnostic; -namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense { - /// - /// Kusto specific class for intellisense helper functions. - /// - public class KustoIntellisenseHelper + public class KustoIntellisenseClient : IIntellisenseClient { + private readonly IKustoClient _kustoClient; + + /// + /// SchemaState used for getting intellisense info. + /// + private GlobalState _schemaState; + + public KustoIntellisenseClient(IKustoClient kustoClient) + { + _kustoClient = kustoClient; + _schemaState = LoadSchemaState(kustoClient.DatabaseName, kustoClient.ClusterName); + } + + public void UpdateDatabase(string databaseName) + { + _schemaState = LoadSchemaState(databaseName, _kustoClient.ClusterName); + } + + private GlobalState LoadSchemaState(string databaseName, string clusterName) + { + IEnumerable tableSchemas = Enumerable.Empty(); + IEnumerable functionSchemas = Enumerable.Empty(); + + if (!string.IsNullOrWhiteSpace(databaseName)) + { + var source = new CancellationTokenSource(); + Parallel.Invoke(() => + { + tableSchemas = + _kustoClient.ExecuteQueryAsync($".show database {databaseName} schema", source.Token, databaseName) + .Result; + }, + () => + { + functionSchemas = _kustoClient.ExecuteQueryAsync(".show functions", source.Token, databaseName).Result; + }); + } + + return AddOrUpdateDatabase(tableSchemas, functionSchemas, GlobalState.Default, databaseName, + clusterName); + } + + /// + /// Loads the schema for the specified database and returns a new with the database added or updated. + /// + private GlobalState AddOrUpdateDatabase(IEnumerable tableSchemas, + IEnumerable functionSchemas, GlobalState globals, + string databaseName, string clusterName) + { + // try and show error from here. + DatabaseSymbol databaseSymbol = null; + + if (databaseName != null) + { + databaseSymbol = LoadDatabase(tableSchemas, functionSchemas, databaseName); + } + + if (databaseSymbol == null) + { + return globals; + } + + var cluster = globals.GetCluster(clusterName); + if (cluster == null) + { + cluster = new ClusterSymbol(clusterName, new[] {databaseSymbol}, isOpen: true); + globals = globals.AddOrUpdateCluster(cluster); + } + else + { + cluster = cluster.AddOrUpdateDatabase(databaseSymbol); + globals = globals.AddOrUpdateCluster(cluster); + } + + return globals.WithCluster(cluster).WithDatabase(databaseSymbol); + } + + /// + /// Loads the schema for the specified database into a . + /// + private DatabaseSymbol LoadDatabase(IEnumerable tableSchemas, + IEnumerable functionSchemas, + string databaseName) + { + if (tableSchemas == null) + { + return null; + } + + tableSchemas = tableSchemas + .Where(r => !string.IsNullOrEmpty(r.TableName) && !string.IsNullOrEmpty(r.ColumnName)) + .ToArray(); + + var members = new List(); + foreach (var table in tableSchemas.GroupBy(s => s.TableName)) + { + var columns = table.Select(s => new ColumnSymbol(s.ColumnName, GetKustoType(s.ColumnType))).ToList(); + var tableSymbol = new TableSymbol(table.Key, columns); + members.Add(tableSymbol); + } + + if (functionSchemas == null) + { + return null; + } + + foreach (var fun in functionSchemas) + { + var parameters = TranslateParameters(fun.Parameters); + var functionSymbol = new FunctionSymbol(fun.Name, fun.Body, parameters); + members.Add(functionSymbol); + } + + return new DatabaseSymbol(databaseName, members); + } + /// /// Convert CLR type name into a Kusto scalar type. /// - private static ScalarSymbol GetKustoType(string clrTypeName) + private ScalarSymbol GetKustoType(string clrTypeName) { switch (clrTypeName) { @@ -96,220 +205,121 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense throw new InvalidOperationException($"Unhandled clr type: {clrTypeName}"); } } - - private static IReadOnlyList NoParameters = new Parameter[0]; - + /// /// Translate Kusto parameter list declaration into into list of instances. /// - private static IReadOnlyList TranslateParameters(string parameters) + private IReadOnlyList TranslateParameters(string parameters) { parameters = parameters.Trim(); if (string.IsNullOrEmpty(parameters) || parameters == "()") - return NoParameters; + { + return new Parameter[0]; + } if (parameters[0] != '(') + { parameters = "(" + parameters; + } + if (parameters[parameters.Length - 1] != ')') + { parameters = parameters + ")"; + } var query = "let fn = " + parameters + " { };"; var code = KustoCode.ParseAndAnalyze(query); var let = code.Syntax.GetFirstDescendant(); - FunctionSymbol function = let.Name.ReferencedSymbol is VariableSymbol variable + FunctionSymbol function = let.Name.ReferencedSymbol is VariableSymbol variable ? variable.Type as FunctionSymbol : let.Name.ReferencedSymbol as FunctionSymbol; return function.Signatures[0].Parameters; } - /// - /// Loads the schema for the specified databasea into a a . - /// - private static DatabaseSymbol LoadDatabaseAsync(IEnumerable tableSchemas, - IEnumerable functionSchemas, - string databaseName) + public ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, string queryText) { - if (tableSchemas == null) + var kustoCodeService = new KustoCodeService(queryText, _schemaState); + var script = CodeScript.From(queryText, _schemaState); + var parseResult = new List(); + + foreach (var codeBlock in script.Blocks) { - return null; + parseResult.AddRange(codeBlock.Service.GetDiagnostics()); } - tableSchemas = tableSchemas - .Where(r => !string.IsNullOrEmpty(r.TableName) && !string.IsNullOrEmpty(r.ColumnName)) - .ToArray(); - - var members = new List(); - foreach (var table in tableSchemas.GroupBy(s => s.TableName)) - { - var columns = table.Select(s => new ColumnSymbol(s.ColumnName, GetKustoType(s.ColumnType))).ToList(); - var tableSymbol = new TableSymbol(table.Key, columns); - members.Add(tableSymbol); - } - - if (functionSchemas == null) - { - return null; - } - - foreach (var fun in functionSchemas) - { - var parameters = TranslateParameters(fun.Parameters); - var functionSymbol = new FunctionSymbol(fun.Name, fun.Body, parameters); - members.Add(functionSymbol); - } - - return new DatabaseSymbol(databaseName, members); - } - - public static CompletionItemKind CreateCompletionItemKind(CompletionKind kustoKind) - { - CompletionItemKind kind = CompletionItemKind.Variable; - switch (kustoKind) - { - case CompletionKind.Syntax: - kind = CompletionItemKind.Module; - break; - case CompletionKind.Column: - kind = CompletionItemKind.Field; - break; - case CompletionKind.Variable: - kind = CompletionItemKind.Variable; - break; - case CompletionKind.Table: - kind = CompletionItemKind.File; - break; - case CompletionKind.Database: - kind = CompletionItemKind.Method; - break; - case CompletionKind.LocalFunction: - case CompletionKind.DatabaseFunction: - case CompletionKind.BuiltInFunction: - case CompletionKind.AggregateFunction: - kind = CompletionItemKind.Function; - break; - default: - kind = CompletionItemKind.Keyword; - break; - } - - return kind; - } - - /// - /// Gets default keyword when user if not connected to any Kusto cluster. - /// - public static LanguageServices.Contracts.CompletionItem[] GetDefaultKeywords( - ScriptDocumentInfo scriptDocumentInfo, Position textDocumentPosition) - { - var kustoCodeService = new KustoCodeService(scriptDocumentInfo.Contents, GlobalState.Default); - var script = CodeScript.From(scriptDocumentInfo.Contents, GlobalState.Default); - script.TryGetTextPosition(textDocumentPosition.Line + 1, textDocumentPosition.Character, - out int position); // Gets the actual offset based on line and local offset - var completion = kustoCodeService.GetCompletionItems(position); - - List completions = - new List(); - foreach (var autoCompleteItem in completion.Items) - { - var label = autoCompleteItem.DisplayText; - // convert the completion item candidates into vscode format CompletionItems - completions.Add(AutoCompleteHelper.CreateCompletionItem(label, label + " keyword", label, - CompletionItemKind.Keyword, scriptDocumentInfo.StartLine, scriptDocumentInfo.StartColumn, - textDocumentPosition.Character)); - } - - return completions.ToArray(); - } - - /// - /// Gets default diagnostics when user if not connected to any Kusto cluster. - /// - public static ScriptFileMarker[] GetDefaultDiagnostics(ScriptParseInfo parseInfo, ScriptFile scriptFile, - string queryText) - { - var kustoCodeService = new KustoCodeService(queryText, GlobalState.Default); - var script = CodeScript.From(queryText, GlobalState.Default); - var parseResult = kustoCodeService.GetDiagnostics(); - parseInfo.ParseResult = parseResult; - - // build a list of Kusto script file markers from the errors. - List markers = new List(); - if (parseResult != null && parseResult.Count() > 0) + + if (!parseResult.Any()) { - 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); + return Array.Empty(); + } + + // build a list of Kusto script file markers from the errors. + var markers = new List(); + + foreach (var error in parseResult) + { + script.TryGetLineAndOffset(error.Start, out var startLine, out var startOffset); + script.TryGetLineAndOffset(error.End, out var endLine, out var endOffset); - // vscode specific format for error markers. - markers.Add(new ScriptFileMarker() + // vscode specific format for error markers. + markers.Add(new ScriptFileMarker + { + Message = error.Message, + Level = ScriptFileMarkerLevel.Error, + ScriptRegion = new ScriptRegion { - Message = error.Message, - Level = ScriptFileMarkerLevel.Error, - ScriptRegion = new ScriptRegion() - { - File = scriptFile.FilePath, - StartLineNumber = startLine, - StartColumnNumber = startOffset, - StartOffset = 0, - EndLineNumber = endLine, - EndColumnNumber = endOffset, - EndOffset = 0 - } - }); - } + File = scriptFile.FilePath, + StartLineNumber = startLine, + StartColumnNumber = startOffset, + StartOffset = 0, + EndLineNumber = endLine, + EndColumnNumber = endOffset, + EndOffset = 0 + } + }); } return markers.ToArray(); } - - /// - /// Loads the schema for the specified database and returns a new with the database added or updated. - /// - public static GlobalState AddOrUpdateDatabase(IEnumerable tableSchemas, - IEnumerable functionSchemas, GlobalState globals, - string databaseName, string clusterName) + + public DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, bool throwOnError = false) { - // try and show error from here. - DatabaseSymbol databaseSymbol = null; + //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 (databaseName != null) + if (relatedInfo != null && relatedInfo.Elements.Count > 1) { - databaseSymbol = LoadDatabaseAsync(tableSchemas, functionSchemas, databaseName); } - if (databaseSymbol == null) - { - return globals; - } - - var cluster = globals.GetCluster(clusterName); - if (cluster == null) - { - cluster = new ClusterSymbol(clusterName, new[] {databaseSymbol}, isOpen: true); - globals = globals.AddOrUpdateCluster(cluster); - } - else - { - cluster = cluster.AddOrUpdateDatabase(databaseSymbol); - globals = globals.AddOrUpdateCluster(cluster); - } - - globals = globals.WithCluster(cluster).WithDatabase(databaseSymbol); - - return globals; + return null; } - - /// - public static LanguageServices.Contracts.CompletionItem[] GetAutoCompleteSuggestions( - ScriptDocumentInfo scriptDocumentInfo, Position textPosition, GlobalState schemaState, - bool throwOnError = false) + + public Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, bool throwOnError = false) { - var script = CodeScript.From(scriptDocumentInfo.Contents, schemaState); + 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 @@ -334,86 +344,29 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense return completions.ToArray(); } - - /// - public static Hover GetHoverHelp(ScriptDocumentInfo scriptDocumentInfo, Position textPosition, - GlobalState schemaState, bool throwOnError = false) + + private CompletionItemKind CreateCompletionItemKind(CompletionKind kustoKind) { - 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 static DefinitionResult GetDefinition(string queryText, int index, int startLine, int startColumn, - GlobalState schemaState, bool throwOnError = false) - { - var abc = KustoCode.ParseAndAnalyze(queryText, - schemaState); //TODOKusto: API wasnt working properly, need to check that part. - var kustoCodeService = new KustoCodeService(abc); - //var kustoCodeService = new KustoCodeService(queryText, globals); - var relatedInfo = kustoCodeService.GetRelatedElements(index); - - if (relatedInfo != null && relatedInfo.Elements.Count > 1) + 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; } - - return null; - } - - /// - public static ScriptFileMarker[] GetSemanticMarkers(ScriptParseInfo parseInfo, ScriptFile scriptFile, - string queryText, GlobalState schemaState) - { - var kustoCodeService = new KustoCodeService(queryText, schemaState); - var script = CodeScript.From(queryText, schemaState); - var parseResult = new List(); - - foreach (var codeBlock in script.Blocks) - { - parseResult.AddRange(codeBlock.Service.GetDiagnostics()); - } - - parseInfo.ParseResult = parseResult; - - // build a list of Kusto script file markers from the errors. - List markers = new List(); - if (parseResult != null && parseResult.Any()) - { - 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(); } } -} +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseHelper.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseHelper.cs new file mode 100644 index 00000000..764c7543 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/KustoIntellisenseHelper.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.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; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense +{ + /// + /// Kusto specific class for intellisense helper functions. + /// + public class KustoIntellisenseHelper + { + /// + /// Gets default keyword when user if not connected to any Kusto cluster. + /// + public static LanguageServices.Contracts.CompletionItem[] GetDefaultKeywords( + ScriptDocumentInfo scriptDocumentInfo, Position textDocumentPosition) + { + var kustoCodeService = new KustoCodeService(scriptDocumentInfo.Contents, GlobalState.Default); + var script = CodeScript.From(scriptDocumentInfo.Contents, GlobalState.Default); + script.TryGetTextPosition(textDocumentPosition.Line + 1, textDocumentPosition.Character, + out int position); // Gets the actual offset based on line and local offset + var completion = kustoCodeService.GetCompletionItems(position); + + List completions = + new List(); + foreach (var autoCompleteItem in completion.Items) + { + var label = autoCompleteItem.DisplayText; + // convert the completion item candidates into vscode format CompletionItems + completions.Add(AutoCompleteHelper.CreateCompletionItem(label, label + " keyword", label, + CompletionItemKind.Keyword, scriptDocumentInfo.StartLine, scriptDocumentInfo.StartColumn, + textDocumentPosition.Character)); + } + + return completions.ToArray(); + } + + /// + /// Gets default diagnostics when user if not connected to any Kusto cluster. + /// + public static ScriptFileMarker[] GetDefaultDiagnostics(ScriptParseInfo parseInfo, ScriptFile scriptFile, + string queryText) + { + var kustoCodeService = new KustoCodeService(queryText, GlobalState.Default); + var script = CodeScript.From(queryText, GlobalState.Default); + var parseResult = kustoCodeService.GetDiagnostics(); + + parseInfo.ParseResult = parseResult; + + // build a list of Kusto script file markers from the errors. + List markers = new List(); + if (parseResult != null && parseResult.Count() > 0) + { + 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(); + } + } +} diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ScriptParseInfo.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ScriptParseInfo.cs similarity index 95% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ScriptParseInfo.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ScriptParseInfo.cs index d92433a3..0035bef5 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ScriptParseInfo.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ScriptParseInfo.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using Kusto.Language; using Kusto.Language.Editor; -namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense { /// /// Data Source specific class for storing cached metadata regarding a parsed KQL file. diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowDatabaseSchemaResult.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowDatabaseSchemaResult.cs similarity index 84% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowDatabaseSchemaResult.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowDatabaseSchemaResult.cs index d194d72d..6aecafc4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowDatabaseSchemaResult.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowDatabaseSchemaResult.cs @@ -1,4 +1,4 @@ -namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense { public class ShowDatabaseSchemaResult { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowDatabasesResult.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowDatabasesResult.cs similarity index 82% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowDatabasesResult.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowDatabasesResult.cs index 0cbb930a..75813c67 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowDatabasesResult.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowDatabasesResult.cs @@ -1,4 +1,4 @@ -namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense { public class ShowDatabasesResult { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowFunctionsResult.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowFunctionsResult.cs similarity index 73% rename from src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowFunctionsResult.cs rename to src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowFunctionsResult.cs index 812da976..72961ec4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/ShowFunctionsResult.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Intellisense/ShowFunctionsResult.cs @@ -1,4 +1,4 @@ -namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense +namespace Microsoft.Kusto.ServiceLayer.DataSource.Intellisense { public class ShowFunctionsResult { diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs index 69af13c5..2c062e7e 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoClient.cs @@ -16,7 +16,6 @@ using Kusto.Language; using Kusto.Language.Editor; using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.DataSource.Contracts; -using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; using Microsoft.Kusto.ServiceLayer.Utility; namespace Microsoft.Kusto.ServiceLayer.DataSource @@ -30,12 +29,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ICslQueryProvider _kustoQueryProvider; - - /// - /// SchemaState used for getting intellisense info. - /// - public GlobalState SchemaState { get; private set; } - + public string ClusterName { get; private set; } public string DatabaseName { get; private set; } @@ -43,7 +37,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource { _ownerUri = ownerUri; Initialize(connectionDetails); - SchemaState = LoadSchemaState(); } private string ParseDatabaseName(string databaseName) @@ -55,31 +48,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource : databaseName; } - private GlobalState LoadSchemaState() - { - IEnumerable tableSchemas = Enumerable.Empty(); - IEnumerable functionSchemas = Enumerable.Empty(); - - if (!string.IsNullOrWhiteSpace(DatabaseName)) - { - var source = new CancellationTokenSource(); - Parallel.Invoke(() => - { - tableSchemas = - ExecuteQueryAsync($".show database {DatabaseName} schema", source.Token, DatabaseName) - .Result; - }, - () => - { - functionSchemas = ExecuteQueryAsync(".show functions", source.Token, DatabaseName).Result; - }); - } - - return KustoIntellisenseHelper.AddOrUpdateDatabase(tableSchemas, functionSchemas, - GlobalState.Default, - DatabaseName, ClusterName); - } - private void Initialize(DataSourceConnectionDetails connectionDetails) { var stringBuilder = GetKustoConnectionStringBuilder(connectionDetails); @@ -275,7 +243,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public void UpdateDatabase(string databaseName) { DatabaseName = ParseDatabaseName(databaseName); - SchemaState = LoadSchemaState(); } public void Dispose() diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs index 95458e99..ba54f84b 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs @@ -14,10 +14,13 @@ using System.Threading.Tasks; using Kusto.Cloud.Platform.Data; using Kusto.Data; using Kusto.Data.Data; -using Kusto.Language; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.DataSource.Models; +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Utility; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; namespace Microsoft.Kusto.ServiceLayer.DataSource { @@ -26,7 +29,8 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// public class KustoDataSource : DataSourceBase { - private IKustoClient _kustoClient; + private readonly IKustoClient _kustoClient; + private readonly IIntellisenseClient _intellisenseClient; /// /// List of databases. @@ -56,8 +60,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public override string DatabaseName => _kustoClient.DatabaseName; public override string ClusterName => _kustoClient.ClusterName; - - public override GlobalState SchemaState => _kustoClient.SchemaState; // Some clusters have this signature. Queries might slightly differ for Aria private const string AriaProxyURL = "kusto.aria.microsoft.com"; @@ -77,9 +79,10 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// /// Prevents a default instance of the class from being created. /// - public KustoDataSource(IKustoClient kustoClient) + public KustoDataSource(IKustoClient kustoClient, IIntellisenseClient intellisenseClient) { _kustoClient = kustoClient; + _intellisenseClient = intellisenseClient; // Check if a connection can be made ValidationUtils.IsTrue(Exists().Result, $"Unable to connect. ClusterName = {ClusterName}, DatabaseName = {DatabaseName}"); @@ -246,6 +249,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource public override void UpdateDatabase(string databaseName) { _kustoClient.UpdateDatabase(databaseName); + _intellisenseClient.UpdateDatabase(databaseName); } /// @@ -803,12 +807,32 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource ? string.Empty : $"{functionInfo.Name}{functionInfo.Parameters}"; } - + private string GenerateMetadataKey(string databaseName, string objectName) { return string.IsNullOrWhiteSpace(objectName) ? databaseName : $"{databaseName}.{objectName}"; } + 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); + } + #endregion } } diff --git a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs index 165c8a16..40c472a8 100644 --- a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs @@ -78,7 +78,7 @@ namespace Microsoft.Kusto.ServiceLayer WorkspaceService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(WorkspaceService.Instance); - LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue, dataSourceFactory); + LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue); serviceProvider.RegisterSingleService(LanguageService.Instance); ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue, dataSourceFactory); diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs index a0b10605..f756d489 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs @@ -15,10 +15,9 @@ using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.Kusto.ServiceLayer.DataSource; -using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection.Contracts; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Workspace; @@ -67,9 +66,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices internal const int PeekDefinitionTimeout = 10 * OneSecond; - // For testability only - internal Task DelayedDiagnosticsTask = null; - private ConnectionService connectionService = null; private WorkspaceService workspaceServiceInstance; @@ -209,7 +205,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// /// /// - public void InitializeService(ServiceHost serviceHost, IConnectedBindingQueue connectedBindingQueue, IDataSourceFactory dataSourceFactory) + public void InitializeService(ServiceHost serviceHost, IConnectedBindingQueue connectedBindingQueue) { _bindingQueue = connectedBindingQueue; // Register the requests that this service will handle @@ -766,18 +762,16 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices connInfo.TryGetConnection("Default", out connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - return KustoIntellisenseHelper.GetDefinition(scriptFile.Contents, textDocumentPosition.Position.Character, 1, 1, dataSource.SchemaState); + return dataSource.GetDefinition(scriptFile.Contents, textDocumentPosition.Position.Character, 1, 1); } - else + + // User is not connected. + return new DefinitionResult { - // User is not connected. - return new DefinitionResult - { - IsErrorResult = true, - Message = SR.PeekDefinitionNotConnectedError, - Locations = null - }; - } + IsErrorResult = true, + Message = SR.PeekDefinitionNotConnectedError, + Locations = null + }; } /// @@ -809,9 +803,8 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices ReliableDataSourceConnection connection; connInfo.TryGetConnection("Default", out connection); - IDataSource dataSource = connection.GetUnderlyingConnection(); - - return KustoIntellisenseHelper.GetHoverHelp(scriptDocumentInfo, textDocumentPosition.Position, dataSource.SchemaState); + IDataSource dataSource = connection.GetUnderlyingConnection(); + return dataSource.GetHoverHelp(scriptDocumentInfo, textDocumentPosition.Position); }); queueItem.ItemProcessed.WaitOne(); @@ -855,14 +848,17 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices ScriptDocumentInfo scriptDocumentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo); - if(connInfo != null){ + if (connInfo != null) + { ReliableDataSourceConnection connection; connInfo.TryGetConnection("Default", out connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - - resultCompletionItems = KustoIntellisenseHelper.GetAutoCompleteSuggestions(scriptDocumentInfo, textDocumentPosition.Position, dataSource.SchemaState); + + resultCompletionItems = + dataSource.GetAutoCompleteSuggestions(scriptDocumentInfo, textDocumentPosition.Position); } - else{ + else + { resultCompletionItems = DataSourceFactory.GetDefaultAutoComplete(DataSourceType.Kusto, scriptDocumentInfo, textDocumentPosition.Position); } @@ -932,7 +928,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices existingRequestCancellation = new CancellationTokenSource(); Task.Factory.StartNew( () => - this.DelayedDiagnosticsTask = DelayThenInvokeDiagnostics( + DelayThenInvokeDiagnostics( LanguageService.DiagnosticParseDelay, filesToAnalyze, eventContext, @@ -1000,17 +996,19 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices ConnectionServiceInstance.TryFindConnection( scriptFile.ClientUri, out connInfo); - - if(connInfo != null){ + + if (connInfo != null) + { connInfo.TryGetConnection("Default", out var connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - - semanticMarkers = KustoIntellisenseHelper.GetSemanticMarkers(parseInfo, scriptFile, scriptFile.Contents, dataSource.SchemaState); - } - else{ + + semanticMarkers = dataSource.GetSemanticMarkers(parseInfo, scriptFile, scriptFile.Contents); + } + else + { semanticMarkers = DataSourceFactory.GetDefaultSemanticMarkers(DataSourceType.Kusto, parseInfo, scriptFile, scriptFile.Contents); } - + Logger.Write(TraceEventType.Verbose, "Analysis complete."); await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext); diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs index 0f8eb968..eb54e066 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs @@ -3,12 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.Utility; -using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; -using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; -namespace Microsoft.Kusto.ServiceLayer.LanguageServices.Completion +namespace Microsoft.Kusto.ServiceLayer.LanguageServices { /// /// A class to calculate the numbers used by SQL parser using the text positions and content diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs index f90b827a..6d403b3b 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource; -using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using NUnit.Framework; diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs new file mode 100644 index 00000000..42295401 --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseClientTests.cs @@ -0,0 +1,174 @@ +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.LanguageServices; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; +using Moq; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource.DataSourceIntellisense +{ + public class KustoIntellisenseClientTests + { + private Mock GetMockKustoClient() + { + var kustoClientMock = new Mock(); + kustoClientMock.Setup(x => x.ClusterName).Returns("https://fake.url.com"); + kustoClientMock.Setup(x => x.DatabaseName).Returns("FakeDatabaseName"); + + var databaseSchema = new ShowDatabaseSchemaResult + { + DatabaseName = "FakeDatabaseName", + TableName = "FakeTableName", + ColumnName = "FakeColumnName", + ColumnType = "bool", + IsDefaultTable = false, + IsDefaultColumn = false, + PrettyName = "Fake Table Name", + Version = "", + Folder = "FakeTableFolder", + DocName = "" + }; + + var databaseSchemaResults = new List {databaseSchema} as IEnumerable; + kustoClientMock.Setup(x => x.ExecuteQueryAsync(It.IsAny(), It.IsAny(), "FakeDatabaseName")) + .Returns(Task.FromResult(databaseSchemaResults)); + + var functionSchema = new ShowFunctionsResult + { + Name = "FakeFunctionName", + Parameters = "a:real, b:real", + Body = "a+b", + Folder = "FakeFunctionFolder", + DocString = "" + }; + + var functionSchemaResults = new List {functionSchema} as IEnumerable; + kustoClientMock.Setup(x => x.ExecuteQueryAsync(It.IsAny(), It.IsAny(), "FakeDatabaseName")) + .Returns(Task.FromResult(functionSchemaResults)); + + return kustoClientMock; + } + + [Test] + public void GetSemanticMarkers_Returns_Error_For_InvalidText() + { + var kustoClientMock = GetMockKustoClient(); + + var client = new KustoIntellisenseClient(kustoClientMock.Object); + var semanticMarkers = client.GetSemanticMarkers(new ScriptParseInfo(), new ScriptFile("", "", ""), "InvalidText"); + + var semanticMarker = semanticMarkers.Single(); + Assert.AreEqual(ScriptFileMarkerLevel.Error, semanticMarker.Level); + Assert.AreEqual("The name 'InvalidText' does not refer to any known column, table, variable or function.", semanticMarker.Message); + Assert.IsNotNull(semanticMarker.ScriptRegion); + } + + [Test] + public void GetSemanticMarkers_Returns_Zero_SemanticMarkers_For_ValidQueryText() + { + var kustoClientMock = GetMockKustoClient(); + + var client = new KustoIntellisenseClient(kustoClientMock.Object); + var queryText = @".show commands"; + var semanticMarkers = client.GetSemanticMarkers(new ScriptParseInfo(), new ScriptFile("", "", ""), queryText); + + Assert.AreEqual(0, semanticMarkers.Length); + } + + [Test] + public void GetDefinition_Returns_Null() + { + var kustoClientMock = GetMockKustoClient(); + var client = new KustoIntellisenseClient(kustoClientMock.Object); + var definition = client.GetDefinition("queryText", 0, 1, 1); + + // finish these assertions once the function is implemented + Assert.IsNull(definition); + } + + [Test] + public void GetHoverHelp_Returns_Hover() + { + var kustoClientMock = GetMockKustoClient(); + var client = new KustoIntellisenseClient(kustoClientMock.Object); + var textDocumentPosition = new TextDocumentPosition + { + Position = new Position() + }; + var scriptFile = new ScriptFile("", "", ""); + var scriptParseInfo = new ScriptParseInfo(); + var documentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo); + + var hover = client.GetHoverHelp(documentInfo, new Position()); + + Assert.IsNotNull(hover); + } + + [Test] + public void GetAutoCompleteSuggestions_Returns_CompletionItems() + { + var kustoClientMock = GetMockKustoClient(); + var client = new KustoIntellisenseClient(kustoClientMock.Object); + + var position = new Position(); + var textDocumentPosition = new TextDocumentPosition + { + Position = position + }; + var scriptFile = new ScriptFile("", "", ""); + var scriptParseInfo = new ScriptParseInfo(); + var documentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo); + var items = client.GetAutoCompleteSuggestions(documentInfo, position); + + Assert.AreEqual(20, items.Length); + } + [Test] + public void UpdateDatabase_Updates_SchemaState() + { + var kustoClientMock = GetMockKustoClient(); + + var databaseSchema = new ShowDatabaseSchemaResult + { + DatabaseName = "NewDatabaseName", + TableName = "NewTableName", + ColumnName = "NewColumnName", + ColumnType = "bool", + IsDefaultTable = false, + IsDefaultColumn = false, + PrettyName = "New Table Name", + Version = "", + Folder = "NewTableFolder", + DocName = "" + }; + + var databaseSchemaResults = new List {databaseSchema} as IEnumerable; + kustoClientMock.Setup(x => x.ExecuteQueryAsync(It.IsAny(), It.IsAny(), "NewDatabaseName")) + .Returns(Task.FromResult(databaseSchemaResults)); + + var client = new KustoIntellisenseClient(kustoClientMock.Object); + client.UpdateDatabase("NewDatabaseName"); + + var position = new Position(); + var textDocumentPosition = new TextDocumentPosition + { + Position = position + }; + var scriptFile = new ScriptFile("", "", ""); + var scriptParseInfo = new ScriptParseInfo(); + var documentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo); + var items = client.GetAutoCompleteSuggestions(documentInfo, position); + + Assert.AreEqual(19, items.Length); + var tableItem = items.FirstOrDefault(x => x.Detail == "Table"); + + // assert new table is being returned to show database has changed + Assert.IsNotNull(tableItem); + Assert.AreEqual("NewTableName", tableItem.InsertText); + Assert.AreEqual("NewTableName", tableItem.Label); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs index 1f45e269..ec24b83e 100644 --- a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceIntellisense/KustoIntellisenseHelperTests.cs @@ -1,8 +1,5 @@ -using System.Linq; -using Kusto.Language.Editor; -using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; -using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource.Intellisense; +using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using NUnit.Framework; @@ -10,37 +7,6 @@ namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource.DataSourceIntellisen { public class KustoIntellisenseHelperTests { - [TestCase(CompletionKind.Syntax, CompletionItemKind.Module)] - [TestCase(CompletionKind.Column, CompletionItemKind.Field)] - [TestCase(CompletionKind.Variable, CompletionItemKind.Variable)] - [TestCase(CompletionKind.Table, CompletionItemKind.File)] - [TestCase(CompletionKind.Database, CompletionItemKind.Method)] - [TestCase(CompletionKind.LocalFunction, CompletionItemKind.Function)] - [TestCase(CompletionKind.DatabaseFunction, CompletionItemKind.Function)] - [TestCase(CompletionKind.BuiltInFunction, CompletionItemKind.Function)] - [TestCase(CompletionKind.AggregateFunction, CompletionItemKind.Function)] - [TestCase(CompletionKind.Unknown, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.Keyword, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.Punctuation, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.Identifier, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.Example, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.ScalarPrefix, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.TabularPrefix, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.TabularSuffix, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.QueryPrefix, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.CommandPrefix, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.ScalarInfix, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.RenderChart, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.Parameter, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.Cluster, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.MaterialiedView, CompletionItemKind.Keyword)] - [TestCase(CompletionKind.ScalarType, CompletionItemKind.Keyword)] - public void CreateCompletionItemKind_Returns_Kind(CompletionKind completionKind, CompletionItemKind expected) - { - var result = KustoIntellisenseHelper.CreateCompletionItemKind(completionKind); - Assert.AreEqual(expected, result); - } - [Test] public void GetDefaultKeywords_Returns_Keywords() { diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs new file mode 100644 index 00000000..396aa4af --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/KustoClientTests.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.DataSource.Contracts; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource +{ + public class KustoClientTests + { + [Test] + public void Constructor_Throws_ArgumentException_For_MissingToken() + { + var connectionDetails = new DataSourceConnectionDetails + { + UserToken = "" + }; + + Assert.Throws(() => new KustoClient(connectionDetails, "ownerUri")); + } + + [Test] + public void Constructor_Sets_ClusterName_With_DefaultDatabaseName() + { + string clusterName = "https://fake.url.com"; + var connectionDetails = new DataSourceConnectionDetails + { + UserToken = "UserToken", + ServerName = clusterName, + DatabaseName = "", + AuthenticationType = "AzureMFA" + }; + + var client = new KustoClient(connectionDetails, "ownerUri"); + + Assert.AreEqual(clusterName, client.ClusterName); + Assert.AreEqual("NetDefaultDB", client.DatabaseName); + } + + [TestCase("dstsAuth")] + [TestCase("AzureMFA")] + public void Constructor_Creates_Client_With_Valid_AuthenticationType(string authenticationType) + { + string clusterName = "https://fake.url.com"; + var connectionDetails = new DataSourceConnectionDetails + { + UserToken = "UserToken", + ServerName = clusterName, + DatabaseName = "FakeDatabaseName", + AuthenticationType = authenticationType + }; + + var client = new KustoClient(connectionDetails, "ownerUri"); + + Assert.AreEqual(clusterName, client.ClusterName); + Assert.AreEqual("FakeDatabaseName", client.DatabaseName); + } + } +} \ No newline at end of file