diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index 7b54484e..38dbca5d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -57,16 +57,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution #endregion - internal Batch(string batchText, int startLine, int startColumn, int endLine, int endColumn, IFileStreamFactory outputFileFactory) + internal Batch(string batchText, SelectionData selection, int ordinalId, IFileStreamFactory outputFileFactory) { // Sanity check for input Validate.IsNotNullOrEmptyString(nameof(batchText), batchText); Validate.IsNotNull(nameof(outputFileFactory), outputFileFactory); + Validate.IsGreaterThan(nameof(ordinalId), ordinalId, 0); // Initialize the internal state BatchText = batchText; - Selection = new SelectionData(startLine, startColumn, endLine, endColumn); + Selection = selection; + executionStartTime = DateTime.Now; HasExecuted = false; + Id = ordinalId; resultSets = new List(); resultMessages = new List(); this.outputFileFactory = outputFileFactory; @@ -74,6 +77,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution #region Properties + /// + /// Asynchronous handler for when batches are completed + /// + /// The batch that completed + public delegate Task BatchAsyncEventHandler(Batch batch); + + /// + /// Event that will be called when the batch has completed execution + /// + public event BatchAsyncEventHandler BatchCompletion; + /// /// The text of batch that will be executed /// @@ -113,6 +127,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public bool HasExecuted { get; set; } + /// + /// Ordinal of the batch in the query + /// + public int Id { get; private set; } + /// /// Messages that have come back from the server /// @@ -145,6 +164,27 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution } } + /// + /// Creates a based on the batch instance + /// + public BatchSummary Summary + { + get + { + return new BatchSummary + { + HasError = HasError, + Id = Id, + ResultSetSummaries = ResultSummaries, + Messages = ResultMessages.ToArray(), + Selection = Selection, + ExecutionElapsed = ExecutionElapsedTime, + ExecutionStart = ExecutionStartTimeStamp, + ExecutionEnd = ExecutionEndTimeStamp + }; + } + } + /// /// The range from the file that is this batch /// @@ -169,8 +209,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution try { - DbCommand command = null; + // Register the message listener to *this instance* of the batch + // Note: This is being done to associate messages with batches ReliableSqlConnection sqlConn = conn as ReliableSqlConnection; + DbCommand command; if (sqlConn != null) { // Register the message listener to *this instance* of the batch @@ -258,6 +300,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution // Mark that we have executed HasExecuted = true; executionEndTime = DateTime.Now; + + // Fire an event to signify that the batch has completed + if (BatchCompletion != null) + { + await BatchCompletion(this); + } } } @@ -270,6 +318,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// A subset of results public Task GetSubset(int resultSetIndex, int startRow, int rowCount) { + // Sanity check to make sure that the batch has finished + if (!HasExecuted) + { + throw new InvalidOperationException(SR.QueryServiceSubsetBatchNotCompleted); + } + // Sanity check to make sure we have valid numbers if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchCompleteNotification.cs new file mode 100644 index 00000000..6a1a6a6c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchCompleteNotification.cs @@ -0,0 +1,32 @@ +// +// 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.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts +{ + /// + /// Parameters to be sent back as part of a QueryExecuteBatchCompleteEvent to indicate that a + /// batch of a query completed. + /// + public class QueryExecuteBatchCompleteParams + { + /// + /// Summary of the batch that just completed + /// + public BatchSummary BatchSummary { get; set; } + + /// + /// URI for the editor that owns the query + /// + public string OwnerUri { get; set; } + } + + public class QueryExecuteBatchCompleteEvent + { + public static readonly + EventType Type = + EventType.Create("query/batchComplete"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteRequest.cs index 6079bf51..4d9bf3ba 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteRequest.cs @@ -7,21 +7,6 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts { - /// - /// Container class for a selection range from file - /// - public class SelectionData { - public int StartLine { get; set; } - public int StartColumn { get; set; } - public int EndLine { get; set; } - public int EndColumn { get; set; } - public SelectionData(int startLine, int startColumn, int endLine, int endColumn) { - StartLine = startLine; - StartColumn = startColumn; - EndLine = endLine; - EndColumn = endColumn; - } - } /// /// Parameters for the query execute request /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SelectionData.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SelectionData.cs new file mode 100644 index 00000000..183f2b3c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SelectionData.cs @@ -0,0 +1,52 @@ +// +// 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.Workspace.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts +{ + /// + /// Container class for a selection range from file + /// + /// TODO: Remove this in favor of buffer range end-to-end + public class SelectionData + { + public SelectionData() { } + + public SelectionData(int startLine, int startColumn, int endLine, int endColumn) + { + StartLine = startLine; + StartColumn = startColumn; + EndLine = endLine; + EndColumn = endColumn; + } + + #region Properties + + public int EndColumn { get; set; } + + public int EndLine { get; set; } + + public int StartColumn { get; set; } + public int StartLine { get; set; } + + #endregion + + public BufferRange ToBufferRange() + { + return new BufferRange(StartLine, StartColumn, EndLine, EndColumn); + } + + public static SelectionData FromBufferRange(BufferRange range) + { + return new SelectionData + { + StartLine = range.Start.Line, + StartColumn = range.Start.Column, + EndLine = range.End.Line, + EndColumn = range.End.Column + }; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs index d47fafca..fb915be7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs @@ -51,11 +51,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// private bool hasExecuteBeenCalled; - /// - /// The factory to use for outputting the results of this query - /// - private readonly IFileStreamFactory outputFileFactory; - #endregion /// @@ -77,7 +72,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution QueryText = queryText; editorConnection = connection; cancellationSource = new CancellationTokenSource(); - outputFileFactory = outputFactory; // Process the query into batches ParseResult parseResult = Parser.Parse(queryText, new ParseOptions @@ -85,13 +79,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution BatchSeparator = settings.BatchSeparator }); // NOTE: We only want to process batches that have statements (ie, ignore comments and empty lines) - Batches = parseResult.Script.Batches.Where(b => b.Statements.Count > 0) - .Select(b => new Batch(b.Sql, - b.StartLocation.LineNumber - 1, - b.StartLocation.ColumnNumber - 1, - b.EndLocation.LineNumber - 1, - b.EndLocation.ColumnNumber - 1, - outputFileFactory)).ToArray(); + var batchSelection = parseResult.Script.Batches + .Where(batch => batch.Statements.Count > 0) + .Select((batch, index) => + new Batch(batch.Sql, + new SelectionData( + batch.StartLocation.LineNumber - 1, + batch.StartLocation.ColumnNumber - 1, + batch.EndLocation.LineNumber - 1, + batch.EndLocation.ColumnNumber - 1), + index, outputFactory)); + Batches = batchSelection.ToArray(); } #region Properties @@ -102,10 +100,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// The query that completed public delegate Task QueryAsyncEventHandler(Query q); + /// + /// Event to be called when a batch is completed. + /// + public event Batch.BatchAsyncEventHandler BatchCompleted; + /// /// Delegate type for callback when a query connection fails /// - /// The query that completed + /// Message to return public delegate Task QueryAsyncErrorEventHandler(string message); /// @@ -139,18 +142,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { throw new InvalidOperationException("Query has not been executed."); } - - return Batches.Select((batch, index) => new BatchSummary - { - Id = index, - ExecutionStart = batch.ExecutionStartTimeStamp, - ExecutionEnd = batch.ExecutionEndTimeStamp, - ExecutionElapsed = batch.ExecutionElapsedTime, - HasError = batch.HasError, - Messages = batch.ResultMessages.ToArray(), - ResultSetSummaries = batch.ResultSummaries, - Selection = batch.Selection - }).ToArray(); + return Batches.Select(b => b.Summary).ToArray(); } } @@ -214,12 +206,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// A subset of results public Task 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) { @@ -278,6 +264,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution // We need these to execute synchronously, otherwise the user will be very unhappy foreach (Batch b in Batches) { + b.BatchCompletion += BatchCompleted; await b.Execute(conn, cancellationSource.Token); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs index c7614596..71c51336 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -442,6 +442,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution query.QueryFailed += callback; query.QueryConnectionException += errorCallback; + // Setup the batch completion callback + Batch.BatchAsyncEventHandler batchCallback = async b => + { + QueryExecuteBatchCompleteParams eventParams = new QueryExecuteBatchCompleteParams + { + BatchSummary = b.Summary, + OwnerUri = executeParams.OwnerUri + }; + await requestContext.SendEvent(QueryExecuteBatchCompleteEvent.Type, eventParams); + }; + query.BatchCompleted += batchCallback; + // Launch this as an asynchronous task query.Execute(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/SaveResults.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/SaveResults.cs index 713eb764..0490f6d4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/SaveResults.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/SaveResults.cs @@ -61,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// The field to encode /// The CSV encoded version of the original field - internal static String EncodeCsvField(String field) + internal static string EncodeCsvField(string field) { StringBuilder sbField = new StringBuilder(field); @@ -102,9 +102,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution //Replace all quotes in the original field with double quotes sbField.Replace("\"", "\"\""); - - String ret = sbField.ToString(); - + string ret = sbField.ToString(); + if (embedInQuotes) { ret = "\"" + ret + "\""; @@ -121,7 +120,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution internal static bool IsSaveSelection(SaveResultsRequestParams saveParams) { return (saveParams.ColumnStartIndex != null && saveParams.ColumnEndIndex != null - && saveParams.RowEndIndex != null && saveParams.RowEndIndex != null); + && saveParams.RowStartIndex != null && saveParams.RowEndIndex != null); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/sr.cs index ef1c9526..84405a5a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.cs @@ -173,11 +173,11 @@ namespace Microsoft.SqlTools.ServiceLayer } } - public static string QueryServiceSubsetNotCompleted + public static string QueryServiceSubsetBatchNotCompleted { get { - return Keys.GetString(Keys.QueryServiceSubsetNotCompleted); + return Keys.GetString(Keys.QueryServiceSubsetBatchNotCompleted); } } @@ -468,7 +468,7 @@ namespace Microsoft.SqlTools.ServiceLayer public const string QueryServiceQueryCancelled = "QueryServiceQueryCancelled"; - public const string QueryServiceSubsetNotCompleted = "QueryServiceSubsetNotCompleted"; + public const string QueryServiceSubsetBatchNotCompleted = "QueryServiceSubsetBatchNotCompleted"; public const string QueryServiceSubsetBatchOutOfRange = "QueryServiceSubsetBatchOutOfRange"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/sr.resx index 27f0993d..5494a742 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.resx @@ -209,8 +209,8 @@ Query was canceled by user - - The query has not completed, yet + + The batch has not completed, yet diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/sr.strings index 9b9a138e..67534aed 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.strings @@ -83,7 +83,7 @@ QueryServiceQueryCancelled = Query was canceled by user ### Subset Request -QueryServiceSubsetNotCompleted = The query has not completed, yet +QueryServiceSubsetBatchNotCompleted = The batch has not completed, yet QueryServiceSubsetBatchOutOfRange = Batch index cannot be less than 0 or greater than the number of batches diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs index 3899507c..29afec38 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs @@ -33,7 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // If: // ... I request a query (doesn't matter what kind) and execute it var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); - var executeParams = new QueryExecuteParams { QuerySelection = Common.GetSubSectionDocument(), OwnerUri = Common.OwnerUri }; + var executeParams = new QueryExecuteParams { QuerySelection = Common.SubsectionDocument, OwnerUri = Common.OwnerUri }; var executeRequest = RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs index bd5abe81..0747792f 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs @@ -1,19 +1,20 @@ -// +// // 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.Collections.Generic; using System.Data; using System.Data.Common; -using System.IO; using System.Data.SqlClient; +using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.SmoMetadataProvider; +using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlServer.Management.Common; using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; @@ -29,37 +30,61 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { public class Common { - public const SelectionData WholeDocument = null; - - public const string StandardQuery = "SELECT * FROM sys.objects"; + #region Constants public const string InvalidQuery = "SELECT *** FROM sys.objects"; public const string NoOpQuery = "-- No ops here, just us chickens."; - public const string UdtQuery = "SELECT hierarchyid::Parse('/')"; + public const int Ordinal = 0; public const string OwnerUri = "testFile"; - public const int StandardRows = 5; - public const int StandardColumns = 5; - public static string TestServer { get; set; } + public const string StandardQuery = "SELECT * FROM sys.objects"; - public static string TestDatabase { get; set; } + public const int StandardRows = 5; - static Common() + public const string UdtQuery = "SELECT hierarchyid::Parse('/')"; + + public const SelectionData WholeDocument = null; + + public static readonly ConnectionDetails StandardConnectionDetails = new ConnectionDetails { - TestServer = "sqltools11"; - TestDatabase = "master"; - } + DatabaseName = "123", + Password = "456", + ServerName = "789", + UserName = "012" + }; + + public static readonly SelectionData SubsectionDocument = new SelectionData(0, 0, 2, 2); + + #endregion public static Dictionary[] StandardTestData { get { return GetTestData(StandardRows, StandardColumns); } } + #region Public Methods + + public static Batch GetBasicExecutedBatch() + { + Batch batch = new Batch(StandardQuery, SubsectionDocument, 1, GetFileStreamFactory()); + batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait(); + return batch; + } + + public static Query GetBasicExecutedQuery() + { + ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false); + Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory()); + query.Execute(); + query.ExecutionTask.Wait(); + return query; + } + public static Dictionary[] GetTestData(int columns, int rows) { Dictionary[] output = new Dictionary[rows]; @@ -76,26 +101,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution return output; } - public static SelectionData GetSubSectionDocument() - { - return new SelectionData(0, 0, 2, 2); - } - - public static Batch GetBasicExecutedBatch() - { - Batch batch = new Batch(StandardQuery, 0, 0, 2, 2, GetFileStreamFactory()); - batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait(); - return batch; - } - - public static Query GetBasicExecutedQuery() - { - ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false); - Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory()); - query.Execute(); - query.ExecutionTask.Wait(); - return query; - } + #endregion #region FileStreamWriteMocking @@ -113,20 +119,30 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public class InMemoryWrapper : IFileStreamWrapper { - private readonly byte[] storage = new byte[8192]; private readonly MemoryStream memoryStream; private bool readingOnly; + private readonly byte[] storage = new byte[8192]; public InMemoryWrapper() { memoryStream = new MemoryStream(storage); } + public void Close() + { + memoryStream.Dispose(); + } + public void Dispose() { // We'll dispose this via a special method } + public void Flush() + { + if (readingOnly) { throw new InvalidOperationException(); } + } + public void Init(string fileName, int bufferSize, FileAccess fAccess) { readingOnly = fAccess == FileAccess.Read; @@ -150,16 +166,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution memoryStream.Flush(); return bytes; } - - public void Flush() - { - if (readingOnly) { throw new InvalidOperationException(); } - } - - public void Close() - { - memoryStream.Dispose(); - } } #endregion @@ -213,27 +219,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public static ConnectionInfo CreateTestConnectionInfo(Dictionary[][] data, bool throwOnRead) { - // Create connection info - ConnectionDetails connDetails = new ConnectionDetails - { - UserName = "sa", - Password = "Yukon900", - DatabaseName = Common.TestDatabase, - ServerName = Common.TestServer - }; - - return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, connDetails); + return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, StandardConnectionDetails); } #endregion #region Service Mocking - + public static void GetAutoCompleteTestObjects( out TextDocumentPosition textDocument, out ScriptFile scriptFile, out ConnectionInfo connInfo - ) + ) { textDocument = new TextDocumentPosition { @@ -245,6 +242,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution } }; + + connInfo = CreateTestConnectionInfo(null, false); + + var srvConn = GetServerConnection(connInfo); + var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn); + var binder = BinderProvider.CreateBinder(metadataProvider); connInfo = Common.CreateTestConnectionInfo(null, false); LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo()); @@ -259,17 +262,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var sqlConnection = new SqlConnection(connectionString); return new ServerConnection(sqlConnection); } - - public static ConnectionDetails GetTestConnectionDetails() - { - return new ConnectionDetails - { - DatabaseName = "123", - Password = "456", - ServerName = "789", - UserName = "012" - }; - } public static async Task GetPrimedExecutionService(ISqlConnectionFactory factory, bool isConnected, WorkspaceService workspaceService) { @@ -278,7 +270,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { await connectionService.Connect(new ConnectParams { - Connection = GetTestConnectionDetails(), + Connection = StandardConnectionDetails, OwnerUri = OwnerUri }); } @@ -300,6 +292,5 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution } #endregion - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs index 72137474..e76b4ecf 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs @@ -32,7 +32,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public void BatchCreationTest() { // If I create a new batch... - Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); // Then: // ... The text of the batch should be stored @@ -49,13 +49,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... The start line of the batch should be 0 Assert.Equal(0, batch.Selection.StartLine); + + // ... It's ordinal ID should be what I set it to + Assert.Equal(Common.Ordinal, batch.Id); } [Fact] public void BatchExecuteNoResultSets() { + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + // If I execute a query that should get no result sets - Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); + batch.BatchCompletion += callback; batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait(); // Then: @@ -73,16 +87,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... There should be a message for how many rows were affected Assert.Equal(1, batch.ResultMessages.Count()); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); } [Fact] public void BatchExecuteOneResultSet() { - int resultSets = 1; + const int resultSets = 1; ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + // If I execute a query that should get one result set - Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); + batch.BatchCompletion += callback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -104,6 +133,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... There should be a message for how many rows were affected Assert.Equal(resultSets, batch.ResultMessages.Count()); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); } [Fact] @@ -113,8 +146,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution int resultSets = dataset.Length; ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false); + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + // If I execute a query that should get two result sets - Batch batch = new Batch(Common.StandardQuery, 0, 0, 1, 1, Common.GetFileStreamFactory()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); + batch.BatchCompletion += callback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -145,15 +189,30 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... Inside each result summary, there should be 5 column definitions Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length); } + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); } [Fact] public void BatchExecuteInvalidQuery() { + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); // If I execute a batch that is invalid - Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); + batch.BatchCompletion += callback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -167,15 +226,30 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... There should be plenty of messages for the error Assert.NotEmpty(batch.ResultMessages); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); } [Fact] public async Task BatchExecuteExecuted() { + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); // If I execute a batch - Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); + batch.BatchCompletion += callback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -183,6 +257,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.True(batch.HasExecuted, "The batch should have been marked executed."); Assert.False(batch.HasError, "The batch should not have an error"); + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); + // If I execute it again // Then: // ... It should throw an invalid operation exception @@ -205,7 +283,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... I create a batch that has an empty query // Then: // ... It should throw an exception - Assert.Throws(() => new Batch(query, 0, 0, 2, 2, Common.GetFileStreamFactory())); + Assert.Throws(() => new Batch(query, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory())); } [Fact] @@ -215,7 +293,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... I create a batch that has no file stream factory // Then: // ... It should throw an exception - Assert.Throws(() => new Batch("stuff", 0, 0, 2, 2, null)); + Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, Common.Ordinal, null)); + } + + [Fact] + public void BatchInvalidOrdinal() + { + // If: + // ... I create a batch has has an ordinal less than 0 + // Then: + // ... It should throw an exception + Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, -1, Common.GetFileStreamFactory())); } #endregion @@ -268,10 +356,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public void QueryExecuteSingleBatch() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from a single batch (without separator) ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); + query.BatchCompleted += batchCallback; // Then: // ... I should get a single batch to execute that hasn't been executed @@ -291,15 +389,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.True(query.HasExecuted); Assert.NotEmpty(query.BatchSummaries); Assert.Equal(1, query.BatchSummaries.Length); + + // ... The batch callback should have been called precisely 1 time + Assert.Equal(1, batchCallbacksReceived); } [Fact] public void QueryExecuteNoOpBatch() { + // Setup: + // ... Create a callback for batch completion + Batch.BatchAsyncEventHandler batchCallback = summary => + { + throw new Exception("Batch completion callback was called"); + }; + // If: // ... I create a query from a single batch that does nothing ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); + query.BatchCompleted += batchCallback; // Then: // ... I should get no batches back @@ -322,11 +431,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public void QueryExecuteMultipleBatches() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from two batches (with separator) ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery); Query query = new Query(queryText, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); + query.BatchCompleted += batchCallback; // Then: // ... I should get back two batches to execute that haven't been executed @@ -346,16 +465,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.True(query.HasExecuted); Assert.NotEmpty(query.BatchSummaries); Assert.Equal(2, query.BatchSummaries.Length); + + // ... The batch callback should have been called precisely 2 times + Assert.Equal(2, batchCallbacksReceived); } [Fact] public void QueryExecuteMultipleBatchesWithNoOp() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from a two batches (with separator) ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery); Query query = new Query(queryText, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); + query.BatchCompleted += batchCallback; // Then: // ... I should get back one batch to execute that hasn't been executed @@ -374,15 +506,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.True(query.HasExecuted); Assert.NotEmpty(query.BatchSummaries); Assert.Equal(1, query.BatchSummaries.Length); + + // ... The batch callback should have been called precisely 1 time + Assert.Equal(1, batchCallbacksReceived); } [Fact] public void QueryExecuteInvalidBatch() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from an invalid batch ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); + query.BatchCompleted += batchCallback; // Then: // ... I should get back a query with one batch not executed @@ -404,6 +549,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.Equal(1, query.BatchSummaries.Length); Assert.True(query.BatchSummaries[0].HasError); Assert.NotEmpty(query.BatchSummaries[0].Messages); + + // ... The batch callback should have been called once + Assert.Equal(1, batchCallbacksReceived); } #endregion @@ -431,24 +579,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; - var requestContext = - RequestContextMocks.SetupRequestContextMock( - resultCallback: qer => result = qer, - expectedEvent: QueryExecuteCompleteEvent.Type, - eventCallback: (et, cp) => completeParams = cp, - errorCallback: null); - await AwaitExecution(queryService, queryParams, requestContext.Object); + QueryExecuteBatchCompleteParams batchCompleteParams = null; + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); + queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); // Then: // ... No Errors should have been sent // ... A successful result should have been sent with messages on the first batch // ... A completion event should have been fired with empty results - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never()); + // ... A batch completion event should have been fired with empty results + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); Assert.Null(result.Messages); + Assert.Equal(1, completeParams.BatchSummaries.Length); Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries); Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); + Assert.NotNull(batchCompleteParams); + Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries); + Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); + Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); + // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); } @@ -472,25 +625,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; - var requestContext = - RequestContextMocks.SetupRequestContextMock( - resultCallback: qer => result = qer, - expectedEvent: QueryExecuteCompleteEvent.Type, - eventCallback: (et, cp) => completeParams = cp, - errorCallback: null); - await AwaitExecution(queryService, queryParams, requestContext.Object); + QueryExecuteBatchCompleteParams batchCompleteParams = null; + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); + queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); // Then: // ... No errors should have been sent // ... A successful result should have been sent with messages // ... A completion event should have been fired with one result - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); Assert.Null(result.Messages); + Assert.Equal(1, completeParams.BatchSummaries.Length); Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries); Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); Assert.False(completeParams.BatchSummaries[0].HasError); + Assert.NotNull(batchCompleteParams); + Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); + Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); + Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); + // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); } @@ -508,14 +665,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution object error = null; var requestContext = RequestContextMocks.Create(null) .AddErrorHandling(e => error = e); - await queryService.HandleExecuteRequest(queryParams, requestContext.Object); + queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); // Then: // ... An error should have been returned // ... No result should have been returned // ... No completion event should have been fired // ... There should be no active queries - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once()); + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Once()); Assert.IsType(error); Assert.NotEmpty((string)error); Assert.Empty(queryService.ActiveQueries); @@ -553,11 +710,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... An error should have been sent // ... A result should have not have been sent // ... No completion event should have been fired - // ... The original query should exist - VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.Never(), Times.Once()); + // ... There should only be one active query + VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Once()); Assert.IsType(error); Assert.NotEmpty((string)error); - Assert.Contains(Common.OwnerUri, queryService.ActiveQueries.Keys); + Assert.Equal(1, queryService.ActiveQueries.Count); } [Fact] @@ -578,29 +735,36 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; // Note, we don't care about the results of the first request - var firstRequestContext = RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); - await AwaitExecution(queryService, queryParams, firstRequestContext.Object); + var firstRequestContext = RequestContextMocks.Create(null); + + queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait(); // ... And then I request another query after waiting for the first to complete QueryExecuteResult result = null; QueryExecuteCompleteParams complete = null; - var secondRequestContext = - RequestContextMocks.SetupRequestContextMock(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null); - await AwaitExecution(queryService, queryParams, secondRequestContext.Object); + QueryExecuteBatchCompleteParams batchComplete = null; + var secondRequestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); + queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait(); // Then: // ... No errors should have been sent // ... A result should have been sent with no errors // ... There should only be one active query - VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Never()); + VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); Assert.Null(result.Messages); + Assert.False(complete.BatchSummaries.Any(b => b.HasError)); Assert.Equal(1, queryService.ActiveQueries.Count); + + Assert.NotNull(batchComplete); + Assert.False(batchComplete.BatchSummary.HasError); + Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); } - [Theory] - [InlineData(null)] - public async Task QueryExecuteMissingSelectionTest(SelectionData selection) + [Fact] + public async Task QueryExecuteMissingSelectionTest() { // Set up file for returning the query @@ -613,19 +777,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // If: // ... I request to execute a query with a missing query string 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 = null }; object errorResult = null; var requestContext = RequestContextMocks.Create(null) .AddErrorHandling(error => errorResult = error); await queryService.HandleExecuteRequest(queryParams, requestContext.Object); + // Then: // ... Am error should have been sent // ... No result should have been sent // ... No completion event should have been fired // ... An active query should not have been added - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once()); + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Once()); Assert.NotNull(errorResult); Assert.IsType(errorResult); Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys); @@ -651,19 +816,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution QueryExecuteResult result = null; QueryExecuteCompleteParams complete = null; - var requestContext = - RequestContextMocks.SetupRequestContextMock(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null); - await AwaitExecution(queryService, queryParams, requestContext.Object); + QueryExecuteBatchCompleteParams batchComplete = null; + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); + queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); // Then: // ... No errors should have been sent // ... A result should have been sent with success (we successfully started the query) // ... A completion event should have been sent with error - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); Assert.Null(result.Messages); + Assert.Equal(1, complete.BatchSummaries.Length); Assert.True(complete.BatchSummaries[0].HasError); Assert.NotEmpty(complete.BatchSummaries[0].Messages); + + Assert.NotNull(batchComplete); + Assert.True(batchComplete.BatchSummary.HasError); + Assert.NotEmpty(batchComplete.BatchSummary.Messages); + Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); } #if USE_LIVE_CONNECTION @@ -694,14 +867,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution } #endif -#endregion + #endregion - private static void VerifyQueryExecuteCallCount(Mock> mock, Times sendResultCalls, Times sendEventCalls, Times sendErrorCalls) + private static void VerifyQueryExecuteCallCount(Mock> mock, Times sendResultCalls, + Times sendCompletionEventCalls, Times sendBatchCompletionEvent, Times sendErrorCalls) { mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); mock.Verify(rc => rc.SendEvent( It.Is>(m => m == QueryExecuteCompleteEvent.Type), - It.IsAny()), sendEventCalls); + It.IsAny()), sendCompletionEventCalls); + mock.Verify(rc => rc.SendEvent( + It.Is>(m => m == QueryExecuteBatchCompleteEvent.Type), + It.IsAny()), sendBatchCompletionEvent); mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs index 4036c655..148c2b8a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs @@ -95,22 +95,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.ThrowsAsync(() => b.GetSubset(resultSetIndex, 0, 2)).Wait(); } + [Fact] + public async Task BatchSubsetIncompleteTest() + { + // If: + // ... I have a batch that hasn't completed execution + Batch b = new Batch(Common.StandardQuery, Common.WholeDocument, Common.Ordinal, Common.GetFileStreamFactory()); + Assert.False(b.HasExecuted); + + // ... And I ask for a subset + // Then: + // ... It should throw an exception + await Assert.ThrowsAsync(() => b.GetSubset(Common.Ordinal, 0, 2)); + + } + #endregion #region Query Class Tests - [Fact] - public void SubsetUnexecutedQueryTest() - { - // If I have a query that has *not* been executed - Query q = new Query(Common.StandardQuery, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), Common.GetFileStreamFactory()); - - // ... And I ask for a subset with valid arguments - // Then: - // ... It should throw an exception - Assert.ThrowsAsync(() => q.GetSubset(0, 0, 0, 2)).Wait(); - } - [Theory] [InlineData(-1)] // Invalid batch, too low [InlineData(2)] // Invalid batch, too high diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs index 91e05a76..798e3bcd 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/RequestContextMocks.cs @@ -50,11 +50,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility this Mock> mock, Action errorCallback) { - // Setup the mock for SendError var sendErrorFlow = mock.Setup(rc => rc.SendError(It.IsAny())) .Returns(Task.FromResult(0)); - if (mock != null && errorCallback != null) + if (errorCallback != null) { sendErrorFlow.Callback(errorCallback); }