mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
Add IntelliSense binding queue (#73)
* Initial code for binding queue * Fix-up some of the timeout wait code * Add some initial test code * Add missing test file * Update the binding queue tests * Add more test coverage and refactor a bit. Disable reliabile connection until we can fix it..it's holding an open data reader connection. * A few more test updates * Initial integrate queue with language service. * Hook up the connected binding queue into al binding calls. * Cleanup comments and remove dead code * More missing comments * Fix build break. Reenable ReliabileConnection. * Revert all changes to SqlConnectionFactory * Resolve merge conflicts * Cleanup some more of the timeouts and sync code * Address code review feedback * Address more code review feedback
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,6 +14,7 @@ project.lock.json
|
|||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
*.exe
|
*.exe
|
||||||
|
scratch.txt
|
||||||
|
|
||||||
# mergetool conflict files
|
# mergetool conflict files
|
||||||
*.orig
|
*.orig
|
||||||
@@ -55,6 +56,7 @@ cross/rootfs/
|
|||||||
# MSTest test Results
|
# MSTest test Results
|
||||||
[Tt]est[Rr]esult*/
|
[Tt]est[Rr]esult*/
|
||||||
[Bb]uild[Ll]og.*
|
[Bb]uild[Ll]og.*
|
||||||
|
test*json
|
||||||
|
|
||||||
#NUNIT
|
#NUNIT
|
||||||
*.VisualState.xml
|
*.VisualState.xml
|
||||||
|
|||||||
@@ -47,6 +47,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
|||||||
|
|
||||||
private Dictionary<string, ConnectionInfo> ownerToConnectionMap = new Dictionary<string, ConnectionInfo>();
|
private Dictionary<string, ConnectionInfo> ownerToConnectionMap = new Dictionary<string, ConnectionInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map from script URIs to ConnectionInfo objects
|
||||||
|
/// This is internal for testing access only
|
||||||
|
/// </summary>
|
||||||
|
internal Dictionary<string, ConnectionInfo> OwnerToConnectionMap
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.ownerToConnectionMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service host object for sending/receiving requests/events.
|
/// Service host object for sending/receiving requests/events.
|
||||||
/// Internal for testing purposes.
|
/// Internal for testing purposes.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
@@ -21,6 +22,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AutoCompleteHelper
|
public static class AutoCompleteHelper
|
||||||
{
|
{
|
||||||
|
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
|
||||||
|
|
||||||
private static readonly string[] DefaultCompletionText = new string[]
|
private static readonly string[] DefaultCompletionText = new string[]
|
||||||
{
|
{
|
||||||
"absolute",
|
"absolute",
|
||||||
@@ -421,6 +424,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
"zone"
|
"zone"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current workspace service instance
|
||||||
|
/// Setter for internal testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal static WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (AutoCompleteHelper.workspaceServiceInstance == null)
|
||||||
|
{
|
||||||
|
AutoCompleteHelper.workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
|
||||||
|
}
|
||||||
|
return AutoCompleteHelper.workspaceServiceInstance;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
AutoCompleteHelper.workspaceServiceInstance = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the default completion list from hard-coded list
|
/// Get the default completion list from hard-coded list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -538,11 +561,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info"></param>
|
/// <param name="info"></param>
|
||||||
/// <param name="scriptInfo"></param>
|
/// <param name="scriptInfo"></param>
|
||||||
internal static void PrepopulateCommonMetadata(ConnectionInfo info, ScriptParseInfo scriptInfo)
|
internal static void PrepopulateCommonMetadata(
|
||||||
|
ConnectionInfo info,
|
||||||
|
ScriptParseInfo scriptInfo,
|
||||||
|
ConnectedBindingQueue bindingQueue)
|
||||||
{
|
{
|
||||||
if (scriptInfo.IsConnected)
|
if (scriptInfo.IsConnected)
|
||||||
{
|
{
|
||||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
|
||||||
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
||||||
|
|
||||||
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
|
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
|
||||||
@@ -551,14 +577,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
scriptInfo.BuildingMetadataEvent.Reset();
|
scriptInfo.BuildingMetadataEvent.Reset();
|
||||||
|
|
||||||
|
QueueItem queueItem = bindingQueue.QueueBindingOperation(
|
||||||
|
key: scriptInfo.ConnectionKey,
|
||||||
|
bindOperation: (bindingContext, cancelToken) =>
|
||||||
|
{
|
||||||
// parse a simple statement that returns common metadata
|
// parse a simple statement that returns common metadata
|
||||||
ParseResult parseResult = Parser.Parse(
|
ParseResult parseResult = Parser.Parse(
|
||||||
"select ",
|
"select ",
|
||||||
scriptInfo.ParseOptions);
|
bindingContext.ParseOptions);
|
||||||
|
|
||||||
List<ParseResult> parseResults = new List<ParseResult>();
|
List<ParseResult> parseResults = new List<ParseResult>();
|
||||||
parseResults.Add(parseResult);
|
parseResults.Add(parseResult);
|
||||||
scriptInfo.Binder.Bind(
|
bindingContext.Binder.Bind(
|
||||||
parseResults,
|
parseResults,
|
||||||
info.ConnectionDetails.DatabaseName,
|
info.ConnectionDetails.DatabaseName,
|
||||||
BindMode.Batch);
|
BindMode.Batch);
|
||||||
@@ -566,18 +596,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// get the completion list from SQL Parser
|
// get the completion list from SQL Parser
|
||||||
var suggestions = Resolver.FindCompletions(
|
var suggestions = Resolver.FindCompletions(
|
||||||
parseResult, 1, 8,
|
parseResult, 1, 8,
|
||||||
scriptInfo.MetadataDisplayInfoProvider);
|
bindingContext.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
// this forces lazy evaluation of the suggestion metadata
|
// this forces lazy evaluation of the suggestion metadata
|
||||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
|
||||||
|
|
||||||
parseResult = Parser.Parse(
|
parseResult = Parser.Parse(
|
||||||
"exec ",
|
"exec ",
|
||||||
scriptInfo.ParseOptions);
|
bindingContext.ParseOptions);
|
||||||
|
|
||||||
parseResults = new List<ParseResult>();
|
parseResults = new List<ParseResult>();
|
||||||
parseResults.Add(parseResult);
|
parseResults.Add(parseResult);
|
||||||
scriptInfo.Binder.Bind(
|
bindingContext.Binder.Bind(
|
||||||
parseResults,
|
parseResults,
|
||||||
info.ConnectionDetails.DatabaseName,
|
info.ConnectionDetails.DatabaseName,
|
||||||
BindMode.Batch);
|
BindMode.Batch);
|
||||||
@@ -585,10 +615,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// get the completion list from SQL Parser
|
// get the completion list from SQL Parser
|
||||||
suggestions = Resolver.FindCompletions(
|
suggestions = Resolver.FindCompletions(
|
||||||
parseResult, 1, 6,
|
parseResult, 1, 6,
|
||||||
scriptInfo.MetadataDisplayInfoProvider);
|
bindingContext.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
// this forces lazy evaluation of the suggestion metadata
|
// this forces lazy evaluation of the suggestion metadata
|
||||||
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
|
||||||
|
return Task.FromResult(null as object);
|
||||||
|
});
|
||||||
|
|
||||||
|
queueItem.ItemProcessed.WaitOne();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -600,5 +634,53 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a SQL Parser QuickInfo object into a VS Code Hover object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="quickInfo"></param>
|
||||||
|
/// <param name="row"></param>
|
||||||
|
/// <param name="startColumn"></param>
|
||||||
|
/// <param name="endColumn"></param>
|
||||||
|
internal static Hover ConvertQuickInfoToHover(
|
||||||
|
Babel.CodeObjectQuickInfo quickInfo,
|
||||||
|
int row,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn)
|
||||||
|
{
|
||||||
|
// convert from the parser format to the VS Code wire format
|
||||||
|
var markedStrings = new MarkedString[1];
|
||||||
|
if (quickInfo != null)
|
||||||
|
{
|
||||||
|
markedStrings[0] = new MarkedString()
|
||||||
|
{
|
||||||
|
Language = "SQL",
|
||||||
|
Value = quickInfo.Text
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Hover()
|
||||||
|
{
|
||||||
|
Contents = markedStrings,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = startColumn
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for the Binding Queue
|
||||||
|
/// </summary>
|
||||||
|
public class BindingQueue<T> where T : IBindingContext, new()
|
||||||
|
{
|
||||||
|
private CancellationTokenSource processQueueCancelToken = new CancellationTokenSource();
|
||||||
|
|
||||||
|
private ManualResetEvent itemQueuedEvent = new ManualResetEvent(initialState: false);
|
||||||
|
|
||||||
|
private object bindingQueueLock = new object();
|
||||||
|
|
||||||
|
private LinkedList<QueueItem> bindingQueue = new LinkedList<QueueItem>();
|
||||||
|
|
||||||
|
private object bindingContextLock = new object();
|
||||||
|
|
||||||
|
private Task queueProcessorTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map from context keys to binding context instances
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal Dictionary<string, IBindingContext> BindingContextMap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for a binding queue instance
|
||||||
|
/// </summary>
|
||||||
|
public BindingQueue()
|
||||||
|
{
|
||||||
|
this.BindingContextMap = new Dictionary<string, IBindingContext>();
|
||||||
|
|
||||||
|
this.queueProcessorTask = StartQueueProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the binding queue by sending cancellation request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeout"></param>
|
||||||
|
public bool StopQueueProcessor(int timeout)
|
||||||
|
{
|
||||||
|
this.processQueueCancelToken.Cancel();
|
||||||
|
return this.queueProcessorTask.Wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a binding request item
|
||||||
|
/// </summary>
|
||||||
|
public QueueItem QueueBindingOperation(
|
||||||
|
string key,
|
||||||
|
Func<IBindingContext, CancellationToken, Task<object>> bindOperation,
|
||||||
|
Func<IBindingContext, Task<object>> timeoutOperation = null,
|
||||||
|
int? bindingTimeout = null)
|
||||||
|
{
|
||||||
|
// don't add null operations to the binding queue
|
||||||
|
if (bindOperation == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueItem queueItem = new QueueItem()
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
BindOperation = bindOperation,
|
||||||
|
TimeoutOperation = timeoutOperation,
|
||||||
|
BindingTimeout = bindingTimeout
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (this.bindingQueueLock)
|
||||||
|
{
|
||||||
|
this.bindingQueue.AddLast(queueItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemQueuedEvent.Set();
|
||||||
|
|
||||||
|
return queueItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or creates a binding context for the provided context key
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
protected IBindingContext GetOrCreateBindingContext(string key)
|
||||||
|
{
|
||||||
|
// use a default binding context for disconnected requests
|
||||||
|
if (string.IsNullOrWhiteSpace(key))
|
||||||
|
{
|
||||||
|
key = "disconnected_binding_context";
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (this.bindingContextLock)
|
||||||
|
{
|
||||||
|
if (!this.BindingContextMap.ContainsKey(key))
|
||||||
|
{
|
||||||
|
this.BindingContextMap.Add(key, new T());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.BindingContextMap[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasPendingQueueItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.bindingQueueLock)
|
||||||
|
{
|
||||||
|
return this.bindingQueue.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next pending queue item
|
||||||
|
/// </summary>
|
||||||
|
private QueueItem GetNextQueueItem()
|
||||||
|
{
|
||||||
|
lock (this.bindingQueueLock)
|
||||||
|
{
|
||||||
|
if (this.bindingQueue.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueItem queueItem = this.bindingQueue.First.Value;
|
||||||
|
this.bindingQueue.RemoveFirst();
|
||||||
|
return queueItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the queue processing thread
|
||||||
|
/// </summary>
|
||||||
|
private Task StartQueueProcessor()
|
||||||
|
{
|
||||||
|
return Task.Factory.StartNew(
|
||||||
|
ProcessQueue,
|
||||||
|
this.processQueueCancelToken.Token,
|
||||||
|
TaskCreationOptions.LongRunning,
|
||||||
|
TaskScheduler.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The core queue processing method
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
private void ProcessQueue()
|
||||||
|
{
|
||||||
|
CancellationToken token = this.processQueueCancelToken.Token;
|
||||||
|
WaitHandle[] waitHandles = new WaitHandle[2]
|
||||||
|
{
|
||||||
|
this.itemQueuedEvent,
|
||||||
|
token.WaitHandle
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// wait for with an item to be queued or the a cancellation request
|
||||||
|
WaitHandle.WaitAny(waitHandles);
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// dispatch all pending queue items
|
||||||
|
while (this.HasPendingQueueItems)
|
||||||
|
{
|
||||||
|
QueueItem queueItem = GetNextQueueItem();
|
||||||
|
if (queueItem == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
||||||
|
if (bindingContext == null)
|
||||||
|
{
|
||||||
|
queueItem.ItemProcessed.Set();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.BindingLocked.WaitOne(bindTimeout))
|
||||||
|
{
|
||||||
|
queueItem.ResultsTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
var timeoutTask = queueItem.TimeoutOperation(bindingContext);
|
||||||
|
timeoutTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
|
||||||
|
return timeoutTask.Result;
|
||||||
|
});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the binding operation
|
||||||
|
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||||
|
queueItem.ResultsTask = queueItem.BindOperation(
|
||||||
|
bindingContext,
|
||||||
|
cancelToken.Token);
|
||||||
|
|
||||||
|
// set notification events once the binding operation task completes
|
||||||
|
queueItem.ResultsTask.ContinueWith((obj) =>
|
||||||
|
{
|
||||||
|
queueItem.ItemProcessed.Set();
|
||||||
|
bindingContext.BindingLocked.Set();
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if the binding tasks completed within the binding timeout
|
||||||
|
if (!queueItem.ResultsTask.Wait(bindTimeout))
|
||||||
|
{
|
||||||
|
// if the task didn't complete then call the timeout callback
|
||||||
|
if (queueItem.TimeoutOperation != null)
|
||||||
|
{
|
||||||
|
cancelToken.Cancel();
|
||||||
|
queueItem.ResultsTask = queueItem.TimeoutOperation(bindingContext);
|
||||||
|
queueItem.ResultsTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a queue processing cancellation was requested then exit the loop
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// reset the item queued event since we've processed all the pending items
|
||||||
|
this.itemQueuedEvent.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for the binding context for connected sessions
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectedBindingContext : IBindingContext
|
||||||
|
{
|
||||||
|
private ParseOptions parseOptions;
|
||||||
|
|
||||||
|
private ServerConnection serverConnection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connected binding context constructor
|
||||||
|
/// </summary>
|
||||||
|
public ConnectedBindingContext()
|
||||||
|
{
|
||||||
|
this.BindingLocked = new ManualResetEvent(initialState: true);
|
||||||
|
this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
|
||||||
|
this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating if the binder is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binding server connection
|
||||||
|
/// </summary>
|
||||||
|
public ServerConnection ServerConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.serverConnection;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.serverConnection = value;
|
||||||
|
|
||||||
|
// reset the parse options so the get recreated for the current connection
|
||||||
|
this.parseOptions = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the metadata display info provider
|
||||||
|
/// </summary>
|
||||||
|
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SMO metadata provider
|
||||||
|
/// </summary>
|
||||||
|
public SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binder
|
||||||
|
/// </summary>
|
||||||
|
public IBinder Binder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an event to signal if a binding operation is in progress
|
||||||
|
/// </summary>
|
||||||
|
public ManualResetEvent BindingLocked { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binding operation timeout in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
public int BindingTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Language Service ServerVersion
|
||||||
|
/// </summary>
|
||||||
|
public ServerVersion ServerVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.ServerConnection != null
|
||||||
|
? this.ServerConnection.ServerVersion
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current DataEngineType
|
||||||
|
/// </summary>
|
||||||
|
public DatabaseEngineType DatabaseEngineType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.ServerConnection != null
|
||||||
|
? this.ServerConnection.DatabaseEngineType
|
||||||
|
: DatabaseEngineType.Standalone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current connections TransactSqlVersion
|
||||||
|
/// </summary>
|
||||||
|
public TransactSqlVersion TransactSqlVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.IsConnected
|
||||||
|
? GetTransactSqlVersion(this.ServerVersion)
|
||||||
|
: TransactSqlVersion.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current DatabaseCompatibilityLevel
|
||||||
|
/// </summary>
|
||||||
|
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.IsConnected
|
||||||
|
? GetDatabaseCompatibilityLevel(this.ServerVersion)
|
||||||
|
: DatabaseCompatibilityLevel.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current ParseOptions
|
||||||
|
/// </summary>
|
||||||
|
public ParseOptions ParseOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.parseOptions == null)
|
||||||
|
{
|
||||||
|
this.parseOptions = new ParseOptions(
|
||||||
|
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||||
|
isQuotedIdentifierSet: true,
|
||||||
|
compatibilityLevel: DatabaseCompatibilityLevel,
|
||||||
|
transactSqlVersion: TransactSqlVersion);
|
||||||
|
}
|
||||||
|
return this.parseOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the database compatibility level from a server version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverVersion"></param>
|
||||||
|
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
|
||||||
|
{
|
||||||
|
int versionMajor = Math.Max(serverVersion.Major, 8);
|
||||||
|
|
||||||
|
switch (versionMajor)
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
return DatabaseCompatibilityLevel.Version80;
|
||||||
|
case 9:
|
||||||
|
return DatabaseCompatibilityLevel.Version90;
|
||||||
|
case 10:
|
||||||
|
return DatabaseCompatibilityLevel.Version100;
|
||||||
|
case 11:
|
||||||
|
return DatabaseCompatibilityLevel.Version110;
|
||||||
|
case 12:
|
||||||
|
return DatabaseCompatibilityLevel.Version120;
|
||||||
|
case 13:
|
||||||
|
return DatabaseCompatibilityLevel.Version130;
|
||||||
|
default:
|
||||||
|
return DatabaseCompatibilityLevel.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the transaction sql version from a server version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverVersion"></param>
|
||||||
|
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
|
||||||
|
{
|
||||||
|
int versionMajor = Math.Max(serverVersion.Major, 9);
|
||||||
|
|
||||||
|
switch (versionMajor)
|
||||||
|
{
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
// In case of 10.0 we still use Version 10.5 as it is the closest available.
|
||||||
|
return TransactSqlVersion.Version105;
|
||||||
|
case 11:
|
||||||
|
return TransactSqlVersion.Version110;
|
||||||
|
case 12:
|
||||||
|
return TransactSqlVersion.Version120;
|
||||||
|
case 13:
|
||||||
|
return TransactSqlVersion.Version130;
|
||||||
|
default:
|
||||||
|
return TransactSqlVersion.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ConnectedBindingQueue class for processing online binding requests
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectedBindingQueue : BindingQueue<ConnectedBindingContext>
|
||||||
|
{
|
||||||
|
internal const int DefaultBindingTimeout = 60000;
|
||||||
|
|
||||||
|
internal const int DefaultMinimumConnectionTimeout = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current settings
|
||||||
|
/// </summary>
|
||||||
|
internal SqlToolsSettings CurrentSettings
|
||||||
|
{
|
||||||
|
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a unique key based on the ConnectionInfo object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connInfo"></param>
|
||||||
|
private string GetConnectionContextKey(ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
ConnectionDetails details = connInfo.ConnectionDetails;
|
||||||
|
return string.Format("{0}_{1}_{2}_{3}",
|
||||||
|
details.ServerName ?? "NULL",
|
||||||
|
details.DatabaseName ?? "NULL",
|
||||||
|
details.UserName ?? "NULL",
|
||||||
|
details.AuthenticationType ?? "NULL"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use a ConnectionInfo item to create a connected binding context
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connInfo"></param>
|
||||||
|
public virtual string AddConnectionContext(ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
if (connInfo == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup the current binding context
|
||||||
|
string connectionKey = GetConnectionContextKey(connInfo);
|
||||||
|
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// increase the connection timeout to at least 30 seconds and and build connection string
|
||||||
|
// enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections
|
||||||
|
int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout;
|
||||||
|
bool? originalPersistSecurityInfo = connInfo.ConnectionDetails.PersistSecurityInfo;
|
||||||
|
connInfo.ConnectionDetails.ConnectTimeout = Math.Max(DefaultMinimumConnectionTimeout, originalTimeout ?? 0);
|
||||||
|
connInfo.ConnectionDetails.PersistSecurityInfo = true;
|
||||||
|
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
|
||||||
|
connInfo.ConnectionDetails.ConnectTimeout = originalTimeout;
|
||||||
|
connInfo.ConnectionDetails.PersistSecurityInfo = originalPersistSecurityInfo;
|
||||||
|
|
||||||
|
// open a dedicated binding server connection
|
||||||
|
SqlConnection sqlConn = new SqlConnection(connectionString);
|
||||||
|
if (sqlConn != null)
|
||||||
|
{
|
||||||
|
sqlConn.Open();
|
||||||
|
|
||||||
|
// populate the binding context to work with the SMO metadata provider
|
||||||
|
ServerConnection serverConn = new ServerConnection(sqlConn);
|
||||||
|
bindingContext.SmoMetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
|
||||||
|
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
|
bindingContext.MetadataDisplayInfoProvider.BuiltInCasing =
|
||||||
|
this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value
|
||||||
|
? CasingStyle.Lowercase : CasingStyle.Uppercase;
|
||||||
|
bindingContext.Binder = BinderProvider.CreateBinder(bindingContext.SmoMetadataProvider);
|
||||||
|
bindingContext.ServerConnection = serverConn;
|
||||||
|
bindingContext.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
|
||||||
|
bindingContext.IsConnected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
bindingContext.IsConnected = false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bindingContext.BindingLocked.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context used for binding requests
|
||||||
|
/// </summary>
|
||||||
|
public interface IBindingContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag indicating if the context is connected
|
||||||
|
/// </summary>
|
||||||
|
bool IsConnected { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binding server connection
|
||||||
|
/// </summary>
|
||||||
|
ServerConnection ServerConnection { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the metadata display info provider
|
||||||
|
/// </summary>
|
||||||
|
MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SMO metadata provider
|
||||||
|
/// </summary>
|
||||||
|
SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binder
|
||||||
|
/// </summary>
|
||||||
|
IBinder Binder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an event to signal if a binding operation is in progress
|
||||||
|
/// </summary>
|
||||||
|
ManualResetEvent BindingLocked { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binding operation timeout in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
int BindingTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current connection parse options
|
||||||
|
/// </summary>
|
||||||
|
ParseOptions ParseOptions { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current connection server version
|
||||||
|
/// </summary>
|
||||||
|
ServerVersion ServerVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the database engine type
|
||||||
|
/// </summary>
|
||||||
|
DatabaseEngineType DatabaseEngineType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the T-SQL version
|
||||||
|
/// </summary>
|
||||||
|
TransactSqlVersion TransactSqlVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the database compatibility level
|
||||||
|
/// </summary>
|
||||||
|
DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,11 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.SqlServer.Management.Common;
|
using Microsoft.SqlServer.Management.Common;
|
||||||
using Microsoft.SqlServer.Management.SqlParser;
|
using Microsoft.SqlServer.Management.SqlParser;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
@@ -38,22 +37,50 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
internal const int DiagnosticParseDelay = 750;
|
internal const int DiagnosticParseDelay = 750;
|
||||||
|
|
||||||
|
internal const int HoverTimeout = 3000;
|
||||||
|
|
||||||
internal const int FindCompletionsTimeout = 3000;
|
internal const int FindCompletionsTimeout = 3000;
|
||||||
|
|
||||||
internal const int FindCompletionStartTimeout = 50;
|
internal const int FindCompletionStartTimeout = 50;
|
||||||
|
|
||||||
internal const int OnConnectionWaitTimeout = 300000;
|
internal const int OnConnectionWaitTimeout = 300000;
|
||||||
|
|
||||||
|
private static ConnectionService connectionService = null;
|
||||||
|
|
||||||
|
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
|
||||||
|
|
||||||
private object parseMapLock = new object();
|
private object parseMapLock = new object();
|
||||||
|
|
||||||
private ScriptParseInfo currentCompletionParseInfo;
|
private ScriptParseInfo currentCompletionParseInfo;
|
||||||
|
|
||||||
private ConnectionService connectionService = null;
|
private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||||
|
|
||||||
|
private ParseOptions defaultParseOptions = new ParseOptions(
|
||||||
|
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||||
|
isQuotedIdentifierSet: true,
|
||||||
|
compatibilityLevel: DatabaseCompatibilityLevel.Current,
|
||||||
|
transactSqlVersion: TransactSqlVersion.Current);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binding queue instance
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal ConnectedBindingQueue BindingQueue
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.bindingQueue;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.bindingQueue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal for testing purposes only
|
/// Internal for testing purposes only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ConnectionService ConnectionServiceInstance
|
internal static ConnectionService ConnectionServiceInstance
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -96,6 +123,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
get { return instance.Value; }
|
get { return instance.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ParseOptions DefaultParseOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.defaultParseOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default, parameterless constructor.
|
/// Default, parameterless constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -109,14 +144,40 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
private static CancellationTokenSource ExistingRequestCancellation { get; set; }
|
private static CancellationTokenSource ExistingRequestCancellation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current settings
|
||||||
|
/// </summary>
|
||||||
internal SqlToolsSettings CurrentSettings
|
internal SqlToolsSettings CurrentSettings
|
||||||
{
|
{
|
||||||
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
|
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current workspace service instance
|
||||||
|
/// Setter for internal testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal static WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (LanguageService.workspaceServiceInstance == null)
|
||||||
|
{
|
||||||
|
LanguageService.workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
|
||||||
|
}
|
||||||
|
return LanguageService.workspaceServiceInstance;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
LanguageService.workspaceServiceInstance = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current workspace instance
|
||||||
|
/// </summary>
|
||||||
internal Workspace.Workspace CurrentWorkspace
|
internal Workspace.Workspace CurrentWorkspace
|
||||||
{
|
{
|
||||||
get { return WorkspaceService<SqlToolsSettings>.Instance.Workspace; }
|
get { return LanguageService.WorkspaceServiceInstance.Workspace; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -181,7 +242,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// <param name="textDocumentPosition"></param>
|
/// <param name="textDocumentPosition"></param>
|
||||||
/// <param name="requestContext"></param>
|
/// <param name="requestContext"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static async Task HandleCompletionRequest(
|
internal static async Task HandleCompletionRequest(
|
||||||
TextDocumentPosition textDocumentPosition,
|
TextDocumentPosition textDocumentPosition,
|
||||||
RequestContext<CompletionItem[]> requestContext)
|
RequestContext<CompletionItem[]> requestContext)
|
||||||
{
|
{
|
||||||
@@ -193,11 +254,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// get the current list of completion items and return to client
|
// get the current list of completion items and return to client
|
||||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(
|
||||||
textDocumentPosition.TextDocument.Uri);
|
textDocumentPosition.TextDocument.Uri);
|
||||||
|
|
||||||
ConnectionInfo connInfo;
|
ConnectionInfo connInfo;
|
||||||
ConnectionService.Instance.TryFindConnection(
|
LanguageService.ConnectionServiceInstance.TryFindConnection(
|
||||||
scriptFile.ClientFilePath,
|
scriptFile.ClientFilePath,
|
||||||
out connInfo);
|
out connInfo);
|
||||||
|
|
||||||
@@ -339,12 +400,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// update the current settings to reflect any changes
|
// update the current settings to reflect any changes
|
||||||
CurrentSettings.Update(newSettings);
|
CurrentSettings.Update(newSettings);
|
||||||
|
|
||||||
// update the script parse info objects if the settings have changed
|
|
||||||
foreach (var scriptInfo in this.ScriptParseInfoMap.Values)
|
|
||||||
{
|
|
||||||
scriptInfo.OnSettingsChanged(newSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if script analysis settings have changed we need to clear the current diagnostic markers
|
// if script analysis settings have changed we need to clear the current diagnostic markers
|
||||||
if (oldEnableIntelliSense != newSettings.SqlTools.EnableIntellisense
|
if (oldEnableIntelliSense != newSettings.SqlTools.EnableIntellisense
|
||||||
|| oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableDiagnostics)
|
|| oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableDiagnostics)
|
||||||
@@ -391,7 +446,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath"></param>
|
/// <param name="filePath"></param>
|
||||||
/// <param name="sqlText"></param>
|
/// <param name="sqlText"></param>
|
||||||
/// <returns></returns>
|
/// <returns>The ParseResult instance returned from SQL Parser</returns>
|
||||||
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||||
{
|
{
|
||||||
// get or create the current parse info object
|
// get or create the current parse info object
|
||||||
@@ -403,21 +458,34 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
parseInfo.BuildingMetadataEvent.Reset();
|
parseInfo.BuildingMetadataEvent.Reset();
|
||||||
|
|
||||||
|
if (connInfo == null || !parseInfo.IsConnected)
|
||||||
|
{
|
||||||
// parse current SQL file contents to retrieve a list of errors
|
// 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,
|
||||||
parseInfo.ParseOptions);
|
this.DefaultParseOptions);
|
||||||
|
|
||||||
parseInfo.ParseResult = parseResult;
|
parseInfo.ParseResult = parseResult;
|
||||||
|
}
|
||||||
if (connInfo != null && parseInfo.IsConnected)
|
else
|
||||||
|
{
|
||||||
|
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||||
|
key: parseInfo.ConnectionKey,
|
||||||
|
bindOperation: (bindingContext, cancelToken) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
|
scriptFile.Contents,
|
||||||
|
parseInfo.ParseResult,
|
||||||
|
bindingContext.ParseOptions);
|
||||||
|
|
||||||
|
parseInfo.ParseResult = parseResult;
|
||||||
|
|
||||||
List<ParseResult> parseResults = new List<ParseResult>();
|
List<ParseResult> parseResults = new List<ParseResult>();
|
||||||
parseResults.Add(parseResult);
|
parseResults.Add(parseResult);
|
||||||
parseInfo.Binder.Bind(
|
bindingContext.Binder.Bind(
|
||||||
parseResults,
|
parseResults,
|
||||||
connInfo.ConnectionDetails.DatabaseName,
|
connInfo.ConnectionDetails.DatabaseName,
|
||||||
BindMode.Batch);
|
BindMode.Batch);
|
||||||
@@ -430,7 +498,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(null as object);
|
||||||
|
});
|
||||||
|
|
||||||
|
queueItem.ItemProcessed.WaitOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// reset the parse result to do a full parse next time
|
||||||
|
parseInfo.ParseResult = null;
|
||||||
|
Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -455,20 +538,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
scriptInfo.BuildingMetadataEvent.Reset();
|
scriptInfo.BuildingMetadataEvent.Reset();
|
||||||
|
scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(info);
|
||||||
ReliableSqlConnection sqlConn = info.SqlConnection as ReliableSqlConnection;
|
|
||||||
if (sqlConn != null)
|
|
||||||
{
|
|
||||||
ServerConnection serverConn = new ServerConnection(sqlConn.GetUnderlyingConnection());
|
|
||||||
scriptInfo.MetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
|
|
||||||
scriptInfo.Binder = BinderProvider.CreateBinder(scriptInfo.MetadataProvider);
|
|
||||||
scriptInfo.ServerConnection = serverConn;
|
|
||||||
scriptInfo.IsConnected = true;
|
scriptInfo.IsConnected = true;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Unknown error in OnConnection " + ex.ToString());
|
||||||
scriptInfo.IsConnected = false;
|
scriptInfo.IsConnected = false;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -479,8 +555,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate SMO metadata provider with most common info
|
AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
||||||
AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,42 +630,31 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
scriptParseInfo.BuildingMetadataEvent.Reset();
|
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||||
|
key: scriptParseInfo.ConnectionKey,
|
||||||
|
bindingTimeout: LanguageService.HoverTimeout,
|
||||||
|
bindOperation: (bindingContext, cancelToken) =>
|
||||||
{
|
{
|
||||||
// get the current quick info text
|
// get the current quick info text
|
||||||
Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo(
|
Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo(
|
||||||
scriptParseInfo.ParseResult,
|
scriptParseInfo.ParseResult,
|
||||||
startLine + 1,
|
startLine + 1,
|
||||||
endColumn + 1,
|
endColumn + 1,
|
||||||
scriptParseInfo.MetadataDisplayInfoProvider);
|
bindingContext.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
// convert from the parser format to the VS Code wire format
|
// convert from the parser format to the VS Code wire format
|
||||||
var markedStrings = new MarkedString[1];
|
return Task.FromResult(
|
||||||
if (quickInfo != null)
|
AutoCompleteHelper.ConvertQuickInfoToHover(
|
||||||
{
|
quickInfo,
|
||||||
markedStrings[0] = new MarkedString()
|
startLine,
|
||||||
{
|
startColumn,
|
||||||
Language = "SQL",
|
endColumn
|
||||||
Value = quickInfo.Text
|
) as object);
|
||||||
};
|
});
|
||||||
|
|
||||||
return new Hover()
|
queueItem.ItemProcessed.WaitOne();
|
||||||
{
|
return queueItem.GetResultAsT<Hover>();
|
||||||
Contents = markedStrings,
|
|
||||||
Range = new Range
|
|
||||||
{
|
|
||||||
Start = new Position
|
|
||||||
{
|
|
||||||
Line = startLine,
|
|
||||||
Character = startColumn
|
|
||||||
},
|
|
||||||
End = new Position
|
|
||||||
{
|
|
||||||
Line = startLine,
|
|
||||||
Character = endColumn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -647,7 +711,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
|
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
|
||||||
{
|
{
|
||||||
scriptParseInfo.BuildingMetadataEvent.Reset();
|
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||||
Task<CompletionItem[]> findCompletionsTask = Task.Run(() => {
|
|
||||||
|
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||||
|
key: scriptParseInfo.ConnectionKey,
|
||||||
|
bindOperation: (bindingContext, cancelToken) =>
|
||||||
|
{
|
||||||
|
CompletionItem[] completions = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// get the completion list from SQL Parser
|
// get the completion list from SQL Parser
|
||||||
@@ -655,13 +724,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
scriptParseInfo.ParseResult,
|
scriptParseInfo.ParseResult,
|
||||||
textDocumentPosition.Position.Line + 1,
|
textDocumentPosition.Position.Line + 1,
|
||||||
textDocumentPosition.Position.Character + 1,
|
textDocumentPosition.Position.Character + 1,
|
||||||
scriptParseInfo.MetadataDisplayInfoProvider);
|
bindingContext.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
// cache the current script parse info object to resolve completions later
|
// cache the current script parse info object to resolve completions later
|
||||||
this.currentCompletionParseInfo = scriptParseInfo;
|
this.currentCompletionParseInfo = scriptParseInfo;
|
||||||
|
|
||||||
// convert the suggestion list to the VS Code format
|
// convert the suggestion list to the VS Code format
|
||||||
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
completions = AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||||
scriptParseInfo.CurrentSuggestions,
|
scriptParseInfo.CurrentSuggestions,
|
||||||
startLine,
|
startLine,
|
||||||
startColumn,
|
startColumn,
|
||||||
@@ -671,14 +740,20 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
scriptParseInfo.BuildingMetadataEvent.Set();
|
scriptParseInfo.BuildingMetadataEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(completions as object);
|
||||||
|
},
|
||||||
|
timeoutOperation: (bindingContext) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult(
|
||||||
|
AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions) as object);
|
||||||
});
|
});
|
||||||
|
|
||||||
findCompletionsTask.Wait(LanguageService.FindCompletionsTimeout);
|
queueItem.ItemProcessed.WaitOne();
|
||||||
if (findCompletionsTask.IsCompleted
|
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
|
||||||
&& findCompletionsTask.Result != null
|
if (completionItems != null && completionItems.Length > 0)
|
||||||
&& findCompletionsTask.Result.Length > 0)
|
|
||||||
{
|
{
|
||||||
return findCompletionsTask.Result;
|
return completionItems;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,7 +904,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void AddOrUpdateScriptParseInfo(string uri, ScriptParseInfo scriptInfo)
|
/// <summary>
|
||||||
|
/// Adds a new or updates an existing script parse info instance in local cache
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri"></param>
|
||||||
|
/// <param name="scriptInfo"></param>
|
||||||
|
internal void AddOrUpdateScriptParseInfo(string uri, ScriptParseInfo scriptInfo)
|
||||||
{
|
{
|
||||||
lock (this.parseMapLock)
|
lock (this.parseMapLock)
|
||||||
{
|
{
|
||||||
@@ -845,7 +925,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScriptParseInfo GetScriptParseInfo(string uri, bool createIfNotExists = false)
|
/// <summary>
|
||||||
|
/// Gets a script parse info object for a file from the local cache
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri"></param>
|
||||||
|
/// <param name="createIfNotExists">Creates a new instance if one doesn't exist</param>
|
||||||
|
internal ScriptParseInfo GetScriptParseInfo(string uri, bool createIfNotExists = false)
|
||||||
{
|
{
|
||||||
lock (this.parseMapLock)
|
lock (this.parseMapLock)
|
||||||
{
|
{
|
||||||
@@ -857,7 +943,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
// 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();
|
||||||
scriptInfo.OnSettingsChanged(this.CurrentSettings);
|
|
||||||
this.ScriptParseInfoMap.Add(uri, scriptInfo);
|
this.ScriptParseInfoMap.Add(uri, scriptInfo);
|
||||||
return scriptInfo;
|
return scriptInfo;
|
||||||
}
|
}
|
||||||
@@ -874,9 +959,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
if (this.ScriptParseInfoMap.ContainsKey(uri))
|
if (this.ScriptParseInfoMap.ContainsKey(uri))
|
||||||
{
|
{
|
||||||
var scriptInfo = this.ScriptParseInfoMap[uri];
|
|
||||||
scriptInfo.ServerConnection.Disconnect();
|
|
||||||
scriptInfo.ServerConnection = null;
|
|
||||||
return this.ScriptParseInfoMap.Remove(uri);
|
return this.ScriptParseInfoMap.Remove(uri);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that stores the state of a binding queue request item
|
||||||
|
/// </summary>
|
||||||
|
public class QueueItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// QueueItem constructor
|
||||||
|
/// </summary>
|
||||||
|
public QueueItem()
|
||||||
|
{
|
||||||
|
this.ItemProcessed = new ManualResetEvent(initialState: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the queue item key
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the bind operation callback method
|
||||||
|
/// </summary>
|
||||||
|
public Func<IBindingContext, CancellationToken, Task<object>> BindOperation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timeout operation to call if the bind operation doesn't finish within timeout period
|
||||||
|
/// </summary>
|
||||||
|
public Func<IBindingContext, Task<object>> TimeoutOperation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an event to signal when this queue item has been processed
|
||||||
|
/// </summary>
|
||||||
|
public ManualResetEvent ItemProcessed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the task that was used to execute this queue item.
|
||||||
|
/// This allows the queuer to retrieve the execution result.
|
||||||
|
/// </summary>
|
||||||
|
public Task<object> ResultsTask { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the binding operation timeout in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
public int? BindingTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the result of the execution task to type T
|
||||||
|
/// </summary>
|
||||||
|
public T GetResultAsT<T>() where T : class
|
||||||
|
{
|
||||||
|
var task = this.ResultsTask;
|
||||||
|
return (task != null && task.IsCompleted && task.Result != null)
|
||||||
|
? task.Result as T
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.SqlServer.Management.Common;
|
|
||||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -24,16 +17,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
{
|
{
|
||||||
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
|
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
|
||||||
|
|
||||||
private ParseOptions parseOptions = new ParseOptions();
|
|
||||||
|
|
||||||
private ServerConnection serverConnection;
|
|
||||||
|
|
||||||
private Lazy<MetadataDisplayInfoProvider> metadataDisplayInfoProvider = new Lazy<MetadataDisplayInfoProvider>(() =>
|
|
||||||
{
|
|
||||||
var infoProvider = new MetadataDisplayInfoProvider();
|
|
||||||
return infoProvider;
|
|
||||||
});
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event which tells if MetadataProvider is built fully or not
|
/// Event which tells if MetadataProvider is built fully or not
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -48,181 +31,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
public bool IsConnected { get; set; }
|
public bool IsConnected { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the LanguageService SMO ServerConnection
|
/// Gets or sets the binding queue connection context key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ServerConnection ServerConnection
|
public string ConnectionKey { get; set; }
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.serverConnection;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.serverConnection = value;
|
|
||||||
this.parseOptions = new ParseOptions(
|
|
||||||
batchSeparator: LanguageService.DefaultBatchSeperator,
|
|
||||||
isQuotedIdentifierSet: true,
|
|
||||||
compatibilityLevel: DatabaseCompatibilityLevel,
|
|
||||||
transactSqlVersion: TransactSqlVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Language Service ServerVersion
|
|
||||||
/// </summary>
|
|
||||||
public ServerVersion ServerVersion
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.ServerConnection != null
|
|
||||||
? this.ServerConnection.ServerVersion
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current DataEngineType
|
|
||||||
/// </summary>
|
|
||||||
public DatabaseEngineType DatabaseEngineType
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.ServerConnection != null
|
|
||||||
? this.ServerConnection.DatabaseEngineType
|
|
||||||
: DatabaseEngineType.Standalone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current connections TransactSqlVersion
|
|
||||||
/// </summary>
|
|
||||||
public TransactSqlVersion TransactSqlVersion
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.IsConnected
|
|
||||||
? GetTransactSqlVersion(this.ServerVersion)
|
|
||||||
: TransactSqlVersion.Current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current DatabaseCompatibilityLevel
|
|
||||||
/// </summary>
|
|
||||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.IsConnected
|
|
||||||
? GetDatabaseCompatibilityLevel(this.ServerVersion)
|
|
||||||
: DatabaseCompatibilityLevel.Current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current ParseOptions
|
|
||||||
/// </summary>
|
|
||||||
public ParseOptions ParseOptions
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.parseOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the SMO binder for schema-aware intellisense
|
|
||||||
/// </summary>
|
|
||||||
public IBinder Binder { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the previous SQL parse result
|
/// Gets or sets the previous SQL parse result
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ParseResult ParseResult { get; set; }
|
public ParseResult ParseResult { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or set the SMO metadata provider that's bound to the current connection
|
|
||||||
/// </summary>
|
|
||||||
public SmoMetadataProvider MetadataProvider { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the SMO metadata display info provider
|
|
||||||
/// </summary>
|
|
||||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.metadataDisplayInfoProvider.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the current autocomplete suggestion list
|
/// Gets or sets the current autocomplete suggestion list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<Declaration> CurrentSuggestions { get; set; }
|
public IEnumerable<Declaration> CurrentSuggestions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update parse settings if the current configuration has changed
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="settings"></param>
|
|
||||||
public void OnSettingsChanged(SqlToolsSettings settings)
|
|
||||||
{
|
|
||||||
this.MetadataDisplayInfoProvider.BuiltInCasing =
|
|
||||||
settings.SqlTools.IntelliSense.LowerCaseSuggestions.Value
|
|
||||||
? CasingStyle.Lowercase
|
|
||||||
: CasingStyle.Uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the database compatibility level from a server version
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverVersion"></param>
|
|
||||||
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
|
|
||||||
{
|
|
||||||
int versionMajor = Math.Max(serverVersion.Major, 8);
|
|
||||||
|
|
||||||
switch (versionMajor)
|
|
||||||
{
|
|
||||||
case 8:
|
|
||||||
return DatabaseCompatibilityLevel.Version80;
|
|
||||||
case 9:
|
|
||||||
return DatabaseCompatibilityLevel.Version90;
|
|
||||||
case 10:
|
|
||||||
return DatabaseCompatibilityLevel.Version100;
|
|
||||||
case 11:
|
|
||||||
return DatabaseCompatibilityLevel.Version110;
|
|
||||||
case 12:
|
|
||||||
return DatabaseCompatibilityLevel.Version120;
|
|
||||||
case 13:
|
|
||||||
return DatabaseCompatibilityLevel.Version130;
|
|
||||||
default:
|
|
||||||
return DatabaseCompatibilityLevel.Current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the transaction sql version from a server version
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverVersion"></param>
|
|
||||||
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
|
|
||||||
{
|
|
||||||
int versionMajor = Math.Max(serverVersion.Major, 9);
|
|
||||||
|
|
||||||
switch (versionMajor)
|
|
||||||
{
|
|
||||||
case 9:
|
|
||||||
case 10:
|
|
||||||
// In case of 10.0 we still use Version 10.5 as it is the closest available.
|
|
||||||
return TransactSqlVersion.Version105;
|
|
||||||
case 11:
|
|
||||||
return TransactSqlVersion.Version110;
|
|
||||||
case 12:
|
|
||||||
return TransactSqlVersion.Version120;
|
|
||||||
case 13:
|
|
||||||
return TransactSqlVersion.Version130;
|
|
||||||
default:
|
|
||||||
return TransactSqlVersion.Current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the path which the editor client uses to identify this file.
|
/// Gets or sets the path which the editor client uses to identify this file.
|
||||||
/// Setter for testing purposes only
|
/// Setter for testing purposes only
|
||||||
|
/// virtual to allow mocking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientFilePath { get; internal set; }
|
public virtual string ClientFilePath { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a boolean that determines whether
|
/// Gets or sets a boolean that determines whether
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"Newtonsoft.Json": "9.0.1",
|
"Newtonsoft.Json": "9.0.1",
|
||||||
"System.Data.Common": "4.1.0",
|
"System.Data.Common": "4.1.0",
|
||||||
"System.Data.SqlClient": "4.1.0",
|
"System.Data.SqlClient": "4.1.0",
|
||||||
"Microsoft.SqlServer.Smo": "140.1.7",
|
"Microsoft.SqlServer.Smo": "140.1.8",
|
||||||
"System.Security.SecureString": "4.0.0",
|
"System.Security.SecureString": "4.0.0",
|
||||||
"System.Collections.Specialized": "4.0.1",
|
"System.Collections.Specialized": "4.0.1",
|
||||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
using Microsoft.SqlTools.Test.Utility;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the language service autocomplete component
|
||||||
|
/// </summary>
|
||||||
|
public class AutocompleteTests
|
||||||
|
{
|
||||||
|
private const int TaskTimeout = 60000;
|
||||||
|
|
||||||
|
private readonly string testScriptUri = TestObjects.ScriptUri;
|
||||||
|
|
||||||
|
private readonly string testConnectionKey = "testdbcontextkey";
|
||||||
|
|
||||||
|
private Mock<ConnectedBindingQueue> bindingQueue;
|
||||||
|
|
||||||
|
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
|
||||||
|
|
||||||
|
private Mock<RequestContext<CompletionItem[]>> requestContext;
|
||||||
|
|
||||||
|
private Mock<IBinder> binder;
|
||||||
|
|
||||||
|
private TextDocumentPosition textDocument;
|
||||||
|
|
||||||
|
private void InitializeTestObjects()
|
||||||
|
{
|
||||||
|
// initial cursor position in the script file
|
||||||
|
textDocument = new TextDocumentPosition
|
||||||
|
{
|
||||||
|
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
|
||||||
|
Position = new Position
|
||||||
|
{
|
||||||
|
Line = 0,
|
||||||
|
Character = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// default settings are stored in the workspace service
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||||
|
|
||||||
|
// set up file for returning the query
|
||||||
|
var fileMock = new Mock<ScriptFile>();
|
||||||
|
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||||
|
fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
|
||||||
|
|
||||||
|
// set up workspace mock
|
||||||
|
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||||
|
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||||
|
.Returns(fileMock.Object);
|
||||||
|
|
||||||
|
// setup binding queue mock
|
||||||
|
bindingQueue = new Mock<ConnectedBindingQueue>();
|
||||||
|
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>()))
|
||||||
|
.Returns(this.testConnectionKey);
|
||||||
|
|
||||||
|
// inject mock instances into the Language Service
|
||||||
|
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
|
||||||
|
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
|
||||||
|
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
|
||||||
|
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
|
||||||
|
LanguageService.Instance.BindingQueue = bindingQueue.Object;
|
||||||
|
|
||||||
|
// setup the mock for SendResult
|
||||||
|
requestContext = new Mock<RequestContext<CompletionItem[]>>();
|
||||||
|
requestContext.Setup(rc => rc.SendResult(It.IsAny<CompletionItem[]>()))
|
||||||
|
.Returns(Task.FromResult(0));
|
||||||
|
|
||||||
|
// setup the IBinder mock
|
||||||
|
binder = new Mock<IBinder>();
|
||||||
|
binder.Setup(b => b.Bind(
|
||||||
|
It.IsAny<IEnumerable<ParseResult>>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<BindMode>()));
|
||||||
|
|
||||||
|
var testScriptParseInfo = new ScriptParseInfo();
|
||||||
|
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
|
||||||
|
testScriptParseInfo.IsConnected = true;
|
||||||
|
testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
|
||||||
|
|
||||||
|
// setup the binding context object
|
||||||
|
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
|
||||||
|
bindingContext.Binder = binder.Object;
|
||||||
|
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
|
LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the primary completion list event handler
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void GetCompletionsHandlerTest()
|
||||||
|
{
|
||||||
|
InitializeTestObjects();
|
||||||
|
|
||||||
|
// request the completion list
|
||||||
|
Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object);
|
||||||
|
handleCompletion.Wait(TaskTimeout);
|
||||||
|
|
||||||
|
// verify that send result was called with a completion array
|
||||||
|
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test class for the test binding context
|
||||||
|
/// </summary>
|
||||||
|
public class TestBindingContext : IBindingContext
|
||||||
|
{
|
||||||
|
public TestBindingContext()
|
||||||
|
{
|
||||||
|
this.BindingLocked = new ManualResetEvent(initialState: true);
|
||||||
|
this.BindingTimeout = 3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConnected { get; set; }
|
||||||
|
|
||||||
|
public ServerConnection ServerConnection { get; set; }
|
||||||
|
|
||||||
|
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||||
|
|
||||||
|
public SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||||
|
|
||||||
|
public IBinder Binder { get; set; }
|
||||||
|
|
||||||
|
public ManualResetEvent BindingLocked { get; set; }
|
||||||
|
|
||||||
|
public int BindingTimeout { get; set; }
|
||||||
|
|
||||||
|
public ParseOptions ParseOptions { get; }
|
||||||
|
|
||||||
|
public ServerVersion ServerVersion { get; }
|
||||||
|
|
||||||
|
public DatabaseEngineType DatabaseEngineType { get; }
|
||||||
|
|
||||||
|
public TransactSqlVersion TransactSqlVersion { get; }
|
||||||
|
|
||||||
|
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the Binding Queue
|
||||||
|
/// </summary>
|
||||||
|
public class BindingQueueTests
|
||||||
|
{
|
||||||
|
private int bindCallCount = 0;
|
||||||
|
|
||||||
|
private int timeoutCallCount = 0;
|
||||||
|
|
||||||
|
private int bindCallbackDelay = 0;
|
||||||
|
|
||||||
|
private bool isCancelationRequested = false;
|
||||||
|
|
||||||
|
private IBindingContext bindingContext = null;
|
||||||
|
|
||||||
|
private BindingQueue<TestBindingContext> bindingQueue = null;
|
||||||
|
|
||||||
|
private void InitializeTestSettings()
|
||||||
|
{
|
||||||
|
this.bindCallCount = 0;
|
||||||
|
this.timeoutCallCount = 0;
|
||||||
|
this.bindCallbackDelay = 10;
|
||||||
|
this.isCancelationRequested = false;
|
||||||
|
this.bindingContext = GetMockBindingContext();
|
||||||
|
this.bindingQueue = new BindingQueue<TestBindingContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBindingContext GetMockBindingContext()
|
||||||
|
{
|
||||||
|
return new TestBindingContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test bind operation callback
|
||||||
|
/// </summary>
|
||||||
|
private Task<object> TestBindOperation(
|
||||||
|
IBindingContext bindContext,
|
||||||
|
CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay);
|
||||||
|
this.isCancelationRequested = cancelToken.IsCancellationRequested;
|
||||||
|
if (!this.isCancelationRequested)
|
||||||
|
{
|
||||||
|
++this.bindCallCount;
|
||||||
|
}
|
||||||
|
return new CompletionItem[0] as object;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test callback for the bind timeout operation
|
||||||
|
/// </summary>
|
||||||
|
private Task<object> TestTimeoutOperation(
|
||||||
|
IBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
++this.timeoutCallCount;
|
||||||
|
return Task.FromResult(new CompletionItem[0] as object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs for a few seconds to allow the queue to pump any requests
|
||||||
|
/// </summary>
|
||||||
|
private void WaitForQueue(int delay = 5000)
|
||||||
|
{
|
||||||
|
int step = 50;
|
||||||
|
int steps = delay / step + 1;
|
||||||
|
for (int i = 0; i < steps; ++i)
|
||||||
|
{
|
||||||
|
Thread.Sleep(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a single task
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void QueueOneBindingOperationTest()
|
||||||
|
{
|
||||||
|
InitializeTestSettings();
|
||||||
|
|
||||||
|
this.bindingQueue.QueueBindingOperation(
|
||||||
|
key: "testkey",
|
||||||
|
bindOperation: TestBindOperation,
|
||||||
|
timeoutOperation: TestTimeoutOperation);
|
||||||
|
|
||||||
|
WaitForQueue();
|
||||||
|
|
||||||
|
this.bindingQueue.StopQueueProcessor(15000);
|
||||||
|
|
||||||
|
Assert.True(this.bindCallCount == 1);
|
||||||
|
Assert.True(this.timeoutCallCount == 0);
|
||||||
|
Assert.False(this.isCancelationRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a 100 short tasks
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Queue100BindingOperationTest()
|
||||||
|
{
|
||||||
|
InitializeTestSettings();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
this.bindingQueue.QueueBindingOperation(
|
||||||
|
key: "testkey",
|
||||||
|
bindOperation: TestBindOperation,
|
||||||
|
timeoutOperation: TestTimeoutOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForQueue();
|
||||||
|
|
||||||
|
this.bindingQueue.StopQueueProcessor(15000);
|
||||||
|
|
||||||
|
Assert.True(this.bindCallCount == 100);
|
||||||
|
Assert.True(this.timeoutCallCount == 0);
|
||||||
|
Assert.False(this.isCancelationRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue an task with a long operation causing a timeout
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void QueueWithTimeout()
|
||||||
|
{
|
||||||
|
InitializeTestSettings();
|
||||||
|
|
||||||
|
this.bindCallbackDelay = 10000;
|
||||||
|
|
||||||
|
this.bindingQueue.QueueBindingOperation(
|
||||||
|
key: "testkey",
|
||||||
|
bindOperation: TestBindOperation,
|
||||||
|
timeoutOperation: TestTimeoutOperation);
|
||||||
|
|
||||||
|
WaitForQueue(this.bindCallbackDelay + 2000);
|
||||||
|
|
||||||
|
this.bindingQueue.StopQueueProcessor(15000);
|
||||||
|
|
||||||
|
Assert.True(this.bindCallCount == 0);
|
||||||
|
Assert.True(this.timeoutCallCount == 1);
|
||||||
|
Assert.True(this.isCancelationRequested);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
{
|
{
|
||||||
#region "Diagnostics tests"
|
#region "Diagnostics tests"
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verify that the latest SqlParser (2016 as of this writing) is used by default
|
/// Verify that the latest SqlParser (2016 as of this writing) is used by default
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -154,12 +155,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
InitializeTestServices();
|
InitializeTestServices();
|
||||||
|
|
||||||
Assert.True(LanguageService.Instance.Context != null);
|
Assert.True(LanguageService.Instance.Context != null);
|
||||||
Assert.True(LanguageService.Instance.ConnectionServiceInstance != null);
|
Assert.True(LanguageService.ConnectionServiceInstance != null);
|
||||||
Assert.True(LanguageService.Instance.CurrentSettings != null);
|
Assert.True(LanguageService.Instance.CurrentSettings != null);
|
||||||
Assert.True(LanguageService.Instance.CurrentWorkspace != null);
|
Assert.True(LanguageService.Instance.CurrentWorkspace != null);
|
||||||
|
|
||||||
LanguageService.Instance.ConnectionServiceInstance = null;
|
LanguageService.ConnectionServiceInstance = null;
|
||||||
Assert.True(LanguageService.Instance.ConnectionServiceInstance == null);
|
Assert.True(LanguageService.ConnectionServiceInstance == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -178,6 +179,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
Connection = TestObjects.GetTestConnectionDetails()
|
Connection = TestObjects.GetTestConnectionDetails()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set up file for returning the query
|
||||||
|
var fileMock = new Mock<ScriptFile>();
|
||||||
|
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||||
|
fileMock.SetupGet(file => file.ClientFilePath).Returns(ownerUri);
|
||||||
|
|
||||||
|
// set up workspace mock
|
||||||
|
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||||
|
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||||
|
.Returns(fileMock.Object);
|
||||||
|
|
||||||
|
AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object;
|
||||||
|
|
||||||
ConnectionInfo connInfo = null;
|
ConnectionInfo connInfo = null;
|
||||||
connectionService.TryFindConnection(ownerUri, out connInfo);
|
connectionService.TryFindConnection(ownerUri, out connInfo);
|
||||||
|
|
||||||
@@ -212,7 +225,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||||
scriptInfo.IsConnected = true;
|
scriptInfo.IsConnected = true;
|
||||||
|
|
||||||
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo);
|
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTestSqlFile()
|
private string GetTestSqlFile()
|
||||||
|
|||||||
@@ -253,12 +253,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
||||||
var binder = BinderProvider.CreateBinder(metadataProvider);
|
var binder = BinderProvider.CreateBinder(metadataProvider);
|
||||||
|
|
||||||
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri,
|
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo());
|
||||||
new ScriptParseInfo
|
|
||||||
{
|
|
||||||
Binder = binder,
|
|
||||||
MetadataProvider = metadataProvider
|
|
||||||
});
|
|
||||||
|
|
||||||
scriptFile = new ScriptFile {ClientFilePath = textDocument.TextDocument.Uri};
|
scriptFile = new ScriptFile {ClientFilePath = textDocument.TextDocument.Uri};
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ namespace Microsoft.SqlTools.Test.Utility
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestObjects
|
public class TestObjects
|
||||||
{
|
{
|
||||||
|
public const string ScriptUri = "file://some/file.sql";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a test connection service
|
/// Creates a test connection service
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -42,7 +44,7 @@ namespace Microsoft.SqlTools.Test.Utility
|
|||||||
{
|
{
|
||||||
return new ConnectionInfo(
|
return new ConnectionInfo(
|
||||||
GetTestSqlConnectionFactory(),
|
GetTestSqlConnectionFactory(),
|
||||||
"file://some/file.sql",
|
ScriptUri,
|
||||||
GetTestConnectionDetails());
|
GetTestConnectionDetails());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ namespace Microsoft.SqlTools.Test.Utility
|
|||||||
{
|
{
|
||||||
return new ConnectParams()
|
return new ConnectParams()
|
||||||
{
|
{
|
||||||
OwnerUri = "file://some/file.sql",
|
OwnerUri = ScriptUri,
|
||||||
Connection = GetTestConnectionDetails()
|
Connection = GetTestConnectionDetails()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"System.Runtime.Serialization.Primitives": "4.1.1",
|
"System.Runtime.Serialization.Primitives": "4.1.1",
|
||||||
"System.Data.Common": "4.1.0",
|
"System.Data.Common": "4.1.0",
|
||||||
"System.Data.SqlClient": "4.1.0",
|
"System.Data.SqlClient": "4.1.0",
|
||||||
"Microsoft.SqlServer.Smo": "140.1.7",
|
"Microsoft.SqlServer.Smo": "140.1.8",
|
||||||
"System.Security.SecureString": "4.0.0",
|
"System.Security.SecureString": "4.0.0",
|
||||||
"System.Collections.Specialized": "4.0.1",
|
"System.Collections.Specialized": "4.0.1",
|
||||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user