Merge branch 'release/ctp10'

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

3
.gitignore vendored
View File

@@ -284,4 +284,5 @@ Session.vim
# Stuff from cake # Stuff from cake
/artifacts/ /artifacts/
/.tools/ /.tools/
/.dotnet/

Binary file not shown.

Binary file not shown.

View File

@@ -257,6 +257,7 @@ Task("TestCore")
/// </summary> /// </summary>
Task("Test") Task("Test")
.IsDependentOn("Setup") .IsDependentOn("Setup")
.IsDependentOn("SRGen")
.IsDependentOn("BuildTest") .IsDependentOn("BuildTest")
.Does(() => .Does(() =>
{ {
@@ -304,6 +305,7 @@ Task("Test")
/// </summary> /// </summary>
Task("OnlyPublish") Task("OnlyPublish")
.IsDependentOn("Setup") .IsDependentOn("Setup")
.IsDependentOn("SRGen")
.Does(() => .Does(() =>
{ {
var project = buildPlan.MainProject; var project = buildPlan.MainProject;
@@ -322,7 +324,13 @@ Task("OnlyPublish")
publishArguments = $"{publishArguments} --output \"{outputFolder}\" \"{projectFolder}\""; publishArguments = $"{publishArguments} --output \"{outputFolder}\" \"{projectFolder}\"";
Run(dotnetcli, publishArguments) Run(dotnetcli, publishArguments)
.ExceptionOnError($"Failed to publish {project} / {framework}"); .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) if (requireArchive)
{ {
Package(runtime, framework, outputFolder, packageFolder, buildPlan.MainProject.ToLower()); Package(runtime, framework, outputFolder, packageFolder, buildPlan.MainProject.ToLower());
@@ -359,7 +367,6 @@ Task("RestrictToLocalRuntime")
/// </summary> /// </summary>
Task("LocalPublish") Task("LocalPublish")
.IsDependentOn("Restore") .IsDependentOn("Restore")
.IsDependentOn("SrGen")
.IsDependentOn("RestrictToLocalRuntime") .IsDependentOn("RestrictToLocalRuntime")
.IsDependentOn("OnlyPublish") .IsDependentOn("OnlyPublish")
.Does(() => .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}\"", 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); srgenPath, outputResx, outputCs, projectName, projectName, projectStrings);
Information("{0}", dotnetArgs); Information("{0}", dotnetArgs);
Run(dotnetcli, dotnetArgs); Run(dotnetcli, dotnetArgs)
.ExceptionOnError("Failed to run SRGen.");
} }
}); });

0
build.sh Normal file → Executable file
View File

View File

@@ -2,6 +2,7 @@
<configuration > <configuration >
<packageSources> <packageSources>
<add key="Nuget" value="https://www.nuget.org/api/v2" /> <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> </packageSources>
</configuration> </configuration>

View File

@@ -2,5 +2,5 @@
<packages> <packages>
<package id="Cake" version="0.10.1" /> <package id="Cake" version="0.10.1" />
<package id="Newtonsoft.Json" version="8.0.3" /> <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> </packages>

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.Common; using System.Data.Common;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
@@ -205,7 +206,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
var cancellationTask = Task.Run(() => var cancellationTask = Task.Run(() =>
{ {
source.Token.WaitHandle.WaitOne(); source.Token.WaitHandle.WaitOne();
source.Token.ThrowIfCancellationRequested(); try
{
source.Token.ThrowIfCancellationRequested();
}
catch (ObjectDisposedException)
{
// Ignore
}
}); });
var openTask = Task.Run(async () => { var openTask = Task.Run(async () => {
@@ -367,7 +375,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
// Success // Success
return true; return true;
} }
/// <summary> /// <summary>
/// List all databases on the server specified /// List all databases on the server specified
/// </summary> /// </summary>
@@ -393,18 +401,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails)); var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails));
connection.Open(); 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>(); 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(); connection.Close();
ListDatabasesResponse response = new ListDatabasesResponse(); ListDatabasesResponse response = new ListDatabasesResponse();

View File

@@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
@@ -215,6 +216,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
public static bool IsRetryableNetworkConnectivityError(int errorNumber) 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); return _retryableNetworkConnectivityErrors.Contains(errorNumber);
} }

View File

@@ -150,14 +150,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
Capabilities = new ServerCapabilities Capabilities = new ServerCapabilities
{ {
TextDocumentSync = TextDocumentSyncKind.Incremental, TextDocumentSync = TextDocumentSyncKind.Incremental,
DefinitionProvider = true, DefinitionProvider = false,
ReferencesProvider = true, ReferencesProvider = false,
DocumentHighlightProvider = true, DocumentHighlightProvider = false,
HoverProvider = true, HoverProvider = true,
CompletionProvider = new CompletionOptions CompletionProvider = new CompletionOptions
{ {
ResolveProvider = true, ResolveProvider = true,
TriggerCharacters = new string[] { ".", "-", ":", "\\", ",", " " } TriggerCharacters = new string[] { ".", "-", ":", "\\" }
}, },
SignatureHelpProvider = new SignatureHelpOptions SignatureHelpProvider = new SignatureHelpOptions
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
@@ -52,6 +51,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary> /// </summary>
public int? BindingTimeout { get; set; } 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> /// <summary>
/// Converts the result of the execution to type T /// Converts the result of the execution to type T
/// </summary> /// </summary>

View File

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

View File

@@ -23,9 +23,16 @@ namespace Microsoft.SqlTools.ServiceLayer
/// </summary> /// </summary>
internal static void Main(string[] args) 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 // turn on Verbose logging during early development
// we need to switch to Normal when preparing for public preview // 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"); Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host");
// set up the host details and profile paths // set up the host details and profile paths

View File

@@ -31,7 +31,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
private bool disposed; private bool disposed;
/// <summary> /// <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> /// </summary>
private readonly IFileStreamFactory outputFileFactory; private readonly IFileStreamFactory outputFileFactory;
@@ -69,6 +79,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary> /// </summary>
public string BatchText { get; set; } 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> /// <summary>
/// Whether or not this batch has an error /// Whether or not this batch has an error
/// </summary> /// </summary>
@@ -90,7 +124,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <summary> /// <summary>
/// The result sets of the batch execution /// The result sets of the batch execution
/// </summary> /// </summary>
public IEnumerable<ResultSet> ResultSets public IList<ResultSet> ResultSets
{ {
get { return resultSets; } get { return resultSets; }
} }
@@ -136,14 +170,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
try try
{ {
DbCommand command = null; 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; ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
if (sqlConn != null) 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; sqlConn.GetUnderlyingConnection().InfoMessage += StoreDbMessage;
command = sqlConn.GetUnderlyingConnection().CreateCommand(); command = sqlConn.GetUnderlyingConnection().CreateCommand();
// Add a handler for when the command completes
SqlCommand sqlCommand = (SqlCommand) command;
sqlCommand.StatementCompleted += StatementCompletedHandler;
} }
else 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 // 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 // Create a command that we'll use for executing the query
using (command) using (command)
@@ -159,6 +197,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
command.CommandText = BatchText; command.CommandText = BatchText;
command.CommandType = CommandType.Text; command.CommandType = CommandType.Text;
command.CommandTimeout = 0; command.CommandTimeout = 0;
executionStartTime = DateTime.Now;
// Execute the command to get back a reader // Execute the command to get back a reader
using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken)) 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) // Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries)
if (!reader.HasRows && reader.FieldCount == 0) 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; continue;
} }
// This resultset has results (ie, SELECT/etc queries) // This resultset has results (ie, SELECT/etc queries)
// Read until we hit the end of the result set
ResultSet resultSet = new ResultSet(reader, outputFileFactory); ResultSet resultSet = new ResultSet(reader, outputFileFactory);
await resultSet.ReadResultToEnd(cancellationToken);
// Add the result set to the results of the query // Add the result set to the results of the query
resultSets.Add(resultSet); 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)); } 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; HasError = true;
UnwrapDbException(dbe); UnwrapDbException(dbe);
} }
catch (Exception) catch (TaskCanceledException)
{
resultMessages.Add(new ResultMessage(SR.QueryServiceQueryCancelled));
throw;
}
catch (Exception e)
{ {
HasError = true; HasError = true;
resultMessages.Add(new ResultMessage(SR.QueryServiceQueryFailed(e.Message)));
throw; throw;
} }
finally finally
@@ -210,6 +257,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Mark that we have executed // Mark that we have executed
HasExecuted = true; HasExecuted = true;
executionEndTime = DateTime.Now;
} }
} }
@@ -236,6 +284,29 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
#region Private Helpers #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> /// <summary>
/// Delegate handler for storing messages that are returned from the server /// 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 /// 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; SqlException se = dbe as SqlException;
if (se != null) 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; // User cancellation error, add the single message
if (sqlError != null) 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}", string message = string.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}",
sqlError.Number, sqlError.Class, sqlError.State, lineNumber, error.Number, error.Class, error.State, lineNumber,
Environment.NewLine, sqlError.Message); Environment.NewLine, error.Message);
resultMessages.Add(new ResultMessage(message)); resultMessages.Add(new ResultMessage(message));
} }
} }

View File

@@ -10,6 +10,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// </summary> /// </summary>
public class BatchSummary 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> /// <summary>
/// Whether or not the batch was successful. True indicates errors, false indicates success /// Whether or not the batch was successful. True indicates errors, false indicates success
/// </summary> /// </summary>

View File

@@ -21,6 +21,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// Summaries of the result sets that were returned with the query /// Summaries of the result sets that were returned with the query
/// </summary> /// </summary>
public BatchSummary[] BatchSummaries { get; set; } public BatchSummary[] BatchSummaries { get; set; }
/// <summary>
/// Error message, if any
/// </summary>
public string Message { get; set; }
} }
public class QueryExecuteCompleteEvent public class QueryExecuteCompleteEvent

View File

@@ -60,17 +60,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// Parameters to save results as CSV /// Parameters to save results as CSV
/// </summary> /// </summary>
public class SaveResultsAsCsvRequestParams: SaveResultsRequestParams{ 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> /// <summary>
/// Include headers of columns in CSV /// Include headers of columns in CSV
/// </summary> /// </summary>
@@ -95,6 +84,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
public string Messages { get; set; } 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> /// <summary>
/// Request type to save results as CSV /// Request type to save results as CSV
/// </summary> /// </summary>

View File

@@ -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
}
}
}
}

View File

@@ -96,6 +96,33 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
#region Properties #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> /// <summary>
/// The batches underneath this query /// The batches underneath this query
/// </summary> /// </summary>
@@ -116,6 +143,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return Batches.Select((batch, index) => new BatchSummary return Batches.Select((batch, index) => new BatchSummary
{ {
Id = index, Id = index,
ExecutionStart = batch.ExecutionStartTimeStamp,
ExecutionEnd = batch.ExecutionEndTimeStamp,
ExecutionElapsed = batch.ExecutionElapsedTime,
HasError = batch.HasError, HasError = batch.HasError,
Messages = batch.ResultMessages.ToArray(), Messages = batch.ResultMessages.ToArray(),
ResultSetSummaries = batch.ResultSummaries, ResultSetSummaries = batch.ResultSummaries,
@@ -124,6 +154,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
} }
} }
internal Task ExecutionTask { get; private set; }
/// <summary> /// <summary>
/// Whether or not the query has completed executed, regardless of success or failure /// Whether or not the query has completed executed, regardless of success or failure
/// </summary> /// </summary>
@@ -167,10 +199,44 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
cancellationSource.Cancel(); 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> /// <summary>
/// Executes this query asynchronously and collects all result sets /// Executes this query asynchronously and collects all result sets
/// </summary> /// </summary>
public async Task Execute() private async Task ExecuteInternal()
{ {
// Mark that we've internally executed // Mark that we've internally executed
hasExecuteBeenCalled = true; hasExecuteBeenCalled = true;
@@ -186,7 +252,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// TODO: Don't create a new connection every time, see TFS #834978 // TODO: Don't create a new connection every time, see TFS #834978
using (DbConnection conn = editorConnection.Factory.CreateSqlConnection(connectionString)) 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; ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
if (sqlConn != null) if (sqlConn != null)
@@ -202,6 +280,20 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{ {
await b.Execute(conn, cancellationSource.Token); 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 finally
{ {
@@ -227,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
throw new InvalidOperationException(SR.QueryServiceMessageSenderNotSql); 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)? // Did the database context change (error code 5701)?
if (error.Number == DatabaseContextChangeErrorNumber) 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 #endregion
#region IDisposable Implementation #region IDisposable Implementation

View File

@@ -4,7 +4,6 @@
// //
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
@@ -16,7 +15,6 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Newtonsoft.Json;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{ {
@@ -129,19 +127,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
public async Task HandleExecuteRequest(QueryExecuteParams executeParams, public async Task HandleExecuteRequest(QueryExecuteParams executeParams,
RequestContext<QueryExecuteResult> requestContext) 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 // Execute the query -- asynchronously
await ExecuteAndCompleteQuery(executeParams, requestContext, newQuery); await ExecuteAndCompleteQuery(executeParams, requestContext, newQuery);
}
catch (Exception e)
{
// Dump any unexpected exceptions as errors
await requestContext.SendError(e.Message);
}
} }
public async Task HandleResultSubsetRequest(QueryExecuteSubsetParams subsetParams, public async Task HandleResultSubsetRequest(QueryExecuteSubsetParams subsetParams,
@@ -239,21 +229,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return; return;
} }
// Cancel the query // Cancel the query and send a success message
result.Cancel(); 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()); await requestContext.SendResult(new QueryCancelResult());
} }
catch (InvalidOperationException e) catch (InvalidOperationException e)
@@ -273,7 +250,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <summary> /// <summary>
/// Process request to save a resultSet to a file in CSV format /// Process request to save a resultSet to a file in CSV format
/// </summary> /// </summary>
public async Task HandleSaveResultsAsCsvRequest(SaveResultsAsCsvRequestParams saveParams, internal async Task HandleSaveResultsAsCsvRequest(SaveResultsAsCsvRequestParams saveParams,
RequestContext<SaveResultRequestResult> requestContext) RequestContext<SaveResultRequestResult> requestContext)
{ {
// retrieve query for OwnerUri // retrieve query for OwnerUri
@@ -286,67 +263,39 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}); });
return; 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 selectedResultSet.RemoveSaveTask(saveParams.FilePath);
Batch selectedBatch = result.Batches[saveParams.BatchIndex]; await requestContext.SendResult(new SaveResultRequestResult { Messages = message });
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex]; };
int columnCount = 0; saveAsCsv.SaveCompleted += successHandler;
int rowCount = 0; SaveResults.AsyncSaveEventHandler errorHandler = async message =>
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))
{ {
File.Delete(saveParams.FilePath); selectedResultSet.RemoveSaveTask(saveParams.FilePath);
} await requestContext.SendError(new SaveResultRequestError { message = message });
await requestContext.SendError(ex.Message); };
saveAsCsv.SaveFailed += errorHandler;
saveAsCsv.SaveResultSetAsCsv(saveParams, requestContext, result);
// Associate the ResultSet with the save task
selectedResultSet.AddSaveTask(saveParams.FilePath, saveAsCsv.SaveTask);
} }
} }
/// <summary> /// <summary>
/// Process request to save a resultSet to a file in JSON format /// Process request to save a resultSet to a file in JSON format
/// </summary> /// </summary>
public async Task HandleSaveResultsAsJsonRequest(SaveResultsAsJsonRequestParams saveParams, internal async Task HandleSaveResultsAsJsonRequest(SaveResultsAsJsonRequestParams saveParams,
RequestContext<SaveResultRequestResult> requestContext) RequestContext<SaveResultRequestResult> requestContext)
{ {
// retrieve query for OwnerUri // retrieve query for OwnerUri
@@ -359,73 +308,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}); });
return; 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))) // Create SaveResults object and add success and error handlers to respective events
using (JsonWriter jsonWriter = new JsonTextWriter(jsonFile) ) SaveResults saveAsJson = new SaveResults();
SaveResults.AsyncSaveEventHandler successHandler = async message =>
{ {
jsonWriter.Formatting = Formatting.Indented; selectedResultSet.RemoveSaveTask(saveParams.FilePath);
jsonWriter.WriteStartArray(); await requestContext.SendResult(new SaveResultRequestResult { Messages = message });
};
// get the requested resultSet from query saveAsJson.SaveCompleted += successHandler;
Batch selectedBatch = result.Batches[saveParams.BatchIndex]; SaveResults.AsyncSaveEventHandler errorHandler = async message =>
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))
{ {
File.Delete(saveParams.FilePath); selectedResultSet.RemoveSaveTask(saveParams.FilePath);
} await requestContext.SendError(new SaveResultRequestError { message = message });
await requestContext.SendError(ex.Message); };
saveAsJson.SaveFailed += errorHandler;
saveAsJson.SaveResultSetAsJson(saveParams, requestContext, result);
// Associate the ResultSet with the save task
selectedResultSet.AddSaveTask(saveParams.FilePath, saveAsJson.SaveTask);
} }
} }
#endregion #endregion
@@ -440,10 +347,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
ConnectionInfo connectionInfo; ConnectionInfo connectionInfo;
if (!ConnectionService.TryFindConnection(executeParams.OwnerUri, out connectionInfo)) if (!ConnectionService.TryFindConnection(executeParams.OwnerUri, out connectionInfo))
{ {
await requestContext.SendResult(new QueryExecuteResult await requestContext.SendError(SR.QueryServiceQueryInvalidOwnerUri);
{
Messages = SR.QueryServiceQueryInvalidOwnerUri
});
return null; return null;
} }
@@ -463,49 +367,47 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
string queryText; string queryText;
if (executeParams.QuerySelection != null) if (executeParams.QuerySelection != null)
{ {
string[] queryTextArray = queryFile.GetLinesInRange( string[] queryTextArray = queryFile.GetLinesInRange(
new BufferRange( new BufferRange(
new BufferPosition( new BufferPosition(
executeParams.QuerySelection.StartLine + 1, executeParams.QuerySelection.StartLine + 1,
executeParams.QuerySelection.StartColumn + 1 executeParams.QuerySelection.StartColumn + 1
), ),
new BufferPosition( new BufferPosition(
executeParams.QuerySelection.EndLine + 1, executeParams.QuerySelection.EndLine + 1,
executeParams.QuerySelection.EndColumn + 1 executeParams.QuerySelection.EndColumn + 1
) )
) )
); );
queryText = queryTextArray.Aggregate((a, b) => a + '\r' + '\n' + b); queryText = queryTextArray.Aggregate((a, b) => a + '\r' + '\n' + b);
} }
else else
{ {
queryText = queryFile.Contents; queryText = queryFile.Contents;
} }
// If we can't add the query now, it's assumed the query is in progress // If we can't add the query now, it's assumed the query is in progress
Query newQuery = new Query(queryText, connectionInfo, settings, BufferFileFactory); Query newQuery = new Query(queryText, connectionInfo, settings, BufferFileFactory);
if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery)) if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery))
{ {
await requestContext.SendResult(new QueryExecuteResult await requestContext.SendError(SR.QueryServiceQueryInProgress);
{ newQuery.Dispose();
Messages = SR.QueryServiceQueryInProgress
});
return null; return null;
} }
return newQuery; return newQuery;
} }
catch (ArgumentException ane) catch (Exception e)
{ {
await requestContext.SendResult(new QueryExecuteResult { Messages = ane.Message }); await requestContext.SendError(e.Message);
return null; return null;
} }
// Any other exceptions will fall through here and be collected at the end // 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 // Skip processing if the query is null
if (query == null) if (query == null)
@@ -513,21 +415,41 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return; return;
} }
// Launch the query and respond with successfully launching it // Setup the query completion/failure callbacks
Task executeTask = query.Execute(); 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 await requestContext.SendResult(new QueryExecuteResult
{ {
Messages = null 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 #endregion

View File

@@ -4,9 +4,11 @@
// //
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Common; using System.Data.Common;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
@@ -43,22 +45,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary> /// </summary>
private readonly IFileStreamFactory fileStreamFactory; 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> /// <summary>
/// Whether or not the result set has been read in from the database /// Whether or not the result set has been read in from the database
/// </summary> /// </summary>
private bool hasBeenRead; private bool hasBeenRead;
/// <summary>
/// Whether resultSet is a 'for xml' or 'for json' result
/// </summary>
private bool isSingleColumnXmlJsonResultSet;
/// <summary> /// <summary>
/// The name of the temporary file we're using to output these results in /// The name of the temporary file we're using to output these results in
/// </summary> /// </summary>
private readonly string outputFileName; 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 #endregion
/// <summary> /// <summary>
@@ -80,10 +91,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Store the factory // Store the factory
fileStreamFactory = factory; fileStreamFactory = factory;
hasBeenRead = false; hasBeenRead = false;
saveTasks = new ConcurrentDictionary<string, Task>();
} }
#region Properties #region Properties
/// <summary>
/// Whether the resultSet is in the process of being disposed
/// </summary>
/// <returns></returns>
internal bool IsBeingDisposed
{
get
{
return isBeingDisposed;
}
}
/// <summary> /// <summary>
/// The columns for this result set /// The columns for this result set
/// </summary> /// </summary>
@@ -114,18 +138,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary> /// </summary>
public long RowCount { get; private set; } 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 #endregion
#region Public Methods #region Public Methods
@@ -139,7 +151,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
public Task<ResultSetSubset> GetSubset(int startRow, int rowCount) public Task<ResultSetSubset> GetSubset(int startRow, int rowCount)
{ {
// Sanity check to make sure that the results have been read beforehand // Sanity check to make sure that the results have been read beforehand
if (!hasBeenRead || fileStreamReader == null) if (!hasBeenRead)
{ {
throw new InvalidOperationException(SR.QueryServiceResultSetNotRead); throw new InvalidOperationException(SR.QueryServiceResultSetNotRead);
} }
@@ -156,14 +168,32 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return Task.Factory.StartNew(() => 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;
string[][] rows = rowOffsets.Select(rowOffset =>
fileStreamReader.ReadRow(rowOffset, Columns).Select(cell => cell.DisplayValue).ToArray())
.ToArray();
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 // Retrieve the subset of the results as per the request
return new ResultSetSubset return new ResultSetSubset
{ {
@@ -179,6 +209,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="cancellationToken">Cancellation token for cancelling the query</param> /// <param name="cancellationToken">Cancellation token for cancelling the query</param>
public async Task ReadResultToEnd(CancellationToken cancellationToken) public async Task ReadResultToEnd(CancellationToken cancellationToken)
{ {
// Mark that result has been read
hasBeenRead = true;
// Open a writer for the file // Open a writer for the file
using (IFileStreamWriter fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxXmlCharsToStore)) 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 // Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata
SingleColumnXmlJsonResultSet(); SingleColumnXmlJsonResultSet();
// Mark that result has been read
hasBeenRead = true;
fileStreamReader = fileStreamFactory.GetReader(outputFileName);
} }
#endregion #endregion
@@ -222,13 +251,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return; return;
} }
if (disposing) isBeingDisposed = true;
// Check if saveTasks are running for this ResultSet
if (!saveTasks.IsEmpty)
{ {
fileStreamReader?.Dispose(); // Wait for tasks to finish before disposing ResultSet
fileStreamFactory.DisposeFile(outputFileName); 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 #endregion
@@ -243,19 +290,43 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary> /// </summary>
private void SingleColumnXmlJsonResultSet() { private void SingleColumnXmlJsonResultSet() {
if (Columns?.Length == 1) if (Columns?.Length == 1 && RowCount != 0)
{ {
if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal)) if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal))
{ {
Columns[0].IsXml = true; Columns[0].IsXml = true;
isSingleColumnXmlJsonResultSet = true;
RowCount = 1;
} }
else if (Columns[0].ColumnName.Equals(NameOfForJSONColumn, StringComparison.Ordinal)) else if (Columns[0].ColumnName.Equals(NameOfForJSONColumn, StringComparison.Ordinal))
{ {
Columns[0].IsJson = true; Columns[0].IsJson = true;
isSingleColumnXmlJsonResultSet = true;
RowCount = 1;
} }
} }
} }
#endregion #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
} }
} }

View File

@@ -3,15 +3,47 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System; using System;
using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Newtonsoft.Json;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution 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 /// Method ported from SSMS
/// <summary> /// <summary>
/// Encodes a single field for inserting into a CSV record. The following rules are applied: /// Encodes a single field for inserting into a CSV record. The following rules are applied:
/// <list type="bullet"> /// <list type="bullet">
@@ -32,7 +64,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
internal static String EncodeCsvField(String field) internal static String EncodeCsvField(String field)
{ {
StringBuilder sbField = new StringBuilder(field); StringBuilder sbField = new StringBuilder(field);
//Whether this field has special characters which require it to be embedded in quotes //Whether this field has special characters which require it to be embedded in quotes
bool embedInQuotes = false; bool embedInQuotes = false;
@@ -67,12 +99,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
} }
} }
} }
//Replace all quotes in the original field with double quotes //Replace all quotes in the original field with double quotes
sbField.Replace("\"", "\"\""); sbField.Replace("\"", "\"\"");
String ret = sbField.ToString(); String ret = sbField.ToString();
if (embedInQuotes) if (embedInQuotes)
{ {
ret = "\"" + ret + "\""; ret = "\"" + ret + "\"";
@@ -81,11 +113,208 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return ret; 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 return (saveParams.ColumnStartIndex != null && saveParams.ColumnEndIndex != null
&& saveParams.RowEndIndex != null && saveParams.RowEndIndex != 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);
}
}
});
}
} }
} }

View File

@@ -15,12 +15,19 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// </summary> /// </summary>
public IntelliSenseSettings() public IntelliSenseSettings()
{ {
this.EnableIntellisense = true;
this.EnableSuggestions = true; this.EnableSuggestions = true;
this.LowerCaseSuggestions = false; this.LowerCaseSuggestions = false;
this.EnableDiagnostics = true; this.EnableErrorChecking = true;
this.EnableQuickInfo = true; this.EnableQuickInfo = true;
} }
/// <summary>
/// Gets or sets a flag determining if IntelliSense is enabled
/// </summary>
/// <returns></returns>
public bool EnableIntellisense { get; set; }
/// <summary> /// <summary>
/// Gets or sets a flag determining if suggestions are enabled /// Gets or sets a flag determining if suggestions are enabled
/// </summary> /// </summary>
@@ -35,7 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// <summary> /// <summary>
/// Gets or sets a flag determining if diagnostics are enabled /// Gets or sets a flag determining if diagnostics are enabled
/// </summary> /// </summary>
public bool? EnableDiagnostics { get; set; } public bool? EnableErrorChecking { get; set; }
/// <summary> /// <summary>
/// Gets or sets a flag determining if quick info is enabled /// 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.EnableSuggestions = settings.EnableSuggestions;
this.LowerCaseSuggestions = settings.LowerCaseSuggestions; this.LowerCaseSuggestions = settings.LowerCaseSuggestions;
this.EnableDiagnostics = settings.EnableDiagnostics; this.EnableErrorChecking = settings.EnableErrorChecking;
this.EnableQuickInfo = settings.EnableQuickInfo; this.EnableQuickInfo = settings.EnableQuickInfo;
} }
} }

View File

@@ -3,6 +3,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using Newtonsoft.Json;
namespace Microsoft.SqlTools.ServiceLayer.SqlContext namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{ {
/// <summary> /// <summary>
@@ -15,6 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// <summary> /// <summary>
/// Gets or sets the underlying settings value object /// Gets or sets the underlying settings value object
/// </summary> /// </summary>
[JsonProperty("mssql")]
public SqlToolsSettingsValues SqlTools public SqlToolsSettingsValues SqlTools
{ {
get get
@@ -47,7 +50,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{ {
if (settings != null) if (settings != null)
{ {
this.SqlTools.EnableIntellisense = settings.SqlTools.EnableIntellisense; this.SqlTools.IntelliSense.EnableIntellisense = settings.SqlTools.IntelliSense.EnableIntellisense;
this.SqlTools.IntelliSense.Update(settings.SqlTools.IntelliSense); this.SqlTools.IntelliSense.Update(settings.SqlTools.IntelliSense);
} }
} }
@@ -59,8 +62,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{ {
get get
{ {
return this.SqlTools.EnableIntellisense return this.SqlTools.IntelliSense.EnableIntellisense
&& this.SqlTools.IntelliSense.EnableDiagnostics.Value; && this.SqlTools.IntelliSense.EnableErrorChecking.Value;
} }
} }
@@ -71,7 +74,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{ {
get get
{ {
return this.SqlTools.EnableIntellisense return this.SqlTools.IntelliSense.EnableIntellisense
&& this.SqlTools.IntelliSense.EnableSuggestions.Value; && this.SqlTools.IntelliSense.EnableSuggestions.Value;
} }
} }
@@ -83,7 +86,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{ {
get get
{ {
return this.SqlTools.EnableIntellisense return this.SqlTools.IntelliSense.EnableIntellisense
&& this.SqlTools.IntelliSense.EnableQuickInfo.Value; && this.SqlTools.IntelliSense.EnableQuickInfo.Value;
} }
} }
@@ -99,17 +102,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// </summary> /// </summary>
public SqlToolsSettingsValues() public SqlToolsSettingsValues()
{ {
this.EnableIntellisense = true;
this.IntelliSense = new IntelliSenseSettings(); this.IntelliSense = new IntelliSenseSettings();
this.QueryExecutionSettings = new QueryExecutionSettings(); this.QueryExecutionSettings = new QueryExecutionSettings();
} }
/// <summary>
/// Gets or sets a flag determining if IntelliSense is enabled
/// </summary>
/// <returns></returns>
public bool EnableIntellisense { get; set; }
/// <summary> /// <summary>
/// Gets or sets the detailed IntelliSense settings /// Gets or sets the detailed IntelliSense settings
/// </summary> /// </summary>

View File

@@ -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;
}
}
}
}

View File

@@ -6,7 +6,34 @@
namespace Microsoft.SqlTools.ServiceLayer.Utility namespace Microsoft.SqlTools.ServiceLayer.Utility
{ {
public static class TextUtilities 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> /// <summary>
/// Find the position of the previous delimeter for autocomplete token replacement. /// 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. /// 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="sql"></param>
/// <param name="startRow"></param> /// <param name="startRow"></param>
/// <param name="startColumn"></param> /// <param name="startColumn"></param>
/// <returns></returns> /// <param name="tokenText"></param>
public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn) public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
{ {
if (string.IsNullOrWhiteSpace(sql)) int prevNewLine;
{ int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
return 1;
}
int prevLineColumns = 0; if (delimeterPos - 1 < sql.Length)
for (int i = 0; i < startRow; ++i)
{ {
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length) while (--delimeterPos >= prevNewLine)
{ {
++prevLineColumns; if (IsCharacterDelimeter(sql[delimeterPos]))
}
++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] == '%')
{ {
break; 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 == ')';
} }
} }
} }

View File

@@ -8,8 +8,8 @@
"dependencies": { "dependencies": {
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"System.Data.Common": "4.1.0", "System.Data.Common": "4.1.0",
"System.Data.SqlClient": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04",
"Microsoft.SqlServer.Smo": "140.1.8", "Microsoft.SqlServer.Smo": "140.1.11",
"System.Security.SecureString": "4.0.0", "System.Security.SecureString": "4.0.0",
"System.Collections.Specialized": "4.0.1", "System.Collections.Specialized": "4.0.1",
"System.ComponentModel.TypeConverter": "4.1.0", "System.ComponentModel.TypeConverter": "4.1.0",

View File

@@ -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 &apos;{0}&apos; for AuthenticationType. Valid values are &apos;Integrated&apos; and &apos;SqlLogin&apos;..
/// </summary>
public static string ConnectionServiceConnStringInvalidAuthType {
get {
return ResourceManager.GetString("ConnectionServiceConnStringInvalidAuthType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Invalid value &apos;{0}&apos; for ApplicationIntent. Valid values are &apos;ReadWrite&apos; and &apos;ReadOnly&apos;..
/// </summary>
public static string ConnectionServiceConnStringInvalidIntent {
get {
return ResourceManager.GetString("ConnectionServiceConnStringInvalidIntent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SpecifiedUri &apos;{0}&apos; 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 &apos;:&apos;.
/// </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&apos;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);
}
}
}
}

View File

@@ -165,6 +165,14 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string QueryServiceQueryCancelled
{
get
{
return Keys.GetString(Keys.QueryServiceQueryCancelled);
}
}
public static string QueryServiceSubsetNotCompleted public static string QueryServiceSubsetNotCompleted
{ {
get get
@@ -237,6 +245,14 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string QueryServiceAffectedOneRow
{
get
{
return Keys.GetString(Keys.QueryServiceAffectedOneRow);
}
}
public static string QueryServiceCompletedSuccessfully public static string QueryServiceCompletedSuccessfully
{ {
get get
@@ -363,6 +379,11 @@ namespace Microsoft.SqlTools.ServiceLayer
return Keys.GetString(Keys.QueryServiceErrorFormat, msg, lvl, state, line, newLine, message); 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) public static string WorkspaceServicePositionColumnOutOfRange(int line)
{ {
return Keys.GetString(Keys.WorkspaceServicePositionColumnOutOfRange, line); return Keys.GetString(Keys.WorkspaceServicePositionColumnOutOfRange, line);
@@ -444,6 +465,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string QueryServiceCancelDisposeFailed = "QueryServiceCancelDisposeFailed"; public const string QueryServiceCancelDisposeFailed = "QueryServiceCancelDisposeFailed";
public const string QueryServiceQueryCancelled = "QueryServiceQueryCancelled";
public const string QueryServiceSubsetNotCompleted = "QueryServiceSubsetNotCompleted"; public const string QueryServiceSubsetNotCompleted = "QueryServiceSubsetNotCompleted";
@@ -471,6 +495,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string QueryServiceFileWrapperReadOnly = "QueryServiceFileWrapperReadOnly"; public const string QueryServiceFileWrapperReadOnly = "QueryServiceFileWrapperReadOnly";
public const string QueryServiceAffectedOneRow = "QueryServiceAffectedOneRow";
public const string QueryServiceAffectedRows = "QueryServiceAffectedRows"; public const string QueryServiceAffectedRows = "QueryServiceAffectedRows";
@@ -480,6 +507,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string QueryServiceErrorFormat = "QueryServiceErrorFormat"; public const string QueryServiceErrorFormat = "QueryServiceErrorFormat";
public const string QueryServiceQueryFailed = "QueryServiceQueryFailed";
public const string QueryServiceColumnNull = "QueryServiceColumnNull"; public const string QueryServiceColumnNull = "QueryServiceColumnNull";

View File

@@ -205,6 +205,10 @@
<value>Query successfully cancelled, failed to dispose query. Owner URI not found.</value> <value>Query successfully cancelled, failed to dispose query. Owner URI not found.</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="QueryServiceQueryCancelled" xml:space="preserve">
<value>Query was canceled by user</value>
<comment></comment>
</data>
<data name="QueryServiceSubsetNotCompleted" xml:space="preserve"> <data name="QueryServiceSubsetNotCompleted" xml:space="preserve">
<value>The query has not completed, yet</value> <value>The query has not completed, yet</value>
<comment></comment> <comment></comment>
@@ -241,19 +245,28 @@
<value>This FileStreamWrapper cannot be used for writing</value> <value>This FileStreamWrapper cannot be used for writing</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="QueryServiceAffectedOneRow" xml:space="preserve">
<value>(1 row affected)</value>
<comment></comment>
</data>
<data name="QueryServiceAffectedRows" xml:space="preserve"> <data name="QueryServiceAffectedRows" xml:space="preserve">
<value>({0} row(s) affected)</value> <value>({0} rows affected)</value>
<comment>. <comment>.
Parameters: 0 - rows (long) </comment> Parameters: 0 - rows (long) </comment>
</data> </data>
<data name="QueryServiceCompletedSuccessfully" xml:space="preserve"> <data name="QueryServiceCompletedSuccessfully" xml:space="preserve">
<value>Command(s) copleted successfully.</value> <value>Commands completed successfully.</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="QueryServiceErrorFormat" xml:space="preserve"> <data name="QueryServiceErrorFormat" xml:space="preserve">
<value>Msg {0}, Level {1}, State {2}, Line {3}{4}{5}</value> <value>Msg {0}, Level {1}, State {2}, Line {3}{4}{5}</value>
<comment>. <comment>.
Parameters: 0 - msg (int), 1 - lvl (int), 2 - state (int), 3 - line (int), 4 - newLine (string), 5 - message (string) </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>
<data name="QueryServiceColumnNull" xml:space="preserve"> <data name="QueryServiceColumnNull" xml:space="preserve">
<value>(No column name)</value> <value>(No column name)</value>

View File

@@ -79,6 +79,8 @@ QueryServiceCancelAlreadyCompleted = The query has already completed, it cannot
QueryServiceCancelDisposeFailed = Query successfully cancelled, failed to dispose query. Owner URI not found. QueryServiceCancelDisposeFailed = Query successfully cancelled, failed to dispose query. Owner URI not found.
QueryServiceQueryCancelled = Query was canceled by user
### Subset Request ### Subset Request
QueryServiceSubsetNotCompleted = The query has not completed, yet QueryServiceSubsetNotCompleted = The query has not completed, yet
@@ -105,12 +107,16 @@ QueryServiceFileWrapperReadOnly = This FileStreamWrapper cannot be used for writ
### Query Request ### 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} 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) QueryServiceColumnNull = (No column name)
QueryServiceRequestsNoQuery = The requested query does not exist QueryServiceRequestsNoQuery = The requested query does not exist

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.Common; using System.Data.Common;
using System.Data.SqlClient;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -35,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
var commandMockSetup = commandMock.Protected() var commandMockSetup = commandMock.Protected()
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>()); .Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
commandMockSetup.Returns(new TestDbDataReader(data)); commandMockSetup.Returns(() => new TestDbDataReader(data));
return commandMock.Object; return commandMock.Object;
} }
@@ -830,5 +831,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
ConnectionInfo info; ConnectionInfo info;
Assert.True(service.TryFindConnection(connectParams.OwnerUri, out 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);
}
});
}
} }
} }

View File

@@ -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

View File

@@ -4,7 +4,6 @@
// //
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider; using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.Binder;
@@ -25,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
{ {
public TestBindingContext() public TestBindingContext()
{ {
this.BindingLocked = new ManualResetEvent(initialState: true); this.BindingLock = new ManualResetEvent(true);
this.BindingTimeout = 3000; this.BindingTimeout = 3000;
} }
@@ -39,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
public IBinder Binder { get; set; } public IBinder Binder { get; set; }
public ManualResetEvent BindingLocked { get; set; } public ManualResetEvent BindingLock { get; set; }
public int BindingTimeout { get; set; } public int BindingTimeout { get; set; }

View File

@@ -24,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// Set up file for returning the query // Set up file for returning the query
var fileMock = new Mock<ScriptFile>(); var fileMock = new Mock<ScriptFile>();
fileMock.Setup(file => file.GetLinesInRange(It.IsAny<BufferRange>())) fileMock.Setup(file => file.GetLinesInRange(It.IsAny<BufferRange>()))
.Returns(new string[] { Common.StandardQuery }); .Returns(new[] { Common.StandardQuery });
// Set up workspace mock // Set up workspace mock
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>(); var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>())) 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 executeParams = new QueryExecuteParams { QuerySelection = Common.GetSubSectionDocument(), OwnerUri = Common.OwnerUri };
var executeRequest = var executeRequest =
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null); 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 queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
// ... And then I request to cancel the query // ... And then I request to cancel the query
@@ -50,8 +51,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
VerifyQueryCancelCallCount(cancelRequest, Times.Once(), Times.Never()); VerifyQueryCancelCallCount(cancelRequest, Times.Once(), Times.Never());
Assert.Null(result.Messages); Assert.Null(result.Messages);
// ... The query should have been disposed as well // ... The query should not have been disposed
Assert.Empty(queryService.ActiveQueries); Assert.Equal(1, queryService.ActiveQueries.Count);
} }
[Fact] [Fact]
@@ -71,13 +72,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri}; var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri};
var executeRequest = var executeRequest =
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null); 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 // ... And then I request to cancel the query
var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri}; var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri};
QueryCancelResult result = null; QueryCancelResult result = null;
var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null); var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null);
queryService.HandleCancelRequest(cancelParams, cancelRequest.Object).Wait(); await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
// Then: // Then:
// ... I should have seen a result event with an error message // ... I should have seen a result event with an error message

View File

@@ -14,9 +14,6 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlServer.Management.Common; 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.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
@@ -95,7 +92,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
{ {
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false); ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory()); Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory());
query.Execute().Wait(); query.Execute();
query.ExecutionTask.Wait();
return query; return query;
} }
@@ -287,6 +285,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
return new QueryExecutionService(connectionService, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory()}; 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 #endregion
} }

View File

@@ -4,7 +4,6 @@
// //
using System; using System;
using System.Data.Common;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution; 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 queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri}; var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null); 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 // ... And then I dispose of the query
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri}; 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 queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null); var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(queryParams, requestContext.Object); await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// ... And it sticks around as an active query // ... And it sticks around as an active query
Assert.Equal(1, queryService.ActiveQueries.Count); Assert.Equal(1, queryService.ActiveQueries.Count);

View File

@@ -19,7 +19,6 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.Test.Utility;
using Moq; using Moq;
using Xunit; using Xunit;
@@ -74,9 +73,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... There should be a message for how many rows were affected // ... There should be a message for how many rows were affected
Assert.Equal(1, batch.ResultMessages.Count()); 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] [Fact]
@@ -108,7 +104,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... There should be a message for how many rows were affected // ... There should be a message for how many rows were affected
Assert.Equal(resultSets, batch.ResultMessages.Count()); Assert.Equal(resultSets, batch.ResultMessages.Count());
Assert.Contains(Common.StandardRows.ToString(), batch.ResultMessages.First().Message);
} }
[Fact] [Fact]
@@ -150,13 +145,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... Inside each result summary, there should be 5 column definitions // ... Inside each result summary, there should be 5 column definitions
Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length); 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] [Fact]
@@ -295,7 +283,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If: // If:
// ... I then execute the query // ... I then execute the query
query.Execute().Wait(); query.Execute();
query.ExecutionTask.Wait();
// Then: // Then:
// ... The query should have completed successfully with one batch summary returned // ... The query should have completed successfully with one batch summary returned
@@ -321,7 +310,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If: // If:
// ... I Then execute the query // ... I Then execute the query
query.Execute().Wait(); query.Execute();
query.ExecutionTask.Wait();
// Then: // Then:
// ... The query should have completed successfully with no batch summaries returned // ... The query should have completed successfully with no batch summaries returned
@@ -348,7 +338,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If: // If:
// ... I then execute the query // ... I then execute the query
query.Execute().Wait(); query.Execute();
query.ExecutionTask.Wait();
// Then: // Then:
// ... The query should have completed successfully with two batch summaries returned // ... The query should have completed successfully with two batch summaries returned
@@ -376,7 +367,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If: // If:
// .. I then execute the query // .. I then execute the query
query.Execute().Wait(); query.Execute();
query.ExecutionTask.Wait();
// ... The query should have completed successfully with one batch summary returned // ... The query should have completed successfully with one batch summary returned
Assert.True(query.HasExecuted); Assert.True(query.HasExecuted);
@@ -402,7 +394,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If: // If:
// ... I then execute the query // ... I then execute the query
query.Execute().Wait(); query.Execute();
query.ExecutionTask.Wait();
// Then: // Then:
// ... There should be an error on the batch // ... There should be an error on the batch
@@ -444,7 +437,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
expectedEvent: QueryExecuteCompleteEvent.Type, expectedEvent: QueryExecuteCompleteEvent.Type,
eventCallback: (et, cp) => completeParams = cp, eventCallback: (et, cp) => completeParams = cp,
errorCallback: null); errorCallback: null);
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); await AwaitExecution(queryService, queryParams, requestContext.Object);
// Then: // Then:
// ... No Errors should have been sent // ... No Errors should have been sent
@@ -485,7 +478,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
expectedEvent: QueryExecuteCompleteEvent.Type, expectedEvent: QueryExecuteCompleteEvent.Type,
eventCallback: (et, cp) => completeParams = cp, eventCallback: (et, cp) => completeParams = cp,
errorCallback: null); errorCallback: null);
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); await AwaitExecution(queryService, queryParams, requestContext.Object);
// Then: // Then:
// ... No errors should have been sent // ... 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 queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument }; var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
QueryExecuteResult result = null; object error = null;
var requestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null); var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); .AddErrorHandling(e => error = e);
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
// Then: // 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 completion event should have been fired
// ... No error event should have been fired
// ... There should be no active queries // ... There should be no active queries
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Never(), Times.Never()); VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once());
Assert.NotNull(result.Messages); Assert.IsType<string>(error);
Assert.NotEmpty(result.Messages); Assert.NotEmpty((string)error);
Assert.Empty(queryService.ActiveQueries); Assert.Empty(queryService.ActiveQueries);
} }
@@ -545,24 +539,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
// Note, we don't care about the results of the first request // Note, we don't care about the results of the first request
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null); var firstRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait(); await AwaitExecution(queryService, queryParams, firstRequestContext.Object);
// ... And then I request another query without waiting for the first to complete // ... And then I request another query without waiting for the first to complete
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
QueryExecuteResult result = null; object error = null;
var secondRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null); var secondRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait(); .AddErrorHandling(e => error = e);
await AwaitExecution(queryService, queryParams, secondRequestContext.Object);
// Then: // Then:
// ... No errors should have been sent // ... An error should have been sent
// ... A result should have been sent with an error message // ... A result should have not have been sent
// ... No completion event should have been fired // ... No completion event should have been fired
// ... There should only be one active query // ... The original query should exist
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.AtMostOnce(), Times.Never()); VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.Never(), Times.Once());
Assert.NotNull(result.Messages); Assert.IsType<string>(error);
Assert.NotEmpty(result.Messages); Assert.NotEmpty((string)error);
Assert.Equal(1, queryService.ActiveQueries.Count); Assert.Contains(Common.OwnerUri, queryService.ActiveQueries.Keys);
} }
[Fact] [Fact]
@@ -584,15 +579,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// Note, we don't care about the results of the first request // Note, we don't care about the results of the first request
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null); var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
await AwaitExecution(queryService, queryParams, firstRequestContext.Object);
queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait();
// ... And then I request another query after waiting for the first to complete // ... And then I request another query after waiting for the first to complete
QueryExecuteResult result = null; QueryExecuteResult result = null;
QueryExecuteCompleteParams complete = null; QueryExecuteCompleteParams complete = null;
var secondRequestContext = var secondRequestContext =
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null); 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: // Then:
// ... No errors should have been sent // ... No errors should have been sent
@@ -606,7 +600,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Theory] [Theory]
[InlineData(null)] [InlineData(null)]
public async void QueryExecuteMissingSelectionTest(SelectionData selection) public async Task QueryExecuteMissingSelectionTest(SelectionData selection)
{ {
// Set up file for returning the query // 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 queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = selection }; var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = selection };
QueryExecuteResult result = null; object errorResult = null;
var requestContext = var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, null, null); .AddErrorHandling(error => errorResult = error);
queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
// Then: // Then:
// ... No errors should have been sent // ... Am error should have been sent
// ... A result should have been sent with an error message // ... No result should have been sent
// ... No completion event should have been fired // ... No completion event should have been fired
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Never(), Times.Never()); // ... An active query should not have been added
Assert.NotNull(result.Messages); VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once());
Assert.NotEmpty(result.Messages); Assert.NotNull(errorResult);
Assert.IsType<string>(errorResult);
Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys);
// ... There should not be an active query // ... There should not be an active query
Assert.Empty(queryService.ActiveQueries); Assert.Empty(queryService.ActiveQueries);
@@ -657,7 +653,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
QueryExecuteCompleteParams complete = null; QueryExecuteCompleteParams complete = null;
var requestContext = var requestContext =
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null); 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: // Then:
// ... No errors should have been sent // ... No errors should have been sent
@@ -700,7 +696,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
#endregion #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.SendResult(It.IsAny<QueryExecuteResult>()), sendResultCalls);
mock.Verify(rc => rc.SendEvent( mock.Verify(rc => rc.SendEvent(
@@ -709,9 +705,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls); 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)); 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;
}
} }
} }

View File

@@ -3,15 +3,16 @@
// //
using System; using System;
using System.Linq;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; 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.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Moq; using Moq;
using Xunit; using Xunit;
@@ -28,19 +29,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsCsvSuccessTest() 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 // 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 executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = GetQueryExecuteResultContextMock(null, null, null); var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as csv with correct parameters // Request to save the results as csv with correct parameters
var saveParams = new SaveResultsAsCsvRequestParams var saveParams = new SaveResultsAsCsvRequestParams
@@ -54,7 +48,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
SaveResultRequestResult result = null; SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); 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 // Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages); Assert.Null(result.Messages);
@@ -74,20 +73,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsCsvWithSelectionSuccessTest() 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 // 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 executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
var executeRequest = GetQueryExecuteResultContextMock(null, null, null); var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as csv with correct parameters // Request to save the results as csv with correct parameters
var saveParams = new SaveResultsAsCsvRequestParams var saveParams = new SaveResultsAsCsvRequestParams
@@ -105,7 +96,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
SaveResultRequestResult result = null; SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); 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 // Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages); Assert.Null(result.Messages);
@@ -124,21 +120,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
/// </summary> /// </summary>
[Fact] [Fact]
public async void SaveResultsAsCsvExceptionTest() 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 // 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 executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = GetQueryExecuteResultContextMock(null, null, null); var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as csv with incorrect filepath // Request to save the results as csv with incorrect filepath
var saveParams = new SaveResultsAsCsvRequestParams var saveParams = new SaveResultsAsCsvRequestParams
@@ -148,11 +136,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
BatchIndex = 0, BatchIndex = 0,
FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.csv" : "/test.csv" FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.csv" : "/test.csv"
}; };
// SaveResultRequestResult result = null;
string errMessage = null; SaveResultRequestError errMessage = null;
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err); var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); 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 // Expect to see error message
Assert.NotNull(errMessage); Assert.NotNull(errMessage);
@@ -166,13 +159,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsCsvQueryNotFoundTest() public async void SaveResultsAsCsvQueryNotFoundTest()
{ {
// Create a query execution service
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>(); var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
// 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, 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 // Request to save the results as csv with query that is no longer active
var saveParams = new SaveResultsAsCsvRequestParams var saveParams = new SaveResultsAsCsvRequestParams
@@ -198,19 +187,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsJsonSuccessTest() 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 // 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 executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = GetQueryExecuteResultContextMock(null, null, null); var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as json with correct parameters // Request to save the results as json with correct parameters
var saveParams = new SaveResultsAsJsonRequestParams var saveParams = new SaveResultsAsJsonRequestParams
@@ -223,7 +205,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
SaveResultRequestResult result = null; SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); 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 // Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages); Assert.Null(result.Messages);
@@ -243,19 +232,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsJsonWithSelectionSuccessTest() 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 // 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 executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
var executeRequest = GetQueryExecuteResultContextMock(null, null, null); var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as json with correct parameters // Request to save the results as json with correct parameters
var saveParams = new SaveResultsAsJsonRequestParams var saveParams = new SaveResultsAsJsonRequestParams
@@ -265,14 +247,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
BatchIndex = 0, BatchIndex = 0,
FilePath = "testwrite_5.json", FilePath = "testwrite_5.json",
RowStartIndex = 0, RowStartIndex = 0,
RowEndIndex = 0, RowEndIndex = 1,
ColumnStartIndex = 0, ColumnStartIndex = 0,
ColumnEndIndex = 0 ColumnEndIndex = 1
}; };
SaveResultRequestResult result = null; SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); 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 // Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages); Assert.Null(result.Messages);
@@ -292,18 +279,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsJsonExceptionTest() 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 // 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 executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = GetQueryExecuteResultContextMock(null, null, null); var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as json with incorrect filepath // Request to save the results as json with incorrect filepath
var saveParams = new SaveResultsAsJsonRequestParams var saveParams = new SaveResultsAsJsonRequestParams
@@ -313,11 +294,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
BatchIndex = 0, BatchIndex = 0,
FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.json" : "/test.json" 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.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 // Expect to see error message
Assert.NotNull(errMessage); Assert.NotNull(errMessage);
@@ -331,12 +318,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SaveResultsAsJsonQueryNotFoundTest() public async void SaveResultsAsJsonQueryNotFoundTest()
{ {
// Create a query service
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>(); var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
// 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, 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 // Request to save the results as json with query that is no longer active
var saveParams = new SaveResultsAsJsonRequestParams var saveParams = new SaveResultsAsJsonRequestParams
@@ -404,52 +389,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls); 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 #endregion
} }

View File

@@ -146,8 +146,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true, Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true,
workspaceService.Object); workspaceService.Object);
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri}; 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.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// ... And I then ask for a valid set of results from it // ... 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}; 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, Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true,
workspaceService.Object); workspaceService.Object);
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri }; 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);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false;
// ... And I then ask for a valid set of results from it // ... And I then ask for a valid set of results from it
@@ -224,17 +226,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact] [Fact]
public async void SubsetServiceOutOfRangeSubsetTest() public async void SubsetServiceOutOfRangeSubsetTest()
{ {
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
// If: // If:
// ... I have a query that doesn't have any result sets // ... I have a query that doesn't have any result sets
var queryService = await Common.GetPrimedExecutionService( var queryService = await Common.GetPrimedExecutionService(
Common.CreateMockFactory(null, false), true, Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
workspaceService.Object);
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri }; 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);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait(); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// ... And I then ask for a set of results from it // ... And I then ask for a set of results from it
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; 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<QueryExecuteSubsetResult> resultCallback,
Action<object> errorCallback) Action<object> errorCallback)
{ {
var requestContext = new Mock<RequestContext<QueryExecuteSubsetResult>>(); return RequestContextMocks.Create(resultCallback)
.AddErrorHandling(errorCallback);
// 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;
} }
private static void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls, private static void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls,

View File

@@ -22,8 +22,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
var sqlToolsSettings = new SqlToolsSettings(); var sqlToolsSettings = new SqlToolsSettings();
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled); Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
Assert.True(sqlToolsSettings.IsSuggestionsEnabled); Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
Assert.True(sqlToolsSettings.SqlTools.EnableIntellisense); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense);
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking);
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions);
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo);
Assert.False(sqlToolsSettings.SqlTools.IntelliSense.LowerCaseSuggestions); Assert.False(sqlToolsSettings.SqlTools.IntelliSense.LowerCaseSuggestions);
@@ -38,17 +38,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
var sqlToolsSettings = new SqlToolsSettings(); var sqlToolsSettings = new SqlToolsSettings();
// diagnostics is enabled if IntelliSense and Diagnostics flags are set // diagnostics is enabled if IntelliSense and Diagnostics flags are set
sqlToolsSettings.SqlTools.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = true; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled); Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
// diagnostics is disabled if either IntelliSense and Diagnostics flags is not set // diagnostics is disabled if either IntelliSense and Diagnostics flags is not set
sqlToolsSettings.SqlTools.EnableIntellisense = false; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = true; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled); Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
sqlToolsSettings.SqlTools.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableDiagnostics = false; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = false;
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled); Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
} }
@@ -61,16 +61,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
var sqlToolsSettings = new SqlToolsSettings(); var sqlToolsSettings = new SqlToolsSettings();
// suggestions is enabled if IntelliSense and Suggestions flags are set // suggestions is enabled if IntelliSense and Suggestions flags are set
sqlToolsSettings.SqlTools.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true; sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
Assert.True(sqlToolsSettings.IsSuggestionsEnabled); Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
// suggestions is disabled if either IntelliSense and Suggestions flags is not set // 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; sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
Assert.False(sqlToolsSettings.IsSuggestionsEnabled); Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
sqlToolsSettings.SqlTools.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = false; sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = false;
Assert.False(sqlToolsSettings.IsSuggestionsEnabled); Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
} }
@@ -84,16 +84,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
var sqlToolsSettings = new SqlToolsSettings(); var sqlToolsSettings = new SqlToolsSettings();
// quick info is enabled if IntelliSense and quick info flags are set // 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; sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
Assert.True(sqlToolsSettings.IsQuickInfoEnabled); Assert.True(sqlToolsSettings.IsQuickInfoEnabled);
// quick info is disabled if either IntelliSense and quick info flags is not set // 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; sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
Assert.False(sqlToolsSettings.IsQuickInfoEnabled); Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
sqlToolsSettings.SqlTools.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = false; sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = false;
Assert.False(sqlToolsSettings.IsQuickInfoEnabled); Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
} }

View File

@@ -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);
}
}
}

View File

@@ -14,6 +14,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
test(); test();
} }
} }
public static void RunIfLinuxOrOSX(Action test)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
test();
}
}
public static void RunIfWindows(Action test) public static void RunIfWindows(Action test)
{ {

View File

@@ -4,12 +4,21 @@
"buildOptions": { "buildOptions": {
"debugType": "portable" "debugType": "portable"
}, },
"configurations": {
"Integration": {
"buildOptions": {
"define": [
"LIVE_CONNECTION_TESTS"
]
}
}
},
"dependencies": { "dependencies": {
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"System.Runtime.Serialization.Primitives": "4.1.1", "System.Runtime.Serialization.Primitives": "4.1.1",
"System.Data.Common": "4.1.0", "System.Data.Common": "4.1.0",
"System.Data.SqlClient": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04",
"Microsoft.SqlServer.Smo": "140.1.8", "Microsoft.SqlServer.Smo": "140.1.11",
"System.Security.SecureString": "4.0.0", "System.Security.SecureString": "4.0.0",
"System.Collections.Specialized": "4.0.1", "System.Collections.Specialized": "4.0.1",
"System.ComponentModel.TypeConverter": "4.1.0", "System.ComponentModel.TypeConverter": "4.1.0",

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View 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();
}
}
}

View File

@@ -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");
}
}
}
}
}

View 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": {}
}
}