mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
This changes adds the following two notifications from the results processing within a batch. These new notifications allows a consumer to stream results from a resultset instead of getting them all at once after the entire resultset has been fetched. ResultsAvailable This is issued after at least 1 row has been fetched for this resultset. ResultsUpdated This is issued periodically as more rows are available on this resultset. The final send of this notification when all rows have been fetched has the property 'Complete' set to true in the ResultSummary object. Detailed Change Log: * Initial completed implementation of QueryResults stream feature. 3 unittests still need fixing * Fix for the 3 failing test. I will look into making MockBehavior strict again for the three tests later * Making GetReader/GetWriter use filestream objects in FileShare.ReadWrite mode so the file can be concurrently read and written * Changing resultsAvailable also to fire off on a timer instead of after 1st row * adding a project for clr TableValuedFunction to produce result set with delays after each row. This is helpful in end to end testing. * Fixing up some tests and simplifying implementation of result update timer * Address review comments * Some test fixes * Disabled flaky test verification
745 lines
36 KiB
C#
745 lines
36 KiB
C#
//
|
|
// 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.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Common.RequestContextMocking;
|
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using Xunit;
|
|
using Assert = Xunit.Assert;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
|
{
|
|
public class ServiceIntegrationTests
|
|
{
|
|
|
|
#region Get SQL Tests
|
|
|
|
[Fact]
|
|
public void ExecuteDocumentStatementTest()
|
|
{
|
|
string query = string.Format("{0}{1}GO{1}{0}", Constants.StandardQuery, Environment.NewLine);
|
|
var workspaceService = GetDefaultWorkspaceService(query);
|
|
var queryService = new QueryExecutionService(null, workspaceService);
|
|
|
|
var queryParams = new ExecuteDocumentStatementParams { OwnerUri = Constants.OwnerUri, Line = 0, Column = 0 };
|
|
var queryText = queryService.GetSqlText(queryParams);
|
|
|
|
// The text should match the standard query
|
|
Assert.Equal(queryText, Constants.StandardQuery);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteDocumentStatementSameLine()
|
|
{
|
|
var statement1 = Constants.StandardQuery;
|
|
var statement2 = "SELECT * FROM sys.databases";
|
|
// Test putting the cursor at the start of the line
|
|
ExecuteDocumentStatementSameLineHelper(statement1, statement2, 0, statement1);
|
|
// Test putting the cursor at the end of statement 1
|
|
ExecuteDocumentStatementSameLineHelper(statement1, statement2, statement1.Length, statement1);
|
|
// Test putting the cursor at the start of statement 2
|
|
ExecuteDocumentStatementSameLineHelper(statement1, statement2, statement1.Length + 1, statement2);
|
|
// Test putting the cursor at the end of the line
|
|
ExecuteDocumentStatementSameLineHelper(statement1, statement2, statement1.Length + 1 + statement2.Length, statement2);
|
|
// Test putting the cursor after a semicolon when only one statement is on the line
|
|
ExecuteDocumentStatementSameLineHelper(statement1, "", statement1.Length + 1, statement1);
|
|
}
|
|
|
|
private void ExecuteDocumentStatementSameLineHelper(string statement1, string statement2, int cursorColumn, string expectedQueryText)
|
|
{
|
|
string query = string.Format("{0};{1}", statement1, statement2);
|
|
var workspaceService = GetDefaultWorkspaceService(query);
|
|
var queryService = new QueryExecutionService(null, workspaceService);
|
|
|
|
// If a line has multiple statements and the cursor is somewhere in the line
|
|
var queryParams = new ExecuteDocumentStatementParams { OwnerUri = Constants.OwnerUri, Line = 0, Column = cursorColumn };
|
|
var queryText = queryService.GetSqlText(queryParams);
|
|
|
|
// The query text should match the expected statement at the cursor
|
|
Assert.Equal(expectedQueryText, queryText);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSqlTextFromDocumentRequestFull()
|
|
{
|
|
// Setup:
|
|
// ... Create a workspace service with a multi-line constructed query
|
|
// ... Create a query execution service without a connection service (we won't be
|
|
// executing queries), and the previously created workspace service
|
|
string query = string.Format("{0}{1}GO{1}{0}", Constants.StandardQuery, Environment.NewLine);
|
|
var workspaceService = GetDefaultWorkspaceService(query);
|
|
var queryService = new QueryExecutionService(null, workspaceService);
|
|
|
|
// If: I attempt to get query text from execute document params (entire document)
|
|
var queryParams = new ExecuteDocumentSelectionParams {OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
var queryText = queryService.GetSqlText(queryParams);
|
|
|
|
// Then: The text should match the constructed query
|
|
Assert.Equal(query, queryText);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSqlTextFromDocumentRequestPartial()
|
|
{
|
|
// Setup:
|
|
// ... Create a workspace service with a multi-line constructed query
|
|
string query = string.Format("{0}{1}GO{1}{0}", Constants.StandardQuery, Environment.NewLine);
|
|
var workspaceService = GetDefaultWorkspaceService(query);
|
|
var queryService = new QueryExecutionService(null, workspaceService);
|
|
|
|
// If: I attempt to get query text from execute document params (partial document)
|
|
var queryParams = new ExecuteDocumentSelectionParams {OwnerUri = Constants.OwnerUri, QuerySelection = Common.SubsectionDocument};
|
|
var queryText = queryService.GetSqlText(queryParams);
|
|
|
|
// Then: The text should be a subset of the constructed query
|
|
Assert.Contains(queryText, query);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSqlTextFromStringRequest()
|
|
{
|
|
// Setup:
|
|
// ... Create a query execution service without a connection service or workspace
|
|
// service (we won't execute code that uses either
|
|
var queryService = new QueryExecutionService(null, null);
|
|
|
|
// If: I attempt to get query text from execute string params
|
|
var queryParams = new ExecuteStringParams {OwnerUri = Constants.OwnerUri, Query = Constants.StandardQuery};
|
|
var queryText = queryService.GetSqlText(queryParams);
|
|
|
|
// Then: The text should match the standard query
|
|
Assert.Equal(Constants.StandardQuery, queryText);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSqlTextFromInvalidType()
|
|
{
|
|
// Setup:
|
|
// ... Mock up an implementation of ExecuteRequestParamsBase
|
|
// ... Create a query execution service without a connection service or workspace
|
|
// service (we won't execute code that uses either
|
|
var mockParams = new Mock<ExecuteRequestParamsBase>().Object;
|
|
var queryService = new QueryExecutionService(null, null);
|
|
|
|
// If: I attempt to get query text from the mock params
|
|
// Then: It should throw an exception
|
|
Assert.Throws<InvalidCastException>(() => queryService.GetSqlText(mockParams));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Inter-Service API Tests
|
|
|
|
[Fact]
|
|
public async Task InterServiceExecuteNullExecuteParams()
|
|
{
|
|
// Setup: Create a query service
|
|
var qes = new QueryExecutionService(null, null);
|
|
var eventSender = new EventFlowValidator<ExecuteRequestResult>().Complete().Object;
|
|
|
|
|
|
// If: I call the inter-service API to execute with a null execute params
|
|
// Then: It should throw
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => qes.InterServiceExecuteQuery(null, null, eventSender, null, null, null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InterServiceExecuteNullEventSender()
|
|
{
|
|
// Setup: Create a query service, and execute params
|
|
var qes = new QueryExecutionService(null, null);
|
|
var executeParams = new ExecuteStringParams();
|
|
|
|
// If: I call the inter-service API to execute a query with a a null event sender
|
|
// Then: It should throw
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => qes.InterServiceExecuteQuery(executeParams, null, null, null, null, null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InterServiceDisposeNullSuccessFunc()
|
|
{
|
|
// Setup: Create a query service and dispose params
|
|
var qes = new QueryExecutionService(null, null);
|
|
Func<string, Task> failureFunc = Task.FromResult;
|
|
|
|
// If: I call the inter-service API to dispose a query with a null success function
|
|
// Then: It should throw
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => qes.InterServiceDisposeQuery(Constants.OwnerUri, null, failureFunc));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InterServiceDisposeNullFailureFunc()
|
|
{
|
|
// Setup: Create a query service and dispose params
|
|
var qes = new QueryExecutionService(null, null);
|
|
Func<Task> successFunc = () => Task.FromResult(0);
|
|
|
|
// If: I call the inter-service API to dispose a query with a null success function
|
|
// Then: It should throw
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => qes.InterServiceDisposeQuery(Constants.OwnerUri, successFunc, null));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Execution Tests
|
|
// NOTE: In order to limit test duplication, we're running the ExecuteDocumentSelection
|
|
// version of execute query. The code paths are almost identical.
|
|
|
|
[Fact]
|
|
private async Task QueryExecuteAllBatchesNoOp()
|
|
{
|
|
// If:
|
|
// ... I request to execute a valid query with all batches as no op
|
|
var workspaceService = GetDefaultWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.NoOpQuery));
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri };
|
|
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddStandardMessageValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardMessageValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddEventValidation(QueryCompleteEvent.Type, p =>
|
|
{
|
|
// Validate OwnerURI matches
|
|
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
|
Assert.NotNull(p.BatchSummaries);
|
|
Assert.Equal(2, p.BatchSummaries.Length);
|
|
Assert.All(p.BatchSummaries, bs => Assert.Equal(0, bs.ResultSetSummaries.Length));
|
|
}).Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteSingleBatchNoResultsTest()
|
|
{
|
|
// If:
|
|
// ... I request to execute a valid query with no results
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri};
|
|
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddStandardMessageValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.Complete();
|
|
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteSingleBatchSingleResultTest()
|
|
{
|
|
// If:
|
|
// ... I request to execute a valid query with results
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
|
|
List<ResultSetEventParams> collectedResultSetEventParams = new List<ResultSetEventParams>();
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddResultSetValidator(ResultSetAvailableEvent.Type, collectedResultSetEventParams)
|
|
.AddResultSetValidator(ResultSetUpdatedEvent.Type, collectedResultSetEventParams)
|
|
.AddResultSetValidator(ResultSetCompleteEvent.Type, collectedResultSetEventParams)
|
|
.AddStandardMessageValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.ValidateResultSetSummaries(collectedResultSetEventParams).Validate();
|
|
|
|
// ... There should be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteSingleBatchMultipleResultTest()
|
|
{
|
|
// If:
|
|
// ... I request to execute a valid query with one batch and multiple result sets
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var dataset = new[] {Common.StandardTestResultSet, Common.StandardTestResultSet};
|
|
var queryService = Common.GetPrimedExecutionService(dataset, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
|
|
List<ResultSetEventParams> collectedResultSetEventParams = new List<ResultSetEventParams>();
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddResultSetValidator(ResultSetAvailableEvent.Type, collectedResultSetEventParams)
|
|
.AddResultSetValidator(ResultSetUpdatedEvent.Type, collectedResultSetEventParams)
|
|
.AddResultSetValidator(ResultSetCompleteEvent.Type, collectedResultSetEventParams)
|
|
.AddStandardMessageValidator()
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteMultipleBatchSingleResultTest()
|
|
{
|
|
// If:
|
|
// ... I request a to execute a valid query with multiple batches
|
|
var workspaceService = GetDefaultWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Constants.StandardQuery));
|
|
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
|
|
List<ResultSetEventParams> collectedResultSetEventParams = new List<ResultSetEventParams>();
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddResultSetValidator(ResultSetAvailableEvent.Type, collectedResultSetEventParams)
|
|
.AddResultSetValidator(ResultSetUpdatedEvent.Type, collectedResultSetEventParams)
|
|
.AddResultSetValidator(ResultSetCompleteEvent.Type, collectedResultSetEventParams)
|
|
.AddStandardMessageValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardMessageValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardQueryCompleteValidator(2)
|
|
.Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteUnconnectedUriTest()
|
|
{
|
|
// Given:
|
|
// If:
|
|
// ... I request to execute a query using a file URI that isn't connected
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, false, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
|
|
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardErrorValidation()
|
|
.Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should be no active queries
|
|
Assert.Empty(queryService.ActiveQueries);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteInProgressTest()
|
|
{
|
|
// If:
|
|
// ... I request to execute a query
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
|
|
// Note, we don't care about the results of the first request
|
|
var firstRequestContext = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
|
await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
|
|
|
// ... And then I request another query without waiting for the first to complete
|
|
queryService.ActiveQueries[Constants.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardErrorValidation()
|
|
.Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should only be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteCompletedTest()
|
|
{
|
|
// If:
|
|
// ... I request to execute a query
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
|
|
// Note, we don't care about the results of the first request
|
|
var firstRequestContext = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
|
await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
|
|
|
// ... And then I request another query after waiting for the first to complete
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.Complete();
|
|
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... All events should have been called as per their flow validator
|
|
efv.Validate();
|
|
|
|
// ... There should only be one active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task QueryExecuteInvalidQueryTest()
|
|
{
|
|
// If:
|
|
// ... I request to execute a query that is invalid
|
|
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, true, true, false, workspaceService);
|
|
var queryParams = new ExecuteDocumentSelectionParams {OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
|
|
|
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
|
.AddStandardQueryResultValidator()
|
|
.AddStandardBatchStartValidator()
|
|
.AddStandardBatchCompleteValidator()
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.AddStandardQueryCompleteValidator(1)
|
|
.Complete();
|
|
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
|
|
|
// Then:
|
|
// ... An error should have been sent
|
|
efv.Validate();
|
|
|
|
// ... There should not be an active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
// TODO https://github.com/Microsoft/vscode-mssql/issues/1003 reenable and make non-flaky
|
|
// [Fact]
|
|
public async Task SimpleExecuteErrorWithNoResultsTest()
|
|
{
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, false, null);
|
|
var queryParams = new SimpleExecuteParams { OwnerUri = Constants.OwnerUri, QueryString = Constants.StandardQuery };
|
|
var efv = new EventFlowValidator<SimpleExecuteResult>()
|
|
.AddSimpleExecuteErrorValidator(SR.QueryServiceResultSetHasNoResults)
|
|
.Complete();
|
|
await queryService.HandleSimpleExecuteRequest(queryParams, efv.Object);
|
|
|
|
await Task.WhenAll(queryService.ActiveSimpleExecuteRequests.Values);
|
|
|
|
Query q = queryService.ActiveQueries.Values.First();
|
|
Assert.NotNull(q);
|
|
q.ExecutionTask.Wait();
|
|
|
|
efv.Validate();
|
|
|
|
Assert.Equal(0, queryService.ActiveQueries.Count);
|
|
|
|
}
|
|
|
|
// TODO reenable and make non-flaky
|
|
// [Fact]
|
|
public async Task SimpleExecuteVerifyResultsTest()
|
|
{
|
|
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, false, null);
|
|
var queryParams = new SimpleExecuteParams { OwnerUri = Constants.OwnerUri, QueryString = Constants.StandardQuery };
|
|
var efv = new EventFlowValidator<SimpleExecuteResult>()
|
|
.AddSimpleExecuteQueryResultValidator(Common.StandardTestDataSet)
|
|
.Complete();
|
|
await queryService.HandleSimpleExecuteRequest(queryParams, efv.Object);
|
|
|
|
await Task.WhenAll(queryService.ActiveSimpleExecuteRequests.Values);
|
|
|
|
Query q = queryService.ActiveQueries.Values.First();
|
|
|
|
Assert.NotNull(q);
|
|
|
|
// wait on the task to finish
|
|
q.ExecutionTask.Wait();
|
|
|
|
efv.Validate();
|
|
|
|
Assert.Equal(0, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SimpleExecuteMultipleQueriesTest()
|
|
{
|
|
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, false, null);
|
|
var queryParams = new SimpleExecuteParams { OwnerUri = Constants.OwnerUri, QueryString = Constants.StandardQuery };
|
|
var efv1 = new EventFlowValidator<SimpleExecuteResult>()
|
|
.AddSimpleExecuteQueryResultValidator(Common.StandardTestDataSet)
|
|
.Complete();
|
|
var efv2 = new EventFlowValidator<SimpleExecuteResult>()
|
|
.AddSimpleExecuteQueryResultValidator(Common.StandardTestDataSet)
|
|
.Complete();
|
|
Task qT1 = queryService.HandleSimpleExecuteRequest(queryParams, efv1.Object);
|
|
Task qT2 = queryService.HandleSimpleExecuteRequest(queryParams, efv2.Object);
|
|
|
|
await Task.WhenAll(qT1, qT2);
|
|
|
|
await Task.WhenAll(queryService.ActiveSimpleExecuteRequests.Values);
|
|
|
|
var queries = queryService.ActiveQueries.Values.ToArray();
|
|
var queryTasks = queries.Select(query => query.ExecutionTask);
|
|
|
|
await Task.WhenAll(queryTasks);
|
|
|
|
efv1.Validate();
|
|
efv2.Validate();
|
|
|
|
Assert.Equal(0, queryService.ActiveQueries.Count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private static WorkspaceService<SqlToolsSettings> GetDefaultWorkspaceService(string query)
|
|
{
|
|
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
|
var workspaceService = Common.GetPrimedWorkspaceService(query);
|
|
return workspaceService;
|
|
}
|
|
}
|
|
|
|
public static class QueryExecutionEventFlowValidatorExtensions
|
|
{
|
|
|
|
public static EventFlowValidator<SimpleExecuteResult> AddSimpleExecuteQueryResultValidator(
|
|
this EventFlowValidator<SimpleExecuteResult> efv, TestResultSet[] testData)
|
|
{
|
|
return efv.AddResultValidation(p =>
|
|
{
|
|
Assert.Equal(p.RowCount, testData[0].Rows.Count);
|
|
});
|
|
}
|
|
|
|
public static EventFlowValidator<SimpleExecuteResult> AddSimpleExecuteErrorValidator(
|
|
this EventFlowValidator<SimpleExecuteResult> efv, string expectedMessage)
|
|
{
|
|
return efv.AddSimpleErrorValidation((m, e) =>
|
|
{
|
|
Assert.Equal(m, expectedMessage);
|
|
});
|
|
}
|
|
|
|
public static EventFlowValidator<ExecuteRequestResult> AddStandardQueryResultValidator(
|
|
this EventFlowValidator<ExecuteRequestResult> efv)
|
|
{
|
|
// We just need to makes sure we get a result back, there's no params to validate
|
|
return efv.AddResultValidation(Assert.NotNull);
|
|
}
|
|
|
|
public static EventFlowValidator<TRequestContext> AddStandardBatchStartValidator<TRequestContext>(
|
|
this EventFlowValidator<TRequestContext> efv)
|
|
{
|
|
return efv.AddEventValidation(BatchStartEvent.Type, p =>
|
|
{
|
|
// Validate OwnerURI and batch summary is returned
|
|
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
|
Assert.NotNull(p.BatchSummary);
|
|
});
|
|
}
|
|
|
|
public static EventFlowValidator<TRequestContext> AddStandardBatchCompleteValidator<TRequestContext>(
|
|
this EventFlowValidator<TRequestContext> efv)
|
|
{
|
|
return efv.AddEventValidation(BatchCompleteEvent.Type, p =>
|
|
{
|
|
// Validate OwnerURI and result summary are returned
|
|
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
|
Assert.NotNull(p.BatchSummary);
|
|
});
|
|
}
|
|
|
|
public static EventFlowValidator<TRequestContext> AddStandardMessageValidator<TRequestContext>(
|
|
this EventFlowValidator<TRequestContext> efv)
|
|
{
|
|
return efv.AddEventValidation(MessageEvent.Type, p =>
|
|
{
|
|
// Validate OwnerURI and message are returned
|
|
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
|
Assert.NotNull(p.Message);
|
|
});
|
|
}
|
|
|
|
public static EventFlowValidator<TRequestContext> AddResultSetValidator<TRequestContext, T>(
|
|
this EventFlowValidator<TRequestContext> efv, EventType<T> expectedEvent, List<ResultSetEventParams> resultSetEventParamList = null) where T : ResultSetEventParams
|
|
{
|
|
return efv.SetupCallbackOnMethodSendEvent(expectedEvent, (p) =>
|
|
{
|
|
// Validate OwnerURI and summary are returned
|
|
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
|
Assert.NotNull(p.ResultSetSummary);
|
|
resultSetEventParamList?.Add(p);
|
|
});
|
|
}
|
|
|
|
public static EventFlowValidator<TRequestContext> ValidateResultSetSummaries<TRequestContext>(
|
|
this EventFlowValidator<TRequestContext> efv, List<ResultSetEventParams> resultSetEventParamList)
|
|
{
|
|
string GetResultSetKey(ResultSetSummary summary)
|
|
{
|
|
return $"BatchId:{summary.BatchId}, ResultId:{summary.Id}";
|
|
}
|
|
|
|
// Separate the result set resultSetEventParamsList by batchid, resultsetid and by resultseteventtype.
|
|
ConcurrentDictionary<string, List<ResultSetEventParams>> resultSetDictionary =
|
|
new ConcurrentDictionary<string, List<ResultSetEventParams>>();
|
|
|
|
foreach (var resultSetEventParam in resultSetEventParamList)
|
|
{
|
|
resultSetDictionary
|
|
.GetOrAdd(GetResultSetKey(resultSetEventParam.ResultSetSummary), (key) => new List<ResultSetEventParams>())
|
|
.Add(resultSetEventParam);
|
|
}
|
|
|
|
foreach (var (key, list) in resultSetDictionary)
|
|
{
|
|
ResultSetSummary completeSummary = null, lastResultSetSummary = null;
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
VerifyResultSummary(key, i, list, ref completeSummary, ref lastResultSetSummary);
|
|
}
|
|
|
|
// Verify that the completeEvent and lastResultSetSummary has same number of rows
|
|
//
|
|
if (lastResultSetSummary != null && completeSummary != null)
|
|
{
|
|
Assert.True(lastResultSetSummary.RowCount == completeSummary.RowCount, "CompleteSummary and last Update Summary should have same number of rows");
|
|
}
|
|
}
|
|
|
|
|
|
return efv;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that a ResultSummary at a given position as expected within the list of ResultSummary items
|
|
/// </summary>
|
|
/// <param name="batchIdResultSetId">The batchId and ResultSetId for this list of events</param>
|
|
/// <param name="position">The position with resultSetEventParamsList that we are verifying in this call<</param>
|
|
/// <param name="resultSetEventParamsList">The list of resultSetParams that we are verifying</param>
|
|
/// <param name="completeSummary"> This should be null when we start validating the list of ResultSetEventParams</param>
|
|
/// <param name="lastResultSetSummary"> This should be null when we start validating the list of ResultSetEventParams</param>
|
|
private static void VerifyResultSummary(string batchIdResultSetId, int position, List<ResultSetEventParams> resultSetEventParamsList, ref ResultSetSummary completeSummary, ref ResultSetSummary lastResultSetSummary)
|
|
{
|
|
ResultSetEventParams resultSetEventParams = resultSetEventParamsList[position];
|
|
switch (resultSetEventParams.GetType().Name)
|
|
{
|
|
case nameof(ResultSetAvailableEventParams):
|
|
// Save the lastResultSetSummary for this event for other verifications.
|
|
//
|
|
lastResultSetSummary = resultSetEventParams.ResultSetSummary;
|
|
break;
|
|
case nameof(ResultSetUpdatedEventParams):
|
|
// Verify that the updateEvent is not the first in the sequence. Since we set lastResultSetSummary on each available or updatedEvent, we check that there has been no lastResultSetSummary previously set yet.
|
|
//
|
|
Assert.True(null != lastResultSetSummary,
|
|
$"UpdateResultSet was found to be the first message received for {batchIdResultSetId}"
|
|
+ $"\r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", resultSetEventParamsList.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}"
|
|
);
|
|
|
|
// Verify that the number of rows in the current updatedSummary is >= those in the lastResultSetSummary
|
|
//
|
|
Assert.True(resultSetEventParams.ResultSetSummary.RowCount >= lastResultSetSummary.RowCount,
|
|
$"UpdatedResultSetSummary at position: {position} has less rows than LastUpdatedSummary (or AvailableSummary) received for {batchIdResultSetId}"
|
|
+ $"\r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", resultSetEventParamsList.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}"
|
|
+ $"\r\n\t\t LastUpdatedSummary (or Available):{lastResultSetSummary}"
|
|
+ $"\r\n\t\t UpdatedResultSetSummary:{resultSetEventParams.ResultSetSummary}");
|
|
|
|
// Save the lastResultSetSummary for this event for other verifications.
|
|
//
|
|
lastResultSetSummary = resultSetEventParams.ResultSetSummary;
|
|
break;
|
|
case nameof(ResultSetCompleteEventParams):
|
|
// Verify that there is only one completeEvent
|
|
//
|
|
Assert.True(null == completeSummary,
|
|
$"CompleteResultSet was received multiple times for {batchIdResultSetId}"
|
|
+ $"\r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", resultSetEventParamsList.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}"
|
|
);
|
|
|
|
// Save the completeSummary for this event for other verifications.
|
|
//
|
|
completeSummary = resultSetEventParams.ResultSetSummary;
|
|
|
|
// Verify that the complete flag is set
|
|
//
|
|
Assert.True(completeSummary.Complete,
|
|
$"completeSummary.Complete is not true"
|
|
+ $"\r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", resultSetEventParamsList.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}"
|
|
);
|
|
break;
|
|
default:
|
|
throw new AssertionException(
|
|
$"Unknown type of ResultSetEventParams, actual type received is: {resultSetEventParams.GetType().Name}");
|
|
}
|
|
}
|
|
|
|
public static EventFlowValidator<TRequestContext> AddStandardQueryCompleteValidator<TRequestContext>(
|
|
this EventFlowValidator<TRequestContext> efv, int expectedBatches)
|
|
{
|
|
return efv.AddEventValidation(QueryCompleteEvent.Type, p =>
|
|
{
|
|
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
|
Assert.NotNull(p.BatchSummaries);
|
|
Assert.Equal(expectedBatches, p.BatchSummaries.Length);
|
|
});
|
|
}
|
|
}
|
|
}
|