mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-26 09:35:38 -05:00
Merge branch 'release/ctp10'
This commit is contained in:
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
@@ -205,7 +206,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
var cancellationTask = Task.Run(() =>
|
||||
{
|
||||
source.Token.WaitHandle.WaitOne();
|
||||
source.Token.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
source.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
});
|
||||
|
||||
var openTask = Task.Run(async () => {
|
||||
@@ -367,7 +375,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List all databases on the server specified
|
||||
/// </summary>
|
||||
@@ -393,18 +401,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails));
|
||||
connection.Open();
|
||||
|
||||
DbCommand command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT name FROM sys.databases ORDER BY database_id ASC";
|
||||
command.CommandTimeout = 15;
|
||||
command.CommandType = CommandType.Text;
|
||||
var reader = command.ExecuteReader();
|
||||
|
||||
List<string> results = new List<string>();
|
||||
while (reader.Read())
|
||||
var systemDatabases = new string[] {"master", "model", "msdb", "tempdb"};
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
results.Add(reader[0].ToString());
|
||||
command.CommandText = "SELECT name FROM sys.databases ORDER BY name ASC";
|
||||
command.CommandTimeout = 15;
|
||||
command.CommandType = CommandType.Text;
|
||||
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
results.Add(reader[0].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put system databases at the top of the list
|
||||
results =
|
||||
results.Where(s => systemDatabases.Any(s.Equals)).Concat(
|
||||
results.Where(s => systemDatabases.All(x => !s.Equals(x)))).ToList();
|
||||
|
||||
connection.Close();
|
||||
|
||||
ListDatabasesResponse response = new ListDatabasesResponse();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
@@ -215,6 +216,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
|
||||
public static bool IsRetryableNetworkConnectivityError(int errorNumber)
|
||||
{
|
||||
// .NET core has a bug on OSX/Linux that makes this error number always zero (issue 12472)
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return errorNumber != 0 && _retryableNetworkConnectivityErrors.Contains(errorNumber);
|
||||
}
|
||||
return _retryableNetworkConnectivityErrors.Contains(errorNumber);
|
||||
}
|
||||
|
||||
|
||||
@@ -150,14 +150,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
Capabilities = new ServerCapabilities
|
||||
{
|
||||
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
||||
DefinitionProvider = true,
|
||||
ReferencesProvider = true,
|
||||
DocumentHighlightProvider = true,
|
||||
HoverProvider = true,
|
||||
DefinitionProvider = false,
|
||||
ReferencesProvider = false,
|
||||
DocumentHighlightProvider = false,
|
||||
HoverProvider = true,
|
||||
CompletionProvider = new CompletionOptions
|
||||
{
|
||||
ResolveProvider = true,
|
||||
TriggerCharacters = new string[] { ".", "-", ":", "\\", ",", " " }
|
||||
TriggerCharacters = new string[] { ".", "-", ":", "\\" }
|
||||
},
|
||||
SignatureHelpProvider = new SignatureHelpOptions
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -23,9 +23,16 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
/// </summary>
|
||||
internal static void Main(string[] args)
|
||||
{
|
||||
// read command-line arguments
|
||||
CommandOptions commandOptions = new CommandOptions(args);
|
||||
if (commandOptions.ShouldExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// turn on Verbose logging during early development
|
||||
// we need to switch to Normal when preparing for public preview
|
||||
Logger.Initialize(minimumLogLevel: LogLevel.Verbose);
|
||||
Logger.Initialize(minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging);
|
||||
Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host");
|
||||
|
||||
// set up the host details and profile paths
|
||||
|
||||
@@ -31,7 +31,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating readers/writrs for the output of the batch
|
||||
/// Local time when the execution and retrieval of files is finished
|
||||
/// </summary>
|
||||
private DateTime executionEndTime;
|
||||
|
||||
/// <summary>
|
||||
/// Local time when the execution starts, specifically when the object is created
|
||||
/// </summary>
|
||||
private DateTime executionStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating readers/writers for the output of the batch
|
||||
/// </summary>
|
||||
private readonly IFileStreamFactory outputFileFactory;
|
||||
|
||||
@@ -69,6 +79,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
public string BatchText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Localized timestamp for when the execution completed.
|
||||
/// Stored in UTC ISO 8601 format; should be localized before displaying to any user
|
||||
/// </summary>
|
||||
public string ExecutionEndTimeStamp { get { return executionEndTime.ToString("o"); } }
|
||||
|
||||
/// <summary>
|
||||
/// Localized timestamp for how long it took for the execution to complete
|
||||
/// </summary>
|
||||
public string ExecutionElapsedTime
|
||||
{
|
||||
get
|
||||
{
|
||||
TimeSpan elapsedTime = executionEndTime - executionStartTime;
|
||||
return elapsedTime.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localized timestamp for when the execution began.
|
||||
/// Stored in UTC ISO 8601 format; should be localized before displaying to any user
|
||||
/// </summary>
|
||||
public string ExecutionStartTimeStamp { get { return executionStartTime.ToString("o"); } }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this batch has an error
|
||||
/// </summary>
|
||||
@@ -90,7 +124,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// The result sets of the batch execution
|
||||
/// </summary>
|
||||
public IEnumerable<ResultSet> ResultSets
|
||||
public IList<ResultSet> ResultSets
|
||||
{
|
||||
get { return resultSets; }
|
||||
}
|
||||
@@ -136,14 +170,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
try
|
||||
{
|
||||
DbCommand command = null;
|
||||
|
||||
// Register the message listener to *this instance* of the batch
|
||||
// Note: This is being done to associate messages with batches
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
// Register the message listener to *this instance* of the batch
|
||||
// Note: This is being done to associate messages with batches
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage += StoreDbMessage;
|
||||
command = sqlConn.GetUnderlyingConnection().CreateCommand();
|
||||
|
||||
// Add a handler for when the command completes
|
||||
SqlCommand sqlCommand = (SqlCommand) command;
|
||||
sqlCommand.StatementCompleted += StatementCompletedHandler;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -151,7 +188,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
// Make sure we aren't using a ReliableCommad since we do not want automatic retry
|
||||
Debug.Assert(!(command is ReliableSqlConnection.ReliableSqlCommand), "ReliableSqlCommand command should not be used to execute queries");
|
||||
Debug.Assert(!(command is ReliableSqlConnection.ReliableSqlCommand),
|
||||
"ReliableSqlCommand command should not be used to execute queries");
|
||||
|
||||
// Create a command that we'll use for executing the query
|
||||
using (command)
|
||||
@@ -159,6 +197,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
command.CommandText = BatchText;
|
||||
command.CommandType = CommandType.Text;
|
||||
command.CommandTimeout = 0;
|
||||
executionStartTime = DateTime.Now;
|
||||
|
||||
// Execute the command to get back a reader
|
||||
using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken))
|
||||
@@ -168,24 +207,26 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries)
|
||||
if (!reader.HasRows && reader.FieldCount == 0)
|
||||
{
|
||||
// Create a message with the number of affected rows -- IF the query affects rows
|
||||
resultMessages.Add(new ResultMessage(reader.RecordsAffected >= 0
|
||||
? SR.QueryServiceAffectedRows(reader.RecordsAffected)
|
||||
: SR.QueryServiceCompletedSuccessfully));
|
||||
continue;
|
||||
}
|
||||
|
||||
// This resultset has results (ie, SELECT/etc queries)
|
||||
// Read until we hit the end of the result set
|
||||
ResultSet resultSet = new ResultSet(reader, outputFileFactory);
|
||||
await resultSet.ReadResultToEnd(cancellationToken);
|
||||
|
||||
|
||||
// Add the result set to the results of the query
|
||||
resultSets.Add(resultSet);
|
||||
|
||||
// Read until we hit the end of the result set
|
||||
await resultSet.ReadResultToEnd(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Add a message for the number of rows the query returned
|
||||
resultMessages.Add(new ResultMessage(SR.QueryServiceAffectedRows(resultSet.RowCount)));
|
||||
} while (await reader.NextResultAsync(cancellationToken));
|
||||
|
||||
// If there were no messages, for whatever reason (NO COUNT set, messages
|
||||
// were emitted, records returned), output a "successful" message
|
||||
if (resultMessages.Count == 0)
|
||||
{
|
||||
resultMessages.Add(new ResultMessage(SR.QueryServiceCompletedSuccessfully));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,9 +235,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
HasError = true;
|
||||
UnwrapDbException(dbe);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
resultMessages.Add(new ResultMessage(SR.QueryServiceQueryCancelled));
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HasError = true;
|
||||
resultMessages.Add(new ResultMessage(SR.QueryServiceQueryFailed(e.Message)));
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
@@ -210,6 +257,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
// Mark that we have executed
|
||||
HasExecuted = true;
|
||||
executionEndTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +284,29 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Handler for when the StatementCompleted event is fired for this batch's command. This
|
||||
/// will be executed ONLY when there is a rowcount to report. If this event is not fired
|
||||
/// either NOCOUNT has been set or the command doesn't affect records.
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender of the event</param>
|
||||
/// <param name="args">Arguments for the event</param>
|
||||
private void StatementCompletedHandler(object sender, StatementCompletedEventArgs args)
|
||||
{
|
||||
// Add a message for the number of rows the query returned
|
||||
string message;
|
||||
if (args.RecordCount == 1)
|
||||
{
|
||||
message = SR.QueryServiceAffectedOneRow;
|
||||
}
|
||||
else
|
||||
{
|
||||
message = SR.QueryServiceAffectedRows(args.RecordCount);
|
||||
}
|
||||
|
||||
resultMessages.Add(new ResultMessage(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate handler for storing messages that are returned from the server
|
||||
/// NOTE: Only messages that are below a certain severity will be returned via this
|
||||
@@ -260,15 +331,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
SqlException se = dbe as SqlException;
|
||||
if (se != null)
|
||||
{
|
||||
foreach (var error in se.Errors)
|
||||
var errors = se.Errors.Cast<SqlError>().ToList();
|
||||
// Detect user cancellation errors
|
||||
if (errors.Any(error => error.Class == 11 && error.Number == 0))
|
||||
{
|
||||
SqlError sqlError = error as SqlError;
|
||||
if (sqlError != null)
|
||||
// User cancellation error, add the single message
|
||||
HasError = false;
|
||||
resultMessages.Add(new ResultMessage(SR.QueryServiceQueryCancelled));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a user cancellation error, add all
|
||||
foreach (var error in errors)
|
||||
{
|
||||
int lineNumber = sqlError.LineNumber + Selection.StartLine;
|
||||
int lineNumber = error.LineNumber + Selection.StartLine;
|
||||
string message = string.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}",
|
||||
sqlError.Number, sqlError.Class, sqlError.State, lineNumber,
|
||||
Environment.NewLine, sqlError.Message);
|
||||
error.Number, error.Class, error.State, lineNumber,
|
||||
Environment.NewLine, error.Message);
|
||||
resultMessages.Add(new ResultMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// </summary>
|
||||
public class BatchSummary
|
||||
{
|
||||
/// <summary>
|
||||
/// Localized timestamp for how long it took for the execution to complete
|
||||
/// </summary>
|
||||
public string ExecutionElapsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Localized timestamp for when the execution completed.
|
||||
/// </summary>
|
||||
public string ExecutionEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Localized timestamp for when the execution started.
|
||||
/// </summary>
|
||||
public string ExecutionStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the batch was successful. True indicates errors, false indicates success
|
||||
/// </summary>
|
||||
|
||||
@@ -21,6 +21,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// Summaries of the result sets that were returned with the query
|
||||
/// </summary>
|
||||
public BatchSummary[] BatchSummaries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message, if any
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class QueryExecuteCompleteEvent
|
||||
|
||||
@@ -60,17 +60,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// Parameters to save results as CSV
|
||||
/// </summary>
|
||||
public class SaveResultsAsCsvRequestParams: SaveResultsRequestParams{
|
||||
|
||||
/// <summary>
|
||||
/// CSV - Write values in quotes
|
||||
/// </summary>
|
||||
public Boolean ValueInQuotes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The encoding of the file to save results in
|
||||
/// </summary>
|
||||
public string FileEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Include headers of columns in CSV
|
||||
/// </summary>
|
||||
@@ -95,6 +84,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
public string Messages { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error object for save result
|
||||
/// </summary>
|
||||
public class SaveResultRequestError
|
||||
{
|
||||
/// <summary>
|
||||
/// Error message
|
||||
/// </summary>
|
||||
public string message { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request type to save results as CSV
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// 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.IO;
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
internal static class FileUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if file exists and swallows exceptions, if any
|
||||
/// </summary>
|
||||
/// <param name="path"> path of the file</param>
|
||||
/// <returns></returns>
|
||||
internal static bool SafeFileExists(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Swallow exception
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file and swallows exceptions, if any
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
internal static void SafeFileDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Swallow exception, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,33 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for callback when a query completes or fails
|
||||
/// </summary>
|
||||
/// <param name="q">The query that completed</param>
|
||||
public delegate Task QueryAsyncEventHandler(Query q);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for callback when a query connection fails
|
||||
/// </summary>
|
||||
/// <param name="q">The query that completed</param>
|
||||
public delegate Task QueryAsyncErrorEventHandler(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when the query has completed successfully
|
||||
/// </summary>
|
||||
public event QueryAsyncEventHandler QueryCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when the query has failed
|
||||
/// </summary>
|
||||
public event QueryAsyncEventHandler QueryFailed;
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when the query connection has failed
|
||||
/// </summary>
|
||||
public event QueryAsyncErrorEventHandler QueryConnectionException;
|
||||
|
||||
/// <summary>
|
||||
/// The batches underneath this query
|
||||
/// </summary>
|
||||
@@ -116,6 +143,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return Batches.Select((batch, index) => new BatchSummary
|
||||
{
|
||||
Id = index,
|
||||
ExecutionStart = batch.ExecutionStartTimeStamp,
|
||||
ExecutionEnd = batch.ExecutionEndTimeStamp,
|
||||
ExecutionElapsed = batch.ExecutionElapsedTime,
|
||||
HasError = batch.HasError,
|
||||
Messages = batch.ResultMessages.ToArray(),
|
||||
ResultSetSummaries = batch.ResultSummaries,
|
||||
@@ -124,6 +154,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
|
||||
internal Task ExecutionTask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the query has completed executed, regardless of success or failure
|
||||
/// </summary>
|
||||
@@ -167,10 +199,44 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
cancellationSource.Cancel();
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
ExecutionTask = Task.Run(ExecuteInternal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a subset of the result sets
|
||||
/// </summary>
|
||||
/// <param name="batchIndex">The index for selecting the batch item</param>
|
||||
/// <param name="resultSetIndex">The index for selecting the result set</param>
|
||||
/// <param name="startRow">The starting row of the results</param>
|
||||
/// <param name="rowCount">How many rows to retrieve</param>
|
||||
/// <returns>A subset of results</returns>
|
||||
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, int startRow, int rowCount)
|
||||
{
|
||||
// Sanity check that the results are available
|
||||
if (!HasExecuted)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceSubsetNotCompleted);
|
||||
}
|
||||
|
||||
// Sanity check to make sure that the batch is within bounds
|
||||
if (batchIndex < 0 || batchIndex >= Batches.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(batchIndex), SR.QueryServiceSubsetBatchOutOfRange);
|
||||
}
|
||||
|
||||
return Batches[batchIndex].GetSubset(resultSetIndex, startRow, rowCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Executes this query asynchronously and collects all result sets
|
||||
/// </summary>
|
||||
public async Task Execute()
|
||||
private async Task ExecuteInternal()
|
||||
{
|
||||
// Mark that we've internally executed
|
||||
hasExecuteBeenCalled = true;
|
||||
@@ -186,7 +252,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// TODO: Don't create a new connection every time, see TFS #834978
|
||||
using (DbConnection conn = editorConnection.Factory.CreateSqlConnection(connectionString))
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
this.HasExecuted = true;
|
||||
if (QueryConnectionException != null)
|
||||
{
|
||||
await QueryConnectionException(exception.Message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
@@ -202,6 +280,20 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
await b.Execute(conn, cancellationSource.Token);
|
||||
}
|
||||
|
||||
// Call the query execution callback
|
||||
if (QueryCompleted != null)
|
||||
{
|
||||
await QueryCompleted(this);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Call the query failure callback
|
||||
if (QueryFailed != null)
|
||||
{
|
||||
await QueryFailed(this);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -227,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
throw new InvalidOperationException(SR.QueryServiceMessageSenderNotSql);
|
||||
}
|
||||
|
||||
foreach(SqlError error in args.Errors)
|
||||
foreach (SqlError error in args.Errors)
|
||||
{
|
||||
// Did the database context change (error code 5701)?
|
||||
if (error.Number == DatabaseContextChangeErrorNumber)
|
||||
@@ -237,31 +329,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a subset of the result sets
|
||||
/// </summary>
|
||||
/// <param name="batchIndex">The index for selecting the batch item</param>
|
||||
/// <param name="resultSetIndex">The index for selecting the result set</param>
|
||||
/// <param name="startRow">The starting row of the results</param>
|
||||
/// <param name="rowCount">How many rows to retrieve</param>
|
||||
/// <returns>A subset of results</returns>
|
||||
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, int startRow, int rowCount)
|
||||
{
|
||||
// Sanity check that the results are available
|
||||
if (!HasExecuted)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceSubsetNotCompleted);
|
||||
}
|
||||
|
||||
// Sanity check to make sure that the batch is within bounds
|
||||
if (batchIndex < 0 || batchIndex >= Batches.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(batchIndex), SR.QueryServiceSubsetBatchOutOfRange);
|
||||
}
|
||||
|
||||
return Batches[batchIndex].GetSubset(resultSetIndex, startRow, rowCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
@@ -16,7 +15,6 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
@@ -129,19 +127,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
public async Task HandleExecuteRequest(QueryExecuteParams executeParams,
|
||||
RequestContext<QueryExecuteResult> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get a query new active query
|
||||
Query newQuery = await CreateAndActivateNewQuery(executeParams, requestContext);
|
||||
// Get a query new active query
|
||||
Query newQuery = await CreateAndActivateNewQuery(executeParams, requestContext);
|
||||
|
||||
// Execute the query
|
||||
await ExecuteAndCompleteQuery(executeParams, requestContext, newQuery);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Dump any unexpected exceptions as errors
|
||||
await requestContext.SendError(e.Message);
|
||||
}
|
||||
// Execute the query -- asynchronously
|
||||
await ExecuteAndCompleteQuery(executeParams, requestContext, newQuery);
|
||||
}
|
||||
|
||||
public async Task HandleResultSubsetRequest(QueryExecuteSubsetParams subsetParams,
|
||||
@@ -239,21 +229,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel the query
|
||||
// Cancel the query and send a success message
|
||||
result.Cancel();
|
||||
result.Dispose();
|
||||
|
||||
// Attempt to dispose the query
|
||||
if (!ActiveQueries.TryRemove(cancelParams.OwnerUri, out result))
|
||||
{
|
||||
// It really shouldn't be possible to get to this scenario, but we'll cover it anyhow
|
||||
await requestContext.SendResult(new QueryCancelResult
|
||||
{
|
||||
Messages = SR.QueryServiceCancelDisposeFailed
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await requestContext.SendResult(new QueryCancelResult());
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
@@ -273,7 +250,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in CSV format
|
||||
/// </summary>
|
||||
public async Task HandleSaveResultsAsCsvRequest(SaveResultsAsCsvRequestParams saveParams,
|
||||
internal async Task HandleSaveResultsAsCsvRequest(SaveResultsAsCsvRequestParams saveParams,
|
||||
RequestContext<SaveResultRequestResult> requestContext)
|
||||
{
|
||||
// retrieve query for OwnerUri
|
||||
@@ -286,67 +263,39 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
});
|
||||
return;
|
||||
}
|
||||
try
|
||||
|
||||
|
||||
ResultSet selectedResultSet = result.Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
if (!selectedResultSet.IsBeingDisposed)
|
||||
{
|
||||
using (StreamWriter csvFile = new StreamWriter(File.Open(saveParams.FilePath, FileMode.Create)))
|
||||
// Create SaveResults object and add success and error handlers to respective events
|
||||
SaveResults saveAsCsv = new SaveResults();
|
||||
|
||||
SaveResults.AsyncSaveEventHandler successHandler = async message =>
|
||||
{
|
||||
// get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex];
|
||||
int columnCount = 0;
|
||||
int rowCount = 0;
|
||||
int columnStartIndex = 0;
|
||||
int rowStartIndex = 0;
|
||||
|
||||
// set column, row counts depending on whether save request is for entire result set or a subset
|
||||
if (SaveResults.isSaveSelection(saveParams))
|
||||
{
|
||||
columnCount = saveParams.ColumnEndIndex.Value - saveParams.ColumnStartIndex.Value + 1;
|
||||
rowCount = saveParams.RowEndIndex.Value - saveParams.RowStartIndex.Value + 1;
|
||||
columnStartIndex = saveParams.ColumnStartIndex.Value;
|
||||
rowStartIndex =saveParams.RowStartIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnCount = selectedResultSet.Columns.Length;
|
||||
rowCount = (int)selectedResultSet.RowCount;
|
||||
}
|
||||
|
||||
// write column names if include headers option is chosen
|
||||
if (saveParams.IncludeHeaders)
|
||||
{
|
||||
await csvFile.WriteLineAsync( string.Join( ",", selectedResultSet.Columns.Skip(columnStartIndex).Take(columnCount).Select( column =>
|
||||
SaveResults.EncodeCsvField(column.ColumnName) ?? string.Empty)));
|
||||
}
|
||||
|
||||
// retrieve rows and write as csv
|
||||
ResultSetSubset resultSubset = await result.GetSubset(saveParams.BatchIndex, saveParams.ResultSetIndex, rowStartIndex, rowCount);
|
||||
foreach (var row in resultSubset.Rows)
|
||||
{
|
||||
await csvFile.WriteLineAsync( string.Join( ",", row.Skip(columnStartIndex).Take(columnCount).Select( field =>
|
||||
SaveResults.EncodeCsvField((field != null) ? field.ToString(): "NULL"))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Successfully wrote file, send success result
|
||||
await requestContext.SendResult(new SaveResultRequestResult { Messages = null });
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// Delete file when exception occurs
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
selectedResultSet.RemoveSaveTask(saveParams.FilePath);
|
||||
await requestContext.SendResult(new SaveResultRequestResult { Messages = message });
|
||||
};
|
||||
saveAsCsv.SaveCompleted += successHandler;
|
||||
SaveResults.AsyncSaveEventHandler errorHandler = async message =>
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
await requestContext.SendError(ex.Message);
|
||||
selectedResultSet.RemoveSaveTask(saveParams.FilePath);
|
||||
await requestContext.SendError(new SaveResultRequestError { message = message });
|
||||
};
|
||||
saveAsCsv.SaveFailed += errorHandler;
|
||||
|
||||
saveAsCsv.SaveResultSetAsCsv(saveParams, requestContext, result);
|
||||
|
||||
// Associate the ResultSet with the save task
|
||||
selectedResultSet.AddSaveTask(saveParams.FilePath, saveAsCsv.SaveTask);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in JSON format
|
||||
/// </summary>
|
||||
public async Task HandleSaveResultsAsJsonRequest(SaveResultsAsJsonRequestParams saveParams,
|
||||
internal async Task HandleSaveResultsAsJsonRequest(SaveResultsAsJsonRequestParams saveParams,
|
||||
RequestContext<SaveResultRequestResult> requestContext)
|
||||
{
|
||||
// retrieve query for OwnerUri
|
||||
@@ -359,73 +308,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
});
|
||||
return;
|
||||
}
|
||||
try
|
||||
|
||||
ResultSet selectedResultSet = result.Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
if (!selectedResultSet.IsBeingDisposed)
|
||||
{
|
||||
using (StreamWriter jsonFile = new StreamWriter(File.Open(saveParams.FilePath, FileMode.Create)))
|
||||
using (JsonWriter jsonWriter = new JsonTextWriter(jsonFile) )
|
||||
// Create SaveResults object and add success and error handlers to respective events
|
||||
SaveResults saveAsJson = new SaveResults();
|
||||
SaveResults.AsyncSaveEventHandler successHandler = async message =>
|
||||
{
|
||||
jsonWriter.Formatting = Formatting.Indented;
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
// get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = selectedBatch.ResultSets.ToList()[saveParams.ResultSetIndex];
|
||||
int rowCount = 0;
|
||||
int rowStartIndex = 0;
|
||||
int columnStartIndex = 0;
|
||||
int columnEndIndex = 0;
|
||||
|
||||
// set column, row counts depending on whether save request is for entire result set or a subset
|
||||
if (SaveResults.isSaveSelection(saveParams))
|
||||
{
|
||||
|
||||
rowCount = saveParams.RowEndIndex.Value - saveParams.RowStartIndex.Value + 1;
|
||||
rowStartIndex = saveParams.RowStartIndex.Value;
|
||||
columnStartIndex = saveParams.ColumnStartIndex.Value;
|
||||
columnEndIndex = saveParams.ColumnEndIndex.Value + 1 ; // include the last column
|
||||
}
|
||||
else
|
||||
{
|
||||
rowCount = (int)selectedResultSet.RowCount;
|
||||
columnEndIndex = selectedResultSet.Columns.Length;
|
||||
}
|
||||
|
||||
// retrieve rows and write as json
|
||||
ResultSetSubset resultSubset = await result.GetSubset(saveParams.BatchIndex, saveParams.ResultSetIndex, rowStartIndex, rowCount);
|
||||
foreach (var row in resultSubset.Rows)
|
||||
{
|
||||
jsonWriter.WriteStartObject();
|
||||
for (int i = columnStartIndex ; i < columnEndIndex; i++)
|
||||
{
|
||||
//get column name
|
||||
DbColumnWrapper col = selectedResultSet.Columns[i];
|
||||
string val = row[i]?.ToString();
|
||||
jsonWriter.WritePropertyName(col.ColumnName);
|
||||
if (val == null)
|
||||
{
|
||||
jsonWriter.WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonWriter.WriteValue(val);
|
||||
}
|
||||
}
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
jsonWriter.WriteEndArray();
|
||||
}
|
||||
|
||||
await requestContext.SendResult(new SaveResultRequestResult { Messages = null });
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// Delete file when exception occurs
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
selectedResultSet.RemoveSaveTask(saveParams.FilePath);
|
||||
await requestContext.SendResult(new SaveResultRequestResult { Messages = message });
|
||||
};
|
||||
saveAsJson.SaveCompleted += successHandler;
|
||||
SaveResults.AsyncSaveEventHandler errorHandler = async message =>
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
await requestContext.SendError(ex.Message);
|
||||
selectedResultSet.RemoveSaveTask(saveParams.FilePath);
|
||||
await requestContext.SendError(new SaveResultRequestError { message = message });
|
||||
};
|
||||
saveAsJson.SaveFailed += errorHandler;
|
||||
|
||||
saveAsJson.SaveResultSetAsJson(saveParams, requestContext, result);
|
||||
|
||||
// Associate the ResultSet with the save task
|
||||
selectedResultSet.AddSaveTask(saveParams.FilePath, saveAsJson.SaveTask);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -440,10 +347,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
ConnectionInfo connectionInfo;
|
||||
if (!ConnectionService.TryFindConnection(executeParams.OwnerUri, out connectionInfo))
|
||||
{
|
||||
await requestContext.SendResult(new QueryExecuteResult
|
||||
{
|
||||
Messages = SR.QueryServiceQueryInvalidOwnerUri
|
||||
});
|
||||
await requestContext.SendError(SR.QueryServiceQueryInvalidOwnerUri);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -463,49 +367,47 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
string queryText;
|
||||
|
||||
if (executeParams.QuerySelection != null)
|
||||
if (executeParams.QuerySelection != null)
|
||||
{
|
||||
string[] queryTextArray = queryFile.GetLinesInRange(
|
||||
new BufferRange(
|
||||
new BufferPosition(
|
||||
executeParams.QuerySelection.StartLine + 1,
|
||||
executeParams.QuerySelection.StartLine + 1,
|
||||
executeParams.QuerySelection.StartColumn + 1
|
||||
),
|
||||
),
|
||||
new BufferPosition(
|
||||
executeParams.QuerySelection.EndLine + 1,
|
||||
executeParams.QuerySelection.EndLine + 1,
|
||||
executeParams.QuerySelection.EndColumn + 1
|
||||
)
|
||||
)
|
||||
);
|
||||
queryText = queryTextArray.Aggregate((a, b) => a + '\r' + '\n' + b);
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
queryText = queryFile.Contents;
|
||||
}
|
||||
|
||||
|
||||
// If we can't add the query now, it's assumed the query is in progress
|
||||
Query newQuery = new Query(queryText, connectionInfo, settings, BufferFileFactory);
|
||||
if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery))
|
||||
{
|
||||
await requestContext.SendResult(new QueryExecuteResult
|
||||
{
|
||||
Messages = SR.QueryServiceQueryInProgress
|
||||
});
|
||||
await requestContext.SendError(SR.QueryServiceQueryInProgress);
|
||||
newQuery.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
return newQuery;
|
||||
}
|
||||
catch (ArgumentException ane)
|
||||
catch (Exception e)
|
||||
{
|
||||
await requestContext.SendResult(new QueryExecuteResult { Messages = ane.Message });
|
||||
await requestContext.SendError(e.Message);
|
||||
return null;
|
||||
}
|
||||
// Any other exceptions will fall through here and be collected at the end
|
||||
}
|
||||
|
||||
private async Task ExecuteAndCompleteQuery(QueryExecuteParams executeParams, RequestContext<QueryExecuteResult> requestContext, Query query)
|
||||
private static async Task ExecuteAndCompleteQuery(QueryExecuteParams executeParams, RequestContext<QueryExecuteResult> requestContext, Query query)
|
||||
{
|
||||
// Skip processing if the query is null
|
||||
if (query == null)
|
||||
@@ -513,21 +415,41 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch the query and respond with successfully launching it
|
||||
Task executeTask = query.Execute();
|
||||
// Setup the query completion/failure callbacks
|
||||
Query.QueryAsyncEventHandler callback = async q =>
|
||||
{
|
||||
// Send back the results
|
||||
QueryExecuteCompleteParams eventParams = new QueryExecuteCompleteParams
|
||||
{
|
||||
OwnerUri = executeParams.OwnerUri,
|
||||
BatchSummaries = q.BatchSummaries
|
||||
};
|
||||
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
|
||||
};
|
||||
|
||||
Query.QueryAsyncErrorEventHandler errorCallback = async errorMessage =>
|
||||
{
|
||||
// Send back the error message
|
||||
QueryExecuteCompleteParams eventParams = new QueryExecuteCompleteParams
|
||||
{
|
||||
OwnerUri = executeParams.OwnerUri,
|
||||
Message = errorMessage
|
||||
};
|
||||
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
|
||||
};
|
||||
|
||||
query.QueryCompleted += callback;
|
||||
query.QueryFailed += callback;
|
||||
query.QueryConnectionException += errorCallback;
|
||||
|
||||
// Launch this as an asynchronous task
|
||||
query.Execute();
|
||||
|
||||
// Send back a result showing we were successful
|
||||
await requestContext.SendResult(new QueryExecuteResult
|
||||
{
|
||||
Messages = null
|
||||
});
|
||||
|
||||
// Wait for query execution and then send back the results
|
||||
await Task.WhenAll(executeTask);
|
||||
QueryExecuteCompleteParams eventParams = new QueryExecuteCompleteParams
|
||||
{
|
||||
OwnerUri = executeParams.OwnerUri,
|
||||
BatchSummaries = query.BatchSummaries
|
||||
};
|
||||
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
@@ -43,22 +45,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
private readonly IFileStreamFactory fileStreamFactory;
|
||||
|
||||
/// <summary>
|
||||
/// File stream reader that will be reused to make rapid-fire retrieval of result subsets
|
||||
/// quick and low perf impact.
|
||||
/// </summary>
|
||||
private IFileStreamReader fileStreamReader;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the result set has been read in from the database
|
||||
/// </summary>
|
||||
private bool hasBeenRead;
|
||||
|
||||
/// <summary>
|
||||
/// Whether resultSet is a 'for xml' or 'for json' result
|
||||
/// </summary>
|
||||
private bool isSingleColumnXmlJsonResultSet;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the temporary file we're using to output these results in
|
||||
/// </summary>
|
||||
private readonly string outputFileName;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the resultSet is in the process of being disposed
|
||||
/// </summary>
|
||||
private bool isBeingDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// All save tasks currently saving this ResultSet
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<string, Task> saveTasks;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -80,10 +91,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Store the factory
|
||||
fileStreamFactory = factory;
|
||||
hasBeenRead = false;
|
||||
saveTasks = new ConcurrentDictionary<string, Task>();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Whether the resultSet is in the process of being disposed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool IsBeingDisposed
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBeingDisposed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The columns for this result set
|
||||
/// </summary>
|
||||
@@ -114,18 +138,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
public long RowCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rows of this result set
|
||||
/// </summary>
|
||||
public IEnumerable<string[]> Rows
|
||||
{
|
||||
get
|
||||
{
|
||||
return FileOffsets.Select(
|
||||
offset => fileStreamReader.ReadRow(offset, Columns).Select(cell => cell.DisplayValue).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
@@ -139,7 +151,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
public Task<ResultSetSubset> GetSubset(int startRow, int rowCount)
|
||||
{
|
||||
// Sanity check to make sure that the results have been read beforehand
|
||||
if (!hasBeenRead || fileStreamReader == null)
|
||||
if (!hasBeenRead)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceResultSetNotRead);
|
||||
}
|
||||
@@ -156,14 +168,32 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
// Figure out which rows we need to read back
|
||||
IEnumerable<long> rowOffsets = FileOffsets.Skip(startRow).Take(rowCount);
|
||||
|
||||
// Iterate over the rows we need and process them into output
|
||||
string[][] rows = rowOffsets.Select(rowOffset =>
|
||||
fileStreamReader.ReadRow(rowOffset, Columns).Select(cell => cell.DisplayValue).ToArray())
|
||||
.ToArray();
|
||||
string[][] rows;
|
||||
|
||||
using (IFileStreamReader fileStreamReader = fileStreamFactory.GetReader(outputFileName))
|
||||
{
|
||||
// If result set is 'for xml' or 'for json',
|
||||
// Concatenate all the rows together into one row
|
||||
if (isSingleColumnXmlJsonResultSet)
|
||||
{
|
||||
// Iterate over all the rows and process them into a list of string builders
|
||||
IEnumerable<string> rowValues = FileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue);
|
||||
rows = new[] { new[] { string.Join(string.Empty, rowValues) } };
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Figure out which rows we need to read back
|
||||
IEnumerable<long> rowOffsets = FileOffsets.Skip(startRow).Take(rowCount);
|
||||
|
||||
// Iterate over the rows we need and process them into output
|
||||
rows = rowOffsets.Select(rowOffset =>
|
||||
fileStreamReader.ReadRow(rowOffset, Columns).Select(cell => cell.DisplayValue).ToArray())
|
||||
.ToArray();
|
||||
|
||||
}
|
||||
}
|
||||
// Retrieve the subset of the results as per the request
|
||||
return new ResultSetSubset
|
||||
{
|
||||
@@ -179,6 +209,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <param name="cancellationToken">Cancellation token for cancelling the query</param>
|
||||
public async Task ReadResultToEnd(CancellationToken cancellationToken)
|
||||
{
|
||||
// Mark that result has been read
|
||||
hasBeenRead = true;
|
||||
|
||||
// Open a writer for the file
|
||||
using (IFileStreamWriter fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxXmlCharsToStore))
|
||||
{
|
||||
@@ -199,10 +232,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
// Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata
|
||||
SingleColumnXmlJsonResultSet();
|
||||
|
||||
// Mark that result has been read
|
||||
hasBeenRead = true;
|
||||
fileStreamReader = fileStreamFactory.GetReader(outputFileName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -222,13 +251,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
isBeingDisposed = true;
|
||||
// Check if saveTasks are running for this ResultSet
|
||||
if (!saveTasks.IsEmpty)
|
||||
{
|
||||
fileStreamReader?.Dispose();
|
||||
fileStreamFactory.DisposeFile(outputFileName);
|
||||
// Wait for tasks to finish before disposing ResultSet
|
||||
Task.WhenAll(saveTasks.Values.ToArray()).ContinueWith((antecedent) =>
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
fileStreamFactory.DisposeFile(outputFileName);
|
||||
}
|
||||
disposed = true;
|
||||
isBeingDisposed = false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// If saveTasks is empty, continue with dispose
|
||||
if (disposing)
|
||||
{
|
||||
fileStreamFactory.DisposeFile(outputFileName);
|
||||
}
|
||||
disposed = true;
|
||||
isBeingDisposed = false;
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -243,19 +290,43 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
private void SingleColumnXmlJsonResultSet() {
|
||||
|
||||
if (Columns?.Length == 1)
|
||||
if (Columns?.Length == 1 && RowCount != 0)
|
||||
{
|
||||
if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal))
|
||||
{
|
||||
Columns[0].IsXml = true;
|
||||
isSingleColumnXmlJsonResultSet = true;
|
||||
RowCount = 1;
|
||||
}
|
||||
else if (Columns[0].ColumnName.Equals(NameOfForJSONColumn, StringComparison.Ordinal))
|
||||
{
|
||||
Columns[0].IsJson = true;
|
||||
isSingleColumnXmlJsonResultSet = true;
|
||||
RowCount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods to Add and Remove save tasks
|
||||
internal void AddSaveTask(string key, Task saveTask)
|
||||
{
|
||||
saveTasks.TryAdd(key, saveTask);
|
||||
}
|
||||
|
||||
internal void RemoveSaveTask(string key)
|
||||
{
|
||||
Task completedTask;
|
||||
saveTasks.TryRemove(key, out completedTask);
|
||||
}
|
||||
|
||||
internal Task GetSaveTask(string key)
|
||||
{
|
||||
Task completedTask;
|
||||
saveTasks.TryRemove(key, out completedTask);
|
||||
return completedTask;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,47 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
internal class SaveResults{
|
||||
internal class SaveResults
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of rows being read from the ResultSubset in one read
|
||||
/// </summary>
|
||||
private const int BatchSize = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Save Task that asynchronously writes ResultSet to file
|
||||
/// </summary>
|
||||
internal Task SaveTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event Handler for save events
|
||||
/// </summary>
|
||||
/// <param name="message"> Message to be returned to client</param>
|
||||
/// <returns></returns>
|
||||
internal delegate Task AsyncSaveEventHandler(string message);
|
||||
|
||||
/// <summary>
|
||||
/// A successful save event
|
||||
/// </summary>
|
||||
internal event AsyncSaveEventHandler SaveCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// A failed save event
|
||||
/// </summary>
|
||||
internal event AsyncSaveEventHandler SaveFailed;
|
||||
|
||||
/// Method ported from SSMS
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a single field for inserting into a CSV record. The following rules are applied:
|
||||
/// <list type="bullet">
|
||||
@@ -32,7 +64,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
internal static String EncodeCsvField(String field)
|
||||
{
|
||||
StringBuilder sbField = new StringBuilder(field);
|
||||
|
||||
|
||||
//Whether this field has special characters which require it to be embedded in quotes
|
||||
bool embedInQuotes = false;
|
||||
|
||||
@@ -67,12 +99,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Replace all quotes in the original field with double quotes
|
||||
sbField.Replace("\"", "\"\"");
|
||||
|
||||
String ret = sbField.ToString();
|
||||
|
||||
|
||||
if (embedInQuotes)
|
||||
{
|
||||
ret = "\"" + ret + "\"";
|
||||
@@ -81,11 +113,208 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal static bool isSaveSelection(SaveResultsRequestParams saveParams)
|
||||
/// <summary>
|
||||
/// Check if request is a subset of result set or whole result set
|
||||
/// </summary>
|
||||
/// <param name="saveParams"> Parameters from the request </param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsSaveSelection(SaveResultsRequestParams saveParams)
|
||||
{
|
||||
return (saveParams.ColumnStartIndex != null && saveParams.ColumnEndIndex != null
|
||||
&& saveParams.RowEndIndex != null && saveParams.RowEndIndex != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save results as JSON format to the file specified in saveParams
|
||||
/// </summary>
|
||||
/// <param name="saveParams"> Parameters from the request </param>
|
||||
/// <param name="requestContext"> Request context for save results </param>
|
||||
/// <param name="result"> Result query object </param>
|
||||
/// <returns></returns>
|
||||
internal void SaveResultSetAsJson(SaveResultsAsJsonRequestParams saveParams, RequestContext<SaveResultRequestResult> requestContext, Query result)
|
||||
{
|
||||
// Run in a separate thread
|
||||
SaveTask = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamWriter jsonFile = new StreamWriter(File.Open(saveParams.FilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)))
|
||||
using (JsonWriter jsonWriter = new JsonTextWriter(jsonFile))
|
||||
{
|
||||
|
||||
int rowCount = 0;
|
||||
int rowStartIndex = 0;
|
||||
int columnStartIndex = 0;
|
||||
int columnEndIndex = 0;
|
||||
|
||||
jsonWriter.Formatting = Formatting.Indented;
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
// Get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = selectedBatch.ResultSets[saveParams.ResultSetIndex];
|
||||
|
||||
// Set column, row counts depending on whether save request is for entire result set or a subset
|
||||
if (IsSaveSelection(saveParams))
|
||||
{
|
||||
|
||||
rowCount = saveParams.RowEndIndex.Value - saveParams.RowStartIndex.Value + 1;
|
||||
rowStartIndex = saveParams.RowStartIndex.Value;
|
||||
columnStartIndex = saveParams.ColumnStartIndex.Value;
|
||||
columnEndIndex = saveParams.ColumnEndIndex.Value + 1; // include the last column
|
||||
}
|
||||
else
|
||||
{
|
||||
rowCount = (int)selectedResultSet.RowCount;
|
||||
columnEndIndex = selectedResultSet.Columns.Length;
|
||||
}
|
||||
|
||||
// Split rows into batches
|
||||
for (int count = 0; count < (rowCount / BatchSize) + 1; count++)
|
||||
{
|
||||
int numberOfRows = (count < rowCount / BatchSize) ? BatchSize : (rowCount % BatchSize);
|
||||
if (numberOfRows == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Retrieve rows and write as json
|
||||
ResultSetSubset resultSubset = await result.GetSubset(saveParams.BatchIndex, saveParams.ResultSetIndex, rowStartIndex + count * BatchSize, numberOfRows);
|
||||
foreach (var row in resultSubset.Rows)
|
||||
{
|
||||
jsonWriter.WriteStartObject();
|
||||
for (int i = columnStartIndex; i < columnEndIndex; i++)
|
||||
{
|
||||
// Write columnName, value pair
|
||||
DbColumnWrapper col = selectedResultSet.Columns[i];
|
||||
string val = row[i]?.ToString();
|
||||
jsonWriter.WritePropertyName(col.ColumnName);
|
||||
if (val == null)
|
||||
{
|
||||
jsonWriter.WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonWriter.WriteValue(val);
|
||||
}
|
||||
}
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
|
||||
}
|
||||
jsonWriter.WriteEndArray();
|
||||
}
|
||||
|
||||
// Successfully wrote file, send success result
|
||||
if (SaveCompleted != null)
|
||||
{
|
||||
await SaveCompleted(null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Delete file when exception occurs
|
||||
if (FileUtils.SafeFileExists(saveParams.FilePath))
|
||||
{
|
||||
FileUtils.SafeFileDelete(saveParams.FilePath);
|
||||
}
|
||||
if (SaveFailed != null)
|
||||
{
|
||||
await SaveFailed(ex.Message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save results as CSV format to the file specified in saveParams
|
||||
/// </summary>
|
||||
/// <param name="saveParams"> Parameters from the request </param>
|
||||
/// <param name="requestContext"> Request context for save results </param>
|
||||
/// <param name="result"> Result query object </param>
|
||||
/// <returns></returns>
|
||||
internal void SaveResultSetAsCsv(SaveResultsAsCsvRequestParams saveParams, RequestContext<SaveResultRequestResult> requestContext, Query result)
|
||||
{
|
||||
// Run in a separate thread
|
||||
SaveTask = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamWriter csvFile = new StreamWriter(File.Open(saveParams.FilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)))
|
||||
{
|
||||
ResultSetSubset resultSubset;
|
||||
int columnCount = 0;
|
||||
int rowCount = 0;
|
||||
int columnStartIndex = 0;
|
||||
int rowStartIndex = 0;
|
||||
|
||||
// Get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = (selectedBatch.ResultSets)[saveParams.ResultSetIndex];
|
||||
// Set column, row counts depending on whether save request is for entire result set or a subset
|
||||
if (IsSaveSelection(saveParams))
|
||||
{
|
||||
columnCount = saveParams.ColumnEndIndex.Value - saveParams.ColumnStartIndex.Value + 1;
|
||||
rowCount = saveParams.RowEndIndex.Value - saveParams.RowStartIndex.Value + 1;
|
||||
columnStartIndex = saveParams.ColumnStartIndex.Value;
|
||||
rowStartIndex = saveParams.RowStartIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnCount = selectedResultSet.Columns.Length;
|
||||
rowCount = (int)selectedResultSet.RowCount;
|
||||
}
|
||||
|
||||
// Write column names if include headers option is chosen
|
||||
if (saveParams.IncludeHeaders)
|
||||
{
|
||||
csvFile.WriteLine(string.Join(",", selectedResultSet.Columns.Skip(columnStartIndex).Take(columnCount).Select(column =>
|
||||
EncodeCsvField(column.ColumnName) ?? string.Empty)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < (rowCount / BatchSize) + 1; i++)
|
||||
{
|
||||
int numberOfRows = (i < rowCount / BatchSize) ? BatchSize : (rowCount % BatchSize);
|
||||
if (numberOfRows == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// Retrieve rows and write as csv
|
||||
resultSubset = await result.GetSubset(saveParams.BatchIndex, saveParams.ResultSetIndex, rowStartIndex + i * BatchSize, numberOfRows);
|
||||
|
||||
foreach (var row in resultSubset.Rows)
|
||||
{
|
||||
csvFile.WriteLine(string.Join(",", row.Skip(columnStartIndex).Take(columnCount).Select(field =>
|
||||
EncodeCsvField((field != null) ? field.ToString() : "NULL"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully wrote file, send success result
|
||||
if (SaveCompleted != null)
|
||||
{
|
||||
await SaveCompleted(null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Delete file when exception occurs
|
||||
if (FileUtils.SafeFileExists(saveParams.FilePath))
|
||||
{
|
||||
FileUtils.SafeFileDelete(saveParams.FilePath);
|
||||
}
|
||||
|
||||
if (SaveFailed != null)
|
||||
{
|
||||
await SaveFailed(ex.Message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,12 +15,19 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// </summary>
|
||||
public IntelliSenseSettings()
|
||||
{
|
||||
this.EnableIntellisense = true;
|
||||
this.EnableSuggestions = true;
|
||||
this.LowerCaseSuggestions = false;
|
||||
this.EnableDiagnostics = true;
|
||||
this.EnableErrorChecking = true;
|
||||
this.EnableQuickInfo = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if IntelliSense is enabled
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool EnableIntellisense { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if suggestions are enabled
|
||||
/// </summary>
|
||||
@@ -35,7 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if diagnostics are enabled
|
||||
/// </summary>
|
||||
public bool? EnableDiagnostics { get; set; }
|
||||
public bool? EnableErrorChecking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if quick info is enabled
|
||||
@@ -52,7 +59,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
this.EnableSuggestions = settings.EnableSuggestions;
|
||||
this.LowerCaseSuggestions = settings.LowerCaseSuggestions;
|
||||
this.EnableDiagnostics = settings.EnableDiagnostics;
|
||||
this.EnableErrorChecking = settings.EnableErrorChecking;
|
||||
this.EnableQuickInfo = settings.EnableQuickInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,6 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying settings value object
|
||||
/// </summary>
|
||||
[JsonProperty("mssql")]
|
||||
public SqlToolsSettingsValues SqlTools
|
||||
{
|
||||
get
|
||||
@@ -47,7 +50,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
if (settings != null)
|
||||
{
|
||||
this.SqlTools.EnableIntellisense = settings.SqlTools.EnableIntellisense;
|
||||
this.SqlTools.IntelliSense.EnableIntellisense = settings.SqlTools.IntelliSense.EnableIntellisense;
|
||||
this.SqlTools.IntelliSense.Update(settings.SqlTools.IntelliSense);
|
||||
}
|
||||
}
|
||||
@@ -59,8 +62,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableDiagnostics.Value;
|
||||
return this.SqlTools.IntelliSense.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableErrorChecking.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +74,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.EnableIntellisense
|
||||
return this.SqlTools.IntelliSense.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableSuggestions.Value;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +86,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.EnableIntellisense
|
||||
return this.SqlTools.IntelliSense.EnableIntellisense
|
||||
&& this.SqlTools.IntelliSense.EnableQuickInfo.Value;
|
||||
}
|
||||
}
|
||||
@@ -99,17 +102,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// </summary>
|
||||
public SqlToolsSettingsValues()
|
||||
{
|
||||
this.EnableIntellisense = true;
|
||||
|
||||
this.IntelliSense = new IntelliSenseSettings();
|
||||
this.QueryExecutionSettings = new QueryExecutionSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining if IntelliSense is enabled
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool EnableIntellisense { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the detailed IntelliSense settings
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// The command-line options helper class.
|
||||
/// </summary>
|
||||
internal class CommandOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct and parse command line options from the arguments array
|
||||
/// </summary>
|
||||
public CommandOptions(string[] args)
|
||||
{
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
{
|
||||
string arg = args[i];
|
||||
if (arg.StartsWith("--") || arg.StartsWith("-"))
|
||||
{
|
||||
arg = arg.Substring(1).ToLowerInvariant();
|
||||
switch (arg)
|
||||
{
|
||||
case "-enable-logging":
|
||||
EnableLogging = true;
|
||||
break;
|
||||
case "h":
|
||||
case "-help":
|
||||
ShouldExit = true;
|
||||
return;
|
||||
default:
|
||||
ErrorMessage += String.Format("Unknown argument \"{0}\"" + Environment.NewLine, arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage += ex.ToString();
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage) || ShouldExit)
|
||||
{
|
||||
Console.WriteLine(Usage);
|
||||
ShouldExit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal string ErrorMessage { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether diagnostic logging is enabled
|
||||
/// </summary>
|
||||
public bool EnableLogging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the program should exit immediately. Set to true when the usage is printed.
|
||||
/// </summary>
|
||||
public bool ShouldExit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the usage string describing command-line arguments for the program
|
||||
/// </summary>
|
||||
public string Usage
|
||||
{
|
||||
get
|
||||
{
|
||||
var str = string.Format("{0}" + Environment.NewLine +
|
||||
"Microsoft.SqlTools.ServiceLayer.exe " + Environment.NewLine +
|
||||
" Options:" + Environment.NewLine +
|
||||
" [--enable-logging]" + Environment.NewLine +
|
||||
" [--help]" + Environment.NewLine,
|
||||
ErrorMessage);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,34 @@
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
public static class TextUtilities
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Find the position of the cursor in the SQL script content buffer and return previous new line position
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <param name="prevNewLine"></param>
|
||||
public static int PositionOfCursor(string sql, int startRow, int startColumn, out int prevNewLine)
|
||||
{
|
||||
prevNewLine = 0;
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < startRow; ++i)
|
||||
{
|
||||
while (prevNewLine < sql.Length && sql[prevNewLine] != '\n')
|
||||
{
|
||||
++prevNewLine;
|
||||
}
|
||||
++prevNewLine;
|
||||
}
|
||||
|
||||
return startColumn + prevNewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||
@@ -14,49 +41,73 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="tokenText"></param>
|
||||
public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
int prevNewLine;
|
||||
int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
|
||||
|
||||
int prevLineColumns = 0;
|
||||
for (int i = 0; i < startRow; ++i)
|
||||
if (delimeterPos - 1 < sql.Length)
|
||||
{
|
||||
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
||||
while (--delimeterPos >= prevNewLine)
|
||||
{
|
||||
++prevLineColumns;
|
||||
}
|
||||
++prevLineColumns;
|
||||
}
|
||||
|
||||
startColumn += prevLineColumns;
|
||||
|
||||
if (startColumn - 1 < sql.Length)
|
||||
{
|
||||
while (--startColumn >= prevLineColumns)
|
||||
{
|
||||
if (sql[startColumn] == ' '
|
||||
|| sql[startColumn] == '\t'
|
||||
|| sql[startColumn] == '\n'
|
||||
|| sql[startColumn] == '.'
|
||||
|| sql[startColumn] == '+'
|
||||
|| sql[startColumn] == '-'
|
||||
|| sql[startColumn] == '*'
|
||||
|| sql[startColumn] == '>'
|
||||
|| sql[startColumn] == '<'
|
||||
|| sql[startColumn] == '='
|
||||
|| sql[startColumn] == '/'
|
||||
|| sql[startColumn] == '%')
|
||||
if (IsCharacterDelimeter(sql[delimeterPos]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delimeterPos = delimeterPos + 1 - prevNewLine;
|
||||
}
|
||||
|
||||
return startColumn + 1 - prevLineColumns;
|
||||
return delimeterPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the position of the next delimeter for autocomplete token replacement.
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
public static int PositionOfNextDelimeter(string sql, int startRow, int startColumn)
|
||||
{
|
||||
int prevNewLine;
|
||||
int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
|
||||
|
||||
while (delimeterPos < sql.Length)
|
||||
{
|
||||
if (IsCharacterDelimeter(sql[delimeterPos]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
++delimeterPos;
|
||||
}
|
||||
|
||||
return delimeterPos - prevNewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the character is a SQL token delimiter
|
||||
/// </summary>
|
||||
/// <param name="ch"></param>
|
||||
private static bool IsCharacterDelimeter(char ch)
|
||||
{
|
||||
return ch == ' '
|
||||
|| ch == '\t'
|
||||
|| ch == '\n'
|
||||
|| ch == '.'
|
||||
|| ch == '+'
|
||||
|| ch == '-'
|
||||
|| ch == '*'
|
||||
|| ch == '>'
|
||||
|| ch == '<'
|
||||
|| ch == '='
|
||||
|| ch == '/'
|
||||
|| ch == '%'
|
||||
|| ch == ','
|
||||
|| ch == ';'
|
||||
|| ch == '('
|
||||
|| ch == ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"System.Data.Common": "4.1.0",
|
||||
"System.Data.SqlClient": "4.1.0",
|
||||
"Microsoft.SqlServer.Smo": "140.1.8",
|
||||
"System.Data.SqlClient": "4.4.0-sqltools-24613-04",
|
||||
"Microsoft.SqlServer.Smo": "140.1.11",
|
||||
"System.Security.SecureString": "4.0.0",
|
||||
"System.Collections.Specialized": "4.0.1",
|
||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||
|
||||
467
src/Microsoft.SqlTools.ServiceLayer/sr.Designer.cs
generated
467
src/Microsoft.SqlTools.ServiceLayer/sr.Designer.cs
generated
@@ -1,467 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class sr {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
internal sr() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SqlTools.ServiceLayer.sr", typeof(sr).GetTypeInfo().Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connection details object cannot be null.
|
||||
/// </summary>
|
||||
public static string ConnectionParamsValidateNullConnection {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionParamsValidateNullConnection", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OwnerUri cannot be null or empty.
|
||||
/// </summary>
|
||||
public static string ConnectionParamsValidateNullOwnerUri {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionParamsValidateNullOwnerUri", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ServerName cannot be null or empty.
|
||||
/// </summary>
|
||||
public static string ConnectionParamsValidateNullServerName {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionParamsValidateNullServerName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} cannot be null or empty when using SqlLogin authentication.
|
||||
/// </summary>
|
||||
public static string ConnectionParamsValidateNullSqlAuth {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionParamsValidateNullSqlAuth", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connection parameters cannot be null.
|
||||
/// </summary>
|
||||
public static string ConnectionServiceConnectErrorNullParams {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionServiceConnectErrorNullParams", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid value '{0}' for AuthenticationType. Valid values are 'Integrated' and 'SqlLogin'..
|
||||
/// </summary>
|
||||
public static string ConnectionServiceConnStringInvalidAuthType {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionServiceConnStringInvalidAuthType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid value '{0}' for ApplicationIntent. Valid values are 'ReadWrite' and 'ReadOnly'..
|
||||
/// </summary>
|
||||
public static string ConnectionServiceConnStringInvalidIntent {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionServiceConnStringInvalidIntent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to SpecifiedUri '{0}' does not have existing connection.
|
||||
/// </summary>
|
||||
public static string ConnectionServiceListDbErrorNotConnected {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionServiceListDbErrorNotConnected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OwnerUri cannot be null or empty.
|
||||
/// </summary>
|
||||
public static string ConnectionServiceListDbErrorNullOwnerUri {
|
||||
get {
|
||||
return ResourceManager.GetString("ConnectionServiceListDbErrorNullOwnerUri", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Win32Credential object is already disposed.
|
||||
/// </summary>
|
||||
public static string CredentialServiceWin32CredentialDisposed {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialServiceWin32CredentialDisposed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid CriticalHandle!.
|
||||
/// </summary>
|
||||
public static string CredentialsServiceInvalidCriticalHandle {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialsServiceInvalidCriticalHandle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The password has exceeded 512 bytes.
|
||||
/// </summary>
|
||||
public static string CredentialsServicePasswordLengthExceeded {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialsServicePasswordLengthExceeded", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Target must be specified to delete a credential.
|
||||
/// </summary>
|
||||
public static string CredentialsServiceTargetForDelete {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialsServiceTargetForDelete", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Target must be specified to check existance of a credential.
|
||||
/// </summary>
|
||||
public static string CredentialsServiceTargetForLookup {
|
||||
get {
|
||||
return ResourceManager.GetString("CredentialsServiceTargetForLookup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Message header must separate key and value using ':'.
|
||||
/// </summary>
|
||||
public static string HostingHeaderMissingColon {
|
||||
get {
|
||||
return ResourceManager.GetString("HostingHeaderMissingColon", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fatal error: Content-Length header must be provided.
|
||||
/// </summary>
|
||||
public static string HostingHeaderMissingContentLengthHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("HostingHeaderMissingContentLengthHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fatal error: Content-Length value is not an integer.
|
||||
/// </summary>
|
||||
public static string HostingHeaderMissingContentLengthValue {
|
||||
get {
|
||||
return ResourceManager.GetString("HostingHeaderMissingContentLengthValue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to MessageReader's input stream ended unexpectedly, terminating.
|
||||
/// </summary>
|
||||
public static string HostingUnexpectedEndOfStream {
|
||||
get {
|
||||
return ResourceManager.GetString("HostingUnexpectedEndOfStream", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ({0} row(s) affected).
|
||||
/// </summary>
|
||||
public static string QueryServiceAffectedRows {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceAffectedRows", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The query has already completed, it cannot be cancelled.
|
||||
/// </summary>
|
||||
public static string QueryServiceCancelAlreadyCompleted {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceCancelAlreadyCompleted", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Query successfully cancelled, failed to dispose query. Owner URI not found..
|
||||
/// </summary>
|
||||
public static string QueryServiceCancelDisposeFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceCancelDisposeFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to (No column name).
|
||||
/// </summary>
|
||||
public static string QueryServiceColumnNull {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceColumnNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Command(s) copleted successfully..
|
||||
/// </summary>
|
||||
public static string QueryServiceCompletedSuccessfully {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceCompletedSuccessfully", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Maximum number of bytes to return must be greater than zero.
|
||||
/// </summary>
|
||||
public static string QueryServiceDataReaderByteCountInvalid {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceDataReaderByteCountInvalid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Maximum number of chars to return must be greater than zero.
|
||||
/// </summary>
|
||||
public static string QueryServiceDataReaderCharCountInvalid {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceDataReaderCharCountInvalid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Maximum number of XML bytes to return must be greater than zero.
|
||||
/// </summary>
|
||||
public static string QueryServiceDataReaderXmlCountInvalid {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceDataReaderXmlCountInvalid", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Msg {0}, Level {1}, State {2}, Line {3}{4}{5}.
|
||||
/// </summary>
|
||||
public static string QueryServiceErrorFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceErrorFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to FileStreamWrapper must be initialized before performing operations.
|
||||
/// </summary>
|
||||
public static string QueryServiceFileWrapperNotInitialized {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceFileWrapperNotInitialized", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This FileStreamWrapper cannot be used for writing.
|
||||
/// </summary>
|
||||
public static string QueryServiceFileWrapperReadOnly {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceFileWrapperReadOnly", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Access method cannot be write-only.
|
||||
/// </summary>
|
||||
public static string QueryServiceFileWrapperWriteOnly {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceFileWrapperWriteOnly", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Sender for OnInfoMessage event must be a SqlConnection.
|
||||
/// </summary>
|
||||
public static string QueryServiceMessageSenderNotSql {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceMessageSenderNotSql", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A query is already in progress for this editor session. Please cancel this query or wait for its completion..
|
||||
/// </summary>
|
||||
public static string QueryServiceQueryInProgress {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceQueryInProgress", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This editor is not connected to a database.
|
||||
/// </summary>
|
||||
public static string QueryServiceQueryInvalidOwnerUri {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceQueryInvalidOwnerUri", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The requested query does not exist.
|
||||
/// </summary>
|
||||
public static string QueryServiceRequestsNoQuery {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceRequestsNoQuery", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Could not retrieve column schema for result set.
|
||||
/// </summary>
|
||||
public static string QueryServiceResultSetNoColumnSchema {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceResultSetNoColumnSchema", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cannot read subset unless the results have been read from the server.
|
||||
/// </summary>
|
||||
public static string QueryServiceResultSetNotRead {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceResultSetNotRead", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reader cannot be null.
|
||||
/// </summary>
|
||||
public static string QueryServiceResultSetReaderNull {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceResultSetReaderNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Row count must be a positive integer.
|
||||
/// </summary>
|
||||
public static string QueryServiceResultSetRowCountOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceResultSetRowCountOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Start row cannot be less than 0 or greater than the number of rows in the result set.
|
||||
/// </summary>
|
||||
public static string QueryServiceResultSetStartRowOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceResultSetStartRowOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Batch index cannot be less than 0 or greater than the number of batches.
|
||||
/// </summary>
|
||||
public static string QueryServiceSubsetBatchOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceSubsetBatchOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The query has not completed, yet.
|
||||
/// </summary>
|
||||
public static string QueryServiceSubsetNotCompleted {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceSubsetNotCompleted", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Result set index cannot be less than 0 or greater than the number of result sets.
|
||||
/// </summary>
|
||||
public static string QueryServiceSubsetResultSetOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryServiceSubsetResultSetOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Start position ({0}, {1}) must come before or be equal to the end position ({2}, {3}).
|
||||
/// </summary>
|
||||
public static string WorkspaceServiceBufferPositionOutOfOrder {
|
||||
get {
|
||||
return ResourceManager.GetString("WorkspaceServiceBufferPositionOutOfOrder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Position is outside of column range for line {0}.
|
||||
/// </summary>
|
||||
public static string WorkspaceServicePositionColumnOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("WorkspaceServicePositionColumnOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Position is outside of file line range.
|
||||
/// </summary>
|
||||
public static string WorkspaceServicePositionLineOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("WorkspaceServicePositionLineOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,14 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
}
|
||||
}
|
||||
|
||||
public static string QueryServiceQueryCancelled
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.QueryServiceQueryCancelled);
|
||||
}
|
||||
}
|
||||
|
||||
public static string QueryServiceSubsetNotCompleted
|
||||
{
|
||||
get
|
||||
@@ -237,6 +245,14 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
}
|
||||
}
|
||||
|
||||
public static string QueryServiceAffectedOneRow
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.QueryServiceAffectedOneRow);
|
||||
}
|
||||
}
|
||||
|
||||
public static string QueryServiceCompletedSuccessfully
|
||||
{
|
||||
get
|
||||
@@ -363,6 +379,11 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
return Keys.GetString(Keys.QueryServiceErrorFormat, msg, lvl, state, line, newLine, message);
|
||||
}
|
||||
|
||||
public static string QueryServiceQueryFailed(string message)
|
||||
{
|
||||
return Keys.GetString(Keys.QueryServiceQueryFailed, message);
|
||||
}
|
||||
|
||||
public static string WorkspaceServicePositionColumnOutOfRange(int line)
|
||||
{
|
||||
return Keys.GetString(Keys.WorkspaceServicePositionColumnOutOfRange, line);
|
||||
@@ -444,6 +465,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string QueryServiceCancelDisposeFailed = "QueryServiceCancelDisposeFailed";
|
||||
|
||||
|
||||
public const string QueryServiceQueryCancelled = "QueryServiceQueryCancelled";
|
||||
|
||||
|
||||
public const string QueryServiceSubsetNotCompleted = "QueryServiceSubsetNotCompleted";
|
||||
|
||||
|
||||
@@ -471,6 +495,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string QueryServiceFileWrapperReadOnly = "QueryServiceFileWrapperReadOnly";
|
||||
|
||||
|
||||
public const string QueryServiceAffectedOneRow = "QueryServiceAffectedOneRow";
|
||||
|
||||
|
||||
public const string QueryServiceAffectedRows = "QueryServiceAffectedRows";
|
||||
|
||||
|
||||
@@ -480,6 +507,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string QueryServiceErrorFormat = "QueryServiceErrorFormat";
|
||||
|
||||
|
||||
public const string QueryServiceQueryFailed = "QueryServiceQueryFailed";
|
||||
|
||||
|
||||
public const string QueryServiceColumnNull = "QueryServiceColumnNull";
|
||||
|
||||
|
||||
|
||||
@@ -205,6 +205,10 @@
|
||||
<value>Query successfully cancelled, failed to dispose query. Owner URI not found.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceQueryCancelled" xml:space="preserve">
|
||||
<value>Query was canceled by user</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceSubsetNotCompleted" xml:space="preserve">
|
||||
<value>The query has not completed, yet</value>
|
||||
<comment></comment>
|
||||
@@ -241,19 +245,28 @@
|
||||
<value>This FileStreamWrapper cannot be used for writing</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceAffectedOneRow" xml:space="preserve">
|
||||
<value>(1 row affected)</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceAffectedRows" xml:space="preserve">
|
||||
<value>({0} row(s) affected)</value>
|
||||
<value>({0} rows affected)</value>
|
||||
<comment>.
|
||||
Parameters: 0 - rows (long) </comment>
|
||||
</data>
|
||||
<data name="QueryServiceCompletedSuccessfully" xml:space="preserve">
|
||||
<value>Command(s) copleted successfully.</value>
|
||||
<value>Commands completed successfully.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceErrorFormat" xml:space="preserve">
|
||||
<value>Msg {0}, Level {1}, State {2}, Line {3}{4}{5}</value>
|
||||
<comment>.
|
||||
Parameters: 0 - msg (int), 1 - lvl (int), 2 - state (int), 3 - line (int), 4 - newLine (string), 5 - message (string) </comment>
|
||||
</data>
|
||||
<data name="QueryServiceQueryFailed" xml:space="preserve">
|
||||
<value>Query failed: {0}</value>
|
||||
<comment>.
|
||||
Parameters: 0 - message (string) </comment>
|
||||
</data>
|
||||
<data name="QueryServiceColumnNull" xml:space="preserve">
|
||||
<value>(No column name)</value>
|
||||
|
||||
@@ -79,6 +79,8 @@ QueryServiceCancelAlreadyCompleted = The query has already completed, it cannot
|
||||
|
||||
QueryServiceCancelDisposeFailed = Query successfully cancelled, failed to dispose query. Owner URI not found.
|
||||
|
||||
QueryServiceQueryCancelled = Query was canceled by user
|
||||
|
||||
### Subset Request
|
||||
|
||||
QueryServiceSubsetNotCompleted = The query has not completed, yet
|
||||
@@ -105,12 +107,16 @@ QueryServiceFileWrapperReadOnly = This FileStreamWrapper cannot be used for writ
|
||||
|
||||
### Query Request
|
||||
|
||||
QueryServiceAffectedRows(long rows) = ({0} row(s) affected)
|
||||
QueryServiceAffectedOneRow = (1 row affected)
|
||||
|
||||
QueryServiceCompletedSuccessfully = Command(s) copleted successfully.
|
||||
QueryServiceAffectedRows(long rows) = ({0} rows affected)
|
||||
|
||||
QueryServiceCompletedSuccessfully = Commands completed successfully.
|
||||
|
||||
QueryServiceErrorFormat(int msg, int lvl, int state, int line, string newLine, string message) = Msg {0}, Level {1}, State {2}, Line {3}{4}{5}
|
||||
|
||||
QueryServiceQueryFailed(string message) = Query failed: {0}
|
||||
|
||||
QueryServiceColumnNull = (No column name)
|
||||
|
||||
QueryServiceRequestsNoQuery = The requested query does not exist
|
||||
|
||||
Reference in New Issue
Block a user