mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-23 09:35:38 -05:00
Merge branch 'release/ctp10'
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -284,4 +284,5 @@ Session.vim
|
||||
|
||||
# Stuff from cake
|
||||
/artifacts/
|
||||
/.tools/
|
||||
/.tools/
|
||||
/.dotnet/
|
||||
BIN
bin/nuget/Microsoft.DataTools.SrGen.1.0.2.nupkg
Normal file
BIN
bin/nuget/Microsoft.DataTools.SrGen.1.0.2.nupkg
Normal file
Binary file not shown.
BIN
bin/nuget/Microsoft.SqlServer.Smo.140.1.11.nupkg
Normal file
BIN
bin/nuget/Microsoft.SqlServer.Smo.140.1.11.nupkg
Normal file
Binary file not shown.
BIN
bin/nuget/System.Data.SqlClient.4.4.0-sqltools-24613-04.nupkg
Normal file
BIN
bin/nuget/System.Data.SqlClient.4.4.0-sqltools-24613-04.nupkg
Normal file
Binary file not shown.
14
build.cake
14
build.cake
@@ -257,6 +257,7 @@ Task("TestCore")
|
||||
/// </summary>
|
||||
Task("Test")
|
||||
.IsDependentOn("Setup")
|
||||
.IsDependentOn("SRGen")
|
||||
.IsDependentOn("BuildTest")
|
||||
.Does(() =>
|
||||
{
|
||||
@@ -304,6 +305,7 @@ Task("Test")
|
||||
/// </summary>
|
||||
Task("OnlyPublish")
|
||||
.IsDependentOn("Setup")
|
||||
.IsDependentOn("SRGen")
|
||||
.Does(() =>
|
||||
{
|
||||
var project = buildPlan.MainProject;
|
||||
@@ -322,7 +324,13 @@ Task("OnlyPublish")
|
||||
publishArguments = $"{publishArguments} --output \"{outputFolder}\" \"{projectFolder}\"";
|
||||
Run(dotnetcli, publishArguments)
|
||||
.ExceptionOnError($"Failed to publish {project} / {framework}");
|
||||
|
||||
//Setting the rpath for System.Security.Cryptography.Native.dylib library
|
||||
//Only required for mac. We're assuming the openssl is installed in /usr/local/opt/openssl
|
||||
//If that's not the case user has to run the command manually
|
||||
if (!IsRunningOnWindows() && runtime.Contains("osx"))
|
||||
{
|
||||
Run("install_name_tool", "-add_rpath /usr/local/opt/openssl/lib " + outputFolder + "/System.Security.Cryptography.Native.dylib");
|
||||
}
|
||||
if (requireArchive)
|
||||
{
|
||||
Package(runtime, framework, outputFolder, packageFolder, buildPlan.MainProject.ToLower());
|
||||
@@ -359,7 +367,6 @@ Task("RestrictToLocalRuntime")
|
||||
/// </summary>
|
||||
Task("LocalPublish")
|
||||
.IsDependentOn("Restore")
|
||||
.IsDependentOn("SrGen")
|
||||
.IsDependentOn("RestrictToLocalRuntime")
|
||||
.IsDependentOn("OnlyPublish")
|
||||
.Does(() =>
|
||||
@@ -529,7 +536,8 @@ Task("SRGen")
|
||||
var dotnetArgs = string.Format("{0} -or \"{1}\" -oc \"{2}\" -ns \"{3}\" -an \"{4}\" -cn SR -l CS -dnx \"{5}\"",
|
||||
srgenPath, outputResx, outputCs, projectName, projectName, projectStrings);
|
||||
Information("{0}", dotnetArgs);
|
||||
Run(dotnetcli, dotnetArgs);
|
||||
Run(dotnetcli, dotnetArgs)
|
||||
.ExceptionOnError("Failed to run SRGen.");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<configuration >
|
||||
<packageSources>
|
||||
<add key="Nuget" value="https://www.nuget.org/api/v2" />
|
||||
<add key="DataTools Nuget" value="http://dtnuget/api/v2/" />
|
||||
<add key="Myget" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
|
||||
<add key="DataTools Nuget" value="./bin/nuget" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<packages>
|
||||
<package id="Cake" version="0.10.1" />
|
||||
<package id="Newtonsoft.Json" version="8.0.3" />
|
||||
<package id="Microsoft.DataTools.SrGen" version="1.0.1-preview" />
|
||||
<package id="Microsoft.DataTools.SrGen" version="1.0.2" />
|
||||
</packages>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -35,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
var commandMockSetup = commandMock.Protected()
|
||||
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
|
||||
|
||||
commandMockSetup.Returns(new TestDbDataReader(data));
|
||||
commandMockSetup.Returns(() => new TestDbDataReader(data));
|
||||
|
||||
return commandMock.Object;
|
||||
}
|
||||
@@ -830,5 +831,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
ConnectionInfo info;
|
||||
Assert.True(service.TryFindConnection(connectParams.OwnerUri, out info));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that Linux/OSX SqlExceptions thrown do not contain an error code.
|
||||
/// This is a bug in .NET core (see https://github.com/dotnet/corefx/issues/12472).
|
||||
/// If this test ever fails, it means that this bug has been fixed. When this is
|
||||
/// the case, look at RetryPolicyUtils.cs in IsRetryableNetworkConnectivityError(),
|
||||
/// and remove the code block specific to Linux/OSX.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestThatLinuxAndOSXSqlExceptionHasNoErrorCode()
|
||||
{
|
||||
TestUtils.RunIfLinuxOrOSX(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
|
||||
builder.DataSource = "bad-server-name";
|
||||
builder.UserID = "sa";
|
||||
builder.Password = "bad password";
|
||||
|
||||
SqlConnection connection = new SqlConnection(builder.ConnectionString);
|
||||
connection.Open(); // This should fail
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
// Error code should be 0 due to bug
|
||||
Assert.Equal(ex.Number, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#if LIVE_CONNECTION_TESTS
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the ReliableConnection module.
|
||||
/// These tests all assume a live connection to a database on localhost using integrated auth.
|
||||
/// </summary>
|
||||
public class ReliableConnectionTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Environment variable that stores the name of the test server hosting the SQL Server instance.
|
||||
/// </summary>
|
||||
public static string TestServerEnvironmentVariable
|
||||
{
|
||||
get { return "TEST_SERVER"; }
|
||||
}
|
||||
|
||||
private static Lazy<string> testServerName = new Lazy<string>(() => Environment.GetEnvironmentVariable(TestServerEnvironmentVariable));
|
||||
|
||||
/// <summary>
|
||||
/// Name of the test server hosting the SQL Server instance.
|
||||
/// </summary>
|
||||
public static string TestServerName
|
||||
{
|
||||
get { return testServerName.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create an integrated auth connection builder for testing.
|
||||
/// </summary>
|
||||
private SqlConnectionStringBuilder CreateTestConnectionStringBuilder()
|
||||
{
|
||||
SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder();
|
||||
csb.DataSource = TestServerName;
|
||||
csb.IntegratedSecurity = true;
|
||||
|
||||
return csb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create an integrated auth reliable connection for testing.
|
||||
/// </summary>
|
||||
private DbConnection CreateTestConnection()
|
||||
{
|
||||
SqlConnectionStringBuilder csb = CreateTestConnectionStringBuilder();
|
||||
|
||||
RetryPolicy connectionRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy();
|
||||
RetryPolicy commandRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy();
|
||||
|
||||
ReliableSqlConnection connection = new ReliableSqlConnection(csb.ConnectionString, connectionRetryPolicy, commandRetryPolicy);
|
||||
return connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test ReliableConnectionHelper.GetDefaultDatabaseFilePath()
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestGetDefaultDatabaseFilePath()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
var connectionBuilder = CreateTestConnectionStringBuilder();
|
||||
Assert.NotNull(connectionBuilder);
|
||||
|
||||
string filePath = string.Empty;
|
||||
string logPath = string.Empty;
|
||||
|
||||
ReliableConnectionHelper.OpenConnection(
|
||||
connectionBuilder,
|
||||
usingConnection: (conn) =>
|
||||
{
|
||||
filePath = ReliableConnectionHelper.GetDefaultDatabaseFilePath(conn);
|
||||
logPath = ReliableConnectionHelper.GetDefaultDatabaseLogPath(conn);
|
||||
},
|
||||
catchException: null,
|
||||
useRetry: false);
|
||||
|
||||
Assert.False(string.IsNullOrWhiteSpace(filePath));
|
||||
Assert.False(string.IsNullOrWhiteSpace(logPath));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test ReliableConnectionHelper.GetServerVersion()
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestGetServerVersion()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
using (var connection = CreateTestConnection())
|
||||
{
|
||||
Assert.NotNull(connection);
|
||||
connection.Open();
|
||||
|
||||
ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connection);
|
||||
ReliableConnectionHelper.ServerInfo serverInfo2;
|
||||
using (var connection2 = CreateTestConnection())
|
||||
{
|
||||
connection2.Open();
|
||||
serverInfo2 = ReliableConnectionHelper.GetServerVersion(connection);
|
||||
}
|
||||
|
||||
Assert.NotNull(serverInfo);
|
||||
Assert.NotNull(serverInfo2);
|
||||
Assert.True(serverInfo.ServerMajorVersion != 0);
|
||||
Assert.True(serverInfo.ServerMajorVersion == serverInfo2.ServerMajorVersion);
|
||||
Assert.True(serverInfo.ServerMinorVersion == serverInfo2.ServerMinorVersion);
|
||||
Assert.True(serverInfo.ServerReleaseVersion == serverInfo2.ServerReleaseVersion);
|
||||
Assert.True(serverInfo.ServerEdition == serverInfo2.ServerEdition);
|
||||
Assert.True(serverInfo.IsCloud == serverInfo2.IsCloud);
|
||||
Assert.True(serverInfo.AzureVersion == serverInfo2.AzureVersion);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests ReliableConnectionHelper.GetCompleteServerName()
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestGetCompleteServerName()
|
||||
{
|
||||
string name = ReliableConnectionHelper.GetCompleteServerName(@".\SQL2008");
|
||||
Assert.True(name.Contains(Environment.MachineName));
|
||||
|
||||
name = ReliableConnectionHelper.GetCompleteServerName(@"(local)");
|
||||
Assert.True(name.Contains(Environment.MachineName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests ReliableConnectionHelper.IsDatabaseReadonly()
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestIsDatabaseReadonly()
|
||||
{
|
||||
var connectionBuilder = CreateTestConnectionStringBuilder();
|
||||
Assert.NotNull(connectionBuilder);
|
||||
|
||||
bool isReadOnly = ReliableConnectionHelper.IsDatabaseReadonly(connectionBuilder);
|
||||
Assert.False(isReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify ANSI_NULL and QUOTED_IDENTIFIER settings can be set and retrieved for a session
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void VerifyAnsiNullAndQuotedIdentifierSettingsReplayed()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
using (ReliableSqlConnection conn = (ReliableSqlConnection)ReliableConnectionHelper.OpenConnection(CreateTestConnectionStringBuilder(), useRetry: true))
|
||||
{
|
||||
VerifySessionSettings(conn, true);
|
||||
VerifySessionSettings(conn, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void VerifySessionSettings(ReliableSqlConnection conn, bool expectedSessionValue)
|
||||
{
|
||||
Tuple<string, bool>[] settings = null;
|
||||
using (IDbCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
if (expectedSessionValue)
|
||||
{
|
||||
cmd.CommandText = "SET ANSI_NULLS, QUOTED_IDENTIFIER ON";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.CommandText = "SET ANSI_NULLS, QUOTED_IDENTIFIER OFF";
|
||||
}
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
|
||||
//baseline assertion
|
||||
AssertSessionValues(cmd, ansiNullsValue: expectedSessionValue, quotedIdentifersValue: expectedSessionValue);
|
||||
|
||||
// verify the initial values are correct
|
||||
settings = conn.CacheOrReplaySessionSettings(cmd, settings);
|
||||
|
||||
// assert no change is session settings
|
||||
AssertSessionValues(cmd, ansiNullsValue: expectedSessionValue, quotedIdentifersValue: expectedSessionValue);
|
||||
|
||||
// assert cached settings are correct
|
||||
Assert.Equal("ANSI_NULLS", settings[0].Item1);
|
||||
Assert.Equal(expectedSessionValue, settings[0].Item2);
|
||||
|
||||
Assert.Equal("QUOTED_IDENTIFIER", settings[1].Item1);
|
||||
Assert.Equal(expectedSessionValue, settings[1].Item2);
|
||||
|
||||
// invert session values and assert we reset them
|
||||
|
||||
if (expectedSessionValue)
|
||||
{
|
||||
cmd.CommandText = "SET ANSI_NULLS, QUOTED_IDENTIFIER OFF";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.CommandText = "SET ANSI_NULLS, QUOTED_IDENTIFIER ON";
|
||||
}
|
||||
cmd.ExecuteNonQuery();
|
||||
|
||||
// baseline assertion
|
||||
AssertSessionValues(cmd, ansiNullsValue: !expectedSessionValue, quotedIdentifersValue: !expectedSessionValue);
|
||||
|
||||
// replay cached value
|
||||
settings = conn.CacheOrReplaySessionSettings(cmd, settings);
|
||||
|
||||
// assert session settings correctly set
|
||||
AssertSessionValues(cmd, ansiNullsValue: expectedSessionValue, quotedIdentifersValue: expectedSessionValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertSessionValues(IDbCommand cmd, bool ansiNullsValue, bool quotedIdentifersValue)
|
||||
{
|
||||
// assert session was updated
|
||||
cmd.CommandText = "SELECT SESSIONPROPERTY ('ANSI_NULLS'), SESSIONPROPERTY ('QUOTED_IDENTIFIER')";
|
||||
using (IDataReader reader = cmd.ExecuteReader())
|
||||
{
|
||||
Assert.True(reader.Read(), "Missing session settings");
|
||||
bool actualAnsiNullsOnValue = ((int)reader[0] == 1);
|
||||
bool actualQuotedIdentifierOnValue = ((int)reader[1] == 1);
|
||||
Assert.Equal(ansiNullsValue, actualAnsiNullsOnValue);
|
||||
Assert.Equal(quotedIdentifersValue, actualQuotedIdentifierOnValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that the retry policy factory constructs all possible types of policies successfully.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RetryPolicyFactoryConstructsPoliciesSuccessfully()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(RetryPolicyFactory.CreateColumnEncryptionTransferRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDatabaseCommandRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDataScriptUpdateRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDefaultConnectionRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDefaultDataConnectionRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDefaultDataSqlCommandRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDefaultDataTransferRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDefaultSchemaCommandRetryPolicy(true));
|
||||
Assert.NotNull(RetryPolicyFactory.CreateDefaultSchemaConnectionRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateElementCommandRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateFastDataRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateNoRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreatePrimaryKeyCommandRetryPolicy());
|
||||
Assert.NotNull(RetryPolicyFactory.CreateSchemaCommandRetryPolicy(6));
|
||||
Assert.NotNull(RetryPolicyFactory.CreateSchemaConnectionRetryPolicy(6));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ReliableConnectionHelper.IsCloud() should be false for a local server
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestIsCloudIsFalseForLocalServer()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
using (var connection = CreateTestConnection())
|
||||
{
|
||||
Assert.NotNull(connection);
|
||||
|
||||
connection.Open();
|
||||
Assert.False(ReliableConnectionHelper.IsCloud(connection));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that ReliableConnectionHelper.OpenConnection() opens a connection if it is closed
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestOpenConnectionOpensConnection()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
using (var connection = CreateTestConnection())
|
||||
{
|
||||
Assert.NotNull(connection);
|
||||
|
||||
Assert.True(connection.State == ConnectionState.Closed);
|
||||
ReliableConnectionHelper.OpenConnection(connection);
|
||||
Assert.True(connection.State == ConnectionState.Open);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that ReliableConnectionHelper.ExecuteNonQuery() runs successfully
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestExecuteNonQuery()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
var result = ReliableConnectionHelper.ExecuteNonQuery(
|
||||
CreateTestConnectionStringBuilder(),
|
||||
"SET NOCOUNT ON; SET NOCOUNT OFF;",
|
||||
ReliableConnectionHelper.SetCommandTimeout,
|
||||
null,
|
||||
true
|
||||
);
|
||||
Assert.NotNull(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that TryGetServerVersion() gets server information
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestTryGetServerVersion()
|
||||
{
|
||||
TestUtils.RunIfWindows(() =>
|
||||
{
|
||||
ReliableConnectionHelper.ServerInfo info = null;
|
||||
Assert.True(ReliableConnectionHelper.TryGetServerVersion(CreateTestConnectionStringBuilder().ConnectionString, out info));
|
||||
|
||||
Assert.NotNull(info);
|
||||
Assert.NotNull(info.ServerVersion);
|
||||
Assert.NotEmpty(info.ServerVersion);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // LIVE_CONNECTION_TESTS
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
@@ -25,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
public TestBindingContext()
|
||||
{
|
||||
this.BindingLocked = new ManualResetEvent(initialState: true);
|
||||
this.BindingLock = new ManualResetEvent(true);
|
||||
this.BindingTimeout = 3000;
|
||||
}
|
||||
|
||||
@@ -39,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
|
||||
public IBinder Binder { get; set; }
|
||||
|
||||
public ManualResetEvent BindingLocked { get; set; }
|
||||
public ManualResetEvent BindingLock { get; set; }
|
||||
|
||||
public int BindingTimeout { get; set; }
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.Setup(file => file.GetLinesInRange(It.IsAny<BufferRange>()))
|
||||
.Returns(new string[] { Common.StandardQuery });
|
||||
.Returns(new[] { Common.StandardQuery });
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
@@ -36,7 +36,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.GetSubSectionDocument(), OwnerUri = Common.OwnerUri };
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
|
||||
|
||||
// ... And then I request to cancel the query
|
||||
@@ -50,8 +51,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
VerifyQueryCancelCallCount(cancelRequest, Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
// ... The query should have been disposed as well
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
// ... The query should not have been disposed
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -71,13 +72,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And then I request to cancel the query
|
||||
var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri};
|
||||
QueryCancelResult result = null;
|
||||
var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleCancelRequest(cancelParams, cancelRequest.Object).Wait();
|
||||
await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... I should have seen a result event with an error message
|
||||
|
||||
@@ -14,9 +14,6 @@ using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
@@ -95,7 +92,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
|
||||
Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory());
|
||||
query.Execute().Wait();
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -287,6 +285,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
return new QueryExecutionService(connectionService, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory()};
|
||||
}
|
||||
|
||||
public static WorkspaceService<SqlToolsSettings> GetPrimedWorkspaceService()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(StandardQuery);
|
||||
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
return workspaceService.Object;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
@@ -51,7 +50,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And then I dispose of the query
|
||||
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri};
|
||||
@@ -107,6 +107,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And it sticks around as an active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
|
||||
@@ -19,7 +19,6 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -74,9 +73,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(1, batch.ResultMessages.Count());
|
||||
Assert.Contains("1 ", batch.ResultMessages.First().Message);
|
||||
// NOTE: 1 is expected because this test simulates a 'update' statement where 1 row was affected.
|
||||
// The 1 in quotes is to make sure the 1 isn't part of a larger number
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -108,7 +104,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||
Assert.Contains(Common.StandardRows.ToString(), batch.ResultMessages.First().Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -150,13 +145,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// ... Inside each result summary, there should be 5 column definitions
|
||||
Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length);
|
||||
}
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||
foreach (var rsm in batch.ResultMessages)
|
||||
{
|
||||
Assert.Contains(Common.StandardRows.ToString(), rsm.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -295,7 +283,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute().Wait();
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
@@ -321,7 +310,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// If:
|
||||
// ... I Then execute the query
|
||||
query.Execute().Wait();
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with no batch summaries returned
|
||||
@@ -348,7 +338,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute().Wait();
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with two batch summaries returned
|
||||
@@ -376,7 +367,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// If:
|
||||
// .. I then execute the query
|
||||
query.Execute().Wait();
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
Assert.True(query.HasExecuted);
|
||||
@@ -402,7 +394,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute().Wait();
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... There should be an error on the batch
|
||||
@@ -444,7 +437,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
expectedEvent: QueryExecuteCompleteEvent.Type,
|
||||
eventCallback: (et, cp) => completeParams = cp,
|
||||
errorCallback: null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
await AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No Errors should have been sent
|
||||
@@ -485,7 +478,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
expectedEvent: QueryExecuteCompleteEvent.Type,
|
||||
eventCallback: (et, cp) => completeParams = cp,
|
||||
errorCallback: null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
await AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
@@ -512,18 +505,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
var requestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
object error = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(e => error = e);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error message should have been returned via the result
|
||||
// ... An error should have been returned
|
||||
// ... No result should have been returned
|
||||
// ... No completion event should have been fired
|
||||
// ... No error event should have been fired
|
||||
// ... There should be no active queries
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Never(), Times.Never());
|
||||
Assert.NotNull(result.Messages);
|
||||
Assert.NotEmpty(result.Messages);
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.IsType<string>(error);
|
||||
Assert.NotEmpty((string)error);
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
@@ -545,24 +539,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait();
|
||||
var firstRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query without waiting for the first to complete
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
|
||||
QueryExecuteResult result = null;
|
||||
var secondRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait();
|
||||
object error = null;
|
||||
var secondRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(e => error = e);
|
||||
await AwaitExecution(queryService, queryParams, secondRequestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A result should have been sent with an error message
|
||||
// ... An error should have been sent
|
||||
// ... A result should have not have been sent
|
||||
// ... No completion event should have been fired
|
||||
// ... There should only be one active query
|
||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.AtMostOnce(), Times.Never());
|
||||
Assert.NotNull(result.Messages);
|
||||
Assert.NotEmpty(result.Messages);
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
// ... The original query should exist
|
||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.IsType<string>(error);
|
||||
Assert.NotEmpty((string)error);
|
||||
Assert.Contains(Common.OwnerUri, queryService.ActiveQueries.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -584,15 +579,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
|
||||
queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait();
|
||||
await AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query after waiting for the first to complete
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
var secondRequestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null);
|
||||
queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait();
|
||||
await AwaitExecution(queryService, queryParams, secondRequestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
@@ -606,7 +600,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
public async void QueryExecuteMissingSelectionTest(SelectionData selection)
|
||||
public async Task QueryExecuteMissingSelectionTest(SelectionData selection)
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
@@ -621,18 +615,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = selection };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
object errorResult = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(error => errorResult = error);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A result should have been sent with an error message
|
||||
// ... Am error should have been sent
|
||||
// ... No result should have been sent
|
||||
// ... No completion event should have been fired
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Never(), Times.Never());
|
||||
Assert.NotNull(result.Messages);
|
||||
Assert.NotEmpty(result.Messages);
|
||||
// ... An active query should not have been added
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.NotNull(errorResult);
|
||||
Assert.IsType<string>(errorResult);
|
||||
Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys);
|
||||
|
||||
// ... There should not be an active query
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
@@ -657,7 +653,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null);
|
||||
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait();
|
||||
await AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
@@ -700,7 +696,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
#endregion
|
||||
|
||||
private void VerifyQueryExecuteCallCount(Mock<RequestContext<QueryExecuteResult>> mock, Times sendResultCalls, Times sendEventCalls, Times sendErrorCalls)
|
||||
private static void VerifyQueryExecuteCallCount(Mock<RequestContext<QueryExecuteResult>> mock, Times sendResultCalls, Times sendEventCalls, Times sendErrorCalls)
|
||||
{
|
||||
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()), sendResultCalls);
|
||||
mock.Verify(rc => rc.SendEvent(
|
||||
@@ -709,9 +705,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
||||
}
|
||||
|
||||
private DbConnection GetConnection(ConnectionInfo info)
|
||||
private static DbConnection GetConnection(ConnectionInfo info)
|
||||
{
|
||||
return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
}
|
||||
|
||||
private static async Task AwaitExecution(QueryExecutionService service, QueryExecuteParams qeParams,
|
||||
RequestContext<QueryExecuteResult> requestContext)
|
||||
{
|
||||
await service.HandleExecuteRequest(qeParams, requestContext);
|
||||
await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -28,19 +29,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvSuccessTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// Request to save the results as csv with correct parameters
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -54,7 +48,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
@@ -74,20 +73,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvWithSelectionSuccessTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// Request to save the results as csv with correct parameters
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -105,7 +96,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
@@ -124,21 +120,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvExceptionTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// Request to save the results as csv with incorrect filepath
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -148,11 +136,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
BatchIndex = 0,
|
||||
FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.csv" : "/test.csv"
|
||||
};
|
||||
// SaveResultRequestResult result = null;
|
||||
string errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err);
|
||||
|
||||
SaveResultRequestError errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
// Expect to see error message
|
||||
Assert.NotNull(errMessage);
|
||||
@@ -166,13 +159,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvQueryNotFoundTest()
|
||||
{
|
||||
|
||||
// Create a query execution service
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as csv with query that is no longer active
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -198,19 +187,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsJsonSuccessTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// Request to save the results as json with correct parameters
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -223,7 +205,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
@@ -243,19 +232,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsJsonWithSelectionSuccessTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// Request to save the results as json with correct parameters
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -265,14 +247,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
BatchIndex = 0,
|
||||
FilePath = "testwrite_5.json",
|
||||
RowStartIndex = 0,
|
||||
RowEndIndex = 0,
|
||||
RowEndIndex = 1,
|
||||
ColumnStartIndex = 0,
|
||||
ColumnEndIndex = 0
|
||||
ColumnEndIndex = 1
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
@@ -292,18 +279,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsJsonExceptionTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// Request to save the results as json with incorrect filepath
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -313,11 +294,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
BatchIndex = 0,
|
||||
FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.json" : "/test.json"
|
||||
};
|
||||
// SaveResultRequestResult result = null;
|
||||
string errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err);
|
||||
|
||||
|
||||
SaveResultRequestError errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
// Expect to see error message
|
||||
Assert.NotNull(errMessage);
|
||||
@@ -331,12 +318,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async void SaveResultsAsJsonQueryNotFoundTest()
|
||||
{
|
||||
|
||||
// Create a query service
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as json with query that is no longer active
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -404,52 +389,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock request context for executing a query
|
||||
/// </summary>
|
||||
/// <param name="resultCallback"></param>
|
||||
/// <param name="Action<EventType<QueryExecuteCompleteParams>"></param>
|
||||
/// <param name="eventCallback"></param>
|
||||
/// <param name="errorCallback"></param>
|
||||
/// <returns></returns>
|
||||
public static Mock<RequestContext<QueryExecuteResult>> GetQueryExecuteResultContextMock(
|
||||
Action<QueryExecuteResult> resultCallback,
|
||||
Action<EventType<QueryExecuteCompleteParams>, QueryExecuteCompleteParams> eventCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<QueryExecuteResult>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendEvent
|
||||
var sendEventFlow = requestContext.Setup(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteCompleteParams>>(m => m == QueryExecuteCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteCompleteParams>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (eventCallback != null)
|
||||
{
|
||||
sendEventFlow.Callback(eventCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = requestContext.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
@@ -146,8 +146,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true,
|
||||
workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And I then ask for a valid set of results from it
|
||||
var subsetParams = new QueryExecuteSubsetParams {OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0};
|
||||
@@ -203,8 +204,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true,
|
||||
workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false;
|
||||
|
||||
// ... And I then ask for a valid set of results from it
|
||||
@@ -224,17 +226,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
[Fact]
|
||||
public async void SubsetServiceOutOfRangeSubsetTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that doesn't have any result sets
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(null, false), true,
|
||||
workspaceService.Object);
|
||||
Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And I then ask for a set of results from it
|
||||
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
@@ -259,27 +259,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Action<QueryExecuteSubsetResult> resultCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<QueryExecuteSubsetResult>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<QueryExecuteSubsetResult>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = requestContext
|
||||
.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return requestContext;
|
||||
return RequestContextMocks.Create(resultCallback)
|
||||
.AddErrorHandling(errorCallback);
|
||||
}
|
||||
|
||||
private static void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls,
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
Assert.True(sqlToolsSettings.SqlTools.EnableIntellisense);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo);
|
||||
Assert.False(sqlToolsSettings.SqlTools.IntelliSense.LowerCaseSuggestions);
|
||||
@@ -38,17 +38,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// diagnostics is enabled if IntelliSense and Diagnostics flags are set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
|
||||
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
|
||||
// diagnostics is disabled if either IntelliSense and Diagnostics flags is not set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
|
||||
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = false;
|
||||
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
}
|
||||
|
||||
@@ -61,16 +61,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// suggestions is enabled if IntelliSense and Suggestions flags are set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
|
||||
Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
|
||||
// suggestions is disabled if either IntelliSense and Suggestions flags is not set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
|
||||
Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = false;
|
||||
Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
}
|
||||
@@ -84,16 +84,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// quick info is enabled if IntelliSense and quick info flags are set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
|
||||
Assert.True(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
|
||||
// quick info is disabled if either IntelliSense and quick info flags is not set
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
|
||||
Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = false;
|
||||
Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.Test.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the CommandOptions class
|
||||
/// </summary>
|
||||
public class CommandOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void LoggingEnabledWhenFlagProvided()
|
||||
{
|
||||
var args = new string[] {"--enable-logging"};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.True(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoggingDisabledWhenFlagNotProvided()
|
||||
{
|
||||
var args = new string[] {};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.False(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsageIsShownWhenHelpFlagProvided()
|
||||
{
|
||||
var args = new string[] {"--help"};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.True(options.ShouldExit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsageIsShownWhenBadArgumentsProvided()
|
||||
{
|
||||
var args = new string[] {"--unknown-argument", "/bad-argument"};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.True(options.ShouldExit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultValuesAreUsedWhenNoArgumentsAreProvided()
|
||||
{
|
||||
var args = new string[] {};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.False(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
||||
test();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunIfLinuxOrOSX(Action test)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
test();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunIfWindows(Action test)
|
||||
{
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
"buildOptions": {
|
||||
"debugType": "portable"
|
||||
},
|
||||
"configurations": {
|
||||
"Integration": {
|
||||
"buildOptions": {
|
||||
"define": [
|
||||
"LIVE_CONNECTION_TESTS"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"System.Runtime.Serialization.Primitives": "4.1.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",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
//
|
||||
// The following is based upon code from PowerShell Editor Services
|
||||
// License: https://github.com/PowerShell/PowerShellEditorServices/blob/develop/LICENSE
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver
|
||||
{
|
||||
/// <summary>
|
||||
/// Test driver for the service host
|
||||
/// </summary>
|
||||
public class ServiceTestDriver : TestDriverBase
|
||||
{
|
||||
public ServiceTestDriver(string serviceHostExecutable)
|
||||
{
|
||||
var clientChannel = new StdioClientChannel(serviceHostExecutable);
|
||||
this.protocolClient = new ProtocolEndpoint(clientChannel, MessageProtocolType.LanguageServer);
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
await this.protocolClient.Start();
|
||||
await Task.Delay(1000); // Wait for the service host to start
|
||||
}
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
await this.protocolClient.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
//
|
||||
// The following is based upon code from PowerShell Editor Services
|
||||
// License: https://github.com/PowerShell/PowerShellEditorServices/blob/develop/LICENSE
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps the ProtocolEndpoint class with queues to handle events/requests
|
||||
/// </summary>
|
||||
public class TestDriverBase
|
||||
{
|
||||
protected ProtocolEndpoint protocolClient;
|
||||
|
||||
private ConcurrentDictionary<string, AsyncQueue<object>> eventQueuePerType =
|
||||
new ConcurrentDictionary<string, AsyncQueue<object>>();
|
||||
|
||||
private ConcurrentDictionary<string, AsyncQueue<object>> requestQueuePerType =
|
||||
new ConcurrentDictionary<string, AsyncQueue<object>>();
|
||||
|
||||
public Task<TResult> SendRequest<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
TParams requestParams)
|
||||
{
|
||||
return
|
||||
this.protocolClient.SendRequest(
|
||||
requestType,
|
||||
requestParams);
|
||||
}
|
||||
|
||||
public Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
||||
{
|
||||
return
|
||||
this.protocolClient.SendEvent(
|
||||
eventType,
|
||||
eventParams);
|
||||
}
|
||||
|
||||
public void QueueEventsForType<TParams>(EventType<TParams> eventType)
|
||||
{
|
||||
var eventQueue =
|
||||
this.eventQueuePerType.AddOrUpdate(
|
||||
eventType.MethodName,
|
||||
new AsyncQueue<object>(),
|
||||
(key, queue) => queue);
|
||||
|
||||
this.protocolClient.SetEventHandler(
|
||||
eventType,
|
||||
(p, ctx) =>
|
||||
{
|
||||
return eventQueue.EnqueueAsync(p);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<TParams> WaitForEvent<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
int timeoutMilliseconds = 5000)
|
||||
{
|
||||
Task<TParams> eventTask = null;
|
||||
|
||||
// Use the event queue if one has been registered
|
||||
AsyncQueue<object> eventQueue = null;
|
||||
if (this.eventQueuePerType.TryGetValue(eventType.MethodName, out eventQueue))
|
||||
{
|
||||
eventTask =
|
||||
eventQueue
|
||||
.DequeueAsync()
|
||||
.ContinueWith<TParams>(
|
||||
task => (TParams)task.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskCompletionSource<TParams> eventTaskSource = new TaskCompletionSource<TParams>();
|
||||
|
||||
this.protocolClient.SetEventHandler(
|
||||
eventType,
|
||||
(p, ctx) =>
|
||||
{
|
||||
if (!eventTaskSource.Task.IsCompleted)
|
||||
{
|
||||
eventTaskSource.SetResult(p);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
},
|
||||
true); // Override any existing handler
|
||||
|
||||
eventTask = eventTaskSource.Task;
|
||||
}
|
||||
|
||||
await
|
||||
Task.WhenAny(
|
||||
eventTask,
|
||||
Task.Delay(timeoutMilliseconds));
|
||||
|
||||
if (!eventTask.IsCompleted)
|
||||
{
|
||||
throw new TimeoutException(
|
||||
string.Format(
|
||||
"Timed out waiting for '{0}' event!",
|
||||
eventType.MethodName));
|
||||
}
|
||||
|
||||
return await eventTask;
|
||||
}
|
||||
|
||||
public async Task<Tuple<TParams, RequestContext<TResponse>>> WaitForRequest<TParams, TResponse>(
|
||||
RequestType<TParams, TResponse> requestType,
|
||||
int timeoutMilliseconds = 5000)
|
||||
{
|
||||
Task<Tuple<TParams, RequestContext<TResponse>>> requestTask = null;
|
||||
|
||||
// Use the request queue if one has been registered
|
||||
AsyncQueue<object> requestQueue = null;
|
||||
if (this.requestQueuePerType.TryGetValue(requestType.MethodName, out requestQueue))
|
||||
{
|
||||
requestTask =
|
||||
requestQueue
|
||||
.DequeueAsync()
|
||||
.ContinueWith(
|
||||
task => (Tuple<TParams, RequestContext<TResponse>>)task.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
var requestTaskSource =
|
||||
new TaskCompletionSource<Tuple<TParams, RequestContext<TResponse>>>();
|
||||
|
||||
this.protocolClient.SetRequestHandler(
|
||||
requestType,
|
||||
(p, ctx) =>
|
||||
{
|
||||
if (!requestTaskSource.Task.IsCompleted)
|
||||
{
|
||||
requestTaskSource.SetResult(
|
||||
new Tuple<TParams, RequestContext<TResponse>>(p, ctx));
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
|
||||
requestTask = requestTaskSource.Task;
|
||||
}
|
||||
|
||||
await
|
||||
Task.WhenAny(
|
||||
requestTask,
|
||||
Task.Delay(timeoutMilliseconds));
|
||||
|
||||
if (!requestTask.IsCompleted)
|
||||
{
|
||||
throw new TimeoutException(
|
||||
string.Format(
|
||||
"Timed out waiting for '{0}' request!",
|
||||
requestType.MethodName));
|
||||
}
|
||||
|
||||
return await requestTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66
test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs
Normal file
66
test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.TestDriver
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
internal static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
Console.WriteLine( "Microsoft.SqlTools.ServiceLayer.TestDriver.exe <service host executable> [tests]" + Environment.NewLine +
|
||||
" <service host executable> is the path to the Microsoft.SqlTools.ServiceLayer.exe executable" + Environment.NewLine +
|
||||
" [tests] is a space-separated list of tests to run." + Environment.NewLine +
|
||||
" They are qualified within the Microsoft.SqlTools.ServiceLayer.TestDriver.Tests namespace");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var serviceHostExecutable = args[0];
|
||||
var tests = args.Skip(1);
|
||||
|
||||
foreach (var test in tests)
|
||||
{
|
||||
ServiceTestDriver driver = null;
|
||||
|
||||
try
|
||||
{
|
||||
driver = new ServiceTestDriver(serviceHostExecutable);
|
||||
|
||||
var className = test.Substring(0, test.LastIndexOf('.'));
|
||||
var methodName = test.Substring(test.LastIndexOf('.') + 1);
|
||||
|
||||
var type = Type.GetType("Microsoft.SqlTools.ServiceLayer.TestDriver.Tests." + className);
|
||||
var typeInstance = Activator.CreateInstance(type);
|
||||
MethodInfo methodInfo = type.GetMethod(methodName);
|
||||
|
||||
await driver.Start();
|
||||
Console.WriteLine("Running test " + test);
|
||||
await (Task)methodInfo.Invoke(typeInstance, new object[] {driver});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (driver != null)
|
||||
{
|
||||
await driver.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
|
||||
{
|
||||
public class ExampleTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Example test that performs a connect, then disconnect.
|
||||
/// All tests must have the same signature of returning an async Task
|
||||
/// and taking in a ServiceTestDriver as a parameter.
|
||||
/// </summary>
|
||||
public async Task ConnectDisconnectTest(ServiceTestDriver driver)
|
||||
{
|
||||
var connectParams = new ConnectParams();
|
||||
connectParams.OwnerUri = "file";
|
||||
connectParams.Connection = new ConnectionDetails();
|
||||
connectParams.Connection.ServerName = "localhost";
|
||||
connectParams.Connection.AuthenticationType = "Integrated";
|
||||
|
||||
var result = await driver.SendRequest(ConnectionRequest.Type, connectParams);
|
||||
if (result)
|
||||
{
|
||||
await driver.WaitForEvent(ConnectionCompleteNotification.Type);
|
||||
|
||||
var disconnectParams = new DisconnectParams();
|
||||
disconnectParams.OwnerUri = "file";
|
||||
var result2 = await driver.SendRequest(DisconnectRequest.Type, disconnectParams);
|
||||
if (result2)
|
||||
{
|
||||
Console.WriteLine("success");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json
Normal file
29
test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Microsoft.SqlTools.ServiceLayer.TestDriver",
|
||||
"version": "1.0.0-*",
|
||||
"buildOptions": {
|
||||
"debugType": "portable",
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.SqlTools.ServiceLayer": {
|
||||
"target": "project"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"dotnet5.4",
|
||||
"portable-net451+win8"
|
||||
],
|
||||
}
|
||||
},
|
||||
"runtimes": {
|
||||
"win7-x64": {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user