mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-26 01:25:42 -05:00
feature/edit/subset (#283)
* 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
This commit is contained in:
@@ -37,9 +37,9 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// <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
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -241,6 +242,20 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Threading.Tasks;
|
||||
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
|
||||
@@ -186,6 +187,60 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
||||
@@ -85,6 +85,22 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
|
||||
#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>
|
||||
|
||||
@@ -122,6 +122,26 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
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>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Globalization;
|
||||
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
|
||||
@@ -72,6 +73,24 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
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>
|
||||
|
||||
@@ -85,6 +85,14 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
/// <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>
|
||||
|
||||
@@ -113,6 +113,30 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
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>
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <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)
|
||||
|
||||
@@ -19,6 +19,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// <summary>
|
||||
/// 2D array of the cell values requested from result set
|
||||
/// </summary>
|
||||
public string[][] Rows { get; set; }
|
||||
public DbCellValue[][] Rows { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// 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
|
||||
@@ -46,11 +46,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// </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>
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <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)
|
||||
|
||||
@@ -176,42 +176,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attempt to load the query
|
||||
Query query;
|
||||
if (!ActiveQueries.TryGetValue(subsetParams.OwnerUri, out query))
|
||||
{
|
||||
await requestContext.SendResult(new SubsetResult
|
||||
{
|
||||
Message = SR.QueryServiceRequestsNoQuery
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the requested subset and return it
|
||||
ResultSetSubset subset = await InterServiceResultSubset(subsetParams);
|
||||
var result = new SubsetResult
|
||||
{
|
||||
Message = null,
|
||||
ResultSubset = await query.GetSubset(subsetParams.BatchIndex,
|
||||
subsetParams.ResultSetIndex, subsetParams.RowsStartIndex, subsetParams.RowsCount)
|
||||
ResultSubset = subset
|
||||
};
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch (InvalidOperationException ioe)
|
||||
{
|
||||
// Return the error as a result
|
||||
await requestContext.SendResult(new SubsetResult
|
||||
{
|
||||
Message = ioe.Message
|
||||
});
|
||||
}
|
||||
catch (ArgumentOutOfRangeException aoore)
|
||||
{
|
||||
// Return the error as a result
|
||||
await requestContext.SendResult(new SubsetResult
|
||||
{
|
||||
Message = aoore.Message
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// This was unexpected, so send back as error
|
||||
@@ -415,7 +386,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <param name="ownerUri">The identifier of the query to be disposed</param>
|
||||
/// <param name="successAction">Action to perform on success</param>
|
||||
/// <param name="failureAction">Action to perform on failure</param>
|
||||
/// <returns></returns>
|
||||
public async Task InterServiceDisposeQuery(string ownerUri, Func<Task> successAction,
|
||||
Func<string, Task> failureAction)
|
||||
{
|
||||
@@ -444,6 +414,29 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the requested subset of rows from the requested result set. Intended to be
|
||||
/// called by another service.
|
||||
/// </summary>
|
||||
/// <param name="subsetParams">Parameters for the subset to retrieve</param>
|
||||
/// <returns>The requested subset</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The requested query does not exist</exception>
|
||||
public async Task<ResultSetSubset> InterServiceResultSubset(SubsetParams subsetParams)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString(nameof(subsetParams.OwnerUri), subsetParams.OwnerUri);
|
||||
|
||||
// Attempt to load the query
|
||||
Query query;
|
||||
if (!ActiveQueries.TryGetValue(subsetParams.OwnerUri, out query))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(SR.QueryServiceRequestsNoQuery);
|
||||
}
|
||||
|
||||
// Retrieve the requested subset and return it
|
||||
return await query.GetSubset(subsetParams.BatchIndex, subsetParams.ResultSetIndex,
|
||||
subsetParams.RowsStartIndex, subsetParams.RowsCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
@@ -223,7 +224,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <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 startRow, int rowCount)
|
||||
public Task<ResultSetSubset> GetSubset(long startRow, int rowCount)
|
||||
{
|
||||
// Sanity check to make sure that the results have been read beforehand
|
||||
if (!hasBeenRead)
|
||||
@@ -244,7 +245,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
|
||||
string[][] rows;
|
||||
DbCellValue[][] rows;
|
||||
|
||||
using (IFileStreamReader fileStreamReader = fileStreamFactory.GetReader(outputFileName))
|
||||
{
|
||||
@@ -255,19 +256,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Iterate over all the rows and process them into a list of string builders
|
||||
// ReSharper disable once AccessToDisposedClosure The lambda is used immediately in string.Join call
|
||||
IEnumerable<string> rowValues = fileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue);
|
||||
rows = new[] { new[] { string.Join(string.Empty, rowValues) } };
|
||||
string singleString = string.Join(string.Empty, rowValues);
|
||||
DbCellValue cellValue = new DbCellValue
|
||||
{
|
||||
DisplayValue = singleString,
|
||||
IsNull = false,
|
||||
RawObject = singleString
|
||||
};
|
||||
rows = new[] { new[] { cellValue } };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Figure out which rows we need to read back
|
||||
IEnumerable<long> rowOffsets = fileOffsets.Skip(startRow).Take(rowCount);
|
||||
IEnumerable<long> rowOffsets = fileOffsets.LongSkip(startRow).Take(rowCount);
|
||||
|
||||
// Iterate over the rows we need and process them into output
|
||||
// ReSharper disable once AccessToDisposedClosure The lambda is used immediately in .ToArray call
|
||||
rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)
|
||||
.Select(cell => cell.DisplayValue).ToArray())
|
||||
.ToArray();
|
||||
|
||||
rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns).ToArray()).ToArray();
|
||||
}
|
||||
}
|
||||
// Retrieve the subset of the results as per the request
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.Utility
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection class that permits storage of over <c>int.MaxValue</c> items. This is performed
|
||||
@@ -22,8 +23,7 @@ namespace Microsoft.SqlTools.Utility
|
||||
public class LongList<T> : IEnumerable<T>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private int expandListSize = int.MaxValue;
|
||||
|
||||
private List<List<T>> expandedList;
|
||||
private readonly List<T> shortList;
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace Microsoft.SqlTools.Utility
|
||||
public LongList()
|
||||
{
|
||||
shortList = new List<T>();
|
||||
ExpandListSize = int.MaxValue;
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
@@ -45,11 +46,15 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// </summary>
|
||||
public long Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to get or set the value at a given index in the list
|
||||
/// </summary>
|
||||
/// <param name="index">Index into the list to access</param>
|
||||
public T this[long index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetItem(index);
|
||||
get
|
||||
{
|
||||
return GetItem(index);
|
||||
}
|
||||
|
||||
set
|
||||
@@ -58,17 +63,10 @@ namespace Microsoft.SqlTools.Utility
|
||||
}
|
||||
}
|
||||
|
||||
public int ExpandListSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.expandListSize;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
this.expandListSize = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The number of elements to store in a single list before expanding into multiple lists
|
||||
/// </summary>
|
||||
public int ExpandListSize { get; internal set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -81,7 +79,7 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// <returns>Index of the item that was just added</returns>
|
||||
public long Add(T val)
|
||||
{
|
||||
if (Count <= this.ExpandListSize)
|
||||
if (Count < this.ExpandListSize)
|
||||
{
|
||||
shortList.Add(val);
|
||||
}
|
||||
@@ -118,21 +116,22 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// <returns>The item at the index specified</returns>
|
||||
public T GetItem(long index)
|
||||
{
|
||||
T val = default(T);
|
||||
Validate.IsWithinRange(nameof(index), index, 0, Count - 1);
|
||||
|
||||
if (Count <= this.ExpandListSize)
|
||||
T val = default(T);
|
||||
if (Count < this.ExpandListSize)
|
||||
{
|
||||
int i32Index = Convert.ToInt32(index);
|
||||
val = shortList[i32Index];
|
||||
}
|
||||
else
|
||||
{
|
||||
int iArray32Index = (int) (Count / this.ExpandListSize);
|
||||
int iArray32Index = (int) (index / this.ExpandListSize);
|
||||
if (expandedList.Count > iArray32Index)
|
||||
{
|
||||
List<T> arr = expandedList[iArray32Index];
|
||||
|
||||
int i32Index = (int) (Count % this.ExpandListSize);
|
||||
int i32Index = (int) (index % this.ExpandListSize);
|
||||
if (arr.Count > i32Index)
|
||||
{
|
||||
val = arr[i32Index];
|
||||
@@ -142,6 +141,25 @@ namespace Microsoft.SqlTools.Utility
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips ahead the number of elements requested and returns the elements after that many elements
|
||||
/// </summary>
|
||||
/// <param name="start">The number of elements to skip</param>
|
||||
/// <returns>All elements after the number of elements to skip</returns>
|
||||
public IEnumerable<T> LongSkip(long start)
|
||||
{
|
||||
Validate.IsWithinRange(nameof(start), start, 0, Count - 1);
|
||||
|
||||
// Generate an enumerator over this list and jump ahead to the position we want
|
||||
LongListEnumerator<T> longEnumerator = new LongListEnumerator<T>(this) {Index = start - 1};
|
||||
|
||||
// While there are results to get, yield return them
|
||||
while (longEnumerator.MoveNext())
|
||||
{
|
||||
yield return longEnumerator.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the item at the specified index
|
||||
/// </summary>
|
||||
@@ -158,10 +176,10 @@ namespace Microsoft.SqlTools.Utility
|
||||
}
|
||||
else
|
||||
{
|
||||
int iArray32Index = (int) (Count / this.ExpandListSize);
|
||||
int iArray32Index = (int) (index / this.ExpandListSize);
|
||||
List<T> arr = expandedList[iArray32Index];
|
||||
|
||||
int i32Index = (int)(Count % this.ExpandListSize);
|
||||
int i32Index = (int)(index % this.ExpandListSize);
|
||||
arr[i32Index] = value;
|
||||
}
|
||||
}
|
||||
@@ -173,6 +191,8 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// <param name="index">The index to remove from the list</param>
|
||||
public void RemoveAt(long index)
|
||||
{
|
||||
Validate.IsWithinRange(nameof(index), index, 0, Count - 1);
|
||||
|
||||
if (Count <= this.ExpandListSize)
|
||||
{
|
||||
int iArray32MemberIndex = Convert.ToInt32(index); // 0 based
|
||||
@@ -189,14 +209,19 @@ namespace Microsoft.SqlTools.Utility
|
||||
arr.RemoveAt(iArray32MemberIndex);
|
||||
|
||||
// now shift members of the array back one
|
||||
int iArray32TotalIndex = (int) (Count / this.ExpandListSize);
|
||||
for (int i = arrayIndex + 1; i < iArray32TotalIndex; i++)
|
||||
//int iArray32TotalIndex = (int) (Count / this.ExpandListSize);
|
||||
for (int i = arrayIndex + 1; i < expandedList.Count; i++)
|
||||
{
|
||||
List<T> arr1 = expandedList[i - 1];
|
||||
List<T> arr2 = expandedList[i];
|
||||
|
||||
arr1.Add(arr2[this.ExpandListSize - 1]);
|
||||
arr1.Add(arr2[0]);
|
||||
arr2.RemoveAt(0);
|
||||
|
||||
if (arr2.Count == 0)
|
||||
{
|
||||
expandedList.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
--Count;
|
||||
@@ -228,19 +253,15 @@ namespace Microsoft.SqlTools.Utility
|
||||
|
||||
public class LongListEnumerator<TEt> : IEnumerator<TEt>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// The index into the list of the item that is the current item
|
||||
/// </summary>
|
||||
private long index;
|
||||
|
||||
/// <summary>
|
||||
/// The current list that we're iterating over.
|
||||
/// </summary>
|
||||
private readonly LongList<TEt> localList;
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// The current index into the list
|
||||
/// </summary>
|
||||
private long index;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new enumerator for a given LongList
|
||||
@@ -249,10 +270,27 @@ namespace Microsoft.SqlTools.Utility
|
||||
public LongListEnumerator(LongList<TEt> list)
|
||||
{
|
||||
localList = list;
|
||||
index = 0;
|
||||
index = -1;
|
||||
Current = default(TEt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index into the list of the item that is the current item. Upon setting,
|
||||
/// <see cref="Current"/> will be updated if the index is in range. Otherwise,
|
||||
/// <c>default(<see cref="TEt"/>)</c> will be used.
|
||||
/// </summary>
|
||||
public long Index
|
||||
{
|
||||
get { return index; }
|
||||
set
|
||||
{
|
||||
index = value;
|
||||
Current = value >= localList.Count || value < 0
|
||||
? default(TEt)
|
||||
: localList[index];
|
||||
}
|
||||
}
|
||||
|
||||
#region IEnumerator Implementation
|
||||
|
||||
/// <summary>
|
||||
@@ -260,10 +298,10 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// </summary>
|
||||
public TEt Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get { return Current; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the current item in the enumeration
|
||||
/// </summary>
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next item in the list we're iterating over
|
||||
@@ -271,14 +309,8 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// <returns>Whether or not the move was successful</returns>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (index < localList.Count)
|
||||
{
|
||||
Current = localList[index];
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
Current = default(TEt);
|
||||
return false;
|
||||
Index++;
|
||||
return Index < localList.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -286,7 +318,7 @@ namespace Microsoft.SqlTools.Utility
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
index = 0;
|
||||
Index = 0;
|
||||
Current = default(TEt);
|
||||
}
|
||||
|
||||
@@ -216,6 +216,33 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void AsDbCellValue(bool isNull)
|
||||
{
|
||||
// Setup: Create a cell update
|
||||
var value = isNull ? "NULL" : "foo";
|
||||
var col = GetWrapper<string>("NTEXT");
|
||||
CellUpdate cu = new CellUpdate(col, value);
|
||||
|
||||
// If: I convert it to a DbCellvalue
|
||||
DbCellValue dbc = cu.AsDbCellValue;
|
||||
|
||||
// Then:
|
||||
// ... It should not be null
|
||||
Assert.NotNull(dbc);
|
||||
|
||||
// ... The display value should be the same as the value we supplied
|
||||
Assert.Equal(value, dbc.DisplayValue);
|
||||
|
||||
// ... The null-ness of the value should be the same as what we supplied
|
||||
Assert.Equal(isNull, dbc.IsNull);
|
||||
|
||||
// ... We don't care *too* much about the raw value, but we'll check it anyhow
|
||||
Assert.Equal(isNull ? (object)DBNull.Value : value, dbc.RawObject);
|
||||
}
|
||||
|
||||
private static DbColumnWrapper GetWrapper<T>(string dataTypeName, bool allowNull = true)
|
||||
{
|
||||
return new DbColumnWrapper(new CellUpdateTestDbColumn(typeof(T), dataTypeName, allowNull));
|
||||
|
||||
@@ -12,6 +12,7 @@ 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.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
@@ -181,6 +182,64 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
Assert.Throws<InvalidOperationException>(() => rc.GetCommand(mockConn));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditRowNoAdditions()
|
||||
{
|
||||
// Setup: Generate a standard row create
|
||||
RowCreate rc = GetStandardRowCreate();
|
||||
|
||||
// If: I request an edit row from the row create
|
||||
EditRow er = rc.GetEditRow(null);
|
||||
|
||||
// Then:
|
||||
// ... The row should not be null
|
||||
Assert.NotNull(er);
|
||||
|
||||
// ... The row should not be clean
|
||||
Assert.True(er.IsDirty);
|
||||
Assert.Equal(EditRow.EditRowState.DirtyInsert, er.State);
|
||||
|
||||
// ... The row should have a bunch of empty cells (equal to number of columns)
|
||||
Assert.Equal(rc.newCells.Length, er.Cells.Length);
|
||||
Assert.All(er.Cells, dbc =>
|
||||
{
|
||||
Assert.Equal(string.Empty, dbc.DisplayValue);
|
||||
Assert.False(dbc.IsNull);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditRowWithAdditions()
|
||||
{
|
||||
// Setp: Generate a row create with a cell added to it
|
||||
RowCreate rc = GetStandardRowCreate();
|
||||
rc.SetCell(0, "foo");
|
||||
|
||||
// If: I request an edit row from the row create
|
||||
EditRow er = rc.GetEditRow(null);
|
||||
|
||||
// Then:
|
||||
// ... The row should not be null and contain the same number of cells as columns
|
||||
Assert.NotNull(er);
|
||||
Assert.Equal(EditRow.EditRowState.DirtyInsert, er.State);
|
||||
|
||||
// ... The row should not be clean
|
||||
Assert.True(er.IsDirty);
|
||||
Assert.Equal(EditRow.EditRowState.DirtyInsert, er.State);
|
||||
|
||||
// ... The row should have a single non-empty cell at the beginning
|
||||
Assert.Equal("foo", er.Cells[0].DisplayValue);
|
||||
Assert.False(er.Cells[0].IsNull);
|
||||
|
||||
// ... The rest of the cells should be blank
|
||||
for (int i = 1; i < er.Cells.Length; i++)
|
||||
{
|
||||
DbCellValue dbc = er.Cells[i];
|
||||
Assert.Equal(string.Empty, dbc.DisplayValue);
|
||||
Assert.False(dbc.IsNull);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Negative
|
||||
[InlineData(3)] // At edge of acceptable values
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
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.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
@@ -21,15 +24,15 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
public void RowDeleteConstruction()
|
||||
{
|
||||
// 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(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetStandardMetadata(columns, false);
|
||||
|
||||
// If: I create a RowCreate instance
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
RowDelete rc = new RowDelete(100, rs, etm);
|
||||
|
||||
// Then: The values I provided should be available
|
||||
Assert.Equal(rowId, rc.RowId);
|
||||
Assert.Equal(100, rc.RowId);
|
||||
Assert.Equal(rs, rc.AssociatedResultSet);
|
||||
Assert.Equal(etm, rc.AssociatedObjectMetadata);
|
||||
}
|
||||
@@ -64,14 +67,12 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
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.GetStandardMetadata(columns);
|
||||
|
||||
// If: I ask for the change to be applied
|
||||
RowDelete rd = new RowDelete(rowId, rs, etm);
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
await rd.ApplyChanges(null); // Reader not used, can be null
|
||||
|
||||
// Then : The result set should have one less row in it
|
||||
@@ -87,11 +88,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a row delete
|
||||
const long rowId = 0;
|
||||
var columns = Common.GetColumns(includeIdentity);
|
||||
var rs = Common.GetResultSet(columns, includeIdentity);
|
||||
var etm = Common.GetStandardMetadata(columns, !includeIdentity, isMemoryOptimized);
|
||||
RowDelete rd = new RowDelete(rowId, rs, etm);
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
|
||||
// ... Mock db connection for building the command
|
||||
var mockConn = new TestSqlConnection(null);
|
||||
@@ -131,26 +131,66 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
public void GetCommandNullConnection()
|
||||
{
|
||||
// Setup: Create a row delete
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetStandardMetadata(columns);
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
RowDelete rd = GetStandardRowDelete();
|
||||
|
||||
// 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 GetEditRow()
|
||||
{
|
||||
// Setup: Create a row delete
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetStandardMetadata(columns);
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
|
||||
// If: I attempt to get an edit row
|
||||
DbCellValue[] cells = rs.GetRow(0).ToArray();
|
||||
EditRow er = rd.GetEditRow(cells);
|
||||
|
||||
// Then:
|
||||
// ... The state should be dirty
|
||||
Assert.True(er.IsDirty);
|
||||
Assert.Equal(EditRow.EditRowState.DirtyDelete, er.State);
|
||||
|
||||
// ... The ID should be the same as the one provided
|
||||
Assert.Equal(0, er.Id);
|
||||
|
||||
// ... The row should match the cells that were given
|
||||
Assert.Equal(cells.Length, er.Cells.Length);
|
||||
for (int i = 0; i < cells.Length; i++)
|
||||
{
|
||||
DbCellValue originalCell = cells[i];
|
||||
DbCellValue outputCell = er.Cells[i];
|
||||
|
||||
Assert.Equal(originalCell.DisplayValue, outputCell.DisplayValue);
|
||||
Assert.Equal(originalCell.IsNull, outputCell.IsNull);
|
||||
// Note: No real need to check the RawObject property
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditNullRow()
|
||||
{
|
||||
// Setup: Create a row delete
|
||||
RowDelete rd = GetStandardRowDelete();
|
||||
|
||||
// If: I attempt to get an edit row with a null cached row
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentNullException>(() => rd.GetEditRow(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCell()
|
||||
{
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetStandardMetadata(columns, false);
|
||||
// Setup: Create a row delete
|
||||
RowDelete rd = GetStandardRowDelete();
|
||||
|
||||
// If: I set a cell on a delete row edit
|
||||
// Then: It should throw as invalid operation
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
Assert.Throws<InvalidOperationException>(() => rd.SetCell(0, null));
|
||||
}
|
||||
|
||||
@@ -158,14 +198,19 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
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);
|
||||
RowDelete rd = GetStandardRowDelete();
|
||||
|
||||
// If: I revert a cell on a delete row edit
|
||||
// Then: It should throw
|
||||
Assert.Throws<InvalidOperationException>(() => rd.RevertCell(0));
|
||||
}
|
||||
|
||||
private RowDelete GetStandardRowDelete()
|
||||
{
|
||||
var cols = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(cols, false);
|
||||
var etm = Common.GetStandardMetadata(cols);
|
||||
return new RowDelete(0, rs, etm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
@@ -267,6 +268,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override EditRow GetEditRow(DbCellValue[] cells)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string RevertCell(int columnId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -12,6 +13,7 @@ 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.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
@@ -41,10 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
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);
|
||||
RowUpdate ru = GetStandardRowUpdate();
|
||||
|
||||
// If: I set a cell that can be updated
|
||||
EditUpdateCellResult eucr = ru.SetCell(0, "col1");
|
||||
@@ -234,15 +233,63 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
[Fact]
|
||||
public void GetCommandNullConnection()
|
||||
{
|
||||
// Setup: Create a row create
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetStandardMetadata(columns);
|
||||
RowUpdate rc = new RowUpdate(0, rs, etm);
|
||||
// Setup: Create a row update
|
||||
RowUpdate ru = GetStandardRowUpdate();
|
||||
|
||||
// If: I attempt to create a command with a null connection
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => rc.GetCommand(null));
|
||||
Assert.Throws<ArgumentNullException>(() => ru.GetCommand(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditRow()
|
||||
{
|
||||
// Setup: Create a row update with a cell set
|
||||
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, "foo");
|
||||
|
||||
// If: I attempt to get an edit row
|
||||
DbCellValue[] cells = rs.GetRow(0).ToArray();
|
||||
EditRow er = ru.GetEditRow(cells);
|
||||
|
||||
// Then:
|
||||
// ... The state should be dirty
|
||||
Assert.True(er.IsDirty);
|
||||
Assert.Equal(EditRow.EditRowState.DirtyUpdate, er.State);
|
||||
|
||||
// ... The ID should be the same as the one provided
|
||||
Assert.Equal(0, er.Id);
|
||||
|
||||
// ... The row should match the cells that were given, except for the updated cell
|
||||
Assert.Equal(cells.Length, er.Cells.Length);
|
||||
for (int i = 1; i < cells.Length; i++)
|
||||
{
|
||||
DbCellValue originalCell = cells[i];
|
||||
DbCellValue outputCell = er.Cells[i];
|
||||
|
||||
Assert.Equal(originalCell.DisplayValue, outputCell.DisplayValue);
|
||||
Assert.Equal(originalCell.IsNull, outputCell.IsNull);
|
||||
// Note: No real need to check the RawObject property
|
||||
}
|
||||
|
||||
// ... The updated cell should match what it was set to
|
||||
DbCellValue newCell = er.Cells[0];
|
||||
Assert.Equal(newCell.DisplayValue, "foo");
|
||||
Assert.Equal(newCell.IsNull, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditNullRow()
|
||||
{
|
||||
// Setup: Create a row update
|
||||
RowUpdate ru = GetStandardRowUpdate();
|
||||
|
||||
// If: I attempt to get an edit row with a null cached row
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentNullException>(() => ru.GetEditRow(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -344,5 +391,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
// ... The cell should no longer be set
|
||||
Assert.DoesNotContain(0, ru.cellUpdates.Keys);
|
||||
}
|
||||
|
||||
private RowUpdate GetStandardRowUpdate()
|
||||
{
|
||||
var columns = Common.GetColumns(false);
|
||||
var rs = Common.GetResultSet(columns, false);
|
||||
var etm = Common.GetStandardMetadata(columns);
|
||||
return new RowUpdate(0, rs, etm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dispose Tests
|
||||
@@ -215,6 +217,36 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
edit.Verify(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRowsSuccess()
|
||||
{
|
||||
// Setup: Create an edit data service with a session
|
||||
// Setup: Create an edit data service with a session
|
||||
var eds = new EditDataService(null, null, null);
|
||||
var session = GetDefaultSession();
|
||||
eds.ActiveSessions[Constants.OwnerUri] = session;
|
||||
|
||||
// If: I validly ask for rows
|
||||
var efv = new EventFlowValidator<EditSubsetResult>()
|
||||
.AddResultValidation(esr =>
|
||||
{
|
||||
Assert.NotNull(esr);
|
||||
Assert.NotEmpty(esr.Subset);
|
||||
Assert.NotEqual(0, esr.RowCount);
|
||||
})
|
||||
.Complete();
|
||||
await eds.HandleSubsetRequest(new EditSubsetParams
|
||||
{
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
RowCount = 10,
|
||||
RowStartIndex = 0
|
||||
}, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... It should be successful
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "table", "table")] // Null owner URI
|
||||
[InlineData(Common.OwnerUri, null, "table")] // Null object name
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
@@ -392,6 +393,172 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
|
||||
#endregion
|
||||
|
||||
#region SubSet Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetRowsNoEdits()
|
||||
{
|
||||
// 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.GetStandardMetadata(rs.Columns);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// If: I ask for 3 rows from session skipping the first
|
||||
EditRow[] rows = await s.GetRows(1, 3);
|
||||
|
||||
// Then:
|
||||
// ... I should get back 3 rows
|
||||
Assert.Equal(3, rows.Length);
|
||||
|
||||
// ... Each row should...
|
||||
for (int i = 0; i < rows.Length; i++)
|
||||
{
|
||||
EditRow er = rows[i];
|
||||
|
||||
// ... Have properly set IDs
|
||||
Assert.Equal(i + 1, er.Id);
|
||||
|
||||
// ... Have cells equal to the cells in the result set
|
||||
DbCellValue[] cachedRow = rs.GetRow(i + 1).ToArray();
|
||||
Assert.Equal(cachedRow.Length, er.Cells.Length);
|
||||
for (int j = 0; j < cachedRow.Length; j++)
|
||||
{
|
||||
Assert.Equal(cachedRow[j].DisplayValue, er.Cells[j].DisplayValue);
|
||||
Assert.Equal(cachedRow[j].IsNull, er.Cells[j].IsNull);
|
||||
}
|
||||
|
||||
// ... Be clean, since we didn't apply any updates
|
||||
Assert.Equal(EditRow.EditRowState.Clean, er.State);
|
||||
Assert.False(er.IsDirty);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRowsPendingUpdate()
|
||||
{
|
||||
// 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.GetStandardMetadata(rs.Columns);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a cell update to it
|
||||
s.UpdateCell(1, 0, "foo");
|
||||
|
||||
// If: I ask for 3 rows from the session, skipping the first, including the updated one
|
||||
EditRow[] rows = await s.GetRows(1, 3);
|
||||
|
||||
// Then:
|
||||
// ... I should get back 3 rows
|
||||
Assert.Equal(3, rows.Length);
|
||||
|
||||
// ... The first row should reflect that there is an update pending
|
||||
// (More in depth testing is done in the RowUpdate class tests)
|
||||
var updatedRow = rows[0];
|
||||
Assert.Equal(EditRow.EditRowState.DirtyUpdate, updatedRow.State);
|
||||
Assert.Equal("foo", updatedRow.Cells[0].DisplayValue);
|
||||
|
||||
// ... The other rows should be clean
|
||||
for (int i = 1; i < rows.Length; i++)
|
||||
{
|
||||
Assert.Equal(EditRow.EditRowState.Clean, rows[i].State);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRowsPendingDeletion()
|
||||
{
|
||||
// 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.GetStandardMetadata(rs.Columns);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a row deletion
|
||||
s.DeleteRow(1);
|
||||
|
||||
// If: I ask for 3 rows from the session, skipping the first, including the updated one
|
||||
EditRow[] rows = await s.GetRows(1, 3);
|
||||
|
||||
// Then:
|
||||
// ... I should get back 3 rows
|
||||
Assert.Equal(3, rows.Length);
|
||||
|
||||
// ... The first row should reflect that there is an update pending
|
||||
// (More in depth testing is done in the RowUpdate class tests)
|
||||
var updatedRow = rows[0];
|
||||
Assert.Equal(EditRow.EditRowState.DirtyDelete, updatedRow.State);
|
||||
Assert.NotEmpty(updatedRow.Cells[0].DisplayValue);
|
||||
|
||||
// ... The other rows should be clean
|
||||
for (int i = 1; i < rows.Length; i++)
|
||||
{
|
||||
Assert.Equal(EditRow.EditRowState.Clean, rows[i].State);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRowsPendingInsertion()
|
||||
{
|
||||
// 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.GetStandardMetadata(rs.Columns);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a row creation
|
||||
s.CreateRow();
|
||||
|
||||
// If: I ask for the rows including the new rows
|
||||
EditRow[] rows = await s.GetRows(0, 6);
|
||||
|
||||
// Then:
|
||||
// ... I should get back 6 rows
|
||||
Assert.Equal(6, rows.Length);
|
||||
|
||||
// ... The last row should reflect that there's a new row
|
||||
var updatedRow = rows[5];
|
||||
Assert.Equal(EditRow.EditRowState.DirtyInsert, updatedRow.State);
|
||||
|
||||
// ... The other rows should be clean
|
||||
for (int i = 0; i < rows.Length - 1; i++)
|
||||
{
|
||||
Assert.Equal(EditRow.EditRowState.Clean, rows[i].State);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRowsAllNew()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a session with a query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetStandardMetadata(rs.Columns);
|
||||
EditSession s = new EditSession(rs, etm);
|
||||
|
||||
// ... Add a few row creations
|
||||
s.CreateRow();
|
||||
s.CreateRow();
|
||||
s.CreateRow();
|
||||
|
||||
// If: I ask for the rows included the new rows
|
||||
EditRow[] rows = await s.GetRows(5, 5);
|
||||
|
||||
// Then:
|
||||
// ... I should get back 3 rows back
|
||||
Assert.Equal(3, rows.Length);
|
||||
|
||||
// ... All the rows should be new
|
||||
Assert.All(rows, r => Assert.Equal(EditRow.EditRowState.DirtyInsert, r.State));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Script Edits Tests
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -142,8 +142,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: Messages should be null and subset should not be null
|
||||
Assert.Null(r.Message);
|
||||
// Then: Subset should not be null
|
||||
Assert.NotNull(r.ResultSubset);
|
||||
}).Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
@@ -159,12 +158,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: Messages should not be null and the subset should be null
|
||||
Assert.NotNull(r.Message);
|
||||
Assert.Null(r.ResultSubset);
|
||||
}).Complete();
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
@@ -185,12 +180,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
// ... And I then ask for a valid set of results from it
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: There should not be a subset and message should not be null
|
||||
Assert.NotNull(r.Message);
|
||||
Assert.Null(r.ResultSubset);
|
||||
}).Complete();
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
@@ -210,12 +201,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
// ... And I then ask for a set of results from it
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: There should be an error message and no subset
|
||||
Assert.NotNull(r.Message);
|
||||
Assert.Null(r.ResultSubset);
|
||||
}).Complete();
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
@@ -13,27 +15,368 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
/// </summary>
|
||||
public class LongListTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Add and remove and item in a LongList
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LongListTest()
|
||||
public void LongListConstruction()
|
||||
{
|
||||
var longList = new LongList<char>();
|
||||
longList.Add('.');
|
||||
Assert.True(longList.Count == 1);
|
||||
longList.RemoveAt(0);
|
||||
Assert.True(longList.Count == 0);
|
||||
// If: I construct a new long list
|
||||
LongList<char> ll = new LongList<char>();
|
||||
|
||||
// Then:
|
||||
// ... There should be no values in the list
|
||||
Assert.Equal(0, ll.Count);
|
||||
}
|
||||
|
||||
#region GetItem / Add Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1L)] // Negative index
|
||||
[InlineData(0L)] // Index equal to count of elements
|
||||
[InlineData(100L)] // Index larger than elements
|
||||
public void GetItemOutOfRange(long index)
|
||||
{
|
||||
// If: I construct a new long list
|
||||
LongList<char> ll = new LongList<char>();
|
||||
|
||||
// Then:
|
||||
// ... There should be no values in the list
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ll[index]);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ll.GetItem(index));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)] // Element at beginning
|
||||
[InlineData(1)] // Element in middle
|
||||
[InlineData(2)] // Element at end
|
||||
public void GetItemNotExpanded(long index)
|
||||
{
|
||||
// If: I construct a new long list with a couple items in it
|
||||
LongList<int> ll = new LongList<int> {0, 1, 2};
|
||||
|
||||
// Then: I can read back the value from the list
|
||||
Assert.Equal(3, ll.Count);
|
||||
Assert.Equal(index, ll[index]);
|
||||
Assert.Equal(index, ll.GetItem(index));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetItemExanded()
|
||||
{
|
||||
// If: I construct a new long list that is guaranteed to have been expanded
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... All the added values should be accessible
|
||||
Assert.Equal(10, ll.Count);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Assert.Equal(i, ll[i]);
|
||||
Assert.Equal(i, ll.GetItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SetItem Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1L)] // Negative index
|
||||
[InlineData(0L)] // Index equal to count of elements
|
||||
[InlineData(100L)] // Index larger than elements
|
||||
public void SetItemOutOfRange(long index)
|
||||
{
|
||||
// If: I construct a new long list
|
||||
LongList<int> ll = new LongList<int>();
|
||||
|
||||
// Then:
|
||||
// ... There should be no values in the list
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ll[index] = 8);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ll.SetItem(index, 8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetItemNotExpanded()
|
||||
{
|
||||
// If:
|
||||
// ... I construct a new long list with a few items in it
|
||||
// ... And I set all values to new values
|
||||
LongList<int> ll = new LongList<int> {0, 1, 2};
|
||||
for (int i = 0; i < ll.Count; i++)
|
||||
{
|
||||
ll.SetItem(i, 8);
|
||||
}
|
||||
|
||||
// Then: All values in the list should be 8
|
||||
Assert.All(ll, i => Assert.Equal(8, i));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetItemIndexerNotExpanded()
|
||||
{
|
||||
// If:
|
||||
// ... I construct a new long list with a few items in it
|
||||
// ... And I set all values to new values
|
||||
LongList<int> ll = new LongList<int> {0, 1, 2};
|
||||
for (int i = 0; i < ll.Count; i++)
|
||||
{
|
||||
ll[i] = 8;
|
||||
}
|
||||
|
||||
// Then: All values in the list should be 8
|
||||
Assert.All(ll, i => Assert.Equal(8, i));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetItemExpanded()
|
||||
{
|
||||
// If:
|
||||
// ... I construct a new long list that is guaranteed to have been expanded
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// ... And reset all the values to 8
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll.SetItem(i, 8);
|
||||
}
|
||||
|
||||
// Then: All values in the list should be 8
|
||||
Assert.All(ll, i => Assert.Equal(8, i));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetItemIndexerExpanded()
|
||||
{
|
||||
// If:
|
||||
// ... I construct a new long list that is guaranteed to have been expanded
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// ... And reset all the values to 8
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll[i] = 8;
|
||||
}
|
||||
|
||||
// Then: All values in the list should be 8
|
||||
Assert.All(ll, i => Assert.Equal(8, i));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RemoveAt Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1L)] // Negative index
|
||||
[InlineData(0L)] // Index equal to count of elements
|
||||
[InlineData(100L)] // Index larger than elements
|
||||
public void RemoveOutOfRange(long index)
|
||||
{
|
||||
// If: I construct a new long list
|
||||
LongList<char> ll = new LongList<char>();
|
||||
|
||||
// Then:
|
||||
// ... There should be no values in the list
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ll.RemoveAt(index));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)] // Remove at beginning of list
|
||||
[InlineData(2)] // Remove from middle of list
|
||||
[InlineData(4)] // Remove at end of list
|
||||
public void RemoveAtNotExpanded(long index)
|
||||
{
|
||||
// If:
|
||||
// ... I create a long list with a few elements in it (and one element that will be removed)
|
||||
LongList<int> ll = new LongList<int>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ll.Add(i == index ? 1 : 8);
|
||||
}
|
||||
|
||||
// ... And I delete an element
|
||||
ll.RemoveAt(index);
|
||||
|
||||
// Then:
|
||||
// ... The count should have subtracted
|
||||
Assert.Equal(4, ll.Count);
|
||||
|
||||
// ... All values should be 8 since we removed the 1
|
||||
Assert.All(ll, i => Assert.Equal(8, i));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAtExpanded()
|
||||
{
|
||||
// If:
|
||||
// ... I create a long list that is guaranteed to be expanded
|
||||
// (Created with 2x the values, evaluate the )
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// ... And I delete all of the first half of values
|
||||
// (we're doing this backwards to make sure remove works at different points in the list)
|
||||
for (int i = 9; i >= 0; i--)
|
||||
{
|
||||
ll.RemoveAt(i);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... The second half of the values should still remain
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Assert.Equal(i, ll[i]);
|
||||
}
|
||||
|
||||
// If:
|
||||
// ... I then proceed to add elements onto the end again
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// Then: All the elements should be there, in order
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
int index = j * 10 + i;
|
||||
Assert.Equal(i, ll[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Tests
|
||||
|
||||
[Fact]
|
||||
public void GetEnumerator()
|
||||
{
|
||||
// Setup: Create a long list with a handful of elements
|
||||
LongList<int> ll = new LongList<int>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// If: I get iterate over the list via GetEnumerator
|
||||
// Then: All the elements should be returned, in order
|
||||
int val = 0;
|
||||
foreach (int element in ll)
|
||||
{
|
||||
Assert.Equal(val++, element);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEnumeratorExpanded()
|
||||
{
|
||||
// Setup: Create a long list with a handful of elements
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// If: I get iterate over the list via GetEnumerator
|
||||
// Then: All the elements should be returned, in order
|
||||
int val = 0;
|
||||
foreach (int element in ll)
|
||||
{
|
||||
Assert.Equal(val++, element);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Negative
|
||||
[InlineData(5)] // Equal to count
|
||||
[InlineData(100)] // Far too large
|
||||
public void LongSkipOutOfRange(long index)
|
||||
{
|
||||
// Setup: Create a long list with a handful of elements
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// If: I attempt to skip ahead by a value that is out of range
|
||||
// Then: I should get an exception
|
||||
// NOTE: We must do the .ToList in order to evaluate the LongSkip since it is implemented
|
||||
// with a yield return
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ll.LongSkip(index).ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)] // Don't actually skip anything
|
||||
[InlineData(2)] // Skip within the short list
|
||||
public void LongSkip(long index)
|
||||
{
|
||||
// Setup: Create a long list with a handful of elements
|
||||
LongList<int> ll = new LongList<int>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// If: I skip ahead by a few elements and get all elements in an array
|
||||
int[] values = ll.LongSkip(index).ToArray();
|
||||
|
||||
// Then: The elements including the skip start index should be in the output
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
Assert.Equal(ll[i+index], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)] // Don't actually skip anything
|
||||
[InlineData(1)] // Skip within the short list
|
||||
[InlineData(3)] // Skip across expanded lists
|
||||
public void LongSkipExpanded(long index)
|
||||
{
|
||||
// Setup: Create a long list with a handful of elements
|
||||
LongList<int> ll = new LongList<int> {ExpandListSize = 2};
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ll.Add(i);
|
||||
}
|
||||
|
||||
// If: I skip ahead by a few elements and get all elements in an array
|
||||
int[] values = ll.LongSkip(index).ToArray();
|
||||
|
||||
// Then: The elements including the skip start index should be in the output
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
Assert.Equal(ll[i+index], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Add and remove and item in a LongList causing an expansion
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LongListExpandTest()
|
||||
{
|
||||
var longList = new LongList<int>();
|
||||
longList.ExpandListSize = 3;
|
||||
var longList = new LongList<int> {ExpandListSize = 3};
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
longList.Add(i);
|
||||
|
||||
Reference in New Issue
Block a user