Skip to content

Commit

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

Co-authored-by: Nick Craver <[email protected]>
  • Loading branch information
Avital-Fine and NickCraver authored Apr 11, 2022
1 parent c2a7348 commit ba352d2
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 2 deletions.
3 changes: 2 additions & 1 deletion 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 `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078))

## 2.5.61

Expand All @@ -25,7 +26,7 @@

## 2.5.43

- Adds: Bounds checking for `ExponentialRetry` backoff policy ([#1921 by gliljas](https://github.com/StackExchange/StackExchange.Redis/pull/1921))
- Adds: Bounds checking for `ExponentialRetry` backoff policy ([#1921 by gliljas](https://github.com/StackExchange/StackExchange.Redis/pull/1921))
- Adds: `DefaultOptionsProvider` support for endpoint-based defaults configuration ([#1987 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/1987))
- Adds: Envoy proxy support ([#1989 by rkarthick](https://github.com/StackExchange/StackExchange.Redis/pull/1989))
- Performance: When `SUBSCRIBE` is disabled, give proper errors and connect faster ([#2001 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2001))
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 @@ -148,6 +148,7 @@ internal enum RedisCommand
SETRANGE,
SHUTDOWN,
SINTER,
SINTERCARD,
SINTERSTORE,
SISMEMBER,
SLAVEOF,
Expand Down
16 changes: 16 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,22 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>https://redis.io/commands/sismember</remarks>
bool SetContains(RedisKey key, RedisValue value, 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"/>.
/// </para>
/// <para>
/// If the intersection cardinality reaches <paramref name="limit"/> partway through the computation,
/// the algorithm will exit and yield <paramref name="limit"/> as the cardinality.
/// </para>
/// </summary>
/// <param name="keys">The keys of the sets.</param>
/// <param name="limit">The number of elements to check (defaults to 0 and means unlimited).</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>The cardinality (number of elements) of the set, or 0 if key does not exist.</returns>
/// <remarks>https://redis.io/commands/scard</remarks>
long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns the set cardinality (number of elements) of the set stored at key.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,22 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>https://redis.io/commands/sismember</remarks>
Task<bool> SetContainsAsync(RedisKey key, RedisValue value, 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"/>.
/// </para>
/// <para>
/// If the intersection cardinality reaches <paramref name="limit"/> partway through the computation,
/// the algorithm will exit and yield <paramref name="limit"/> as the cardinality.
/// </para>
/// </summary>
/// <param name="keys">The keys of the sets.</param>
/// <param name="limit">The number of elements to check (defaults to 0 and means unlimited).</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>The cardinality (number of elements) of the set, or 0 if key does not exist.</returns>
/// <remarks>https://redis.io/commands/scard</remarks>
Task<long> SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns the set cardinality (number of elements) of the set stored at key.
/// </summary>
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 long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
Inner.SetIntersectionLength(keys, limit, flags);

public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.SetLength(ToInner(key), 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<long> SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
Inner.SetIntersectionLengthAsync(keys, limit, flags);

public Task<long> SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
Inner.SetLengthAsync(ToInner(key), 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.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[]!
StackExchange.Redis.IDatabase.SetMove(StackExchange.Redis.RedisKey source, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
Expand Down Expand Up @@ -763,6 +764,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.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[]!>!
StackExchange.Redis.IDatabaseAsync.SetMoveAsync(StackExchange.Redis.RedisKey source, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<bool>!
Expand Down
32 changes: 32 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 long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
{
var msg = GetSetIntersectionLengthMessage(keys, limit, flags);
return ExecuteSync(msg, ResultProcessor.Int64);
}

public Task<long> SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
{
var msg = GetSetIntersectionLengthMessage(keys, limit, flags);
return ExecuteAsync(msg, ResultProcessor.Int64);
}

public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.SCARD, key);
Expand Down Expand Up @@ -2997,6 +3009,26 @@ private Message GetRestoreMessage(RedisKey key, byte[] value, TimeSpan? expiry,
return Message.Create(Database, flags, RedisCommand.RESTORE, key, pttl, value);
}

private Message GetSetIntersectionLengthMessage(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
{
if (keys == null) throw new ArgumentNullException(nameof(keys));

var values = new RedisValue[1 + keys.Length + (limit > 0 ? 2 : 0)];
int i = 0;
values[i++] = keys.Length;
for (var j = 0; j < keys.Length; j++)
{
values[i++] = keys[j].AsRedisValue();
}
if (limit > 0)
{
values[i++] = RedisLiterals.LIMIT;
values[i] = limit;
}

return Message.Create(Database, flags, RedisCommand.SINTERCARD, values);
}

private Message GetSortedSetAddMessage(RedisKey key, RedisValue member, double score, When when, CommandFlags flags)
{
WhenAlwaysOrExistsOrNotExists(when);
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 SetIntersectionLength()
{
var keys = new RedisKey[] { "key1", "key2" };
wrapper.SetIntersectionLength(keys);
mock.Verify(_ => _.SetIntersectionLength(keys, 0, CommandFlags.None));
}

[Fact]
public void SetLength()
{
Expand Down
54 changes: 53 additions & 1 deletion tests/StackExchange.Redis.Tests/Sets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,66 @@ public class Sets : TestBase
{
public Sets(ITestOutputHelper output, SharedConnectionFixture fixture) : base (output, fixture) { }

[Fact]
public void SetIntersectionLength()
{
using var conn = Create();
Skip.IfBelow(conn, RedisFeatures.v7_0_0_rc1);
var db = conn.GetDatabase();

var key1 = Me() + "1";
db.KeyDelete(key1, CommandFlags.FireAndForget);
db.SetAdd(key1, new RedisValue[] { 0, 1, 2, 3, 4 }, CommandFlags.FireAndForget);
var key2 = Me() + "2";
db.KeyDelete(key2, CommandFlags.FireAndForget);
db.SetAdd(key2, new RedisValue[] { 1, 2, 3, 4, 5 }, CommandFlags.FireAndForget);

Assert.Equal(4, db.SetIntersectionLength(new RedisKey[]{ key1, key2}));
// with limit
Assert.Equal(3, db.SetIntersectionLength(new RedisKey[]{ key1, key2}, 3));

// Missing keys should be 0
var key3 = Me() + "3";
var key4 = Me() + "4";
db.KeyDelete(key3, CommandFlags.FireAndForget);
Assert.Equal(0, db.SetIntersectionLength(new RedisKey[] { key1, key3 }));
Assert.Equal(0, db.SetIntersectionLength(new RedisKey[] { key3, key4 }));
}

[Fact]
public async Task SetIntersectionLengthAsync()
{
using var conn = Create();
Skip.IfBelow(conn, RedisFeatures.v7_0_0_rc1);
var db = conn.GetDatabase();

var key1 = Me() + "1";
db.KeyDelete(key1, CommandFlags.FireAndForget);
db.SetAdd(key1, new RedisValue[] { 0, 1, 2, 3, 4 }, CommandFlags.FireAndForget);
var key2 = Me() + "2";
db.KeyDelete(key2, CommandFlags.FireAndForget);
db.SetAdd(key2, new RedisValue[] { 1, 2, 3, 4, 5 }, CommandFlags.FireAndForget);

Assert.Equal(4, await db.SetIntersectionLengthAsync(new RedisKey[]{ key1, key2}));
// with limit
Assert.Equal(3, await db.SetIntersectionLengthAsync(new RedisKey[]{ key1, key2}, 3));

// Missing keys should be 0
var key3 = Me() + "3";
var key4 = Me() + "4";
db.KeyDelete(key3, CommandFlags.FireAndForget);
Assert.Equal(0, await db.SetIntersectionLengthAsync(new RedisKey[] { key1, key3 }));
Assert.Equal(0, await db.SetIntersectionLengthAsync(new RedisKey[] { key3, key4 }));
}

[Fact]
public void SScan()
{
using (var conn = Create())
{
var server = GetAnyPrimary(conn);

RedisKey key = Me();
var key = Me();
var db = conn.GetDatabase();
int totalUnfiltered = 0, totalFiltered = 0;
for (int i = 1; i < 1001; i++)
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 SetIntersectionLengthAsync()
{
var keys = new RedisKey[] { "key1", "key2" };
wrapper.SetIntersectionLengthAsync(keys);
mock.Verify(_ => _.SetIntersectionLengthAsync(keys, 0, CommandFlags.None));
}

[Fact]
public void SetLengthAsync()
{
Expand Down

0 comments on commit ba352d2

Please sign in to comment.