mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-24 17:24:14 -05:00
edit/commit Command (#262)
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
This commit is contained in:
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
@@ -26,7 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// If: I attempt to create a CellUpdate with a null string value
|
||||
// Then: I should get an exception thrown
|
||||
Assert.Throws<ArgumentNullException>(() => new CellUpdate(new CellUpdateTestDbColumn(null), null));
|
||||
Assert.Throws<ArgumentNullException>(() => new CellUpdate(GetWrapper<string>("ntext"), null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -34,7 +35,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// If: I attempt to create a CellUpdate to set it to NULL (with mixed cases)
|
||||
const string nullString = "NULL";
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(string));
|
||||
DbColumnWrapper col = GetWrapper<string>("ntext");
|
||||
CellUpdate cu = new CellUpdate(col, nullString);
|
||||
|
||||
// Then: The value should be a DBNull and the string value should be the same as what
|
||||
@@ -49,7 +50,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
public void NullTextStringTest()
|
||||
{
|
||||
// If: I attempt to create a CellUpdate with the text 'NULL' (with mixed case)
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(string));
|
||||
DbColumnWrapper col = GetWrapper<string>("ntext");
|
||||
CellUpdate cu = new CellUpdate(col, "'NULL'");
|
||||
|
||||
// Then: The value should be NULL
|
||||
@@ -64,7 +65,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
public void ByteArrayTest(string strValue, byte[] expectedValue, string expectedString)
|
||||
{
|
||||
// If: I attempt to create a CellUpdate for a binary column
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(byte[]));
|
||||
DbColumnWrapper col = GetWrapper<byte[]>("binary");
|
||||
CellUpdate cu = new CellUpdate(col, strValue);
|
||||
|
||||
// Then: The value should be a binary and should match the expected data
|
||||
@@ -118,7 +119,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// If: I attempt to create a CellUpdate for a binary column
|
||||
// Then: It should throw an exception
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(byte[]));
|
||||
DbColumnWrapper col = GetWrapper<byte[]>("binary");
|
||||
Assert.Throws<FormatException>(() => new CellUpdate(col, "this is totally invalid"));
|
||||
}
|
||||
|
||||
@@ -127,7 +128,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
public void BoolTest(string input, bool output, string outputString)
|
||||
{
|
||||
// If: I attempt to create a CellUpdate for a boolean column
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(bool));
|
||||
DbColumnWrapper col = GetWrapper<bool>("bit");
|
||||
CellUpdate cu = new CellUpdate(col, input);
|
||||
|
||||
// Then: The value should match what was expected
|
||||
@@ -153,23 +154,22 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// If: I create a CellUpdate for a bool column and provide an invalid numeric value
|
||||
// Then: It should throw an exception
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(bool));
|
||||
DbColumnWrapper col = GetWrapper<bool>("bit");
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new CellUpdate(col, "12345"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RoundTripTestParams))]
|
||||
public void RoundTripTest(Type dbColType, object obj)
|
||||
public void RoundTripTest(DbColumnWrapper col, object obj)
|
||||
{
|
||||
// Setup: Figure out the test string
|
||||
string testString = obj.ToString();
|
||||
|
||||
// If: I attempt to create a CellUpdate for a GUID column
|
||||
DbColumn col = new CellUpdateTestDbColumn(dbColType);
|
||||
// If: I attempt to create a CellUpdate
|
||||
CellUpdate cu = new CellUpdate(col, testString);
|
||||
|
||||
// Then: The value and type should match what we put in
|
||||
Assert.IsType(dbColType, cu.Value);
|
||||
Assert.IsType(col.DataType, cu.Value);
|
||||
Assert.Equal(obj, cu.Value);
|
||||
Assert.Equal(testString, cu.ValueAsString);
|
||||
Assert.Equal(col, cu.Column);
|
||||
@@ -179,29 +179,35 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {typeof(Guid), Guid.NewGuid()};
|
||||
yield return new object[] {typeof(TimeSpan), new TimeSpan(0, 1, 20, 0, 123)};
|
||||
yield return new object[] {typeof(DateTime), new DateTime(2016, 04, 25, 9, 45, 0)};
|
||||
yield return new object[] {GetWrapper<Guid>("uniqueidentifier"), Guid.NewGuid()};
|
||||
yield return new object[] {GetWrapper<TimeSpan>("time"), new TimeSpan(0, 1, 20, 0, 123)};
|
||||
yield return new object[] {GetWrapper<DateTime>("datetime"), new DateTime(2016, 04, 25, 9, 45, 0)};
|
||||
yield return new object[]
|
||||
{
|
||||
typeof(DateTimeOffset),
|
||||
GetWrapper<DateTimeOffset>("datetimeoffset"),
|
||||
new DateTimeOffset(2016, 04, 25, 9, 45, 0, TimeSpan.FromHours(8))
|
||||
};
|
||||
yield return new object[] {typeof(long), 1000L};
|
||||
yield return new object[] {typeof(decimal), new decimal(3.14)};
|
||||
yield return new object[] {typeof(int), 1000};
|
||||
yield return new object[] {typeof(short), (short) 1000};
|
||||
yield return new object[] {typeof(byte), (byte) 5};
|
||||
yield return new object[] {typeof(double), 3.14d};
|
||||
yield return new object[] {typeof(float), 3.14f};
|
||||
yield return new object[] {GetWrapper<long>("bigint"), 1000L};
|
||||
yield return new object[] {GetWrapper<decimal>("decimal"), new decimal(3.14)};
|
||||
yield return new object[] {GetWrapper<int>("int"), 1000};
|
||||
yield return new object[] {GetWrapper<short>("smallint"), (short) 1000};
|
||||
yield return new object[] {GetWrapper<byte>("tinyint"), (byte) 5};
|
||||
yield return new object[] {GetWrapper<double>("float"), 3.14d};
|
||||
yield return new object[] {GetWrapper<float>("real"), 3.14f};
|
||||
}
|
||||
}
|
||||
|
||||
private static DbColumnWrapper GetWrapper<T>(string dataTypeName)
|
||||
{
|
||||
return new DbColumnWrapper(new CellUpdateTestDbColumn(typeof(T), dataTypeName));
|
||||
}
|
||||
|
||||
private class CellUpdateTestDbColumn : DbColumn
|
||||
{
|
||||
public CellUpdateTestDbColumn(Type dataType)
|
||||
public CellUpdateTestDbColumn(Type dataType, string dataTypeName)
|
||||
{
|
||||
DataType = dataType;
|
||||
DataTypeName = dataTypeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,18 +69,27 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
return columns.ToArray();
|
||||
}
|
||||
|
||||
public static ResultSet GetResultSet(DbColumn[] columns, bool includeIdentity)
|
||||
public static ResultSet GetResultSet(DbColumn[] columns, bool includeIdentity, int rowCount = 1)
|
||||
{
|
||||
object[][] rows = includeIdentity
|
||||
? new[] { new object[] { "id", "1", "2", "3" } }
|
||||
: new[] { new object[] { "1", "2", "3" } };
|
||||
IEnumerable<object[]> rows = includeIdentity
|
||||
? Enumerable.Repeat(new object[] { "id", "1", "2", "3" }, rowCount)
|
||||
: Enumerable.Repeat(new object[] { "1", "2", "3" }, rowCount);
|
||||
var testResultSet = new TestResultSet(columns, rows);
|
||||
var reader = new TestDbDataReader(new[] { testResultSet });
|
||||
var resultSet = new ResultSet(reader, 0, 0, MemoryFileSystem.GetFileStreamFactory());
|
||||
resultSet.ReadResultToEnd(CancellationToken.None).Wait();
|
||||
var resultSet = new ResultSet(0, 0, MemoryFileSystem.GetFileStreamFactory());
|
||||
resultSet.ReadResultToEnd(reader, CancellationToken.None).Wait();
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
public static DbDataReader GetNewRowDataReader(DbColumn[] columns, bool includeIdentity)
|
||||
{
|
||||
object[][] rows = includeIdentity
|
||||
? new[] {new object[] {"id", "q", "q", "q"}}
|
||||
: new[] {new object[] {"q", "q", "q"}};
|
||||
var testResultSet = new TestResultSet(columns, rows);
|
||||
return new TestDbDataReader(new [] {testResultSet});
|
||||
}
|
||||
|
||||
public static void AddCells(RowEditBase rc, bool includeIdentity)
|
||||
{
|
||||
// Skip the first column since if identity, since identity columns can't be updated
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
@@ -80,5 +82,110 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
Assert.Throws<InvalidOperationException>(() => rc.GetScript());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ApplyChanges(bool includeIdentity)
|
||||
{
|
||||
// Setup:
|
||||
// ... Generate the parameters for the row create
|
||||
const long rowId = 100;
|
||||
DbColumn[] columns = Common.GetColumns(includeIdentity);
|
||||
ResultSet rs = Common.GetResultSet(columns, includeIdentity);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns);
|
||||
|
||||
// ... Setup a db reader for the result of an insert
|
||||
var newRowReader = Common.GetNewRowDataReader(columns, includeIdentity);
|
||||
|
||||
// If: I ask for the change to be applied
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
await rc.ApplyChanges(newRowReader);
|
||||
|
||||
// Then: The result set should have an additional row in it
|
||||
Assert.Equal(2, rs.RowCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GetCommand(bool includeIdentity)
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a row create with cell updates
|
||||
const long rowId = 100;
|
||||
var columns = Common.GetColumns(includeIdentity);
|
||||
var rs = Common.GetResultSet(columns, includeIdentity);
|
||||
var etm = Common.GetMetadata(columns);
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
Common.AddCells(rc, includeIdentity);
|
||||
|
||||
// ... Mock db connection for building the command
|
||||
var mockConn = new TestSqlConnection(null);
|
||||
|
||||
// If: I attempt to get a command for the edit
|
||||
DbCommand cmd = rc.GetCommand(mockConn);
|
||||
|
||||
// Then:
|
||||
// ... The command should not be null
|
||||
Assert.NotNull(cmd);
|
||||
|
||||
// ... There should be parameters in it
|
||||
Assert.Equal(3, cmd.Parameters.Count);
|
||||
|
||||
// ... It should be formatted into an insert script with output
|
||||
Regex r = new Regex(@"INSERT INTO (.+)\((.+)\) OUTPUT (.+) VALUES \((.+)\)");
|
||||
var m = r.Match(cmd.CommandText);
|
||||
Assert.True(m.Success);
|
||||
|
||||
// ... There should be a table
|
||||
string tbl = m.Groups[1].Value;
|
||||
Assert.Equal(etm.EscapedMultipartName, tbl);
|
||||
|
||||
// ... There should be 3 columns for input
|
||||
string inCols = m.Groups[2].Value;
|
||||
Assert.Equal(3, inCols.Split(',').Length);
|
||||
|
||||
// ... There should be 3 OR 4 columns for output that are inserted.
|
||||
string[] outCols = m.Groups[3].Value.Split(',');
|
||||
Assert.Equal(includeIdentity ? 4 : 3, outCols.Length);
|
||||
Assert.All(outCols, s => Assert.StartsWith("inserted.", s.Trim()));
|
||||
|
||||
// ... There should be 3 parameters
|
||||
string[] param = m.Groups[4].Value.Split(',');
|
||||
Assert.Equal(3, param.Length);
|
||||
Assert.All(param, s => Assert.StartsWith("@Value", s.Trim()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCommandNullConnection()
|
||||
{
|
||||
// Setup: Create a row create
|
||||
const long rowId = 100;
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetMetadata(columns);
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
|
||||
// If: I attempt to create a command with a null connection
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => rc.GetCommand(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCommandMissingCell()
|
||||
{
|
||||
// Setup: Generate the parameters for the row create
|
||||
const long rowId = 100;
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetMetadata(columns);
|
||||
var mockConn = new TestSqlConnection(null);
|
||||
|
||||
// If: I ask for a script to be generated without setting any values
|
||||
// Then: An exception should be thrown for missing cells
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
Assert.Throws<InvalidOperationException>(() => rc.GetCommand(mockConn));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
@@ -34,11 +37,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GetScriptTest(bool isHekaton)
|
||||
public void GetScriptTest(bool isMemoryOptimized)
|
||||
{
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false, isHekaton);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false, isMemoryOptimized);
|
||||
|
||||
// If: I ask for a script to be generated for delete
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
@@ -50,13 +53,94 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
|
||||
// ... It should be formatted as a delete script
|
||||
string scriptStart = $"DELETE FROM {etm.EscapedMultipartName}";
|
||||
if (isHekaton)
|
||||
if (isMemoryOptimized)
|
||||
{
|
||||
scriptStart += " WITH(SNAPSHOT)";
|
||||
}
|
||||
Assert.StartsWith(scriptStart, script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyChanges()
|
||||
{
|
||||
// Setup: Generate the parameters for the row delete object
|
||||
// We don't care about the values besides the row ID
|
||||
const long rowId = 0;
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetMetadata(columns);
|
||||
|
||||
// If: I ask for the change to be applied
|
||||
RowDelete rd = new RowDelete(rowId, rs, etm);
|
||||
await rd.ApplyChanges(null); // Reader not used, can be null
|
||||
|
||||
// Then : The result set should have one less row in it
|
||||
Assert.Equal(0, rs.RowCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, true)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(false, false)]
|
||||
public void GetCommand(bool includeIdentity, bool isMemoryOptimized)
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a row delete
|
||||
const long rowId = 0;
|
||||
var columns = Common.GetColumns(includeIdentity);
|
||||
var rs = Common.GetResultSet(columns, includeIdentity);
|
||||
var etm = Common.GetMetadata(columns, !includeIdentity, isMemoryOptimized);
|
||||
RowDelete rd = new RowDelete(rowId, rs, etm);
|
||||
|
||||
// ... Mock db connection for building the command
|
||||
var mockConn = new TestSqlConnection(null);
|
||||
|
||||
// If: I attempt to get a command for the edit
|
||||
DbCommand cmd = rd.GetCommand(mockConn);
|
||||
|
||||
// Then:
|
||||
// ... The command should not be null
|
||||
Assert.NotNull(cmd);
|
||||
|
||||
// ... Only the keys should be used for parameters
|
||||
int expectedKeys = includeIdentity ? 1 : 3;
|
||||
Assert.Equal(expectedKeys, cmd.Parameters.Count);
|
||||
|
||||
// ... It should be formatted into an delete script
|
||||
string regexTest = isMemoryOptimized
|
||||
? @"DELETE FROM (.+) WITH\(SNAPSHOT\) WHERE (.+)"
|
||||
: @"DELETE FROM (.+) WHERE (.+)";
|
||||
Regex r = new Regex(regexTest);
|
||||
var m = r.Match(cmd.CommandText);
|
||||
Assert.True(m.Success);
|
||||
|
||||
// ... There should be a table
|
||||
string tbl = m.Groups[1].Value;
|
||||
Assert.Equal(etm.EscapedMultipartName, tbl);
|
||||
|
||||
// ... There should be as many where components as there are keys
|
||||
string[] whereComponents = m.Groups[2].Value.Split(new[] {"AND"}, StringSplitOptions.None);
|
||||
Assert.Equal(expectedKeys, whereComponents.Length);
|
||||
|
||||
// ... Each component should have be equal to a parameter
|
||||
Assert.All(whereComponents, c => Assert.True(Regex.IsMatch(c.Trim(), @"\(.+ = @.+\)")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCommandNullConnection()
|
||||
{
|
||||
// Setup: Create a row delete
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetMetadata(columns);
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
|
||||
// If: I attempt to create a command with a null connection
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => rd.GetCommand(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCell()
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
@@ -99,13 +100,104 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
rt.ValidateWhereClauseNoKeys();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SortingByTypeTest()
|
||||
{
|
||||
// Setup: Create a result set and metadata we can reuse
|
||||
var cols = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(cols, false);
|
||||
var etm = Common.GetMetadata(cols);
|
||||
|
||||
// If: I request to sort a list of the three different edit operations
|
||||
List<RowEditBase> rowEdits = new List<RowEditBase>
|
||||
{
|
||||
new RowDelete(0, rs, etm),
|
||||
new RowUpdate(0, rs, etm),
|
||||
new RowCreate(0, rs, etm)
|
||||
};
|
||||
rowEdits.Sort();
|
||||
|
||||
// Then: Delete should be the last operation to execute
|
||||
// (we don't care about the order of the other two)
|
||||
Assert.IsType<RowDelete>(rowEdits.Last());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SortingUpdatesByRowIdTest()
|
||||
{
|
||||
// Setup: Create a result set and metadata we can reuse
|
||||
var cols = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(cols, false, 4);
|
||||
var etm = Common.GetMetadata(cols);
|
||||
|
||||
// If: I sort 3 edit operations of the same type
|
||||
List<RowEditBase> rowEdits = new List<RowEditBase>
|
||||
{
|
||||
new RowUpdate(3, rs, etm),
|
||||
new RowUpdate(1, rs, etm),
|
||||
new RowUpdate(2, rs, etm)
|
||||
};
|
||||
rowEdits.Sort();
|
||||
|
||||
// Then: They should be in order by row ID ASCENDING
|
||||
Assert.Equal(1, rowEdits[0].RowId);
|
||||
Assert.Equal(2, rowEdits[1].RowId);
|
||||
Assert.Equal(3, rowEdits[2].RowId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SortingCreatesByRowIdTest()
|
||||
{
|
||||
// Setup: Create a result set and metadata we can reuse
|
||||
var cols = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(cols, false);
|
||||
var etm = Common.GetMetadata(cols);
|
||||
|
||||
// If: I sort 3 edit operations of the same type
|
||||
List<RowEditBase> rowEdits = new List<RowEditBase>
|
||||
{
|
||||
new RowCreate(3, rs, etm),
|
||||
new RowCreate(1, rs, etm),
|
||||
new RowCreate(2, rs, etm)
|
||||
};
|
||||
rowEdits.Sort();
|
||||
|
||||
// Then: They should be in order by row ID ASCENDING
|
||||
Assert.Equal(1, rowEdits[0].RowId);
|
||||
Assert.Equal(2, rowEdits[1].RowId);
|
||||
Assert.Equal(3, rowEdits[2].RowId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SortingDeletesByRowIdTest()
|
||||
{
|
||||
// Setup: Create a result set and metadata we can reuse
|
||||
var cols = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(cols, false);
|
||||
var etm = Common.GetMetadata(cols);
|
||||
|
||||
// If: I sort 3 delete operations of the same type
|
||||
List<RowEditBase> rowEdits = new List<RowEditBase>
|
||||
{
|
||||
new RowDelete(1, rs, etm),
|
||||
new RowDelete(3, rs, etm),
|
||||
new RowDelete(2, rs, etm)
|
||||
};
|
||||
rowEdits.Sort();
|
||||
|
||||
// Then: They should be in order by row ID DESCENDING
|
||||
Assert.Equal(3, rowEdits[0].RowId);
|
||||
Assert.Equal(2, rowEdits[1].RowId);
|
||||
Assert.Equal(1, rowEdits[2].RowId);
|
||||
}
|
||||
|
||||
private static ResultSet GetResultSet(DbColumn[] columns, object[] row)
|
||||
{
|
||||
object[][] rows = {row};
|
||||
var testResultSet = new TestResultSet(columns, rows);
|
||||
var testReader = new TestDbDataReader(new [] {testResultSet});
|
||||
var resultSet = new ResultSet(testReader, 0,0, MemoryFileSystem.GetFileStreamFactory());
|
||||
resultSet.ReadResultToEnd(CancellationToken.None).Wait();
|
||||
var resultSet = new ResultSet(0,0, MemoryFileSystem.GetFileStreamFactory());
|
||||
resultSet.ReadResultToEnd(testReader, CancellationToken.None).Wait();
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
@@ -179,6 +271,18 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task ApplyChanges(DbDataReader reader)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override DbCommand GetCommand(DbConnection conn)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override int SortId => 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
@@ -69,12 +72,12 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GetScriptTest(bool isHekaton)
|
||||
public void GetScriptTest(bool isMemoryOptimized)
|
||||
{
|
||||
// Setup: Create a fake table to update
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false, isHekaton);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false, isMemoryOptimized);
|
||||
|
||||
// If: I ask for a script to be generated for update
|
||||
RowUpdate ru = new RowUpdate(0, rs, etm);
|
||||
@@ -86,7 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Assert.NotNull(script);
|
||||
|
||||
// ... It should be formatted as an update script
|
||||
string regexString = isHekaton
|
||||
string regexString = isMemoryOptimized
|
||||
? @"UPDATE (.+) WITH \(SNAPSHOT\) SET (.*) WHERE .+"
|
||||
: @"UPDATE (.+) SET (.*) WHERE .+";
|
||||
Regex r = new Regex(regexString);
|
||||
@@ -101,5 +104,117 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Assert.Equal(3, updateSplit.Length);
|
||||
Assert.All(updateSplit, s => Assert.Equal(2, s.Split('=').Length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(false, false)]
|
||||
public void GetCommand(bool includeIdentity, bool isMemoryOptimized)
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a row update with cell updates
|
||||
var columns = Common.GetColumns(includeIdentity);
|
||||
var rs = Common.GetResultSet(columns, includeIdentity);
|
||||
var etm = Common.GetMetadata(columns, !includeIdentity, isMemoryOptimized);
|
||||
RowUpdate ru = new RowUpdate(0, rs, etm);
|
||||
Common.AddCells(ru, includeIdentity);
|
||||
|
||||
// ... Mock db connection for building the command
|
||||
var mockConn = new TestSqlConnection(null);
|
||||
|
||||
// If: I ask for a command to be generated for update
|
||||
DbCommand cmd = ru.GetCommand(mockConn);
|
||||
|
||||
// Then:
|
||||
// ... The command should not be null
|
||||
Assert.NotNull(cmd);
|
||||
|
||||
// ... There should be an appropriate number of parameters in it
|
||||
// (1 or 3 keys, 3 value parameters)
|
||||
int expectedKeys = includeIdentity ? 1 : 3;
|
||||
Assert.Equal(expectedKeys + 3, cmd.Parameters.Count);
|
||||
|
||||
// ... It should be formatted into an update script with output
|
||||
string regexFormat = isMemoryOptimized
|
||||
? @"UPDATE (.+) WITH \(SNAPSHOT\) SET (.+) OUTPUT (.+) WHERE (.+)"
|
||||
: @"UPDATE (.+) SET (.+) OUTPUT(.+) WHERE (.+)";
|
||||
Regex r = new Regex(regexFormat);
|
||||
var m = r.Match(cmd.CommandText);
|
||||
Assert.True(m.Success);
|
||||
|
||||
// ... There should be a table
|
||||
string tbl = m.Groups[1].Value;
|
||||
Assert.Equal(etm.EscapedMultipartName, tbl);
|
||||
|
||||
// ... There should be 3 parameters for input
|
||||
string[] inCols = m.Groups[2].Value.Split(',');
|
||||
Assert.Equal(3, inCols.Length);
|
||||
Assert.All(inCols, s => Assert.Matches(@"\[.+\] = @Value\d+", s));
|
||||
|
||||
// ... There should be 3 OR 4 columns for output
|
||||
string[] outCols = m.Groups[3].Value.Split(',');
|
||||
Assert.Equal(includeIdentity ? 4 : 3, outCols.Length);
|
||||
Assert.All(outCols, s => Assert.StartsWith("inserted.", s.Trim()));
|
||||
|
||||
// ... There should be 1 OR 3 columns for where components
|
||||
string[] whereComponents = m.Groups[4].Value.Split(new[] {"AND"}, StringSplitOptions.None);
|
||||
Assert.Equal(expectedKeys, whereComponents.Length);
|
||||
Assert.All(whereComponents, s => Assert.Matches(@"\(.+ = @Param\d+\)", s));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCommandNullConnection()
|
||||
{
|
||||
// Setup: Create a row create
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetMetadata(columns);
|
||||
RowUpdate rc = new RowUpdate(0, rs, etm);
|
||||
|
||||
// If: I attempt to create a command with a null connection
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => rc.GetCommand(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ApplyChanges(bool includeIdentity)
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a row update (no cell updates needed)
|
||||
var columns = Common.GetColumns(includeIdentity);
|
||||
var rs = Common.GetResultSet(columns, includeIdentity);
|
||||
var etm = Common.GetMetadata(columns, !includeIdentity);
|
||||
RowUpdate ru = new RowUpdate(0, rs, etm);
|
||||
long oldBytesWritten = rs.totalBytesWritten;
|
||||
|
||||
// ... Setup a db reader for the result of an update
|
||||
var newRowReader = Common.GetNewRowDataReader(columns, includeIdentity);
|
||||
|
||||
// If: I ask for the change to be applied
|
||||
await ru.ApplyChanges(newRowReader);
|
||||
|
||||
// Then:
|
||||
// ... The result set should have the same number of rows as before
|
||||
Assert.Equal(1, rs.RowCount);
|
||||
Assert.True(oldBytesWritten < rs.totalBytesWritten);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyChangesNullReader()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a row update (no cell updates needed)
|
||||
var columns = Common.GetColumns(true);
|
||||
var rs = Common.GetResultSet(columns, true);
|
||||
var etm = Common.GetMetadata(columns, false);
|
||||
RowUpdate ru = new RowUpdate(0, rs, etm);
|
||||
|
||||
// If: I ask for the changes to be applied with a null db reader
|
||||
// Then: I should get an exception
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => ru.ApplyChanges(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class ServiceIntegrationTests
|
||||
{
|
||||
#region Session Operation Helper Tests
|
||||
#region EditSession Operation Helper Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
@@ -128,7 +128,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be a delete in the session
|
||||
Session s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
EditSession s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
Assert.True(s.EditCache.Any(e => e.Value is RowDelete));
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be a create in the session
|
||||
Session s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
EditSession s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
Assert.True(s.EditCache.Any(e => e.Value is RowCreate));
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
efv.Validate();
|
||||
|
||||
// ... The edit cache should be empty again
|
||||
Session s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
EditSession s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
Assert.Empty(s.EditCache);
|
||||
}
|
||||
|
||||
@@ -215,13 +215,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
edit.Verify(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
private static Session GetDefaultSession()
|
||||
private static EditSession GetDefaultSession()
|
||||
{
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
@@ -28,7 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// If: I create a session object without a null query
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Session(null, Common.GetMetadata(new DbColumn[] {})));
|
||||
Assert.Throws<ArgumentNullException>(() => new EditSession(null, Common.GetMetadata(new DbColumn[] {})));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -38,7 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
// Then: It should throw an exception
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
Assert.Throws<ArgumentNullException>(() => new Session(rs, null));
|
||||
Assert.Throws<ArgumentNullException>(() => new EditSession(rs, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -48,12 +49,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// Then:
|
||||
// ... The edit cache should exist and be empty
|
||||
Assert.NotNull(s.EditCache);
|
||||
Assert.Empty(s.EditCache);
|
||||
Assert.Null(s.CommitTask);
|
||||
|
||||
// ... The next row ID should be equivalent to the number of rows in the result set
|
||||
Assert.Equal(q.Batches[0].ResultSets[0].RowCount, s.NextRowId);
|
||||
@@ -70,7 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
// Then: It should throw an exception
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
q.HasExecuted = false;
|
||||
Assert.Throws<InvalidOperationException>(() => Session.ValidateQueryForSession(q));
|
||||
Assert.Throws<InvalidOperationException>(() => EditSession.ValidateQueryForSession(q));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -94,7 +96,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
|
||||
// If: I create a session object with a query that has !=1 result sets
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<InvalidOperationException>(() => Session.ValidateQueryForSession(query));
|
||||
Assert.Throws<InvalidOperationException>(() => EditSession.ValidateQueryForSession(query));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -102,7 +104,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// If: I validate a query for a session with a valid query
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = Session.ValidateQueryForSession(q);
|
||||
ResultSet rs = EditSession.ValidateQueryForSession(q);
|
||||
|
||||
// Then: I should get the only result set back
|
||||
Assert.NotNull(rs);
|
||||
@@ -121,7 +123,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>().Object;
|
||||
@@ -146,7 +148,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I add a row to the session
|
||||
long newId = s.CreateRow();
|
||||
@@ -167,13 +169,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RowIdOutOfRangeData))]
|
||||
public void RowIdOutOfRange(long rowId, Action<Session, long> testAction)
|
||||
public void RowIdOutOfRange(long rowId, Action<EditSession, long> testAction)
|
||||
{
|
||||
// Setup: Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I delete a row that is out of range for the result set
|
||||
// Then: I should get an exception
|
||||
@@ -185,12 +187,12 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
get
|
||||
{
|
||||
// Delete Row
|
||||
Action<Session, long> delAction = (s, l) => s.DeleteRow(l);
|
||||
Action<EditSession, long> delAction = (s, l) => s.DeleteRow(l);
|
||||
yield return new object[] { -1L, delAction };
|
||||
yield return new object[] { 100L, delAction };
|
||||
|
||||
// Update Cell
|
||||
Action<Session, long> upAction = (s, l) => s.UpdateCell(l, 0, null);
|
||||
Action<EditSession, long> upAction = (s, l) => s.UpdateCell(l, 0, null);
|
||||
yield return new object[] { -1L, upAction };
|
||||
yield return new object[] { 100L, upAction };
|
||||
}
|
||||
@@ -206,7 +208,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>().Object;
|
||||
@@ -228,7 +230,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I add a row to the session
|
||||
s.DeleteRow(0);
|
||||
@@ -249,7 +251,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I revert a row that doesn't have any pending changes
|
||||
// Then: I should get an exception
|
||||
@@ -264,7 +266,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>().Object;
|
||||
@@ -290,7 +292,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>();
|
||||
@@ -314,7 +316,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I update a cell on a row that does not have a pending edit
|
||||
s.UpdateCell(0, 0, "");
|
||||
@@ -339,7 +341,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I try to script the edit cache with a null or whitespace output path
|
||||
// Then: It should throw an exception
|
||||
@@ -354,7 +356,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add two mock edits that will generate a script
|
||||
Mock<RowEditBase> edit = new Mock<RowEditBase>();
|
||||
@@ -377,5 +379,163 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commit Tests
|
||||
|
||||
[Fact]
|
||||
public void CommitNullConnection()
|
||||
{
|
||||
// Setup: Create a basic session
|
||||
EditSession s = GetBasicSession();
|
||||
|
||||
// If: I attempt to commit with a null connection
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => s.CommitEdits(null, () => Task.CompletedTask, e => Task.CompletedTask));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommitNullSuccessHandler()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a basic session
|
||||
EditSession s = GetBasicSession();
|
||||
|
||||
// ... Mock db connection
|
||||
DbConnection conn = new TestSqlConnection(null);
|
||||
|
||||
// If: I attempt to commit with a null success handler
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentNullException>(() => s.CommitEdits(conn, null, e => Task.CompletedTask));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommitNullFailureHandler()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a basic session
|
||||
EditSession s = GetBasicSession();
|
||||
|
||||
// ... Mock db connection
|
||||
DbConnection conn = new TestSqlConnection(null);
|
||||
|
||||
// If: I attempt to commit with a null success handler
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentNullException>(() => s.CommitEdits(conn, () => Task.CompletedTask, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommitInProgress()
|
||||
{
|
||||
// Setup:
|
||||
// ... Basic session and db connection
|
||||
EditSession s = GetBasicSession();
|
||||
DbConnection conn = new TestSqlConnection(null);
|
||||
|
||||
// ... Mock a task that has not completed
|
||||
Task notCompleted = new Task(() => {});
|
||||
s.CommitTask = notCompleted;
|
||||
|
||||
// If: I attempt to commit while a task is in progress
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => s.CommitEdits(conn, () => Task.CompletedTask, e => Task.CompletedTask));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CommitSuccess()
|
||||
{
|
||||
// Setup:
|
||||
// ... Basic session and db connection
|
||||
EditSession s = GetBasicSession();
|
||||
DbConnection conn = new TestSqlConnection(null);
|
||||
|
||||
// ... Add a mock commands for fun
|
||||
Mock<RowEditBase> edit = new Mock<RowEditBase>();
|
||||
edit.Setup(e => e.GetCommand(It.IsAny<DbConnection>())).Returns<DbConnection>(dbc => dbc.CreateCommand());
|
||||
edit.Setup(e => e.ApplyChanges(It.IsAny<DbDataReader>())).Returns(Task.FromResult(0));
|
||||
s.EditCache[0] = edit.Object;
|
||||
|
||||
// If: I commit these changes (and await completion)
|
||||
bool successCalled = false;
|
||||
bool failureCalled = false;
|
||||
s.CommitEdits(conn,
|
||||
() => {
|
||||
successCalled = true;
|
||||
return Task.FromResult(0);
|
||||
},
|
||||
e => {
|
||||
failureCalled = true;
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
await s.CommitTask;
|
||||
|
||||
// Then:
|
||||
// ... The task should still exist
|
||||
Assert.NotNull(s.CommitTask);
|
||||
|
||||
// ... The success handler should have been called (not failure)
|
||||
Assert.True(successCalled);
|
||||
Assert.False(failureCalled);
|
||||
|
||||
// ... The mock edit should have generated a command and applied changes
|
||||
edit.Verify(e => e.GetCommand(conn), Times.Once);
|
||||
edit.Verify(e => e.ApplyChanges(It.IsAny<DbDataReader>()), Times.Once);
|
||||
|
||||
// ... The edit cache should be empty
|
||||
Assert.Empty(s.EditCache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CommitFailure()
|
||||
{
|
||||
// Setup:
|
||||
// ... Basic session and db connection
|
||||
EditSession s = GetBasicSession();
|
||||
DbConnection conn = new TestSqlConnection(null);
|
||||
|
||||
// ... Add a mock edit that will explode on generating a command
|
||||
Mock<RowEditBase> edit = new Mock<RowEditBase>();
|
||||
edit.Setup(e => e.GetCommand(It.IsAny<DbConnection>())).Throws<Exception>();
|
||||
s.EditCache[0] = edit.Object;
|
||||
|
||||
// If: I commit these changes (and await completion)
|
||||
bool successCalled = false;
|
||||
bool failureCalled = false;
|
||||
s.CommitEdits(conn,
|
||||
() => {
|
||||
successCalled = true;
|
||||
return Task.FromResult(0);
|
||||
},
|
||||
e => {
|
||||
failureCalled = true;
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
await s.CommitTask;
|
||||
|
||||
// Then:
|
||||
// ... The task should still exist
|
||||
Assert.NotNull(s.CommitTask);
|
||||
|
||||
// ... The error handler should have been called (not success)
|
||||
Assert.False(successCalled);
|
||||
Assert.True(failureCalled);
|
||||
|
||||
// ... The mock edit should have been asked to generate a command
|
||||
edit.Verify(e => e.GetCommand(conn), Times.Once);
|
||||
|
||||
// ... The edit cache should not be empty
|
||||
Assert.NotEmpty(s.EditCache);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static EditSession GetBasicSession()
|
||||
{
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
return new EditSession(rs, etm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user