Implicit Revert Row Cleanup Logic (#297)

This change enhances the way that edit/updateCell and edit/revertCell operations are performed. 

## **THE API BREAKING CHANGES**:
* edit/updateCell now returns an EditCell (a DbCellValue with a dirty flag) and a row dirty flag.
* edit/revertCell now returns an EditCell (a DbCellValue with a dirty flag) and a row dirty flag.

If by setting the value of a cell via edit/updateCell the row no longer has any edits (an "implicit revert"), the entire row's edit will be removed from the cache. Additionally, if by requesting edit/revert all the pending edits for a row are removed, the entire row's edit will be removed from the cache. This will prevent issues where committing will generate an invalid script because it has no pending changes.

* Adding EditCell class
Returning EditCell with EditUpdateCellResult

* Adding code that will remove a row update if the row is clean after a cell update

* Adding code that will return an EditCell and row dirty flag when a cell is reverted.
If the row is reverted by the cell revert, the pending update will be removed

* Comments for edit cell

* Changes as per pull request comments
This commit is contained in:
Benjamin Russell
2017-03-29 13:51:29 -07:00
committed by GitHub
parent fc04f6822f
commit 42498446cc
16 changed files with 357 additions and 124 deletions

View File

@@ -0,0 +1,39 @@
//
// 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.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
{
/// <summary>
/// Cell that wraps info from <see cref="DbCellValue"/> for edit purposes
/// </summary>
public class EditCell : DbCellValue
{
/// <summary>
/// Default, parameterless constructor to make sure that JSON serializing is happy
/// </summary>
public EditCell() {}
/// <summary>
/// Constructs a new EditCell based on a DbCellValue
/// </summary>
/// <param name="dbCellValue">The DbCellValue that will be enhanced</param>
/// <param name="isDirty">Whether or not the edit cell is dirty</param>
public EditCell(DbCellValue dbCellValue, bool isDirty)
{
IsDirty = isDirty;
DisplayValue = dbCellValue.DisplayValue;
IsNull = dbCellValue.IsNull;
RawObject = dbCellValue.RawObject;
}
/// <summary>
/// Whether or not the cell is considered dirty
/// </summary>
public bool IsDirty { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
{
/// <summary>
/// Parameters to return when a cell is updated in isolation
/// </summary>
public class EditCellResult
{
/// <summary>
/// The cell after the revert was applied
/// </summary>
public EditCell Cell { get; set; }
/// <summary>
/// Whether or not the row is dirty after the revert has been applied
/// </summary>
public bool IsRowDirty { get; set; }
}
}

View File

@@ -7,14 +7,19 @@ using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
{
/// <summary>
/// Parameters for the cell revert request
/// </summary>
public class EditRevertCellParams : RowOperationParams
{
public int ColumnId { get; set; }
}
public class EditRevertCellResult
/// <summary>
/// Parameters to return upon successful revert of the cell
/// </summary>
public class EditRevertCellResult : EditCellResult
{
public string NewValue { get; set; }
}
public class EditRevertCellRequest

View File

@@ -26,31 +26,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
/// <summary>
/// Parameters to return upon successful update of the cell
/// </summary>
public class EditUpdateCellResult
public class EditUpdateCellResult : EditCellResult
{
/// <summary>
/// Whether or not the cell value was modified from the provided string.
/// If <c>true</c>, the client should replace the display value of the cell with the value
/// in <see cref="NewValue"/>
/// </summary>
public bool HasCorrections { get; set; }
/// <summary>
/// Whether or not the cell was reverted with the change.
/// If <c>true</c>, the client should unmark the cell as having an update and replace the
/// display value of the cell with the value in <see cref="NewValue"/>
/// </summary>
public bool IsRevert { get; set; }
/// <summary>
/// Whether or not the new value of the cell is null
/// </summary>
public bool IsNull { get; set; }
/// <summary>
/// The new string value of the cell
/// </summary>
public string NewValue { get; set; }
}
public class EditUpdateCellRequest

View File

@@ -184,14 +184,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
internal Task HandleRevertCellRequest(EditRevertCellParams revertParams,
RequestContext<EditRevertCellResult> requestContext)
{
return HandleSessionRequest(revertParams, requestContext, session =>
{
string newValue = session.RevertCell(revertParams.RowId, revertParams.ColumnId);
return new EditRevertCellResult
{
NewValue = newValue
};
});
return HandleSessionRequest(revertParams, requestContext,
session => session.RevertCell(revertParams.RowId, revertParams.ColumnId));
}
internal Task HandleRevertRowRequest(EditRevertRowParams revertParams,

View File

@@ -308,7 +308,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
/// <param name="rowId">Internal ID of the row to have its edits reverted</param>
/// <param name="columnId">Ordinal ID of the column to revert</param>
/// <returns>String version of the old value for the cell</returns>
public string RevertCell(long rowId, int columnId)
public EditRevertCellResult RevertCell(long rowId, int columnId)
{
ThrowIfNotInitialized();
@@ -319,8 +319,12 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
throw new ArgumentOutOfRangeException(nameof(rowId), SR.EditDataUpdateNotPending);
}
// Update the row
EditRevertCellResult revertResult = pendingEdit.RevertCell(columnId);
CleanupEditIfRowClean(rowId, revertResult);
// Have the edit base revert the cell
return pendingEdit.RevertCell(columnId);
return revertResult;
}
/// <summary>
@@ -410,8 +414,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
// doesn't already exist in the cache
RowEditBase editRow = EditCache.GetOrAdd(rowId, key => new RowUpdate(rowId, associatedResultSet, objectMetadata));
// Pass the call to the row update
return editRow.SetCell(columnId, newValue);
// Update the row
EditUpdateCellResult result = editRow.SetCell(columnId, newValue);
CleanupEditIfRowClean(rowId, result);
return result;
}
#endregion
@@ -521,6 +528,24 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
}
}
/// <summary>
/// Removes the edit from the edit cache if the row is no longer dirty
/// </summary>
/// <param name="rowId">ID of the update to cleanup</param>
/// <param name="editCellResult">Result with row dirty flag</param>
private void CleanupEditIfRowClean(long rowId, EditCellResult editCellResult)
{
// If the row is still dirty, don't do anything
if (editCellResult.IsRowDirty)
{
return;
}
// Make an attempt to remove the clean row edit. If this fails, it'll be handled on commit attempt.
RowEditBase removedRow;
EditCache.TryRemove(rowId, out removedRow);
}
#endregion
/// <summary>

View File

@@ -6,6 +6,7 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;
@@ -101,6 +102,14 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
}
}
/// <summary>
/// Generates a new EditCell that represents the contents of the cell update
/// </summary>
public EditCell AsEditCell
{
get { return new EditCell(AsDbCellValue, true); }
}
/// <summary>
/// The column that the cell will be placed in
/// </summary>

View File

@@ -184,14 +184,16 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// </summary>
/// <param name="columnId">The ordinal ID of the cell to reset</param>
/// <returns>The default value for the column, or null if no default is defined</returns>
public override string RevertCell(int columnId)
public override EditRevertCellResult 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
return new EditRevertCellResult {IsRowDirty = true, Cell = null};
// @TODO: Return default value when we have support checked in
// @TODO: RETURN THE DEFAULT VALUE
}
/// <summary>
@@ -212,14 +214,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
newCells[columnId] = update;
// Put together a result of the change
EditUpdateCellResult eucr = new EditUpdateCellResult
return new EditUpdateCellResult
{
HasCorrections = update.ValueAsString != newValue,
NewValue = update.ValueAsString != newValue ? update.ValueAsString : null,
IsNull = update.Value == DBNull.Value,
IsRevert = false // Editing cells of new rows cannot be reverts
};
return eucr;
IsRowDirty = true, // Row creates will always be dirty
Cell = update.AsEditCell
};
}
#endregion

View File

@@ -105,7 +105,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// deletion.
/// </summary>
/// <param name="columnId">Ordinal of the column to update</param>
public override string RevertCell(int columnId)
public override EditRevertCellResult RevertCell(int columnId)
{
throw new InvalidOperationException(SR.EditDataDeleteSetCell);
}

View File

@@ -109,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// </summary>
/// <param name="columnId">Ordinal ID of the column to revert</param>
/// <returns>String value of the original value of the cell</returns>
public abstract string RevertCell(int columnId);
public abstract EditRevertCellResult RevertCell(int columnId);
/// <summary>
/// Changes the value a cell in the row.

View File

@@ -3,7 +3,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
@@ -167,15 +166,21 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// </summary>
/// <param name="columnId">Ordinal of the column to revert</param>
/// <returns>The value that was </returns>
public override string RevertCell(int columnId)
public override EditRevertCellResult RevertCell(int columnId)
{
Validate.IsWithinRange(nameof(columnId), columnId, 0, associatedRow.Count - 1);
// Remove the cell update
// NOTE: This is best effort. The only way TryRemove can fail is if it is already
// removed. If this happens, it is OK.
CellUpdate cellUpdate;
cellUpdates.TryRemove(columnId, out cellUpdate);
return associatedRow[columnId].DisplayValue;
return new EditRevertCellResult
{
IsRowDirty = cellUpdates.Count > 0,
Cell = new EditCell(associatedRow[columnId], false)
};
}
/// <summary>
@@ -203,10 +208,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
cellUpdates.TryRemove(columnId, out cu);
return new EditUpdateCellResult
{
HasCorrections = false,
NewValue = associatedRow[columnId].DisplayValue,
IsRevert = true,
IsNull = associatedRow[columnId].IsNull
IsRowDirty = cellUpdates.Count > 0,
Cell = new EditCell(associatedRow[columnId], false)
};
}
@@ -214,10 +217,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
cellUpdates.AddOrUpdate(columnId, update, (i, cu) => update);
return new EditUpdateCellResult
{
HasCorrections = update.ValueAsString != newValue,
NewValue = update.ValueAsString != newValue ? update.ValueAsString : null,
IsNull = update.Value == DBNull.Value,
IsRevert = false // If we're in this branch, it is not a revert
IsRowDirty = true,
Cell = update.AsEditCell
};
}