Files
sqltoolsservice/src/Microsoft.SqlTools.ServiceLayer/Utility/LongList.cs
Benjamin Russell d7ecfb1a87 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
2017-03-21 15:14:04 -07:00

337 lines
11 KiB
C#

//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Collection class that permits storage of over <c>int.MaxValue</c> items. This is performed
/// by using a 2D list of lists. The internal lists are only initialized as necessary. This
/// collection implements IEnumerable to make it easier to run LINQ queries against it.
/// </summary>
/// <remarks>
/// This class is based on code from $\Data Tools\SSMS_Main\sql\ssms\core\DataStorage\ArrayList64.cs
/// with additions to bring it up to .NET 4.5 standards
/// </remarks>
/// <typeparam name="T">Type of the values to store</typeparam>
public class LongList<T> : IEnumerable<T>
{
#region Member Variables
private List<List<T>> expandedList;
private readonly List<T> shortList;
#endregion
/// <summary>
/// Creates a new long list
/// </summary>
public LongList()
{
shortList = new List<T>();
ExpandListSize = int.MaxValue;
Count = 0;
}
#region Properties
/// <summary>
/// The total number of elements in the array
/// </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);
}
set
{
SetItem(index, 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
#region Public Methods
/// <summary>
/// Adds the specified value to the end of the list
/// </summary>
/// <param name="val">Value to add to the list</param>
/// <returns>Index of the item that was just added</returns>
public long Add(T val)
{
if (Count < this.ExpandListSize)
{
shortList.Add(val);
}
else // need to split values into several arrays
{
if (expandedList == null)
{
// very inefficient so delay as much as possible
// immediately add 0th array
expandedList = new List<List<T>> {shortList};
}
int arrayIndex = (int)(Count / this.ExpandListSize); // 0 based
List<T> arr;
if (expandedList.Count <= arrayIndex) // need to make a new array
{
arr = new List<T>();
expandedList.Add(arr);
}
else // use existing array
{
arr = expandedList[arrayIndex];
}
arr.Add(val);
}
return (++Count);
}
/// <summary>
/// Returns the item at the specified index
/// </summary>
/// <param name="index">Index of the item to return</param>
/// <returns>The item at the index specified</returns>
public T GetItem(long index)
{
Validate.IsWithinRange(nameof(index), index, 0, Count - 1);
T val = default(T);
if (Count < this.ExpandListSize)
{
int i32Index = Convert.ToInt32(index);
val = shortList[i32Index];
}
else
{
int iArray32Index = (int) (index / this.ExpandListSize);
if (expandedList.Count > iArray32Index)
{
List<T> arr = expandedList[iArray32Index];
int i32Index = (int) (index % this.ExpandListSize);
if (arr.Count > i32Index)
{
val = arr[i32Index];
}
}
}
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>
/// <param name="index">Index of the item to set</param>
/// <param name="value">The item to store at the index specified</param>
public void SetItem(long index, T value)
{
Validate.IsLessThan(nameof(index), index, Count);
if (Count <= this.ExpandListSize)
{
int i32Index = Convert.ToInt32(index);
shortList[i32Index] = value;
}
else
{
int iArray32Index = (int) (index / this.ExpandListSize);
List<T> arr = expandedList[iArray32Index];
int i32Index = (int)(index % this.ExpandListSize);
arr[i32Index] = value;
}
}
/// <summary>
/// Removes an item at the specified location and shifts all the items after the provided
/// index up by one.
/// </summary>
/// <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
shortList.RemoveAt(iArray32MemberIndex);
}
else // handle the case of multiple arrays
{
// find out which array it is in
int arrayIndex = (int) (index / this.ExpandListSize);
List<T> arr = expandedList[arrayIndex];
// find out index into this array
int iArray32MemberIndex = (int) (index % this.ExpandListSize);
arr.RemoveAt(iArray32MemberIndex);
// now shift members of the array back one
//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[0]);
arr2.RemoveAt(0);
if (arr2.Count == 0)
{
expandedList.RemoveAt(i);
}
}
}
--Count;
}
#endregion
#region IEnumerable<object> Implementation
/// <summary>
/// Returns a generic enumerator for enumeration of this LongList
/// </summary>
/// <returns>Enumerator for LongList</returns>
public IEnumerator<T> GetEnumerator()
{
return new LongListEnumerator<T>(this);
}
/// <summary>
/// Returns an enumerator for enumeration of this LongList
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
public class LongListEnumerator<TEt> : IEnumerator<TEt>
{
/// <summary>
/// The current list that we're iterating over.
/// </summary>
private readonly LongList<TEt> localList;
/// <summary>
/// The current index into the list
/// </summary>
private long index;
/// <summary>
/// Constructs a new enumerator for a given LongList
/// </summary>
/// <param name="list">The list to enumerate</param>
public LongListEnumerator(LongList<TEt> list)
{
localList = list;
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>
/// Returns the current item in the enumeration
/// </summary>
public TEt Current { get; private set; }
/// <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
/// </summary>
/// <returns>Whether or not the move was successful</returns>
public bool MoveNext()
{
Index++;
return Index < localList.Count;
}
/// <summary>
/// Resets the enumeration
/// </summary>
public void Reset()
{
Index = 0;
Current = default(TEt);
}
/// <summary>
/// Disposal method. Does nothing.
/// </summary>
public void Dispose()
{
}
#endregion
}
}
}