From c0468e763f73e92f266afc7e121db77e98383c9a Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Wed, 8 Mar 2017 14:49:13 -0800 Subject: [PATCH] edit/revertCell (#268) // edit/dispose ------------------------------------------------------------------------------- export interface EditDisposeParams extends IEditSessionOperationParams { } export interface EditDisposeResult { } * Initial plumbing for edit/revertCell * Implementation of revert cell in the parents of the row edit base * Adding unit tests --- .../Contracts/EditRevertCellRequest.cs | 26 +++ .../EditData/EditDataService.cs | 14 ++ .../EditData/EditSession.cs | 19 ++ .../EditData/UpdateManagement/RowCreate.cs | 17 +- .../EditData/UpdateManagement/RowDelete.cs | 10 + .../EditData/UpdateManagement/RowEdit.cs | 7 + .../EditData/UpdateManagement/RowUpdate.cs | 31 ++- .../Localization/sr.cs | 11 ++ .../Localization/sr.resx | 6 +- .../Localization/sr.strings | 4 +- .../Localization/sr.xlf | 7 +- .../EditData/RowCreateTests.cs | 181 ++++++++++++++++-- .../EditData/RowDeleteTests.cs | 14 ++ .../EditData/RowEditBaseTests.cs | 5 + .../EditData/RowUpdateTests.cs | 130 ++++++++++++- .../EditData/ServiceIntegrationTests.cs | 3 - .../Utility/TestDbDataReader.cs | 23 ++- 17 files changed, 469 insertions(+), 39 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditRevertCellRequest.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditRevertCellRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditRevertCellRequest.cs new file mode 100644 index 00000000..a25dcff6 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditRevertCellRequest.cs @@ -0,0 +1,26 @@ +// +// 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.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts +{ + public class EditRevertCellParams : RowOperationParams + { + public int ColumnId { get; set; } + } + + public class EditRevertCellResult + { + public string NewValue { get; set; } + } + + public class EditRevertCellRequest + { + public static readonly + RequestType Type = + RequestType.Create("edit/revertCell"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs index 0eeef854..de1867d1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs @@ -89,6 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData serviceHost.SetRequestHandler(EditDeleteRowRequest.Type, HandleDeleteRowRequest); serviceHost.SetRequestHandler(EditDisposeRequest.Type, HandleDisposeRequest); serviceHost.SetRequestHandler(EditInitializeRequest.Type, HandleInitializeRequest); + serviceHost.SetRequestHandler(EditRevertCellRequest.Type, HandleRevertCellRequest); serviceHost.SetRequestHandler(EditRevertRowRequest.Type, HandleRevertRowRequest); serviceHost.SetRequestHandler(EditUpdateCellRequest.Type, HandleUpdateCellRequest); } @@ -217,6 +218,19 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData } } + internal Task HandleRevertCellRequest(EditRevertCellParams revertParams, + RequestContext requestContext) + { + return HandleSessionRequest(revertParams, requestContext, session => + { + string newValue = session.RevertCell(revertParams.RowId, revertParams.ColumnId); + return new EditRevertCellResult + { + NewValue = newValue + }; + }); + } + internal Task HandleRevertRowRequest(EditRevertRowParams revertParams, RequestContext requestContext) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs index 6652f92d..bccd3aa5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs @@ -186,6 +186,25 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData } } + /// + /// Reverts a cell in a pending edit + /// + /// Internal ID of the row to have its edits reverted + /// Ordinal ID of the column to revert + /// String version of the old value for the cell + public string RevertCell(long rowId, int columnId) + { + // Attempt to get the row edit with the given ID + RowEditBase pendingEdit; + if (!EditCache.TryGetValue(rowId, out pendingEdit)) + { + throw new ArgumentOutOfRangeException(nameof(rowId), SR.EditDataUpdateNotPending); + } + + // Have the edit base revert the cell + return pendingEdit.RevertCell(columnId); + } + /// /// Removes a pending row update from the update cache. /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs index 51d72248..db9fbf29 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs @@ -27,7 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement private const string InsertCompleteScript = "{0} VALUES ({1})"; private const string InsertCompleteOutput = "{0} OUTPUT {1} VALUES ({2})"; - private readonly CellUpdate[] newCells; + internal readonly CellUpdate[] newCells; /// /// Creates a new Row Creation edit to the result set @@ -159,6 +159,21 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement return string.Format(InsertCompleteScript, start, joinedValues); } + /// + /// Reverts a cell to an unset value. + /// + /// The ordinal ID of the cell to reset + /// The default value for the column, or null if no default is defined + public override string RevertCell(int columnId) + { + // Validate that the column can be reverted + Validate.IsWithinRange(nameof(columnId), columnId, 0, newCells.Length - 1); + + // Remove the cell update from list of set cells + newCells[columnId] = null; + return null; // @TODO: Return default value when we have support checked in + } + /// /// Sets the value of a cell in the row to be added /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowDelete.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowDelete.cs index 43d89419..fc92e4af 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowDelete.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowDelete.cs @@ -81,6 +81,16 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement return GetCommandText(GetWhereClause(false).CommandText); } + /// + /// This method should not be called. A cell cannot be reverted on a row that is pending + /// deletion. + /// + /// Ordinal of the column to update + public override string RevertCell(int columnId) + { + throw new InvalidOperationException(SR.EditDataDeleteSetCell); + } + /// /// This method should not be called. A cell cannot be updated on a row that is pending /// deletion. diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowEdit.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowEdit.cs index b69a4c9d..6260a39d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowEdit.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowEdit.cs @@ -91,6 +91,13 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement /// A SQL statement public abstract string GetScript(); + /// + /// Reverts a specific cell in row with pending edits + /// + /// Ordinal ID of the column to revert + /// String value of the original value of the cell + public abstract string RevertCell(int columnId); + /// /// Changes the value a cell in the row. /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs index 95c5631d..6e43aa29 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs @@ -4,6 +4,7 @@ // using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Data.Common; @@ -29,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement private const string UpdateScript = @"{0} SET {1} {2}"; private const string UpdateScriptOutput = @"{0} SET {1} OUTPUT {2} {3}"; - private readonly Dictionary cellUpdates; + internal readonly ConcurrentDictionary cellUpdates; private readonly IList associatedRow; /// @@ -41,7 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement public RowUpdate(long rowId, ResultSet associatedResultSet, IEditTableMetadata associatedMetadata) : base(rowId, associatedResultSet, associatedMetadata) { - cellUpdates = new Dictionary(); + cellUpdates = new ConcurrentDictionary(); associatedRow = associatedResultSet.GetRow(rowId); } @@ -137,6 +138,22 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement return string.Format(UpdateScript, statementStart, setClause, whereClause); } + /// + /// Reverts the value of a cell to its original value + /// + /// Ordinal of the column to revert + /// The value that was + public override string RevertCell(int columnId) + { + Validate.IsWithinRange(nameof(columnId), columnId, 0, associatedRow.Count - 1); + + // Remove the cell update + CellUpdate cellUpdate; + cellUpdates.TryRemove(columnId, out cellUpdate); + + return associatedRow[columnId].DisplayValue; + } + /// /// Sets the value of the cell in the associated row. If is /// identical to the original value, this will remove the cell update from the row update. @@ -157,11 +174,9 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement // NOTE: We must use .Equals in order to ignore object to object comparisons if (update.Value.Equals(associatedRow[columnId].RawObject)) { - // Remove any pending change and stop processing this - if (cellUpdates.ContainsKey(columnId)) - { - cellUpdates.Remove(columnId); - } + // Remove any pending change and stop processing this (we don't care if we fail to remove something) + CellUpdate cu; + cellUpdates.TryRemove(columnId, out cu); return new EditUpdateCellResult { HasCorrections = false, @@ -172,7 +187,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement } // The change is real, so set it - cellUpdates[columnId] = update; + cellUpdates.AddOrUpdate(columnId, update, (i, cu) => update); return new EditUpdateCellResult { HasCorrections = update.ValueAsString != newValue, diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 513fb1ee..ac5414e5 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -421,6 +421,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string EditDataColumnUpdateNotPending + { + get + { + return Keys.GetString(Keys.EditDataColumnUpdateNotPending); + } + } + public static string EditDataObjectMetadataNotFound { get @@ -1032,6 +1040,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string EditDataUpdateNotPending = "EditDataUpdateNotPending"; + public const string EditDataColumnUpdateNotPending = "EditDataColumnUpdateNotPending"; + + public const string EditDataObjectMetadataNotFound = "EditDataObjectMetadataNotFound"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index b0b3787d..addb71fb 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -380,7 +380,11 @@ - Given row ID does not have pending updated + Given row ID does not have pending update + + + + Give column ID does not have pending update diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 206c19bd..12792b5d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -178,7 +178,9 @@ EditDataRowOutOfRange = Given row ID is outside the range of rows in the edit ca EditDataUpdatePending = An update is already pending for this row and must be reverted first -EditDataUpdateNotPending = Given row ID does not have pending updated +EditDataUpdateNotPending = Given row ID does not have pending update + +EditDataColumnUpdateNotPending = Give column ID does not have pending update EditDataObjectMetadataNotFound = Table or view metadata could not be found diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index da181095..c5cd03c9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -460,7 +460,7 @@ - Given row ID does not have pending updated + Given row ID does not have pending update Given row ID does not have pending updated @@ -536,6 +536,11 @@ <TBD> + + Give column ID does not have pending update + Give column ID does not have pending update + + Another edit data initialize is in progress for this owner URI. Please wait for completion. Another edit data initialize is in progress for this owner URI. Please wait for completion. diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs index 9c5169e8..80ac1a66 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs @@ -6,10 +6,13 @@ using System; using System.Data.Common; 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; using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; using Xunit; @@ -22,8 +25,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData { // Setup: Create the values to store const long rowId = 100; - ResultSet rs = QueryExecution.Common.GetBasicExecutedBatch().ResultSets[0]; - IEditTableMetadata etm = Common.GetStandardMetadata(rs.Columns); + DbColumn[] columns = Common.GetColumns(false); + ResultSet rs = Common.GetResultSet(columns, false); + IEditTableMetadata etm = Common.GetStandardMetadata(columns); // If: I create a RowCreate instance RowCreate rc = new RowCreate(rowId, rs, etm); @@ -72,14 +76,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData public void GetScriptMissingCell() { // Setup: Generate the parameters for the row create - const long rowId = 100; - DbColumn[] columns = Common.GetColumns(false); - ResultSet rs = Common.GetResultSet(columns, false); - IEditTableMetadata etm = Common.GetStandardMetadata(columns); + RowCreate rc = GetStandardRowCreate(); // 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(() => rc.GetScript()); } @@ -161,11 +161,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData 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.GetStandardMetadata(columns); - RowCreate rc = new RowCreate(rowId, rs, etm); + RowCreate rc = GetStandardRowCreate(); + // If: I attempt to create a command with a null connection // Then: It should throw an exception @@ -176,16 +173,166 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData 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.GetStandardMetadata(columns); + RowCreate rc = GetStandardRowCreate(); 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(() => rc.GetCommand(mockConn)); } + + [Theory] + [InlineData(-1)] // Negative + [InlineData(3)] // At edge of acceptable values + [InlineData(100)] // Way too large value + public void SetCellOutOfRange(int columnId) + { + // Setup: Generate a row create + RowCreate rc = GetStandardRowCreate(); + + // If: I attempt to set a cell on a column that is out of range, I should get an exception + Assert.Throws(() => rc.SetCell(columnId, string.Empty)); + } + + [Fact] + public void SetCellNoChange() + { + // Setup: Generate a row create + RowCreate rc = GetStandardRowCreate(); + + // If: I set a cell in the newly created row to something that doesn't need changing + EditUpdateCellResult eucr = rc.SetCell(0, "1"); + + // Then: + // ... The returned value should not have corrections + Assert.False(eucr.HasCorrections); + Assert.Null(eucr.NewValue); + + // ... The set value is not null + Assert.False(eucr.IsNull); + + // ... The result is not an implicit revert + Assert.False(eucr.IsRevert); + + // ... There should be a cell update in the cell list + Assert.NotNull(rc.newCells[0]); + } + + [Fact] + public void SetCellHasCorrections() + { + // Setup: + // ... Generate a result set with a single binary column + DbColumn[] cols = {new TestDbColumn("bin", "binary", typeof(byte[]))}; + object[][] rows = {}; + var testResultSet = new TestResultSet(cols, rows); + var testReader = new TestDbDataReader(new[] {testResultSet}); + var rs = new ResultSet(0, 0, MemoryFileSystem.GetFileStreamFactory()); + rs.ReadResultToEnd(testReader, CancellationToken.None).Wait(); + + // ... Generate the metadata + var etm = Common.GetStandardMetadata(cols); + + // ... Create the row create + RowCreate rc = new RowCreate(100, rs, etm); + + // If: I set a cell in the newly created row to something that will be corrected + EditUpdateCellResult eucr = rc.SetCell(0, "1000"); + + // Then: + // ... The returned value should have corrections + Assert.True(eucr.HasCorrections); + Assert.NotEmpty(eucr.NewValue); + + // ... The set value is not null + Assert.False(eucr.IsNull); + + // ... The result is not an implicit revert + Assert.False(eucr.IsRevert); + + // ... There should be a cell update in the cell list + Assert.NotNull(rc.newCells[0]); + } + + [Fact] + public void SetCellNull() + { + // Setup: Generate a row create + RowCreate rc = GetStandardRowCreate(); + + // If: I set a cell in the newly created row to null + EditUpdateCellResult eucr = rc.SetCell(0, "NULL"); + + // Then: + // ... The returned value should not have corrections + Assert.False(eucr.HasCorrections); + Assert.Null(eucr.NewValue); + + // ... The set value is null + Assert.True(eucr.IsNull); + + // ... The result is not an implicit revert + Assert.False(eucr.IsRevert); + + // ... There should be a cell update in the cell list + Assert.NotNull(rc.newCells[0]); + } + + [Theory] + [InlineData(-1)] // Negative + [InlineData(3)] // At edge of acceptable values + [InlineData(100)] // Way too large value + public void RevertCellOutOfRange(int columnId) + { + // Setup: Generate the row create + RowCreate rc = GetStandardRowCreate(); + + // If: I attempt to revert a cell that is out of range + // Then: I should get an exception + Assert.Throws(() => rc.RevertCell(columnId)); + } + + [Fact] + public void RevertCellNotSet() + { + // Setup: Generate the row create + RowCreate rc = GetStandardRowCreate(); + + // If: I attempt to revert a cell that has not been set + string result = rc.RevertCell(0); + + // Then: We should get null back + // @TODO: Check for a default value when we support it + Assert.Null(result); + + // ... The cell should no longer be set + Assert.Null(rc.newCells[0]); + } + + [Fact] + public void RevertCellThatWasSet() + { + // Setup: Generate the row create + RowCreate rc = GetStandardRowCreate(); + rc.SetCell(0, "1"); + + // If: I attempt to revert a cell that was set + string result = rc.RevertCell(0); + + // Then: + // ... We should get null back + Assert.Null(result); + + // ... The cell should no longer be set + Assert.Null(rc.newCells[0]); + } + + private static RowCreate GetStandardRowCreate() + { + var cols = Common.GetColumns(false); + var rs = Common.GetResultSet(cols, false); + var etm = Common.GetStandardMetadata(cols); + return new RowCreate(100, rs, etm); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowDeleteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowDeleteTests.cs index 5308a0a9..dfa4e647 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowDeleteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowDeleteTests.cs @@ -153,5 +153,19 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData RowDelete rd = new RowDelete(0, rs, etm); Assert.Throws(() => rd.SetCell(0, null)); } + + [Fact] + public void RevertCell() + { + // Setup: Create a row delete + DbColumn[] cols = Common.GetColumns(false); + ResultSet rs = Common.GetResultSet(cols, false); + IEditTableMetadata etm = Common.GetStandardMetadata(cols); + RowDelete rd = new RowDelete(0, rs, etm); + + // If: I revert a cell on a delete row edit + // Then: It should throw + Assert.Throws(() => rd.RevertCell(0)); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowEditBaseTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowEditBaseTests.cs index 274f11c2..19f98728 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowEditBaseTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowEditBaseTests.cs @@ -267,6 +267,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData throw new NotImplementedException(); } + public override string RevertCell(int columnId) + { + throw new NotImplementedException(); + } + public override EditUpdateCellResult SetCell(int columnId, string newValue) { throw new NotImplementedException(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowUpdateTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowUpdateTests.cs index bd0fb2d1..2fe050d7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowUpdateTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowUpdateTests.cs @@ -6,10 +6,13 @@ using System; using System.Data.Common; 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; using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; using Xunit; @@ -35,7 +38,72 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData } [Fact] - public void ImplicitRevertTest() + public void SetCell() + { + // Setup: Create a row update + var columns = Common.GetColumns(false); + var rs = Common.GetResultSet(columns, false); + var etm = Common.GetStandardMetadata(columns); + RowUpdate ru = new RowUpdate(0, rs, etm); + + // If: I set a cell that can be updated + EditUpdateCellResult eucr = ru.SetCell(0, "col1"); + + // Then: + // ... The returned value should not have corrections + Assert.False(eucr.HasCorrections); + Assert.Null(eucr.NewValue); + + // ... The set value is not null + Assert.False(eucr.IsNull); + + // ... The result is not an implicit revert + Assert.False(eucr.IsRevert); + + // ... There should be a cell update in the cell list + Assert.Contains(0, ru.cellUpdates.Keys); + Assert.NotNull(ru.cellUpdates[0]); + } + + [Fact] + public void SetCellHasCorrections() + { + // Setup: + // ... Generate a result set with a single binary column + DbColumn[] cols = { new TestDbColumn("bin", "binary", typeof(byte[])) }; + object[][] rows = { new object[]{new byte[] {0x00}}}; + var testResultSet = new TestResultSet(cols, rows); + var testReader = new TestDbDataReader(new[] { testResultSet }); + var rs = new ResultSet(0, 0, MemoryFileSystem.GetFileStreamFactory()); + rs.ReadResultToEnd(testReader, CancellationToken.None).Wait(); + + // ... Generate the metadata + var etm = Common.GetStandardMetadata(cols); + + // ... Create the row update + RowUpdate ru = new RowUpdate(0, rs, etm); + + // If: I set a cell in the newly created row to something that will be corrected + EditUpdateCellResult eucr = ru.SetCell(0, "1000"); + + // Then: + // ... The returned value should have corrections + Assert.True(eucr.HasCorrections); + Assert.NotEmpty(eucr.NewValue); + + // ... The set value is not null + Assert.False(eucr.IsNull); + + // ... The result is not an implicit revert + Assert.False(eucr.IsRevert); + + // ... There should be a cell update in the cell list + Assert.Contains(0, ru.cellUpdates.Keys); + Assert.NotNull(ru.cellUpdates[0]); + } + + [Fact] + public void SetCellImplicitRevertTest() { // Setup: Create a fake table to update DbColumn[] columns = Common.GetColumns(true); @@ -216,5 +284,65 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Then: I should get an exception await Assert.ThrowsAsync(() => ru.ApplyChanges(null)); } + + [Theory] + [InlineData(-1)] // Negative + [InlineData(3)] // At edge of acceptable values + [InlineData(100)] // Way too large value + public void RevertCellOutOfRange(int columnId) + { + // Setup: + // ... Create a row update (no cell updates needed) + var columns = Common.GetColumns(false); + var rs = Common.GetResultSet(columns, false); + var etm = Common.GetStandardMetadata(columns); + RowUpdate ru = new RowUpdate(0, rs, etm); + + // If: I attempt to revert a cell that is out of range + // Then: I should get an exception + Assert.Throws(() => ru.RevertCell(columnId)); + } + + [Fact] + public void RevertCellNotSet() + { + // Setup: + // ... Create a row update (no cell updates needed) + var columns = Common.GetColumns(true); + var rs = Common.GetResultSet(columns, true); + var etm = Common.GetStandardMetadata(columns); + RowUpdate ru = new RowUpdate(0, rs, etm); + + // If: I attempt to revert a cell that has not been set + string result = ru.RevertCell(0); + + // Then: We should get the original value back + Assert.NotEmpty(result); + + // ... The cell should no longer be set + Assert.DoesNotContain(0, ru.cellUpdates.Keys); + } + + [Fact] + public void RevertCellThatWasSet() + { + // Setup: + // ... Create a row update + var columns = Common.GetColumns(false); + var rs = Common.GetResultSet(columns, false); + var etm = Common.GetStandardMetadata(columns); + RowUpdate ru = new RowUpdate(0, rs, etm); + ru.SetCell(0, "1"); + + // If: I attempt to revert a cell that was set + string result = ru.RevertCell(0); + + // Then: + // ... We should get the original value back + Assert.NotEmpty(result); + + // ... The cell should no longer be set + Assert.DoesNotContain(0, ru.cellUpdates.Keys); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs index fa856e90..f9674c25 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs @@ -6,14 +6,11 @@ using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.SqlTools.ServiceLayer.Connection; -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Test.Common; -using Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; using Moq; using Xunit; diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestDbDataReader.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestDbDataReader.cs index b3504f78..d6ec82fb 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestDbDataReader.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestDbDataReader.cs @@ -117,7 +117,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility /// Collection of test columns in the current result set public ReadOnlyCollection GetColumnSchema() { - if (ResultSetEnumerator?.Current == null || ResultSetEnumerator.Current.Rows.Count <= 0) + if (ResultSetEnumerator?.Current == null) { return new ReadOnlyCollection(new List()); } @@ -125,6 +125,22 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility return new ReadOnlyCollection(ResultSetEnumerator.Current.Columns); } + public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) + { + if (ResultSetEnumerator.Current.Columns[ordinal].DataType == typeof(byte[])) + { + byte[] data = (byte[]) this[ordinal]; + if (buffer == null) + { + return data.Length; + } + + Array.Copy(data, (int)dataOffset, buffer, bufferOffset, length); + return Math.Min(length, data.Length); + } + throw new InvalidOperationException(); + } + #endregion #region Not Implemented @@ -139,11 +155,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility throw new NotImplementedException(); } - public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) - { - throw new NotImplementedException(); - } - public override char GetChar(int ordinal) { throw new NotImplementedException();