mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -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.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
|
||||||
using Microsoft.SqlTools.Utility;
|
using Microsoft.SqlTools.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -130,7 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
return context.IsConnected;
|
return context.IsConnected;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -144,7 +143,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
key = "disconnected_binding_context";
|
key = "disconnected_binding_context";
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (this.bindingContextLock)
|
lock (this.bindingContextLock)
|
||||||
{
|
{
|
||||||
if (!this.BindingContextMap.ContainsKey(key))
|
if (!this.BindingContextMap.ContainsKey(key))
|
||||||
@@ -155,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.BindingContextMap[key];
|
return this.BindingContextMap[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<IBindingContext> GetBindingContexts(string keyPrefix)
|
protected IEnumerable<IBindingContext> GetBindingContexts(string keyPrefix)
|
||||||
@@ -269,10 +268,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
this.itemQueuedEvent,
|
this.itemQueuedEvent,
|
||||||
token.WaitHandle
|
token.WaitHandle
|
||||||
};
|
};
|
||||||
|
|
||||||
while (true)
|
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);
|
WaitHandle.WaitAny(waitHandles);
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -283,7 +282,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
// dispatch all pending queue items
|
// dispatch all pending queue items
|
||||||
while (this.HasPendingQueueItems)
|
while (this.HasPendingQueueItems)
|
||||||
{
|
{
|
||||||
QueueItem queueItem = GetNextQueueItem();
|
QueueItem queueItem = GetNextQueueItem();
|
||||||
if (queueItem == null)
|
if (queueItem == null)
|
||||||
{
|
{
|
||||||
@@ -291,7 +290,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
||||||
if (bindingContext == null)
|
if (bindingContext == null)
|
||||||
{
|
{
|
||||||
queueItem.ItemProcessed.Set();
|
queueItem.ItemProcessed.Set();
|
||||||
continue;
|
continue;
|
||||||
@@ -300,140 +299,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
var bindingContextTask = this.BindingContextTasks[bindingContext];
|
var bindingContextTask = this.BindingContextTasks[bindingContext];
|
||||||
|
|
||||||
// Run in the binding context task in case this task has to wait for a previous binding operation
|
// Run in the binding context task in case this task has to wait for a previous binding operation
|
||||||
this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith((task) =>
|
this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith(
|
||||||
{
|
task => DispatchQueueItem(bindingContext, queueItem)
|
||||||
bool lockTaken = false;
|
, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
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);
|
|
||||||
|
|
||||||
// if a queue processing cancellation was requested then exit the loop
|
// if a queue processing cancellation was requested then exit the loop
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
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>
|
/// <summary>
|
||||||
/// Clear queued items
|
/// Clear queued items
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -487,7 +483,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsExceptionOfType(Exception ex, Type t)
|
private bool IsExceptionOfType(Exception ex, Type t)
|
||||||
{
|
{
|
||||||
return ex.GetType() == t || (ex.InnerException != null && ex.InnerException.GetType() == t);
|
return ex.GetType() == t || (ex.InnerException != null && ex.InnerException.GetType() == t);
|
||||||
|
|||||||
@@ -107,18 +107,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
compatibilityLevel: DatabaseCompatibilityLevel.Current,
|
compatibilityLevel: DatabaseCompatibilityLevel.Current,
|
||||||
transactSqlVersion: TransactSqlVersion.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
|
private Lazy<ConcurrentDictionary<string, ScriptParseInfo>> scriptParseInfoMap
|
||||||
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
|
= new Lazy<ConcurrentDictionary<string, ScriptParseInfo>>(() => new());
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new ConcurrentDictionary<string, ICompletionExtension>();
|
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new();
|
||||||
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new ConcurrentDictionary<string, DateTime>();
|
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
|
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Dictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
internal ConcurrentDictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -289,7 +289,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
WorkspaceServiceInstance.RegisterTextDocCloseCallback(HandleDidCloseTextDocumentNotification);
|
WorkspaceServiceInstance.RegisterTextDocCloseCallback(HandleDidCloseTextDocumentNotification);
|
||||||
|
|
||||||
// Register a callback for when a connection is created
|
// Register a callback for when a connection is created
|
||||||
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection);
|
ConnectionServiceInstance.RegisterOnConnectionTask(StartUpdateLanguageServiceOnConnection);
|
||||||
|
|
||||||
// Register a callback for when a connection is closed
|
// Register a callback for when a connection is closed
|
||||||
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
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(
|
internal async Task HandleSignatureHelpRequest(
|
||||||
TextDocumentPosition textDocumentPosition,
|
TextDocumentPosition textDocumentPosition,
|
||||||
RequestContext<SignatureHelp> requestContext)
|
RequestContext<SignatureHelp> requestContext)
|
||||||
@@ -551,18 +534,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
ScriptFile scriptFile = CurrentWorkspace.GetFile(
|
ScriptFile scriptFile = CurrentWorkspace.GetFile(
|
||||||
textDocumentPosition.TextDocument.Uri);
|
textDocumentPosition.TextDocument.Uri);
|
||||||
SignatureHelp help = null;
|
|
||||||
if (scriptFile != null)
|
if (scriptFile != null)
|
||||||
{
|
{
|
||||||
help = GetSignatureHelp(textDocumentPosition, scriptFile);
|
// 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.
|
||||||
if (help != null)
|
// 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
|
||||||
await requestContext.SendResult(help);
|
GetSignatureHelp(textDocumentPosition, scriptFile)
|
||||||
}
|
.ContinueWith(async task =>
|
||||||
else
|
{
|
||||||
{
|
var result = await task;
|
||||||
await requestContext.SendResult(new SignatureHelp());
|
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>
|
/// <summary>
|
||||||
/// Handle the rebuild IntelliSense cache notification
|
/// Handle the rebuild IntelliSense cache notification
|
||||||
/// </summary>
|
/// </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,
|
RebuildIntelliSenseParams rebuildParams,
|
||||||
EventContext eventContext)
|
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
|
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 %)
|
// 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
|
// 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.
|
// 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
|
// Skip closing this file if the file doesn't exist
|
||||||
var scriptFile = this.CurrentWorkspace.GetFile(escapedOwnerUri);
|
var scriptFile = this.CurrentWorkspace.GetFile(escapedOwnerUri);
|
||||||
if (scriptFile == null)
|
if (scriptFile == null)
|
||||||
@@ -710,47 +725,44 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// check that there is an active connection for the current editor
|
// check that there is an active connection for the current editor
|
||||||
if (connInfo != null)
|
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
|
try
|
||||||
ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false);
|
|
||||||
if (scriptInfo != null && scriptInfo.IsConnected &&
|
|
||||||
Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
|
||||||
{
|
{
|
||||||
try
|
this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
|
||||||
{
|
RemoveScriptParseInfo(rebuildParams.OwnerUri);
|
||||||
this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
|
await UpdateLanguageServiceOnConnection(connInfo);
|
||||||
RemoveScriptParseInfo(rebuildParams.OwnerUri);
|
}
|
||||||
UpdateLanguageServiceOnConnection(connInfo).Wait();
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
Logger.Error("Unknown error " + ex.ToString());
|
||||||
{
|
}
|
||||||
Logger.Error("Unknown error " + ex.ToString());
|
finally
|
||||||
}
|
{
|
||||||
finally
|
// Set Metadata Build event to Signal state.
|
||||||
{
|
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||||
// 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 not in the preview window and diagnostics are enabled then run diagnostics
|
||||||
if (!IsPreviewWindow(scriptFile)
|
if (!IsPreviewWindow(scriptFile)
|
||||||
&& CurrentWorkspaceSettings.IsDiagnosticsEnabled)
|
&& CurrentWorkspaceSettings.IsDiagnosticsEnabled)
|
||||||
{
|
{
|
||||||
RunScriptDiagnostics(
|
await RunScriptDiagnostics(
|
||||||
new ScriptFile[] { scriptFile },
|
new ScriptFile[] { scriptFile },
|
||||||
eventContext);
|
eventContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a notification to signal that autocomplete is ready
|
// Send a notification to signal that autocomplete is ready
|
||||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
|
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
|
||||||
});
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
// Send a notification to signal that autocomplete is ready
|
||||||
// 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 = rebuildParams.OwnerUri });
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -887,121 +899,125 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// <param name="filePath"></param>
|
/// <param name="filePath"></param>
|
||||||
/// <param name="sqlText"></param>
|
/// <param name="sqlText"></param>
|
||||||
/// <returns>The ParseResult instance returned from SQL Parser</returns>
|
/// <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}");
|
Logger.Verbose($"ParseAndBind - {scriptFile}");
|
||||||
// get or create the current parse info object
|
// get or create the current parse info object
|
||||||
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientUri, createIfNotExists: true);
|
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientUri, createIfNotExists: true);
|
||||||
|
return Task.Run(() =>
|
||||||
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
|
|
||||||
{
|
{
|
||||||
try
|
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
|
||||||
{
|
{
|
||||||
if (connInfo == null || !parseInfo.IsConnected)
|
try
|
||||||
{
|
{
|
||||||
// parse on separate thread so stack size can be increased
|
if (connInfo == null || !parseInfo.IsConnected)
|
||||||
var parseThread = new Thread(() =>
|
|
||||||
{
|
{
|
||||||
try
|
// parse on separate thread so stack size can be increased
|
||||||
{
|
var parseThread = new Thread(() =>
|
||||||
// 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) =>
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// parse current SQL file contents to retrieve a list of errors
|
||||||
ParseResult parseResult = Parser.IncrementalParse(
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
scriptFile.Contents,
|
scriptFile.Contents,
|
||||||
parseInfo.ParseResult,
|
parseInfo.ParseResult,
|
||||||
bindingContext.ParseOptions);
|
this.DefaultParseOptions);
|
||||||
|
|
||||||
parseInfo.ParseResult = parseResult;
|
parseInfo.ParseResult = parseResult;
|
||||||
|
}
|
||||||
List<ParseResult> parseResults = new List<ParseResult>();
|
catch (Exception e)
|
||||||
parseResults.Add(parseResult);
|
{
|
||||||
if (bindingContext.IsConnected && bindingContext.Binder != null)
|
// 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(
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
parseResults,
|
scriptFile.Contents,
|
||||||
connInfo.ConnectionDetails.DatabaseName,
|
parseInfo.ParseResult,
|
||||||
BindMode.Batch);
|
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
|
Logger.Warning("Binding metadata lock timeout in ParseAndBind");
|
||||||
parseInfo.ParseResult = null;
|
|
||||||
Logger.Error("Unknown exception during parsing " + ex.ToString());
|
|
||||||
}
|
}
|
||||||
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>
|
/// <summary>
|
||||||
/// Starts a Task to update the autocomplete metadata provider when the user connects to a database
|
/// Starts a Task to update the autocomplete metadata provider when the user connects to a database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">Connection info</param>
|
/// <param name="info"></param>
|
||||||
public Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
|
public async 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)
|
|
||||||
{
|
{
|
||||||
if (ConnectionService.IsDedicatedAdminConnection(info.ConnectionDetails))
|
if (ConnectionService.IsDedicatedAdminConnection(info.ConnectionDetails))
|
||||||
{
|
{
|
||||||
@@ -1028,10 +1044,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
Monitor.Exit(scriptInfo.BuildingMetadataLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
await PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue).ContinueWith(async _ =>
|
||||||
|
{
|
||||||
// Send a notification to signal that autocomplete is ready
|
// Send a notification to signal that autocomplete is ready
|
||||||
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
|
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1043,7 +1060,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info"></param>
|
/// <param name="info"></param>
|
||||||
/// <param name="scriptInfo"></param>
|
/// <param name="scriptInfo"></param>
|
||||||
internal void PrepopulateCommonMetadata(
|
internal async Task PrepopulateCommonMetadata(
|
||||||
ConnectionInfo info,
|
ConnectionInfo info,
|
||||||
ScriptParseInfo scriptInfo,
|
ScriptParseInfo scriptInfo,
|
||||||
ConnectedBindingQueue bindingQueue)
|
ConnectedBindingQueue bindingQueue)
|
||||||
@@ -1060,71 +1077,73 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseAndBind(scriptFile, info);
|
await ParseAndBind(scriptFile, info).ContinueWith(t =>
|
||||||
|
|
||||||
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
|
||||||
{
|
{
|
||||||
try
|
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||||
{
|
{
|
||||||
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
try
|
||||||
key: scriptInfo.ConnectionKey,
|
{
|
||||||
bindingTimeout: PrepopulateBindTimeout,
|
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||||
waitForLockTimeout: PrepopulateBindTimeout,
|
key: scriptInfo.ConnectionKey,
|
||||||
bindOperation: (bindingContext, cancelToken) =>
|
bindingTimeout: PrepopulateBindTimeout,
|
||||||
{
|
waitForLockTimeout: PrepopulateBindTimeout,
|
||||||
// parse a simple statement that returns common metadata
|
bindOperation: (bindingContext, cancelToken) =>
|
||||||
ParseResult parseResult = Parser.Parse(
|
|
||||||
"select ",
|
|
||||||
bindingContext.ParseOptions);
|
|
||||||
if (bindingContext.IsConnected && bindingContext.Binder != null)
|
|
||||||
{
|
{
|
||||||
List<ParseResult> parseResults = new List<ParseResult>();
|
// parse a simple statement that returns common metadata
|
||||||
parseResults.Add(parseResult);
|
ParseResult parseResult = Parser.Parse(
|
||||||
bindingContext.Binder.Bind(
|
"select ",
|
||||||
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 ",
|
|
||||||
bindingContext.ParseOptions);
|
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>();
|
// get the completion list from SQL Parser
|
||||||
parseResults.Add(parseResult);
|
var suggestions = Resolver.FindCompletions(
|
||||||
bindingContext.Binder.Bind(
|
parseResult, 1, 8,
|
||||||
parseResults,
|
bindingContext.MetadataDisplayInfoProvider);
|
||||||
info.ConnectionDetails.DatabaseName,
|
|
||||||
BindMode.Batch);
|
|
||||||
|
|
||||||
// get the completion list from SQL Parser
|
// this forces lazy evaluation of the suggestion metadata
|
||||||
suggestions = Resolver.FindCompletions(
|
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
||||||
parseResult, 1, 6,
|
|
||||||
bindingContext.MetadataDisplayInfoProvider);
|
|
||||||
|
|
||||||
// this forces lazy evaluation of the suggestion metadata
|
parseResult = Parser.Parse(
|
||||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
"exec ",
|
||||||
}
|
bindingContext.ParseOptions);
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
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))
|
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||||
{
|
{
|
||||||
scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo);
|
scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get token from selected text
|
// Get token from selected text
|
||||||
@@ -1499,7 +1518,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get function signature help for the current position
|
/// Get function signature help for the current position
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal SignatureHelp? GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
internal async Task<SignatureHelp?> GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||||
{
|
{
|
||||||
Logger.Verbose($"GetSignatureHelp - {scriptFile}");
|
Logger.Verbose($"GetSignatureHelp - {scriptFile}");
|
||||||
int startLine = textDocumentPosition.Position.Line;
|
int startLine = textDocumentPosition.Position.Line;
|
||||||
@@ -1522,8 +1541,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// reparse and bind the SQL statement if needed
|
// reparse and bind the SQL statement if needed
|
||||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||||
{
|
{
|
||||||
ParseAndBind(scriptFile, connInfo);
|
await ParseAndBind(scriptFile, connInfo);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Logger.Verbose($"GetSignatureHelp - No reparse needed for {scriptFile}");
|
Logger.Verbose($"GetSignatureHelp - No reparse needed for {scriptFile}");
|
||||||
}
|
}
|
||||||
@@ -1570,6 +1590,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
Logger.Verbose($"GetSignatureHelp - Got result {queueItem.Result}");
|
Logger.Verbose($"GetSignatureHelp - Got result {queueItem.Result}");
|
||||||
return queueItem.GetResultAsT<SignatureHelp>();
|
return queueItem.GetResultAsT<SignatureHelp>();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error("Exception in GetSignatureHelp " + ex.ToString());
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||||
@@ -1615,7 +1639,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// reparse and bind the SQL statement if needed
|
// reparse and bind the SQL statement if needed
|
||||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||||
{
|
{
|
||||||
ParseAndBind(scriptFile, connInfo);
|
await ParseAndBind(scriptFile, connInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the parse failed then return the default list
|
// 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
|
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scriptFile"></param>
|
/// <param name="scriptFile"></param>
|
||||||
internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
internal async Task<ScriptFileMarker[]> GetSemanticMarkers(ScriptFile scriptFile)
|
||||||
{
|
{
|
||||||
ConnectionInfo connInfo;
|
ConnectionInfo connInfo;
|
||||||
ConnectionServiceInstance.TryFindConnection(
|
ConnectionServiceInstance.TryFindConnection(
|
||||||
scriptFile.ClientUri,
|
scriptFile.ClientUri,
|
||||||
out connInfo);
|
out connInfo);
|
||||||
|
|
||||||
var parseResult = ParseAndBind(scriptFile, connInfo);
|
var parseResult = await ParseAndBind(scriptFile, connInfo);
|
||||||
|
|
||||||
// build a list of SQL script file markers from the errors
|
// build a list of SQL script file markers from the errors
|
||||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||||
@@ -1835,10 +1859,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger.Verbose("Analyzing script file: " + scriptFile.FilePath);
|
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
|
else
|
||||||
{
|
{
|
||||||
Logger.Verbose($"Adding ScriptParseInfo for uri {uri}");
|
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");
|
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
|
// create a new script parse info object and initialize with the current settings
|
||||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||||
this.ScriptParseInfoMap.Add(uri, scriptInfo);
|
this.ScriptParseInfoMap.TryAdd(uri, scriptInfo);
|
||||||
return scriptInfo;
|
return scriptInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1902,15 +1934,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
lock (this.parseMapLock)
|
lock (this.parseMapLock)
|
||||||
{
|
{
|
||||||
if (this.ScriptParseInfoMap.ContainsKey(uri))
|
Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
|
||||||
{
|
return this.ScriptParseInfoMap.TryRemove(uri, out _);
|
||||||
Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
|
|
||||||
return this.ScriptParseInfoMap.Remove(uri);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1064,22 +1064,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
// what the document events are sent in as.
|
// what the document events are sent in as.
|
||||||
var escapedOwnerUri = Uri.EscapeUriString(request.OwnerUri);
|
var escapedOwnerUri = Uri.EscapeUriString(request.OwnerUri);
|
||||||
// If it is a document selection, we'll retrieve the text from the document
|
// If it is a document selection, we'll retrieve the text from the document
|
||||||
ExecuteDocumentSelectionParams docRequest = request as ExecuteDocumentSelectionParams;
|
if (request is ExecuteDocumentSelectionParams docRequest)
|
||||||
if (docRequest != null)
|
|
||||||
{
|
{
|
||||||
return GetSqlTextFromSelectionData(escapedOwnerUri, docRequest.QuerySelection);
|
return GetSqlTextFromSelectionData(escapedOwnerUri, docRequest.QuerySelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is a document statement, we'll retrieve the text from the document
|
// If it is a document statement, we'll retrieve the text from the document
|
||||||
ExecuteDocumentStatementParams stmtRequest = request as ExecuteDocumentStatementParams;
|
if (request is ExecuteDocumentStatementParams stmtRequest)
|
||||||
if (stmtRequest != null)
|
|
||||||
{
|
{
|
||||||
return GetSqlStatementAtPosition(escapedOwnerUri, stmtRequest.Line, stmtRequest.Column);
|
return GetSqlStatementAtPosition(escapedOwnerUri, stmtRequest.Line, stmtRequest.Column);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is an ExecuteStringParams, return the text as is
|
// If it is an ExecuteStringParams, return the text as is
|
||||||
ExecuteStringParams stringRequest = request as ExecuteStringParams;
|
if (request is ExecuteStringParams stringRequest)
|
||||||
if (stringRequest != null)
|
|
||||||
{
|
{
|
||||||
return stringRequest.Query;
|
return stringRequest.Query;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Microsoft.SqlTools.Utility;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||||
{
|
{
|
||||||
@@ -35,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
"vscode-notebook-cell"
|
"vscode-notebook-cell"
|
||||||
};
|
};
|
||||||
|
|
||||||
private Dictionary<string, ScriptFile> workspaceFiles = new Dictionary<string, ScriptFile>();
|
private ConcurrentDictionary<string, ScriptFile> workspaceFiles = new ConcurrentDictionary<string, ScriptFile>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
{
|
{
|
||||||
scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri,streamReader);
|
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);
|
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);
|
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);
|
Logger.Verbose("Opened file as in-memory buffer: " + resolvedFile.FilePath);
|
||||||
}
|
}
|
||||||
@@ -260,7 +261,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
{
|
{
|
||||||
Validate.IsNotNull("scriptFile", scriptFile);
|
Validate.IsNotNull("scriptFile", scriptFile);
|
||||||
|
|
||||||
this.workspaceFiles.Remove(scriptFile.Id);
|
this.workspaceFiles.TryRemove(scriptFile.Id, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string GetBaseFilePath(string filePath)
|
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);
|
requestContext.Verify(x => x.SendError(It.IsAny<string>(), 0, It.IsAny<string>()), Times.Once);
|
||||||
|
|
||||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
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);
|
scriptInfo.ConnectionKey = autoCompleteService.BindingQueue.AddConnectionContext(result.ConnectionInfo);
|
||||||
|
|
||||||
//Invoke auto completion with extension enabled
|
//Invoke auto completion with extension enabled
|
||||||
@@ -219,7 +219,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
|||||||
/// provide signature help.
|
/// provide signature help.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void GetSignatureHelpReturnsNotNullIfParseInfoInitialized()
|
public async Task GetSignatureHelpReturnsNotNullIfParseInfoInitialized()
|
||||||
{
|
{
|
||||||
// When we make a connection to a live database
|
// When we make a connection to a live database
|
||||||
Hosting.ServiceHost.SendEventIgnoreExceptions = true;
|
Hosting.ServiceHost.SendEventIgnoreExceptions = true;
|
||||||
@@ -243,14 +243,14 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
|||||||
|
|
||||||
// If the SQL has already been parsed
|
// If the SQL has already been parsed
|
||||||
var service = CreateLanguageService(result.ScriptFile);
|
var service = CreateLanguageService(result.ScriptFile);
|
||||||
service.DoUpdateLanguageServiceOnConnection(result.ConnectionInfo);
|
await service.UpdateLanguageServiceOnConnection(result.ConnectionInfo);
|
||||||
|
|
||||||
// We should get back a non-null ScriptParseInfo
|
// We should get back a non-null ScriptParseInfo
|
||||||
ScriptParseInfo? parseInfo = service.GetScriptParseInfo(result.ScriptFile.ClientUri);
|
ScriptParseInfo? parseInfo = service.GetScriptParseInfo(result.ScriptFile.ClientUri);
|
||||||
Assert.That(parseInfo, Is.Not.Null, "ScriptParseInfo");
|
Assert.That(parseInfo, Is.Not.Null, "ScriptParseInfo");
|
||||||
|
|
||||||
// And we should get back a non-null SignatureHelp
|
// 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");
|
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)");
|
testDb.RunQuery("CREATE TABLE dbo.foo(col1 int)");
|
||||||
|
|
||||||
// And refresh the cache
|
// And refresh the cache
|
||||||
await langService.HandleRebuildIntelliSenseNotification(
|
await langService.DoHandleRebuildIntellisenseNotification(
|
||||||
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
|
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
|
||||||
new TestEventContext());
|
new TestEventContext());
|
||||||
|
|
||||||
@@ -446,7 +446,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
|
|||||||
testDb.RunQuery(createTableQueries);
|
testDb.RunQuery(createTableQueries);
|
||||||
|
|
||||||
// And refresh the cache
|
// And refresh the cache
|
||||||
await langService.HandleRebuildIntelliSenseNotification(
|
await langService.DoHandleRebuildIntellisenseNotification(
|
||||||
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
|
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
|
||||||
new TestEventContext());
|
new TestEventContext());
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ GO";
|
|||||||
scriptFile.Contents = "select * from dbo.func ()";
|
scriptFile.Contents = "select * from dbo.func ()";
|
||||||
|
|
||||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
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
|
// 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)
|
// 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);
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||||
service.ParseAndBind(scriptFile, connInfo);
|
await service.ParseAndBind(scriptFile, connInfo);
|
||||||
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
|
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
|
||||||
service.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
|
service.ScriptParseInfoMap.TryAdd(OwnerUri, scriptInfo);
|
||||||
|
|
||||||
// When I call the language service
|
// When I call the language service
|
||||||
var objectResult = service.GetDefinition(objectDocument, scriptFile, connInfo);
|
var objectResult = service.GetDefinition(objectDocument, scriptFile, connInfo);
|
||||||
@@ -750,7 +750,7 @@ GO";
|
|||||||
Cleanup(objectResult.Locations);
|
Cleanup(objectResult.Locations);
|
||||||
Cleanup(sysResult.Locations);
|
Cleanup(sysResult.Locations);
|
||||||
Cleanup(masterResult.Locations);
|
Cleanup(masterResult.Locations);
|
||||||
service.ScriptParseInfoMap.Remove(OwnerUri);
|
service.ScriptParseInfoMap.TryRemove(OwnerUri, out _);
|
||||||
connInfo.RemoveAllConnections();
|
connInfo.RemoveAllConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,9 +786,9 @@ GO";
|
|||||||
Thread.Sleep(2000);
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||||
service.ParseAndBind(scriptFile, connInfo);
|
await service.ParseAndBind(scriptFile, connInfo);
|
||||||
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
|
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
|
||||||
service.ScriptParseInfoMap.Add(TestUri, scriptInfo);
|
service.ScriptParseInfoMap.TryAdd(TestUri, scriptInfo);
|
||||||
|
|
||||||
// When I call the language service
|
// When I call the language service
|
||||||
var fnResult = service.GetDefinition(fnDocument, scriptFile, connInfo);
|
var fnResult = service.GetDefinition(fnDocument, scriptFile, connInfo);
|
||||||
@@ -807,7 +807,7 @@ GO";
|
|||||||
Cleanup(fnResult.Locations);
|
Cleanup(fnResult.Locations);
|
||||||
Cleanup(sysResult.Locations);
|
Cleanup(sysResult.Locations);
|
||||||
Cleanup(masterResult.Locations);
|
Cleanup(masterResult.Locations);
|
||||||
service.ScriptParseInfoMap.Remove(TestUri);
|
service.ScriptParseInfoMap.TryRemove(TestUri, out _);
|
||||||
connInfo.RemoveAllConnections();
|
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);
|
ScriptParseInfo scriptParseInfo = langService.GetScriptParseInfo(scriptFile.ClientUri, true);
|
||||||
|
|
||||||
return new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
|
return new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
|
||||||
|
|||||||
@@ -26,17 +26,17 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
public void LatestSqlParserIsUsedByDefault()
|
public void LatestSqlParserIsUsedByDefault()
|
||||||
{
|
{
|
||||||
// This should only parse correctly on SQL server 2016 or newer
|
// This should only parse correctly on SQL server 2016 or newer
|
||||||
const string sql2016Text =
|
const string sql2016Text =
|
||||||
@"CREATE SECURITY POLICY [FederatedSecurityPolicy]" + "\r\n" +
|
@"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];";
|
@"ON [dbo].[Customer];";
|
||||||
|
|
||||||
LanguageService service = TestObjects.GetTestLanguageService();
|
LanguageService service = TestObjects.GetTestLanguageService();
|
||||||
|
|
||||||
// parse
|
// parse
|
||||||
var scriptFile = new ScriptFile();
|
var scriptFile = new ScriptFile();
|
||||||
scriptFile.SetFileContents(sql2016Text);
|
scriptFile.SetFileContents(sql2016Text);
|
||||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||||
|
|
||||||
// verify that no errors are detected
|
// verify that no errors are detected
|
||||||
Assert.AreEqual(0, fileMarkers.Length);
|
Assert.AreEqual(0, fileMarkers.Length);
|
||||||
@@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
// parse the sql statement
|
// parse the sql statement
|
||||||
var scriptFile = new ScriptFile();
|
var scriptFile = new ScriptFile();
|
||||||
scriptFile.SetFileContents(sqlWithErrors);
|
scriptFile.SetFileContents(sqlWithErrors);
|
||||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||||
|
|
||||||
// verify there are no errors
|
// verify there are no errors
|
||||||
Assert.AreEqual(0, fileMarkers.Length);
|
Assert.AreEqual(0, fileMarkers.Length);
|
||||||
@@ -78,7 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
// parse sql statement
|
// parse sql statement
|
||||||
var scriptFile = new ScriptFile();
|
var scriptFile = new ScriptFile();
|
||||||
scriptFile.SetFileContents(sqlWithErrors);
|
scriptFile.SetFileContents(sqlWithErrors);
|
||||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||||
|
|
||||||
// verify there is one error
|
// verify there is one error
|
||||||
Assert.AreEqual(1, fileMarkers.Length);
|
Assert.AreEqual(1, fileMarkers.Length);
|
||||||
@@ -97,7 +97,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
public void ParseMultilineSqlWithErrors()
|
public void ParseMultilineSqlWithErrors()
|
||||||
{
|
{
|
||||||
// multiline sql with errors
|
// multiline sql with errors
|
||||||
const string sqlWithErrors =
|
const string sqlWithErrors =
|
||||||
"SELECT *** FROM sys.objects;\n" +
|
"SELECT *** FROM sys.objects;\n" +
|
||||||
"GO\n" +
|
"GO\n" +
|
||||||
"SELECT *** FROM sys.objects;\n";
|
"SELECT *** FROM sys.objects;\n";
|
||||||
@@ -108,7 +108,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
// parse sql
|
// parse sql
|
||||||
var scriptFile = new ScriptFile();
|
var scriptFile = new ScriptFile();
|
||||||
scriptFile.SetFileContents(sqlWithErrors);
|
scriptFile.SetFileContents(sqlWithErrors);
|
||||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
|
||||||
|
|
||||||
// verify there are two errors
|
// verify there are two errors
|
||||||
Assert.AreEqual(2, fileMarkers.Length);
|
Assert.AreEqual(2, fileMarkers.Length);
|
||||||
@@ -140,29 +140,29 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
scriptFile.SetFileContents(docContent);
|
scriptFile.SetFileContents(docContent);
|
||||||
|
|
||||||
// When requesting SignatureHelp
|
// 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
|
// Then null is returned as no parse info can be used to find the signature
|
||||||
Assert.Null(signatureHelp);
|
Assert.Null(signatureHelp);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void EmptyCompletionListTest()
|
public void EmptyCompletionListTest()
|
||||||
{
|
{
|
||||||
Assert.AreEqual(0, AutoCompleteHelper.EmptyCompletionList.Length);
|
Assert.AreEqual(0, AutoCompleteHelper.EmptyCompletionList.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class TestScriptDocumentInfo : ScriptDocumentInfo
|
internal sealed class TestScriptDocumentInfo : ScriptDocumentInfo
|
||||||
{
|
{
|
||||||
public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
|
public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
|
||||||
string tokenText = null)
|
string tokenText = null)
|
||||||
:base(textDocumentPosition, scriptFile, scriptParseInfo)
|
: base(textDocumentPosition, scriptFile, scriptParseInfo)
|
||||||
{
|
{
|
||||||
this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
|
this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string tokenText;
|
private string tokenText;
|
||||||
|
|
||||||
public override string TokenText
|
public override string TokenText
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void GetDefaultCompletionListWithNoMatchesTest()
|
public void GetDefaultCompletionListWithNoMatchesTest()
|
||||||
{
|
{
|
||||||
var scriptFile = new ScriptFile();
|
var scriptFile = new ScriptFile();
|
||||||
scriptFile.SetFileContents("koko wants a bananas");
|
scriptFile.SetFileContents("koko wants a bananas");
|
||||||
|
|
||||||
@@ -183,10 +183,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
|||||||
var scriptDocumentInfo = new TestScriptDocumentInfo(
|
var scriptDocumentInfo = new TestScriptDocumentInfo(
|
||||||
new TextDocumentPosition()
|
new TextDocumentPosition()
|
||||||
{
|
{
|
||||||
TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
|
TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
|
||||||
Position = new Position() { Line = 0, Character = 0 }
|
Position = new Position() { Line = 0, Character = 0 }
|
||||||
}, scriptFile, scriptInfo);
|
}, scriptFile, scriptInfo);
|
||||||
|
|
||||||
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
|
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user