Skip to content

Commit

Permalink
SmartContract: add FindOptions.Backwards to iterate in reverse order (#…
Browse files Browse the repository at this point in the history
…2819)

* SmartContract: add FindOptions.Backwards to iterate in reverse order

Depending on the data scheme contracts may want to iterate over prefix in
descending order. Fixes #2789.

* Ternary!

Syntax sweetener.

Co-authored-by: Anna Shaleva <shaleva.ann@gmail.com>

* Update src/Neo/SmartContract/FindOptions.cs

Co-authored-by: Erik Zhang <erik@neo.org>

* Persistence: move deferred part of the Find to a method of its own

And add a test for null/empty cases.

---------

Co-authored-by: Anna Shaleva <shaleva.ann@gmail.com>
Co-authored-by: Shargon <shargon@gmail.com>
Co-authored-by: Erik Zhang <erik@neo.org>
  • Loading branch information
4 people authored Apr 12, 2023
1 parent 5f36a03 commit d6620cb
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 6 deletions.
35 changes: 32 additions & 3 deletions src/Neo/Persistence/DataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,42 @@ public void Delete(StorageKey key)
/// Finds the entries starting with the specified prefix.
/// </summary>
/// <param name="key_prefix">The prefix of the key.</param>
/// <param name="direction">The search direction.</param>
/// <returns>The entries found with the desired prefix.</returns>
public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[] key_prefix = null)
public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[] key_prefix = null, SeekDirection direction = SeekDirection.Forward)
{
foreach (var (key, value) in Seek(key_prefix, SeekDirection.Forward))
var seek_prefix = key_prefix;
if (direction == SeekDirection.Backward)
{
if (key_prefix == null || key_prefix.Length == 0)
{ // Backwards seek for zero prefix is not supported for now.
throw new ArgumentException();
}
seek_prefix = null;
for (int i = key_prefix.Length - 1; i >= 0; i--)
{
if (key_prefix[i] < 0xff)
{
seek_prefix = key_prefix.Take(i + 1).ToArray();
// The next key after the key_prefix.
seek_prefix[i]++;
break;
}
}
if (seek_prefix == null)
{
throw new ArgumentException();
}
}
return FindInternal(key_prefix, seek_prefix, direction);
}

private IEnumerable<(StorageKey Key, StorageItem Value)> FindInternal(byte[] key_prefix, byte[] seek_prefix, SeekDirection direction)
{
foreach (var (key, value) in Seek(seek_prefix, direction))
if (key.ToArray().AsSpan().StartsWith(key_prefix))
yield return (key, value);
else
else if (direction == SeekDirection.Forward || !key.ToArray().SequenceEqual(seek_prefix))
yield break;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Neo/SmartContract/ApplicationEngine.Storage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Persistence;
using Neo.SmartContract.Iterators;
using Neo.SmartContract.Native;
using System;
Expand Down Expand Up @@ -152,7 +153,8 @@ protected internal IIterator Find(StorageContext context, byte[] prefix, FindOpt
if ((options.HasFlag(FindOptions.PickField0) || options.HasFlag(FindOptions.PickField1)) && !options.HasFlag(FindOptions.DeserializeValues))
throw new ArgumentException(null, nameof(options));
byte[] prefix_key = StorageKey.CreateSearchPrefix(context.Id, prefix);
return new StorageIterator(Snapshot.Find(prefix_key).GetEnumerator(), prefix.Length, options);
SeekDirection direction = options.HasFlag(FindOptions.Backwards) ? SeekDirection.Backward : SeekDirection.Forward;
return new StorageIterator(Snapshot.Find(prefix_key, direction).GetEnumerator(), prefix.Length, options);
}

/// <summary>
Expand Down
7 changes: 6 additions & 1 deletion src/Neo/SmartContract/FindOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ public enum FindOptions : byte
/// </summary>
PickField1 = 1 << 5,

/// <summary>
/// Indicates that results should be returned in backwards (descending) order.
/// </summary>
Backwards = 1 << 7,

/// <summary>
/// This value is only for internal use, and shouldn't be used in smart contracts.
/// </summary>
All = KeysOnly | RemovePrefix | ValuesOnly | DeserializeValues | PickField0 | PickField1
All = KeysOnly | RemovePrefix | ValuesOnly | DeserializeValues | PickField0 | PickField1 | Backwards
}
}
43 changes: 42 additions & 1 deletion tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,52 @@ public void TestFind()
store.Put(key3.ToArray(), value3.ToArray());
store.Put(key4.ToArray(), value4.ToArray());

var items = myDataCache.Find(key1.ToArray());
var k1 = key1.ToArray();
var items = myDataCache.Find(k1);
key1.Should().Be(items.ElementAt(0).Key);
value1.Should().Be(items.ElementAt(0).Value);
items.Count().Should().Be(1);

// null and empty with the forward direction -> finds everything.
items = myDataCache.Find(null);
items.Count().Should().Be(4);
items = myDataCache.Find(new byte[] { });
items.Count().Should().Be(4);

// null and empty with the backwards direction -> miserably fails.
Action action = () => myDataCache.Find(null, SeekDirection.Backward);
action.Should().Throw<ArgumentException>();
action = () => myDataCache.Find(new byte[] { }, SeekDirection.Backward);
action.Should().Throw<ArgumentException>();

items = myDataCache.Find(k1, SeekDirection.Backward);
key1.Should().Be(items.ElementAt(0).Key);
value1.Should().Be(items.ElementAt(0).Value);
items.Count().Should().Be(1);

var prefix = k1.Take(k1.Count() - 1).ToArray(); // Just the "key" part to match everything.
items = myDataCache.Find(prefix);
items.Count().Should().Be(4);
key1.Should().Be(items.ElementAt(0).Key);
value1.Should().Be(items.ElementAt(0).Value);
key2.Should().Be(items.ElementAt(1).Key);
value2.Should().Be(items.ElementAt(1).Value);
key3.Should().Be(items.ElementAt(2).Key);
value3.EqualsTo(items.ElementAt(2).Value).Should().BeTrue();
key4.Should().Be(items.ElementAt(3).Key);
value4.EqualsTo(items.ElementAt(3).Value).Should().BeTrue();

items = myDataCache.Find(prefix, SeekDirection.Backward);
items.Count().Should().Be(4);
key4.Should().Be(items.ElementAt(0).Key);
value4.EqualsTo(items.ElementAt(0).Value).Should().BeTrue();
key3.Should().Be(items.ElementAt(1).Key);
value3.EqualsTo(items.ElementAt(1).Value).Should().BeTrue();
key2.Should().Be(items.ElementAt(2).Key);
value2.Should().Be(items.ElementAt(2).Value);
key1.Should().Be(items.ElementAt(3).Key);
value1.Should().Be(items.ElementAt(3).Value);

items = myDataCache.Find(key5.ToArray());
items.Count().Should().Be(0);
}
Expand Down

0 comments on commit d6620cb

Please sign in to comment.