Merge branch 'release/ctp10'

This commit is contained in:
Karl Burtram
2016-11-06 20:45:48 -08:00
56 changed files with 2478 additions and 1360 deletions

View File

@@ -3,7 +3,12 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser;
@@ -25,89 +30,44 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
private static Regex ValidSqlNameRegex = new Regex(@"^[\p{L}_@][\p{L}\p{N}@$#_]{0,127}$");
private static CompletionItem[] emptyCompletionList = new CompletionItem[0];
private static readonly string[] DefaultCompletionText = new string[]
{
"absolute",
"accent_sensitivity",
"action",
"activation",
"add",
"address",
"admin",
"after",
"aggregate",
"algorithm",
"allow_page_locks",
"allow_row_locks",
"allow_snapshot_isolation",
{
"all",
"alter",
"always",
"ansi_null_default",
"ansi_nulls",
"ansi_padding",
"ansi_warnings",
"application",
"arithabort",
"and",
"apply",
"as",
"asc",
"assembly",
"asymmetric",
"at",
"atomic",
"audit",
"authentication",
"authorization",
"auto",
"auto_close",
"auto_shrink",
"auto_update_statistics",
"auto_update_statistics_async",
"availability",
"backup",
"before",
"begin",
"binary",
"bit",
"block",
"break",
"browse",
"bucket_count",
"bulk",
"by",
"call",
"caller",
"card",
"cascade",
"case",
"catalog",
"catch",
"change_tracking",
"changes",
"char",
"character",
"check",
"checkpoint",
"close",
"clustered",
"collection",
"column",
"column_encryption_key",
"columnstore",
"commit",
"compatibility_level",
"compress_all_row_groups",
"compression",
"compression_delay",
"compute",
"concat_null_yields_null",
"configuration",
"connect",
"constraint",
"containstable",
"continue",
"create",
"cube",
"current",
"cross",
"current_date",
"cursor",
"cursor_close_on_commit",
@@ -116,47 +76,34 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"data_compression",
"database",
"date",
"date_correlation_optimization",
"datefirst",
"datetime",
"datetime2",
"days",
"db_chaining",
"dbcc",
"deallocate",
"dec",
"decimal",
"declare",
"default",
"delayed_durability",
"delete",
"deny",
"desc",
"description",
"disable_broker",
"disabled",
"disk",
"distinct",
"distributed",
"double",
"drop",
"drop_existing",
"dump",
"durability",
"dynamic",
"else",
"enable",
"encrypted",
"encryption_type",
"end",
"end-exec",
"entry",
"errlvl",
"escape",
"event",
"except",
"exec",
"execute",
"exists",
"exit",
"external",
"fast_forward",
@@ -165,20 +112,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"filegroup",
"filename",
"filestream",
"fillfactor",
"filter",
"first",
"float",
"for",
"foreign",
"freetext",
"freetexttable",
"from",
"full",
"fullscan",
"fulltext",
"function",
"generated",
"geography",
"get",
"global",
@@ -194,30 +135,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"holdlock",
"hours",
"identity",
"identity_insert",
"identitycol",
"if",
"ignore_dup_key",
"image",
"immediate",
"include",
"index",
"inflectional",
"insensitive",
"inner",
"insert",
"instead",
"int",
"integer",
"integrated",
"intersect",
"into",
"isolation",
"join",
"json",
"key",
"kill",
"language",
"last",
"legacy_cardinality_estimation",
"left",
"level",
"lineno",
"load",
@@ -226,16 +163,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"location",
"login",
"masked",
"master",
"maxdop",
"memory_optimized",
"merge",
"message",
"modify",
"move",
"multi_user",
"namespace",
"national",
"native_compilation",
"nchar",
"next",
@@ -245,9 +178,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"nonclustered",
"none",
"norecompute",
"not",
"now",
"null",
"numeric",
"numeric_roundabort",
"object",
"of",
"off",
@@ -255,21 +189,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"on",
"online",
"open",
"opendatasource",
"openquery",
"openrowset",
"openxml",
"option",
"or",
"order",
"out",
"outer",
"output",
"over",
"owner",
"pad_index",
"page",
"page_verify",
"parameter_sniffing",
"parameterization",
"partial",
"partition",
"password",
@@ -280,7 +209,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"persisted",
"plan",
"policy",
"population",
"precision",
"predicate",
"primary",
@@ -289,7 +217,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"proc",
"procedure",
"public",
"query_optimizer_hotfixes",
"query_store",
"quoted_identifier",
"raiserror",
@@ -312,7 +239,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"relative",
"remove",
"reorganize",
"replication",
"required",
"restart",
"restore",
@@ -322,7 +248,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"returns",
"revert",
"revoke",
"role",
"rollback",
"rollup",
"row",
@@ -338,11 +263,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"scroll",
"secondary",
"security",
"securityaudit",
"select",
"semantickeyphrasetable",
"semanticsimilaritydetailstable",
"semanticsimilaritytable",
"send",
"sent",
"sequence",
@@ -351,12 +272,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"set",
"sets",
"setuser",
"shutdown",
"simple",
"smallint",
"smallmoney",
"snapshot",
"sort_in_tempdb",
"sql",
"standard",
"start",
@@ -368,20 +287,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"statistics_norecompute",
"status",
"stopped",
"supported",
"symmetric",
"sysname",
"system",
"system_time",
"system_versioning",
"table",
"tablesample",
"take",
"target",
"textimage_on",
"textsize",
"then",
"thesaurus",
"throw",
"time",
"timestamp",
@@ -392,14 +304,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"transaction",
"trigger",
"truncate",
"trustworthy",
"try",
"tsql",
"type",
"uncommitted",
"union",
"unique",
"uniqueidentifier",
"unlimited",
"updatetext",
"use",
"user",
@@ -407,24 +318,32 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
"value",
"values",
"varchar",
"varying",
"version",
"view",
"waitfor",
"weight",
"when",
"where",
"while",
"with",
"within",
"within group",
"without",
"writetext",
"xact_abort",
"xml",
"zone"
};
/// <summary>
/// Gets a static instance of an empty completion list to avoid
// unneeded memory allocations
/// </summary>
internal static CompletionItem[] EmptyCompletionList
{
get
{
return AutoCompleteHelper.emptyCompletionList;
}
}
/// <summary>
/// Gets or sets the current workspace service instance
/// Setter for internal testing purposes only
@@ -443,7 +362,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
AutoCompleteHelper.workspaceServiceInstance = value;
}
}
}
/// <summary>
/// Get the default completion list from hard-coded list
@@ -456,17 +375,47 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int row,
int startColumn,
int endColumn,
bool useLowerCase)
bool useLowerCase,
string tokenText = null)
{
var completionItems = new CompletionItem[DefaultCompletionText.Length];
for (int i = 0; i < DefaultCompletionText.Length; ++i)
// determine how many default completion items there will be
int listSize = DefaultCompletionText.Length;
if (!string.IsNullOrWhiteSpace(tokenText))
{
completionItems[i] = CreateDefaultCompletionItem(
useLowerCase ? DefaultCompletionText[i].ToLower() : DefaultCompletionText[i].ToUpper(),
row,
startColumn,
endColumn);
listSize = 0;
foreach (var completionText in DefaultCompletionText)
{
if (completionText.StartsWith(tokenText, StringComparison.OrdinalIgnoreCase))
{
++listSize;
}
}
}
// special case empty list to avoid unneed array allocations
if (listSize == 0)
{
return emptyCompletionList;
}
// build the default completion list
var completionItems = new CompletionItem[listSize];
int completionItemIndex = 0;
foreach (var completionText in DefaultCompletionText)
{
// add item to list if the tokenText is null (meaning return whole list)
// or if the completion item begins with the tokenText
if (string.IsNullOrWhiteSpace(tokenText) || completionText.StartsWith(tokenText, StringComparison.OrdinalIgnoreCase))
{
completionItems[completionItemIndex] = CreateDefaultCompletionItem(
useLowerCase ? completionText.ToLower() : completionText.ToUpper(),
row,
startColumn,
endColumn);
++completionItemIndex;
}
}
return completionItems;
}
@@ -483,14 +432,44 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int startColumn,
int endColumn)
{
return new CompletionItem()
return CreateCompletionItem(label, label + " keyword", label, CompletionItemKind.Keyword, row, startColumn, endColumn);
}
internal static CompletionItem[] AddTokenToItems(CompletionItem[] currentList, Token token, int row,
int startColumn,
int endColumn)
{
if (currentList != null &&
token != null && !string.IsNullOrWhiteSpace(token.Text) &&
token.Text.All(ch => char.IsLetter(ch)) &&
currentList.All(x => string.Compare(x.Label, token.Text, true) != 0
))
{
var list = currentList.ToList();
list.Insert(0, CreateCompletionItem(token.Text, token.Text, token.Text, CompletionItemKind.Text, row, startColumn, endColumn));
return list.ToArray();
}
return currentList;
}
private static CompletionItem CreateCompletionItem(
string label,
string detail,
string insertText,
CompletionItemKind kind,
int row,
int startColumn,
int endColumn)
{
CompletionItem item = new CompletionItem()
{
Label = label,
Kind = CompletionItemKind.Keyword,
Detail = label + " keyword",
Kind = kind,
Detail = detail,
InsertText = insertText,
TextEdit = new TextEdit
{
NewText = label,
NewText = insertText,
Range = new Range
{
Start = new Position
@@ -506,6 +485,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
};
return item;
}
/// <summary>
@@ -521,39 +502,55 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int row,
int startColumn,
int endColumn)
{
{
List<CompletionItem> completions = new List<CompletionItem>();
foreach (var autoCompleteItem in suggestions)
{
// convert the completion item candidates into CompletionItems
completions.Add(new CompletionItem()
string insertText = GetCompletionItemInsertName(autoCompleteItem);
CompletionItemKind kind = CompletionItemKind.Variable;
switch (autoCompleteItem.Type)
{
Label = autoCompleteItem.Title,
Kind = CompletionItemKind.Variable,
Detail = autoCompleteItem.Title,
TextEdit = new TextEdit
{
NewText = autoCompleteItem.Title,
Range = new Range
{
Start = new Position
{
Line = row,
Character = startColumn
},
End = new Position
{
Line = row,
Character = endColumn
}
}
}
});
case DeclarationType.Schema:
kind = CompletionItemKind.Module;
break;
case DeclarationType.Column:
kind = CompletionItemKind.Field;
break;
case DeclarationType.Table:
case DeclarationType.View:
kind = CompletionItemKind.File;
break;
case DeclarationType.Database:
kind = CompletionItemKind.Method;
break;
case DeclarationType.ScalarValuedFunction:
case DeclarationType.TableValuedFunction:
case DeclarationType.BuiltInFunction:
kind = CompletionItemKind.Value;
break;
default:
kind = CompletionItemKind.Unit;
break;
}
// convert the completion item candidates into CompletionItems
completions.Add(CreateCompletionItem(autoCompleteItem.Title, autoCompleteItem.Title, insertText, kind, row, startColumn, endColumn));
}
return completions.ToArray();
}
private static string GetCompletionItemInsertName(Declaration autoCompleteItem)
{
string insertText = autoCompleteItem.Title;
if (!string.IsNullOrEmpty(autoCompleteItem.Title) && !ValidSqlNameRegex.IsMatch(autoCompleteItem.Title))
{
insertText = string.Format(CultureInfo.InvariantCulture, "[{0}]", autoCompleteItem.Title);
}
return insertText;
}
/// <summary>
/// Preinitialize the parser and binder with common metadata.
/// This should front load the long binding wait to the time the
@@ -572,15 +569,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
LanguageService.Instance.ParseAndBind(scriptFile, info);
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
try
{
scriptInfo.BuildingMetadataEvent.Reset();
QueueItem queueItem = bindingQueue.QueueBindingOperation(
key: scriptInfo.ConnectionKey,
bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
waitForLockTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
// parse a simple statement that returns common metadata
@@ -631,13 +627,61 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
scriptInfo.BuildingMetadataEvent.Set();
Monitor.Exit(scriptInfo.BuildingMetadataLock);
}
}
}
}
/// <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;
}
}
/// <summary>
/// Converts a SQL Parser QuickInfo object into a VS Code Hover object
/// </summary>

View File

@@ -15,7 +15,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// 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);
@@ -61,7 +61,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string key,
Func<IBindingContext, CancellationToken, object> bindOperation,
Func<IBindingContext, object> timeoutOperation = null,
int? bindingTimeout = null)
int? bindingTimeout = null,
int? waitForLockTimeout = null)
{
// don't add null operations to the binding queue
if (bindOperation == null)
@@ -74,7 +75,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Key = key,
BindOperation = bindOperation,
TimeoutOperation = timeoutOperation,
BindingTimeout = bindingTimeout
BindingTimeout = bindingTimeout,
WaitForLockTimeout = waitForLockTimeout
};
lock (this.bindingQueueLock)
@@ -98,7 +100,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
key = "disconnected_binding_context";
}
lock (this.bindingContextLock)
{
if (!this.BindingContextMap.ContainsKey(key))
@@ -107,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
return this.BindingContextMap[key];
}
}
}
private bool HasPendingQueueItems
@@ -191,19 +193,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
continue;
}
bool lockTaken = false;
try
{
// prefer the queue item binding item, otherwise use the context default timeout
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
// handle the case a previous binding operation is still running
if (!bindingContext.BindingLocked.WaitOne(bindTimeout))
// handle the case a previous binding operation is still running
if (!bindingContext.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
{
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
queueItem.ItemProcessed.Set();
queueItem.Result = queueItem.TimeoutOperation != null
? queueItem.TimeoutOperation(bindingContext)
: null;
continue;
}
bindingContext.BindingLock.Reset();
lockTaken = true;
// execute the binding operation
object result = null;
CancellationTokenSource cancelToken = new CancellationTokenSource();
@@ -220,13 +229,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
queueItem.Result = result;
}
else
{
{
cancelToken.Cancel();
// if the task didn't complete then call the timeout callback
if (queueItem.TimeoutOperation != null)
{
cancelToken.Cancel();
{
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
}
lockTaken = false;
bindTask.ContinueWith((a) => bindingContext.BindingLock.Set());
}
}
catch (Exception ex)
@@ -237,7 +251,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
bindingContext.BindingLocked.Set();
if (lockTaken)
{
bindingContext.BindingLock.Set();
}
queueItem.ItemProcessed.Set();
}
@@ -250,8 +268,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
// reset the item queued event since we've processed all the pending items
this.itemQueuedEvent.Reset();
lock (this.bindingQueueLock)
{
// verify the binding queue is still empty
if (this.bindingQueue.Count == 0)
{
// reset the item queued event since we've processed all the pending items
this.itemQueuedEvent.Reset();
}
}
}
}
}

View File

@@ -21,6 +21,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
private ParseOptions parseOptions;
private ManualResetEvent bindingLock;
private ServerConnection serverConnection;
/// <summary>
@@ -28,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
public ConnectedBindingContext()
{
this.BindingLocked = new ManualResetEvent(initialState: true);
this.bindingLock = new ManualResetEvent(initialState: true);
this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
}
@@ -72,9 +74,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public IBinder Binder { get; set; }
/// <summary>
/// Gets or sets an event to signal if a binding operation is in progress
/// Gets the binding lock object
/// </summary>
public ManualResetEvent BindingLocked { get; set; }
public ManualResetEvent BindingLock
{
get
{
return this.bindingLock;
}
}
/// <summary>
/// Gets or sets the binding operation timeout in milliseconds

View File

@@ -21,7 +21,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
public class ConnectedBindingQueue : BindingQueue<ConnectedBindingContext>
{
internal const int DefaultBindingTimeout = 60000;
internal const int DefaultBindingTimeout = 500;
internal const int DefaultMinimumConnectionTimeout = 30;
@@ -63,22 +63,24 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string connectionKey = GetConnectionContextKey(connInfo);
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey);
try
if (bindingContext.BindingLock.WaitOne())
{
// 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)
try
{
bindingContext.BindingLock.Reset();
// 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);
sqlConn.Open();
// populate the binding context to work with the SMO metadata provider
@@ -91,16 +93,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
bindingContext.Binder = BinderProvider.CreateBinder(bindingContext.SmoMetadataProvider);
bindingContext.ServerConnection = serverConn;
bindingContext.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
bindingContext.IsConnected = true;
bindingContext.IsConnected = true;
}
}
catch (Exception)
{
bindingContext.IsConnected = false;
}
finally
{
bindingContext.BindingLocked.Set();
catch (Exception)
{
bindingContext.IsConnected = false;
}
finally
{
bindingContext.BindingLock.Set();
}
}
return connectionKey;

View File

@@ -44,9 +44,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
IBinder Binder { get; set; }
/// <summary>
/// Gets or sets an event to signal if a binding operation is in progress
/// Gets the binding lock object
/// </summary>
ManualResetEvent BindingLocked { get; set; }
ManualResetEvent BindingLock { get; }
/// <summary>
/// Gets or sets the binding operation timeout in milliseconds

View File

@@ -37,11 +37,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal const int DiagnosticParseDelay = 750;
internal const int HoverTimeout = 3000;
internal const int HoverTimeout = 500;
internal const int BindingTimeout = 3000;
internal const int FindCompletionStartTimeout = 50;
internal const int BindingTimeout = 500;
internal const int OnConnectionWaitTimeout = 300000;
@@ -264,10 +262,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
var completionItems = Instance.GetCompletionItems(
textDocumentPosition, scriptFile, connInfo);
await requestContext.SendResult(completionItems);
await requestContext.SendResult(completionItems);
}
}
}
/// <summary>
/// Handle the resolve completion request event to provide additional
@@ -394,15 +392,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
SqlToolsSettings oldSettings,
EventContext eventContext)
{
bool oldEnableIntelliSense = oldSettings.SqlTools.EnableIntellisense;
bool? oldEnableDiagnostics = oldSettings.SqlTools.IntelliSense.EnableDiagnostics;
bool oldEnableIntelliSense = oldSettings.SqlTools.IntelliSense.EnableIntellisense;
bool? oldEnableDiagnostics = oldSettings.SqlTools.IntelliSense.EnableErrorChecking;
// update the current settings to reflect any changes
CurrentSettings.Update(newSettings);
// if script analysis settings have changed we need to clear the current diagnostic markers
if (oldEnableIntelliSense != newSettings.SqlTools.EnableIntellisense
|| oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableDiagnostics)
if (oldEnableIntelliSense != newSettings.SqlTools.IntelliSense.EnableIntellisense
|| oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableErrorChecking)
{
// if the user just turned off diagnostics then send an event to clear the error markers
if (!newSettings.IsDiagnositicsEnabled)
@@ -452,12 +450,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// get or create the current parse info object
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true);
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.BindingTimeout))
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
{
try
{
parseInfo.BuildingMetadataEvent.Reset();
if (connInfo == null || !parseInfo.IsConnected)
{
// parse current SQL file contents to retrieve a list of errors
@@ -518,7 +514,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
parseInfo.BuildingMetadataEvent.Set();
Monitor.Exit(parseInfo.BuildingMetadataLock);
}
}
else
@@ -538,11 +534,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
await Task.Run(() =>
{
ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true);
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
try
{
scriptInfo.BuildingMetadataEvent.Reset();
{
scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(info);
scriptInfo.IsConnected = true;
@@ -556,7 +551,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
// Set Metadata Build event to Signal state.
// (Tell Language Service that I am ready with Metadata Provider Object)
scriptInfo.BuildingMetadataEvent.Set();
Monitor.Exit(scriptInfo.BuildingMetadataLock);
}
}
@@ -588,28 +583,45 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="completionItem"></param>
internal CompletionItem ResolveCompletionItem(CompletionItem completionItem)
{
try
var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo;
if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null)
{
var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo;
if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null)
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
{
foreach (var suggestion in scriptParseInfo.CurrentSuggestions)
try
{
if (string.Equals(suggestion.Title, completionItem.Label))
{
completionItem.Detail = suggestion.DatabaseQualifiedName;
completionItem.Documentation = suggestion.Description;
break;
}
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
foreach (var suggestion in scriptParseInfo.CurrentSuggestions)
{
if (string.Equals(suggestion.Title, completionItem.Label))
{
completionItem.Detail = suggestion.DatabaseQualifiedName;
completionItem.Documentation = suggestion.Description;
break;
}
}
return completionItem;
});
queueItem.ItemProcessed.WaitOne();
}
catch (Exception ex)
{
// if any exceptions are raised looking up extended completion metadata
// then just return the original completion item
Logger.Write(LogLevel.Error, "Exeception in ResolveCompletionItem " + ex.ToString());
}
finally
{
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
}
catch (Exception ex)
{
// if any exceptions are raised looking up extended completion metadata
// then just return the original completion item
Logger.Write(LogLevel.Error, "Exeception in ResolveCompletionItem " + ex.ToString());
}
return completionItem;
}
@@ -631,9 +643,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null)
{
if (scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
{
scriptParseInfo.BuildingMetadataEvent.Reset();
try
{
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
@@ -661,8 +672,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
scriptParseInfo.BuildingMetadataEvent.Set();
}
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
}
@@ -680,23 +691,32 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ScriptFile scriptFile,
ConnectionInfo connInfo)
{
// initialize some state to parse and bind the current script file
this.currentCompletionParseInfo = null;
CompletionItem[] resultCompletionItems = null;
string filePath = textDocumentPosition.TextDocument.Uri;
int startLine = textDocumentPosition.Position.Line;
int parserLine = textDocumentPosition.Position.Line + 1;
int startColumn = TextUtilities.PositionOfPrevDelimeter(
scriptFile.Contents,
textDocumentPosition.Position.Line,
textDocumentPosition.Position.Character);
int endColumn = textDocumentPosition.Position.Character;
int endColumn = TextUtilities.PositionOfNextDelimeter(
scriptFile.Contents,
textDocumentPosition.Position.Line,
textDocumentPosition.Position.Character);
int parserColumn = textDocumentPosition.Position.Character + 1;
bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value;
this.currentCompletionParseInfo = null;
// Take a reference to the list at a point in time in case we update and replace the list
// get the current script parse info object
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
if (connInfo == null || scriptParseInfo == null)
if (scriptParseInfo == null)
{
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
return AutoCompleteHelper.GetDefaultCompletionItems(
startLine,
startColumn,
endColumn,
useLowerCaseSuggestions);
}
// reparse and bind the SQL statement if needed
@@ -705,62 +725,128 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ParseAndBind(scriptFile, connInfo);
}
// if the parse failed then return the default list
if (scriptParseInfo.ParseResult == null)
{
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
return AutoCompleteHelper.GetDefaultCompletionItems(
startLine,
startColumn,
endColumn,
useLowerCaseSuggestions);
}
// need to adjust line & column for base-1 parser indices
Token token = GetToken(scriptParseInfo, parserLine, parserColumn);
string tokenText = token != null ? token.Text : null;
if (scriptParseInfo.IsConnected
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
{
scriptParseInfo.BuildingMetadataEvent.Reset();
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
CompletionItem[] completions = null;
try
// check if the file is connected and the file lock is available
if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
{
try
{
// queue the completion task with the binding queue
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
// get the completion list from SQL Parser
scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
scriptParseInfo.ParseResult,
textDocumentPosition.Position.Line + 1,
textDocumentPosition.Position.Character + 1,
parserLine,
parserColumn,
bindingContext.MetadataDisplayInfoProvider);
// cache the current script parse info object to resolve completions later
this.currentCompletionParseInfo = scriptParseInfo;
// convert the suggestion list to the VS Code format
completions = AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
scriptParseInfo.CurrentSuggestions,
startLine,
startColumn,
endColumn);
}
finally
},
timeoutOperation: (bindingContext) =>
{
scriptParseInfo.BuildingMetadataEvent.Set();
}
// return the default list if the connected bind fails
return AutoCompleteHelper.GetDefaultCompletionItems(
startLine,
startColumn,
endColumn,
useLowerCaseSuggestions,
tokenText);
});
return completions;
},
timeoutOperation: (bindingContext) =>
// wait for the queue item
queueItem.ItemProcessed.WaitOne();
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
if (completionItems != null && completionItems.Length > 0)
{
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
});
queueItem.ItemProcessed.WaitOne();
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
if (completionItems != null && completionItems.Length > 0)
resultCompletionItems = completionItems;
}
else if (!ShouldShowCompletionList(token))
{
resultCompletionItems = AutoCompleteHelper.EmptyCompletionList;
}
}
finally
{
return completionItems;
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
// if there are no completions then provide the default list
if (resultCompletionItems == null)
{
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(
startLine,
startColumn,
endColumn,
useLowerCaseSuggestions,
tokenText);
}
return resultCompletionItems;
}
private static Token GetToken(ScriptParseInfo scriptParseInfo, int startLine, int startColumn)
{
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
{
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
if (tokenIndex >= 0)
{
// return the current token
int currentIndex = 0;
foreach (var token in scriptParseInfo.ParseResult.Script.Tokens)
{
if (currentIndex == tokenIndex)
{
return token;
}
++currentIndex;
}
}
}
return null;
}
private static bool ShouldShowCompletionList(Token token)
{
bool result = true;
if (token != null)
{
switch (token.Id)
{
case (int)Tokens.LEX_MULTILINE_COMMENT:
case (int)Tokens.LEX_END_OF_LINE_COMMENT:
result = false;
break;
}
}
return result;
}
#endregion
@@ -897,6 +983,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// Get the requested files
foreach (ScriptFile scriptFile in filesToAnalyze)
{
if (IsPreviewWindow(scriptFile))
{
continue;
}
Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath);
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
Logger.Write(LogLevel.Verbose, "Analysis complete.");

View File

@@ -5,7 +5,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
@@ -52,6 +51,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
public int? BindingTimeout { get; set; }
/// <summary>
/// Gets or sets the timeout for how long to wait for the binding lock
/// </summary>
public int? WaitForLockTimeout { get; set; }
/// <summary>
/// Converts the result of the execution to type T
/// </summary>

View File

@@ -4,7 +4,6 @@
//
using System.Collections.Generic;
using System.Threading;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser;
@@ -15,14 +14,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
internal class ScriptParseInfo
{
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
private object buildingMetadataLock = new object();
/// <summary>
/// Event which tells if MetadataProvider is built fully or not
/// </summary>
public ManualResetEvent BuildingMetadataEvent
public object BuildingMetadataLock
{
get { return this.buildingMetadataEvent; }
get { return this.buildingMetadataLock; }
}
/// <summary>