mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
Fixes BindingQueue, GetSignatureHelp and RefreshIntellisenseCache to be truly async (#2175)
This commit is contained in:
@@ -12,9 +12,8 @@ using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
@@ -130,7 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
return context.IsConnected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +143,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
key = "disconnected_binding_context";
|
||||
}
|
||||
|
||||
|
||||
lock (this.bindingContextLock)
|
||||
{
|
||||
if (!this.BindingContextMap.ContainsKey(key))
|
||||
@@ -155,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
|
||||
return this.BindingContextMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<IBindingContext> GetBindingContexts(string keyPrefix)
|
||||
@@ -269,10 +268,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
this.itemQueuedEvent,
|
||||
token.WaitHandle
|
||||
};
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
// wait for with an item to be queued or the a cancellation request
|
||||
// wait for with an item to be queued or the cancellation request
|
||||
WaitHandle.WaitAny(waitHandles);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -283,7 +282,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
// dispatch all pending queue items
|
||||
while (this.HasPendingQueueItems)
|
||||
{
|
||||
{
|
||||
QueueItem queueItem = GetNextQueueItem();
|
||||
if (queueItem == null)
|
||||
{
|
||||
@@ -291,7 +290,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
|
||||
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
||||
if (bindingContext == null)
|
||||
if (bindingContext == null)
|
||||
{
|
||||
queueItem.ItemProcessed.Set();
|
||||
continue;
|
||||
@@ -300,140 +299,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
var bindingContextTask = this.BindingContextTasks[bindingContext];
|
||||
|
||||
// Run in the binding context task in case this task has to wait for a previous binding operation
|
||||
this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith((task) =>
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
// prefer the queue item binding item, otherwise use the context default timeout
|
||||
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
|
||||
|
||||
// handle the case a previous binding operation is still running
|
||||
if (!bindingContext.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Warning("Binding queue operation timed out waiting for previous operation to finish");
|
||||
queueItem.Result = queueItem.TimeoutOperation != null
|
||||
? queueItem.TimeoutOperation(bindingContext)
|
||||
: null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Exception running binding queue lock timeout handler: " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bindingContext.BindingLock.Reset();
|
||||
|
||||
lockTaken = true;
|
||||
|
||||
// execute the binding operation
|
||||
object result = null;
|
||||
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||
|
||||
// run the operation in a separate thread
|
||||
var bindTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
result = queueItem.BindOperation(
|
||||
bindingContext,
|
||||
cancelToken.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Unexpected exception on the binding queue: " + ex.ToString());
|
||||
if (queueItem.ErrorHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = queueItem.ErrorHandler(ex);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Logger.Error("Unexpected exception in binding queue error handler: " + ex2.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (IsExceptionOfType(ex, typeof(SqlException)) || IsExceptionOfType(ex, typeof(SocketException)))
|
||||
{
|
||||
if (this.OnUnhandledException != null)
|
||||
{
|
||||
this.OnUnhandledException(queueItem.Key, ex);
|
||||
}
|
||||
|
||||
RemoveBindingContext(queueItem.Key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// check if the binding tasks completed within the binding timeout
|
||||
if (bindTask.Wait(bindTimeout))
|
||||
{
|
||||
queueItem.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
|
||||
// if the task didn't complete then call the timeout callback
|
||||
if (queueItem.TimeoutOperation != null)
|
||||
{
|
||||
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
|
||||
}
|
||||
|
||||
bindTask.ContinueWithOnFaulted(t => Logger.Error("Binding queue threw exception " + t.Exception.ToString()));
|
||||
|
||||
// Give the task a chance to complete before moving on to the next operation
|
||||
bindTask.Wait();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Binding queue task completion threw exception " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// set item processed to avoid deadlocks
|
||||
if (lockTaken)
|
||||
{
|
||||
bindingContext.BindingLock.Set();
|
||||
}
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// catch and log any exceptions raised in the binding calls
|
||||
// set item processed to avoid deadlocks
|
||||
Logger.Error("Binding queue threw exception " + ex.ToString());
|
||||
// set item processed to avoid deadlocks
|
||||
if (lockTaken)
|
||||
{
|
||||
bindingContext.BindingLock.Set();
|
||||
}
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
}, TaskContinuationOptions.None);
|
||||
this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith(
|
||||
task => DispatchQueueItem(bindingContext, queueItem)
|
||||
, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
// if a queue processing cancellation was requested then exit the loop
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -450,6 +325,127 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchQueueItem(IBindingContext bindingContext, QueueItem queueItem)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
// prefer the queue item binding item, otherwise use the context default timeout - timeout is in milliseconds
|
||||
int bindTimeoutInMs = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
|
||||
|
||||
// handle the case a previous binding operation is still running
|
||||
if (!bindingContext.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Warning("Binding queue operation timed out waiting for previous operation to finish");
|
||||
queueItem.Result = queueItem.TimeoutOperation != null
|
||||
? queueItem.TimeoutOperation(bindingContext)
|
||||
: null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Exception running binding queue lock timeout handler: " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
}
|
||||
|
||||
bindingContext.BindingLock.Reset();
|
||||
|
||||
lockTaken = true;
|
||||
|
||||
// execute the binding operation
|
||||
object result = null;
|
||||
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||
|
||||
// run the operation in a separate thread
|
||||
var bindTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
result = queueItem.BindOperation(
|
||||
bindingContext,
|
||||
cancelToken.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Unexpected exception on the binding queue: " + ex.ToString());
|
||||
if (queueItem.ErrorHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = queueItem.ErrorHandler(ex);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Logger.Error("Unexpected exception in binding queue error handler: " + ex2.ToString());
|
||||
}
|
||||
}
|
||||
if (IsExceptionOfType(ex, typeof(SqlException)) || IsExceptionOfType(ex, typeof(SocketException)))
|
||||
{
|
||||
if (this.OnUnhandledException != null)
|
||||
{
|
||||
this.OnUnhandledException(queueItem.Key, ex);
|
||||
}
|
||||
RemoveBindingContext(queueItem.Key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// check if the binding tasks completed within the binding timeout
|
||||
if (bindTask.Wait(bindTimeoutInMs))
|
||||
{
|
||||
queueItem.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
// if the task didn't complete then call the timeout callback
|
||||
if (queueItem.TimeoutOperation != null)
|
||||
{
|
||||
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
|
||||
}
|
||||
bindTask.ContinueWithOnFaulted(t => Logger.Error("Binding queue threw exception " + t.Exception.ToString()));
|
||||
// Give the task a chance to complete before moving on to the next operation
|
||||
bindTask.Wait();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Binding queue task completion threw exception " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// set item processed to avoid deadlocks
|
||||
if (lockTaken)
|
||||
{
|
||||
bindingContext.BindingLock.Set();
|
||||
}
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// catch and log any exceptions raised in the binding calls
|
||||
// set item processed to avoid deadlocks
|
||||
Logger.Error("Binding queue threw exception " + ex.ToString());
|
||||
// set item processed to avoid deadlocks
|
||||
if (lockTaken)
|
||||
{
|
||||
bindingContext.BindingLock.Set();
|
||||
}
|
||||
queueItem.ItemProcessed.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear queued items
|
||||
/// </summary>
|
||||
@@ -487,7 +483,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool IsExceptionOfType(Exception ex, Type t)
|
||||
{
|
||||
return ex.GetType() == t || (ex.InnerException != null && ex.InnerException.GetType() == t);
|
||||
|
||||
@@ -107,18 +107,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
compatibilityLevel: DatabaseCompatibilityLevel.Current,
|
||||
transactSqlVersion: TransactSqlVersion.Current);
|
||||
|
||||
private ConcurrentDictionary<string, bool> nonMssqlUriMap = new ConcurrentDictionary<string, bool>();
|
||||
private ConcurrentDictionary<string, bool> nonMssqlUriMap = new();
|
||||
|
||||
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
|
||||
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
|
||||
private Lazy<ConcurrentDictionary<string, ScriptParseInfo>> scriptParseInfoMap
|
||||
= new Lazy<ConcurrentDictionary<string, ScriptParseInfo>>(() => new());
|
||||
|
||||
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new ConcurrentDictionary<string, ICompletionExtension>();
|
||||
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new ConcurrentDictionary<string, DateTime>();
|
||||
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new();
|
||||
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
|
||||
/// </summary>
|
||||
internal Dictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
||||
internal ConcurrentDictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -289,7 +289,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
WorkspaceServiceInstance.RegisterTextDocCloseCallback(HandleDidCloseTextDocumentNotification);
|
||||
|
||||
// Register a callback for when a connection is created
|
||||
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection);
|
||||
ConnectionServiceInstance.RegisterOnConnectionTask(StartUpdateLanguageServiceOnConnection);
|
||||
|
||||
// Register a callback for when a connection is closed
|
||||
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||
@@ -521,23 +521,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
};
|
||||
}
|
||||
|
||||
// turn off this code until needed (10/28/2016)
|
||||
#if false
|
||||
private async Task HandleReferencesRequest(
|
||||
ReferencesParams referencesParams,
|
||||
RequestContext<Location[]> requestContext)
|
||||
{
|
||||
await requestContext.SendResult(null);
|
||||
}
|
||||
|
||||
private async Task HandleDocumentHighlightRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<DocumentHighlight[]> requestContext)
|
||||
{
|
||||
await requestContext.SendResult(null);
|
||||
}
|
||||
#endif
|
||||
|
||||
internal async Task HandleSignatureHelpRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<SignatureHelp> requestContext)
|
||||
@@ -551,18 +534,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
ScriptFile scriptFile = CurrentWorkspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
SignatureHelp help = null;
|
||||
if (scriptFile != null)
|
||||
{
|
||||
help = GetSignatureHelp(textDocumentPosition, scriptFile);
|
||||
}
|
||||
if (help != null)
|
||||
{
|
||||
await requestContext.SendResult(help);
|
||||
}
|
||||
else
|
||||
{
|
||||
await requestContext.SendResult(new SignatureHelp());
|
||||
// Start task asynchronously without blocking main thread - this is by design.
|
||||
// Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
|
||||
// All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
GetSignatureHelp(textDocumentPosition, scriptFile)
|
||||
.ContinueWith(async task =>
|
||||
{
|
||||
var result = await task;
|
||||
if (result != null)
|
||||
{
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
await requestContext.SendResult(new SignatureHelp());
|
||||
}
|
||||
});
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -683,9 +674,33 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <summary>
|
||||
/// Handle the rebuild IntelliSense cache notification
|
||||
/// </summary>
|
||||
public async Task HandleRebuildIntelliSenseNotification(
|
||||
/// <param name="rebuildParams">Rebuild params</param>
|
||||
/// <param name="eventContext">Event context</param>
|
||||
/// <returns>Async task</returns>
|
||||
public Task HandleRebuildIntelliSenseNotification(
|
||||
RebuildIntelliSenseParams rebuildParams,
|
||||
EventContext eventContext)
|
||||
{
|
||||
// Start task asynchronously without blocking main thread - this is by design.
|
||||
// Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
|
||||
// All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
await DoHandleRebuildIntellisenseNotification(rebuildParams, eventContext);
|
||||
}, CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
TaskScheduler.Default);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to handle rebuild intellisense notification
|
||||
/// </summary>
|
||||
/// <param name="rebuildParams">Rebuild params</param>
|
||||
/// <param name="eventContext">Event context</param>
|
||||
/// <returns>Async task</returns>
|
||||
public async Task DoHandleRebuildIntellisenseNotification(RebuildIntelliSenseParams rebuildParams, EventContext eventContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -694,7 +709,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
// This URI doesn't come in escaped - so if it's a file path with reserved characters (such as %)
|
||||
// then we'll fail to find it since GetFile expects the URI to be a fully-escaped URI as that's
|
||||
// what the document events are sent in as.
|
||||
var escapedOwnerUri = Uri.EscapeUriString(rebuildParams.OwnerUri);
|
||||
var escapedOwnerUri = Uri.EscapeDataString(rebuildParams.OwnerUri);
|
||||
// Skip closing this file if the file doesn't exist
|
||||
var scriptFile = this.CurrentWorkspace.GetFile(escapedOwnerUri);
|
||||
if (scriptFile == null)
|
||||
@@ -710,47 +725,44 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
// check that there is an active connection for the current editor
|
||||
if (connInfo != null)
|
||||
{
|
||||
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);
|
||||
if (scriptInfo != null && scriptInfo.IsConnected &&
|
||||
Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||
{
|
||||
// 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);
|
||||
if (scriptInfo != null && scriptInfo.IsConnected &&
|
||||
Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
|
||||
RemoveScriptParseInfo(rebuildParams.OwnerUri);
|
||||
UpdateLanguageServiceOnConnection(connInfo).Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Unknown error " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Set Metadata Build event to Signal state.
|
||||
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||
}
|
||||
this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
|
||||
RemoveScriptParseInfo(rebuildParams.OwnerUri);
|
||||
await UpdateLanguageServiceOnConnection(connInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Unknown error " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Set Metadata Build event to Signal state.
|
||||
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||
}
|
||||
|
||||
// if not in the preview window and diagnostics are enabled then run diagnostics
|
||||
if (!IsPreviewWindow(scriptFile)
|
||||
&& CurrentWorkspaceSettings.IsDiagnosticsEnabled)
|
||||
{
|
||||
RunScriptDiagnostics(
|
||||
new ScriptFile[] { scriptFile },
|
||||
eventContext);
|
||||
await RunScriptDiagnostics(
|
||||
new ScriptFile[] { scriptFile },
|
||||
eventContext);
|
||||
}
|
||||
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -887,121 +899,125 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="sqlText"></param>
|
||||
/// <returns>The ParseResult instance returned from SQL Parser</returns>
|
||||
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||
public Task<ParseResult> ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||
{
|
||||
Logger.Verbose($"ParseAndBind - {scriptFile}");
|
||||
// get or create the current parse info object
|
||||
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientUri, createIfNotExists: true);
|
||||
|
||||
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
|
||||
{
|
||||
if (connInfo == null || !parseInfo.IsConnected)
|
||||
try
|
||||
{
|
||||
// parse on separate thread so stack size can be increased
|
||||
var parseThread = new Thread(() =>
|
||||
if (connInfo == null || !parseInfo.IsConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
this.DefaultParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Log the exception but don't rethrow it to prevent parsing errors from crashing SQL Tools Service
|
||||
Logger.Error(string.Format("An unexpected error occured while parsing: {0}", e.ToString()));
|
||||
}
|
||||
}, ConnectedBindingQueue.QueueThreadStackSize);
|
||||
parseThread.Start();
|
||||
parseThread.Join();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: parseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
// parse on separate thread so stack size can be increased
|
||||
var parseThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
bindingContext.ParseOptions);
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
this.DefaultParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
if (bindingContext.IsConnected && bindingContext.Binder != null)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Log the exception but don't rethrow it to prevent parsing errors from crashing SQL Tools Service
|
||||
Logger.Error(string.Format("An unexpected error occured while parsing: {0}", e.ToString()));
|
||||
}
|
||||
}, ConnectedBindingQueue.QueueThreadStackSize);
|
||||
parseThread.Start();
|
||||
parseThread.Join();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: parseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
connInfo.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
bindingContext.ParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
if (bindingContext.IsConnected && bindingContext.Binder != null)
|
||||
{
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
connInfo.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
}
|
||||
}
|
||||
catch (ConnectionException)
|
||||
{
|
||||
Logger.Error("Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (SqlParserInternalBinderError)
|
||||
{
|
||||
Logger.Error("Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Unknown exception during parsing " + ex.ToString());
|
||||
}
|
||||
}
|
||||
catch (ConnectionException)
|
||||
{
|
||||
Logger.Error("Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (SqlParserInternalBinderError)
|
||||
{
|
||||
Logger.Error("Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Unknown exception during parsing " + ex.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// reset the parse result to do a full parse next time
|
||||
parseInfo.ParseResult = null;
|
||||
Logger.Error("Unknown exception during parsing " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(parseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
// reset the parse result to do a full parse next time
|
||||
parseInfo.ParseResult = null;
|
||||
Logger.Error("Unknown exception during parsing " + ex.ToString());
|
||||
Logger.Warning("Binding metadata lock timeout in ParseAndBind");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(parseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("Binding metadata lock timeout in ParseAndBind");
|
||||
}
|
||||
|
||||
return parseInfo.ParseResult;
|
||||
return parseInfo.ParseResult;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs UpdateLanguageServiceOnConnection as a background task
|
||||
/// </summary>
|
||||
/// <param name="info">Connection Info</param>
|
||||
/// <returns></returns>
|
||||
public Task StartUpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||
{
|
||||
// Start task asynchronously without blocking main thread - this is by design.
|
||||
// Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
|
||||
// All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
|
||||
Task.Factory.StartNew(() => UpdateLanguageServiceOnConnection(info));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a Task to update the autocomplete metadata provider when the user connects to a database
|
||||
/// </summary>
|
||||
/// <param name="info">Connection info</param>
|
||||
public Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
DoUpdateLanguageServiceOnConnection(info);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the autocomplete metadata provider when the user connects to a database synchronously
|
||||
/// </summary>
|
||||
/// <param name="info">Connection info</param>
|
||||
public void DoUpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||
/// <param name="info"></param>
|
||||
public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||
{
|
||||
if (ConnectionService.IsDedicatedAdminConnection(info.ConnectionDetails))
|
||||
{
|
||||
@@ -1028,10 +1044,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
||||
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
|
||||
await PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue).ContinueWith(async _ =>
|
||||
{
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1043,7 +1060,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="scriptInfo"></param>
|
||||
internal void PrepopulateCommonMetadata(
|
||||
internal async Task PrepopulateCommonMetadata(
|
||||
ConnectionInfo info,
|
||||
ScriptParseInfo scriptInfo,
|
||||
ConnectedBindingQueue bindingQueue)
|
||||
@@ -1060,71 +1077,73 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
return;
|
||||
}
|
||||
|
||||
ParseAndBind(scriptFile, info);
|
||||
|
||||
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||
await ParseAndBind(scriptFile, info).ContinueWith(t =>
|
||||
{
|
||||
try
|
||||
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||
{
|
||||
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||
key: scriptInfo.ConnectionKey,
|
||||
bindingTimeout: PrepopulateBindTimeout,
|
||||
waitForLockTimeout: PrepopulateBindTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// parse a simple statement that returns common metadata
|
||||
ParseResult parseResult = Parser.Parse(
|
||||
"select ",
|
||||
bindingContext.ParseOptions);
|
||||
if (bindingContext.IsConnected && bindingContext.Binder != null)
|
||||
try
|
||||
{
|
||||
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||
key: scriptInfo.ConnectionKey,
|
||||
bindingTimeout: PrepopulateBindTimeout,
|
||||
waitForLockTimeout: PrepopulateBindTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
var suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 8,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
||||
|
||||
parseResult = Parser.Parse(
|
||||
"exec ",
|
||||
// parse a simple statement that returns common metadata
|
||||
ParseResult parseResult = Parser.Parse(
|
||||
"select ",
|
||||
bindingContext.ParseOptions);
|
||||
if (bindingContext.IsConnected && bindingContext.Binder != null)
|
||||
{
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
|
||||
parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
// get the completion list from SQL Parser
|
||||
var suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 8,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 6,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
||||
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
parseResult = Parser.Parse(
|
||||
"exec ",
|
||||
bindingContext.ParseOptions);
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
bindingContext.Binder.Bind(
|
||||
parseResults,
|
||||
info.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
suggestions = Resolver.FindCompletions(
|
||||
parseResult, 1, 6,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// this forces lazy evaluation of the suggestion metadata
|
||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Exception in PrepopulateCommonMetadata " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1348,7 +1367,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo);
|
||||
scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// Get token from selected text
|
||||
@@ -1499,7 +1518,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <summary>
|
||||
/// Get function signature help for the current position
|
||||
/// </summary>
|
||||
internal SignatureHelp? GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||
internal async Task<SignatureHelp?> GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||
{
|
||||
Logger.Verbose($"GetSignatureHelp - {scriptFile}");
|
||||
int startLine = textDocumentPosition.Position.Line;
|
||||
@@ -1522,8 +1541,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
// reparse and bind the SQL statement if needed
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
ParseAndBind(scriptFile, connInfo);
|
||||
} else
|
||||
await ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Verbose($"GetSignatureHelp - No reparse needed for {scriptFile}");
|
||||
}
|
||||
@@ -1570,6 +1590,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
Logger.Verbose($"GetSignatureHelp - Got result {queueItem.Result}");
|
||||
return queueItem.GetResultAsT<SignatureHelp>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Exception in GetSignatureHelp " + ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
@@ -1615,7 +1639,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
// reparse and bind the SQL statement if needed
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
ParseAndBind(scriptFile, connInfo);
|
||||
await ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
// if the parse failed then return the default list
|
||||
@@ -1693,14 +1717,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||
/// </summary>
|
||||
/// <param name="scriptFile"></param>
|
||||
internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||
internal async Task<ScriptFileMarker[]> GetSemanticMarkers(ScriptFile scriptFile)
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
ConnectionServiceInstance.TryFindConnection(
|
||||
scriptFile.ClientUri,
|
||||
out connInfo);
|
||||
|
||||
var parseResult = ParseAndBind(scriptFile, connInfo);
|
||||
var parseResult = await ParseAndBind(scriptFile, connInfo);
|
||||
|
||||
// build a list of SQL script file markers from the errors
|
||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||
@@ -1835,10 +1859,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
|
||||
Logger.Verbose("Analyzing script file: " + scriptFile.FilePath);
|
||||
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
|
||||
Logger.Verbose("Analysis complete.");
|
||||
|
||||
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
||||
// Start task asynchronously without blocking main thread - this is by design.
|
||||
// Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
|
||||
// All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
GetSemanticMarkers(scriptFile).ContinueWith(async t =>
|
||||
{
|
||||
var semanticMarkers = t.GetAwaiter().GetResult();
|
||||
Logger.Verbose("Analysis complete.");
|
||||
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
||||
}, CancellationToken.None);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1861,7 +1893,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
else
|
||||
{
|
||||
Logger.Verbose($"Adding ScriptParseInfo for uri {uri}");
|
||||
this.ScriptParseInfoMap.Add(uri, scriptInfo);
|
||||
this.ScriptParseInfoMap.TryAdd(uri, scriptInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1887,7 +1919,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
Logger.Verbose($"ScriptParseInfo for uri {uri} did not exist, creating new one");
|
||||
// create a new script parse info object and initialize with the current settings
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||
this.ScriptParseInfoMap.Add(uri, scriptInfo);
|
||||
this.ScriptParseInfoMap.TryAdd(uri, scriptInfo);
|
||||
return scriptInfo;
|
||||
}
|
||||
else
|
||||
@@ -1902,15 +1934,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
lock (this.parseMapLock)
|
||||
{
|
||||
if (this.ScriptParseInfoMap.ContainsKey(uri))
|
||||
{
|
||||
Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
|
||||
return this.ScriptParseInfoMap.Remove(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
|
||||
return this.ScriptParseInfoMap.TryRemove(uri, out _);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1064,22 +1064,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// what the document events are sent in as.
|
||||
var escapedOwnerUri = Uri.EscapeUriString(request.OwnerUri);
|
||||
// If it is a document selection, we'll retrieve the text from the document
|
||||
ExecuteDocumentSelectionParams docRequest = request as ExecuteDocumentSelectionParams;
|
||||
if (docRequest != null)
|
||||
if (request is ExecuteDocumentSelectionParams docRequest)
|
||||
{
|
||||
return GetSqlTextFromSelectionData(escapedOwnerUri, docRequest.QuerySelection);
|
||||
}
|
||||
|
||||
// If it is a document statement, we'll retrieve the text from the document
|
||||
ExecuteDocumentStatementParams stmtRequest = request as ExecuteDocumentStatementParams;
|
||||
if (stmtRequest != null)
|
||||
if (request is ExecuteDocumentStatementParams stmtRequest)
|
||||
{
|
||||
return GetSqlStatementAtPosition(escapedOwnerUri, stmtRequest.Line, stmtRequest.Column);
|
||||
}
|
||||
|
||||
// If it is an ExecuteStringParams, return the text as is
|
||||
ExecuteStringParams stringRequest = request as ExecuteStringParams;
|
||||
if (stringRequest != null)
|
||||
if (request is ExecuteStringParams stringRequest)
|
||||
{
|
||||
return stringRequest.Query;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
{
|
||||
@@ -35,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
"vscode-notebook-cell"
|
||||
};
|
||||
|
||||
private Dictionary<string, ScriptFile> workspaceFiles = new Dictionary<string, ScriptFile>();
|
||||
private ConcurrentDictionary<string, ScriptFile> workspaceFiles = new ConcurrentDictionary<string, ScriptFile>();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -121,7 +122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
{
|
||||
scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri,streamReader);
|
||||
|
||||
this.workspaceFiles.Add(keyName, scriptFile);
|
||||
this.workspaceFiles.TryAdd(keyName, scriptFile);
|
||||
}
|
||||
|
||||
Logger.Verbose("Opened file on disk: " + resolvedFile.FilePath);
|
||||
@@ -235,7 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
{
|
||||
scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri, initialBuffer);
|
||||
|
||||
this.workspaceFiles.Add(keyName, scriptFile);
|
||||
this.workspaceFiles.TryAdd(keyName, scriptFile);
|
||||
|
||||
Logger.Verbose("Opened file as in-memory buffer: " + resolvedFile.FilePath);
|
||||
}
|
||||
@@ -260,7 +261,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
{
|
||||
Validate.IsNotNull("scriptFile", scriptFile);
|
||||
|
||||
this.workspaceFiles.Remove(scriptFile.Id);
|
||||
this.workspaceFiles.TryRemove(scriptFile.Id, out _);
|
||||
}
|
||||
|
||||
internal string GetBaseFilePath(string filePath)
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
||||
requestContext.Verify(x => x.SendError(It.IsAny<string>(), 0, It.IsAny<string>()), Times.Once);
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||
autoCompleteService.ParseAndBind(result.ScriptFile, result.ConnectionInfo);
|
||||
await autoCompleteService.ParseAndBind(result.ScriptFile, result.ConnectionInfo);
|
||||
scriptInfo.ConnectionKey = autoCompleteService.BindingQueue.AddConnectionContext(result.ConnectionInfo);
|
||||
|
||||
//Invoke auto completion with extension enabled
|
||||
@@ -219,7 +219,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
||||
/// provide signature help.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetSignatureHelpReturnsNotNullIfParseInfoInitialized()
|
||||
public async Task GetSignatureHelpReturnsNotNullIfParseInfoInitialized()
|
||||
{
|
||||
// When we make a connection to a live database
|
||||
Hosting.ServiceHost.SendEventIgnoreExceptions = true;
|
||||
@@ -243,14 +243,14 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
||||
|
||||
// If the SQL has already been parsed
|
||||
var service = CreateLanguageService(result.ScriptFile);
|
||||
service.DoUpdateLanguageServiceOnConnection(result.ConnectionInfo);
|
||||
await service.UpdateLanguageServiceOnConnection(result.ConnectionInfo);
|
||||
|
||||
// We should get back a non-null ScriptParseInfo
|
||||
ScriptParseInfo? parseInfo = service.GetScriptParseInfo(result.ScriptFile.ClientUri);
|
||||
Assert.That(parseInfo, Is.Not.Null, "ScriptParseInfo");
|
||||
|
||||
// And we should get back a non-null SignatureHelp
|
||||
SignatureHelp? signatureHelp = service.GetSignatureHelp(textDocument, result.ScriptFile);
|
||||
SignatureHelp? signatureHelp = await service.GetSignatureHelp(textDocument, result.ScriptFile);
|
||||
Assert.That(signatureHelp, Is.Not.Null, "SignatureHelp");
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
||||
testDb.RunQuery("CREATE TABLE dbo.foo(col1 int)");
|
||||
|
||||
// And refresh the cache
|
||||
await langService.HandleRebuildIntelliSenseNotification(
|
||||
await langService.DoHandleRebuildIntellisenseNotification(
|
||||
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
|
||||
new TestEventContext());
|
||||
|
||||
@@ -446,7 +446,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
||||
testDb.RunQuery(createTableQueries);
|
||||
|
||||
// And refresh the cache
|
||||
await langService.HandleRebuildIntelliSenseNotification(
|
||||
await langService.DoHandleRebuildIntellisenseNotification(
|
||||
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
|
||||
new TestEventContext());
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ GO";
|
||||
scriptFile.Contents = "select * from dbo.func ()";
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||
languageService.ScriptParseInfoMap.Add(scriptFile.ClientUri, scriptInfo);
|
||||
languageService.ScriptParseInfoMap.TryAdd(scriptFile.ClientUri, scriptInfo);
|
||||
|
||||
// Pass in null connection info to force doing a local parse since that hits the BindingQueue timeout
|
||||
// before we want it to (this is testing the timeout trying to fetch the definitions after the parse)
|
||||
@@ -729,9 +729,9 @@ GO";
|
||||
Thread.Sleep(2000);
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||
service.ParseAndBind(scriptFile, connInfo);
|
||||
await service.ParseAndBind(scriptFile, connInfo);
|
||||
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
|
||||
service.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
|
||||
service.ScriptParseInfoMap.TryAdd(OwnerUri, scriptInfo);
|
||||
|
||||
// When I call the language service
|
||||
var objectResult = service.GetDefinition(objectDocument, scriptFile, connInfo);
|
||||
@@ -750,7 +750,7 @@ GO";
|
||||
Cleanup(objectResult.Locations);
|
||||
Cleanup(sysResult.Locations);
|
||||
Cleanup(masterResult.Locations);
|
||||
service.ScriptParseInfoMap.Remove(OwnerUri);
|
||||
service.ScriptParseInfoMap.TryRemove(OwnerUri, out _);
|
||||
connInfo.RemoveAllConnections();
|
||||
}
|
||||
|
||||
@@ -786,9 +786,9 @@ GO";
|
||||
Thread.Sleep(2000);
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||
service.ParseAndBind(scriptFile, connInfo);
|
||||
await service.ParseAndBind(scriptFile, connInfo);
|
||||
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
|
||||
service.ScriptParseInfoMap.Add(TestUri, scriptInfo);
|
||||
service.ScriptParseInfoMap.TryAdd(TestUri, scriptInfo);
|
||||
|
||||
// When I call the language service
|
||||
var fnResult = service.GetDefinition(fnDocument, scriptFile, connInfo);
|
||||
@@ -807,7 +807,7 @@ GO";
|
||||
Cleanup(fnResult.Locations);
|
||||
Cleanup(sysResult.Locations);
|
||||
Cleanup(masterResult.Locations);
|
||||
service.ScriptParseInfoMap.Remove(TestUri);
|
||||
service.ScriptParseInfoMap.TryRemove(TestUri, out _);
|
||||
connInfo.RemoveAllConnections();
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
};
|
||||
|
||||
|
||||
ParseResult parseResult = langService.ParseAndBind(scriptFile, null);
|
||||
ParseResult parseResult = langService.ParseAndBind(scriptFile, null).GetAwaiter().GetResult();
|
||||
ScriptParseInfo scriptParseInfo = langService.GetScriptParseInfo(scriptFile.ClientUri, true);
|
||||
|
||||
return new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
|
||||
|
||||
@@ -26,17 +26,17 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
public void LatestSqlParserIsUsedByDefault()
|
||||
{
|
||||
// This should only parse correctly on SQL server 2016 or newer
|
||||
const string sql2016Text =
|
||||
const string sql2016Text =
|
||||
@"CREATE SECURITY POLICY [FederatedSecurityPolicy]" + "\r\n" +
|
||||
@"ADD FILTER PREDICATE [rls].[fn_securitypredicate]([CustomerId])" + "\r\n" +
|
||||
@"ADD FILTER PREDICATE [rls].[fn_securitypredicate]([CustomerId])" + "\r\n" +
|
||||
@"ON [dbo].[Customer];";
|
||||
|
||||
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
|
||||
// parse
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sql2016Text);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||
|
||||
// verify that no errors are detected
|
||||
Assert.AreEqual(0, fileMarkers.Length);
|
||||
@@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
// parse the sql statement
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sqlWithErrors);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||
|
||||
// verify there are no errors
|
||||
Assert.AreEqual(0, fileMarkers.Length);
|
||||
@@ -78,7 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
// parse sql statement
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sqlWithErrors);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||
|
||||
// verify there is one error
|
||||
Assert.AreEqual(1, fileMarkers.Length);
|
||||
@@ -97,7 +97,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
public void ParseMultilineSqlWithErrors()
|
||||
{
|
||||
// multiline sql with errors
|
||||
const string sqlWithErrors =
|
||||
const string sqlWithErrors =
|
||||
"SELECT *** FROM sys.objects;\n" +
|
||||
"GO\n" +
|
||||
"SELECT *** FROM sys.objects;\n";
|
||||
@@ -108,7 +108,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
// parse sql
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sqlWithErrors);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||
|
||||
// verify there are two errors
|
||||
Assert.AreEqual(2, fileMarkers.Length);
|
||||
@@ -140,29 +140,29 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
scriptFile.SetFileContents(docContent);
|
||||
|
||||
// When requesting SignatureHelp
|
||||
SignatureHelp signatureHelp = service.GetSignatureHelp(TestObjects.GetTestDocPosition(), scriptFile);
|
||||
|
||||
SignatureHelp signatureHelp = service.GetSignatureHelp(TestObjects.GetTestDocPosition(), scriptFile).GetAwaiter().GetResult();
|
||||
|
||||
// Then null is returned as no parse info can be used to find the signature
|
||||
Assert.Null(signatureHelp);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EmptyCompletionListTest()
|
||||
{
|
||||
{
|
||||
Assert.AreEqual(0, AutoCompleteHelper.EmptyCompletionList.Length);
|
||||
}
|
||||
|
||||
internal sealed class TestScriptDocumentInfo : ScriptDocumentInfo
|
||||
{
|
||||
public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
|
||||
public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
|
||||
string tokenText = null)
|
||||
:base(textDocumentPosition, scriptFile, scriptParseInfo)
|
||||
: base(textDocumentPosition, scriptFile, scriptParseInfo)
|
||||
{
|
||||
this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
|
||||
}
|
||||
|
||||
private string tokenText;
|
||||
|
||||
|
||||
public override string TokenText
|
||||
{
|
||||
get
|
||||
@@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
|
||||
[Test]
|
||||
public void GetDefaultCompletionListWithNoMatchesTest()
|
||||
{
|
||||
{
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents("koko wants a bananas");
|
||||
|
||||
@@ -183,10 +183,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
var scriptDocumentInfo = new TestScriptDocumentInfo(
|
||||
new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
|
||||
TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
|
||||
Position = new Position() { Line = 0, Character = 0 }
|
||||
}, scriptFile, scriptInfo);
|
||||
|
||||
|
||||
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user