Files
sqltoolsservice/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
Benjamin Russell 7ea1b1bb87 Move Save As to ResultSet (#181)
It's an overhaul of the Save As mechanism to utilize the file reader/writer classes to better align with the patterns laid out by the rest of the query execution. Why make this change? This change makes our code base more uniform and adherent to the patterns/paradigms we've set up. This change also helps with the encapsulation of the classes to "separate the concerns" of each component of the save as function. 

* Replumbing the save as execution to pass the call down the query stack as QueryExecutionService->Query->Batch->ResultSet
    * Each layer performs it's own parameter checking
        * QueryExecutionService checks if the query exists
        * Query checks if the batch exists
        * Batch checks if the result set exists
        * ResultSet checks if the row counts are valid and if the result set has been executed
    * Success/Failure delegates are passed down the chain as well
* Determination of whether a save request is a "selection" moved to the SaveResultsRequest class to eliminate duplication of code and creation of utility classes
* Making the IFileStream* classes more generic
    * Removing the requirements of max characters to store from the GetWriter method, and moving it into the constructor for the temporary buffer writer - the values have been moved to the settings and given defaults
    * Removing the individual type writers from IFileStreamWriter
    * Removing the individual type writers from IFIleStreamReader
* Adding a new overload for WriteRow to IFileStreamWriter that will write out data, given a row's worth of data and the list of columns
* Creating a new IFileStreamFactory that creates a reader/writer pair for reading from the temporary files and writing to CSV files
* Creating a new IFileStreamFactory that creates a reader/writer pair for reading from the temporary files and writing to JSON files
* Dramatically simplified the CSV encoding functionality
* Removed duplicated logic for saving in different types and condensed down to a single chain that only differs based on what type of factory is provided
* Removing the logic for managing the list of save as tasks, since the ResultSet now performs the actual saving work, there's no real need to expose the internals of the ResultSet
* Adding new strings to the sr.strings file for save as error messages
* Completely rewriting the unit tests for the save as mechanism. Very fine grained unit tests now that should cover majority of cases (aside from race conditions)


* Refactoring maxchars params into settings and out of file stream factory

* Removing write*/read* methods from file stream readers/writers

* Migrating the CSV save as to the resultset

* Tweaks to unit testing to eliminate writing files to disk

* WIP, moving to a base class for save results writers

* Everything is wired up and compiles

* Adding unit tests for CSV encoding

* Adding unit tests for CSV and Json writers

* Adding tests to the result set for saving

* Refactor to throw exceptions on errors instead of calling failure handler

* Unit tests for batch/query argument in range

* Unit tests

* Adding service integration unit tests

* Final polish, copyright notices, etc

* Adding NULL logic

* Fixing issue of unicode to utf8

* Fixing issues as per @kburtram code review comments

* Adding files that got broken?
2016-12-21 17:52:34 -08:00

235 lines
9.4 KiB
C#

//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Moq;
using Moq.Protected;
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
{
public 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 string OwnerUri = "testFile";
public const int StandardColumns = 5;
public const string StandardQuery = "SELECT * FROM sys.objects";
public const int StandardRows = 5;
public const string UdtQuery = "SELECT hierarchyid::Parse('/')";
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 Dictionary<string, string>[] StandardTestData
{
get { return GetTestData(StandardRows, StandardColumns); }
}
#region Public Methods
public static Batch GetBasicExecutedBatch()
{
Batch batch = new Batch(StandardQuery, SubsectionDocument, 1, GetFileStreamFactory(new Dictionary<string, byte[]>()));
batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait();
return batch;
}
public static Query GetBasicExecutedQuery()
{
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory(new Dictionary<string, byte[]>()));
query.Execute();
query.ExecutionTask.Wait();
return query;
}
public static Dictionary<string, string>[] GetTestData(int columns, int rows)
{
Dictionary<string, string>[] output = new Dictionary<string, string>[rows];
for (int row = 0; row < rows; row++)
{
Dictionary<string, string> rowDictionary = new Dictionary<string, string>();
for (int column = 0; column < columns; column++)
{
rowDictionary.Add(string.Format("column{0}", column), string.Format("val{0}{1}", column, row));
}
output[row] = rowDictionary;
}
return output;
}
public static async Task AwaitExecution(QueryExecutionService service, QueryExecuteParams qeParams,
RequestContext<QueryExecuteResult> 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 FileStreamWriteMocking
public static IFileStreamFactory GetFileStreamFactory(Dictionary<string, byte[]> storage)
{
Mock<IFileStreamFactory> mock = new Mock<IFileStreamFactory>();
mock.Setup(fsf => fsf.CreateFile())
.Returns(() =>
{
string fileName = Guid.NewGuid().ToString();
storage.Add(fileName, new byte[8192]);
return fileName;
});
mock.Setup(fsf => fsf.GetReader(It.IsAny<string>()))
.Returns<string>(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output])));
mock.Setup(fsf => fsf.GetWriter(It.IsAny<string>()))
.Returns<string>(output => new ServiceBufferFileStreamWriter(new MemoryStream(storage[output]), 1024, 1024));
return mock.Object;
}
#endregion
#region DbConnection Mocking
public static DbCommand CreateTestCommand(Dictionary<string, string>[][] data, bool throwOnRead)
{
var commandMock = new Mock<DbCommand> { CallBase = true };
var commandMockSetup = commandMock.Protected()
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
// Setup the expected behavior
if (throwOnRead)
{
var mockException = new Mock<DbException>();
mockException.SetupGet(dbe => dbe.Message).Returns("Message");
commandMockSetup.Throws(mockException.Object);
}
else
{
commandMockSetup.Returns(new TestDbDataReader(data));
}
return commandMock.Object;
}
public static DbConnection CreateTestConnection(Dictionary<string, string>[][] data, bool throwOnRead)
{
var connectionMock = new Mock<DbConnection> { CallBase = true };
connectionMock.Protected()
.Setup<DbCommand>("CreateDbCommand")
.Returns(() => CreateTestCommand(data, 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;
}
public static ISqlConnectionFactory CreateMockFactory(Dictionary<string, string>[][] data, bool throwOnRead)
{
var mockFactory = new Mock<ISqlConnectionFactory>();
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
.Returns(() => CreateTestConnection(data, throwOnRead));
return mockFactory.Object;
}
public static ConnectionInfo CreateTestConnectionInfo(Dictionary<string, string>[][] data, bool throwOnRead)
{
return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, StandardConnectionDetails);
}
#endregion
#region Service Mocking
public static QueryExecutionService GetPrimedExecutionService(Dictionary<string, string>[][] data,
bool isConnected, bool throwOnRead, WorkspaceService<SqlToolsSettings> workspaceService,
out Dictionary<string, byte[]> storage)
{
// Create a place for the temp "files" to be written
storage = new Dictionary<string, byte[]>();
// Create the connection factory with the dataset
var factory = CreateTestConnectionInfo(data, throwOnRead).Factory;
// Mock the connection service
var connectionService = new Mock<ConnectionService>();
ConnectionInfo ci = new ConnectionInfo(factory, OwnerUri, StandardConnectionDetails);
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 = GetFileStreamFactory(storage) };
}
public static QueryExecutionService GetPrimedExecutionService(Dictionary<string, string>[][] data, bool isConnected, bool throwOnRead, WorkspaceService<SqlToolsSettings> workspaceService)
{
Dictionary<string, byte[]> storage;
return GetPrimedExecutionService(data, isConnected, 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
}
}