mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 17:23:27 -05:00
The main goal of this feature is to enable a command that will 1) Generate a parameterized command for each edit that is in the session 2) Execute that command against the server 3) Update the cached results of the table/view that's being edited with the committed changes (including computed/identity columns) There's some secret sauce in here where I cheated around worrying about gaps in the updated results. This was accomplished by implementing an IComparable for row edit objects that ensures deletes are the *last* actions to occur and that they occur from the bottom of the list up (highest row ID to lowest). Thus, all other actions that are dependent on the row ID are performed first, then the largest row ID is deleted, then next largest, etc. Nevertheless, by the end of a commit the associated ResultSet is still the source of truth. It is expected that the results grid will need updating once changes are committed. Also worth noting, although this pull request supports a "many edits, one commit" approach, it will work just fine for a "one edit, one commit" approach. * WIP * Adding basic commit support. Deletions work! * Nailing down the commit logic, insert commits work! * Updates work! * Fixing bug in DbColumnWrapper IsReadOnly setting * Comments * ResultSet unit tests, fixing issue with seeking in mock writers * Unit tests for RowCreate commands * Unit tests for RowDelete * RowUpdate unit tests * Session and edit base tests * Fixing broken unit tests * Moving constants to constants file * Addressing code review feedback * Fixes from merge issues, string consts * Removing ad-hoc code * fixing as per @abist requests * Fixing a couple more issues
184 lines
7.9 KiB
C#
184 lines
7.9 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.Common;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults
|
|
{
|
|
public class ResultSetTests
|
|
{
|
|
[Fact]
|
|
public void SaveAsNullParams()
|
|
{
|
|
// If: I attempt to save with a null set of params
|
|
// Then: I should get a null argument exception
|
|
ResultSet rs = new ResultSet(
|
|
Common.Ordinal, Common.Ordinal,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
Assert.Throws<ArgumentNullException>(() => rs.SaveAs(
|
|
null,
|
|
MemoryFileSystem.GetFileStreamFactory(),
|
|
null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void SaveAsNullFactory()
|
|
{
|
|
// If: I attempt to save with a null set of params
|
|
// Then: I should get a null argument exception
|
|
ResultSet rs = new ResultSet(
|
|
Common.Ordinal, Common.Ordinal,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
Assert.Throws<ArgumentNullException>(() => rs.SaveAs(
|
|
new SaveResultsRequestParams(),
|
|
null, null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void SaveAsFailedIncomplete()
|
|
{
|
|
// If: I attempt to save a result set that hasn't completed execution
|
|
// Then: I should get an invalid operation exception
|
|
ResultSet rs = new ResultSet(
|
|
Common.Ordinal, Common.Ordinal,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
Assert.Throws<InvalidOperationException>(() => rs.SaveAs(
|
|
new SaveResultsRequestParams(),
|
|
MemoryFileSystem.GetFileStreamFactory(),
|
|
null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void SaveAsFailedExistingTaskInProgress()
|
|
{
|
|
// Setup:
|
|
// ... Create a result set that has been executed
|
|
ResultSet rs = new ResultSet(
|
|
Common.Ordinal, Common.Ordinal,
|
|
MemoryFileSystem.GetFileStreamFactory());
|
|
|
|
// ... Insert a non-started task into the save as tasks
|
|
rs.SaveTasks.AddOrUpdate(Constants.OwnerUri, new Task(() => { }), (s, t) => null);
|
|
|
|
// If: I attempt to save results with the same name as the non-completed task
|
|
// Then: I should get an invalid operation exception
|
|
var requestParams = new SaveResultsRequestParams {FilePath = Constants.OwnerUri};
|
|
Assert.Throws<InvalidOperationException>(() => rs.SaveAs(
|
|
requestParams, GetMockFactory(GetMockWriter().Object, null),
|
|
null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SaveAsWithoutRowSelection()
|
|
{
|
|
// Setup:
|
|
// ... Create a mock reader/writer for reading the result
|
|
IFileStreamFactory resultFactory = MemoryFileSystem.GetFileStreamFactory();
|
|
|
|
// ... Create a result set with dummy data and read to the end
|
|
ResultSet rs = new ResultSet(
|
|
Common.Ordinal, Common.Ordinal,
|
|
resultFactory);
|
|
await rs.ReadResultToEnd(GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery), CancellationToken.None);
|
|
|
|
// ... Create a mock writer for writing the save as file
|
|
Mock<IFileStreamWriter> saveWriter = GetMockWriter();
|
|
IFileStreamFactory saveFactory = GetMockFactory(saveWriter.Object, resultFactory.GetReader);
|
|
|
|
// If: I attempt to save results and await completion
|
|
rs.SaveAs(new SaveResultsRequestParams {FilePath = Constants.OwnerUri}, saveFactory, null, null);
|
|
Assert.True(rs.SaveTasks.ContainsKey(Constants.OwnerUri));
|
|
await rs.SaveTasks[Constants.OwnerUri];
|
|
|
|
// Then:
|
|
// ... The task should have completed successfully
|
|
Assert.Equal(TaskStatus.RanToCompletion, rs.SaveTasks[Constants.OwnerUri].Status);
|
|
|
|
// ... All the rows should have been written successfully
|
|
saveWriter.Verify(
|
|
w => w.WriteRow(It.IsAny<IList<DbCellValue>>(), It.IsAny<IList<DbColumnWrapper>>()),
|
|
Times.Exactly(Common.StandardRows));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SaveAsWithRowSelection()
|
|
{
|
|
// Setup:
|
|
// ... Create a mock reader/writer for reading the result
|
|
IFileStreamFactory resultFactory = MemoryFileSystem.GetFileStreamFactory();
|
|
|
|
// ... Create a result set with dummy data and read to the end
|
|
ResultSet rs = new ResultSet(
|
|
Common.Ordinal, Common.Ordinal,
|
|
resultFactory);
|
|
await rs.ReadResultToEnd(GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery), CancellationToken.None);
|
|
|
|
// ... Create a mock writer for writing the save as file
|
|
Mock<IFileStreamWriter> saveWriter = GetMockWriter();
|
|
IFileStreamFactory saveFactory = GetMockFactory(saveWriter.Object, resultFactory.GetReader);
|
|
|
|
// If: I attempt to save results that has a selection made
|
|
var saveParams = new SaveResultsRequestParams
|
|
{
|
|
FilePath = Constants.OwnerUri,
|
|
RowStartIndex = 1,
|
|
RowEndIndex = Common.StandardRows - 2,
|
|
ColumnStartIndex = 0, // Column start/end doesn't matter, but are required to be
|
|
ColumnEndIndex = 10 // considered a "save selection"
|
|
};
|
|
rs.SaveAs(saveParams, saveFactory, null, null);
|
|
Assert.True(rs.SaveTasks.ContainsKey(Constants.OwnerUri));
|
|
await rs.SaveTasks[Constants.OwnerUri];
|
|
|
|
// Then:
|
|
// ... The task should have completed successfully
|
|
Assert.Equal(TaskStatus.RanToCompletion, rs.SaveTasks[Constants.OwnerUri].Status);
|
|
|
|
// ... All the rows should have been written successfully
|
|
saveWriter.Verify(
|
|
w => w.WriteRow(It.IsAny<IList<DbCellValue>>(), It.IsAny<IList<DbColumnWrapper>>()),
|
|
Times.Exactly(Common.StandardRows - 2));
|
|
}
|
|
|
|
private static Mock<IFileStreamWriter> GetMockWriter()
|
|
{
|
|
var mockWriter = new Mock<IFileStreamWriter>();
|
|
mockWriter.Setup(w => w.WriteRow(It.IsAny<IList<DbCellValue>>(), It.IsAny<IList<DbColumnWrapper>>()));
|
|
return mockWriter;
|
|
}
|
|
|
|
private static IFileStreamFactory GetMockFactory(IFileStreamWriter writer, Func<string, IFileStreamReader> readerGenerator)
|
|
{
|
|
var mockFactory = new Mock<IFileStreamFactory>();
|
|
mockFactory.Setup(f => f.GetWriter(It.IsAny<string>()))
|
|
.Returns(writer);
|
|
mockFactory.Setup(f => f.GetReader(It.IsAny<string>()))
|
|
.Returns(readerGenerator);
|
|
return mockFactory.Object;
|
|
}
|
|
|
|
private static DbDataReader GetReader(TestResultSet[] dataSet, bool throwOnRead, string query)
|
|
{
|
|
var info = Common.CreateTestConnectionInfo(dataSet, throwOnRead);
|
|
var connection = info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
|
var command = connection.CreateCommand();
|
|
command.CommandText = query;
|
|
return command.ExecuteReader();
|
|
}
|
|
}
|
|
}
|