Skip to content

Commit

Permalink
Support SMISMEMBER (#2077)
Browse files Browse the repository at this point in the history
Adds support for https://redis-stack.io/commands/smismember/ (#2055)

Co-authored-by: Nick Craver <nrcraver@gmail.com>
  • Loading branch information
Avital-Fine and NickCraver authored Apr 12, 2022
1 parent a64f74f commit 0e9e698
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Note: does *not* increment a major version (as these are warnings to consumers), because: they're warnings (errors are opt-in), removing obsolete types with a 3.0 rev _would_ be binary breaking (this isn't), and reving to 3.0 would cause binding redirect pain for consumers. Bumping from 2.5 to 2.6 only for this change.
- Adds: Support for `COPY` with `.KeyCopy()`/`.KeyCopyAsync()` ([#2064 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2064))
- Adds: Support for `LMOVE` with `.ListMove()`/`.ListMoveAsync()` ([#2065 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2065))
- Adds: Support for `SMISMEMBER` with `.SetContains()`/`.SetContainsAsync()` ([#2077 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2077))
- Adds: Support for `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078))

## 2.5.61
Expand Down
1 change: 1 addition & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ internal enum RedisCommand
SLAVEOF,
SLOWLOG,
SMEMBERS,
SMISMEMBER,
SMOVE,
SORT,
SPOP,
Expand Down
18 changes: 16 additions & 2 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,10 +1103,10 @@ public interface IDatabase : IRedis, IDatabaseAsync
long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns if member is a member of the set stored at key.
/// Returns whether <paramref name="value"/> is a member of the set stored at <paramref name="key"/>.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="value">The value to check for .</param>
/// <param name="value">The value to check for.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// <see langword="true"/> if the element is a member of the set.
Expand All @@ -1115,6 +1115,20 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>https://redis.io/commands/sismember</remarks>
bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns whether each of <paramref name="values"/> is a member of the set stored at <paramref name="key"/>.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="values">The members to check for.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// An array of booleans corresponding to <paramref name="values"/>, for each:
/// <see langword="true"/> if the element is a member of the set.
/// <see langword="false"/> if the element is not a member of the set, or if key does not exist.
/// </returns>
/// <remarks>https://redis.io/commands/smismember</remarks>
bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None);

/// <summary>
/// <para>
/// Returns the set cardinality (number of elements) of the intersection between the sets stored at the given <paramref name="keys"/>.
Expand Down
17 changes: 15 additions & 2 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1079,10 +1079,10 @@ public interface IDatabaseAsync : IRedisAsync
Task<long> SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns if member is a member of the set stored at key.
/// Returns whether <paramref name="value"/> is a member of the set stored at <paramref name="key"/>.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="value">The value to check for .</param>
/// <param name="value">The value to check for.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// <see langword="true"/> if the element is a member of the set.
Expand All @@ -1091,6 +1091,19 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>https://redis.io/commands/sismember</remarks>
Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns whether each of <paramref name="values"/> is a member of the set stored at <paramref name="key"/>.
/// </summary>
/// <param name="key">The key of the set.</param>
/// <param name="values">The members to check for.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>
/// <see langword="true"/> if the element is a member of the set.
/// <see langword="false"/> if the element is not a member of the set, or if key does not exist.
/// </returns>
/// <remarks>https://redis.io/commands/smismember</remarks>
Task<bool[]> SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None);

/// <summary>
/// <para>
/// Returns the set cardinality (number of elements) of the intersection between the sets stored at the given <paramref name="keys"/>.
Expand Down
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey
public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) =>
Inner.SetContains(ToInner(key), value, flags);

public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) =>
Inner.SetContains(ToInner(key), values, flags);

public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
Inner.SetIntersectionLength(keys, limit, flags);

Expand Down
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,9 @@ public Task<RedisValue[]> SetCombineAsync(SetOperation operation, RedisKey first
public Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) =>
Inner.SetContainsAsync(ToInner(key), value, flags);

public Task<bool[]> SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) =>
Inner.SetContainsAsync(ToInner(key), values, flags);

public Task<long> SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
Inner.SetIntersectionLengthAsync(keys, limit, flags);

Expand Down
2 changes: 2 additions & 0 deletions src/StackExchange.Redis/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ StackExchange.Redis.IDatabase.SetCombine(StackExchange.Redis.SetOperation operat
StackExchange.Redis.IDatabase.SetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabase.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool[]!
StackExchange.Redis.IDatabase.SetIntersectionLength(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SetLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SetMembers(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
Expand Down Expand Up @@ -764,6 +765,7 @@ StackExchange.Redis.IDatabaseAsync.SetCombineAndStoreAsync(StackExchange.Redis.S
StackExchange.Redis.IDatabaseAsync.SetCombineAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!
StackExchange.Redis.IDatabaseAsync.SetCombineAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!
StackExchange.Redis.IDatabaseAsync.SetContainsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool>!
StackExchange.Redis.IDatabaseAsync.SetContainsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool[]!>!
StackExchange.Redis.IDatabaseAsync.SetIntersectionLengthAsync(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
StackExchange.Redis.IDatabaseAsync.SetLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
StackExchange.Redis.IDatabaseAsync.SetMembersAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!
Expand Down
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/RawResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ internal bool GetBoolean()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string?[]? GetItemsAsStrings() => this.ToArray<string?>((in RawResult x) => (string?)x.AsRedisValue());

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool[]? GetItemsAsBooleans() => this.ToArray<bool>((in RawResult x) => (bool)x.AsRedisValue());

internal GeoPosition? GetItemsAsGeoPosition()
{
var items = GetItems();
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 @@ -1383,6 +1383,18 @@ public Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags
return ExecuteAsync(msg, ResultProcessor.Boolean);
}

public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values);
return ExecuteSync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty<bool>());
}

public Task<bool[]> SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values);
return ExecuteAsync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty<bool>());
}

public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
{
var msg = GetSetIntersectionLengthMessage(keys, limit, flags);
Expand Down
17 changes: 17 additions & 0 deletions src/StackExchange.Redis/ResultProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ public static readonly ResultProcessor<RedisValue[]>
public static readonly ResultProcessor<string?[]>
StringArray = new StringArrayProcessor();

public static readonly ResultProcessor<bool[]>
BooleanArray = new BooleanArrayProcessor();

public static readonly ResultProcessor<GeoPosition?[]>
RedisGeoPositionArray = new RedisValueGeoPositionArrayProcessor();
public static readonly ResultProcessor<GeoPosition?>
Expand Down Expand Up @@ -1258,6 +1261,20 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
}
}

private sealed class BooleanArrayProcessor : ResultProcessor<bool[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
{
if (result.Type == ResultType.MultiBulk && !result.IsNull)
{
var arr = result.GetItemsAsBooleans()!;
SetResult(message, arr);
return true;
}
return false;
}
}

private sealed class RedisValueGeoPositionProcessor : ResultProcessor<GeoPosition?>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
Expand Down
8 changes: 8 additions & 0 deletions tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,14 @@ public void SetContains()
mock.Verify(_ => _.SetContains("prefix:key", "value", CommandFlags.None));
}

[Fact]
public void SetContains_2()
{
RedisValue[] values = new RedisValue[] { "value1", "value2" };
wrapper.SetContains("key", values, CommandFlags.None);
mock.Verify(_ => _.SetContains("prefix:key", values, CommandFlags.None));
}

[Fact]
public void SetIntersectionLength()
{
Expand Down
66 changes: 66 additions & 0 deletions tests/StackExchange.Redis.Tests/Sets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,72 @@ public class Sets : TestBase
{
public Sets(ITestOutputHelper output, SharedConnectionFixture fixture) : base (output, fixture) { }

[Fact]
public void SetContains()
{
using var conn = Create();
Skip.IfBelow(conn, RedisFeatures.v6_2_0);

var key = Me();
var db = conn.GetDatabase();
db.KeyDelete(key);
for (int i = 1; i < 1001; i++)
{
db.SetAdd(key, i, CommandFlags.FireAndForget);
}

// Single member
var isMemeber = db.SetContains(key, 1);
Assert.True(isMemeber);

// Multi members
var areMemebers = db.SetContains(key, new RedisValue[] { 0, 1, 2 });
Assert.Equal(3, areMemebers.Length);
Assert.False(areMemebers[0]);
Assert.True(areMemebers[1]);

// key not exists
db.KeyDelete(key);
isMemeber = db.SetContains(key, 1);
Assert.False(isMemeber);
areMemebers = db.SetContains(key, new RedisValue[] { 0, 1, 2 });
Assert.Equal(3, areMemebers.Length);
Assert.True(areMemebers.All(i => !i)); // Check that all the elements are False
}

[Fact]
public async Task SetContainsAsync()
{
using var conn = Create();
Skip.IfBelow(conn, RedisFeatures.v6_2_0);

var key = Me();
var db = conn.GetDatabase();
await db.KeyDeleteAsync(key);
for (int i = 1; i < 1001; i++)
{
db.SetAdd(key, i, CommandFlags.FireAndForget);
}

// Single member
var isMemeber = await db.SetContainsAsync(key, 1);
Assert.True(isMemeber);

// Multi members
var areMemebers = await db.SetContainsAsync(key, new RedisValue[] { 0, 1, 2 });
Assert.Equal(3, areMemebers.Length);
Assert.False(areMemebers[0]);
Assert.True(areMemebers[1]);

// key not exists
await db.KeyDeleteAsync(key);
isMemeber = await db.SetContainsAsync(key, 1);
Assert.False(isMemeber);
areMemebers = await db.SetContainsAsync(key, new RedisValue[] { 0, 1, 2 });
Assert.Equal(3, areMemebers.Length);
Assert.True(areMemebers.All(i => !i)); // Check that all the elements are False
}

[Fact]
public void SetIntersectionLength()
{
Expand Down
8 changes: 8 additions & 0 deletions tests/StackExchange.Redis.Tests/WrapperBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,14 @@ public void SetContainsAsync()
mock.Verify(_ => _.SetContainsAsync("prefix:key", "value", CommandFlags.None));
}

[Fact]
public void SetContainsAsync_2()
{
RedisValue[] values = new RedisValue[] { "value1", "value2" };
wrapper.SetContainsAsync("key", values, CommandFlags.None);
mock.Verify(_ => _.SetContainsAsync("prefix:key", values, CommandFlags.None));
}

[Fact]
public void SetIntersectionLengthAsync()
{
Expand Down

0 comments on commit 0e9e698

Please sign in to comment.