mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
adding a new event for when definition is requested (#167)
* sending telemetry events for intellisense usage
This commit is contained in:
@@ -23,6 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
OwnerUri = ownerUri;
|
||||
ConnectionDetails = details;
|
||||
ConnectionId = Guid.NewGuid();
|
||||
IntellisenseMetrics = new InteractionMetrics<double>(new int[] { 50, 100, 200, 500, 1000, 2000 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,5 +50,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// The connection to the SQL database that commands will be run against.
|
||||
/// </summary>
|
||||
public DbConnection SqlConnection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Intellisense Metrics
|
||||
/// </summary>
|
||||
public InteractionMetrics<double> IntellisenseMetrics { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true is the db connection is to a SQL db
|
||||
/// </summary>
|
||||
public bool IsAzure { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
@@ -290,6 +291,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
AzureVersion = serverInfo.AzureVersion,
|
||||
OsVersion = serverInfo.OsVersion
|
||||
};
|
||||
connectionInfo.IsAzure = serverInfo.IsCloud;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
@@ -356,6 +358,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send a telemetry notification for intellisense performance metrics
|
||||
ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams()
|
||||
{
|
||||
Params = new TelemetryProperties
|
||||
{
|
||||
Properties = new Dictionary<string, string>
|
||||
{
|
||||
{ "IsAzure", info.IsAzure ? "1" : "0" }
|
||||
},
|
||||
EventName = TelemetryEventNames.IntellisenseQuantile,
|
||||
Measures = info.IntellisenseMetrics.Quantile
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Close the connection
|
||||
info.SqlConnection.Close();
|
||||
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
@@ -371,12 +370,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="endColumn"></param>
|
||||
/// <param name="useLowerCase"></param>
|
||||
internal static CompletionItem[] GetDefaultCompletionItems(
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn,
|
||||
bool useLowerCase,
|
||||
string tokenText = null)
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
bool useLowerCase)
|
||||
{
|
||||
int row = scriptDocumentInfo.StartLine;
|
||||
int startColumn = scriptDocumentInfo.StartColumn;
|
||||
int endColumn = scriptDocumentInfo.EndColumn;
|
||||
string tokenText = scriptDocumentInfo.TokenText;
|
||||
// determine how many default completion items there will be
|
||||
int listSize = DefaultCompletionText.Length;
|
||||
if (!string.IsNullOrWhiteSpace(tokenText))
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// 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.Diagnostics;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// Includes the objects created by auto completion service
|
||||
/// </summary>
|
||||
public class AutoCompletionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new instance
|
||||
/// </summary>
|
||||
public AutoCompletionResult()
|
||||
{
|
||||
Stopwatch = new Stopwatch();
|
||||
Stopwatch.Start();
|
||||
}
|
||||
|
||||
private Stopwatch Stopwatch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Completes the results to calculate the duration
|
||||
/// </summary>
|
||||
public void CompleteResult(CompletionItem[] completionItems)
|
||||
{
|
||||
Stopwatch.Stop();
|
||||
CompletionItems = completionItems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of milliseconds to process the result
|
||||
/// </summary>
|
||||
public double Duration
|
||||
{
|
||||
get
|
||||
{
|
||||
return Stopwatch.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completion list
|
||||
/// </summary>
|
||||
public CompletionItem[] CompletionItems { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// A service to create auto complete list for given script document
|
||||
/// </summary>
|
||||
internal class CompletionService
|
||||
{
|
||||
private ConnectedBindingQueue BindingQueue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Created new instance given binding queue
|
||||
/// </summary>
|
||||
public CompletionService(ConnectedBindingQueue bindingQueue)
|
||||
{
|
||||
BindingQueue = bindingQueue;
|
||||
}
|
||||
|
||||
private ISqlParserWrapper sqlParserWrapper;
|
||||
|
||||
/// <summary>
|
||||
/// SQL parser wrapper to create the completion list
|
||||
/// </summary>
|
||||
public ISqlParserWrapper SqlParserWrapper
|
||||
{
|
||||
get
|
||||
{
|
||||
if(this.sqlParserWrapper == null)
|
||||
{
|
||||
this.sqlParserWrapper = new SqlParserWrapper();
|
||||
}
|
||||
return this.sqlParserWrapper;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.sqlParserWrapper = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a completion list given connection and document info
|
||||
/// </summary>
|
||||
public AutoCompletionResult CreateCompletions(
|
||||
ConnectionInfo connInfo,
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
bool useLowerCaseSuggestions)
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
// check if the file is connected and the file lock is available
|
||||
if (scriptDocumentInfo.ScriptParseInfo.IsConnected && Monitor.TryEnter(scriptDocumentInfo.ScriptParseInfo.BuildingMetadataLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
QueueItem queueItem = AddToQueue(connInfo, scriptDocumentInfo.ScriptParseInfo, scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
|
||||
// wait for the queue item
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
var completionResult = queueItem.GetResultAsT<AutoCompletionResult>();
|
||||
if (completionResult != null && completionResult.CompletionItems != null && completionResult.CompletionItems.Length > 0)
|
||||
{
|
||||
result = completionResult;
|
||||
}
|
||||
else if (!ShouldShowCompletionList(scriptDocumentInfo.Token))
|
||||
{
|
||||
result.CompleteResult(AutoCompleteHelper.EmptyCompletionList);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptDocumentInfo.ScriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private QueueItem AddToQueue(
|
||||
ConnectionInfo connInfo,
|
||||
ScriptParseInfo scriptParseInfo,
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
bool useLowerCaseSuggestions)
|
||||
{
|
||||
// queue the completion task with the binding queue
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
return CreateCompletionsFromSqlParser(connInfo, scriptParseInfo, scriptDocumentInfo, bindingContext.MetadataDisplayInfoProvider);
|
||||
},
|
||||
timeoutOperation: (bindingContext) =>
|
||||
{
|
||||
// return the default list if the connected bind fails
|
||||
return CreateDefaultCompletionItems(scriptParseInfo, scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
});
|
||||
return queueItem;
|
||||
}
|
||||
|
||||
private static bool ShouldShowCompletionList(Token token)
|
||||
{
|
||||
bool result = true;
|
||||
if (token != null)
|
||||
{
|
||||
switch (token.Id)
|
||||
{
|
||||
case (int)Tokens.LEX_MULTILINE_COMMENT:
|
||||
case (int)Tokens.LEX_END_OF_LINE_COMMENT:
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private AutoCompletionResult CreateDefaultCompletionItems(ScriptParseInfo scriptParseInfo, ScriptDocumentInfo scriptDocumentInfo, bool useLowerCaseSuggestions)
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
CompletionItem[] completionList = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
result.CompleteResult(completionList);
|
||||
return result;
|
||||
}
|
||||
|
||||
private AutoCompletionResult CreateCompletionsFromSqlParser(
|
||||
ConnectionInfo connInfo,
|
||||
ScriptParseInfo scriptParseInfo,
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
MetadataDisplayInfoProvider metadataDisplayInfoProvider)
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
IEnumerable<Declaration> suggestions = SqlParserWrapper.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
scriptDocumentInfo.ParserLine,
|
||||
scriptDocumentInfo.ParserColumn,
|
||||
metadataDisplayInfoProvider);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
scriptParseInfo.CurrentSuggestions = suggestions;
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
CompletionItem[] completionList = AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
scriptParseInfo.CurrentSuggestions,
|
||||
scriptDocumentInfo.StartLine,
|
||||
scriptDocumentInfo.StartColumn,
|
||||
scriptDocumentInfo.EndColumn,
|
||||
scriptDocumentInfo.TokenText);
|
||||
|
||||
result.CompleteResult(completionList);
|
||||
|
||||
//The bucket for number of milliseconds will take to send back auto complete list
|
||||
connInfo.IntellisenseMetrics.UpdateMetrics(result.Duration, 1, (k2, v2) => v2 + 1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
@@ -11,7 +10,7 @@ using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a completion item from SQL parser declaration item
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlParserWrapper interface
|
||||
/// </summary>
|
||||
public interface ISqlParserWrapper
|
||||
{
|
||||
IEnumerable<Declaration> FindCompletions(ParseResult parseResult, int line, int col, IMetadataDisplayInfoProvider displayInfoProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper class around SQL parser methods to make the operations testable
|
||||
/// </summary>
|
||||
public class SqlParserWrapper : ISqlParserWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates completion list given SQL script info
|
||||
/// </summary>
|
||||
public IEnumerable<Declaration> FindCompletions(ParseResult parseResult, int line, int col, IMetadataDisplayInfoProvider displayInfoProvider)
|
||||
{
|
||||
return Resolver.FindCompletions(parseResult, line, col, displayInfoProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||
{
|
||||
public class TelemetryProperties
|
||||
{
|
||||
public string EventName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry properties
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry measures
|
||||
/// </summary>
|
||||
public Dictionary<string, double> Measures { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters sent back with an IntelliSense ready event
|
||||
/// </summary>
|
||||
public class TelemetryParams
|
||||
{
|
||||
public TelemetryProperties Params { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event sent when the language service needs to add a telemetry event
|
||||
/// </summary>
|
||||
public class TelemetryNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<TelemetryParams> Type =
|
||||
EventType<TelemetryParams>.Create("telemetry/event");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of telemetry events
|
||||
/// </summary>
|
||||
public static class TelemetryEventNames
|
||||
{
|
||||
/// <summary>
|
||||
/// telemetry event name for auto complete response time
|
||||
/// </summary>
|
||||
public const string IntellisenseQuantile = "IntellisenseQuantile";
|
||||
|
||||
/// <summary>
|
||||
/// telemetry even name for when definition is requested
|
||||
/// </summary>
|
||||
public const string PeekDefinitionRequested = "PeekDefinitionRequested";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to calculate the value for the metrics using the given bucket
|
||||
/// </summary>
|
||||
public class InteractionMetrics<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new instance given a bucket of metrics
|
||||
/// </summary>
|
||||
public InteractionMetrics(int[] metrics)
|
||||
{
|
||||
Validate.IsNotNull("metrics", metrics);
|
||||
if(metrics.Length == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("metrics");
|
||||
}
|
||||
|
||||
Counters = new ConcurrentDictionary<string, T>();
|
||||
if (!IsSorted(metrics))
|
||||
{
|
||||
Array.Sort(metrics);
|
||||
}
|
||||
Metrics = metrics;
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<string, T> Counters { get; }
|
||||
|
||||
private object perfCountersLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The metrics bucket
|
||||
/// </summary>
|
||||
public int[] Metrics { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given list is sorted
|
||||
/// </summary>
|
||||
private bool IsSorted(int[] metrics)
|
||||
{
|
||||
if (metrics.Length > 1)
|
||||
{
|
||||
int previous = metrics[0];
|
||||
for (int i = 1; i < metrics.Length; i++)
|
||||
{
|
||||
if(metrics[i] < previous)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
previous = metrics[i];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update metric value given new number
|
||||
/// </summary>
|
||||
public void UpdateMetrics(double duration, T newValue, Func<string, T, T> updateValueFactory)
|
||||
{
|
||||
int metric = Metrics[Metrics.Length - 1];
|
||||
for (int i = 0; i < Metrics.Length; i++)
|
||||
{
|
||||
if (duration <= Metrics[i])
|
||||
{
|
||||
metric = Metrics[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
string key = metric.ToString();
|
||||
Counters.AddOrUpdate(key, newValue, updateValueFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the quantile
|
||||
/// </summary>
|
||||
public Dictionary<string, T> Quantile
|
||||
{
|
||||
get
|
||||
{
|
||||
return Counters.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
@@ -310,6 +311,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
if (locations != null)
|
||||
{
|
||||
await requestContext.SendResult(locations);
|
||||
|
||||
// Send a notification to signal that definition is sent
|
||||
await ServiceHost.Instance.SendEvent(TelemetryNotification.Type, new TelemetryParams()
|
||||
{
|
||||
Params = new TelemetryProperties
|
||||
{
|
||||
EventName = TelemetryEventNames.PeekDefinitionRequested
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,7 +700,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
|
||||
// Get token from selected text
|
||||
Token selectedToken = GetToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character);
|
||||
Token selectedToken = ScriptDocumentInfo.GetToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character);
|
||||
if (selectedToken == null)
|
||||
{
|
||||
return null;
|
||||
@@ -871,12 +881,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
startLine + 1,
|
||||
endColumn + 1,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// convert from the parser format to the VS Code wire format
|
||||
return AutoCompleteHelper.ConvertMethodHelpTextListToSignatureHelp(methods,
|
||||
methodLocations,
|
||||
startLine + 1,
|
||||
endColumn + 1);
|
||||
|
||||
if (methodLocations != null)
|
||||
{
|
||||
// convert from the parser format to the VS Code wire format
|
||||
return AutoCompleteHelper.ConvertMethodHelpTextListToSignatureHelp(methods,
|
||||
methodLocations,
|
||||
startLine + 1,
|
||||
endColumn + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
@@ -900,168 +917,49 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
public CompletionItem[] GetCompletionItems(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
ScriptFile scriptFile,
|
||||
ScriptFile scriptFile,
|
||||
ConnectionInfo connInfo)
|
||||
{
|
||||
// initialize some state to parse and bind the current script file
|
||||
this.currentCompletionParseInfo = null;
|
||||
CompletionItem[] resultCompletionItems = null;
|
||||
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||
int startLine = textDocumentPosition.Position.Line;
|
||||
int parserLine = textDocumentPosition.Position.Line + 1;
|
||||
int startColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int endColumn = TextUtilities.PositionOfNextDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int parserColumn = textDocumentPosition.Position.Character + 1;
|
||||
CompletionService completionService = new CompletionService(BindingQueue);
|
||||
bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value;
|
||||
|
||||
// get the current script parse info object
|
||||
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
|
||||
ScriptDocumentInfo scriptDocumentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
|
||||
|
||||
if (scriptParseInfo == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions);
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
// reparse and bind the SQL statement if needed
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
{
|
||||
ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
// if the parse failed then return the default list
|
||||
if (scriptParseInfo.ParseResult == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions);
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
// need to adjust line & column for base-1 parser indices
|
||||
Token token = GetToken(scriptParseInfo, parserLine, parserColumn);
|
||||
string tokenText = token != null ? token.Text : null;
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connInfo, scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
// cache the current script parse info object to resolve completions later
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
resultCompletionItems = result.CompletionItems;
|
||||
|
||||
// check if the file is connected and the file lock is available
|
||||
if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
// queue the completion task with the binding queue
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// get the completion list from SQL Parser
|
||||
scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
parserLine,
|
||||
parserColumn,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// cache the current script parse info object to resolve completions later
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
scriptParseInfo.CurrentSuggestions,
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
tokenText);
|
||||
},
|
||||
timeoutOperation: (bindingContext) =>
|
||||
{
|
||||
// return the default list if the connected bind fails
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions,
|
||||
tokenText);
|
||||
});
|
||||
|
||||
// wait for the queue item
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
|
||||
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
|
||||
if (completionItems != null && completionItems.Length > 0)
|
||||
{
|
||||
resultCompletionItems = completionItems;
|
||||
}
|
||||
else if (!ShouldShowCompletionList(token))
|
||||
{
|
||||
resultCompletionItems = AutoCompleteHelper.EmptyCompletionList;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no completions then provide the default list
|
||||
if (resultCompletionItems == null)
|
||||
{
|
||||
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions,
|
||||
tokenText);
|
||||
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
return resultCompletionItems;
|
||||
}
|
||||
|
||||
private static Token GetToken(ScriptParseInfo scriptParseInfo, int startLine, int startColumn)
|
||||
{
|
||||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
|
||||
{
|
||||
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
|
||||
if (tokenIndex >= 0)
|
||||
{
|
||||
// return the current token
|
||||
int currentIndex = 0;
|
||||
foreach (var token in scriptParseInfo.ParseResult.Script.Tokens)
|
||||
{
|
||||
if (currentIndex == tokenIndex)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
++currentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ShouldShowCompletionList(Token token)
|
||||
{
|
||||
bool result = true;
|
||||
if (token != null)
|
||||
{
|
||||
switch (token.Id)
|
||||
{
|
||||
case (int)Tokens.LEX_MULTILINE_COMMENT:
|
||||
case (int)Tokens.LEX_END_OF_LINE_COMMENT:
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Diagnostic Provider methods
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to calculate the numbers used by SQL parser using the text positions and content
|
||||
/// </summary>
|
||||
internal class ScriptDocumentInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Create new instance
|
||||
/// </summary>
|
||||
public ScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo)
|
||||
{
|
||||
StartLine = textDocumentPosition.Position.Line;
|
||||
ParserLine = textDocumentPosition.Position.Line + 1;
|
||||
StartColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
EndColumn = TextUtilities.PositionOfNextDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
ParserColumn = textDocumentPosition.Position.Character + 1;
|
||||
ScriptParseInfo = scriptParseInfo;
|
||||
Contents = scriptFile.Contents;
|
||||
|
||||
// need to adjust line & column for base-1 parser indices
|
||||
Token = GetToken(scriptParseInfo, ParserLine, ParserColumn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string containing the full contents of the file.
|
||||
/// </summary>
|
||||
public string Contents { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Script Parse Info Instance
|
||||
/// </summary>
|
||||
public ScriptParseInfo ScriptParseInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start Line
|
||||
/// </summary>
|
||||
public int StartLine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parser Line
|
||||
/// </summary>
|
||||
public int ParserLine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start Column
|
||||
/// </summary>
|
||||
public int StartColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// end Column
|
||||
/// </summary>
|
||||
public int EndColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parser Column
|
||||
/// </summary>
|
||||
public int ParserColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token text in the file content used for completion list
|
||||
/// </summary>
|
||||
public string TokenText
|
||||
{
|
||||
get
|
||||
{
|
||||
return Token != null ? Token.Text : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The token in the file content used for completion list
|
||||
/// </summary>
|
||||
public Token Token { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the token that will be used by SQL parser for creating the completion list
|
||||
/// </summary>
|
||||
internal static Token GetToken(ScriptParseInfo scriptParseInfo, int startLine, int startColumn)
|
||||
{
|
||||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
|
||||
{
|
||||
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
|
||||
if (tokenIndex >= 0)
|
||||
{
|
||||
// return the current token
|
||||
int currentIndex = 0;
|
||||
foreach (var token in scriptParseInfo.ParseResult.Script.Tokens)
|
||||
{
|
||||
if (currentIndex == tokenIndex)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
++currentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Completion
|
||||
{
|
||||
public class AutoCompletionResultTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
int duration = 2000;
|
||||
Thread.Sleep(duration);
|
||||
result.CompleteResult(new CompletionItem[] { });
|
||||
Assert.True(result.Duration >= duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Completion
|
||||
{
|
||||
public class ScriptDocumentInfoTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
TextDocumentPosition doc = new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
},
|
||||
Position = new Position()
|
||||
{
|
||||
Line = 1,
|
||||
Character = 14
|
||||
}
|
||||
};
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = "Select * from sys.all_objects"
|
||||
};
|
||||
|
||||
ScriptParseInfo scriptParseInfo = new ScriptParseInfo();
|
||||
ScriptDocumentInfo docInfo = new ScriptDocumentInfo(doc, scriptFile, scriptParseInfo);
|
||||
|
||||
Assert.Equal(docInfo.StartLine, 1);
|
||||
Assert.Equal(docInfo.ParserLine, 2);
|
||||
Assert.Equal(docInfo.StartColumn, 44);
|
||||
Assert.Equal(docInfo.EndColumn, 14);
|
||||
Assert.Equal(docInfo.ParserColumn, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// 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 System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||
{
|
||||
public class CompletionServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public void CompletionItemsShouldCreatedUsingSqlParserIfTheProcessDoesNotTimeout()
|
||||
{
|
||||
ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
ScriptDocumentInfo docInfo = CreateScriptDocumentInfo();
|
||||
CompletionService completionService = new CompletionService(bindingQueue);
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(null, null, null);
|
||||
bool useLowerCaseSuggestions = true;
|
||||
CompletionItem[] defaultCompletionList = AutoCompleteHelper.GetDefaultCompletionItems(docInfo, useLowerCaseSuggestions);
|
||||
|
||||
List<Declaration> declarations = new List<Declaration>();
|
||||
|
||||
var sqlParserWrapper = new Mock<ISqlParserWrapper>();
|
||||
sqlParserWrapper.Setup(x => x.FindCompletions(docInfo.ScriptParseInfo.ParseResult, docInfo.ParserLine, docInfo.ParserColumn,
|
||||
It.IsAny<IMetadataDisplayInfoProvider>())).Returns(declarations);
|
||||
completionService.SqlParserWrapper = sqlParserWrapper.Object;
|
||||
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connectionInfo, docInfo, useLowerCaseSuggestions);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEqual(result.CompletionItems == null ? 0 : result.CompletionItems.Count(), defaultCompletionList.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompletionItemsShouldCreatedUsingDefaultListIfTheSqlParserProcessTimesout()
|
||||
{
|
||||
ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
ScriptDocumentInfo docInfo = CreateScriptDocumentInfo();
|
||||
CompletionService completionService = new CompletionService(bindingQueue);
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(null, null, null);
|
||||
bool useLowerCaseSuggestions = true;
|
||||
List<Declaration> declarations = new List<Declaration>();
|
||||
CompletionItem[] defaultCompletionList = AutoCompleteHelper.GetDefaultCompletionItems(docInfo, useLowerCaseSuggestions);
|
||||
|
||||
var sqlParserWrapper = new Mock<ISqlParserWrapper>();
|
||||
sqlParserWrapper.Setup(x => x.FindCompletions(docInfo.ScriptParseInfo.ParseResult, docInfo.ParserLine, docInfo.ParserColumn,
|
||||
It.IsAny<IMetadataDisplayInfoProvider>())).Callback(() => Thread.Sleep(LanguageService.BindingTimeout + 100)).Returns(declarations);
|
||||
completionService.SqlParserWrapper = sqlParserWrapper.Object;
|
||||
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connectionInfo, docInfo, useLowerCaseSuggestions);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result.CompletionItems.Count(), defaultCompletionList.Count());
|
||||
Thread.Sleep(3000);
|
||||
Assert.True(connectionInfo.IntellisenseMetrics.Quantile.Any());
|
||||
}
|
||||
|
||||
private ScriptDocumentInfo CreateScriptDocumentInfo()
|
||||
{
|
||||
TextDocumentPosition doc = new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
},
|
||||
Position = new Position()
|
||||
{
|
||||
Line = 1,
|
||||
Character = 14
|
||||
}
|
||||
};
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = "Select * from sys.all_objects"
|
||||
};
|
||||
|
||||
ScriptParseInfo scriptParseInfo = new ScriptParseInfo() { IsConnected = true };
|
||||
ScriptDocumentInfo docInfo = new ScriptDocumentInfo(doc, scriptFile, scriptParseInfo);
|
||||
|
||||
return docInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||
{
|
||||
public class InteractionMetricsTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
int[] metrics = new int[] { 4, 8, 1, 11, 3 };
|
||||
int[] expected = new int[] { 1, 3, 4, 8, 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldThrowExceptionGivenNullInput()
|
||||
{
|
||||
int[] metrics = null;
|
||||
Assert.Throws<ArgumentNullException>(() => new InteractionMetrics<int>(metrics));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldThrowExceptionGivenEmptyInput()
|
||||
{
|
||||
int[] metrics = new int[] { };
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new InteractionMetrics<int>(metrics));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldNotChangeGivenSortedArray()
|
||||
{
|
||||
int[] metrics = new int[] { 1, 3, 4, 8, 11 };
|
||||
int[] expected = new int[] { 1, 3, 4, 8, 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldNotChangeGivenArrayWithOneItem()
|
||||
{
|
||||
int[] metrics = new int[] { 11 };
|
||||
int[] expected = new int[] { 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsCalculateQuantileCorrectlyGivenSeveralUpdates()
|
||||
{
|
||||
int[] metrics = new int[] { 50, 100, 300, 500, 1000, 2000 };
|
||||
Func<string, double, double> updateValueFactory = (k, current) => current + 1;
|
||||
InteractionMetrics<double> interactionMetrics = new InteractionMetrics<double>(metrics);
|
||||
interactionMetrics.UpdateMetrics(54.4, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(345, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(23, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(51, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(500, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(4005, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(2500, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(123, 1, updateValueFactory);
|
||||
|
||||
Dictionary<string, double> quantile = interactionMetrics.Quantile;
|
||||
Assert.NotNull(quantile);
|
||||
Assert.Equal(quantile.Count, 5);
|
||||
Assert.Equal(quantile["50"], 1);
|
||||
Assert.Equal(quantile["100"], 2);
|
||||
Assert.Equal(quantile["300"], 1);
|
||||
Assert.Equal(quantile["500"], 2);
|
||||
Assert.Equal(quantile["2000"], 2);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user