diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 16982606..f9ae8543 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -29,7 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// /// Singleton service instance /// - private static Lazy instance + private static readonly Lazy instance = new Lazy(() => new ConnectionService()); /// @@ -48,11 +48,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// private ISqlConnectionFactory connectionFactory; - private Dictionary ownerToConnectionMap = new Dictionary(); + private readonly Dictionary ownerToConnectionMap = new Dictionary(); - private ConcurrentDictionary ownerToCancellationTokenSourceMap = new ConcurrentDictionary(); + private readonly ConcurrentDictionary ownerToCancellationTokenSourceMap = new ConcurrentDictionary(); - private Object cancellationTokenSourceLock = new Object(); + private readonly object cancellationTokenSourceLock = new object(); /// /// Map from script URIs to ConnectionInfo objects @@ -173,13 +173,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); // try to connect - var response = new ConnectionCompleteParams(); - response.OwnerUri = connectionParams.OwnerUri; + var response = new ConnectionCompleteParams {OwnerUri = connectionParams.OwnerUri}; CancellationTokenSource source = null; try { // build the connection string from the input parameters - string connectionString = ConnectionService.BuildConnectionString(connectionInfo.ConnectionDetails); + string connectionString = BuildConnectionString(connectionInfo.ConnectionDetails); // create a sql connection instance connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString); @@ -261,7 +260,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection // Update with the actual database name in connectionInfo and result // Doing this here as we know the connection is open - expect to do this only on connecting connectionInfo.ConnectionDetails.DatabaseName = connectionInfo.SqlConnection.Database; - response.ConnectionSummary = new ConnectionSummary() + response.ConnectionSummary = new ConnectionSummary { ServerName = connectionInfo.ConnectionDetails.ServerName, DatabaseName = connectionInfo.ConnectionDetails.DatabaseName, @@ -269,7 +268,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection }; // invoke callback notifications - invokeOnConnectionActivities(connectionInfo); + InvokeOnConnectionActivities(connectionInfo); // try to get information about the connected SQL Server instance try @@ -278,7 +277,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection DbConnection connection = reliableConnection != null ? reliableConnection.GetUnderlyingConnection() : connectionInfo.SqlConnection; ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connection); - response.ServerInfo = new Contracts.ServerInfo() + response.ServerInfo = new ServerInfo { ServerMajorVersion = serverInfo.ServerMajorVersion, ServerMinorVersion = serverInfo.ServerMinorVersion, @@ -399,7 +398,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connection.Open(); List results = new List(); - var systemDatabases = new string[] {"master", "model", "msdb", "tempdb"}; + var systemDatabases = new[] {"master", "model", "msdb", "tempdb"}; using (DbCommand command = connection.CreateCommand()) { command.CommandText = "SELECT name FROM sys.databases ORDER BY name ASC"; @@ -473,7 +472,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection try { - RunConnectRequestHandlerTask(connectParams, requestContext); + RunConnectRequestHandlerTask(connectParams); await requestContext.SendResult(true); } catch @@ -482,7 +481,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } } - private void RunConnectRequestHandlerTask(ConnectParams connectParams, RequestContext requestContext) + private void RunConnectRequestHandlerTask(ConnectParams connectParams) { // create a task to connect asynchronously so that other requests are not blocked in the meantime Task.Run(async () => @@ -490,7 +489,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection try { // open connection based on request details - ConnectionCompleteParams result = await ConnectionService.Instance.Connect(connectParams); + ConnectionCompleteParams result = await Instance.Connect(connectParams); await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); } catch (Exception ex) @@ -515,7 +514,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection try { - bool result = ConnectionService.Instance.CancelConnect(cancelParams); + bool result = Instance.CancelConnect(cancelParams); await requestContext.SendResult(result); } catch(Exception ex) @@ -535,7 +534,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection try { - bool result = ConnectionService.Instance.Disconnect(disconnectParams); + bool result = Instance.Disconnect(disconnectParams); await requestContext.SendResult(result); } catch(Exception ex) @@ -556,7 +555,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection try { - ListDatabasesResponse result = ConnectionService.Instance.ListDatabases(listDatabasesParams); + ListDatabasesResponse result = Instance.ListDatabases(listDatabasesParams); await requestContext.SendResult(result); } catch(Exception ex) @@ -579,10 +578,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection /// public static string BuildConnectionString(ConnectionDetails connectionDetails) { - SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder(); - connectionBuilder["Data Source"] = connectionDetails.ServerName; - connectionBuilder["User Id"] = connectionDetails.UserName; - connectionBuilder["Password"] = connectionDetails.Password; + SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder + { + ["Data Source"] = connectionDetails.ServerName, + ["User Id"] = connectionDetails.UserName, + ["Password"] = connectionDetails.Password + }; // Check for any optional parameters if (!string.IsNullOrEmpty(connectionDetails.DatabaseName)) @@ -722,7 +723,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection // Fire a connection changed event ConnectionChangedParams parameters = new ConnectionChangedParams(); - ConnectionSummary summary = (ConnectionSummary)(info.ConnectionDetails); + ConnectionSummary summary = info.ConnectionDetails; parameters.Connection = summary.Clone(); parameters.OwnerUri = ownerUri; ServiceHost.SendEvent(ConnectionChangedNotification.Type, parameters); @@ -741,7 +742,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } } - private void invokeOnConnectionActivities(ConnectionInfo connectionInfo) + private void InvokeOnConnectionActivities(ConnectionInfo connectionInfo) { foreach (var activity in this.onConnectionActivities) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index 38dbca5d..b01fb258 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -88,6 +88,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public event BatchAsyncEventHandler BatchCompletion; + /// + /// 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; + /// /// The text of batch that will be executed /// @@ -155,12 +161,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { get { - return ResultSets.Select((set, index) => new ResultSetSummary() + lock (resultSets) { - ColumnInfo = set.Columns, - Id = index, - RowCount = set.RowCount - }).ToArray(); + return resultSets.Select(set => set.Summary).ToArray(); + } } } @@ -221,7 +225,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution command = sqlConn.GetUnderlyingConnection().CreateCommand(); // Add a handler for when the command completes - SqlCommand sqlCommand = (SqlCommand) command; + SqlCommand sqlCommand = (SqlCommand)command; sqlCommand.StatementCompleted += StatementCompletedHandler; } else @@ -244,6 +248,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution // Execute the command to get back a reader using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken)) { + int resultSetOrdinal = 0; do { // Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries) @@ -253,11 +258,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution } // This resultset has results (ie, SELECT/etc queries) - ResultSet resultSet = new ResultSet(reader, outputFileFactory); - + ResultSet resultSet = new ResultSet(reader, resultSetOrdinal, Id, outputFileFactory); + resultSet.ResultCompletion += ResultSetCompletion; + // Add the result set to the results of the query - resultSets.Add(resultSet); - + lock (resultSets) + { + resultSets.Add(resultSet); + resultSetOrdinal++; + } + // Read until we hit the end of the result set await resultSet.ReadResultToEnd(cancellationToken).ConfigureAwait(false); @@ -318,20 +328,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// A subset of results public Task GetSubset(int resultSetIndex, int startRow, int rowCount) { - // Sanity check to make sure that the batch has finished - if (!HasExecuted) + ResultSet targetResultSet; + lock (resultSets) { - throw new InvalidOperationException(SR.QueryServiceSubsetBatchNotCompleted); - } + // Sanity check to make sure we have valid numbers + if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count) + { + throw new ArgumentOutOfRangeException(nameof(resultSetIndex), + SR.QueryServiceSubsetResultSetOutOfRange); + } - // Sanity check to make sure we have valid numbers - if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count) - { - throw new ArgumentOutOfRangeException(nameof(resultSetIndex), SR.QueryServiceSubsetResultSetOutOfRange); + targetResultSet = resultSets[resultSetIndex]; } // Retrieve the result set - return resultSets[resultSetIndex].GetSubset(startRow, rowCount); + return targetResultSet.GetSubset(startRow, rowCount); } #endregion @@ -431,9 +442,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution if (disposing) { - foreach (ResultSet r in ResultSets) + lock (resultSets) { - r.Dispose(); + foreach (ResultSet r in resultSets) + { + r.Dispose(); + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteResultSetCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteResultSetCompleteNotification.cs new file mode 100644 index 00000000..c8eeb00b --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteResultSetCompleteNotification.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts +{ + public class QueryExecuteResultSetCompleteParams + { + public ResultSetSummary ResultSetSummary { get; set; } + + public string OwnerUri { get; set; } + } + + public class QueryExecuteResultSetCompleteEvent + { + public static readonly + EventType Type = + EventType.Create("query/resultSetComplete"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs index c8705d8b..e6fc8691 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs @@ -15,6 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts /// public int Id { get; set; } + /// + /// The ID of the batch set within the query + /// + public int BatchId { get; set; } + /// /// The number of rows that was returned with the resultset /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs index fb915be7..56e3b7d5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs @@ -92,25 +92,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution Batches = batchSelection.ToArray(); } - #region Properties - - /// - /// Delegate type for callback when a query completes or fails - /// - /// The query that completed - public delegate Task QueryAsyncEventHandler(Query q); + #region Events /// /// Event to be called when a batch is completed. /// public event Batch.BatchAsyncEventHandler BatchCompleted; - /// - /// Delegate type for callback when a query connection fails - /// - /// Message to return - public delegate Task QueryAsyncErrorEventHandler(string message); - /// /// Callback for when the query has completed successfully /// @@ -126,6 +114,27 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public event QueryAsyncErrorEventHandler QueryConnectionException; + /// + /// Event to be called when a resultset has completed. + /// + public event ResultSet.ResultSetAsyncEventHandler ResultSetCompleted; + + #endregion + + #region Properties + + /// + /// Delegate type for callback when a query completes or fails + /// + /// The query that completed + public delegate Task QueryAsyncEventHandler(Query q); + + /// + /// Delegate type for callback when a query connection fails + /// + /// Message to return + public delegate Task QueryAsyncErrorEventHandler(string message); + /// /// The batches underneath this query /// @@ -146,6 +155,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution } } + /// + /// Storage for the async task for execution. Set as internal in order to await completion + /// in unit tests. + /// internal Task ExecutionTask { get; private set; } /// @@ -242,11 +255,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { await conn.OpenAsync(); } - catch(Exception exception) + catch (Exception exception) { - this.HasExecuted = true; + this.HasExecuted = true; if (QueryConnectionException != null) - { + { await QueryConnectionException(exception.Message); } return; @@ -265,6 +278,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution foreach (Batch b in Batches) { 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 71c51336..0d666c56 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -437,7 +437,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution }; await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams); }; - query.QueryCompleted += callback; query.QueryFailed += callback; query.QueryConnectionException += errorCallback; @@ -454,6 +453,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution }; query.BatchCompleted += batchCallback; + // Setup the ResultSet completion callback + ResultSet.ResultSetAsyncEventHandler resultCallback = async r => + { + QueryExecuteResultSetCompleteParams eventParams = new QueryExecuteResultSetCompleteParams + { + ResultSetSummary = r.Summary, + OwnerUri = executeParams.OwnerUri + }; + await requestContext.SendEvent(QueryExecuteResultSetCompleteEvent.Type, eventParams); + }; + query.ResultSetCompleted += resultCallback; + // Launch this as an asynchronous task query.Execute(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs index a74a38d1..e3ecb890 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs @@ -59,9 +59,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution private readonly IFileStreamFactory fileStreamFactory; /// - /// Whether or not the result set has been read in from the database + /// Whether or not the result set has been read in from the database, + /// set as internal in order to fake value in unit tests /// - private bool hasBeenRead; + internal bool hasBeenRead; /// /// Whether resultSet is a 'for xml' or 'for json' result @@ -74,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution private readonly string outputFileName; /// - /// Whether the resultSet is in the process of being disposed + /// All save tasks currently saving this ResultSet /// private readonly ConcurrentDictionary saveTasks; @@ -84,13 +85,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Creates a new result set and initializes its state /// /// The reader from executing a query + /// The ID of the resultset, the ordinal of the result within the batch + /// The ID of the batch, the ordinal of the batch within the query /// Factory for creating a reader/writer - public ResultSet(DbDataReader reader, IFileStreamFactory factory) + public ResultSet(DbDataReader reader, int ordinal, int batchOrdinal, IFileStreamFactory factory) { // Sanity check to make sure we got a reader Validate.IsNotNull(nameof(reader), SR.QueryServiceResultSetReaderNull); dataReader = new StorageDataReader(reader); + Id = ordinal; + BatchId = batchOrdinal; // Initialize the storage outputFileName = factory.CreateFile(); @@ -104,6 +109,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution #region Properties + /// + /// Asynchronous handler for when a resultset has completed + /// + /// The result set that completed + public delegate Task ResultSetAsyncEventHandler(ResultSet resultSet); + + /// + /// Event that will be called when the result set has completed execution + /// + public event ResultSetAsyncEventHandler ResultCompletion; + /// /// Whether the resultSet is in the process of being disposed /// @@ -115,6 +131,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public DbColumnWrapper[] Columns { get; private set; } + /// + /// ID of the result set, relative to the batch + /// + public int Id { get; private set; } + + /// + /// ID of the batch set, relative to the query + /// + public int BatchId { get; private set; } + /// /// Maximum number of characters to store for a field /// @@ -130,6 +156,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public long RowCount { get; private set; } + /// + /// Generates a summary of this result set + /// + public ResultSetSummary Summary + { + get + { + return new ResultSetSummary + { + ColumnInfo = Columns, + Id = Id, + BatchId = BatchId, + RowCount = RowCount + }; + } + } + #endregion #region Public Methods @@ -170,9 +213,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution if (isSingleColumnXmlJsonResultSet) { // Iterate over all the rows and process them into a list of string builders + // ReSharper disable once AccessToDisposedClosure The lambda is used immediately in string.Join call IEnumerable rowValues = fileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue); rows = new[] { new[] { string.Join(string.Empty, rowValues) } }; - } else { @@ -180,8 +223,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution IEnumerable rowOffsets = fileOffsets.Skip(startRow).Take(rowCount); // Iterate over the rows we need and process them into output - rows = rowOffsets.Select(rowOffset => - fileStreamReader.ReadRow(rowOffset, Columns).Select(cell => cell.DisplayValue).ToArray()) + // ReSharper disable once AccessToDisposedClosure The lambda is used immediately in .ToArray call + rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns) + .Select(cell => cell.DisplayValue).ToArray()) .ToArray(); } @@ -201,33 +245,41 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Cancellation token for cancelling the query public async Task ReadResultToEnd(CancellationToken cancellationToken) { - // Mark that result has been read - hasBeenRead = true; - - // Open a writer for the file - using (IFileStreamWriter fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxXmlCharsToStore)) + try { - // If we can initialize the columns using the column schema, use that - if (!dataReader.DbDataReader.CanGetColumnSchema()) + // Mark that result has been read + hasBeenRead = true; + + // Open a writer for the file + var fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxCharsToStore); + using (fileWriter) { - throw new InvalidOperationException(SR.QueryServiceResultSetNoColumnSchema); + // If we can initialize the columns using the column schema, use that + if (!dataReader.DbDataReader.CanGetColumnSchema()) + { + throw new InvalidOperationException(SR.QueryServiceResultSetNoColumnSchema); + } + Columns = dataReader.Columns; + long currentFileOffset = 0; + + while (await dataReader.ReadAsync(cancellationToken)) + { + RowCount++; + fileOffsets.Add(currentFileOffset); + currentFileOffset += fileWriter.WriteRow(dataReader); + } } - Columns = dataReader.Columns; - - long currentFileOffset = 0; - while (await dataReader.ReadAsync(cancellationToken)) + // Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata + SingleColumnXmlJsonResultSet(); + } + finally + { + // Fire off a result set completion event if we have one + if (ResultCompletion != null) { - // Store the beginning of the row - long rowStart = currentFileOffset; - currentFileOffset += fileWriter.WriteRow(dataReader); - - // Add the row to the list of rows we have only if the row was successfully written - RowCount++; - fileOffsets.Add(rowStart); + await ResultCompletion(this); } } - // Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata - SingleColumnXmlJsonResultSet(); } #endregion @@ -284,10 +336,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// If the result set represented by this class corresponds to a single JSON /// column that contains results of "for json" query, set isJson = true /// - private void SingleColumnXmlJsonResultSet() { + private void SingleColumnXmlJsonResultSet() + { if (Columns?.Length == 1 && RowCount != 0) - { + { if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal)) { Columns[0].IsXml = true; @@ -299,7 +352,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution Columns[0].IsJson = true; isSingleColumnXmlJsonResultSet = true; RowCount = 1; - } + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs index 3be28f64..9bc04a98 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs @@ -91,7 +91,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution } [Fact] - public async void CancelNonExistantTest() + public async Task CancelNonExistantTest() { var workspaceService = new Mock>(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs index c136b2ba..7923a554 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs @@ -32,7 +32,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public const string NoOpQuery = "-- No ops here, just us chickens."; - public const int Ordinal = 0; + public const int Ordinal = 100; // We'll pick something other than default(int) public const string OwnerUri = "testFile"; @@ -161,7 +161,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var connectionMock = new Mock { CallBase = true }; connectionMock.Protected() .Setup("CreateDbCommand") - .Returns(CreateTestCommand(data, throwOnRead)); + .Returns(() => CreateTestCommand(data, throwOnRead)); connectionMock.Setup(dbc => dbc.Open()) .Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Open)); connectionMock.Setup(dbc => dbc.Close()) @@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { var mockFactory = new Mock(); mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny())) - .Returns(CreateTestConnection(data, throwOnRead)); + .Returns(() => CreateTestConnection(data, throwOnRead)); return mockFactory.Object; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs index bc706bb3..61aee8b8 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs @@ -28,7 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var mockDataReader = Common.CreateTestConnection(null, false).CreateCommand().ExecuteReaderAsync().Result; // If: I setup a single resultset and then dispose it - ResultSet rs = new ResultSet(mockDataReader, mockFileStreamFactory.Object); + ResultSet rs = new ResultSet(mockDataReader, Common.Ordinal, Common.Ordinal, mockFileStreamFactory.Object); rs.Dispose(); // Then: The file that was created should have been deleted @@ -101,6 +101,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... We need a query service var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); + // If: // ... I execute some bogus query var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri }; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs index b36ea6b2..b5b0992a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs @@ -42,6 +42,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... It's ordinal ID should be what I set it to Assert.Equal(Common.Ordinal, batch.Id); + + // ... 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.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); } [Fact] @@ -49,18 +59,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution { // Setup: Create a callback for batch completion BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => + Batch.BatchAsyncEventHandler batchCallback = b => { - completionCallbackFired = true; batchSummaryFromCallback = b.Summary; return Task.FromResult(0); }; + // ... Create a callback for result completion + bool resultCallbackFired = false; + ResultSet.ResultSetAsyncEventHandler resultSetCallback = r => + { + resultCallbackFired = true; + return Task.FromResult(0); + }; + // 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 += callback; + batch.BatchCompletion += batchCallback; + batch.ResultSetCompletion += resultSetCallback; batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait(); // Then: @@ -80,8 +97,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.Equal(1, batch.ResultMessages.Count()); // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); Assert.NotNull(batchSummaryFromCallback); + + // ... 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] @@ -92,18 +121,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // Setup: Create a callback for batch completion BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => + Batch.BatchAsyncEventHandler batchCallback = b => { - completionCallbackFired = true; batchSummaryFromCallback = b.Summary; return Task.FromResult(0); }; + // ... Create a callback for result set completion + bool resultCallbackFired = false; + ResultSet.ResultSetAsyncEventHandler resultSetCallback = r => + { + resultCallbackFired = true; + return Task.FromResult(0); + }; + // If I execute a query that should get one result set var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); - batch.BatchCompletion += callback; + batch.BatchCompletion += batchCallback; + batch.ResultSetCompletion += resultSetCallback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -112,7 +148,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.False(batch.HasError, "The batch should not have an error"); // ... There should be exactly one result set - Assert.Equal(resultSets, batch.ResultSets.Count()); + Assert.Equal(resultSets, batch.ResultSets.Count); Assert.Equal(resultSets, batch.ResultSummaries.Length); // ... Inside the result set should be with 5 rows @@ -127,8 +163,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.Equal(resultSets, batch.ResultMessages.Count()); // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); Assert.NotNull(batchSummaryFromCallback); + + // ... The callback for resultset completion should have been fired + Assert.True(resultCallbackFired); // We only want to validate that it happened, validation of the + // summary is done in result set tests } [Fact] @@ -140,18 +179,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // Setup: Create a callback for batch completion BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => + Batch.BatchAsyncEventHandler batchCallback = b => { - completionCallbackFired = true; batchSummaryFromCallback = b.Summary; return Task.FromResult(0); }; + // ... Create a callback for resultset completion + int resultSummaryCount = 0; + ResultSet.ResultSetAsyncEventHandler resultSetCallback = r => + { + resultSummaryCount++; + return Task.FromResult(0); + }; + // If I execute a query that should get two result sets var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); - batch.BatchCompletion += callback; + batch.BatchCompletion += batchCallback; + batch.ResultSetCompletion += resultSetCallback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -184,8 +230,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution } // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); Assert.NotNull(batchSummaryFromCallback); + + // ... The callback for result set completion should have been fired + Assert.Equal(2, resultSummaryCount); } [Fact] @@ -193,20 +241,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution { // Setup: Create a callback for batch completion BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => + Batch.BatchAsyncEventHandler batchCallback = b => { - completionCallbackFired = true; batchSummaryFromCallback = b.Summary; return Task.FromResult(0); }; + // ... Create a callback that will fail the test if it's called + ResultSet.ResultSetAsyncEventHandler resultSetCallback = r => + { + throw new Exception("ResultSet callback was called when it should not have been."); + }; + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); // 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 += callback; + batch.BatchCompletion += batchCallback; + batch.ResultSetCompletion += resultSetCallback; batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); // Then: @@ -222,14 +275,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotEmpty(batch.ResultMessages); // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); Assert.NotNull(batchSummaryFromCallback); } [Fact] public async Task BatchExecuteExecuted() { - // Setup: Create a callback for batch completion + ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); + + // If I execute a batch + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); + batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); + + // Then: + // ... It should have executed without error + 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 => @@ -239,26 +303,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution return Task.FromResult(0); }; - ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); - - // If I execute a batch - var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); - batch.BatchCompletion += callback; - batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); - - // Then: - // ... It should have executed without error - Assert.True(batch.HasExecuted, "The batch should have been marked executed."); - Assert.False(batch.HasError, "The batch should not have an error"); - - // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); - Assert.NotNull(batchSummaryFromCallback); - // If I execute it again // Then: // ... It should throw an invalid operation exception + batch.BatchCompletion += callback; await Assert.ThrowsAsync(() => batch.Execute(GetConnection(ci), CancellationToken.None)); @@ -267,6 +315,10 @@ 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 23aefba6..c3ff025b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.SqlContext; @@ -61,11 +62,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution [Fact] public void QueryExecuteSingleBatch() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from a single batch (without separator) ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), fileStreamFactory); + query.BatchCompleted += batchCallback; // Then: // ... I should get a single batch to execute that hasn't been executed @@ -85,16 +96,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.True(query.HasExecuted); Assert.NotEmpty(query.BatchSummaries); Assert.Equal(1, query.BatchSummaries.Length); + + // ... The batch callback should have been called precisely 1 time + Assert.Equal(1, batchCallbacksReceived); } [Fact] public void QueryExecuteNoOpBatch() { + // Setup: + // ... Create a callback for batch completion + Batch.BatchAsyncEventHandler batchCallback = summary => + { + throw new Exception("Batch completion callback was called"); + }; + // If: // ... I create a query from a single batch that does nothing ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), fileStreamFactory); + query.BatchCompleted += batchCallback; // Then: // ... I should get no batches back @@ -117,12 +139,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution [Fact] public void QueryExecuteMultipleBatches() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from two batches (with separator) ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory); + query.BatchCompleted += batchCallback; // Then: // ... I should get back two batches to execute that haven't been executed @@ -142,17 +174,30 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.True(query.HasExecuted); Assert.NotEmpty(query.BatchSummaries); Assert.Equal(2, query.BatchSummaries.Length); + + // ... The batch callback should have been called precisely 2 times + Assert.Equal(2, batchCallbacksReceived); } [Fact] public void QueryExecuteMultipleBatchesWithNoOp() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from a two batches (with separator) ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory); + query.BatchCompleted += batchCallback; // Then: // ... I should get back one batch to execute that hasn't been executed @@ -171,16 +216,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.True(query.HasExecuted); Assert.NotEmpty(query.BatchSummaries); Assert.Equal(1, query.BatchSummaries.Length); + + // ... The batch callback should have been called precisely 1 time + Assert.Equal(1, batchCallbacksReceived); } [Fact] public void QueryExecuteInvalidBatch() { + // Setup: + // ... Create a callback for batch completion + int batchCallbacksReceived = 0; + Batch.BatchAsyncEventHandler batchCallback = summary => + { + batchCallbacksReceived++; + return Task.CompletedTask; + }; + // If: // ... I create a query from an invalid batch ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), fileStreamFactory); + query.BatchCompleted += batchCallback; // Then: // ... I should get back a query with one batch not executed @@ -202,6 +260,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.Equal(1, query.BatchSummaries.Length); Assert.True(query.BatchSummaries[0].HasError); Assert.NotEmpty(query.BatchSummaries[0].Messages); + + // ... The batch callback should have been called once + Assert.Equal(1, batchCallbacksReceived); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs index 305c8bf8..3aff6f13 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs @@ -22,14 +22,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution { // If: // ... I create a new result set with a valid db data reader - DbDataReader mockReader = GetReader(null, false, string.Empty); - ResultSet resultSet = new ResultSet(mockReader, Common.GetFileStreamFactory(new Dictionary())); + ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, Common.GetFileStreamFactory(new Dictionary())); // Then: // ... There should not be any data read yet Assert.Null(resultSet.Columns); Assert.Equal(0, resultSet.RowCount); + Assert.Equal(Common.Ordinal, resultSet.Id); + + // ... The summary should include the same info + Assert.Null(resultSet.Summary.ColumnInfo); + Assert.Equal(0, resultSet.Summary.RowCount); + Assert.Equal(Common.Ordinal, resultSet.Summary.Id); + Assert.Equal(Common.Ordinal, resultSet.Summary.BatchId); } [Fact] @@ -39,29 +45,45 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... I create a new result set without a reader // Then: // ... It should throw an exception - Assert.Throws(() => new ResultSet(null, null)); + Assert.Throws(() => new ResultSet(null, Common.Ordinal, Common.Ordinal, null)); } [Fact] public async Task ReadToEndSuccess() { + // Setup: Create a callback for resultset completion + ResultSetSummary resultSummaryFromCallback = null; + ResultSet.ResultSetAsyncEventHandler callback = r => + { + resultSummaryFromCallback = r.Summary; + return Task.FromResult(0); + }; + // 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(new [] {Common.StandardTestData}, false, Common.StandardQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); - ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory); + resultSet.ResultCompletion += callback; await resultSet.ReadResultToEnd(CancellationToken.None); // Then: // ... The columns should be set // ... There should be rows to read back Assert.NotNull(resultSet.Columns); - Assert.NotEmpty(resultSet.Columns); + Assert.Equal(Common.StandardColumns, resultSet.Columns.Length); Assert.Equal(Common.StandardRows, 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); + + // ... The callback for result set completion should have been fired + Assert.NotNull(resultSummaryFromCallback); + } [Theory] [InlineData("JSON")] @@ -78,12 +100,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution } Dictionary[][] dataSets = {data.ToArray()}; + // ... Create a callback for resultset completion + ResultSetSummary resultSummary = null; + ResultSet.ResultSetAsyncEventHandler callback = r => + { + resultSummary = r.Summary; + return Task.FromResult(0); + }; + // If: // ... I create a new resultset with a valid db data reader that is FOR XML/JSON // ... and I read it to the end DbDataReader mockReader = GetReader(dataSets, false, Common.StandardQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); - ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory); + resultSet.ResultCompletion += callback; await resultSet.ReadResultToEnd(CancellationToken.None); // Then: @@ -93,6 +124,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.Equal(1, resultSet.Columns.Length); Assert.Equal(1, resultSet.RowCount); + // ... The callback should have been called + Assert.NotNull(resultSummary); + // If: // ... I attempt to read back the results // Then: @@ -108,7 +142,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... I create a new result set with a valid db data reader without executing it DbDataReader mockReader = GetReader(null, false, string.Empty); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); - ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory); // Then: // ... Attempting to read a subset should fail miserably @@ -126,7 +160,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... And execute the result DbDataReader mockReader = GetReader(new[] {Common.StandardTestData}, false, Common.StandardQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); - ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory); await resultSet.ReadResultToEnd(CancellationToken.None); // ... And attempt to get a subset with invalid parameters @@ -147,7 +181,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... And execute the result set DbDataReader mockReader = GetReader(new[] { Common.StandardTestData }, false, Common.StandardQuery); var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); - ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory); await resultSet.ReadResultToEnd(CancellationToken.None); // ... And attempt to get a subset with valid number of rows diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs index f3bccf08..64bd383d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; @@ -20,7 +21,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution { [Fact] - public async void QueryExecuteValidNoResultsTest() + public async void QueryExecuteSingleBatchNoResultsTest() { // Given: // ... Default settings are stored in the workspace service @@ -38,7 +39,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteBatchCompleteParams batchCompleteParams = null; var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) + .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, null); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); // Then: @@ -46,7 +48,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... A successful result should have been sent with messages on the first batch // ... A completion event should have been fired with empty results // ... A batch completion event should have been fired with empty results - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + // ... A result set completion event should not have been fired + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); Assert.Null(result.Messages); Assert.Equal(1, completeParams.BatchSummaries.Length); @@ -56,7 +59,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotNull(batchCompleteParams); Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries); Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); + Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); @@ -64,7 +67,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution [Fact] - public async void QueryExecuteValidResultsTest() + public async void QueryExecuteSingleBatchSingleResultTest() { // Given: // ... A workspace with a standard query is configured @@ -78,16 +81,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution QueryExecuteResult result = null; QueryExecuteCompleteParams completeParams = null; QueryExecuteBatchCompleteParams batchCompleteParams = null; + QueryExecuteResultSetCompleteParams resultCompleteParams = null; var requestContext = RequestContextMocks.Create(qer => result = qer) .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) + .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams = p); await Common.AwaitExecution(queryService, queryParams, requestContext.Object); // Then: // ... No errors should have been sent - // ... A successful result should have been sent with messages + // ... A successful result should have been sent without messages // ... A completion event should have been fired with one result - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + // ... 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()); Assert.Null(result.Messages); Assert.Equal(1, completeParams.BatchSummaries.Length); @@ -98,7 +105,124 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution Assert.NotNull(batchCompleteParams); Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); + Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); + + Assert.NotNull(resultCompleteParams); + Assert.Equal(Common.StandardColumns, resultCompleteParams.ResultSetSummary.ColumnInfo.Length); + Assert.Equal(Common.StandardRows, resultCompleteParams.ResultSetSummary.RowCount); + Assert.Equal(Common.OwnerUri, resultCompleteParams.OwnerUri); + + // ... There should be one active query + Assert.Equal(1, queryService.ActiveQueries.Count); + } + + [Fact] + public async Task QueryExecuteSingleBatchMultipleResultTest() + { + // Given: + // ... A workspace with a standard query is configured + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + + // If: + // ... I request to execute a valid query with one batch and multiple result sets + var dataset = new[] { Common.StandardTestData, Common.StandardTestData }; + var queryService = Common.GetPrimedExecutionService(dataset, true, false, workspaceService); + var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; + + QueryExecuteResult result = null; + QueryExecuteCompleteParams completeParams = null; + QueryExecuteBatchCompleteParams batchCompleteParams = null; + List resultCompleteParams = new List(); + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) + .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p)); + await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + + // Then: + // ... No errors should have been sent + // ... A successful result should have been sent without messages + // ... 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()); + Assert.Null(result.Messages); + + Assert.Equal(1, completeParams.BatchSummaries.Length); + Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries); + Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); + Assert.False(completeParams.BatchSummaries[0].HasError); + + Assert.NotNull(batchCompleteParams); + Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); + Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); + Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); + + Assert.Equal(2, resultCompleteParams.Count); + foreach (var resultParam in resultCompleteParams) + { + Assert.NotNull(resultCompleteParams); + Assert.Equal(Common.StandardColumns, resultParam.ResultSetSummary.ColumnInfo.Length); + Assert.Equal(Common.StandardRows, resultParam.ResultSetSummary.RowCount); + Assert.Equal(Common.OwnerUri, resultParam.OwnerUri); + } + } + + [Fact] + public async Task QueryExecuteMultipleBatchSingleResultTest() + { + // Given: + // ... A workspace with a standard query is configured + var workspaceService = Common.GetPrimedWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery)); + + // If: + // ... I request a to execute a valid query with multiple batches + var dataSet = new[] { Common.StandardTestData }; + var queryService = Common.GetPrimedExecutionService(dataSet, true, false, workspaceService); + var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; + + QueryExecuteResult result = null; + QueryExecuteCompleteParams completeParams = null; + List batchCompleteParams = new List(); + List resultCompleteParams = new List(); + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams.Add(p)) + .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p)); + await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + + // Then: + // ... 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()); + Assert.Null(result.Messages); + + // ... A completion event should have been fired with one two batch summaries, one result each + Assert.Equal(2, completeParams.BatchSummaries.Length); + Assert.Equal(1, completeParams.BatchSummaries[0].ResultSetSummaries.Length); + Assert.Equal(1, completeParams.BatchSummaries[1].ResultSetSummaries.Length); + Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); + Assert.NotEmpty(completeParams.BatchSummaries[1].Messages); + + // ... Two batch completion events should have been fired + Assert.Equal(2, batchCompleteParams.Count); + foreach (var batch in batchCompleteParams) + { + Assert.NotEmpty(batch.BatchSummary.ResultSetSummaries); + Assert.NotEmpty(batch.BatchSummary.Messages); + Assert.Equal(Common.OwnerUri, batch.OwnerUri); + } + + // ... Two resultset completion events should have been fired + Assert.Equal(2, resultCompleteParams.Count); + foreach (var resultParam in resultCompleteParams) + { + Assert.NotNull(resultParam.ResultSetSummary); + Assert.Equal(Common.StandardColumns, resultParam.ResultSetSummary.ColumnInfo.Length); + Assert.Equal(Common.StandardRows, resultParam.ResultSetSummary.RowCount); + Assert.Equal(Common.OwnerUri, resultParam.OwnerUri); + } // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); @@ -126,7 +250,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.Once()); + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); Assert.IsType(error); Assert.NotEmpty((string)error); Assert.Empty(queryService.ActiveQueries); @@ -159,8 +283,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... An error should have been sent // ... A result should have not have been sent // ... 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.Once()); + VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Never(), Times.Once()); Assert.IsType(error); Assert.NotEmpty((string)error); Assert.Equal(1, queryService.ActiveQueries.Count); @@ -197,7 +322,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... No errors should have been sent // ... A result should have been sent with no errors // ... There should only be one active query - VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + // ... 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()); Assert.Null(result.Messages); Assert.False(complete.BatchSummaries.Any(b => b.HasError)); @@ -230,9 +356,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // Then: // ... Am error should have been sent // ... No result should have been sent - // ... No completion event should have been fired + // ... No completion events should have been fired // ... An active query should not have been added - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Once()); + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); Assert.NotNull(errorResult); Assert.IsType(errorResult); Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys); @@ -264,8 +390,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // Then: // ... No errors should have been sent // ... A result should have been sent with success (we successfully started the query) - // ... A completion event should have been sent with error - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + // ... 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()); Assert.Null(result.Messages); Assert.Equal(1, complete.BatchSummaries.Length); @@ -279,7 +405,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution } private static void VerifyQueryExecuteCallCount(Mock> mock, Times sendResultCalls, - Times sendCompletionEventCalls, Times sendBatchCompletionEvent, Times sendErrorCalls) + Times sendCompletionEventCalls, Times sendBatchCompletionEvent, Times sendResultCompleteEvent, Times sendErrorCalls) { mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); mock.Verify(rc => rc.SendEvent( @@ -288,6 +414,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution mock.Verify(rc => rc.SendEvent( It.Is>(m => m == QueryExecuteBatchCompleteEvent.Type), It.IsAny()), sendBatchCompletionEvent); + mock.Verify(rc => rc.SendEvent( + It.Is>(m => m == QueryExecuteResultSetCompleteEvent.Type), + It.IsAny()), sendResultCompleteEvent); + mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs index 5c7a063c..5317c5d4 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs @@ -149,7 +149,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution /// Test saving results to CSV file when the requested result set is no longer active /// [Fact] - public async void SaveResultsAsCsvQueryNotFoundTest() + public async Task SaveResultsAsCsvQueryNotFoundTest() { // Create a query execution service var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); @@ -302,7 +302,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution /// Test saving results to JSON file when the requested result set is no longer active /// [Fact] - public async void SaveResultsAsJsonQueryNotFoundTest() + public async Task SaveResultsAsJsonQueryNotFoundTest() { // Create a query service var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs index 118d050a..d7a5916a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs @@ -4,6 +4,7 @@ // using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; @@ -20,9 +21,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution #region ResultSet Class Tests [Theory] - [InlineData(0,2)] - [InlineData(0,20)] - [InlineData(1,2)] + [InlineData(0, 2)] + [InlineData(0, 20)] + [InlineData(1, 2)] public void ResultSetValidTest(int startRow, int rowCount) { // Setup: @@ -57,6 +58,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.ThrowsAsync(() => 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())); + await Assert.ThrowsAsync(() => rs.GetSubset(0, 1)); + } + #endregion #region Batch Class Tests @@ -92,21 +104,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.ThrowsAsync(() => b.GetSubset(resultSetIndex, 0, 2)).Wait(); } - [Fact] - public async Task BatchSubsetIncompleteTest() - { - // If: - // ... I have a batch that hasn't completed execution - Batch b = new Batch(Common.StandardQuery, Common.WholeDocument, Common.Ordinal, Common.GetFileStreamFactory(null)); - Assert.False(b.HasExecuted); - - // ... And I ask for a subset - // Then: - // ... It should throw an exception - await Assert.ThrowsAsync(() => b.GetSubset(Common.Ordinal, 0, 2)); - - } - #endregion #region Query Class Tests @@ -142,7 +139,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution 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}; + 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); @@ -157,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution } [Fact] - public async void SubsetServiceMissingQueryTest() + public async Task SubsetServiceMissingQueryTest() { // If: // ... I ask for a set of results for a file that hasn't executed a query @@ -178,7 +175,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution } [Fact] - public async void SubsetServiceUnexecutedQueryTest() + public async Task SubsetServiceUnexecutedQueryTest() { // If: // ... I have a query that hasn't finished executing (doesn't matter what) @@ -188,13 +185,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var executeRequest = RequestContextMocks.Create(null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask; - queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; + 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); - queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object).Wait(); + await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); // Then: // ... I should get an error result @@ -207,7 +204,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async void SubsetServiceOutOfRangeSubsetTest() - { + { // If: // ... I have a query that doesn't have any result sets var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs index 1da7f48a..aaace1b7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs @@ -39,4 +39,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility } } -} \ No newline at end of file +}