diff --git a/src/NRedisStack/CoreCommands/CoreCommandBuilder.cs b/src/NRedisStack/CoreCommands/CoreCommandBuilder.cs index 6c71a8a2..5f8f62e1 100644 --- a/src/NRedisStack/CoreCommands/CoreCommandBuilder.cs +++ b/src/NRedisStack/CoreCommands/CoreCommandBuilder.cs @@ -21,7 +21,7 @@ public static SerializedCommand ClientSetInfo(SetInfoAttr attr, string value) return new SerializedCommand(RedisCoreCommands.CLIENT, RedisCoreCommands.SETINFO, attrValue, value); } - public static SerializedCommand BzmPop(double timeout, RedisKey[] keys, MinMaxModifier minMaxModifier, long? count) + public static SerializedCommand BZMPop(double timeout, RedisKey[] keys, MinMaxModifier minMaxModifier, long? count) { if (keys.Length == 0) { @@ -44,7 +44,17 @@ public static SerializedCommand BzmPop(double timeout, RedisKey[] keys, MinMaxMo return new SerializedCommand(RedisCoreCommands.BZMPOP, args); } - public static SerializedCommand BzPopMin(RedisKey[] keys, double timeout) + public static SerializedCommand BZPopMin(RedisKey[] keys, double timeout) + { + return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BZPOPMIN, keys, timeout); + } + + public static SerializedCommand BZPopMax(RedisKey[] keys, double timeout) + { + return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BZPOPMAX, keys, timeout); + } + + public static SerializedCommand BLMPop(double timeout, RedisKey[] keys, ListSide listSide, long? count) { if (keys.Length == 0) { @@ -52,13 +62,54 @@ public static SerializedCommand BzPopMin(RedisKey[] keys, double timeout) } List args = new List(); + + args.Add(timeout); + args.Add(keys.Length); args.AddRange(keys.Cast()); + args.Add(listSide == ListSide.Left ? CoreArgs.LEFT : CoreArgs.RIGHT); + + if (count != null) + { + args.Add(CoreArgs.COUNT); + args.Add(count); + } + + return new SerializedCommand(RedisCoreCommands.BLMPOP, args); + } + + public static SerializedCommand BLPop(RedisKey[] keys, double timeout) + { + return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BLPOP, keys, timeout); + } + + public static SerializedCommand BRPop(RedisKey[] keys, double timeout) + { + return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BRPOP, keys, timeout); + } + + public static SerializedCommand BLMove(RedisKey source, RedisKey destination, ListSide sourceSide, ListSide destinationSide, double timeout) + { + List args = new List(); + args.Add(source); + args.Add(destination); + args.Add(sourceSide == ListSide.Left ? CoreArgs.LEFT : CoreArgs.RIGHT); + args.Add(destinationSide == ListSide.Left ? CoreArgs.LEFT : CoreArgs.RIGHT); + args.Add(timeout); + + return new SerializedCommand(RedisCoreCommands.BLMOVE, args); + } + + public static SerializedCommand BRPopLPush(RedisKey source, RedisKey destination, double timeout) + { + List args = new List(); + args.Add(source); + args.Add(destination); args.Add(timeout); - return new SerializedCommand(RedisCoreCommands.BZPOPMIN, args); + return new SerializedCommand(RedisCoreCommands.BRPOPLPUSH, args); } - public static SerializedCommand BzPopMax(RedisKey[] keys, double timeout) + private static SerializedCommand BlockingCommandWithKeysAndTimeout(String command, RedisKey[] keys, double timeout) { if (keys.Length == 0) { @@ -69,7 +120,7 @@ public static SerializedCommand BzPopMax(RedisKey[] keys, double timeout) args.AddRange(keys.Cast()); args.Add(timeout); - return new SerializedCommand(RedisCoreCommands.BZPOPMAX, args); + return new SerializedCommand(command, args); } } } diff --git a/src/NRedisStack/CoreCommands/CoreCommands.cs b/src/NRedisStack/CoreCommands/CoreCommands.cs index 6b69074c..514f3176 100644 --- a/src/NRedisStack/CoreCommands/CoreCommands.cs +++ b/src/NRedisStack/CoreCommands/CoreCommands.cs @@ -53,15 +53,15 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val /// A collection of sorted set entries paired with their scores, together with the key they were popped /// from, or null if the server timeout expires. /// - public static Tuple>? BzmPop(this IDatabase db, double timeout, RedisKey[] keys, MinMaxModifier minMaxModifier, long? count = null) + public static Tuple>? BZMPop(this IDatabase db, double timeout, RedisKey[] keys, MinMaxModifier minMaxModifier, long? count = null) { - var command = CoreCommandBuilder.BzmPop(timeout, keys, minMaxModifier, count); + var command = CoreCommandBuilder.BZMPop(timeout, keys, minMaxModifier, count); return db.Execute(command).ToSortedSetPopResults(); } /// /// Syntactic sugar for - /// , + /// , /// where only one key is used. /// /// The class where this extension method is applied. @@ -74,9 +74,9 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val /// A collection of sorted set entries paired with their scores, together with the key they were popped /// from, or null if the server timeout expires. /// - public static Tuple>? BzmPop(this IDatabase db, double timeout, RedisKey key, MinMaxModifier minMaxModifier, long? count = null) + public static Tuple>? BZMPop(this IDatabase db, double timeout, RedisKey key, MinMaxModifier minMaxModifier, long? count = null) { - return BzmPop(db, timeout, new[] { key }, minMaxModifier, count); + return BZMPop(db, timeout, new[] { key }, minMaxModifier, count); } /// @@ -106,14 +106,14 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val /// A sorted set entry paired with its score, together with the key it was popped from, or null /// if the server timeout expires. /// - public static Tuple? BzPopMin(this IDatabase db, RedisKey[] keys, double timeout) + public static Tuple? BZPopMin(this IDatabase db, RedisKey[] keys, double timeout) { - var command = CoreCommandBuilder.BzPopMin(keys, timeout); + var command = CoreCommandBuilder.BZPopMin(keys, timeout); return db.Execute(command).ToSortedSetPopResult(); } /// - /// Syntactic sugar for , + /// Syntactic sugar for , /// where only one key is used. /// /// The class where this extension method is applied. @@ -122,9 +122,9 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val /// A sorted set entry paired with its score, together with the key it was popped from, or null /// if the server timeout expires. /// - public static Tuple? BzPopMin(this IDatabase db, RedisKey key, double timeout) + public static Tuple? BZPopMin(this IDatabase db, RedisKey key, double timeout) { - return BzPopMin(db, new[] { key }, timeout); + return BZPopMin(db, new[] { key }, timeout); } @@ -155,14 +155,14 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val /// A sorted set entry paired with its score, together with the key it was popped from, or null /// if the server timeout expires. /// - public static Tuple? BzPopMax(this IDatabase db, RedisKey[] keys, double timeout) + public static Tuple? BZPopMax(this IDatabase db, RedisKey[] keys, double timeout) { - var command = CoreCommandBuilder.BzPopMax(keys, timeout); + var command = CoreCommandBuilder.BZPopMax(keys, timeout); return db.Execute(command).ToSortedSetPopResult(); } /// - /// Syntactic sugar for , + /// Syntactic sugar for , /// where only one key is used. /// /// The class where this extension method is applied. @@ -171,9 +171,204 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val /// A sorted set entry paired with its score, together with the key it was popped from, or null /// if the server timeout expires. /// - public static Tuple? BzPopMax(this IDatabase db, RedisKey key, double timeout) + public static Tuple? BZPopMax(this IDatabase db, RedisKey key, double timeout) { - return BzPopMax(db, new[] { key }, timeout); + return BZPopMax(db, new[] { key }, timeout); + } + + /// + /// The BLMPOP command. + ///

+ /// Removes and returns up to entries from the first non-empty list in + /// . If none of the lists contain elements, the call blocks on the server until elements + /// become available, or the given expires. A of 0 + /// means to wait indefinitely server-side. Returns null if the server timeout expires. + ///

+ /// When using this, pay attention to the timeout configured in the client, on the + /// , which by default can be too small: + /// + /// ConfigurationOptions configurationOptions = new ConfigurationOptions(); + /// configurationOptions.SyncTimeout = 120000; // set a meaningful value here + /// configurationOptions.EndPoints.Add("localhost"); + /// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions); + /// + /// If the connection multiplexer timeout expires in the client, a StackExchange.Redis.RedisTimeoutException + /// is thrown. + ///

+ /// This is an extension method added to the class, for convenience. + ///

+ /// The class where this extension method is applied. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// The keys to check. + /// Specify from which end of the list to pop values: left or right. + /// The maximum number of records to pop. If set to null then the server default + /// will be used. + /// A collection of values, together with the key they were popped from, or null if the + /// server timeout expires. + /// + public static Tuple>? BLMPop(this IDatabase db, double timeout, RedisKey[] keys, ListSide listSide, long? count = null) + { + var command = CoreCommandBuilder.BLMPop(timeout, keys, listSide, count); + return db.Execute(command).ToListPopResults(); + } + + /// + /// Syntactic sugar for + /// , + /// where only one key is used. + /// + /// The class where this extension method is applied. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// The key to check. + /// Specify from which end of the list to pop values: left or right. + /// The maximum number of records to pop. If set to null then the server default + /// will be used. + /// A collection of values, together with the key they were popped from, or null if the + /// server timeout expires. + /// + public static Tuple>? BLMPop(this IDatabase db, double timeout, RedisKey key, ListSide listSide, long? count = null) + { + return BLMPop(db, timeout, new[] { key }, listSide, count); + } + + /// + /// The BLPOP command. + ///

+ /// Removes and returns an entry from the head (left side) of the first non-empty list in . + /// If none of the lists contain elements, the call blocks on the server until elements + /// become available, or the given expires. A of 0 + /// means to wait indefinitely server-side. Returns null if the server timeout expires. + ///

+ /// When using this, pay attention to the timeout configured in the client, on the + /// , which by default can be too small: + /// + /// ConfigurationOptions configurationOptions = new ConfigurationOptions(); + /// configurationOptions.SyncTimeout = 120000; // set a meaningful value here + /// configurationOptions.EndPoints.Add("localhost"); + /// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions); + /// + /// If the connection multiplexer timeout expires in the client, a StackExchange.Redis.RedisTimeoutException + /// is thrown. + ///

+ /// This is an extension method added to the class, for convenience. + ///

+ /// The class where this extension method is applied. + /// The keys to check. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// A value, together with the key it was popped from, or null if the server timeout + /// expires. + /// + public static Tuple? BLPop(this IDatabase db, RedisKey[] keys, double timeout) + { + var command = CoreCommandBuilder.BLPop(keys, timeout); + return db.Execute(command).ToListPopResult(); + } + + /// + /// Syntactic sugar for , + /// where only one key is used. + /// + /// The class where this extension method is applied. + /// The key to check. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// A value, together with the key it was popped from, or null if the server timeout + /// expires. + /// + public static Tuple? BLPop(this IDatabase db, RedisKey key, double timeout) + { + return BLPop(db, new[] { key }, timeout); + } + + /// + /// The BRPOP command. + ///

+ /// Removes and returns an entry from the tail (right side) of the first non-empty list in . + /// If none of the lists contain elements, the call blocks on the server until elements + /// become available, or the given expires. A of 0 + /// means to wait indefinitely server-side. Returns null if the server timeout expires. + ///

+ /// When using this, pay attention to the timeout configured in the client, on the + /// , which by default can be too small: + /// + /// ConfigurationOptions configurationOptions = new ConfigurationOptions(); + /// configurationOptions.SyncTimeout = 120000; // set a meaningful value here + /// configurationOptions.EndPoints.Add("localhost"); + /// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions); + /// + /// If the connection multiplexer timeout expires in the client, a StackExchange.Redis.RedisTimeoutException + /// is thrown. + ///

+ /// This is an extension method added to the class, for convenience. + ///

+ /// The class where this extension method is applied. + /// The keys to check. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// A value, together with the key it was popped from, or null if the server timeout + /// expires. + /// + public static Tuple? BRPop(this IDatabase db, RedisKey[] keys, double timeout) + { + var command = CoreCommandBuilder.BRPop(keys, timeout); + return db.Execute(command).ToListPopResult(); + } + + /// + /// Syntactic sugar for , + /// where only one key is used. + /// + /// The class where this extension method is applied. + /// The key to check. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// A value, together with the key it was popped from, or null if the server timeout + /// expires. + /// + public static Tuple? BRPop(this IDatabase db, RedisKey key, double timeout) + { + return BRPop(db, new[] { key }, timeout); + } + + /// + /// The BLMOVE command. + ///

+ /// Atomically returns and removes the first or last element of the list stored at + /// (depending on the value of ), and pushes the element as the first or last + /// element of the list stored at (depending on the value of + /// ). + ///

+ /// This is an extension method added to the class, for convenience. + ///

+ /// The class where this extension method is applied. + /// The key of the source list. + /// The key of the destination list. + /// What side of the list to remove from. + /// What side of the list to move to. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// The element being popped and pushed, or null if the server timeout expires. + /// + public static RedisValue? BLMove(this IDatabase db, RedisKey source, RedisKey destination, ListSide sourceSide, ListSide destinationSide, double timeout) + { + var command = CoreCommandBuilder.BLMove(source, destination, sourceSide, destinationSide, timeout); + return db.Execute(command).ToRedisValue(); + } + + /// + /// The BRPOPLPUSH command. + ///

+ /// Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element + /// at the first element (head) of the list stored at destination. + ///

+ /// This is an extension method added to the class, for convenience. + ///

+ /// The class where this extension method is applied. + /// The key of the source list. + /// The key of the destination list. + /// Server-side timeout for the wait. A value of 0 means to wait indefinitely. + /// The element being popped and pushed, or null if the server timeout expires. + /// + public static RedisValue? BRPopLPush(this IDatabase db, RedisKey source, RedisKey destination, double timeout) + { + var command = CoreCommandBuilder.BRPopLPush(source, destination, timeout); + return db.Execute(command).ToRedisValue(); } } } diff --git a/src/NRedisStack/CoreCommands/DataTypes/RedisValueWithScore.cs b/src/NRedisStack/CoreCommands/DataTypes/RedisValueWithScore.cs index 6587db45..9ad3e34d 100644 --- a/src/NRedisStack/CoreCommands/DataTypes/RedisValueWithScore.cs +++ b/src/NRedisStack/CoreCommands/DataTypes/RedisValueWithScore.cs @@ -28,4 +28,4 @@ public RedisValueWithScore(RedisValue value, double score) /// ZADD my-set 5.1 my-value, the score is 5.1. ///
public double Score { get; } -} \ No newline at end of file +} diff --git a/src/NRedisStack/CoreCommands/Literals/CommandArgs.cs b/src/NRedisStack/CoreCommands/Literals/CommandArgs.cs index 4e6e4242..9611cefd 100644 --- a/src/NRedisStack/CoreCommands/Literals/CommandArgs.cs +++ b/src/NRedisStack/CoreCommands/Literals/CommandArgs.cs @@ -3,9 +3,11 @@ namespace NRedisStack.Core.Literals internal static class CoreArgs { public const string COUNT = "COUNT"; - public const string lib_name = "LIB-NAME"; - public const string lib_ver = "LIB-VER"; + public const string LEFT = "LEFT"; public const string MAX = "MAX"; public const string MIN = "MIN"; + public const string RIGHT = "RIGHT"; + public const string lib_name = "LIB-NAME"; + public const string lib_ver = "LIB-VER"; } } diff --git a/src/NRedisStack/CoreCommands/Literals/Commands.cs b/src/NRedisStack/CoreCommands/Literals/Commands.cs index e44c82ed..7e969144 100644 --- a/src/NRedisStack/CoreCommands/Literals/Commands.cs +++ b/src/NRedisStack/CoreCommands/Literals/Commands.cs @@ -5,6 +5,11 @@ namespace NRedisStack.Core.Literals /// internal static class RedisCoreCommands { + public const string BLMOVE = "BLMOVE"; + public const string BLMPOP = "BLMPOP"; + public const string BLPOP = "BLPOP"; + public const string BRPOP = "BRPOP"; + public const string BRPOPLPUSH = "BRPOPLPUSH"; public const string BZMPOP = "BZMPOP"; public const string BZPOPMAX = "BZPOPMAX"; public const string BZPOPMIN = "BZPOPMIN"; diff --git a/src/NRedisStack/ResponseParser.cs b/src/NRedisStack/ResponseParser.cs index b3a7c315..58546d37 100644 --- a/src/NRedisStack/ResponseParser.cs +++ b/src/NRedisStack/ResponseParser.cs @@ -766,5 +766,41 @@ public static Dictionary[] ToDictionarys(this RedisResult r return new Tuple>(resultKey, valuesWithScores); } + + public static Tuple? ToListPopResult(this RedisResult result) + { + if (result.IsNull) + { + return null; + } + + var resultArray = (RedisResult[])result!; + var resultKey = resultArray[0].ToRedisKey(); + var value = resultArray[1].ToRedisValue(); + + return new Tuple(resultKey, value); + } + + public static Tuple>? ToListPopResults(this RedisResult result) + { + if (result.IsNull) + { + return null; + } + + var resultArray = (RedisResult[])result!; + var resultKey = resultArray[0].ToRedisKey(); + var resultSetItems = resultArray[1].ToArray(); + + List values = new List(); + + foreach (var resultSetItem in resultSetItems) + { + var value = (RedisValue)resultSetItem!; + values.Add(value); + } + + return new Tuple>(resultKey, values); + } } } \ No newline at end of file diff --git a/tests/NRedisStack.Tests/Core Commands/CoreTests.cs b/tests/NRedisStack.Tests/Core Commands/CoreTests.cs index 275980bf..b9476d3b 100644 --- a/tests/NRedisStack.Tests/Core Commands/CoreTests.cs +++ b/tests/NRedisStack.Tests/Core Commands/CoreTests.cs @@ -138,11 +138,9 @@ public async Task TestSetInfoNullAsync() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] - public void TestBzmPop() + public void TestBZMPop() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); var sortedSetKey = "my-set"; @@ -153,8 +151,8 @@ public void TestBzmPop() db.SortedSetAdd(sortedSetKey, "d", 9.4); db.SortedSetAdd(sortedSetKey, "e", 7.76); - // Pop two items with default order, which means it will pop the minimum values. - var resultWithDefaultOrder = db.BzmPop(0, sortedSetKey, MinMaxModifier.Min, 2); + // Pop two items with Min modifier, which means it will pop the minimum values. + var resultWithDefaultOrder = db.BZMPop(0, sortedSetKey, MinMaxModifier.Min, 2); Assert.NotNull(resultWithDefaultOrder); Assert.Equal(sortedSetKey, resultWithDefaultOrder!.Item1); @@ -162,8 +160,8 @@ public void TestBzmPop() Assert.Equal("a", resultWithDefaultOrder.Item2[0].Value.ToString()); Assert.Equal("c", resultWithDefaultOrder.Item2[1].Value.ToString()); - // Pop one more item, with descending order, which means it will pop the maximum value. - var resultWithDescendingOrder = db.BzmPop(0, sortedSetKey, MinMaxModifier.Max, 1); + // Pop one more item, with Max modifier, which means it will pop the maximum value. + var resultWithDescendingOrder = db.BZMPop(0, sortedSetKey, MinMaxModifier.Max, 1); Assert.NotNull(resultWithDescendingOrder); Assert.Equal(sortedSetKey, resultWithDescendingOrder!.Item1); @@ -172,40 +170,36 @@ public void TestBzmPop() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] - public void TestBzmPopNull() + public void TestBZMPopNull() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); // Nothing in the set, and a short server timeout, which yields null. - var result = db.BzmPop(0.5, "my-set", MinMaxModifier.Min, null); + var result = db.BZMPop(0.5, "my-set", MinMaxModifier.Min, null); Assert.Null(result); } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] - public void TestBzmPopMultiplexerTimeout() + public void TestBZMPopMultiplexerTimeout() { var configurationOptions = new ConfigurationOptions(); configurationOptions.SyncTimeout = 1000; - configurationOptions.EndPoints.Add("localhost"); - var redis = ConnectionMultiplexer.Connect(configurationOptions); + + using var redis = redisFixture.CustomRedis(configurationOptions, out _); var db = redis.GetDatabase(null); db.Execute("FLUSHALL"); // Server would wait forever, but the multiplexer times out in 1 second. - Assert.Throws(() => db.BzmPop(0, "my-set", MinMaxModifier.Min)); + Assert.Throws(() => db.BZMPop(0, "my-set", MinMaxModifier.Min)); } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] - public void TestBzmPopMultipleSets() + public void TestBZMPopMultipleSets() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); db.SortedSetAdd("set-one", "a", 1.5); @@ -214,28 +208,28 @@ public void TestBzmPopMultipleSets() db.SortedSetAdd("set-two", "d", 9.4); db.SortedSetAdd("set-two", "e", 7.76); - var result = db.BzmPop(0, "set-two", MinMaxModifier.Max); + var result = db.BZMPop(0, "set-two", MinMaxModifier.Max); Assert.NotNull(result); Assert.Equal("set-two", result!.Item1); Assert.Single(result.Item2); Assert.Equal("d", result.Item2[0].Value.ToString()); - result = db.BzmPop(0, new[] { new RedisKey("set-two"), new RedisKey("set-one") }, MinMaxModifier.Min); + result = db.BZMPop(0, new[] { new RedisKey("set-two"), new RedisKey("set-one") }, MinMaxModifier.Min); Assert.NotNull(result); Assert.Equal("set-two", result!.Item1); Assert.Single(result.Item2); Assert.Equal("e", result.Item2[0].Value.ToString()); - result = db.BzmPop(0, new[] { new RedisKey("set-two"), new RedisKey("set-one") }, MinMaxModifier.Max); + result = db.BZMPop(0, new[] { new RedisKey("set-two"), new RedisKey("set-one") }, MinMaxModifier.Max); Assert.NotNull(result); Assert.Equal("set-one", result!.Item1); Assert.Single(result.Item2); Assert.Equal("b", result.Item2[0].Value.ToString()); - result = db.BzmPop(0, "set-one", MinMaxModifier.Min, count: 2); + result = db.BZMPop(0, "set-one", MinMaxModifier.Min, count: 2); Assert.NotNull(result); Assert.Equal("set-one", result!.Item1); @@ -245,41 +239,36 @@ public void TestBzmPopMultipleSets() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] - public void TestBzmPopNoKeysProvided() + public void TestBZMPopNoKeysProvided() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); - // Server would wait forever, but the multiplexer times out in 1 second. - Assert.Throws(() => db.BzmPop(0, Array.Empty(), MinMaxModifier.Min)); + Assert.Throws(() => db.BZMPop(0, Array.Empty(), MinMaxModifier.Min)); } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] - public void TestBzmPopWithOrderEnum() + public void TestBZMPopWithOrderEnum() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); - var sortedSetKey = "my-set"; + var sortedSetKey = "my-set-" + Guid.NewGuid(); db.SortedSetAdd(sortedSetKey, "a", 1.5); db.SortedSetAdd(sortedSetKey, "b", 5.1); db.SortedSetAdd(sortedSetKey, "c", 3.7); - // Pop two items with default order, which means it will pop the minimum values. - var resultWithDefaultOrder = db.BzmPop(0, sortedSetKey, Order.Ascending.ToMinMax()); + // Pop two items with Ascending order, which means it will pop the minimum values. + var resultWithDefaultOrder = db.BZMPop(0, sortedSetKey, Order.Ascending.ToMinMax()); Assert.NotNull(resultWithDefaultOrder); Assert.Equal(sortedSetKey, resultWithDefaultOrder!.Item1); Assert.Single(resultWithDefaultOrder.Item2); Assert.Equal("a", resultWithDefaultOrder.Item2[0].Value.ToString()); - // Pop one more item, with descending order, which means it will pop the maximum value. - var resultWithDescendingOrder = db.BzmPop(0, sortedSetKey, Order.Descending.ToMinMax()); + // Pop one more item, with Descending order, which means it will pop the maximum value. + var resultWithDescendingOrder = db.BZMPop(0, sortedSetKey, Order.Descending.ToMinMax()); Assert.NotNull(resultWithDescendingOrder); Assert.Equal(sortedSetKey, resultWithDescendingOrder!.Item1); @@ -288,11 +277,9 @@ public void TestBzmPopWithOrderEnum() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "5.0.0")] - public void TestBzPopMin() + public void TestBZPopMin() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); var sortedSetKey = "my-set"; @@ -300,7 +287,7 @@ public void TestBzPopMin() db.SortedSetAdd(sortedSetKey, "a", 1.5); db.SortedSetAdd(sortedSetKey, "b", 5.1); - var result = db.BzPopMin(sortedSetKey, 0); + var result = db.BZPopMin(sortedSetKey, 0); Assert.NotNull(result); Assert.Equal(sortedSetKey, result!.Item1); @@ -309,38 +296,34 @@ public void TestBzPopMin() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "5.0.0")] - public void TestBzPopMinNull() + public void TestBZPopMinNull() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); // Nothing in the set, and a short server timeout, which yields null. - var result = db.BzPopMin("my-set", 0.5); + var result = db.BZPopMin("my-set", 0.5); Assert.Null(result); } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "5.0.0")] - public void TestBzPopMinMultipleSets() + public void TestBZPopMinMultipleSets() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); db.SortedSetAdd("set-one", "a", 1.5); db.SortedSetAdd("set-one", "b", 5.1); db.SortedSetAdd("set-two", "e", 7.76); - var result = db.BzPopMin(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); + var result = db.BZPopMin(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); Assert.NotNull(result); Assert.Equal("set-two", result!.Item1); Assert.Equal("e", result.Item2.Value.ToString()); - result = db.BzPopMin(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); + result = db.BZPopMin(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); Assert.NotNull(result); Assert.Equal("set-one", result!.Item1); @@ -348,11 +331,9 @@ public void TestBzPopMinMultipleSets() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "5.0.0")] - public void TestBzPopMax() + public void TestBZPopMax() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); var sortedSetKey = "my-set"; @@ -360,7 +341,7 @@ public void TestBzPopMax() db.SortedSetAdd(sortedSetKey, "a", 1.5); db.SortedSetAdd(sortedSetKey, "b", 5.1); - var result = db.BzPopMax(sortedSetKey, 0); + var result = db.BZPopMax(sortedSetKey, 0); Assert.NotNull(result); Assert.Equal(sortedSetKey, result!.Item1); @@ -369,41 +350,310 @@ public void TestBzPopMax() } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "5.0.0")] - public void TestBzPopMaxNull() + public void TestBZPopMaxNull() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); // Nothing in the set, and a short server timeout, which yields null. - var result = db.BzPopMax("my-set", 0.5); + var result = db.BZPopMax("my-set", 0.5); Assert.Null(result); } [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "5.0.0")] - public void TestBzPopMaxMultipleSets() + public void TestBZPopMaxMultipleSets() { - var redis = ConnectionMultiplexer.Connect("localhost"); - - var db = redis.GetDatabase(null); + var db = redisFixture.Redis.GetDatabase(null); db.Execute("FLUSHALL"); db.SortedSetAdd("set-one", "a", 1.5); db.SortedSetAdd("set-one", "b", 5.1); db.SortedSetAdd("set-two", "e", 7.76); - var result = db.BzPopMax(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); + var result = db.BZPopMax(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); Assert.NotNull(result); Assert.Equal("set-two", result!.Item1); Assert.Equal("e", result.Item2.Value.ToString()); - result = db.BzPopMax(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); + result = db.BZPopMax(new[] { new RedisKey("set-two"), new RedisKey("set-one") }, 0); Assert.NotNull(result); Assert.Equal("set-one", result!.Item1); Assert.Equal("b", result.Item2.Value.ToString()); } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] + public void TestBLMPop() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("my-list", "a"); + db.ListRightPush("my-list", "b"); + db.ListRightPush("my-list", "c"); + db.ListRightPush("my-list", "d"); + db.ListRightPush("my-list", "e"); + + // Pop two items from the left side. + var resultWithDefaultOrder = db.BLMPop(0, "my-list", ListSide.Left, 2); + + Assert.NotNull(resultWithDefaultOrder); + Assert.Equal("my-list", resultWithDefaultOrder!.Item1); + Assert.Equal(2, resultWithDefaultOrder.Item2.Count); + Assert.Equal("a", resultWithDefaultOrder.Item2[0].ToString()); + Assert.Equal("b", resultWithDefaultOrder.Item2[1].ToString()); + + // Pop one more item, from the right side. + var resultWithDescendingOrder = db.BLMPop(0, "my-list", ListSide.Right, 1); + + Assert.NotNull(resultWithDescendingOrder); + Assert.Equal("my-list", resultWithDescendingOrder!.Item1); + Assert.Single(resultWithDescendingOrder.Item2); + Assert.Equal("e", resultWithDescendingOrder.Item2[0].ToString()); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] + public void TestBLMPopNull() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + // Nothing in the list, and a short server timeout, which yields null. + var result = db.BLMPop(0.5, "my-list", ListSide.Left); + + Assert.Null(result); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] + public void TestBLMPopMultipleLists() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("list-one", "a"); + db.ListRightPush("list-one", "b"); + db.ListRightPush("list-one", "c"); + db.ListRightPush("list-two", "d"); + db.ListRightPush("list-two", "e"); + + var result = db.BLMPop(0, "list-two", ListSide.Right); + + Assert.NotNull(result); + Assert.Equal("list-two", result!.Item1); + Assert.Single(result.Item2); + Assert.Equal("e", result.Item2[0].ToString()); + + result = db.BLMPop(0, new[] { new RedisKey("list-two"), new RedisKey("list-one") }, ListSide.Left); + + Assert.NotNull(result); + Assert.Equal("list-two", result!.Item1); + Assert.Single(result.Item2); + Assert.Equal("d", result.Item2[0].ToString()); + + result = db.BLMPop(0, new[] { new RedisKey("list-two"), new RedisKey("list-one") }, ListSide.Right); + + Assert.NotNull(result); + Assert.Equal("list-one", result!.Item1); + Assert.Single(result.Item2); + Assert.Equal("c", result.Item2[0].ToString()); + + result = db.BLMPop(0, "list-one", ListSide.Left, count: 2); + + Assert.NotNull(result); + Assert.Equal("list-one", result!.Item1); + Assert.Equal(2, result.Item2.Count); + Assert.Equal("a", result.Item2[0].ToString()); + Assert.Equal("b", result.Item2[1].ToString()); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.0.0")] + public void TestBLMPopNoKeysProvided() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + Assert.Throws(() => db.BLMPop(0, Array.Empty(), ListSide.Left)); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")] + public void TestBLPop() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("my-list", "a"); + db.ListRightPush("my-list", "b"); + + var result = db.BLPop("my-list", 0); + + Assert.NotNull(result); + Assert.Equal("my-list", result!.Item1); + Assert.Equal("a", result.Item2.ToString()); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")] + public void TestBLPopNull() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + // Nothing in the set, and a short server timeout, which yields null. + var result = db.BLPop("my-set", 0.5); + + Assert.Null(result); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")] + public void TestBLPopMultipleLists() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("list-one", "a"); + db.ListRightPush("list-one", "b"); + db.ListRightPush("list-two", "e"); + + var result = db.BLPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0); + + Assert.NotNull(result); + Assert.Equal("list-two", result!.Item1); + Assert.Equal("e", result.Item2.ToString()); + + result = db.BLPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0); + + Assert.NotNull(result); + Assert.Equal("list-one", result!.Item1); + Assert.Equal("a", result.Item2.ToString()); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")] + public void TestBRPop() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("my-list", "a"); + db.ListRightPush("my-list", "b"); + + var result = db.BRPop("my-list", 0); + + Assert.NotNull(result); + Assert.Equal("my-list", result!.Item1); + Assert.Equal("b", result.Item2.ToString()); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")] + public void TestBRPopNull() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + // Nothing in the set, and a short server timeout, which yields null. + var result = db.BRPop("my-set", 0.5); + + Assert.Null(result); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")] + public void TestBRPopMultipleLists() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("list-one", "a"); + db.ListRightPush("list-one", "b"); + db.ListRightPush("list-two", "e"); + + var result = db.BRPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0); + + Assert.NotNull(result); + Assert.Equal("list-two", result!.Item1); + Assert.Equal("e", result.Item2.ToString()); + + result = db.BRPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0); + + Assert.NotNull(result); + Assert.Equal("list-one", result!.Item1); + Assert.Equal("b", result.Item2.ToString()); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "6.2.0")] + public void TestBLMove() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("list-one", "a"); + db.ListRightPush("list-one", "b"); + + db.ListRightPush("list-two", "c"); + db.ListRightPush("list-two", "d"); + + var result = db.BLMove("list-one", "list-two", ListSide.Right, ListSide.Left, 0); + Assert.NotNull(result); + Assert.Equal("b", result!); + + Assert.Equal(1, db.ListLength("list-one")); + Assert.Equal("a", db.ListGetByIndex("list-one", 0)); + Assert.Equal(3, db.ListLength("list-two")); + Assert.Equal("b", db.ListGetByIndex("list-two", 0)); + Assert.Equal("c", db.ListGetByIndex("list-two", 1)); + Assert.Equal("d", db.ListGetByIndex("list-two", 2)); + + result = db.BLMove("list-two", "list-one", ListSide.Left, ListSide.Right, 0); + Assert.NotNull(result); + Assert.Equal("b", result!); + + Assert.Equal(2, db.ListLength("list-one")); + Assert.Equal("a", db.ListGetByIndex("list-one", 0)); + Assert.Equal("b", db.ListGetByIndex("list-one", 1)); + Assert.Equal(2, db.ListLength("list-two")); + Assert.Equal("c", db.ListGetByIndex("list-two", 0)); + Assert.Equal("d", db.ListGetByIndex("list-two", 1)); + + result = db.BLMove("list-one", "list-two", ListSide.Left, ListSide.Left, 0); + Assert.NotNull(result); + Assert.Equal("a", result!); + + Assert.Equal(1, db.ListLength("list-one")); + Assert.Equal("b", db.ListGetByIndex("list-one", 0)); + Assert.Equal(3, db.ListLength("list-two")); + Assert.Equal("a", db.ListGetByIndex("list-two", 0)); + Assert.Equal("c", db.ListGetByIndex("list-two", 1)); + Assert.Equal("d", db.ListGetByIndex("list-two", 2)); + + result = db.BLMove("list-two", "list-one", ListSide.Right, ListSide.Right, 0); + Assert.NotNull(result); + Assert.Equal("d", result!); + + Assert.Equal(2, db.ListLength("list-one")); + Assert.Equal("b", db.ListGetByIndex("list-one", 0)); + Assert.Equal("d", db.ListGetByIndex("list-one", 1)); + Assert.Equal(2, db.ListLength("list-two")); + Assert.Equal("a", db.ListGetByIndex("list-two", 0)); + Assert.Equal("c", db.ListGetByIndex("list-two", 1)); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.2.0")] + public void TestBRPopLPush() + { + var db = redisFixture.Redis.GetDatabase(null); + db.Execute("FLUSHALL"); + + db.ListRightPush("list-one", "a"); + db.ListRightPush("list-one", "b"); + + db.ListRightPush("list-two", "c"); + db.ListRightPush("list-two", "d"); + + var result = db.BRPopLPush("list-one", "list-two", 0); + Assert.NotNull(result); + Assert.Equal("b", result!); + + Assert.Equal(1, db.ListLength("list-one")); + Assert.Equal(3, db.ListLength("list-two")); + Assert.Equal("b", db.ListLeftPop("list-two")); + } } \ No newline at end of file diff --git a/tests/NRedisStack.Tests/RedisFixture.cs b/tests/NRedisStack.Tests/RedisFixture.cs index f90389c6..02f92316 100644 --- a/tests/NRedisStack.Tests/RedisFixture.cs +++ b/tests/NRedisStack.Tests/RedisFixture.cs @@ -1,5 +1,3 @@ -using System.Net; -using Org.BouncyCastle.Tls; using StackExchange.Redis; namespace NRedisStack.Tests @@ -7,12 +5,35 @@ namespace NRedisStack.Tests public class RedisFixture : IDisposable { // Set the enviroment variable to specify your own alternet host and port: - string redisStandalone = Environment.GetEnvironmentVariable("REDIS") ?? "localhost:6379"; - string? redisCluster = Environment.GetEnvironmentVariable("REDIS_CLUSTER"); - string? numRedisClusterNodesEnv = Environment.GetEnvironmentVariable("NUM_REDIS_CLUSTER_NODES"); - public bool isOSSCluster = false; + readonly string redisStandalone = Environment.GetEnvironmentVariable("REDIS") ?? "localhost:6379"; + readonly string? redisCluster = Environment.GetEnvironmentVariable("REDIS_CLUSTER"); + readonly string? numRedisClusterNodesEnv = Environment.GetEnvironmentVariable("NUM_REDIS_CLUSTER_NODES"); + + public bool isOSSCluster; public RedisFixture() + { + ConfigurationOptions clusterConfig = new ConfigurationOptions + { + AsyncTimeout = 10000, + SyncTimeout = 10000 + }; + Redis = Connect(clusterConfig, out isOSSCluster); + } + + public void Dispose() + { + Redis.Close(); + } + + public ConnectionMultiplexer Redis { get; } + + public ConnectionMultiplexer CustomRedis(ConfigurationOptions configurationOptions, out bool isOssCluster) + { + return Connect(configurationOptions, out isOssCluster); + } + + private ConnectionMultiplexer Connect(ConfigurationOptions configurationOptions, out bool isOssCluster) { // Redis Cluster if (redisCluster != null && numRedisClusterNodesEnv != null) @@ -22,35 +43,23 @@ public RedisFixture() string host = parts[0]; int startPort = int.Parse(parts[1]); - var endpoints = new EndPointCollection(); + configurationOptions.EndPoints.Clear(); int numRedisClusterNodes = int.Parse(numRedisClusterNodesEnv!); for (int i = 0; i < numRedisClusterNodes; i++) { - endpoints.Add(host, startPort + i); + configurationOptions.EndPoints.Add(host, startPort + i); } - - ConfigurationOptions clusterConfig = new ConfigurationOptions - { - EndPoints = endpoints, - AsyncTimeout = 10000, - SyncTimeout = 10000 - }; - - isOSSCluster = true; - Redis = ConnectionMultiplexer.Connect(clusterConfig); + isOssCluster = true; + return ConnectionMultiplexer.Connect(configurationOptions); } // Redis Standalone - else - Redis = ConnectionMultiplexer.Connect($"{redisStandalone}"); - } + configurationOptions.EndPoints.Clear(); + configurationOptions.EndPoints.Add($"{redisStandalone}"); - public void Dispose() - { - Redis.Close(); + isOssCluster = false; + return ConnectionMultiplexer.Connect(configurationOptions); } - - public ConnectionMultiplexer Redis { get; } } } \ No newline at end of file