FIx intellisense cache not refreshing (#831)

* FIx intellisense cache not refreshing

* Remove extra newlines

* Output label to provide more useful information in case of error
This commit is contained in:
Charles Gagnon
2019-06-27 23:50:41 +00:00
committed by GitHub
parent a6450eb180
commit c1f2411b02
4 changed files with 112 additions and 31 deletions

View File

@@ -26,8 +26,8 @@ namespace Microsoft.SqlTools.Hosting.Protocol
this.messageWriter = messageWriter; this.messageWriter = messageWriter;
} }
public async Task SendEvent<TParams>( public virtual async Task SendEvent<TParams>(
EventType<TParams> eventType, EventType<TParams> eventType,
TParams eventParams) TParams eventParams)
{ {
await this.messageWriter.WriteEvent( await this.messageWriter.WriteEvent(

View File

@@ -290,17 +290,17 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="requestContext"></param> /// <param name="requestContext"></param>
/// <returns></returns> /// <returns></returns>
internal async Task HandleSyntaxParseRequest(SyntaxParseParams param, RequestContext<SyntaxParseResult> requestContext) internal async Task HandleSyntaxParseRequest(SyntaxParseParams param, RequestContext<SyntaxParseResult> requestContext)
{ {
await Task.Run(async () => await Task.Run(async () =>
{ {
try try
{ {
ParseResult result = Parser.Parse(param.Query); ParseResult result = Parser.Parse(param.Query);
SyntaxParseResult syntaxResult = new SyntaxParseResult(); SyntaxParseResult syntaxResult = new SyntaxParseResult();
if (result != null && result.Errors.Count() == 0) if (result != null && result.Errors.Count() == 0)
{ {
syntaxResult.Parseable = true; syntaxResult.Parseable = true;
} else } else
{ {
syntaxResult.Parseable = false; syntaxResult.Parseable = false;
string[] errorMessages = new string[result.Errors.Count()]; string[] errorMessages = new string[result.Errors.Count()];
@@ -328,9 +328,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal async Task HandleCompletionRequest( internal async Task HandleCompletionRequest(
TextDocumentPosition textDocumentPosition, TextDocumentPosition textDocumentPosition,
RequestContext<CompletionItem[]> requestContext) RequestContext<CompletionItem[]> requestContext)
{ {
try try
{ {
// check if Intellisense suggestions are enabled // check if Intellisense suggestions are enabled
if (ShouldSkipIntellisense(textDocumentPosition.TextDocument.Uri)) if (ShouldSkipIntellisense(textDocumentPosition.TextDocument.Uri))
{ {
@@ -355,7 +355,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
var completionItems = GetCompletionItems( var completionItems = GetCompletionItems(
textDocumentPosition, scriptFile, connInfo); textDocumentPosition, scriptFile, connInfo);
await requestContext.SendResult(completionItems); await requestContext.SendResult(completionItems);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -397,7 +397,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext<Location[]> requestContext) internal async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext<Location[]> requestContext)
{ {
try try
{ {
DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequested); DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequested);
@@ -414,7 +414,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
isConnected = ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); isConnected = ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo);
definitionResult = GetDefinition(textDocumentPosition, scriptFile, connInfo); definitionResult = GetDefinition(textDocumentPosition, scriptFile, connInfo);
} }
if (definitionResult != null && !definitionResult.IsErrorResult) if (definitionResult != null && !definitionResult.IsErrorResult)
{ {
await requestContext.SendResult(definitionResult.Locations); await requestContext.SendResult(definitionResult.Locations);
@@ -628,13 +628,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
await Task.Run(() => await Task.Run(() =>
{ {
// Get the current ScriptInfo if one exists so we can lock it while we're rebuilding the cache
ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false); ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false);
if (scriptInfo != null && scriptInfo.IsConnected && if (scriptInfo != null && scriptInfo.IsConnected &&
Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{ {
try try
{ {
this.BindingQueue.AddConnectionContext(connInfo, featureName: "LanguageService", overwrite: true); this.BindingQueue.AddConnectionContext(connInfo, featureName: "LanguageService", overwrite: true);
RemoveScriptParseInfo(rebuildParams.OwnerUri);
UpdateLanguageServiceOnConnection(connInfo).Wait();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -727,7 +730,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="info"></param> /// <param name="info"></param>
public async Task HandleDidChangeLanguageFlavorNotification( public async Task HandleDidChangeLanguageFlavorNotification(
LanguageFlavorChangeParams changeParams, LanguageFlavorChangeParams changeParams,
EventContext eventContext) EventContext eventContext)
{ {
try try
{ {
@@ -814,7 +817,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
}, ConnectedBindingQueue.QueueThreadStackSize); }, ConnectedBindingQueue.QueueThreadStackSize);
parseThread.Start(); parseThread.Start();
parseThread.Join(); parseThread.Join();
} }
else else
{ {
@@ -1109,7 +1112,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return completionItem; return completionItem;
} }
/// <summary> /// <summary>
/// Queue a task to the binding queue /// Queue a task to the binding queue
/// </summary> /// </summary>
@@ -1119,7 +1122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="scriptFile"></param> /// <param name="scriptFile"></param>
/// <param name="tokenText"></param> /// <param name="tokenText"></param>
/// <returns> Returns the result of the task as a DefinitionResult </returns> /// <returns> Returns the result of the task as a DefinitionResult </returns>
private DefinitionResult QueueTask(TextDocumentPosition textDocumentPosition, ScriptParseInfo scriptParseInfo, private DefinitionResult QueueTask(TextDocumentPosition textDocumentPosition, ScriptParseInfo scriptParseInfo,
ConnectionInfo connInfo, ScriptFile scriptFile, string tokenText) ConnectionInfo connInfo, ScriptFile scriptFile, string tokenText)
{ {
// Queue the task with the binding queue // Queue the task with the binding queue
@@ -1132,10 +1135,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// Script object using SMO // Script object using SMO
Scripter scripter = new Scripter(bindingContext.ServerConnection, connInfo); Scripter scripter = new Scripter(bindingContext.ServerConnection, connInfo);
return scripter.GetScript( return scripter.GetScript(
scriptParseInfo.ParseResult, scriptParseInfo.ParseResult,
textDocumentPosition.Position, textDocumentPosition.Position,
bindingContext.MetadataDisplayInfoProvider, bindingContext.MetadataDisplayInfoProvider,
tokenText, tokenText,
schemaName); schemaName);
}, },
timeoutOperation: (bindingContext) => timeoutOperation: (bindingContext) =>
@@ -1158,7 +1161,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Locations = null Locations = null
}; };
}); });
// wait for the queue item // wait for the queue item
queueItem.ItemProcessed.WaitOne(); queueItem.ItemProcessed.WaitOne();
var result = queueItem.GetResultAsT<DefinitionResult>(); var result = queueItem.GetResultAsT<DefinitionResult>();
@@ -1235,7 +1238,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
// Get token from selected text // Get token from selected text
Tuple<Stack<Token>, Queue<Token>> selectedToken = ScriptDocumentInfo.GetPeekDefinitionTokens(scriptParseInfo, Tuple<Stack<Token>, Queue<Token>> selectedToken = ScriptDocumentInfo.GetPeekDefinitionTokens(scriptParseInfo,
textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character + 1); textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character + 1);
if (selectedToken == null) if (selectedToken == null)
@@ -1250,7 +1253,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
List<Token> tokenList = childrenTokens.ToList(); List<Token> tokenList = childrenTokens.ToList();
DefinitionResult childrenResult = GetDefinitionFromTokenList(textDocumentPosition, tokenList, scriptParseInfo, scriptFile, connInfo); DefinitionResult childrenResult = GetDefinitionFromTokenList(textDocumentPosition, tokenList, scriptParseInfo, scriptFile, connInfo);
// if the children peak definition returned null then // if the children peak definition returned null then
// try the parents // try the parents
if (childrenResult == null || childrenResult.IsErrorResult) if (childrenResult == null || childrenResult.IsErrorResult)
{ {
@@ -1262,7 +1265,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
else else
{ {
return childrenResult; return childrenResult;
} }
} }
else else
{ {
@@ -1275,9 +1278,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}; };
} }
} }
/// <summary> /// <summary>
/// Wrapper around find token method /// Wrapper around find token method
/// </summary> /// </summary>
/// <param name="scriptParseInfo"></param> /// <param name="scriptParseInfo"></param>
/// <param name="startLine"></param> /// <param name="startLine"></param>
@@ -1774,13 +1777,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
bool? lineHasSingleStatement = null; bool? lineHasSingleStatement = null;
// check if the batch matches parameters // check if the batch matches parameters
if (batch.StartLocation.LineNumber <= parserLine if (batch.StartLocation.LineNumber <= parserLine
&& batch.EndLocation.LineNumber >= parserLine) && batch.EndLocation.LineNumber >= parserLine)
{ {
foreach (var statement in batch.Statements) foreach (var statement in batch.Statements)
{ {
// check if the statement matches parameters // check if the statement matches parameters
if (statement.StartLocation.LineNumber <= parserLine if (statement.StartLocation.LineNumber <= parserLine
&& statement.EndLocation.LineNumber >= parserLine) && statement.EndLocation.LineNumber >= parserLine)
{ {
if (statement.EndLocation.LineNumber == parserLine && statement.EndLocation.ColumnNumber < parserColumn if (statement.EndLocation.LineNumber == parserLine && statement.EndLocation.ColumnNumber < parserColumn

View File

@@ -3,6 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
@@ -74,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
} }
} }
// This test currently requires a live database connection to initialize // This test currently requires a live database connection to initialize
// SMO connected metadata provider. Since we don't want a live DB dependency // SMO connected metadata provider. Since we don't want a live DB dependency
// in the CI unit tests this scenario is currently disabled. // in the CI unit tests this scenario is currently disabled.
[Fact] [Fact]
@@ -155,7 +156,65 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
// add a new connection context // add a new connection context
connectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(result.ConnectionInfo, overwrite: true); connectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(result.ConnectionInfo, overwrite: true);
Assert.True(LanguageService.Instance.BindingQueue.BindingContextMap.ContainsKey(connectionKey)); Assert.True(LanguageService.Instance.BindingQueue.BindingContextMap.ContainsKey(connectionKey));
Assert.False(object.ReferenceEquals(LanguageService.Instance.BindingQueue.BindingContextMap[connectionKey].ServerConnection, orgServerConnection)); Assert.False(object.ReferenceEquals(LanguageService.Instance.BindingQueue.BindingContextMap[connectionKey].ServerConnection, orgServerConnection));
} }
/// <summary>
/// Verifies that clearing the Intellisense cache correctly refreshes the cache with new info from the DB.
/// </summary>
[Fact]
public async Task RebuildIntellisenseCacheClearsScriptParseInfoCorrectly()
{
var testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, null, null, "LangSvcTest");
try
{
var connectionInfoResult = LiveConnectionHelper.InitLiveConnectionInfo(testDb.DatabaseName);
var langService = LanguageService.Instance;
await langService.UpdateLanguageServiceOnConnection(connectionInfoResult.ConnectionInfo);
var queryText = "SELECT * FROM dbo.";
connectionInfoResult.ScriptFile.SetFileContents(queryText);
var textDocumentPosition =
connectionInfoResult.TextDocumentPosition ??
new TextDocumentPosition()
{
TextDocument = new TextDocumentIdentifier
{
Uri = connectionInfoResult.ScriptFile.ClientFilePath
},
Position = new Position
{
Line = 0,
Character = queryText.Length
}
};
// First check that we don't have any items in the completion list as expected
var initialCompletionItems = langService.GetCompletionItems(
textDocumentPosition, connectionInfoResult.ScriptFile, connectionInfoResult.ConnectionInfo);
Assert.True(initialCompletionItems.Length == 0, $"Should not have any completion items initially. Actual : [{string.Join(',', initialCompletionItems.Select(ci => ci.Label))}]");
// Now create a table that should show up in the completion list
testDb.RunQuery("CREATE TABLE dbo.foo(col1 int)");
// And refresh the cache
await langService.HandleRebuildIntelliSenseNotification(
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientFilePath },
new TestEventContext());
// Now we should expect to see the item show up in the completion list
var afterTableCreationCompletionItems = langService.GetCompletionItems(
textDocumentPosition, connectionInfoResult.ScriptFile, connectionInfoResult.ConnectionInfo);
Assert.True(afterTableCreationCompletionItems.Length == 1, $"Should only have a single completion item after rebuilding Intellisense cache. Actual : [{string.Join(',', initialCompletionItems.Select(ci => ci.Label))}]");
Assert.True(afterTableCreationCompletionItems[0].InsertText == "foo", $"Expected single completion item 'foo'. Actual : [{string.Join(',', initialCompletionItems.Select(ci => ci.Label))}]");
}
finally
{
testDb.Cleanup();
}
}
} }
} }

View File

@@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Test.Common
{
/// <summary>
/// Simple EventContext for testing that just swallows all events.
/// </summary>
public class TestEventContext : EventContext
{
public override async Task SendEvent<TParams>(
EventType<TParams> eventType,
TParams eventParams)
{
await Task.FromResult(0);
}
}
}