Skip to content

Commit

Permalink
Add LRANGE and LLEN commands to GarnetClient (#453)
Browse files Browse the repository at this point in the history
* Add LRANGE command

* Refactoring

* Add LLEN command

* Remove extra whitespace

---------

Co-authored-by: Badrish Chandramouli <badrishc@microsoft.com>
  • Loading branch information
hishamco and badrishc authored Jun 11, 2024
1 parent 361f26a commit 5916135
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 53 deletions.
82 changes: 54 additions & 28 deletions libs/client/GarnetClientAPI/GarnetClientListCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,28 @@ public sealed partial class GarnetClient
{
private static readonly Memory<byte> LPUSH = "$5\r\nLPUSH\r\n"u8.ToArray();
private static readonly Memory<byte> RPUSH = "$5\r\nRPUSH\r\n"u8.ToArray();
private static readonly Memory<byte> LRANGE = "$6\r\nLRANGE\r\n"u8.ToArray();
private static readonly Memory<byte> LLEN = "$4\r\nLLEN\r\n"u8.ToArray();

/// <summary>
/// Add the specified element to the head of the list stored at key
/// Add the specified element to the head of the list stored at key.
/// </summary>
/// <param name="key">The key of the list</param>
/// <param name="element">The element to be added</param>
/// <param name="callback">The callback function when operation completes</param>
/// <param name="context">An optional context to correlate request to callback</param>
/// <param name="key">The key of the list.</param>
/// <param name="element">The element to be added.</param>
/// <param name="callback">The callback function when operation completes.</param>
/// <param name="context">An optional context to correlate request to callback.</param>
public void ListLeftPush(string key, string element, Action<long, long, string> callback, long context = 0)
{
ListLeftPush(key, new[] { element }, callback, context);
}

/// <summary>
/// Add the specified elements to the head of the list stored at key
/// Add the specified elements to the head of the list stored at key.
/// </summary>
/// <param name="key">The key of the list</param>
/// <param name="elements">The elements to be added</param>
/// <param name="callback">The callback function when operation completes</param>
/// <param name="context">An optional context to correlate request to callback</param>
/// <param name="key">The key of the list.</param>
/// <param name="elements">The elements to be added.</param>
/// <param name="callback">The callback function when operation completes.</param>
/// <param name="context">An optional context to correlate request to callback.</param>
public void ListLeftPush(string key, IEnumerable<string> elements, Action<long, long, string> callback, long context = 0)
{
ArgumentNullException.ThrowIfNull(key);
Expand All @@ -49,11 +51,11 @@ public void ListLeftPush(string key, IEnumerable<string> elements, Action<long,
}

/// <summary>
/// Asynchronously add the specified elements to the head of the list stored at key
/// Asynchronously add the specified elements to the head of the list stored at key.
/// </summary>
/// <param name="key">The key of the list</param>
/// <param name="elements">The elements to be added</param>
/// <returns>The number of list elements after the addition</returns>
/// <param name="key">The key of the list.</param>
/// <param name="elements">The elements to be added.</param>
/// <returns>The number of list elements after the addition.</returns>
public async Task<long> ListLeftPushAsync(string key, params string[] elements)
{
ArgumentNullException.ThrowIfNull(key);
Expand All @@ -68,24 +70,24 @@ public async Task<long> ListLeftPushAsync(string key, params string[] elements)
}

/// <summary>
/// Add the specified element to the tail of the list stored at key
/// Add the specified element to the tail of the list stored at key.
/// </summary>
/// <param name="key">The key of the list</param>
/// <param name="element">The element to be added</param>
/// <param name="callback">The callback function when operation completes</param>
/// <param name="context">An optional context to correlate request to callback</param>
/// <param name="key">The key of the list.</param>
/// <param name="element">The element to be added.</param>
/// <param name="callback">The callback function when operation completes.</param>
/// <param name="context">An optional context to correlate request to callback.</param>
public void ListRightPush(string key, string element, Action<long, long, string> callback, long context = 0)
{
ListRightPush(key, new[] { element }, callback, context);
}

/// <summary>
/// Add the specified elements to the tail of the list stored at key
/// Add the specified elements to the tail of the list stored at key.
/// </summary>
/// <param name="key">The key of the list</param>
/// <param name="elements">The elements to be added</param>
/// <param name="callback">The callback function when operation completes</param>
/// <param name="context">An optional context to correlate request to callback</param>
/// <param name="key">The key of the list.</param>
/// <param name="elements">The elements to be added.</param>
/// <param name="callback">The callback function when operation completes.</param>
/// <param name="context">An optional context to correlate request to callback.</param>
public void ListRightPush(string key, IEnumerable<string> elements, Action<long, long, string> callback, long context = 0)
{
ArgumentNullException.ThrowIfNull(key);
Expand All @@ -103,11 +105,11 @@ public void ListRightPush(string key, IEnumerable<string> elements, Action<long,
}

/// <summary>
/// Asynchronously add the specified elements to the tail of the list stored at key
/// Asynchronously add the specified elements to the tail of the list stored at key.
/// </summary>
/// <param name="key">The key of the list</param>
/// <param name="elements">The elements to be added</param>
/// <returns>The number of list elements after the addition</returns>
/// <param name="key">The key of the list.</param>
/// <param name="elements">The elements to be added.</param>
/// <returns>The number of list elements after the addition.</returns>
public async Task<long> ListRightPushAsync(string key, params string[] elements)
{
ArgumentNullException.ThrowIfNull(key);
Expand All @@ -120,5 +122,29 @@ public async Task<long> ListRightPushAsync(string key, params string[] elements)

return await ExecuteForLongResultAsync(nameof(RPUSH), [key, .. elements]);
}

/// <summary>
/// Gets the values of the specified list key.
/// </summary>
/// <param name="key">The key of the list.</param>
/// <param name="start">The offset start.</param>
/// <param name="stop">The offset stop.</param>
public async Task<string[]> ListRangeAsync(string key, int start, int stop)
{
ArgumentNullException.ThrowIfNull(key);

return await ExecuteForStringArrayResultAsync(nameof(LRANGE), [key, start.ToString(), stop.ToString()]);
}

/// <summary>
/// Gets the length of the list.
/// </summary>
/// <param name="key">The key of the list.</param>
public async Task<long> ListLengthAsync(string key)
{
ArgumentNullException.ThrowIfNull(key);

return await ExecuteForLongResultAsync(nameof(LLEN), [key]);
}
}
}
95 changes: 70 additions & 25 deletions test/Garnet.test/RespListGarnetClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Threading.Tasks;
using Garnet.client;
using NUnit.Framework;
using StackExchange.Redis;

namespace Garnet.test
{
Expand Down Expand Up @@ -41,19 +40,23 @@ public void Setup()
new object[] { "list2", new[] { "foo", "bar", "baz" }, new[] { "foo", "baz", "foo", "bar", "baz" } }
];

private static string GetTestKey(string key)
{
var testName = TestContext.CurrentContext.Test.MethodName;
return $"{testName}_{key}";
}
private static object[] ListRangeTestCases =
[
new object[] { 0, -1, new string[] { "foo", "bar", "baz" } },
new object[] { 0, 0, new string[] { "foo" } },
new object[] { 1, 2, new string[] { "bar", "baz" } },
new object[] { -3, 1, new string[] { "foo", "bar" } },
new object[] { -3, 2, new string[] { "foo", "bar", "baz" } },
new object[] { -100, 100, new string[] { "foo", "bar", "baz" } }
];

[Test]
[TestCaseSource(nameof(LeftPushTestCases))]
public void AddElementsToTheListHeadInBulk_WithCallback(string key, string[] elements, string[] expectedList)
public async Task AddElementsToTheListHeadInBulk_WithCallback(string key, string[] elements, string[] expectedList)
{
// Arrange
using var db = new GarnetClient(TestUtils.Address, TestUtils.Port);
db.Connect();
await db.ConnectAsync();

using ManualResetEventSlim e = new();

Expand All @@ -74,16 +77,16 @@ public void AddElementsToTheListHeadInBulk_WithCallback(string key, string[] ele
Assert.IsTrue(isResultSet);
Assert.AreEqual(expectedList.Length, actualListLength);

ValidateListContent(testKey, expectedList);
await ValidateListContentAsync(db, testKey, expectedList);
}

[Test]
[TestCaseSource(nameof(LeftPushTestCases))]
public void AddElementsToTheListHead_WithCallback(string key, string[] elements, string[] expectedList)
public async Task AddElementsToTheListHead_WithCallback(string key, string[] elements, string[] expectedList)
{
// Arrange
using var db = new GarnetClient(TestUtils.Address, TestUtils.Port);
db.Connect();
await db.ConnectAsync();

using ManualResetEventSlim e = new();

Expand All @@ -109,7 +112,7 @@ public void AddElementsToTheListHead_WithCallback(string key, string[] elements,
Assert.IsTrue(isResultSet);
Assert.AreEqual(expectedList.Length, actualListLength);

ValidateListContent(testKey, expectedList);
await ValidateListContentAsync(db, testKey, expectedList);
}

[Test]
Expand All @@ -126,16 +129,16 @@ public async Task AddElementsToTheListHead_WithAsync(string key, string[] elemen
var actualListLength = await db.ListLeftPushAsync(testKey, elements);
Assert.AreEqual(expectedList.Length, actualListLength);

ValidateListContent(testKey, expectedList);
await ValidateListContentAsync(db, testKey, expectedList);
}

[Test]
[TestCaseSource(nameof(RightPushTestCases))]
public void AddElementsToListTailInBulk_WithCallback(string key, string[] elements, string[] expectedList)
public async Task AddElementsToListTailInBulk_WithCallback(string key, string[] elements, string[] expectedList)
{
// Arrange
using var db = new GarnetClient(TestUtils.Address, TestUtils.Port);
db.Connect();
await db.ConnectAsync();

using ManualResetEventSlim e = new();

Expand All @@ -156,16 +159,16 @@ public void AddElementsToListTailInBulk_WithCallback(string key, string[] elemen
Assert.IsTrue(isResultSet);
Assert.AreEqual(expectedList.Length, actualListLength);

ValidateListContent(testKey, expectedList);
await ValidateListContentAsync(db, testKey, expectedList);
}

[Test]
[TestCaseSource(nameof(RightPushTestCases))]
public void AddElementsToListTail_WithCallback(string key, string[] elements, string[] expectedList)
public async Task AddElementsToListTail_WithCallback(string key, string[] elements, string[] expectedList)
{
// Arrange
using var db = new GarnetClient(TestUtils.Address, TestUtils.Port);
db.Connect();
await db.ConnectAsync();

using ManualResetEventSlim e = new();

Expand All @@ -191,7 +194,7 @@ public void AddElementsToListTail_WithCallback(string key, string[] elements, st
Assert.IsTrue(isResultSet);
Assert.AreEqual(expectedList.Length, actualListLength);

ValidateListContent(testKey, expectedList);
await ValidateListContentAsync(db, testKey, expectedList);
}

[Test]
Expand All @@ -208,17 +211,59 @@ public async Task AddElementsToTheListTail_WithAsync(string key, string[] elemen
var actualListLength = await db.ListRightPushAsync(testKey, elements.ToArray());
Assert.AreEqual(expectedList.Length, actualListLength);

ValidateListContent(testKey, expectedList);
await ValidateListContentAsync(db, testKey, expectedList);
}

[Test]
[TestCaseSource(nameof(ListRangeTestCases))]
public async Task GetListElements(int start, int stop, string[] expectedValues)
{
// Arrange
var testKey = GetTestKey("list1");
using var db = new GarnetClient(TestUtils.Address, TestUtils.Port);
await db.ConnectAsync();

await db.KeyDeleteAsync([testKey]);
await db.ExecuteForStringResultAsync("RPUSH", [testKey, "foo", "bar", "baz"]);

// Act
var values = await db.ListRangeAsync(testKey, start, stop);

// Assert
Assert.False(expectedValues.Length == 0);
Assert.AreEqual(expectedValues, values);
}

private void ValidateListContent(string key, string[] expectedList)
[Test]
public async Task GetListLength()
{
// Using SE.Redis client to validate list content since Garnet client doesn't yet support LRANGE
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
var db = redis.GetDatabase(0);
// Arrange
var testKey = GetTestKey("list1");
using var db = new GarnetClient(TestUtils.Address, TestUtils.Port);
await db.ConnectAsync();

await db.KeyDeleteAsync([testKey]);
await db.ExecuteForStringResultAsync("RPUSH", [testKey, "foo", "bar", "baz"]);

// Act
var length = await db.ListLengthAsync(testKey);

// Assert
Assert.AreEqual(3, length);
}

private static string GetTestKey(string key)
{
var testName = TestContext.CurrentContext.Test.MethodName;
return $"{testName}_{key}";
}

private static async Task ValidateListContentAsync(GarnetClient db, string key, string[] expectedList)
{
var actualElements = await db.ListRangeAsync(key, 0, -1);

var actualElements = db.ListRange(key);
Assert.AreEqual(expectedList.Length, actualElements.Length);

for (var i = 0; i < actualElements.Length; i++)
{
Assert.AreEqual(expectedList[i], actualElements[i].ToString());
Expand Down

0 comments on commit 5916135

Please sign in to comment.