Skip to content

Commit

Permalink
feature/edit/subset (#283)
Browse files Browse the repository at this point in the history
* Changing query/subset API to only use Result on success, Error on error

* Creating an interservice API for getting query result subsets

* Updates to subset API

* RowStartIndex is now long
* Output of query/subset is a 2D array of DbCellValue
* Adding LongSkip method to LongList to allow skipping ahead by longs
* Moving LongList back to ServiceLayer utilities. Move refactoring

* Stubbing out request for edit/subset

* Initial implementation of getting edit rows

* Unit tests for RowEdit and RowDelete .GetEditRow

* Fixing major bugs in LongList implementation, adding much more thorough tests

* Adding some more unit tests and fixes to make unit tests pass

* Fixing comment
  • Loading branch information
benrr101 authored Mar 21, 2017
1 parent 9e576de commit d7ecfb1
Show file tree
Hide file tree
Showing 26 changed files with 1,163 additions and 163 deletions.
6 changes: 3 additions & 3 deletions src/Microsoft.SqlTools.Hosting/Utility/Validate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public static void IsNotNull(string parameterName, object valueToCheck)
/// <param name="upperLimit">The upper limit which the value should not be greater than.</param>
public static void IsWithinRange(
string parameterName,
int valueToCheck,
int lowerLimit,
int upperLimit)
long valueToCheck,
long lowerLimit,
long upperLimit)
{
// TODO: Debug assert here if lowerLimit >= upperLimit

Expand Down
45 changes: 45 additions & 0 deletions src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditRow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// 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>
/// A way to return a row in a result set that is being edited. It contains state about whether
/// or not the row is dirty
/// </summary>
public class EditRow
{
public enum EditRowState
{
Clean = 0,
DirtyInsert = 1,
DirtyDelete = 2,
DirtyUpdate = 3
}

/// <summary>
/// The cells in the row. If the row has pending changes, they will be represented in
/// this list
/// </summary>
public DbCellValue[] Cells { get; set; }

/// <summary>
/// Internal ID of the row. This should be used whenever referencing a row in row edit operations.
/// </summary>
public long Id { get; set; }

/// <summary>
/// Whether or not the row has changes pending
/// </summary>
public bool IsDirty => State != EditRowState.Clean;

/// <summary>
/// What type of dirty state (or lack thereof) the row is
/// </summary>
public EditRowState State { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// 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
{
/// <summary>
/// Parameters for a subset retrieval request
/// </summary>
public class EditSubsetParams : SessionOperationParams
{
/// <summary>
/// Beginning index of the rows to return from the selected resultset. This index will be
/// included in the results.
/// </summary>
public long RowStartIndex { get; set; }

/// <summary>
/// Number of rows to include in the result of this request. If the number of the rows
/// exceeds the number of rows available after the start index, all available rows after
/// the start index will be returned.
/// </summary>
public int RowCount { get; set; }
}

/// <summary>
/// Parameters for the result of a subset retrieval request
/// </summary>
public class EditSubsetResult
{
/// <summary>
/// The number of rows returned from result set, useful for determining if less rows were
/// returned than requested.
/// </summary>
public int RowCount { get; set; }

/// <summary>
/// The requested subset of rows, with information about whether or not the rows are dirty
/// </summary>
public EditRow[] Subset { get; set; }
}

public class EditSubsetRequest
{
public static readonly
RequestType<EditSubsetParams, EditSubsetResult> Type =
RequestType<EditSubsetParams, EditSubsetResult>.Create("edit/subset");
}
}
15 changes: 15 additions & 0 deletions src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public void InitializeService(ServiceHost serviceHost)
serviceHost.SetRequestHandler(EditInitializeRequest.Type, HandleInitializeRequest);
serviceHost.SetRequestHandler(EditRevertCellRequest.Type, HandleRevertCellRequest);
serviceHost.SetRequestHandler(EditRevertRowRequest.Type, HandleRevertRowRequest);
serviceHost.SetRequestHandler(EditSubsetRequest.Type, HandleSubsetRequest);
serviceHost.SetRequestHandler(EditUpdateCellRequest.Type, HandleUpdateCellRequest);
}

Expand Down Expand Up @@ -241,6 +242,20 @@ internal Task HandleRevertRowRequest(EditRevertRowParams revertParams,
});
}

internal Task HandleSubsetRequest(EditSubsetParams subsetParams,
RequestContext<EditSubsetResult> requestContext)
{
return HandleSessionRequest(subsetParams, requestContext, session =>
{
EditRow[] rows = session.GetRows(subsetParams.RowStartIndex, subsetParams.RowCount).Result;
return new EditSubsetResult
{
RowCount = rows.Length,
Subset = rows
};
});
}

internal Task HandleUpdateCellRequest(EditUpdateCellParams updateParams,
RequestContext<EditUpdateCellResult> requestContext)
{
Expand Down
55 changes: 55 additions & 0 deletions src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;

namespace Microsoft.SqlTools.ServiceLayer.EditData
Expand Down Expand Up @@ -186,6 +187,60 @@ public void DeleteRow(long rowId)
}
}

/// <summary>
/// Retrieves a subset of rows with the pending updates applied. If more rows than exist
/// are requested, only the rows that exist will be returned.
/// </summary>
/// <param name="startIndex">Index to start returning rows from</param>
/// <param name="rowCount">The number of rows to return.</param>
/// <returns>An array of rows with pending edits applied</returns>
public async Task<EditRow[]> GetRows(long startIndex, int rowCount)
{
// Get the cached rows from the result set
ResultSetSubset cachedRows = startIndex < associatedResultSet.RowCount
? await associatedResultSet.GetSubset(startIndex, rowCount)
: new ResultSetSubset
{
RowCount = 0,
Rows = new DbCellValue[][] { }
};

// Convert the rows into EditRows and apply the changes we have
List<EditRow> editRows = new List<EditRow>();
for (int i = 0; i < cachedRows.RowCount; i++)
{
long rowId = i + startIndex;
RowEditBase edr;
if (EditCache.TryGetValue(rowId, out edr))
{
// Ask the edit object to generate an edit row
editRows.Add(edr.GetEditRow(cachedRows.Rows[i]));
}
else
{
// Package up the existing row into a clean edit row
EditRow er = new EditRow
{
Id = rowId,
Cells = cachedRows.Rows[i],
State = EditRow.EditRowState.Clean
};
editRows.Add(er);
}
}

// If the requested range of rows was at the end of the original cell set and we have
// added new rows, we need to reflect those changes
if (rowCount > cachedRows.RowCount)
{
long endIndex = startIndex + cachedRows.RowCount;
var newRows = EditCache.Where(edit => edit.Key >= endIndex).Take(rowCount - cachedRows.RowCount);
editRows.AddRange(newRows.Select(newRow => newRow.Value.GetEditRow(null)));
}

return editRows.ToArray();
}

/// <summary>
/// Reverts a cell in a pending edit
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ public CellUpdate(DbColumnWrapper column, string valueAsString)

#region Properties

/// <summary>
/// Converts the cell update to a DbCellValue
/// </summary>
public DbCellValue AsDbCellValue
{
get
{
return new DbCellValue
{
DisplayValue = ValueAsString,
IsNull = Value == DBNull.Value,
RawObject = Value
};
}
}

/// <summary>
/// The column that the cell will be placed in
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,26 @@ public override DbCommand GetCommand(DbConnection connection)
return command;
}

/// <summary>
/// Generates a edit row that represents a row pending insertion
/// </summary>
/// <param name="cachedRow">Original, cached cell contents. (Should be null in this case)</param>
/// <returns>EditRow of pending update</returns>
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
// Iterate over the new cells. If they are null, generate a blank value
DbCellValue[] editCells = newCells.Select(cell => cell == null
? new DbCellValue {DisplayValue = string.Empty, IsNull = false, RawObject = null}
: cell.AsDbCellValue)
.ToArray();
return new EditRow
{
Id = RowId,
Cells = editCells,
State = EditRow.EditRowState.DirtyInsert
};
}

/// <summary>
/// Generates the INSERT INTO statement that will apply the row creation
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;

namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
Expand Down Expand Up @@ -72,6 +73,24 @@ public override DbCommand GetCommand(DbConnection connection)
return command;
}

/// <summary>
/// Generates a edit row that represents a row pending deletion. All the original cells are
/// intact but the state is dirty.
/// </summary>
/// <param name="cachedRow">Original, cached cell contents</param>
/// <returns>EditRow that is pending deletion</returns>
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
Validate.IsNotNull(nameof(cachedRow), cachedRow);

return new EditRow
{
Id = RowId,
Cells = cachedRow,
State = EditRow.EditRowState.DirtyDelete
};
}

/// <summary>
/// Generates a DELETE statement to delete this row
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ protected RowEditBase(long rowId, ResultSet associatedResultSet, IEditTableMetad
/// <returns>Command to commit the change to the db</returns>
public abstract DbCommand GetCommand(DbConnection connection);

/// <summary>
/// Generates a row that has the pending update applied. The dirty status of the row is
/// reflected in the returned EditRow.
/// </summary>
/// <param name="cachedRow">The original, cached row values</param>
/// <returns>An EditRow with the pending changes applied</returns>
public abstract EditRow GetEditRow(DbCellValue[] cachedRow);

/// <summary>
/// Converts the row edit into a SQL statement
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,30 @@ public override DbCommand GetCommand(DbConnection connection)
return command;
}

/// <summary>
/// Generates a edit row that represents a row with pending update. The cells pending
/// updates are merged into the unchanged cells.
/// </summary>
/// <param name="cachedRow">Original, cached cell contents</param>
/// <returns>EditRow with pending updates</returns>
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
Validate.IsNotNull(nameof(cachedRow), cachedRow);

// For each cell that is pending update, replace the db cell value with a new one
foreach (var cellUpdate in cellUpdates)
{
cachedRow[cellUpdate.Key] = cellUpdate.Value.AsDbCellValue;
}

return new EditRow
{
Id = RowId,
Cells = cachedRow,
State = EditRow.EditRowState.DirtyUpdate
};
}

/// <summary>
/// Constructs an update statement to change the associated row.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ public async Task Execute(DbConnection conn, CancellationToken cancellationToken
/// <param name="startRow">The starting row of the results</param>
/// <param name="rowCount">How many rows to retrieve</param>
/// <returns>A subset of results</returns>
public Task<ResultSetSubset> GetSubset(int resultSetIndex, int startRow, int rowCount)
public Task<ResultSetSubset> GetSubset(int resultSetIndex, long startRow, int rowCount)
{
ResultSet targetResultSet;
lock (resultSets)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public class ResultSetSubset
/// <summary>
/// 2D array of the cell values requested from result set
/// </summary>
public string[][] Rows { get; set; }
public DbCellValue[][] Rows { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class SubsetParams
/// Beginning index of the rows to return from the selected resultset. This index will be
/// included in the results.
/// </summary>
public int RowsStartIndex { get; set; }
public long RowsStartIndex { get; set; }

/// <summary>
/// Number of rows to include in the result of this request. If the number of the rows
Expand All @@ -46,11 +46,6 @@ public class SubsetParams
/// </summary>
public class SubsetResult
{
/// <summary>
/// Subset request error messages. Optional, can be set to null to indicate no errors
/// </summary>
public string Message { get; set; }

/// <summary>
/// The requested subset of results. Optional, can be set to null to indicate an error
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public void Execute()
/// <param name="startRow">The starting row of the results</param>
/// <param name="rowCount">How many rows to retrieve</param>
/// <returns>A subset of results</returns>
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, int startRow, int rowCount)
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, long startRow, int rowCount)
{
// Sanity check to make sure that the batch is within bounds
if (batchIndex < 0 || batchIndex >= Batches.Length)
Expand Down
Loading

0 comments on commit d7ecfb1

Please sign in to comment.