mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-19 01:25:40 -05:00
Feat/result streaming - Fix for issue #746 in toolsservice and issue Microsoft/azuredatastudio#3348 (#753)
* fix for issues 746 & azuredatastudio issue 3348 * test coverage improvement for results streaming * addressed minor review comments * adding generated file test/CodeCoverage/package-lock.json to workaround code coverage issue.
This commit is contained in:
@@ -360,6 +360,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
//
|
||||
Validate.IsNotNull(nameof(dbDataReader), dbDataReader);
|
||||
|
||||
Task availableTask = null;
|
||||
try
|
||||
{
|
||||
// Verify the request hasn't been cancelled
|
||||
@@ -386,34 +387,37 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Mark that read of result has started
|
||||
//
|
||||
hasStartedRead = true;
|
||||
|
||||
// Invoke the SendCurrentResults() asynchronously that will send the results available notification
|
||||
// and also trigger the timer to send periodic updates.
|
||||
//
|
||||
availableTask = SendCurrentResults();
|
||||
|
||||
while (await dataReader.ReadAsync(cancellationToken))
|
||||
{
|
||||
fileOffsets.Add(totalBytesWritten);
|
||||
totalBytesWritten += fileWriter.WriteRow(dataReader);
|
||||
|
||||
// If we have never triggered the timer to start sending the results available/updated notification
|
||||
// then: Trigger the timer to start sending results update notification
|
||||
//
|
||||
if (LastUpdatedSummary == null)
|
||||
{
|
||||
// Invoke the timer to send available/update result set notification immediately
|
||||
//
|
||||
resultsTimer.Change(0, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
CheckForIsJson();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
hasCompletedRead = true; // set the flag to indicate that we are done reading
|
||||
|
||||
// Make a final call to ResultUpdated by invoking the timer to send update result set notification immediately
|
||||
// set the flag to indicate that we are done reading
|
||||
//
|
||||
resultsTimer.Change(0, Timeout.Infinite);
|
||||
hasCompletedRead = true;
|
||||
|
||||
// await the completion of available notification in case it is not already done before proceeding
|
||||
//
|
||||
await availableTask;
|
||||
|
||||
// Make a final call to SendCurrentResults() and await its completion. If the previously scheduled task already took care of latest status send then this should be a no-op
|
||||
//
|
||||
await SendCurrentResults();
|
||||
|
||||
|
||||
// and finally:
|
||||
// Make a call to send ResultCompletion and await for it to Complete
|
||||
// Make a call to send ResultCompletion and await its completion. This is just for backward compatibility with older protocol
|
||||
//
|
||||
await (ResultCompletion?.Invoke(this) ?? Task.CompletedTask);
|
||||
}
|
||||
@@ -613,30 +617,52 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// Sends the ResultsUpdated message if the number of rows has changed since last send.
|
||||
/// </summary>
|
||||
/// <param name="stateInfo"></param>
|
||||
private async void SendResultAvailableOrUpdated (object stateInfo = null)
|
||||
private void SendResultAvailableOrUpdated (object stateInfo = null)
|
||||
{
|
||||
// Make the call to send current results and synchronously wait for it to finish
|
||||
//
|
||||
SendCurrentResults().Wait();
|
||||
}
|
||||
|
||||
private async Task SendCurrentResults()
|
||||
{
|
||||
// await for previous task to complete before proceeding
|
||||
//
|
||||
await ResultsTask;
|
||||
|
||||
ResultSet currentResultSetSnapshot = (ResultSet) MemberwiseClone();
|
||||
if (LastUpdatedSummary == null) // We need to send results available message.
|
||||
if (LastUpdatedSummary == null) // We need to send results available message.
|
||||
{
|
||||
// Fire off results Available task and await for it complete
|
||||
// Fire off results Available task and await it
|
||||
//
|
||||
await (ResultAvailable?.Invoke(currentResultSetSnapshot) ?? Task.CompletedTask);
|
||||
ResultAvailable = null; // set this to null as we need to call ResultAvailable only once
|
||||
ResultsTask = (ResultAvailable?.Invoke(currentResultSetSnapshot) ?? Task.CompletedTask);
|
||||
await ResultsTask;
|
||||
}
|
||||
else // We need to send results updated message.
|
||||
else if (LastUpdatedSummary.Complete) // If last result summary sent had already set the Complete flag
|
||||
{
|
||||
// If there has been no change in rowCount since last update and we are not done yet then log and increase the timer duration
|
||||
// We do not need to do anything except that make sure that RowCount has not update since last send.
|
||||
Debug.Assert(LastUpdatedSummary.RowCount == currentResultSetSnapshot.RowCount, $"Already reported rows should be equal to current RowCount, if had already sent completion flag as set in last message, countReported:{LastUpdatedSummary.RowCount}, current total row count: {currentResultSetSnapshot.RowCount}, row count override: {currentResultSetSnapshot.rowCountOverride}, this.rowCountOverride: {this.rowCountOverride} and this.RowCount: {this.RowCount}, LastUpdatedSummary: {LastUpdatedSummary}");
|
||||
}
|
||||
else // We need to send results updated message.
|
||||
{
|
||||
// Previously reported rows should be less than or equal to current number of rows about to be reported
|
||||
//
|
||||
if (!currentResultSetSnapshot.hasCompletedRead && LastUpdatedSummary.RowCount == currentResultSetSnapshot.RowCount)
|
||||
Debug.Assert(LastUpdatedSummary.RowCount <= currentResultSetSnapshot.RowCount, $"Already reported rows should less than or equal to current total RowCount, countReported:{LastUpdatedSummary.RowCount}, current total row count: {currentResultSetSnapshot.RowCount}, row count override: {currentResultSetSnapshot.rowCountOverride}, this.rowCountOverride: {this.rowCountOverride} and this.RowCount: {this.RowCount}, LastUpdatedSummary: {LastUpdatedSummary}");
|
||||
|
||||
// If there has been no change in rowCount since last update and we have not yet completed read then log and increase the timer duration
|
||||
//
|
||||
if (!currentResultSetSnapshot.hasCompletedRead &&
|
||||
LastUpdatedSummary.RowCount == currentResultSetSnapshot.RowCount)
|
||||
{
|
||||
Logger.Write(TraceEventType.Warning, $"The result set:{Summary} has not made any progress in last {ResultTimerInterval} milliseconds and the read of resultset is not completed yet!");
|
||||
Logger.Write(TraceEventType.Warning,
|
||||
$"The result set:{Summary} has not made any progress in last {ResultTimerInterval} milliseconds and the read of this result set is not yet complete!");
|
||||
ResultsIntervalMultiplier++;
|
||||
}
|
||||
|
||||
// Fire off results updated task and await for it complete
|
||||
// Fire off results updated task and await it
|
||||
//
|
||||
await (ResultUpdated?.Invoke(currentResultSetSnapshot) ?? Task.CompletedTask);
|
||||
|
||||
ResultsTask = (ResultUpdated?.Invoke(currentResultSetSnapshot) ?? Task.CompletedTask);
|
||||
await ResultsTask;
|
||||
}
|
||||
|
||||
// Update the LastUpdatedSummary to be the value captured in current snapshot
|
||||
@@ -644,14 +670,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
LastUpdatedSummary = currentResultSetSnapshot.Summary;
|
||||
|
||||
// Setup timer for the next callback
|
||||
//
|
||||
if (currentResultSetSnapshot.hasCompletedRead)
|
||||
{
|
||||
//If we have already completed reading then we are done and we do not need to send any more updates. Switch off timer.
|
||||
// If we have already completed reading then we are done and we do not need to send any more updates. Switch off timer.
|
||||
//
|
||||
resultsTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have not yet completed reading then set the timer so this method gets called again after ResultTimerInterval milliseconds
|
||||
//
|
||||
resultsTimer.Change(ResultTimerInterval, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
@@ -662,6 +691,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
internal ResultSetSummary LastUpdatedSummary { get; set; } = null;
|
||||
|
||||
internal Task ResultsTask { get; set; } = Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// If the result set represented by this class corresponds to a single XML
|
||||
/// column that contains results of "for xml" query, set isXml = true
|
||||
|
||||
3805
test/CodeCoverage/package-lock.json
generated
Normal file
3805
test/CodeCoverage/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,19 +10,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common
|
||||
public class MemoryFileSystem
|
||||
{
|
||||
|
||||
public static IFileStreamFactory GetFileStreamFactory()
|
||||
public static IFileStreamFactory GetFileStreamFactory(int sizeFactor=1)
|
||||
{
|
||||
return GetFileStreamFactory(new ConcurrentDictionary<string, byte[]>());
|
||||
return GetFileStreamFactory(new ConcurrentDictionary<string, byte[]>(), sizeFactor);
|
||||
}
|
||||
|
||||
public static IFileStreamFactory GetFileStreamFactory(ConcurrentDictionary<string, byte[]> storage)
|
||||
public static IFileStreamFactory GetFileStreamFactory(ConcurrentDictionary<string, byte[]> storage, int sizeFactor=1)
|
||||
{
|
||||
Mock<IFileStreamFactory> mock = new Mock<IFileStreamFactory>();
|
||||
mock.Setup(fsf => fsf.CreateFile())
|
||||
.Returns(() =>
|
||||
{
|
||||
string fileName = Guid.NewGuid().ToString();
|
||||
storage.TryAdd(fileName, new byte[8192]);
|
||||
storage.TryAdd(fileName, new byte[8192 * sizeFactor]);
|
||||
return fileName;
|
||||
});
|
||||
mock.Setup(fsf => fsf.GetReader(It.IsAny<string>()))
|
||||
|
||||
@@ -23,6 +23,7 @@ using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using HostingProtocol = Microsoft.SqlTools.Hosting.Protocol;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
@@ -40,8 +41,20 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
|
||||
public const int StandardRows = 5;
|
||||
|
||||
public const int ZeroRows = 0;
|
||||
|
||||
public const int HundredRows = 100;
|
||||
|
||||
public const int ThousandRows = 1000;
|
||||
|
||||
public const int HundredKRows = 100000;
|
||||
|
||||
public const int MillionRows = 1000000;
|
||||
|
||||
public const int TenMillionRows = 10000000;
|
||||
|
||||
public const int HundredMillionRows = 100000000;
|
||||
|
||||
public const SelectionData WholeDocument = null;
|
||||
|
||||
public static readonly ConnectionDetails StandardConnectionDetails = new ConnectionDetails
|
||||
@@ -55,18 +68,53 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
public static readonly SelectionData SubsectionDocument = new SelectionData(0, 0, 2, 2);
|
||||
|
||||
#endregion
|
||||
public static TestResultSet ZeroRowTestResultSet => new TestResultSet(StandardColumns, ZeroRows);
|
||||
|
||||
public static TestResultSet[] ZeroRowTestDataSet => new[] { ZeroRowTestResultSet };
|
||||
|
||||
public static TestResultSet StandardTestResultSet => new TestResultSet(StandardColumns, StandardRows);
|
||||
|
||||
private static readonly Lazy<TestResultSet> TestResultSet = new Lazy<TestResultSet>(new TestResultSet(StandardColumns, MillionRows));
|
||||
public static TestResultSet[] StandardTestDataSet => new[] { StandardTestResultSet };
|
||||
|
||||
public static TestResultSet MillionRowTestResultSet => TestResultSet.Value;
|
||||
|
||||
private static readonly Lazy<TestResultSet[]> MillionRowResultSet = new Lazy<TestResultSet[]>(new[]{MillionRowTestResultSet});
|
||||
public static TestResultSet HundredRowTestResultSet => new TestResultSet(StandardColumns, HundredRows);
|
||||
|
||||
public static TestResultSet[] StandardTestDataSet => new [] {StandardTestResultSet};
|
||||
public static TestResultSet[] HundredRowTestDataSet => new[] { HundredRowTestResultSet };
|
||||
|
||||
public static TestResultSet[] MillionRowTestDataSet => MillionRowResultSet.Value;
|
||||
|
||||
public static TestResultSet ThousandRowTestResultSet => new TestResultSet(StandardColumns, ThousandRows);
|
||||
|
||||
public static TestResultSet[] ThousandRowTestDataSet => new[] { ThousandRowTestResultSet };
|
||||
|
||||
private static readonly Lazy<TestResultSet> HundredKRowTestResultSetLazy = new Lazy<TestResultSet>(new TestResultSet(StandardColumns, HundredKRows));
|
||||
|
||||
public static TestResultSet HundredKRowTestResultSet => HundredKRowTestResultSetLazy.Value;
|
||||
|
||||
private static readonly Lazy<TestResultSet[]> HundredKRowTestDataSetLazy = new Lazy<TestResultSet[]>(new[] { HundredKRowTestResultSet });
|
||||
|
||||
public static TestResultSet[] HundredKRowTestDataSet => HundredKRowTestDataSetLazy.Value;
|
||||
|
||||
|
||||
private static readonly Lazy<TestResultSet> MillionRowTestResultSetLazy = new Lazy<TestResultSet>(new TestResultSet(StandardColumns, MillionRows));
|
||||
|
||||
public static TestResultSet MillionRowTestResultSet => MillionRowTestResultSetLazy.Value;
|
||||
|
||||
private static readonly Lazy<TestResultSet[]> MillionRowTestDataSetLazy = new Lazy<TestResultSet[]>(new[]{ MillionRowTestResultSet });
|
||||
|
||||
public static TestResultSet[] MillionRowTestDataSet => MillionRowTestDataSetLazy.Value;
|
||||
|
||||
public static IEnumerable<TestResultSet> TestResultSetsEnumeration
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return StandardTestResultSet;
|
||||
yield return HundredRowTestResultSet;
|
||||
yield return ThousandRowTestResultSet;
|
||||
yield return ZeroRowTestResultSet;
|
||||
yield return HundredKRowTestResultSet;
|
||||
yield return MillionRowTestResultSet;
|
||||
}
|
||||
}
|
||||
|
||||
public static TestResultSet[] ExecutionPlanTestDataSet
|
||||
{
|
||||
@@ -230,7 +278,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
bool throwOnExecute,
|
||||
bool throwOnRead,
|
||||
WorkspaceService<SqlToolsSettings> workspaceService,
|
||||
out ConcurrentDictionary<string, byte[]> storage)
|
||||
out ConcurrentDictionary<string, byte[]> storage,
|
||||
int sizeFactor=1)
|
||||
{
|
||||
// Create a place for the temp "files" to be written
|
||||
storage = new ConcurrentDictionary<string, byte[]>();
|
||||
@@ -244,7 +293,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
.OutCallback((string owner, out ConnectionInfo connInfo) => connInfo = isConnected ? ci : null)
|
||||
.Returns(isConnected);
|
||||
|
||||
return new QueryExecutionService(connectionService.Object, workspaceService) { BufferFileStreamFactory = MemoryFileSystem.GetFileStreamFactory(storage) };
|
||||
return new QueryExecutionService(connectionService.Object, workspaceService) { BufferFileStreamFactory = MemoryFileSystem.GetFileStreamFactory(storage, sizeFactor) };
|
||||
}
|
||||
|
||||
public static QueryExecutionService GetPrimedExecutionService(
|
||||
@@ -252,10 +301,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
bool isConnected,
|
||||
bool throwOnExecute,
|
||||
bool throwOnRead,
|
||||
WorkspaceService<SqlToolsSettings> workspaceService)
|
||||
WorkspaceService<SqlToolsSettings> workspaceService,
|
||||
int sizeFactor=1)
|
||||
{
|
||||
ConcurrentDictionary<string, byte[]> storage;
|
||||
return GetPrimedExecutionService(data, isConnected, throwOnExecute, throwOnRead, workspaceService, out storage);
|
||||
return GetPrimedExecutionService(data, isConnected, throwOnExecute, throwOnRead, workspaceService, out storage, sizeFactor);
|
||||
}
|
||||
|
||||
public static WorkspaceService<SqlToolsSettings> GetPrimedWorkspaceService(string query)
|
||||
|
||||
@@ -55,13 +55,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
/// <summary>
|
||||
/// Read to End test
|
||||
/// DevNote: known to fail randomly sometimes due to random race condition
|
||||
/// when multiple tests are run simultaneously.
|
||||
/// Rerunning the test alone always passes.
|
||||
/// Tracking this issue with:https://github.com/Microsoft/sqltoolsservice/issues/746
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadToEndSuccess()
|
||||
/// <param name="testDataSet"></param>
|
||||
[Theory]
|
||||
[MemberData(nameof(ReadToEndSuccessData), parameters: 6)]
|
||||
public async Task ReadToEndSuccess(TestResultSet[] testDataSet)
|
||||
{
|
||||
// Setup: Create a results Available callback for result set
|
||||
//
|
||||
@@ -98,13 +96,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// If:
|
||||
// ... I create a new resultset with a valid db data reader that has data
|
||||
// ... and I read it to the end
|
||||
DbDataReader mockReader = GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
DbDataReader mockReader = GetReader(testDataSet, false, Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory(testDataSet[0].Rows.Count/Common.StandardRows + 1);
|
||||
ResultSet resultSet = new ResultSet(Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
resultSet.ResultAvailable += AvailableCallback;
|
||||
resultSet.ResultUpdated += UpdatedCallback;
|
||||
resultSet.ResultCompletion += CompleteCallback;
|
||||
resultSet.ReadResultToEnd(mockReader, CancellationToken.None).Wait();
|
||||
await resultSet.ReadResultToEnd(mockReader, CancellationToken.None);
|
||||
|
||||
Thread.Yield();
|
||||
resultSet.ResultAvailable -= AvailableCallback;
|
||||
@@ -116,18 +114,18 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// ... There should be rows to read back
|
||||
Assert.NotNull(resultSet.Columns);
|
||||
Assert.Equal(Common.StandardColumns, resultSet.Columns.Length);
|
||||
Assert.Equal(Common.StandardRows, resultSet.RowCount);
|
||||
Assert.Equal(testDataSet[0].Rows.Count, resultSet.RowCount);
|
||||
|
||||
// ... The summary should have the same info
|
||||
Assert.NotNull(resultSet.Summary.ColumnInfo);
|
||||
Assert.Equal(Common.StandardColumns, resultSet.Summary.ColumnInfo.Length);
|
||||
Assert.Equal(Common.StandardRows, resultSet.Summary.RowCount);
|
||||
Assert.Equal(testDataSet[0].Rows.Count, resultSet.Summary.RowCount);
|
||||
|
||||
// and:
|
||||
// disabling verification due to: https://github.com/Microsoft/sqltoolsservice/issues/746
|
||||
//
|
||||
// VerifyReadResultToEnd(resultSet, resultSummaryFromAvailableCallback, resultSummaryFromCompleteCallback, resultSummariesFromUpdatedCallback);
|
||||
VerifyReadResultToEnd(resultSet, resultSummaryFromAvailableCallback, resultSummaryFromCompleteCallback, resultSummariesFromUpdatedCallback);
|
||||
}
|
||||
public static IEnumerable<object[]> ReadToEndSuccessData(int numTests) => Common.TestResultSetsEnumeration.Select(r => new object[] { new TestResultSet[] { r } }).Take(numTests);
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CallMethodWithoutReadingData))]
|
||||
@@ -219,24 +217,34 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
// Setup: Create a results Available callback for result set
|
||||
//
|
||||
ResultSetSummary resultSummaryFromAvailableCallback = null;
|
||||
Task AvailableCallback(ResultSet r)
|
||||
{
|
||||
Debug.WriteLine($"available result notification sent, result summary was: {r.Summary}");
|
||||
resultSummaryFromAvailableCallback = r.Summary;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
// Setup: Create a results updated callback for result set
|
||||
//
|
||||
List<ResultSetSummary> resultSummariesFromUpdatedCallback = new List<ResultSetSummary>();
|
||||
|
||||
Task UpdatedCallback(ResultSet r)
|
||||
{
|
||||
Debug.WriteLine($"updated result notification sent, result summary was: {r.Summary}");
|
||||
resultSummariesFromUpdatedCallback.Add(r.Summary);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Setup: Create a results complete callback for result set
|
||||
//
|
||||
ResultSetSummary resultSummaryFromCompleteCallback = null;
|
||||
Task CompleteCallback(ResultSet r)
|
||||
{
|
||||
Debug.WriteLine($"Completed result notification sent, result summary was: {r.Summary}");
|
||||
Assert.True(r.Summary.Complete);
|
||||
resultSummaryFromCompleteCallback = r.Summary;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -265,6 +273,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
Assert.Equal(1, resultSet.Columns.Length);
|
||||
Assert.Equal(1, resultSet.RowCount);
|
||||
|
||||
|
||||
// and:
|
||||
//
|
||||
VerifyReadResultToEnd(resultSet, resultSummaryFromAvailableCallback, resultSummaryFromCompleteCallback, resultSummariesFromUpdatedCallback);
|
||||
|
||||
// If:
|
||||
// ... I attempt to read back the results
|
||||
// Then:
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
[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);
|
||||
@@ -86,7 +86,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
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 queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
var queryText = queryService.GetSqlText(queryParams);
|
||||
|
||||
// Then: The text should match the constructed query
|
||||
@@ -103,7 +103,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
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 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
|
||||
@@ -119,7 +119,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
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 queryParams = new ExecuteStringParams { OwnerUri = Constants.OwnerUri, Query = Constants.StandardQuery };
|
||||
var queryText = queryService.GetSqlText(queryParams);
|
||||
|
||||
// Then: The text should match the standard query
|
||||
@@ -246,7 +246,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// ... 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 queryParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri };
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
@@ -266,14 +266,18 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleBatchSingleResultTest()
|
||||
public static IEnumerable<object[]> TestResultSetsData(int numTests) => Common.TestResultSetsEnumeration.Select(r => new object[] { r }).Take(numTests);
|
||||
|
||||
[Xunit.Theory]
|
||||
[MemberData(nameof(TestResultSetsData), parameters: 5)]
|
||||
public async Task QueryExecuteSingleBatchSingleResultTest(TestResultSet testResultSet)
|
||||
{
|
||||
// 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};
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var testDataSet = new[] { testResultSet };
|
||||
var queryService = Common.GetPrimedExecutionService(testDataSet, true, false, false, workspaceService, sizeFactor: testResultSet.Rows.Count / Common.StandardRows + 1);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
List<ResultSetEventParams> collectedResultSetEventParams = new List<ResultSetEventParams>();
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
@@ -296,15 +300,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleBatchMultipleResultTest()
|
||||
[Xunit.Theory]
|
||||
[MemberData(nameof(TestResultSetsData), parameters: 4)]
|
||||
public async Task QueryExecuteSingleBatchMultipleResultTest(TestResultSet testResultSet)
|
||||
{
|
||||
// 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};
|
||||
var testDataSet = new[] { testResultSet, testResultSet };
|
||||
var queryService = Common.GetPrimedExecutionService(testDataSet, true, false, false, workspaceService, sizeFactor: testResultSet.Rows.Count / Common.StandardRows + 1);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
List<ResultSetEventParams> collectedResultSetEventParams = new List<ResultSetEventParams>();
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
@@ -320,7 +325,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
efv.ValidateResultSetSummaries(collectedResultSetEventParams).Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
@@ -333,7 +338,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// ... 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};
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
List<ResultSetEventParams> collectedResultSetEventParams = new List<ResultSetEventParams>();
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
@@ -353,7 +358,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
efv.ValidateResultSetSummaries(collectedResultSetEventParams).Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
@@ -389,7 +394,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// ... 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};
|
||||
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);
|
||||
@@ -417,7 +422,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// ... 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};
|
||||
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);
|
||||
@@ -449,7 +454,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// ... 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 queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
@@ -488,9 +493,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
efv.Validate();
|
||||
|
||||
Assert.Equal(0, queryService.ActiveQueries.Count);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO reenable and make non-flaky
|
||||
// [Fact]
|
||||
public async Task SimpleExecuteVerifyResultsTest()
|
||||
@@ -510,7 +515,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
// wait on the task to finish
|
||||
q.ExecutionTask.Wait();
|
||||
|
||||
|
||||
efv.Validate();
|
||||
|
||||
Assert.Equal(0, queryService.ActiveQueries.Count);
|
||||
@@ -536,9 +541,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
var queries = queryService.ActiveQueries.Values.ToArray();
|
||||
var queryTasks = queries.Select(query => query.ExecutionTask);
|
||||
|
||||
|
||||
await Task.WhenAll(queryTasks);
|
||||
|
||||
|
||||
efv1.Validate();
|
||||
efv2.Validate();
|
||||
|
||||
@@ -561,7 +566,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
public static EventFlowValidator<SimpleExecuteResult> AddSimpleExecuteQueryResultValidator(
|
||||
this EventFlowValidator<SimpleExecuteResult> efv, TestResultSet[] testData)
|
||||
{
|
||||
return efv.AddResultValidation(p =>
|
||||
return efv.AddResultValidation(p =>
|
||||
{
|
||||
Assert.Equal(p.RowCount, testData[0].Rows.Count);
|
||||
});
|
||||
@@ -570,7 +575,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
public static EventFlowValidator<SimpleExecuteResult> AddSimpleExecuteErrorValidator(
|
||||
this EventFlowValidator<SimpleExecuteResult> efv, string expectedMessage)
|
||||
{
|
||||
return efv.AddSimpleErrorValidation((m, e) =>
|
||||
return efv.AddSimpleErrorValidation((m, e) =>
|
||||
{
|
||||
Assert.Equal(m, expectedMessage);
|
||||
});
|
||||
@@ -631,10 +636,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
public static EventFlowValidator<TRequestContext> ValidateResultSetSummaries<TRequestContext>(
|
||||
this EventFlowValidator<TRequestContext> efv, List<ResultSetEventParams> resultSetEventParamList)
|
||||
{
|
||||
string GetResultSetKey(ResultSetSummary summary)
|
||||
{
|
||||
return $"BatchId:{summary.BatchId}, ResultId:{summary.Id}";
|
||||
}
|
||||
string GetResultSetKey(ResultSetSummary summary) => $"BatchId:{summary.BatchId}, ResultId:{summary.Id}";
|
||||
|
||||
// Separate the result set resultSetEventParamsList by batchid, resultsetid and by resultseteventtype.
|
||||
ConcurrentDictionary<string, List<ResultSetEventParams>> resultSetDictionary =
|
||||
@@ -649,7 +651,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
|
||||
foreach (var (key, list) in resultSetDictionary)
|
||||
{
|
||||
ResultSetSummary completeSummary = null, lastResultSetSummary = null;
|
||||
ResultSetSummary completeSummary = null, lastResultSetSummary = null;
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
VerifyResultSummary(key, i, list, ref completeSummary, ref lastResultSetSummary);
|
||||
@@ -681,6 +683,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
switch (resultSetEventParams.GetType().Name)
|
||||
{
|
||||
case nameof(ResultSetAvailableEventParams):
|
||||
// Verify that the availableEvent is the first and only one of this type 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,
|
||||
$"AvailableResultSet was not 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))}"
|
||||
);
|
||||
|
||||
// Save the lastResultSetSummary for this event for other verifications.
|
||||
//
|
||||
lastResultSetSummary = resultSetEventParams.ResultSetSummary;
|
||||
@@ -716,7 +725,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
// Save the completeSummary for this event for other verifications.
|
||||
//
|
||||
completeSummary = resultSetEventParams.ResultSetSummary;
|
||||
|
||||
|
||||
// Verify that the complete flag is set
|
||||
//
|
||||
Assert.True(completeSummary.Complete,
|
||||
|
||||
Reference in New Issue
Block a user