mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user