Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support LMOVE #2065

Merged
merged 13 commits into from
Apr 10, 2022
17 changes: 17 additions & 0 deletions src/StackExchange.Redis/Enums/ListSide.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace StackExchange.Redis
{
/// <summary>
/// Specifies what side of the list to refer to.
/// </summary>
public enum ListSide
{
/// <summary>
/// Referce to the head of the list.
/// </summary>
Left,
/// <summary>
/// Referce to the tail of the list.
/// </summary>
Right
Avital-Fine marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ internal enum RedisCommand
LINDEX,
LINSERT,
LLEN,
LMOVE,
LPOP,
LPUSH,
LPUSHX,
Expand Down
13 changes: 13 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,19 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>https://redis.io/commands/llen</remarks>
long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns and removes the first or last element of the list stored at <paramref name="sourceKey"/>, and pushes the element
/// as the first or last element of the list stored at <paramref name="destinationKey"/>.
/// </summary>
/// <param name="sourceKey">The key of the list to remove from.</param>
/// <param name="destinationKey">The key of the list to move to.</param>
/// <param name="whereFrom">What side of the <paramref name="sourceKey"/> list to remove from.</param>
/// <param name="whereTo">What side of the <paramref name="destinationKey"/> list to move to.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>The element being popped and pushed or null if there is no element to move.</returns>
/// <remarks>https://redis.io/commands/lmove</remarks>
RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide whereFrom, ListSide whereTo, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns the specified elements of the list stored at key.
/// The offsets start and stop are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on.
Expand Down
13 changes: 13 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,19 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>https://redis.io/commands/llen</remarks>
Task<long> ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns and removes the first/last element of the list stored at source, and pushes the element
/// at the first/last element of the list stored at destination.
/// </summary>
/// <param name="sourceKey">The key of the list to remove from.</param>
/// <param name="destinationKey">The key of the list to move to.</param>
/// <param name="whereFrom">What side of the list to remove from.</param>
/// <param name="whereTo">What side of the list to move to.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>The element being popped and pushed or null if there is no element to move.</returns>
/// <remarks>https://redis.io/commands/lmove</remarks>
Avital-Fine marked this conversation as resolved.
Show resolved Hide resolved
Task<RedisValue> ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide whereFrom, ListSide whereTo, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns the specified elements of the list stored at key.
/// The offsets start and stop are zero-based indexes, with 0 being the first element of the list (the head of the list), 1 being the next element and so on.
Expand Down
5 changes: 5 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,11 @@ public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None)
return Inner.ListLength(ToInner(key), flags);
}

public RedisValue ListMove(RedisKey source, RedisKey destination, ListSide whereFrom, ListSide whereTo, CommandFlags flags = CommandFlags.None)
{
return Inner.ListMove(ToInner(source), ToInner(destination), whereFrom, whereTo);
}

public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None)
{
return Inner.ListRange(ToInner(key), start, stop, flags);
Expand Down
5 changes: 5 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ public Task<long> ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlag
return Inner.ListLengthAsync(ToInner(key), flags);
}

public Task<RedisValue> ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide whereFrom, ListSide whereTo, CommandFlags flags = CommandFlags.None)
{
return Inner.ListMoveAsync(ToInner(sourceKey), ToInner(destinationKey), whereFrom, whereTo);
}

public Task<RedisValue[]> ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None)
{
return Inner.ListRangeAsync(ToInner(key), start, stop, flags);
Expand Down
5 changes: 5 additions & 0 deletions src/StackExchange.Redis/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ StackExchange.Redis.GeoUnit.Feet = 3 -> StackExchange.Redis.GeoUnit
StackExchange.Redis.GeoUnit.Kilometers = 1 -> StackExchange.Redis.GeoUnit
StackExchange.Redis.GeoUnit.Meters = 0 -> StackExchange.Redis.GeoUnit
StackExchange.Redis.GeoUnit.Miles = 2 -> StackExchange.Redis.GeoUnit
StackExchange.Redis.ListSide
StackExchange.Redis.ListSide.Left = 0 -> StackExchange.Redis.ListSide
StackExchange.Redis.ListSide.Right = 1 -> StackExchange.Redis.ListSide
StackExchange.Redis.HashEntry
StackExchange.Redis.HashEntry.Equals(StackExchange.Redis.HashEntry other) -> bool
StackExchange.Redis.HashEntry.HashEntry() -> void
Expand Down Expand Up @@ -535,6 +538,7 @@ StackExchange.Redis.IDatabase.KeyType(StackExchange.Redis.RedisKey key, StackExc
StackExchange.Redis.IDatabase.ListGetByIndex(StackExchange.Redis.RedisKey key, long index, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
StackExchange.Redis.IDatabase.ListInsertAfter(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pivot, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.ListInsertBefore(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pivot, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.ListMove(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.ListSide whereFrom, StackExchange.Redis.ListSide whereTo, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
StackExchange.Redis.IDatabase.ListLeftPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]
StackExchange.Redis.IDatabase.ListLeftPop(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
Expand Down Expand Up @@ -729,6 +733,7 @@ StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKe
StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[] values, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task<long>
StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[] values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>
StackExchange.Redis.IDatabaseAsync.ListLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>
StackExchange.Redis.IDatabaseAsync.ListMoveAsync(StackExchange.Redis.RedisKey sourceKey, StackExchange.Redis.RedisKey destinationKey, StackExchange.Redis.ListSide whereFrom, StackExchange.Redis.ListSide whereTo, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue>
StackExchange.Redis.IDatabaseAsync.ListRangeAsync(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]>
StackExchange.Redis.IDatabaseAsync.ListRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, long count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>
StackExchange.Redis.IDatabaseAsync.ListRightPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]>
Expand Down
12 changes: 12 additions & 0 deletions src/StackExchange.Redis/RedisDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,18 @@ public Task<long> ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlag
return ExecuteAsync(msg, ResultProcessor.Int64);
}

public RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide whereFrom, ListSide whereTo, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, whereFrom == ListSide.Left ? "Left" : "right", whereTo == ListSide.Left ? "Left" : "right");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be consistent in Left vs right here :)

return ExecuteSync(msg, ResultProcessor.RedisValue);
}

public Task<RedisValue> ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide whereFrom, ListSide whereTo, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, whereFrom == ListSide.Left ? "Left" : "right", whereTo == ListSide.Left ? "Left" : "right");
return ExecuteAsync(msg, ResultProcessor.RedisValue);
}

public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop);
Expand Down
7 changes: 7 additions & 0 deletions tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,13 @@ public void ListLength()
mock.Verify(_ => _.ListLength("prefix:key", CommandFlags.None));
}

[Fact]
public void ListMove()
{
wrapper.ListMove("key", "destination", ListSide.Left, ListSide.Right, CommandFlags.None);
mock.Verify(_ => _.ListMove("prefix:key", "prefix:destination", ListSide.Left, ListSide.Right, CommandFlags.None));
}

[Fact]
public void ListRange()
{
Expand Down
71 changes: 55 additions & 16 deletions tests/StackExchange.Redis.Tests/Lists.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void ListLeftPushEmptyValues()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = db.ListLeftPush(key, Array.Empty<RedisValue>(), When.Always, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -53,7 +53,7 @@ public void ListLeftPushKeyDoesNotExists()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = db.ListLeftPush(key, new RedisValue[] { "testvalue" }, When.Exists, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -66,7 +66,7 @@ public void ListLeftPushToExisitingKey()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = db.ListLeftPush(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -88,7 +88,7 @@ public void ListLeftPushMultipleToExisitingKey()
{
Skip.IfMissingFeature(conn, nameof(RedisFeatures.PushMultiple), f => f.PushMultiple);
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = db.ListLeftPush(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -110,7 +110,7 @@ public async Task ListLeftPushAsyncEmptyValues()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = await db.ListLeftPushAsync(key, Array.Empty<RedisValue>(), When.Always, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -123,7 +123,7 @@ public async Task ListLeftPushAsyncKeyDoesNotExists()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = await db.ListLeftPushAsync(key, new RedisValue[] { "testvalue" }, When.Exists, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -136,7 +136,7 @@ public async Task ListLeftPushAsyncToExisitingKey()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = await db.ListLeftPushAsync(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -158,7 +158,7 @@ public async Task ListLeftPushAsyncMultipleToExisitingKey()
{
Skip.IfMissingFeature(conn, nameof(RedisFeatures.PushMultiple), f => f.PushMultiple);
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = await db.ListLeftPushAsync(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -180,7 +180,7 @@ public void ListRightPushEmptyValues()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = db.ListRightPush(key, Array.Empty<RedisValue>(), When.Always, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -193,7 +193,7 @@ public void ListRightPushKeyDoesNotExists()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = db.ListRightPush(key, new RedisValue[] { "testvalue" }, When.Exists, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -206,7 +206,7 @@ public void ListRightPushToExisitingKey()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = db.ListRightPush(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -228,7 +228,7 @@ public void ListRightPushMultipleToExisitingKey()
{
Skip.IfMissingFeature(conn, nameof(RedisFeatures.PushMultiple), f => f.PushMultiple);
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = db.ListRightPush(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -250,7 +250,7 @@ public async Task ListRightPushAsyncEmptyValues()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = await db.ListRightPushAsync(key, Array.Empty<RedisValue>(), When.Always, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -263,7 +263,7 @@ public async Task ListRightPushAsyncKeyDoesNotExists()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);
var result = await db.ListRightPushAsync(key, new RedisValue[] { "testvalue" }, When.Exists, CommandFlags.None);
Assert.Equal(0, result);
Expand All @@ -276,7 +276,7 @@ public async Task ListRightPushAsyncToExisitingKey()
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = await db.ListRightPushAsync(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -298,7 +298,7 @@ public async Task ListRightPushAsyncMultipleToExisitingKey()
{
Skip.IfMissingFeature(conn, nameof(RedisFeatures.PushMultiple), f => f.PushMultiple);
var db = conn.GetDatabase();
RedisKey key = "testlist";
RedisKey key = Me();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these fixes btw!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

db.KeyDelete(key, CommandFlags.FireAndForget);

var pushResult = await db.ListRightPushAsync(key, new RedisValue[] { "testvalue1" }, CommandFlags.None);
Expand All @@ -313,5 +313,44 @@ public async Task ListRightPushAsyncMultipleToExisitingKey()
Assert.Equal("testvalue3", rangeResult[2]);
}
}

[Fact]
public async Task ListMove()
{
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey src = Me();
RedisKey dest = Me() + "dest";
db.KeyDelete(src, CommandFlags.FireAndForget);

var pushResult = await db.ListRightPushAsync(src, new RedisValue[] { "testvalue1" , "testvalue2" });
Assert.Equal(2, pushResult);

var rangeResult1 = db.ListMove(src, dest, ListSide.Left, ListSide.Right);
var rangeResult2 = db.ListMove(src, dest, ListSide.Left, ListSide.Left);
var rangeResult3 = db.ListMove(dest, src, ListSide.Right, ListSide.Right);
var rangeResult4 = db.ListMove(dest, src, ListSide.Right, ListSide.Left);
Assert.Equal("testvalue1" , rangeResult1);
Assert.Equal("testvalue2" , rangeResult2);
Assert.Equal("testvalue1" , rangeResult3);
Assert.Equal("testvalue2" , rangeResult4);
}
}

[Fact]
public void ListMoveKeyDoesNotExist()
{
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey src = Me();
RedisKey dest = Me() + "dest";
db.KeyDelete(src, CommandFlags.FireAndForget);

var rangeResult1 = db.ListMove(src, dest, ListSide.Left, ListSide.Right);
Assert.True(rangeResult1.IsNull);
}
Avital-Fine marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading