diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index b01fb258..7336cec2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -75,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution this.outputFileFactory = outputFileFactory; } - #region Properties + #region Events /// /// Asynchronous handler for when batches are completed @@ -88,12 +88,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public event BatchAsyncEventHandler BatchCompletion; + /// + /// Event to call when the batch has started execution + /// + public event BatchAsyncEventHandler BatchStart; + /// /// Event that will be called when the resultset has completed execution. It will not be /// called from the Batch but from the ResultSet instance /// public event ResultSet.ResultSetAsyncEventHandler ResultSetCompletion; + #endregion + + #region Properties + /// /// The text of batch that will be executed /// @@ -175,17 +184,25 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { get { - return new BatchSummary + // Batch summary with information available at start + BatchSummary summary = new BatchSummary { HasError = HasError, Id = Id, - ResultSetSummaries = ResultSummaries, - Messages = ResultMessages.ToArray(), Selection = Selection, - ExecutionElapsed = ExecutionElapsedTime, - ExecutionStart = ExecutionStartTimeStamp, - ExecutionEnd = ExecutionEndTimeStamp + ExecutionStart = ExecutionStartTimeStamp }; + + // Add on extra details if we finished executing it + if (HasExecuted) + { + summary.ResultSetSummaries = ResultSummaries; + summary.Messages = ResultMessages.ToArray(); + summary.ExecutionEnd = ExecutionEndTimeStamp; + summary.ExecutionElapsed = ExecutionElapsedTime; + } + + return summary; } } @@ -211,6 +228,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution throw new InvalidOperationException("Batch has already executed."); } + // Notify that we've started execution + if (BatchStart != null) + { + await BatchStart(this); + } + try { // Register the message listener to *this instance* of the batch diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchNotifications.cs similarity index 65% rename from src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchCompleteNotification.cs rename to src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchNotifications.cs index 6a1a6a6c..42877b6d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchCompleteNotification.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteBatchNotifications.cs @@ -10,7 +10,7 @@ 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 + public class QueryExecuteBatchNotificationParams { /// /// Summary of the batch that just completed @@ -26,7 +26,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts public class QueryExecuteBatchCompleteEvent { public static readonly - EventType Type = - EventType.Create("query/batchComplete"); + EventType Type = + EventType.Create("query/batchComplete"); + } + + public class QueryExecuteBatchStartEvent + { + public static readonly + EventType Type = + EventType.Create("query/batchStart"); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs index 120f4454..3c5bae8c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs @@ -99,10 +99,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public event Batch.BatchAsyncEventHandler BatchCompleted; + /// + /// Event to be called when a batch starts execution. + /// + public event Batch.BatchAsyncEventHandler BatchStarted; + /// /// Delegate type for callback when a query connection fails /// - /// The query that completed + /// Error message for the failing query public delegate Task QueryAsyncErrorEventHandler(string message); /// @@ -277,6 +282,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.BatchStart += BatchStarted; b.BatchCompletion += BatchCompleted; b.ResultSetCompletion += ResultSetCompleted; 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 04705004..4bd9f1fa 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -443,17 +443,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution query.QueryFailed += callback; query.QueryConnectionException += errorCallback; - // Setup the batch completion callback - Batch.BatchAsyncEventHandler batchCallback = async b => + // Setup the batch callbacks + Batch.BatchAsyncEventHandler batchStartCallback = async b => { - QueryExecuteBatchCompleteParams eventParams = new QueryExecuteBatchCompleteParams + QueryExecuteBatchNotificationParams eventParams = new QueryExecuteBatchNotificationParams + { + BatchSummary = b.Summary, + OwnerUri = executeParams.OwnerUri + }; + await requestContext.SendEvent(QueryExecuteBatchStartEvent.Type, eventParams); + }; + query.BatchStarted += batchStartCallback; + + Batch.BatchAsyncEventHandler batchCompleteCallback = async b => + { + QueryExecuteBatchNotificationParams eventParams = new QueryExecuteBatchNotificationParams { BatchSummary = b.Summary, OwnerUri = executeParams.OwnerUri }; await requestContext.SendEvent(QueryExecuteBatchCompleteEvent.Type, eventParams); }; - query.BatchCompleted += batchCallback; + query.BatchCompleted += batchCompleteCallback; // Setup the ResultSet completion callback ResultSet.ResultSetAsyncEventHandler resultCallback = async r => diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs index b5b0992a..12b99a3b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs @@ -46,22 +46,34 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... The summary should have the same info Assert.False(batch.Summary.HasError); Assert.Equal(Common.Ordinal, batch.Summary.Id); - Assert.Empty(batch.Summary.ResultSetSummaries); - Assert.Empty(batch.Summary.Messages); + Assert.Null(batch.Summary.ResultSetSummaries); + Assert.Null(batch.Summary.Messages); Assert.Equal(0, batch.Summary.Selection.StartLine); Assert.NotEqual(default(DateTime).ToString("o"), batch.Summary.ExecutionStart); // Should have been set at construction - Assert.Equal(default(DateTime).ToString("o"), batch.Summary.ExecutionEnd); - Assert.Equal((default(DateTime) - DateTime.Parse(batch.Summary.ExecutionStart)).ToString(), batch.Summary.ExecutionElapsed); + Assert.Null(batch.Summary.ExecutionEnd); + Assert.Null(batch.Summary.ExecutionElapsed); } + /// + /// Note: This test also tests the start notification feature + /// [Fact] public void BatchExecuteNoResultSets() { - // Setup: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - Batch.BatchAsyncEventHandler batchCallback = b => + // Setup: + // ... Create a callback for batch start + BatchSummary batchSummaryFromStart = null; + Batch.BatchAsyncEventHandler batchStartCallback = b => { - batchSummaryFromCallback = b.Summary; + batchSummaryFromStart = b.Summary; + return Task.FromResult(0); + }; + + // ... Create a callback for batch completion + BatchSummary batchSummaryFromCompletion = null; + Batch.BatchAsyncEventHandler batchCompleteCallback = b => + { + batchSummaryFromCompletion = b.Summary; return Task.FromResult(0); }; @@ -76,7 +88,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // If I execute a query that should get no result sets var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); - batch.BatchCompletion += batchCallback; + batch.BatchStart += batchStartCallback; + batch.BatchCompletion += batchCompleteCallback; batch.ResultSetCompletion += resultSetCallback; batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait(); @@ -96,21 +109,32 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... There should be a message for how many rows were affected Assert.Equal(1, batch.ResultMessages.Count()); + // ... The callback for batch start should have been called + // ... The info from it should have been basic + Assert.NotNull(batchSummaryFromStart); + Assert.False(batchSummaryFromStart.HasError); + Assert.Equal(Common.Ordinal, batchSummaryFromStart.Id); + Assert.Equal(Common.SubsectionDocument, batchSummaryFromStart.Selection); + Assert.True(DateTime.Parse(batchSummaryFromStart.ExecutionStart) > default(DateTime)); + Assert.Null(batchSummaryFromStart.ResultSetSummaries); + Assert.Null(batchSummaryFromStart.Messages); + Assert.Null(batchSummaryFromStart.ExecutionElapsed); + Assert.Null(batchSummaryFromStart.ExecutionEnd); + // ... The callback for batch completion should have been fired - Assert.NotNull(batchSummaryFromCallback); + // ... The summary should match the expected info + Assert.NotNull(batchSummaryFromCompletion); + Assert.False(batchSummaryFromCompletion.HasError); + Assert.Equal(Common.Ordinal, batchSummaryFromCompletion.Id); + Assert.Equal(0, batchSummaryFromCompletion.ResultSetSummaries.Length); + Assert.Equal(1, batchSummaryFromCompletion.Messages.Length); + Assert.Equal(Common.SubsectionDocument, batchSummaryFromCompletion.Selection); + Assert.True(DateTime.Parse(batchSummaryFromCompletion.ExecutionStart) > default(DateTime)); + Assert.True(DateTime.Parse(batchSummaryFromCompletion.ExecutionEnd) > default(DateTime)); + Assert.NotNull(batchSummaryFromCompletion.ExecutionElapsed); // ... The callback for the result set should NOT have been fired Assert.False(resultCallbackFired); - - // ... The summary should have the same info - Assert.False(batch.Summary.HasError); - Assert.Equal(Common.Ordinal, batch.Summary.Id); - Assert.Equal(0, batch.Summary.ResultSetSummaries.Length); - Assert.Equal(1, batch.Summary.Messages.Length); - Assert.Equal(0, batch.Summary.Selection.StartLine); - Assert.True(DateTime.Parse(batch.Summary.ExecutionStart) > default(DateTime)); - Assert.True(DateTime.Parse(batch.Summary.ExecutionEnd) > default(DateTime)); - Assert.NotNull(batch.Summary.ExecutionElapsed); } [Fact] @@ -239,9 +263,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution [Fact] public void BatchExecuteInvalidQuery() { - // Setup: Create a callback for batch completion + // Setup: + // ... Create a callback for batch start + bool batchStartCalled = false; + Batch.BatchAsyncEventHandler batchStartCallback = b => + { + batchStartCalled = true; + return Task.FromResult(0); + }; + + // ... Create a callback for batch completion BatchSummary batchSummaryFromCallback = null; - Batch.BatchAsyncEventHandler batchCallback = b => + Batch.BatchAsyncEventHandler batchCompleteCallback = b => { batchSummaryFromCallback = b.Summary; return Task.FromResult(0); @@ -258,7 +291,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // If I execute a batch that is invalid var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); - batch.BatchCompletion += batchCallback; + batch.BatchStart += batchStartCallback; + batch.BatchCompletion += batchCompleteCallback; batch.ResultSetCompletion += resultSetCallback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); @@ -276,6 +310,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... The callback for batch completion should have been fired Assert.NotNull(batchSummaryFromCallback); + + // ... The callback for batch start should have been fired + Assert.True(batchStartCalled); } [Fact] @@ -293,20 +330,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.True(batch.HasExecuted, "The batch should have been marked executed."); Assert.False(batch.HasError, "The batch should not have an error"); - // Setup for part 2: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => + // Setup for part 2: + // ... Create a callback for batch completion + Batch.BatchAsyncEventHandler completeCallback = b => { - completionCallbackFired = true; - batchSummaryFromCallback = b.Summary; - return Task.FromResult(0); + throw new Exception("Batch completion callback should not have been called"); + }; + + // ... Create a callback for batch start + Batch.BatchAsyncEventHandler startCallback = b => + { + throw new Exception("Batch start callback should not have been called"); }; // If I execute it again // Then: // ... It should throw an invalid operation exception - batch.BatchCompletion += callback; + batch.BatchStart += startCallback; + batch.BatchCompletion += completeCallback; await Assert.ThrowsAsync(() => batch.Execute(GetConnection(ci), CancellationToken.None)); @@ -315,10 +356,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.True(batch.HasExecuted, "The batch should still be marked executed."); Assert.NotEmpty(batch.ResultSets); Assert.NotEmpty(batch.ResultSummaries); - - // ... The callback for batch completion should not have been fired for the second run - Assert.False(completionCallbackFired); - Assert.Null(batchSummaryFromCallback); } [Theory] diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs index c3ff025b..3df09e13 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs @@ -63,11 +63,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution public void QueryExecuteSingleBatch() { // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => + // ... Create a callback for atch start + int batchStartCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchStartCallback = b => { - batchCallbacksReceived++; + batchStartCallbacksReceived++; + return Task.FromResult(0); + }; + + // ... Create a callback for batch completion + int batchCompleteCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCompleteCallback = summary => + { + batchCompleteCallbacksReceived++; return Task.CompletedTask; }; @@ -76,7 +84,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), fileStreamFactory); - query.BatchCompleted += batchCallback; + query.BatchStarted += batchStartCallback; + query.BatchCompleted += batchCompleteCallback; // Then: // ... I should get a single batch to execute that hasn't been executed @@ -97,16 +106,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(query.BatchSummaries); Assert.Equal(1, query.BatchSummaries.Length); - // ... The batch callback should have been called precisely 1 time - Assert.Equal(1, batchCallbacksReceived); + // ... The batch callbacks should have been called precisely 1 time + Assert.Equal(1, batchStartCallbacksReceived); + Assert.Equal(1, batchCompleteCallbacksReceived); } [Fact] public void QueryExecuteNoOpBatch() { // Setup: + // ... Create a callback for batch startup + Batch.BatchAsyncEventHandler batchStartCallback = b => + { + throw new Exception("Batch startup callback should not have been called."); + }; + // ... Create a callback for batch completion - Batch.BatchAsyncEventHandler batchCallback = summary => + Batch.BatchAsyncEventHandler batchCompletionCallback = summary => { throw new Exception("Batch completion callback was called"); }; @@ -116,7 +132,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), fileStreamFactory); - query.BatchCompleted += batchCallback; + query.BatchStarted += batchStartCallback; + query.BatchCompleted += batchCompletionCallback; // Then: // ... I should get no batches back @@ -140,12 +157,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution public void QueryExecuteMultipleBatches() { // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => + // ... Create a callback for batch start + int batchStartCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchStartCallback = b => { - batchCallbacksReceived++; - return Task.CompletedTask; + batchStartCallbacksReceived++; + return Task.FromResult(0); + }; + + // ... Create a callback for batch completion + int batchCompletedCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCompletedCallback = summary => + { + batchCompletedCallbacksReceived++; + return Task.FromResult(0); }; // If: @@ -154,7 +179,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory); - query.BatchCompleted += batchCallback; + query.BatchStarted += batchStartCallback; + query.BatchCompleted += batchCompletedCallback; // Then: // ... I should get back two batches to execute that haven't been executed @@ -175,19 +201,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(query.BatchSummaries); Assert.Equal(2, query.BatchSummaries.Length); - // ... The batch callback should have been called precisely 2 times - Assert.Equal(2, batchCallbacksReceived); + // ... The batch start and completion callbacks should have been called precisely 2 times + Assert.Equal(2, batchStartCallbacksReceived); + Assert.Equal(2, batchCompletedCallbacksReceived); } [Fact] public void QueryExecuteMultipleBatchesWithNoOp() { // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => + // ... Create a callback for batch start + int batchStartCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchStartCallback = b => { - batchCallbacksReceived++; + batchStartCallbacksReceived++; + return Task.FromResult(0); + }; + + // ... Create a callback for batch completion + int batchCompletionCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCompletionCallback = summary => + { + batchCompletionCallbacksReceived++; return Task.CompletedTask; }; @@ -197,7 +232,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory); - query.BatchCompleted += batchCallback; + query.BatchStarted += batchStartCallback; + query.BatchCompleted += batchCompletionCallback; // Then: // ... I should get back one batch to execute that hasn't been executed @@ -217,19 +253,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(query.BatchSummaries); Assert.Equal(1, query.BatchSummaries.Length); - // ... The batch callback should have been called precisely 1 time - Assert.Equal(1, batchCallbacksReceived); + // ... The batch callbacks should have been called precisely 1 time + Assert.Equal(1, batchStartCallbacksReceived); + Assert.Equal(1, batchCompletionCallbacksReceived); } [Fact] public void QueryExecuteInvalidBatch() { // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => + // ... Create a callback for batch start + int batchStartCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchStartCallback = b => { - batchCallbacksReceived++; + batchStartCallbacksReceived++; + return Task.FromResult(0); + }; + + // ... Create a callback for batch completion + int batchCompletionCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCompltionCallback = summary => + { + batchCompletionCallbacksReceived++; return Task.CompletedTask; }; @@ -238,7 +283,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), fileStreamFactory); - query.BatchCompleted += batchCallback; + query.BatchStarted += batchStartCallback; + query.BatchCompleted += batchCompltionCallback; // Then: // ... I should get back a query with one batch not executed @@ -261,8 +307,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.True(query.BatchSummaries[0].HasError); Assert.NotEmpty(query.BatchSummaries[0].Messages); - // ... The batch callback should have been called once - Assert.Equal(1, batchCallbacksReceived); + // ... The batch callbacks should have been called once + Assert.Equal(1, batchStartCallbacksReceived); + Assert.Equal(1, batchCompletionCallbacksReceived); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs index fa2151c9..c62211b0 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs @@ -36,9 +36,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchCompleteParams batchCompleteParams = null; + QueryExecuteBatchNotificationParams batchStartParams = null; + QueryExecuteBatchNotificationParams batchCompleteParams = null; var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p) .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, null); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); @@ -49,14 +51,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... A completion event should have been fired with empty results // ... A batch completion event should have been fired with empty results // ... A result set completion event should not have been fired - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); Assert.Null(result.Messages); Assert.Equal(1, completeParams.BatchSummaries.Length); Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries); Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); + // ... Batch start summary should not contain result sets, messages, but should contain owner URI + Assert.NotNull(batchStartParams); + Assert.NotNull(batchStartParams.BatchSummary); + Assert.Null(batchStartParams.BatchSummary.Messages); + Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries); + Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri); + + // ... Batch completion summary should contain result sets, messages, and the owner URI Assert.NotNull(batchCompleteParams); + Assert.NotNull(batchCompleteParams.BatchSummary); Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries); Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); @@ -80,10 +91,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchCompleteParams batchCompleteParams = null; + QueryExecuteBatchNotificationParams batchStartParams = null; + QueryExecuteBatchNotificationParams batchCompleteParams = null; QueryExecuteResultSetCompleteParams resultCompleteParams = null; var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p) .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams = p); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); @@ -94,7 +107,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... A completion event should have been fired with one result // ... A batch completion event should have been fired // ... A resultset completion event should have been fired - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never()); Assert.Null(result.Messages); Assert.Equal(1, completeParams.BatchSummaries.Length); @@ -102,6 +115,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); Assert.False(completeParams.BatchSummaries[0].HasError); + // ... Batch start summary should not contain result sets, messages, but should contain owner URI + Assert.NotNull(batchStartParams); + Assert.NotNull(batchStartParams.BatchSummary); + Assert.Null(batchStartParams.BatchSummary.Messages); + Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries); + Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri); + Assert.NotNull(batchCompleteParams); Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); @@ -131,10 +151,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchCompleteParams batchCompleteParams = null; + QueryExecuteBatchNotificationParams batchStartParams = null; + QueryExecuteBatchNotificationParams batchCompleteParams = null; List resultCompleteParams = new List(); var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p) .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p)); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); @@ -145,7 +167,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... A completion event should have been fired with one result // ... A batch completion event should have been fired // ... Two resultset completion events should have been fired - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Exactly(2), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Exactly(2), Times.Never()); Assert.Null(result.Messages); Assert.Equal(1, completeParams.BatchSummaries.Length); @@ -153,6 +175,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); Assert.False(completeParams.BatchSummaries[0].HasError); + // ... Batch start summary should not contain result sets, messages, but should contain owner URI + Assert.NotNull(batchStartParams); + Assert.NotNull(batchStartParams.BatchSummary); + Assert.Null(batchStartParams.BatchSummary.Messages); + Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries); + Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri); + Assert.NotNull(batchCompleteParams); Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); @@ -183,10 +212,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; - List batchCompleteParams = new List(); + List batchStartParams = new List(); + List batchCompleteParams = new List(); List resultCompleteParams = new List(); var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams.Add(p)) .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams.Add(p)) .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p)); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); @@ -195,7 +226,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... No errors should have been sent // ... A successful result should have been sent without messages - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Exactly(2), Times.Exactly(2), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Exactly(2), Times.Exactly(2), Times.Exactly(2), Times.Never()); Assert.Null(result.Messages); // ... A completion event should have been fired with one two batch summaries, one result each @@ -205,6 +236,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); Assert.NotEmpty(completeParams.BatchSummaries[1].Messages); + // ... Two batch start events should have been fired + Assert.Equal(2, batchStartParams.Count); + foreach (var batch in batchStartParams) + { + Assert.Null(batch.BatchSummary.Messages); + Assert.Null(batch.BatchSummary.ResultSetSummaries); + Assert.Equal(Common.OwnerUri, batch.OwnerUri); + } + // ... Two batch completion events should have been fired Assert.Equal(2, batchCompleteParams.Count); foreach (var batch in batchCompleteParams) @@ -250,7 +290,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... 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.Never(), Times.Never(), Times.Once()); + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); Assert.IsType(error); Assert.NotEmpty((string)error); Assert.Empty(queryService.ActiveQueries); @@ -285,7 +325,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... No completion event should have been fired // ... A batch completion event should have fired, but not a resultset event // ... There should only be one active query - VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Never(), Times.Once()); + VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Never(), Times.Once()); Assert.IsType(error); Assert.NotEmpty((string)error); Assert.Equal(1, queryService.ActiveQueries.Count); @@ -311,9 +351,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... And then I request another query after waiting for the first to complete QueryExecuteResult result = null; QueryExecuteCompleteParams complete = null; - QueryExecuteBatchCompleteParams batchComplete = null; + QueryExecuteBatchNotificationParams batchStart = null; + QueryExecuteBatchNotificationParams batchComplete = null; var secondRequestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) + .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStart = p) .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object); @@ -322,20 +364,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... A result should have been sent with no errors // ... There should only be one active query // ... A batch completion event should have fired, but not a result set completion event - VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); + VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); Assert.Null(result.Messages); Assert.False(complete.BatchSummaries.Any(b => b.HasError)); Assert.Equal(1, queryService.ActiveQueries.Count); + Assert.NotNull(batchStart); 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() { // Given: // ... A workspace with a standard query is configured @@ -357,7 +399,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... No result should have been sent // ... No completion events should have been fired // ... An active query should not have been added - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); Assert.NotNull(errorResult); Assert.IsType(errorResult); Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys); @@ -380,9 +422,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteResult result = null; QueryExecuteCompleteParams complete = null; - QueryExecuteBatchCompleteParams batchComplete = null; + QueryExecuteBatchNotificationParams batchStart = null; + QueryExecuteBatchNotificationParams batchComplete = null; var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) + .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStart = p) .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); @@ -390,29 +434,43 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... No errors should have been sent // ... A result should have been sent with success (we successfully started the query) // ... A completion event (query, batch, not resultset) should have been sent with error - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), 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(batchStart); + Assert.False(batchStart.BatchSummary.HasError); + Assert.Null(batchStart.BatchSummary.Messages); + Assert.Null(batchStart.BatchSummary.ResultSetSummaries); + Assert.Equal(Common.OwnerUri, batchStart.OwnerUri); + Assert.NotNull(batchComplete); Assert.True(batchComplete.BatchSummary.HasError); Assert.NotEmpty(batchComplete.BatchSummary.Messages); - Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); + Assert.Equal(Common.OwnerUri, batchComplete.OwnerUri); } - private static void VerifyQueryExecuteCallCount(Mock> mock, Times sendResultCalls, - Times sendCompletionEventCalls, Times sendBatchCompletionEvent, Times sendResultCompleteEvent, Times sendErrorCalls) + private static void VerifyQueryExecuteCallCount(Mock> mock, + Times sendResultCalls, + Times sendCompletionEventCalls, + Times sendBatchStartEvent, + Times sendBatchCompletionEvent, + Times sendResultCompleteEvent, + Times sendErrorCalls) { mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); mock.Verify(rc => rc.SendEvent( It.Is>(m => m == QueryExecuteCompleteEvent.Type), It.IsAny()), sendCompletionEventCalls); mock.Verify(rc => rc.SendEvent( - It.Is>(m => m == QueryExecuteBatchCompleteEvent.Type), - It.IsAny()), sendBatchCompletionEvent); + It.Is>(m => m == QueryExecuteBatchCompleteEvent.Type), + It.IsAny()), sendBatchCompletionEvent); + mock.Verify(rc => rc.SendEvent( + It.Is>(m => m== QueryExecuteBatchStartEvent.Type), + It.IsAny()), sendBatchStartEvent); mock.Verify(rc => rc.SendEvent( It.Is>(m => m == QueryExecuteResultSetCompleteEvent.Type), It.IsAny()), sendResultCompleteEvent);