mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
This changes adds the following two notifications from the results processing within a batch. These new notifications allows a consumer to stream results from a resultset instead of getting them all at once after the entire resultset has been fetched. ResultsAvailable This is issued after at least 1 row has been fetched for this resultset. ResultsUpdated This is issued periodically as more rows are available on this resultset. The final send of this notification when all rows have been fetched has the property 'Complete' set to true in the ResultSummary object. Detailed Change Log: * Initial completed implementation of QueryResults stream feature. 3 unittests still need fixing * Fix for the 3 failing test. I will look into making MockBehavior strict again for the three tests later * Making GetReader/GetWriter use filestream objects in FileShare.ReadWrite mode so the file can be concurrently read and written * Changing resultsAvailable also to fire off on a timer instead of after 1st row * adding a project for clr TableValuedFunction to produce result set with delays after each row. This is helpful in end to end testing. * Fixing up some tests and simplifying implementation of result update timer * Address review comments * Some test fixes * Disabled flaky test verification
278 lines
11 KiB
C#
278 lines
11 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
|
using Moq;
|
|
using Moq.Protected;
|
|
using HostingProtocol = Microsoft.SqlTools.Hosting.Protocol;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
|
{
|
|
public static class Common
|
|
{
|
|
#region Constants
|
|
|
|
public const string InvalidQuery = "SELECT *** FROM sys.objects";
|
|
|
|
public const string NoOpQuery = "-- No ops here, just us chickens.";
|
|
|
|
public const int Ordinal = 100; // We'll pick something other than default(int)
|
|
|
|
public const int StandardColumns = 5;
|
|
|
|
public const int StandardRows = 5;
|
|
|
|
public const int MillionRows = 1000000;
|
|
|
|
public const SelectionData WholeDocument = null;
|
|
|
|
public static readonly ConnectionDetails StandardConnectionDetails = new ConnectionDetails
|
|
{
|
|
DatabaseName = "123",
|
|
Password = "456",
|
|
ServerName = "789",
|
|
UserName = "012"
|
|
};
|
|
|
|
public static readonly SelectionData SubsectionDocument = new SelectionData(0, 0, 2, 2);
|
|
|
|
#endregion
|
|
|
|
public static TestResultSet StandardTestResultSet => new TestResultSet(StandardColumns, StandardRows);
|
|
|
|
private static readonly Lazy<TestResultSet> TestResultSet = new Lazy<TestResultSet>(new TestResultSet(StandardColumns, MillionRows));
|
|
|
|
public static TestResultSet MillionRowTestResultSet => TestResultSet.Value;
|
|
|
|
private static readonly Lazy<TestResultSet[]> MillionRowResultSet = new Lazy<TestResultSet[]>(new[]{MillionRowTestResultSet});
|
|
|
|
public static TestResultSet[] StandardTestDataSet => new [] {StandardTestResultSet};
|
|
|
|
public static TestResultSet[] MillionRowTestDataSet => MillionRowResultSet.Value;
|
|
|
|
public static TestResultSet[] ExecutionPlanTestDataSet
|
|
{
|
|
get
|
|
{
|
|
DbColumn[] columns = { new TestDbColumn("Microsoft SQL Server 2005 XML Showplan") };
|
|
object[][] rows = { new object[] { "Execution Plan" } };
|
|
return new[] {new TestResultSet(columns, rows)};
|
|
}
|
|
}
|
|
|
|
#region Public Methods
|
|
|
|
public static Batch GetBasicExecutedBatch()
|
|
{
|
|
Batch batch = new Batch(Constants.StandardQuery, SubsectionDocument, 1,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
batch.Execute(CreateTestConnection(StandardTestDataSet, false, false), CancellationToken.None).Wait();
|
|
return batch;
|
|
}
|
|
|
|
public static Batch GetExecutedBatchWithExecutionPlan()
|
|
{
|
|
Batch batch = new Batch(Constants.StandardQuery, SubsectionDocument, 1,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
batch.Execute(CreateTestConnection(ExecutionPlanTestDataSet, false, false), CancellationToken.None).Wait();
|
|
return batch;
|
|
}
|
|
|
|
public static Query GetBasicExecutedQuery()
|
|
{
|
|
ConnectionInfo ci = CreateConnectedConnectionInfo(StandardTestDataSet, false, false);
|
|
|
|
// Query won't be able to request a new query DbConnection unless the ConnectionService has a
|
|
// ConnectionInfo with the same URI as the query, so we will manually set it
|
|
ConnectionService.Instance.OwnerToConnectionMap[ci.OwnerUri] = ci;
|
|
|
|
Query query = new Query(Constants.StandardQuery, ci, new QueryExecutionSettings(),
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
query.Execute();
|
|
query.ExecutionTask.Wait();
|
|
return query;
|
|
}
|
|
|
|
public static Query GetBasicExecutedQuery(QueryExecutionSettings querySettings)
|
|
{
|
|
ConnectionInfo ci = CreateConnectedConnectionInfo(StandardTestDataSet, false, false);
|
|
|
|
// Query won't be able to request a new query DbConnection unless the ConnectionService has a
|
|
// ConnectionInfo with the same URI as the query, so we will manually set it
|
|
ConnectionService.Instance.OwnerToConnectionMap[ci.OwnerUri] = ci;
|
|
|
|
Query query = new Query(Constants.StandardQuery, ci, querySettings,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
query.Execute();
|
|
query.ExecutionTask.Wait();
|
|
return query;
|
|
}
|
|
|
|
public static TestResultSet[] GetTestDataSet(int dataSets)
|
|
{
|
|
return Enumerable.Repeat(StandardTestResultSet, dataSets).ToArray();
|
|
}
|
|
|
|
public static async Task AwaitExecution(QueryExecutionService service, ExecuteDocumentSelectionParams qeParams,
|
|
HostingProtocol.RequestContext<ExecuteRequestResult> requestContext)
|
|
{
|
|
await service.HandleExecuteRequest(qeParams, requestContext);
|
|
if (service.ActiveQueries.ContainsKey(qeParams.OwnerUri) && service.ActiveQueries[qeParams.OwnerUri].ExecutionTask != null)
|
|
{
|
|
await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DbConnection Mocking
|
|
|
|
private static DbCommand CreateTestCommand(TestResultSet[] data, bool throwOnExecute, bool throwOnRead)
|
|
{
|
|
var commandMock = new Mock<DbCommand> { CallBase = true };
|
|
var commandMockSetup = commandMock.Protected()
|
|
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
|
|
|
|
// Setup the expected execute behavior
|
|
if (throwOnExecute)
|
|
{
|
|
var mockException = new Mock<DbException>();
|
|
mockException.SetupGet(dbe => dbe.Message).Returns("Message");
|
|
commandMockSetup.Throws(mockException.Object);
|
|
}
|
|
else
|
|
{
|
|
commandMockSetup.Returns(new TestDbDataReader(data, throwOnRead));
|
|
}
|
|
|
|
|
|
return commandMock.Object;
|
|
}
|
|
|
|
private static DbConnection CreateTestConnection(TestResultSet[] data, bool throwOnExecute, bool throwOnRead)
|
|
{
|
|
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
|
connectionMock.Protected()
|
|
.Setup<DbCommand>("CreateDbCommand")
|
|
.Returns(() => CreateTestCommand(data, throwOnExecute, throwOnRead));
|
|
connectionMock.Setup(dbc => dbc.Open())
|
|
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Open));
|
|
connectionMock.Setup(dbc => dbc.Close())
|
|
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Closed));
|
|
|
|
return connectionMock.Object;
|
|
}
|
|
|
|
private static ISqlConnectionFactory CreateMockFactory(TestResultSet[] data, bool throwOnExecute, bool throwOnRead)
|
|
{
|
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>(), It.IsAny<string>()))
|
|
.Returns(() => CreateTestConnection(data, throwOnExecute, throwOnRead));
|
|
|
|
return mockFactory.Object;
|
|
}
|
|
|
|
public static ConnectionInfo CreateTestConnectionInfo(TestResultSet[] data, bool throwOnExecute, bool throwOnRead)
|
|
{
|
|
// Create a connection info and add the default connection to it
|
|
ISqlConnectionFactory factory = CreateMockFactory(data, throwOnExecute, throwOnRead);
|
|
ConnectionInfo ci = new ConnectionInfo(factory, Constants.OwnerUri, StandardConnectionDetails);
|
|
ci.ConnectionTypeToConnectionMap[ConnectionType.Default] = factory.CreateSqlConnection(null, null);
|
|
return ci;
|
|
}
|
|
|
|
public static ConnectionInfo CreateConnectedConnectionInfo(
|
|
TestResultSet[] data,
|
|
bool throwOnExecute,
|
|
bool throwOnRead,
|
|
string type = ConnectionType.Default)
|
|
{
|
|
ConnectionService connectionService = ConnectionService.Instance;
|
|
connectionService.OwnerToConnectionMap.Clear();
|
|
connectionService.ConnectionFactory = CreateMockFactory(data, throwOnExecute, throwOnRead);
|
|
|
|
ConnectParams connectParams = new ConnectParams
|
|
{
|
|
Connection = StandardConnectionDetails,
|
|
OwnerUri = Constants.OwnerUri,
|
|
Type = type
|
|
};
|
|
|
|
connectionService.Connect(connectParams).Wait();
|
|
return connectionService.OwnerToConnectionMap[connectParams.OwnerUri];
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Service Mocking
|
|
|
|
public static QueryExecutionService GetPrimedExecutionService(
|
|
TestResultSet[] data,
|
|
bool isConnected,
|
|
bool throwOnExecute,
|
|
bool throwOnRead,
|
|
WorkspaceService<SqlToolsSettings> workspaceService,
|
|
out ConcurrentDictionary<string, byte[]> storage)
|
|
{
|
|
// Create a place for the temp "files" to be written
|
|
storage = new ConcurrentDictionary<string, byte[]>();
|
|
|
|
// Mock the connection service
|
|
var connectionService = new Mock<ConnectionService>();
|
|
ConnectionInfo ci = CreateConnectedConnectionInfo(data, throwOnExecute, throwOnRead);
|
|
ConnectionInfo outValMock;
|
|
connectionService
|
|
.Setup(service => service.TryFindConnection(It.IsAny<string>(), out outValMock))
|
|
.OutCallback((string owner, out ConnectionInfo connInfo) => connInfo = isConnected ? ci : null)
|
|
.Returns(isConnected);
|
|
|
|
return new QueryExecutionService(connectionService.Object, workspaceService) { BufferFileStreamFactory = MemoryFileSystem.GetFileStreamFactory(storage) };
|
|
}
|
|
|
|
public static QueryExecutionService GetPrimedExecutionService(
|
|
TestResultSet[] data,
|
|
bool isConnected,
|
|
bool throwOnExecute,
|
|
bool throwOnRead,
|
|
WorkspaceService<SqlToolsSettings> workspaceService)
|
|
{
|
|
ConcurrentDictionary<string, byte[]> storage;
|
|
return GetPrimedExecutionService(data, isConnected, throwOnExecute, throwOnRead, workspaceService, out storage);
|
|
}
|
|
|
|
public static WorkspaceService<SqlToolsSettings> GetPrimedWorkspaceService(string query)
|
|
{
|
|
// Set up file for returning the query
|
|
var fileMock = new Mock<ScriptFile>();
|
|
fileMock.SetupGet(file => file.Contents).Returns(query);
|
|
|
|
// Set up workspace mock
|
|
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
|
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
|
.Returns(fileMock.Object);
|
|
|
|
return workspaceService.Object;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|