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

Add support for BZMPOP, BZPOPMIN and BZPOPMAX #240

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Here's how to get started with your code contribution:
3. Write your tests

4. Use the `docker run -p 6379:6379 -it redis/redis-stack-server:edge` as your local environment for running the functional tests. You can also use Development Container as described below.
5. Run dotnet format to make sure your code is formatted
5. Run `dotnet format` to make sure your code is formatted
6. Make sure your tests pass using `dotnet test`
7. Open a pull request

Expand Down Expand Up @@ -121,6 +121,7 @@ e.g. :
```bash
dotnet test --environment "REDIS_CLUSTER=127.0.0.1:16379" --environment "NUM_REDIS_CLUSTER_NODES=6"
```

## How to Report a Bug

### Security Vulnerabilities
Expand All @@ -145,7 +146,7 @@ issue, so if you're unsure, just email [us](mailto:[email protected]).
When filing an issue, make sure to answer these five questions:

1. What version of NRedisStack are you using?
2. What version of redis are you using?
2. What version of Redis are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ This project builds on [StackExchange.Redis](https://github.com/StackExchange/St
The complete documentation for Redis module commands can be found at the [Redis commands website](https://redis.io/commands/).

### Redis OSS commands

You can use Redis OSS commands in the same way as you use them in [StackExchange.Redis](https://github.com/StackExchange/StackExchange.Redis).

### Stack commands

Each module has a command class with its own commands.

The supported modules are [Search](https://redis.io/commands/?group=search), [JSON](https://redis.io/commands/?group=json), [TimeSeries](https://redis.io/commands/?group=timeseries), [Bloom Filter](https://redis.io/commands/?group=bf), [Cuckoo Filter](https://redis.io/commands/?group=cf), [T-Digest](https://redis.io/commands/?group=tdigest), [Count-min Sketch](https://redis.io/commands/?group=cms), and [Top-K](https://redis.io/commands/?group=topk).

**Note:** RedisGraph support has been deprecated starting from Redis Stack version 7.2. For more information, please refer to [this blog post](https://redis.com/blog/redisgraph-eol/).


# Usage

## 💻 Installation
Expand Down
53 changes: 53 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using NRedisStack.RedisStackCommands;
using NRedisStack.Core.Literals;
using NRedisStack.Core;
using NRedisStack.Core.DataTypes;
using StackExchange.Redis;

namespace NRedisStack
{
Expand All @@ -18,5 +20,56 @@ 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)
{
if (keys.Length == 0)
{
throw new ArgumentException("At least one key must be provided.");
}

List<object> args = new List<object>();

args.Add(timeout);
args.Add(keys.Length);
args.AddRange(keys.Cast<object>());
args.Add(minMaxModifier == MinMaxModifier.Min ? CoreArgs.MIN : CoreArgs.MAX);

if (count != null)
{
args.Add(CoreArgs.COUNT);
args.Add(count);
}

return new SerializedCommand(RedisCoreCommands.BZMPOP, args);
}

public static SerializedCommand BzPopMin(RedisKey[] keys, double timeout)
{
if (keys.Length == 0)
{
throw new ArgumentException("At least one key must be provided.");
}

List<object> args = new List<object>();
args.AddRange(keys.Cast<object>());
args.Add(timeout);

return new SerializedCommand(RedisCoreCommands.BZPOPMIN, args);
}

public static SerializedCommand BzPopMax(RedisKey[] keys, double timeout)
{
if (keys.Length == 0)
{
throw new ArgumentException("At least one key must be provided.");
}

List<object> args = new List<object>();
args.AddRange(keys.Cast<object>());
args.Add(timeout);

return new SerializedCommand(RedisCoreCommands.BZPOPMAX, args);
}
}
}
156 changes: 156 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommands.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using NRedisStack.Core;
using NRedisStack.Core.DataTypes;
using StackExchange.Redis;

namespace NRedisStack
{

Expand All @@ -19,5 +21,159 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val
return false;
return db.Execute(CoreCommandBuilder.ClientSetInfo(attr, value)).OKtoBoolean();
}

/// <summary>
/// The BZMPOP command.
/// <p/>
/// Removes and returns up to <paramref name="count"/> entries from the first non-empty sorted set in
/// <paramref name="keys"/>. If none of the sets contain elements, the call blocks on the server until elements
/// become available, or the given <paramref name="timeout"/> expires. A <paramref name="timeout"/> of <c>0</c>
/// means to wait indefinitely server-side. Returns <c>null</c> if the server timeout expires.
/// <p/>
/// When using this, pay attention to the timeout configured in the client, on the
/// <see cref="ConnectionMultiplexer"/>, which by default can be too small:
/// <code>
/// ConfigurationOptions configurationOptions = new ConfigurationOptions();
/// configurationOptions.SyncTimeout = 120000; // set a meaningful value here
/// configurationOptions.EndPoints.Add("localhost");
/// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions);
/// </code>
/// If the connection multiplexer timeout expires in the client, a <c>StackExchange.Redis.RedisTimeoutException</c>
/// is thrown.
/// <p/>
/// This is an extension method added to the <see cref="IDatabase"/> class, for convenience.
/// </summary>
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
/// <param name="keys">The keys to check.</param>
/// <param name="minMaxModifier">Specify from which end of the sorted set to pop values. If set to <c>MinMaxModifier.Min</c>
/// then the minimum elements will be popped, otherwise the maximum values.</param>
/// <param name="count">The maximum number of records to pop out. If set to <c>null</c> then the server default
/// will be used.</param>
/// <returns>A collection of sorted set entries paired with their scores, together with the key they were popped
/// from, or <c>null</c> if the server timeout expires.</returns>
/// <remarks><seealso href="https://redis.io/commands/bzmpop"/></remarks>
public static Tuple<RedisKey, List<RedisValueWithScore>>? BzmPop(this IDatabase db, double timeout, RedisKey[] keys, MinMaxModifier minMaxModifier, long? count = null)
{
var command = CoreCommandBuilder.BzmPop(timeout, keys, minMaxModifier, count);
return db.Execute(command).ToSortedSetPopResults();
}

/// <summary>
/// Syntactic sugar for
/// <see cref="BzmPop(StackExchange.Redis.IDatabase,double,StackExchange.Redis.RedisKey[],NRedisStack.Core.DataTypes.MinMaxModifier,System.Nullable{long})"/>,
/// where only one key is used.
/// </summary>
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
/// <param name="key">The key to check.</param>
/// <param name="minMaxModifier">Specify from which end of the sorted set to pop values. If set to <c>MinMaxModifier.Min</c>
/// then the minimum elements will be popped, otherwise the maximum values.</param>
/// <param name="count">The maximum number of records to pop out. If set to <c>null</c> then the server default
/// will be used.</param>
/// <returns>A collection of sorted set entries paired with their scores, together with the key they were popped
/// from, or <c>null</c> if the server timeout expires.</returns>
/// <remarks><seealso href="https://redis.io/commands/bzmpop"/></remarks>
public static Tuple<RedisKey, List<RedisValueWithScore>>? BzmPop(this IDatabase db, double timeout, RedisKey key, MinMaxModifier minMaxModifier, long? count = null)
{
return BzmPop(db, timeout, new[] { key }, minMaxModifier, count);
}

/// <summary>
/// The BZPOPMIN command.
/// <p/>
/// Removes and returns the entry with the smallest score from the first non-empty sorted set in
/// <paramref name="keys"/>. If none of the sets contain elements, the call blocks on the server until elements
/// become available, or the given <paramref name="timeout"/> expires. A <paramref name="timeout"/> of <c>0</c>
/// means to wait indefinitely server-side. Returns <c>null</c> if the server timeout expires.
/// <p/>
/// When using this, pay attention to the timeout configured in the client, on the
/// <see cref="ConnectionMultiplexer"/>, which by default can be too small:
/// <code>
/// ConfigurationOptions configurationOptions = new ConfigurationOptions();
/// configurationOptions.SyncTimeout = 120000; // set a meaningful value here
/// configurationOptions.EndPoints.Add("localhost");
/// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions);
/// </code>
/// If the connection multiplexer timeout expires in the client, a <c>StackExchange.Redis.RedisTimeoutException</c>
/// is thrown.
/// <p/>
/// This is an extension method added to the <see cref="IDatabase"/> class, for convenience.
/// </summary>
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
/// <param name="keys">The keys to check.</param>
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
/// <returns>A sorted set entry paired with its score, together with the key it was popped from, or <c>null</c>
/// if the server timeout expires.</returns>
/// <remarks><seealso href="https://redis.io/commands/bzpopmin"/></remarks>
public static Tuple<RedisKey, RedisValueWithScore>? BzPopMin(this IDatabase db, RedisKey[] keys, double timeout)
{
var command = CoreCommandBuilder.BzPopMin(keys, timeout);
return db.Execute(command).ToSortedSetPopResult();
}

/// <summary>
/// Syntactic sugar for <see cref="BzPopMin(StackExchange.Redis.IDatabase,StackExchange.Redis.RedisKey[],double)"/>,
/// where only one key is used.
/// </summary>
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
/// <param name="key">The key to check.</param>
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
/// <returns>A sorted set entry paired with its score, together with the key it was popped from, or <c>null</c>
/// if the server timeout expires.</returns>
/// <remarks><seealso href="https://redis.io/commands/bzpopmin"/></remarks>
public static Tuple<RedisKey, RedisValueWithScore>? BzPopMin(this IDatabase db, RedisKey key, double timeout)
{
return BzPopMin(db, new[] { key }, timeout);
}


/// <summary>
/// The BZPOPMAX command.
/// <p/>
/// Removes and returns the entry with the highest score from the first non-empty sorted set in
/// <paramref name="keys"/>. If none of the sets contain elements, the call blocks on the server until elements
/// become available, or the given <paramref name="timeout"/> expires. A <paramref name="timeout"/> of <c>0</c>
/// means to wait indefinitely server-side. Returns <c>null</c> if the server timeout expires.
/// <p/>
/// When using this, pay attention to the timeout configured in the client, on the
/// <see cref="ConnectionMultiplexer"/>, which by default can be too small:
/// <code>
/// ConfigurationOptions configurationOptions = new ConfigurationOptions();
/// configurationOptions.SyncTimeout = 120000; // set a meaningful value here
/// configurationOptions.EndPoints.Add("localhost");
/// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions);
/// </code>
/// If the connection multiplexer timeout expires in the client, a <c>StackExchange.Redis.RedisTimeoutException</c>
/// is thrown.
/// <p/>
/// This is an extension method added to the <see cref="IDatabase"/> class, for convenience.
/// </summary>
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
/// <param name="keys">The keys to check.</param>
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
/// <returns>A sorted set entry paired with its score, together with the key it was popped from, or <c>null</c>
/// if the server timeout expires.</returns>
/// <remarks><seealso href="https://redis.io/commands/bzpopmax"/></remarks>
public static Tuple<RedisKey, RedisValueWithScore>? BzPopMax(this IDatabase db, RedisKey[] keys, double timeout)
{
var command = CoreCommandBuilder.BzPopMax(keys, timeout);
return db.Execute(command).ToSortedSetPopResult();
}

/// <summary>
/// Syntactic sugar for <see cref="BzPopMax(StackExchange.Redis.IDatabase,StackExchange.Redis.RedisKey[],double)"/>,
/// where only one key is used.
/// </summary>
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
/// <param name="key">The key to check.</param>
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
/// <returns>A sorted set entry paired with its score, together with the key it was popped from, or <c>null</c>
/// if the server timeout expires.</returns>
/// <remarks><seealso href="https://redis.io/commands/bzpopmax"/></remarks>
public static Tuple<RedisKey, RedisValueWithScore>? BzPopMax(this IDatabase db, RedisKey key, double timeout)
{
return BzPopMax(db, new[] { key }, timeout);
}
}
}
35 changes: 35 additions & 0 deletions src/NRedisStack/CoreCommands/DataTypes/MinMaxModifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using StackExchange.Redis;

namespace NRedisStack.Core.DataTypes;

/// <summary>
/// Modifier that can be used for sorted set commands, where a MIN/MAX argument is expected by the Redis server.
/// </summary>
public enum MinMaxModifier
{
/// <summary>
/// Maps to the <c>MIN</c> argument on the Redis server.
/// </summary>
Min,

/// <summary>
/// Maps to the <c>MAX</c> argument on the Redis server.
/// </summary>
Max
}

/// <summary>
/// Conversion methods from/to other common data types.
/// </summary>
public static class MinMaxModifierExtensions
{
/// <summary>
/// Convert from <see cref="Order"/> to <see cref="MinMaxModifier"/>.
/// </summary>
public static MinMaxModifier ToMinMax(this Order order) => order switch
{
Order.Ascending => MinMaxModifier.Min,
Order.Descending => MinMaxModifier.Max,
_ => throw new ArgumentOutOfRangeException(nameof(order))
};
}
31 changes: 31 additions & 0 deletions src/NRedisStack/CoreCommands/DataTypes/RedisValueWithScore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using StackExchange.Redis;

namespace NRedisStack.Core.DataTypes;

/// <summary>
/// Holds a <see cref="RedisValue"/> with an associated score.
/// Used when working with sorted sets.
/// </summary>
public struct RedisValueWithScore
{
/// <summary>
/// Pair a <see cref="RedisValue"/> with a numeric score.
/// </summary>
public RedisValueWithScore(RedisValue value, double score)
{
Value = value;
Score = score;
}

/// <summary>
/// The value of an item stored in a sorted set. For example, in the Redis command
/// <c>ZADD my-set 5.1 my-value</c>, the value is <c>my-value</c>.
/// </summary>
public RedisValue Value { get; }

/// <summary>
/// The score of an item stored in a sorted set. For example, in the Redis command
/// <c>ZADD my-set 5.1 my-value</c>, the score is <c>5.1</c>.
/// </summary>
public double Score { get; }
}
5 changes: 4 additions & 1 deletion src/NRedisStack/CoreCommands/Literals/CommandArgs.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace NRedisStack.Core.Literals
{
internal class CoreArgs
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 MAX = "MAX";
public const string MIN = "MIN";
}
}
5 changes: 4 additions & 1 deletion src/NRedisStack/CoreCommands/Literals/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ namespace NRedisStack.Core.Literals
/// <summary>
/// Redis Core command literals
/// </summary>
internal class RedisCoreCommands
internal static class RedisCoreCommands
{
public const string BZMPOP = "BZMPOP";
public const string BZPOPMAX = "BZPOPMAX";
public const string BZPOPMIN = "BZPOPMIN";
public const string CLIENT = "CLIENT";
public const string SETINFO = "SETINFO";
}
Expand Down
7 changes: 4 additions & 3 deletions src/NRedisStack/NRedisStack.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
<PropertyGroup>
<Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Authors>Redis Open Source</Authors>
<Owners>Redis OSS</Owners>
<Description>.Net Client for Redis Stack</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Version>0.11.0</Version>
<ReleaseVersion>0.11.0</ReleaseVersion>
<PackageVersion>0.11.0</PackageVersion>
<Version>0.11.1</Version>
<ReleaseVersion>0.11.1</ReleaseVersion>
<PackageVersion>0.11.1</PackageVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading