mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 09:59:48 -05:00
The main change in this pull request is to add a new event that will be fired upon completion of a resultset but before the completion of a batch. This event will only fire if a resultset is available and generated. Changes: * ConnectionService - Slight changes to enable mocking, cleanup * Batch - Moving summary generation into ResultSet class, adding generation of ordinals for resultset and locking of result set list (which needs further refinement, but would be outside scope of this change) * Adding new event and associated parameters for completion of a resultset. Params return the resultset summary * Adding logic for assigning the event a handler in the query execution service * Adding unit tests for testing the new event /making sure the existing tests work * Refactoring some private properties into member variables * Refactor to remove SectionData class in favor of BufferRange * Adding callback for batch completion that will let the extension know that a batch has completed execution * Refactoring to make progressive results work as per async query execution * Allowing retrieval of batch results while query is in progress * reverting global.json, whoops * Adding a few missing comments, and fixing a couple code style bugs * Using SelectionData everywhere again * One more missing comment * Adding new notification type for result set completion * Plumbing event for result set completion * Unit tests for result set events This includes a fairly substantial change to create a mock of the ConnectionService and to create separate memorystream storage arrays. It preserves more correct behavior with a integration test, fixes an issue where the test db reader will return n-1 rows because the Reliable Connection Helper steals a record. * Adding locking to ResultSets for thread safety * Adding/fixing unit tests * Adding batch ID to result set summary
255 lines
11 KiB
C#
255 lines
11 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.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|
{
|
|
public class SubsetTests
|
|
{
|
|
#region ResultSet Class Tests
|
|
|
|
[Theory]
|
|
[InlineData(0, 2)]
|
|
[InlineData(0, 20)]
|
|
[InlineData(1, 2)]
|
|
public void ResultSetValidTest(int startRow, int rowCount)
|
|
{
|
|
// Setup:
|
|
// ... I have a batch that has been executed
|
|
Batch b = Common.GetBasicExecutedBatch();
|
|
|
|
// If:
|
|
// ... I have a result set and I ask for a subset with valid arguments
|
|
ResultSet rs = b.ResultSets.First();
|
|
ResultSetSubset subset = rs.GetSubset(startRow, rowCount).Result;
|
|
|
|
// Then:
|
|
// ... I should get the requested number of rows back
|
|
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.RowCount);
|
|
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.Rows.Length);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-1, 2)] // Invalid start index, too low
|
|
[InlineData(10, 2)] // Invalid start index, too high
|
|
[InlineData(0, -1)] // Invalid row count, too low
|
|
[InlineData(0, 0)] // Invalid row count, zero
|
|
public void ResultSetInvalidParmsTest(int rowStartIndex, int rowCount)
|
|
{
|
|
// If:
|
|
// I have an executed batch with a resultset in it and request invalid result set from it
|
|
Batch b = Common.GetBasicExecutedBatch();
|
|
ResultSet rs = b.ResultSets.First();
|
|
|
|
// Then:
|
|
// ... It should throw an exception
|
|
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => rs.GetSubset(rowStartIndex, rowCount)).Wait();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResultSetNotReadTest()
|
|
{
|
|
// If:
|
|
// ... I have a resultset that hasn't been executed and I request a valid result set from it
|
|
// Then:
|
|
// ... It should throw an exception for having not been read
|
|
ResultSet rs = new ResultSet(new TestDbDataReader(null), Common.Ordinal, Common.Ordinal, Common.GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => rs.GetSubset(0, 1));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Batch Class Tests
|
|
|
|
[Theory]
|
|
[InlineData(2)]
|
|
[InlineData(20)]
|
|
public void BatchSubsetValidTest(int rowCount)
|
|
{
|
|
// If I have an executed batch
|
|
Batch b = Common.GetBasicExecutedBatch();
|
|
|
|
// ... And I ask for a subset with valid arguments
|
|
ResultSetSubset subset = b.GetSubset(0, 0, rowCount).Result;
|
|
|
|
// Then:
|
|
// I should get the requested number of rows
|
|
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.RowCount);
|
|
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.Rows.Length);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-1)] // Invalid result set, too low
|
|
[InlineData(2)] // Invalid result set, too high
|
|
public void BatchSubsetInvalidParamsTest(int resultSetIndex)
|
|
{
|
|
// If I have an executed batch
|
|
Batch b = Common.GetBasicExecutedBatch();
|
|
|
|
// ... And I ask for a subset with an invalid result set index
|
|
// Then:
|
|
// ... It should throw an exception
|
|
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => b.GetSubset(resultSetIndex, 0, 2)).Wait();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Query Class Tests
|
|
|
|
[Theory]
|
|
[InlineData(-1)] // Invalid batch, too low
|
|
[InlineData(2)] // Invalid batch, too high
|
|
public void QuerySubsetInvalidParamsTest(int batchIndex)
|
|
{
|
|
// If I have an executed query
|
|
Query q = Common.GetBasicExecutedQuery();
|
|
|
|
// ... And I ask for a subset with an invalid result set index
|
|
// Then:
|
|
// ... It should throw an exception
|
|
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => q.GetSubset(batchIndex, 0, 0, 1)).Wait();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Service Intergration Tests
|
|
|
|
[Fact]
|
|
public async Task SubsetServiceValidTest()
|
|
{
|
|
// If:
|
|
// ... I have a query that has results (doesn't matter what)
|
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, true, false, workspaceService);
|
|
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
|
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
|
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
|
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
|
|
|
// ... And I then ask for a valid set of results from it
|
|
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
|
QueryExecuteSubsetResult result = null;
|
|
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
|
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
|
|
|
// Then:
|
|
// ... I should have a successful result
|
|
// ... There should be rows there (other test validate that the rows are correct)
|
|
// ... There should not be any error calls
|
|
VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never());
|
|
Assert.Null(result.Message);
|
|
Assert.NotNull(result.ResultSubset);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubsetServiceMissingQueryTest()
|
|
{
|
|
// If:
|
|
// ... I ask for a set of results for a file that hasn't executed a query
|
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
|
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
|
QueryExecuteSubsetResult result = null;
|
|
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
|
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
|
|
|
// Then:
|
|
// ... I should have an error result
|
|
// ... There should be no rows in the result set
|
|
// ... There should not be any error calls
|
|
VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never());
|
|
Assert.NotNull(result.Message);
|
|
Assert.Null(result.ResultSubset);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubsetServiceUnexecutedQueryTest()
|
|
{
|
|
// If:
|
|
// ... I have a query that hasn't finished executing (doesn't matter what)
|
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, true, false, workspaceService);
|
|
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
|
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
|
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
|
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
|
queryService.ActiveQueries[Common.OwnerUri].Batches[0].ResultSets[0].hasBeenRead = false;
|
|
|
|
// ... And I then ask for a valid set of results from it
|
|
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
|
QueryExecuteSubsetResult result = null;
|
|
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
|
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
|
|
|
// Then:
|
|
// ... I should get an error result
|
|
// ... There should not be rows
|
|
// ... There should not be any error calls
|
|
VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never());
|
|
Assert.NotNull(result.Message);
|
|
Assert.Null(result.ResultSubset);
|
|
}
|
|
|
|
[Fact]
|
|
public async void SubsetServiceOutOfRangeSubsetTest()
|
|
{
|
|
// If:
|
|
// ... I have a query that doesn't have any result sets
|
|
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
|
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
|
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
|
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
|
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
|
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
|
|
|
// ... And I then ask for a set of results from it
|
|
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
|
QueryExecuteSubsetResult result = null;
|
|
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
|
queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object).Wait();
|
|
|
|
// Then:
|
|
// ... I should get an error result
|
|
// ... There should not be rows
|
|
// ... There should not be any error calls
|
|
VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never());
|
|
Assert.NotNull(result.Message);
|
|
Assert.Null(result.ResultSubset);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mocking
|
|
|
|
private static Mock<RequestContext<QueryExecuteSubsetResult>> GetQuerySubsetResultContextMock(
|
|
Action<QueryExecuteSubsetResult> resultCallback,
|
|
Action<object> errorCallback)
|
|
{
|
|
return RequestContextMocks.Create(resultCallback)
|
|
.AddErrorHandling(errorCallback);
|
|
}
|
|
|
|
private static void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls,
|
|
Times sendErrorCalls)
|
|
{
|
|
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteSubsetResult>()), sendResultCalls);
|
|
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|