Skip to content

Commit 0512143

Browse files
author
Gabriel Erzse
committed
Add support for the BLMPOP command
Issue #248
1 parent 695d0c9 commit 0512143

File tree

6 files changed

+208
-5
lines changed

6 files changed

+208
-5
lines changed

src/NRedisStack/CoreCommands/CoreCommandBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,29 @@ public static SerializedCommand BZPopMax(RedisKey[] keys, double timeout)
5454
return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BZPOPMAX, keys, timeout);
5555
}
5656

57+
public static SerializedCommand BLMPop(double timeout, RedisKey[] keys, ListSide listSide, long? count)
58+
{
59+
if (keys.Length == 0)
60+
{
61+
throw new ArgumentException("At least one key must be provided.");
62+
}
63+
64+
List<object> args = new List<object>();
65+
66+
args.Add(timeout);
67+
args.Add(keys.Length);
68+
args.AddRange(keys.Cast<object>());
69+
args.Add(listSide == ListSide.Left ? CoreArgs.LEFT : CoreArgs.RIGHT);
70+
71+
if (count != null)
72+
{
73+
args.Add(CoreArgs.COUNT);
74+
args.Add(count);
75+
}
76+
77+
return new SerializedCommand(RedisCoreCommands.BLMPOP, args);
78+
}
79+
5780
public static SerializedCommand BLPop(RedisKey[] keys, double timeout)
5881
{
5982
return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BLPOP, keys, timeout);

src/NRedisStack/CoreCommands/CoreCommands.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,61 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val
176176
return BZPopMax(db, new[] { key }, timeout);
177177
}
178178

179+
/// <summary>
180+
/// The BLMPOP command.
181+
/// <p/>
182+
/// Removes and returns up to <paramref name="count"/> entries from the first non-empty list in
183+
/// <paramref name="keys"/>. If none of the lists contain elements, the call blocks on the server until elements
184+
/// become available, or the given <paramref name="timeout"/> expires. A <paramref name="timeout"/> of <c>0</c>
185+
/// means to wait indefinitely server-side. Returns <c>null</c> if the server timeout expires.
186+
/// <p/>
187+
/// When using this, pay attention to the timeout configured in the client, on the
188+
/// <see cref="ConnectionMultiplexer"/>, which by default can be too small:
189+
/// <code>
190+
/// ConfigurationOptions configurationOptions = new ConfigurationOptions();
191+
/// configurationOptions.SyncTimeout = 120000; // set a meaningful value here
192+
/// configurationOptions.EndPoints.Add("localhost");
193+
/// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions);
194+
/// </code>
195+
/// If the connection multiplexer timeout expires in the client, a <c>StackExchange.Redis.RedisTimeoutException</c>
196+
/// is thrown.
197+
/// <p/>
198+
/// This is an extension method added to the <see cref="IDatabase"/> class, for convenience.
199+
/// </summary>
200+
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
201+
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
202+
/// <param name="keys">The keys to check.</param>
203+
/// <param name="listSide">Specify from which end of the list to pop values: left or right.</param>
204+
/// <param name="count">The maximum number of records to pop. If set to <c>null</c> then the server default
205+
/// will be used.</param>
206+
/// <returns>A collection of values, together with the key they were popped from, or <c>null</c> if the
207+
/// server timeout expires.</returns>
208+
/// <remarks><seealso href="https://redis.io/commands/blmpop"/></remarks>
209+
public static Tuple<RedisKey, List<RedisValue>>? BLMPop(this IDatabase db, double timeout, RedisKey[] keys, ListSide listSide, long? count = null)
210+
{
211+
var command = CoreCommandBuilder.BLMPop(timeout, keys, listSide, count);
212+
return db.Execute(command).ToListPopResults();
213+
}
214+
215+
/// <summary>
216+
/// Syntactic sugar for
217+
/// <see cref="BLMPop(StackExchange.Redis.IDatabase,double,StackExchange.Redis.RedisKey[],StackExchange.Redis.ListSide,System.Nullable{long})"/>,
218+
/// where only one key is used.
219+
/// </summary>
220+
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
221+
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
222+
/// <param name="key">The key to check.</param>
223+
/// <param name="listSide">Specify from which end of the list to pop values: left or right.</param>
224+
/// <param name="count">The maximum number of records to pop. If set to <c>null</c> then the server default
225+
/// will be used.</param>
226+
/// <returns>A collection of values, together with the key they were popped from, or <c>null</c> if the
227+
/// server timeout expires.</returns>
228+
/// <remarks><seealso href="https://redis.io/commands/blmpop"/></remarks>
229+
public static Tuple<RedisKey, List<RedisValue>>? BLMPop(this IDatabase db, double timeout, RedisKey key, ListSide listSide, long? count = null)
230+
{
231+
return BLMPop(db, timeout, new[] { key }, listSide, count);
232+
}
233+
179234
/// <summary>
180235
/// The BLPOP command.
181236
/// <p/>

src/NRedisStack/CoreCommands/Literals/CommandArgs.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ namespace NRedisStack.Core.Literals
33
internal static class CoreArgs
44
{
55
public const string COUNT = "COUNT";
6-
public const string lib_name = "LIB-NAME";
7-
public const string lib_ver = "LIB-VER";
6+
public const string LEFT = "LEFT";
87
public const string MAX = "MAX";
98
public const string MIN = "MIN";
9+
public const string RIGHT = "RIGHT";
10+
public const string lib_name = "LIB-NAME";
11+
public const string lib_ver = "LIB-VER";
1012
}
1113
}

src/NRedisStack/CoreCommands/Literals/Commands.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace NRedisStack.Core.Literals
55
/// </summary>
66
internal static class RedisCoreCommands
77
{
8+
public const string BLMPOP = "BLMPOP";
89
public const string BLPOP = "BLPOP";
910
public const string BRPOP = "BRPOP";
1011
public const string BZMPOP = "BZMPOP";

src/NRedisStack/ResponseParser.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,5 +780,27 @@ public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult r
780780

781781
return new Tuple<RedisKey, RedisValue>(resultKey, value);
782782
}
783+
784+
public static Tuple<RedisKey, List<RedisValue>>? ToListPopResults(this RedisResult result)
785+
{
786+
if (result.IsNull)
787+
{
788+
return null;
789+
}
790+
791+
var resultArray = (RedisResult[])result!;
792+
var resultKey = resultArray[0].ToRedisKey();
793+
var resultSetItems = resultArray[1].ToArray();
794+
795+
List<RedisValue> values = new List<RedisValue>();
796+
797+
foreach (var resultSetItem in resultSetItems)
798+
{
799+
var value = (RedisValue)resultSetItem!;
800+
values.Add(value);
801+
}
802+
803+
return new Tuple<RedisKey, List<RedisValue>>(resultKey, values);
804+
}
783805
}
784806
}

tests/NRedisStack.Tests/Core Commands/CoreTests.cs

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ public void TestBZMPopNoKeysProvided()
252252
var db = redis.GetDatabase(null);
253253
db.Execute("FLUSHALL");
254254

255-
// Server would wait forever, but the multiplexer times out in 1 second.
256255
Assert.Throws<ArgumentException>(() => db.BZMPop(0, Array.Empty<RedisKey>(), MinMaxModifier.Min));
257256
}
258257

@@ -270,15 +269,15 @@ public void TestBZMPopWithOrderEnum()
270269
db.SortedSetAdd(sortedSetKey, "b", 5.1);
271270
db.SortedSetAdd(sortedSetKey, "c", 3.7);
272271

273-
// Pop two items with default order, which means it will pop the minimum values.
272+
// Pop two items with Ascending order, which means it will pop the minimum values.
274273
var resultWithDefaultOrder = db.BZMPop(0, sortedSetKey, Order.Ascending.ToMinMax());
275274

276275
Assert.NotNull(resultWithDefaultOrder);
277276
Assert.Equal(sortedSetKey, resultWithDefaultOrder!.Item1);
278277
Assert.Single(resultWithDefaultOrder.Item2);
279278
Assert.Equal("a", resultWithDefaultOrder.Item2[0].Value.ToString());
280279

281-
// Pop one more item, with descending order, which means it will pop the maximum value.
280+
// Pop one more item, with Descending order, which means it will pop the maximum value.
282281
var resultWithDescendingOrder = db.BZMPop(0, sortedSetKey, Order.Descending.ToMinMax());
283282

284283
Assert.NotNull(resultWithDescendingOrder);
@@ -407,6 +406,107 @@ public void TestBZPopMaxMultipleSets()
407406
Assert.Equal("b", result.Item2.Value.ToString());
408407
}
409408

409+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")]
410+
public void TestBLMPop()
411+
{
412+
var redis = ConnectionMultiplexer.Connect("localhost");
413+
414+
var db = redis.GetDatabase(null);
415+
db.Execute("FLUSHALL");
416+
417+
db.ListRightPush("my-list", "a");
418+
db.ListRightPush("my-list", "b");
419+
db.ListRightPush("my-list", "c");
420+
db.ListRightPush("my-list", "d");
421+
db.ListRightPush("my-list", "e");
422+
423+
// Pop two items from the left side.
424+
var resultWithDefaultOrder = db.BLMPop(0, "my-list", ListSide.Left, 2);
425+
426+
Assert.NotNull(resultWithDefaultOrder);
427+
Assert.Equal("my-list", resultWithDefaultOrder!.Item1);
428+
Assert.Equal(2, resultWithDefaultOrder.Item2.Count);
429+
Assert.Equal("a", resultWithDefaultOrder.Item2[0].ToString());
430+
Assert.Equal("b", resultWithDefaultOrder.Item2[1].ToString());
431+
432+
// Pop one more item, from the right side.
433+
var resultWithDescendingOrder = db.BLMPop(0, "my-list", ListSide.Right, 1);
434+
435+
Assert.NotNull(resultWithDescendingOrder);
436+
Assert.Equal("my-list", resultWithDescendingOrder!.Item1);
437+
Assert.Single(resultWithDescendingOrder.Item2);
438+
Assert.Equal("e", resultWithDescendingOrder.Item2[0].ToString());
439+
}
440+
441+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")]
442+
public void TestBLMPopNull()
443+
{
444+
var redis = ConnectionMultiplexer.Connect("localhost");
445+
446+
var db = redis.GetDatabase(null);
447+
db.Execute("FLUSHALL");
448+
449+
// Nothing in the list, and a short server timeout, which yields null.
450+
var result = db.BLMPop(0.5, "my-list", ListSide.Left);
451+
452+
Assert.Null(result);
453+
}
454+
455+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")]
456+
public void TestBLMPopMultipleLists()
457+
{
458+
var redis = ConnectionMultiplexer.Connect("localhost");
459+
460+
var db = redis.GetDatabase(null);
461+
db.Execute("FLUSHALL");
462+
463+
db.ListRightPush("list-one", "a");
464+
db.ListRightPush("list-one", "b");
465+
db.ListRightPush("list-one", "c");
466+
db.ListRightPush("list-two", "d");
467+
db.ListRightPush("list-two", "e");
468+
469+
var result = db.BLMPop(0, "list-two", ListSide.Right);
470+
471+
Assert.NotNull(result);
472+
Assert.Equal("list-two", result!.Item1);
473+
Assert.Single(result.Item2);
474+
Assert.Equal("e", result.Item2[0].ToString());
475+
476+
result = db.BLMPop(0, new[] { new RedisKey("list-two"), new RedisKey("list-one") }, ListSide.Left);
477+
478+
Assert.NotNull(result);
479+
Assert.Equal("list-two", result!.Item1);
480+
Assert.Single(result.Item2);
481+
Assert.Equal("d", result.Item2[0].ToString());
482+
483+
result = db.BLMPop(0, new[] { new RedisKey("list-two"), new RedisKey("list-one") }, ListSide.Right);
484+
485+
Assert.NotNull(result);
486+
Assert.Equal("list-one", result!.Item1);
487+
Assert.Single(result.Item2);
488+
Assert.Equal("c", result.Item2[0].ToString());
489+
490+
result = db.BLMPop(0, "list-one", ListSide.Left, count: 2);
491+
492+
Assert.NotNull(result);
493+
Assert.Equal("list-one", result!.Item1);
494+
Assert.Equal(2, result.Item2.Count);
495+
Assert.Equal("a", result.Item2[0].ToString());
496+
Assert.Equal("b", result.Item2[1].ToString());
497+
}
498+
499+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")]
500+
public void TestBLMPopNoKeysProvided()
501+
{
502+
var redis = ConnectionMultiplexer.Connect("localhost");
503+
504+
var db = redis.GetDatabase(null);
505+
db.Execute("FLUSHALL");
506+
507+
Assert.Throws<ArgumentException>(() => db.BLMPop(0, Array.Empty<RedisKey>(), ListSide.Left));
508+
}
509+
410510
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
411511
public void TestBLPop()
412512
{

0 commit comments

Comments
 (0)