Skip to content

Commit 0e9e698

Browse files
Support SMISMEMBER (#2077)
Adds support for https://redis-stack.io/commands/smismember/ (#2055) Co-authored-by: Nick Craver <[email protected]>
1 parent a64f74f commit 0e9e698

File tree

13 files changed

+155
-4
lines changed

13 files changed

+155
-4
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- 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.
1010
- Adds: Support for `COPY` with `.KeyCopy()`/`.KeyCopyAsync()` ([#2064 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2064))
1111
- Adds: Support for `LMOVE` with `.ListMove()`/`.ListMoveAsync()` ([#2065 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2065))
12+
- Adds: Support for `SMISMEMBER` with `.SetContains()`/`.SetContainsAsync()` ([#2077 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2077))
1213
- Adds: Support for `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078))
1314

1415
## 2.5.61

src/StackExchange.Redis/Enums/RedisCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ internal enum RedisCommand
154154
SLAVEOF,
155155
SLOWLOG,
156156
SMEMBERS,
157+
SMISMEMBER,
157158
SMOVE,
158159
SORT,
159160
SPOP,

src/StackExchange.Redis/Interfaces/IDatabase.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,10 +1103,10 @@ public interface IDatabase : IRedis, IDatabaseAsync
11031103
long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None);
11041104

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

1118+
/// <summary>
1119+
/// Returns whether each of <paramref name="values"/> is a member of the set stored at <paramref name="key"/>.
1120+
/// </summary>
1121+
/// <param name="key">The key of the set.</param>
1122+
/// <param name="values">The members to check for.</param>
1123+
/// <param name="flags">The flags to use for this operation.</param>
1124+
/// <returns>
1125+
/// An array of booleans corresponding to <paramref name="values"/>, for each:
1126+
/// <see langword="true"/> if the element is a member of the set.
1127+
/// <see langword="false"/> if the element is not a member of the set, or if key does not exist.
1128+
/// </returns>
1129+
/// <remarks>https://redis.io/commands/smismember</remarks>
1130+
bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None);
1131+
11181132
/// <summary>
11191133
/// <para>
11201134
/// Returns the set cardinality (number of elements) of the intersection between the sets stored at the given <paramref name="keys"/>.

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,10 +1079,10 @@ public interface IDatabaseAsync : IRedisAsync
10791079
Task<long> SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None);
10801080

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

1094+
/// <summary>
1095+
/// Returns whether each of <paramref name="values"/> is a member of the set stored at <paramref name="key"/>.
1096+
/// </summary>
1097+
/// <param name="key">The key of the set.</param>
1098+
/// <param name="values">The members to check for.</param>
1099+
/// <param name="flags">The flags to use for this operation.</param>
1100+
/// <returns>
1101+
/// <see langword="true"/> if the element is a member of the set.
1102+
/// <see langword="false"/> if the element is not a member of the set, or if key does not exist.
1103+
/// </returns>
1104+
/// <remarks>https://redis.io/commands/smismember</remarks>
1105+
Task<bool[]> SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None);
1106+
10941107
/// <summary>
10951108
/// <para>
10961109
/// Returns the set cardinality (number of elements) of the intersection between the sets stored at the given <paramref name="keys"/>.

src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey
294294
public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) =>
295295
Inner.SetContains(ToInner(key), value, flags);
296296

297+
public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) =>
298+
Inner.SetContains(ToInner(key), values, flags);
299+
297300
public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
298301
Inner.SetIntersectionLength(keys, limit, flags);
299302

src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ public Task<RedisValue[]> SetCombineAsync(SetOperation operation, RedisKey first
304304
public Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) =>
305305
Inner.SetContainsAsync(ToInner(key), value, flags);
306306

307+
public Task<bool[]> SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) =>
308+
Inner.SetContainsAsync(ToInner(key), values, flags);
309+
307310
public Task<long> SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
308311
Inner.SetIntersectionLengthAsync(keys, limit, flags);
309312

src/StackExchange.Redis/PublicAPI.Shipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ StackExchange.Redis.IDatabase.SetCombine(StackExchange.Redis.SetOperation operat
573573
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
574574
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
575575
StackExchange.Redis.IDatabase.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
576+
StackExchange.Redis.IDatabase.SetContains(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool[]!
576577
StackExchange.Redis.IDatabase.SetIntersectionLength(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
577578
StackExchange.Redis.IDatabase.SetLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
578579
StackExchange.Redis.IDatabase.SetMembers(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
@@ -764,6 +765,7 @@ StackExchange.Redis.IDatabaseAsync.SetCombineAndStoreAsync(StackExchange.Redis.S
764765
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[]!>!
765766
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[]!>!
766767
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>!
768+
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[]!>!
767769
StackExchange.Redis.IDatabaseAsync.SetIntersectionLengthAsync(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
768770
StackExchange.Redis.IDatabaseAsync.SetLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
769771
StackExchange.Redis.IDatabaseAsync.SetMembersAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!

src/StackExchange.Redis/RawResult.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ internal bool GetBoolean()
262262
[MethodImpl(MethodImplOptions.AggressiveInlining)]
263263
internal string?[]? GetItemsAsStrings() => this.ToArray<string?>((in RawResult x) => (string?)x.AsRedisValue());
264264

265+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
266+
internal bool[]? GetItemsAsBooleans() => this.ToArray<bool>((in RawResult x) => (bool)x.AsRedisValue());
267+
265268
internal GeoPosition? GetItemsAsGeoPosition()
266269
{
267270
var items = GetItems();

src/StackExchange.Redis/RedisDatabase.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,18 @@ public Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags
13831383
return ExecuteAsync(msg, ResultProcessor.Boolean);
13841384
}
13851385

1386+
public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
1387+
{
1388+
var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values);
1389+
return ExecuteSync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty<bool>());
1390+
}
1391+
1392+
public Task<bool[]> SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
1393+
{
1394+
var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values);
1395+
return ExecuteAsync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty<bool>());
1396+
}
1397+
13861398
public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
13871399
{
13881400
var msg = GetSetIntersectionLengthMessage(keys, limit, flags);

src/StackExchange.Redis/ResultProcessor.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public static readonly ResultProcessor<RedisValue[]>
8282
public static readonly ResultProcessor<string?[]>
8383
StringArray = new StringArrayProcessor();
8484

85+
public static readonly ResultProcessor<bool[]>
86+
BooleanArray = new BooleanArrayProcessor();
87+
8588
public static readonly ResultProcessor<GeoPosition?[]>
8689
RedisGeoPositionArray = new RedisValueGeoPositionArrayProcessor();
8790
public static readonly ResultProcessor<GeoPosition?>
@@ -1258,6 +1261,20 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
12581261
}
12591262
}
12601263

1264+
private sealed class BooleanArrayProcessor : ResultProcessor<bool[]>
1265+
{
1266+
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
1267+
{
1268+
if (result.Type == ResultType.MultiBulk && !result.IsNull)
1269+
{
1270+
var arr = result.GetItemsAsBooleans()!;
1271+
SetResult(message, arr);
1272+
return true;
1273+
}
1274+
return false;
1275+
}
1276+
}
1277+
12611278
private sealed class RedisValueGeoPositionProcessor : ResultProcessor<GeoPosition?>
12621279
{
12631280
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)

tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,14 @@ public void SetContains()
603603
mock.Verify(_ => _.SetContains("prefix:key", "value", CommandFlags.None));
604604
}
605605

606+
[Fact]
607+
public void SetContains_2()
608+
{
609+
RedisValue[] values = new RedisValue[] { "value1", "value2" };
610+
wrapper.SetContains("key", values, CommandFlags.None);
611+
mock.Verify(_ => _.SetContains("prefix:key", values, CommandFlags.None));
612+
}
613+
606614
[Fact]
607615
public void SetIntersectionLength()
608616
{

tests/StackExchange.Redis.Tests/Sets.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,72 @@ public class Sets : TestBase
1111
{
1212
public Sets(ITestOutputHelper output, SharedConnectionFixture fixture) : base (output, fixture) { }
1313

14+
[Fact]
15+
public void SetContains()
16+
{
17+
using var conn = Create();
18+
Skip.IfBelow(conn, RedisFeatures.v6_2_0);
19+
20+
var key = Me();
21+
var db = conn.GetDatabase();
22+
db.KeyDelete(key);
23+
for (int i = 1; i < 1001; i++)
24+
{
25+
db.SetAdd(key, i, CommandFlags.FireAndForget);
26+
}
27+
28+
// Single member
29+
var isMemeber = db.SetContains(key, 1);
30+
Assert.True(isMemeber);
31+
32+
// Multi members
33+
var areMemebers = db.SetContains(key, new RedisValue[] { 0, 1, 2 });
34+
Assert.Equal(3, areMemebers.Length);
35+
Assert.False(areMemebers[0]);
36+
Assert.True(areMemebers[1]);
37+
38+
// key not exists
39+
db.KeyDelete(key);
40+
isMemeber = db.SetContains(key, 1);
41+
Assert.False(isMemeber);
42+
areMemebers = db.SetContains(key, new RedisValue[] { 0, 1, 2 });
43+
Assert.Equal(3, areMemebers.Length);
44+
Assert.True(areMemebers.All(i => !i)); // Check that all the elements are False
45+
}
46+
47+
[Fact]
48+
public async Task SetContainsAsync()
49+
{
50+
using var conn = Create();
51+
Skip.IfBelow(conn, RedisFeatures.v6_2_0);
52+
53+
var key = Me();
54+
var db = conn.GetDatabase();
55+
await db.KeyDeleteAsync(key);
56+
for (int i = 1; i < 1001; i++)
57+
{
58+
db.SetAdd(key, i, CommandFlags.FireAndForget);
59+
}
60+
61+
// Single member
62+
var isMemeber = await db.SetContainsAsync(key, 1);
63+
Assert.True(isMemeber);
64+
65+
// Multi members
66+
var areMemebers = await db.SetContainsAsync(key, new RedisValue[] { 0, 1, 2 });
67+
Assert.Equal(3, areMemebers.Length);
68+
Assert.False(areMemebers[0]);
69+
Assert.True(areMemebers[1]);
70+
71+
// key not exists
72+
await db.KeyDeleteAsync(key);
73+
isMemeber = await db.SetContainsAsync(key, 1);
74+
Assert.False(isMemeber);
75+
areMemebers = await db.SetContainsAsync(key, new RedisValue[] { 0, 1, 2 });
76+
Assert.Equal(3, areMemebers.Length);
77+
Assert.True(areMemebers.All(i => !i)); // Check that all the elements are False
78+
}
79+
1480
[Fact]
1581
public void SetIntersectionLength()
1682
{

tests/StackExchange.Redis.Tests/WrapperBaseTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,14 @@ public void SetContainsAsync()
563563
mock.Verify(_ => _.SetContainsAsync("prefix:key", "value", CommandFlags.None));
564564
}
565565

566+
[Fact]
567+
public void SetContainsAsync_2()
568+
{
569+
RedisValue[] values = new RedisValue[] { "value1", "value2" };
570+
wrapper.SetContainsAsync("key", values, CommandFlags.None);
571+
mock.Verify(_ => _.SetContainsAsync("prefix:key", values, CommandFlags.None));
572+
}
573+
566574
[Fact]
567575
public void SetIntersectionLengthAsync()
568576
{

0 commit comments

Comments
 (0)