//
// 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 Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
///
/// Main class for Language Service functionality including anything that reqires knowledge of
/// the language to perfom, such as definitions, intellisense, etc.
///
public static class AutoCompleteHelper
{
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",
"alter",
"always",
"ansi_null_default",
"ansi_nulls",
"ansi_padding",
"ansi_warnings",
"application",
"arithabort",
"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",
"current_date",
"cursor",
"cursor_close_on_commit",
"cursor_default",
"data",
"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",
"exit",
"external",
"fast_forward",
"fetch",
"file",
"filegroup",
"filename",
"filestream",
"fillfactor",
"filter",
"first",
"float",
"for",
"foreign",
"freetext",
"freetexttable",
"from",
"full",
"fullscan",
"fulltext",
"function",
"generated",
"geography",
"get",
"global",
"go",
"goto",
"grant",
"group",
"hash",
"hashed",
"having",
"hidden",
"hierarchyid",
"holdlock",
"hours",
"identity",
"identity_insert",
"identitycol",
"if",
"ignore_dup_key",
"image",
"immediate",
"include",
"index",
"inflectional",
"insensitive",
"insert",
"instead",
"int",
"integer",
"integrated",
"intersect",
"into",
"isolation",
"json",
"key",
"kill",
"language",
"last",
"legacy_cardinality_estimation",
"level",
"lineno",
"load",
"local",
"locate",
"location",
"login",
"masked",
"master",
"maxdop",
"memory_optimized",
"merge",
"message",
"modify",
"move",
"multi_user",
"namespace",
"national",
"native_compilation",
"nchar",
"next",
"no",
"nocheck",
"nocount",
"nonclustered",
"none",
"norecompute",
"now",
"numeric",
"numeric_roundabort",
"object",
"of",
"off",
"offsets",
"on",
"online",
"open",
"opendatasource",
"openquery",
"openrowset",
"openxml",
"option",
"order",
"out",
"output",
"over",
"owner",
"pad_index",
"page",
"page_verify",
"parameter_sniffing",
"parameterization",
"partial",
"partition",
"password",
"path",
"percent",
"percentage",
"period",
"persisted",
"plan",
"policy",
"population",
"precision",
"predicate",
"primary",
"print",
"prior",
"proc",
"procedure",
"public",
"query_optimizer_hotfixes",
"query_store",
"quoted_identifier",
"raiserror",
"range",
"raw",
"read",
"read_committed_snapshot",
"read_only",
"read_write",
"readonly",
"readtext",
"real",
"rebuild",
"receive",
"reconfigure",
"recovery",
"recursive",
"recursive_triggers",
"references",
"relative",
"remove",
"reorganize",
"replication",
"required",
"restart",
"restore",
"restrict",
"resume",
"return",
"returns",
"revert",
"revoke",
"role",
"rollback",
"rollup",
"row",
"rowcount",
"rowguidcol",
"rows",
"rule",
"sample",
"save",
"schema",
"schemabinding",
"scoped",
"scroll",
"secondary",
"security",
"securityaudit",
"select",
"semantickeyphrasetable",
"semanticsimilaritydetailstable",
"semanticsimilaritytable",
"send",
"sent",
"sequence",
"server",
"session",
"set",
"sets",
"setuser",
"shutdown",
"simple",
"smallint",
"smallmoney",
"snapshot",
"sort_in_tempdb",
"sql",
"standard",
"start",
"started",
"state",
"statement",
"static",
"statistics",
"statistics_norecompute",
"status",
"stopped",
"supported",
"symmetric",
"sysname",
"system",
"system_time",
"system_versioning",
"table",
"tablesample",
"take",
"target",
"textimage_on",
"textsize",
"then",
"thesaurus",
"throw",
"time",
"timestamp",
"tinyint",
"to",
"top",
"tran",
"transaction",
"trigger",
"truncate",
"trustworthy",
"try",
"tsql",
"type",
"union",
"unique",
"uniqueidentifier",
"unlimited",
"updatetext",
"use",
"user",
"using",
"value",
"values",
"varchar",
"varying",
"version",
"view",
"waitfor",
"weight",
"when",
"where",
"while",
"with",
"within",
"within group",
"without",
"writetext",
"xact_abort",
"xml",
"zone"
};
internal static CompletionItem[] GetDefaultCompletionItems(
int row,
int startColumn,
int endColumn)
{
var completionItems = new CompletionItem[DefaultCompletionText.Length];
for (int i = 0; i < DefaultCompletionText.Length; ++i)
{
completionItems[i] = CreateDefaultCompletionItem(
DefaultCompletionText[i].ToUpper(),
row,
startColumn,
endColumn);
}
return completionItems;
}
private static CompletionItem CreateDefaultCompletionItem(
string label,
int row,
int startColumn,
int endColumn)
{
return new CompletionItem()
{
Label = label,
Kind = CompletionItemKind.Keyword,
Detail = label + " keyword",
TextEdit = new TextEdit
{
NewText = label,
Range = new Range
{
Start = new Position
{
Line = row,
Character = startColumn
},
End = new Position
{
Line = row,
Character = endColumn
}
}
}
};
}
///
/// Converts a list of Declaration objects to CompletionItem objects
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
///
///
///
///
///
internal static CompletionItem[] ConvertDeclarationsToCompletionItems(
IEnumerable suggestions,
int row,
int startColumn,
int endColumn)
{
List completions = new List();
foreach (var autoCompleteItem in suggestions)
{
// convert the completion item candidates into CompletionItems
completions.Add(new CompletionItem()
{
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
}
}
}
});
}
return completions.ToArray();
}
///
/// Preinitialize the parser and binder with common metadata.
/// This should front load the long binding wait to the time the
/// connection is established. Once this is completed other binding
/// requests should be faster.
///
///
///
internal static void PrepopulateCommonMetadata(ConnectionInfo info, ScriptParseInfo scriptInfo)
{
if (scriptInfo.IsConnected)
{
var scriptFile = WorkspaceService.Instance.Workspace.GetFile(info.OwnerUri);
LanguageService.Instance.ParseAndBind(scriptFile, info);
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
{
try
{
scriptInfo.BuildingMetadataEvent.Reset();
// parse a simple statement that returns common metadata
ParseResult parseResult = Parser.Parse(
"select ",
scriptInfo.ParseOptions);
List parseResults = new List();
parseResults.Add(parseResult);
scriptInfo.Binder.Bind(
parseResults,
info.ConnectionDetails.DatabaseName,
BindMode.Batch);
// get the completion list from SQL Parser
var suggestions = Resolver.FindCompletions(
parseResult, 1, 8,
scriptInfo.MetadataDisplayInfoProvider);
// this forces lazy evaluation of the suggestion metadata
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
parseResult = Parser.Parse(
"exec ",
scriptInfo.ParseOptions);
parseResults = new List();
parseResults.Add(parseResult);
scriptInfo.Binder.Bind(
parseResults,
info.ConnectionDetails.DatabaseName,
BindMode.Batch);
// get the completion list from SQL Parser
suggestions = Resolver.FindCompletions(
parseResult, 1, 6,
scriptInfo.MetadataDisplayInfoProvider);
// this forces lazy evaluation of the suggestion metadata
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
}
catch
{
}
finally
{
scriptInfo.BuildingMetadataEvent.Set();
}
}
}
}
}
}