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 SMISMEMBER #2077

Merged
merged 15 commits into from
Apr 12, 2022
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